From 53a0ad012556cbd202bcd00953820e1245416a4d Mon Sep 17 00:00:00 2001 From: restitux Date: Sun, 25 Jan 2026 01:29:28 -0700 Subject: [PATCH 1/4] gui: fix channel ordering --- gui/src/app.rs | 133 ++++++++++++++++++++++++++++++++++++++++++++----- gui/src/lib.rs | 39 ++------------- 2 files changed, 125 insertions(+), 47 deletions(-) diff --git a/gui/src/app.rs b/gui/src/app.rs index c8cb979..0d8aa3f 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; use mime_guess::Mime; use mumble_web2_common::{ClientConfig, ServerStatus}; use ordermap::OrderSet; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::imp; @@ -54,14 +54,6 @@ pub enum Command { use Command::*; use ConnectionState::*; -#[derive(Default)] -pub struct ChannelState { - pub name: String, - pub children: OrderSet, - pub users: OrderSet, - pub parent: Option, -} - #[derive(Default)] pub struct UserState { pub name: String, @@ -94,8 +86,123 @@ pub struct Chat { } #[derive(Default)] -pub struct ServerState { +pub struct ChannelState { + pub name: String, + pub children: OrderSet, + pub users: OrderSet, + pub parent: Option, + pub position: i32, +} + +impl ChannelState { + pub fn update_from_ChannelState( + &mut self, + channel_state: &mumble_protocol::control::msgs::ChannelState, + ) { + if channel_state.has_position() { + self.position = channel_state.get_position(); + } + if channel_state.has_parent() { + self.parent = Some(channel_state.get_parent()); + } + if channel_state.has_name() { + self.name = channel_state.get_name().to_string(); + } + } +} + +#[derive(Default)] +pub struct ChannelsState { pub channels: HashMap, +} + +impl ChannelsState { + pub fn update_from_ChannelState( + &mut self, + channel_state: &mumble_protocol::control::msgs::ChannelState, + ) { + self.channels + .entry(channel_state.get_channel_id()) + .or_default() + .update_from_ChannelState(channel_state); + + self.update_channel_parents(); + } + + pub fn update_from_ChannelRemove( + &mut self, + channel_remove: &mumble_protocol::control::msgs::ChannelRemove, + ) { + self.channels.remove(&channel_remove.get_channel_id()); + self.update_channel_parents(); + } + + pub fn update_channel_parents(&mut self) { + // Zero out existing children + for (id, state) in self.channels.iter_mut() { + state.children = OrderSet::new(); + } + + let mut channels_updated: HashSet = HashSet::new(); + channels_updated.insert(0); // insert root channel + + let mut channels_to_parse: Vec<(ChannelId, ChannelId, i32)> = Vec::new(); + for (id, state) in self.channels.iter() { + // Skip root channel + if *id == 0 { + continue; + } + + // Ignore channels with no parent + let Some(parent_id) = state.parent else { + continue; + }; + + // If a channel has a parent that we haven't gotten a channel + // state packet for, ignore it + if !self.channels.contains_key(&parent_id) { + continue; + } + + channels_to_parse.push((*id, parent_id, state.position)); + } + + let mut channel_positions = HashMap::new(); + for (id, state) in self.channels.iter() { + channel_positions.insert(*id, state.position); + } + + while channels_updated.len() < channels_to_parse.len() { + for (id, parent_id, position) in &channels_to_parse { + if channels_updated.contains(&id) { + continue; + } + + // Skip channels whose parent we haven't seen yet + if !channels_updated.contains(&parent_id) { + continue; + } + + let parent = self.channels.get_mut(&parent_id).unwrap(); + + let mut after_index = 0; + for (index, child_id) in parent.children.iter().enumerate() { + after_index = index; + if channel_positions[child_id] > *position { + continue; + } + } + parent.children.insert_before(after_index, *id); + + channels_updated.insert(*id); + } + } + } +} + +#[derive(Default)] +pub struct ServerState { + pub channels_state: ChannelsState, pub users: HashMap, pub chat: Vec, pub session: Option, @@ -182,7 +289,7 @@ pub fn Channel(id: ChannelId) -> Element { let net: Coroutine = use_coroutine_handle(); let server = STATE.server.read(); let user = server.session.unwrap(); - let Some(state) = server.channels.get(&id) else { + let Some(state) = server.channels_state.channels.get(&id) else { return rsx!("missing channel {id}"); }; @@ -336,7 +443,7 @@ pub fn ControlView(config: Resource) -> Element { return rsx!(); }; - let current_channel_name = server.channels[&channel].name.clone(); + let current_channel_name = server.channels_state.channels[&channel].name.clone(); let proxy_url = config .read_unchecked() @@ -528,7 +635,7 @@ pub fn ServerView(config: Resource) -> Element { class: "server_grid", div { class: "server_channel_box", - for (id, state) in server.channels.iter() { + for (id, state) in server.channels_state.channels.iter() { if state.parent.is_none() { Channel { id: *id } } diff --git a/gui/src/lib.rs b/gui/src/lib.rs index d21a432..edf1096 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -335,41 +335,11 @@ fn accept_packet( } ControlPacket::ChannelState(u) => { let mut server = STATE.server.write(); - let id = u.get_channel_id(); - - let state = server.channels.entry(id).or_default(); - let new_parent = if u.has_parent() { - if let Some(parent) = state.parent.and_then(|p| server.channels.get_mut(&p)) { - parent.children.remove(&id); - } - - let parent_id = u.get_parent(); - let parent = server.channels.entry(parent_id).or_default(); - if u.has_position() && u.get_position() as usize <= parent.children.len() { - // TODO: what if positions are received out of order? we need to sort afterwards? - parent.children.insert_before(u.get_position() as usize, id); - } else { - parent.children.insert(id); - } - Some(parent_id) - } else { - None - }; - - let state = server.channels.entry(id).or_default(); - state.parent = new_parent; - if u.has_name() { - state.name = u.get_name().to_string(); - } + server.channels_state.update_from_ChannelState(&u); } ControlPacket::ChannelRemove(u) => { let mut server = STATE.server.write(); - let id = u.get_channel_id(); - if let Some(channel) = server.channels.remove(&id) { - if let Some(parent) = channel.parent.and_then(|p| server.channels.get_mut(&p)) { - parent.children.remove(&id); - } - } + server.channels_state.update_from_ChannelRemove(&u); } ControlPacket::UserState(u) => { let mut server = STATE.server.write(); @@ -381,12 +351,13 @@ fn accept_packet( let state = state_entry.or_default(); // the server might now send a channel_id if the user is in channel=0 if u.has_channel_id() || new { - if let Some(parent) = server.channels.get_mut(&state.channel) { + if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) { parent.users.remove(&id); } let channel_id = u.get_channel_id(); server + .channels_state .channels .entry(channel_id) .or_default() @@ -418,7 +389,7 @@ fn accept_packet( let mut server = STATE.server.write(); let id = u.get_session(); if let Some(state) = server.users.remove(&id) { - if let Some(parent) = server.channels.get_mut(&state.channel) { + if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) { parent.users.remove(&id); } } -- 2.52.0 From 8fcf780beb09ff3450833f87d7759445b893e919 Mon Sep 17 00:00:00 2001 From: restitux Date: Sun, 25 Jan 2026 02:19:27 -0700 Subject: [PATCH 2/4] cleanup and fix sorting --- gui/src/app.rs | 62 ++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/gui/src/app.rs b/gui/src/app.rs index 0d8aa3f..7893b17 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -139,22 +139,15 @@ impl ChannelsState { pub fn update_channel_parents(&mut self) { // Zero out existing children - for (id, state) in self.channels.iter_mut() { - state.children = OrderSet::new(); + for state in self.channels.values_mut() { + state.children.clear(); } - let mut channels_updated: HashSet = HashSet::new(); - channels_updated.insert(0); // insert root channel - - let mut channels_to_parse: Vec<(ChannelId, ChannelId, i32)> = Vec::new(); + let mut to_sort: Vec<(ChannelId, Option, i32, String)> = Vec::new(); for (id, state) in self.channels.iter() { - // Skip root channel - if *id == 0 { - continue; - } - - // Ignore channels with no parent + // Hnadle channels with no parent (the root channel) let Some(parent_id) = state.parent else { + to_sort.push((*id, None, 0, state.name.clone())); continue; }; @@ -164,37 +157,42 @@ impl ChannelsState { continue; } - channels_to_parse.push((*id, parent_id, state.position)); + to_sort.push((*id, Some(parent_id), state.position, state.name.clone())); } - let mut channel_positions = HashMap::new(); - for (id, state) in self.channels.iter() { - channel_positions.insert(*id, state.position); - } + let pos_name: HashMap = self + .channels + .iter() + .map(|(&id, state)| (id, (state.position, state.name.clone()))) + .collect(); - while channels_updated.len() < channels_to_parse.len() { - for (id, parent_id, position) in &channels_to_parse { - if channels_updated.contains(&id) { - continue; - } - - // Skip channels whose parent we haven't seen yet - if !channels_updated.contains(&parent_id) { + let mut updated: HashSet = HashSet::new(); + + while updated.len() < to_sort.len() { + for &(id, ref parent_id, position, ref name) in &to_sort { + let Some(parent_id) = parent_id else { + updated.insert(id); + continue; + }; + + if updated.contains(&id) || !updated.contains(&parent_id) { continue; } + // Unwrap should never fail here since we pre filter let parent = self.channels.get_mut(&parent_id).unwrap(); - let mut after_index = 0; - for (index, child_id) in parent.children.iter().enumerate() { - after_index = index; - if channel_positions[child_id] > *position { - continue; + let mut insert_index = parent.children.len(); + for (i, &child) in parent.children.iter().enumerate() { + let (p, ref n) = pos_name[&child]; + if (position == p && name < n) || p > position { + insert_index = i; + break; } } - parent.children.insert_before(after_index, *id); - channels_updated.insert(*id); + parent.children.insert_before(insert_index, id); + updated.insert(id); } } } -- 2.52.0 From 61eea0a858475cfb786b8597508f5e4637c576c1 Mon Sep 17 00:00:00 2001 From: restitux Date: Sun, 25 Jan 2026 11:35:24 -0700 Subject: [PATCH 3/4] change to snake case --- gui/src/app.rs | 8 ++++---- gui/src/lib.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gui/src/app.rs b/gui/src/app.rs index 7893b17..5d36e38 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -95,7 +95,7 @@ pub struct ChannelState { } impl ChannelState { - pub fn update_from_ChannelState( + pub fn update_from_channel_state( &mut self, channel_state: &mumble_protocol::control::msgs::ChannelState, ) { @@ -117,19 +117,19 @@ pub struct ChannelsState { } impl ChannelsState { - pub fn update_from_ChannelState( + pub fn update_from_channel_state( &mut self, channel_state: &mumble_protocol::control::msgs::ChannelState, ) { self.channels .entry(channel_state.get_channel_id()) .or_default() - .update_from_ChannelState(channel_state); + .update_from_channel_state(channel_state); self.update_channel_parents(); } - pub fn update_from_ChannelRemove( + pub fn update_from_channel_remove( &mut self, channel_remove: &mumble_protocol::control::msgs::ChannelRemove, ) { diff --git a/gui/src/lib.rs b/gui/src/lib.rs index edf1096..b44e5fc 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -335,11 +335,11 @@ fn accept_packet( } ControlPacket::ChannelState(u) => { let mut server = STATE.server.write(); - server.channels_state.update_from_ChannelState(&u); + server.channels_state.update_from_channel_state(&u); } ControlPacket::ChannelRemove(u) => { let mut server = STATE.server.write(); - server.channels_state.update_from_ChannelRemove(&u); + server.channels_state.update_from_channel_remove(&u); } ControlPacket::UserState(u) => { let mut server = STATE.server.write(); -- 2.52.0 From b22bdff9497c9f81e703ba5fc7d73b8d7b5b6c22 Mon Sep 17 00:00:00 2001 From: restitux Date: Sun, 25 Jan 2026 11:35:48 -0700 Subject: [PATCH 4/4] typo fix --- gui/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/src/app.rs b/gui/src/app.rs index 5d36e38..0a865d3 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -145,7 +145,7 @@ impl ChannelsState { let mut to_sort: Vec<(ChannelId, Option, i32, String)> = Vec::new(); for (id, state) in self.channels.iter() { - // Hnadle channels with no parent (the root channel) + // Handle channels with no parent (the root channel) let Some(parent_id) = state.parent else { to_sort.push((*id, None, 0, state.name.clone())); continue; -- 2.52.0