From db06d719ddf887bb7ea7bb3316b51712d27934da Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 23 Feb 2026 20:40:00 +0100 Subject: [PATCH] control-center: add GPUs pane --- src/backend.rs | 1 - src/config/handler.rs | 8 +- src/control_center.rs | 9 +- src/control_center/cc_gpus.rs | 146 +++++++++++++++++++++++++++++++ src/control_center/cc_sidebar.rs | 3 + src/ifs/jay_randr.rs | 4 +- src/state.rs | 10 ++- 7 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 src/control_center/cc_gpus.rs diff --git a/src/backend.rs b/src/backend.rs index 678b7346..746fc7b4 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -588,7 +588,6 @@ pub trait BackendDrmDevice { fn set_flip_margin(&self, margin: u64) { let _ = margin; } - #[expect(dead_code)] fn flip_margin(&self) -> Option { None } diff --git a/src/config/handler.rs b/src/config/handler.rs index 4f39c905..a9a54e48 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -951,8 +951,10 @@ impl ConfigProxyHandler { } fn handle_set_flip_margin(&self, device: DrmDevice, margin: Duration) -> Result<(), CphError> { - self.get_drm_device(device)? - .set_flip_margin(margin.as_nanos().try_into().unwrap_or(u64::MAX)); + self.get_drm_device(device)?.set_flip_margin( + &self.state, + margin.as_nanos().try_into().unwrap_or(u64::MAX), + ); Ok(()) } @@ -987,7 +989,7 @@ impl ConfigProxyHandler { match device { Some(dev) => self .get_drm_device(dev)? - .set_direct_scanout_enabled(enabled), + .set_direct_scanout_enabled(&self.state, enabled), _ => self.state.direct_scanout_enabled.set(enabled), } Ok(()) diff --git a/src/control_center.rs b/src/control_center.rs index 1b766656..0d332ccb 100644 --- a/src/control_center.rs +++ b/src/control_center.rs @@ -2,7 +2,8 @@ use { crate::{ control_center::{ cc_color_management::ColorManagementPane, cc_compositor::CompositorPane, - cc_idle::IdlePane, cc_outputs::OutputsPane, cc_xwayland::XwaylandPane, + cc_gpus::GpusPane, cc_idle::IdlePane, cc_outputs::OutputsPane, + cc_xwayland::XwaylandPane, }, egui_adapter::egui_platform::{ EggError, EggWindow, EggWindowOwner, @@ -35,6 +36,7 @@ use { mod cc_color_management; mod cc_compositor; +mod cc_gpus; mod cc_idle; mod cc_outputs; mod cc_sidebar; @@ -76,6 +78,7 @@ bitflags! { CCI_COLOR_MANAGEMENT, CCI_XWAYLAND, CCI_OUTPUTS, + CCI_GPUS, } pub struct ControlCenter { @@ -121,6 +124,7 @@ enum PaneType { ColorManagement(ColorManagementPane), Xwayland(XwaylandPane), Outputs(Box), + GPUs(GpusPane), } struct CcBehavior<'a> { @@ -144,6 +148,7 @@ impl Pane { PaneType::ColorManagement(v) => v.title(res), PaneType::Xwayland(v) => v.title(res), PaneType::Outputs(v) => v.title(res), + PaneType::GPUs(v) => v.title(res), } } @@ -154,6 +159,7 @@ impl Pane { PaneType::ColorManagement(p) => p.show(ui), PaneType::Xwayland(p) => p.show(behavior, ui), PaneType::Outputs(p) => p.show(&mut self.ps, ui), + PaneType::GPUs(p) => p.show(ui), } } } @@ -166,6 +172,7 @@ impl PaneType { PaneType::ColorManagement(_) => CCI_COLOR_MANAGEMENT, PaneType::Xwayland(_) => CCI_XWAYLAND, PaneType::Outputs(_) => CCI_OUTPUTS, + PaneType::GPUs(_) => CCI_GPUS, } } } diff --git a/src/control_center/cc_gpus.rs b/src/control_center/cc_gpus.rs new file mode 100644 index 00000000..8d68a4ff --- /dev/null +++ b/src/control_center/cc_gpus.rs @@ -0,0 +1,146 @@ +use { + crate::{ + control_center::{ + ControlCenterInner, GridExt, bool, combo_box, grid, grid_label, label, row, + }, + egui_adapter::egui_platform::icons::{ICON_ADD, ICON_REMOVE}, + state::{DrmDevData, State}, + }, + egui::{Checkbox, CollapsingHeader, DragValue, TextFormat, Ui, Widget, text::LayoutJob}, + std::rc::Rc, +}; + +pub struct GpusPane { + state: Rc, +} + +impl ControlCenterInner { + pub fn create_gpus_pane(self: &Rc) -> GpusPane { + GpusPane { + state: self.state.clone(), + } + } +} + +impl GpusPane { + pub fn title(&self, res: &mut String) { + res.push_str("GPUs"); + } + + pub fn show(&mut self, ui: &mut Ui) { + let devs = self.state.drm_devs.lock(); + let mut devs: Vec<_> = devs.iter().collect(); + devs.sort_by_key(|d| d.0); + for dev in devs { + self.show_dev(ui, dev.1); + } + } + + fn show_dev(&self, ui: &mut Ui, dev: &DrmDevData) { + let title_buf; + let title = match dev.devnode.as_deref() { + Some(t) => t, + _ => { + let dev_t = dev.dev.dev_t(); + title_buf = format!("{}:{}", uapi::major(dev_t), uapi::minor(dev_t)); + &title_buf + } + }; + let mut layout_job = LayoutJob::default(); + layout_job.append( + title, + 0.0, + TextFormat { + color: ui.style().visuals.widgets.active.text_color(), + ..Default::default() + }, + ); + if let Some(model) = &dev.model { + layout_job.append( + model, + 10.0, + TextFormat { + color: ui.style().visuals.widgets.inactive.text_color(), + ..Default::default() + }, + ); + } + ui.collapsing(layout_job, |ui| { + grid(ui, ("settings", dev.dev.id()), |ui| { + macro_rules! string { + ($field:ident, $name:expr) => { + if let Some(v) = &dev.$field { + label(ui, $name, v); + } + }; + } + string!(vendor, "Vendor"); + string!(model, "Model"); + string!(devnode, "Devnode"); + string!(syspath, "Syspath"); + if let Some(v) = dev.pci_id { + label(ui, "PCI ID", format!("{:x}:{:x}", v.vendor, v.model)); + } + { + let v = dev.dev.dev_t(); + label(ui, "Dev", format!("{}:{}", uapi::major(v), uapi::minor(v))); + } + combo_box(ui, "API", dev.dev.gtx_api(), |v| dev.dev.set_gfx_api(v)); + row(ui, "Primary Device", |ui| { + let mut v = dev.dev.is_render_device(); + let old = v; + ui.add_enabled(!v, Checkbox::without_text(&mut v)); + if v != old { + dev.dev.make_render_device(); + } + }); + bool( + ui, + "Direct Scanout", + dev.dev.direct_scanout_enabled(), + |v| dev.set_direct_scanout_enabled(&self.state, v), + ); + if let Some(mut v) = dev.dev.flip_margin() { + let ui = &mut *ui.row(); + grid_label(ui, "Flip Margin"); + let old = v; + let denom = 1_000_000.0; + ui.horizontal(|ui| { + let mut s = v as f64 / denom; + let res = DragValue::new(&mut s) + .range(0.0..=50.0) + .speed(0.1) + .fixed_decimals(1) + .ui(ui); + if res.changed() { + v = (s * denom) as u64; + } + if ui.button(ICON_REMOVE).clicked() { + v = v.saturating_sub(100_000); + } + if ui.button(ICON_ADD).clicked() { + v += 100_000; + } + }); + if old != v { + dev.set_flip_margin(&self.state, v); + } + } + }); + CollapsingHeader::new("Connectors") + .default_open(true) + .show(ui, |ui| { + let mut cs: Vec<_> = dev + .connectors + .lock() + .values() + .map(|v| v.connector.kernel_id().to_string()) + .collect::>(); + cs.sort(); + for c in cs { + ui.label(c); + } + }); + }); + } +} diff --git a/src/control_center/cc_sidebar.rs b/src/control_center/cc_sidebar.rs index d1a74ea2..e276fcf8 100644 --- a/src/control_center/cc_sidebar.rs +++ b/src/control_center/cc_sidebar.rs @@ -13,6 +13,7 @@ enum PaneName { ColorManagement, Xwayland, Outputs, + GPUs, } impl PaneName { @@ -23,6 +24,7 @@ impl PaneName { PaneName::ColorManagement => "Color Management", PaneName::Xwayland => "Xwayland", PaneName::Outputs => "Outputs", + PaneName::GPUs => "GPUs", } } } @@ -60,6 +62,7 @@ impl ControlCenterInner { PaneName::Outputs => { PaneType::Outputs(Box::new(self.create_outputs_pane())) } + PaneName::GPUs => PaneType::GPUs(self.create_gpus_pane()), }; self.open(tree, ty); ui.ctx().request_repaint(); diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 604ef55b..156c7432 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -350,7 +350,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(dev) = self.get_device(req.dev) else { return Ok(()); }; - dev.set_direct_scanout_enabled(req.enabled != 0); + dev.set_direct_scanout_enabled(&self.state, req.enabled != 0); Ok(()) } @@ -493,7 +493,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(dev) = self.get_device(req.dev) else { return Ok(()); }; - dev.set_flip_margin(req.margin_ns); + dev.set_flip_margin(&self.state, req.margin_ns); Ok(()) } diff --git a/src/state.rs b/src/state.rs index 8de91570..ae17cd5a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -17,7 +17,7 @@ use { compositor::{LIBEI_SOCKET, LogLevel}, config::ConfigProxy, control_center::{ - CCI_COLOR_MANAGEMENT, CCI_COMPOSITOR, CCI_IDLE, CCI_OUTPUTS, CCI_XWAYLAND, + CCI_COLOR_MANAGEMENT, CCI_COMPOSITOR, CCI_GPUS, CCI_IDLE, CCI_OUTPUTS, CCI_XWAYLAND, ControlCenters, }, copy_device::CopyDeviceRegistry, @@ -549,12 +549,14 @@ impl DrmDevData { self.dev.clone().make_render_device(); } - pub fn set_direct_scanout_enabled(&self, enabled: bool) { + pub fn set_direct_scanout_enabled(&self, state: &State, enabled: bool) { self.dev.set_direct_scanout_enabled(enabled); + state.trigger_cci(CCI_GPUS); } - pub fn set_flip_margin(&self, margin: u64) { + pub fn set_flip_margin(&self, state: &State, margin: u64) { self.dev.set_flip_margin(margin); + state.trigger_cci(CCI_GPUS); } } @@ -778,7 +780,7 @@ impl State { } self.expose_new_singletons(); - self.trigger_cci(CCI_COLOR_MANAGEMENT); + self.trigger_cci(CCI_COLOR_MANAGEMENT | CCI_GPUS); } fn reload_cursors(&self) {