system settings
git clone https://git.lucas.co/cce-system-interface.git
audio: box dynamic controls in Vec to avoid dangling pointer crash on reallocation
src/main.rs | 8 ++++----
src/pages/audio.rs | 28 ++++++++++++++--------------
src/pages/layout.rs | 26 ++++++++++++++++++++++++++
3 files changed, 44 insertions(+), 18 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index e77d5ee..d80d765 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1088,16 +1088,16 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
link_parent_child(page_root, &mut self.page_sec_containers[1], &mut self.ui_context);
for sb in &mut self.app.audio.sink_spinboxes {
- link_parent_child(&mut self.page_sec_containers[0], sb, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[0], &mut **sb, &mut self.ui_context);
}
for sb in &mut self.app.audio.source_spinboxes {
- link_parent_child(&mut self.page_sec_containers[1], sb, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[1], &mut **sb, &mut self.ui_context);
}
for slider in &mut self.app.audio.sink_sliders {
- link_parent_child(&mut self.page_sec_containers[0], slider, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[0], &mut **slider, &mut self.ui_context);
}
for slider in &mut self.app.audio.source_sliders {
- link_parent_child(&mut self.page_sec_containers[1], slider, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[1], &mut **slider, &mut self.ui_context);
}
}
Page::Display => {
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 0cd8f92..1364dad 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -25,10 +25,10 @@ pub struct AudioState {
pub loaded: bool,
pub sinks: Vec<AudioSink>,
pub sources: Vec<AudioSource>,
- pub sink_spinboxes: Vec<Spinbox>,
- pub source_spinboxes: Vec<Spinbox>,
- pub sink_sliders: Vec<Slider>,
- pub source_sliders: Vec<Slider>,
+ pub sink_spinboxes: Vec<Box<Spinbox>>,
+ pub source_spinboxes: Vec<Box<Spinbox>>,
+ pub sink_sliders: Vec<Box<Slider>>,
+ pub source_sliders: Vec<Box<Slider>>,
}
#[derive(Debug, Clone)]
@@ -260,9 +260,9 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
state.sink_sliders[idx].set_value(sink.volume);
let slider_x = subsec.ax(bar_x);
- let label_h = cce_ui::widget::label_offset(&state.sink_sliders[idx]);
+ let label_h = cce_ui::widget::label_offset(&*state.sink_sliders[idx]);
let slider_h = cce_ui::layout::slider_height() + label_h;
- render_widget(subsec.pc, &mut state.sink_sliders[idx], slider_x, yt, bar_w, slider_h, ctx);
+ render_widget(subsec.pc, &mut *state.sink_sliders[idx], slider_x, yt, bar_w, slider_h, ctx);
let row_y = subsec.ay() + slider_h + 8.0;
let sb_w = 100.0;
@@ -274,7 +274,7 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
state.sink_spinboxes[idx].value = (sink.volume * 100.0).round() as i32;
state.sink_spinboxes[idx].set_row_rect(row_rect_x, subsec.cw - 16.0);
let sb_x = subsec.ax(bar_x);
- render_widget(subsec.pc, &mut state.sink_spinboxes[idx], sb_x, row_y, sb_w, sb_h, ctx);
+ render_widget(subsec.pc, &mut *state.sink_spinboxes[idx], sb_x, row_y, sb_w, sb_h, ctx);
let mute_label = if sink.muted { "Unmute" } else { "Mute" };
let mute_col = if sink.muted { MUTED_BG } else { BTN_INACTIVE };
@@ -328,9 +328,9 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
state.source_sliders[idx].set_value(src.volume);
let slider_x = subsec.ax(bar_x);
- let label_h = cce_ui::widget::label_offset(&state.source_sliders[idx]);
+ let label_h = cce_ui::widget::label_offset(&*state.source_sliders[idx]);
let slider_h = cce_ui::layout::slider_height() + label_h;
- render_widget(subsec.pc, &mut state.source_sliders[idx], slider_x, yt, bar_w, slider_h, ctx);
+ render_widget(subsec.pc, &mut *state.source_sliders[idx], slider_x, yt, bar_w, slider_h, ctx);
let row_y = subsec.ay() + slider_h + 8.0;
let sb_w = 100.0;
@@ -342,7 +342,7 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
state.source_spinboxes[idx].value = (src.volume * 100.0).round() as i32;
state.source_spinboxes[idx].set_row_rect(row_rect_x, subsec.cw - 16.0);
let sb_x = subsec.ax(bar_x);
- render_widget(subsec.pc, &mut state.source_spinboxes[idx], sb_x, row_y, sb_w, sb_h, ctx);
+ render_widget(subsec.pc, &mut *state.source_spinboxes[idx], sb_x, row_y, sb_w, sb_h, ctx);
let mute_label = if src.muted { "Unmute" } else { "Mute" };
let mute_col = if src.muted { MUTED_BG } else { BTN_INACTIVE };
@@ -366,10 +366,10 @@ pub fn update(state: &mut AudioState, msg: AudioMessage) {
match msg {
AudioMessage::Refreshed(new) => {
*state = new;
- state.sink_spinboxes.resize_with(state.sinks.len(), || Spinbox::new(50, 0, 100, 1));
- state.source_spinboxes.resize_with(state.sources.len(), || Spinbox::new(50, 0, 100, 1));
- state.sink_sliders.resize_with(state.sinks.len(), || Slider::new().with_range(0.0, 1.0).with_scroll(true));
- state.source_sliders.resize_with(state.sources.len(), || Slider::new().with_range(0.0, 1.0).with_scroll(true));
+ state.sink_spinboxes.resize_with(state.sinks.len(), || Box::new(Spinbox::new(50, 0, 100, 1)));
+ state.source_spinboxes.resize_with(state.sources.len(), || Box::new(Spinbox::new(50, 0, 100, 1)));
+ state.sink_sliders.resize_with(state.sinks.len(), || Box::new(Slider::new().with_range(0.0, 1.0).with_scroll(true)));
+ state.source_sliders.resize_with(state.sources.len(), || Box::new(Slider::new().with_range(0.0, 1.0).with_scroll(true)));
}
AudioMessage::SinkVolume(id, vol) => {
if let Some(sink) = state.sinks.iter_mut().find(|s| s.id == id) {
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
index a741b9b..8e166ff 100644
--- a/src/pages/layout.rs
+++ b/src/pages/layout.rs
@@ -921,4 +921,30 @@ mode = "popup"
);
assert!(res);
}
+
+ #[test]
+ fn test_spinbox_right_click_crash() {
+ use cce_ui::widget::Element;
+ let mut state = LayoutState::default();
+ let mut ctx = cce_ui::context::UiContext::new();
+ let sb = &mut state.spinboxes[0];
+ sb.set_rect(0.0, 0.0, 100.0, 44.0);
+ let res = sb.mouse_input(
+ cce_ui::widget::MouseButton::Right,
+ cce_ui::widget::ElementState::Pressed,
+ 50.0,
+ 20.0,
+ &mut ctx,
+ );
+ assert!(res);
+ assert!(cce_ui::widget::context_menu::is_visible());
+
+ // Now replace the state simulating config reload/refresh
+ let new_state = LayoutState::default();
+ state = new_state;
+
+ // Assert that the context menu is hidden (cleared)
+ assert!(!cce_ui::widget::context_menu::is_visible());
+ }
}
+