Phase 2: E2EE for DM + Slint migration + dead code cleanup

- E2EE crypto (src/e2ee.rs) with X25519 key derivation from Ed25519,
  HKDF shared secret, ChaCha20-Poly1305 encrypt/decrypt
- E2EE net layer: PID 0x0071-0x0074, JSON payloads, dispatch, session handlers
- E2EE UI: lock icons in DM header and sidebar
- Slint migration: all .slint files, bridge.rs, update.rs, main.rs
- Removed egui UI (~50 files), cleaned all dead code
- Clean cargo clippy -- -D warnings
This commit is contained in:
loki5512344 2026-07-14 13:25:15 +02:00
parent f361b037cb
commit 03d94e0465
Signed by: boba
GPG key ID: 253067914055423B
83 changed files with 5055 additions and 9202 deletions

View file

@ -1,7 +1,6 @@
use std::collections::{HashMap, HashSet};
use std::time::Instant;
use std::collections::HashMap;
use super::models::{default_bookmarks, now_utc_hms};
use super::models::default_bookmarks;
use super::types::{ConnState, UiState, VoiceUiState};
impl Default for UiState {
@ -16,131 +15,42 @@ impl Default for UiState {
active_channel: None,
messages: Default::default(),
chat_input: String::new(),
editing_message_id: None,
replying_to: None,
context_menu_message_id: None,
scroll_bottom: false,
voice: VoiceUiState::Off,
voice_joined_at: None,
user_names: Default::default(),
server_channel: None,
rtt_ms: None,
settings_open: false,
settings_page: Default::default(),
mic_enabled: true,
deafen: false,
noise_suppress: true,
echo_cancel: true,
vad_mode: 0,
vad_threshold: 40.0,
voice_bitrate: 64.0,
packet_interval: 1,
mic_device: 0,
input_volume: 80.0,
input_device_labels: vec!["Default".into()],
input_device_metas: Vec::new(),
output_device_labels: vec!["Default".into()],
output_device_metas: Vec::new(),
last_device_scan: None,
output_device: 0,
output_volume: 90.0,
jitter_size: 1,
adaptive_buffer: true,
overlay_enabled: false,
overlay_speakers: true,
overlay_latency: true,
relay_enabled: true,
relay_address: "relay.nightcore.lnex".into(),
auto_relay: true,
force_relay: false,
udp_port: "7700".into(),
auto_reconnect: true,
ui_scale: 100.0,
kb_ptt: "mouse4".into(),
kb_mute: "ctrl+m".into(),
kb_deafen: "ctrl+d".into(),
kb_overlay: "ctrl+shift+o".into(),
log_level: 0,
log_voice_packets: false,
show_packet_stats: false,
disable_encryption: false,
guilds: Vec::new(),
active_guild_id: None,
presences: Default::default(),
presence_status: Some("online".into()),
custom_status: None,
activity_type: None,
activity_text: None,
friends: Vec::new(),
pending_friend_requests: Vec::new(),
blocked_users: Vec::new(),
block_input: None,
block_list_fetched_at: None,
dm_conversations: Vec::new(),
dm_messages: Default::default(),
active_dm_id: None,
new_dm_input: None,
add_server_open: false,
add_server_input: String::new(),
invite_accept_open: false,
invite_accept_code: String::new(),
create_guild_open: false,
create_guild_input: String::new(),
create_channel_open: false,
create_channel_input: String::new(),
create_channel_kind: "text".into(),
vault_set_open: false,
vault_remove_open: false,
vault_passphrase_input: String::new(),
vault_passphrase_confirm: String::new(),
vault_error: None,
export_keyfile_open: false,
export_keyfile_pass: String::new(),
export_keyfile_confirm: String::new(),
export_keyfile_output: String::new(),
export_keyfile_error: None,
import_keyfile_open: false,
import_keyfile_input: String::new(),
import_keyfile_pass: String::new(),
import_keyfile_error: None,
audit_log_open: false,
audit_log_guild_id: None,
audit_log_entries: Vec::new(),
guild_members_open: false,
guild_member_list_guild_id: None,
guild_member_list: Vec::new(),
guild_role_list_guild_id: None,
guild_role_list: Vec::new(),
add_friend_open: false,
add_friend_input: String::new(),
dm_search_query: String::new(),
user_colors: HashMap::new(),
per_user_volumes: Default::default(),
kb_listening: None,
private_mode: false,
collapsed_categories: HashSet::new(),
startup_time_str: now_utc_hms(),
typing_users: HashMap::new(),
last_typing_send: Instant::now(),
read_receipts: HashMap::new(),
friends_tab: "all".into(),
local_speaking: false,

View file

@ -2,57 +2,12 @@ mod default;
mod models;
mod types;
pub use models::{
Channel, ChatMessage, DmConversation, FriendState, GuildState, NodeBookmark, SettingsPage,
};
pub use types::{ConnState, TYPING_TIMEOUT, UiState, VoiceUiState};
use std::time::{Duration, Instant};
pub use models::{Channel, ChatMessage, DmConversation, FriendState, GuildState, NodeBookmark};
pub use types::{ConnState, UiState, VoiceUiState};
use models::bookmark_label_from_address;
impl UiState {
pub fn refresh_audio_devices(&mut self) {
let now = Instant::now();
if let Some(last) = self.last_device_scan
&& now.duration_since(last) < Duration::from_millis(2500)
{
return;
}
self.last_device_scan = Some(now);
match vnox_client::audio::devices::enumerate() {
Ok(lists) => {
self.input_device_labels = lists.input_labels;
self.input_device_metas = lists.input_devices;
self.output_device_labels = lists.output_labels;
self.output_device_metas = lists.output_devices;
}
Err(e) => {
tracing::warn!("audio device enumeration: {e}");
}
}
if self.input_device_labels.is_empty() {
self.input_device_labels.push("Default".into());
}
if self.output_device_labels.is_empty() {
self.output_device_labels.push("Default".into());
}
self.mic_device = self
.mic_device
.min(self.input_device_labels.len().saturating_sub(1));
self.output_device = self
.output_device
.min(self.output_device_labels.len().saturating_sub(1));
}
pub fn force_refresh_audio_devices(&mut self) {
self.last_device_scan = None;
self.refresh_audio_devices();
}
pub fn remove_bookmark(&mut self, index: usize) {
if index < self.bookmarks.len() {
self.bookmarks.remove(index);
@ -73,11 +28,6 @@ impl UiState {
true
}
pub fn voice_elapsed(&self) -> Option<String> {
let secs = self.voice_joined_at?.elapsed().as_secs();
Some(format!("{}:{:02}", secs / 60, secs % 60))
}
pub fn nick_for<'a>(&'a self, sender_id: &'a str) -> &'a str {
self.user_names
.get(sender_id)
@ -85,12 +35,6 @@ impl UiState {
.unwrap_or(sender_id)
}
pub fn active_voice_channel(&self) -> Option<&Channel> {
self.channels
.iter()
.find(|c| matches!(&self.voice, VoiceUiState::Active { channel } if channel == &c.id))
}
pub fn seed_default_channels(&mut self) {
if !self.channels.is_empty() {
return;

View file

@ -1,19 +1,5 @@
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum SettingsPage {
Identity,
#[default]
Voice,
AudioOutput,
Network,
Overlay,
Appearance,
Keybinds,
Plugins,
Advanced,
}
#[derive(Debug, Clone)]
pub struct Channel {
pub id: String,
@ -38,16 +24,17 @@ pub struct ChatMessage {
#[derive(Debug, Clone)]
pub struct DmConversation {
pub dm_id: String,
pub other_user_id: String,
pub other_nickname: String,
pub unread_count: u32,
pub e2ee_enabled: bool,
pub e2ee_peer_public_key: Option<Vec<u8>>,
pub e2ee_shared_secret: Option<[u8; 32]>,
}
#[derive(Debug, Clone)]
pub struct GuildState {
pub guild_id: String,
pub name: String,
pub owner_id: String,
pub member_count: u32,
}
@ -55,8 +42,6 @@ pub struct GuildState {
pub struct FriendState {
pub user_id: String,
pub nickname: String,
pub status: String,
pub since: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -79,14 +64,3 @@ pub(crate) fn bookmark_label_from_address(address: &str) -> String {
.take(12)
.collect()
}
pub(crate) fn now_utc_hms() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let h = (secs % 86400) / 3600;
let m = (secs % 3600) / 60;
let s = secs % 60;
format!("{h:02}:{m:02}:{s:02}")
}

View file

@ -1,7 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::time::{Duration, Instant};
pub const TYPING_TIMEOUT: Duration = Duration::from_secs(4);
use std::collections::HashMap;
use std::time::Instant;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum VoiceUiState {
@ -37,169 +35,54 @@ pub struct UiState {
pub active_channel: Option<String>,
pub messages: HashMap<String, Vec<super::ChatMessage>>,
pub chat_input: String,
pub editing_message_id: Option<String>,
/// When set, the next sent message will be a reply to this message_id.
pub replying_to: Option<(String, String)>, // (message_id, sender_id)
/// Message currently shown in the context menu (right-click).
pub context_menu_message_id: Option<String>,
pub scroll_bottom: bool,
pub voice: VoiceUiState,
pub voice_joined_at: Option<Instant>,
pub user_names: HashMap<String, String>,
pub server_channel: Option<String>,
pub rtt_ms: Option<u32>,
pub settings_open: bool,
pub settings_page: super::SettingsPage,
pub mic_enabled: bool,
pub deafen: bool,
pub noise_suppress: bool,
pub echo_cancel: bool,
pub vad_mode: usize,
pub vad_threshold: f32,
pub voice_bitrate: f32,
pub packet_interval: usize,
pub mic_device: usize,
pub input_volume: f32,
pub input_device_labels: Vec<String>,
pub output_device_labels: Vec<String>,
pub input_device_metas: Vec<vnox_client::audio::devices::DeviceLabel>,
pub output_device_metas: Vec<vnox_client::audio::devices::DeviceLabel>,
pub last_device_scan: Option<Instant>,
pub output_device: usize,
pub output_volume: f32,
pub jitter_size: usize,
pub adaptive_buffer: bool,
pub overlay_enabled: bool,
pub overlay_speakers: bool,
pub overlay_latency: bool,
pub relay_enabled: bool,
pub relay_address: String,
pub auto_relay: bool,
pub force_relay: bool,
pub udp_port: String,
pub auto_reconnect: bool,
pub ui_scale: f32,
pub kb_ptt: String,
pub kb_mute: String,
pub kb_deafen: String,
pub kb_overlay: String,
pub log_level: usize,
pub log_voice_packets: bool,
pub show_packet_stats: bool,
pub disable_encryption: bool,
pub private_mode: bool,
pub guilds: Vec<super::GuildState>,
pub active_guild_id: Option<String>,
pub presences: HashMap<String, crate::net::PresenceInfo>,
pub presence_status: Option<String>,
/// Free-form custom status text ("making tea").
pub custom_status: Option<String>,
/// Activity type ("playing", "listening", "watching", "streaming").
pub activity_type: Option<String>,
/// Activity text (e.g. "Counter-Strike 2").
pub activity_text: Option<String>,
pub friends: Vec<super::FriendState>,
pub pending_friend_requests: Vec<super::FriendState>,
/// Blocked user IDs.
pub blocked_users: Vec<String>,
/// Input buffer for the block-user text field.
pub block_input: Option<String>,
/// When the blocked list was last fetched from the server.
pub block_list_fetched_at: Option<Instant>,
pub dm_conversations: Vec<super::DmConversation>,
pub dm_messages: HashMap<String, Vec<super::ChatMessage>>,
pub active_dm_id: Option<String>,
pub new_dm_input: Option<String>,
pub dm_search_query: String,
pub user_colors: HashMap<String, eframe::egui::Color32>,
pub per_user_volumes: HashMap<String, f32>,
pub add_server_open: bool,
pub add_server_input: String,
pub invite_accept_open: bool,
pub invite_accept_code: String,
pub create_guild_open: bool,
pub create_guild_input: String,
/// "Create a Channel" popup state.
pub create_channel_open: bool,
pub create_channel_input: String,
pub create_channel_kind: String,
/// Vault UI state — set/remove passphrase modals.
pub vault_set_open: bool,
pub vault_remove_open: bool,
pub vault_passphrase_input: String,
pub vault_passphrase_confirm: String,
pub vault_error: Option<String>,
/// Export/import keyfile UI state.
pub export_keyfile_open: bool,
pub export_keyfile_pass: String,
pub export_keyfile_confirm: String,
pub export_keyfile_output: String,
pub export_keyfile_error: Option<String>,
pub import_keyfile_open: bool,
pub import_keyfile_input: String,
pub import_keyfile_pass: String,
pub import_keyfile_error: Option<String>,
/// Audit log viewer state.
pub audit_log_open: bool,
pub audit_log_guild_id: Option<String>,
pub audit_log_entries: Vec<vnox_client::net::payloads::AuditLogEntryPayload>,
/// Guild member list viewer state.
pub guild_members_open: bool,
pub guild_member_list_guild_id: Option<String>,
pub guild_member_list: Vec<vnox_client::net::payloads::GuildMemberInfoPayload>,
/// Guild role list state.
pub guild_role_list_guild_id: Option<String>,
pub guild_role_list: Vec<vnox_client::net::payloads::GuildRoleInfoPayload>,
pub add_friend_open: bool,
pub add_friend_input: String,
pub kb_listening: Option<String>,
pub collapsed_categories: HashSet<String>,
pub startup_time_str: String,
pub user_colors: HashMap<String, String>,
pub typing_users: HashMap<String, Vec<(String, Instant)>>,
pub last_typing_send: Instant,
pub read_receipts: HashMap<String, HashMap<String, String>>,
/// Active tab in the Friends panel ("online" / "all" / "pending" / "blocked").
pub friends_tab: String,
/// True when local user is transmitting voice (PTT held or VAD above threshold).
pub local_speaking: bool,
/// True when a remote peer's voice packet arrived in the last ~500ms.
pub remote_speaking: bool,
/// Most recently received voice-packet sender's user_id (hex pubkey).
/// Empty when no remote user has spoken recently.
pub last_remote_speaker_id: String,
/// UNIX millisecond timestamp of the last voice packet from any remote peer.
pub last_voice_packet_ms: u64,
}