From e6a3a3a9c9dc4eb601d9775d4cb569339a890d4c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 9 Jan 2026 18:34:51 +0100 Subject: [PATCH] xwm: Set xcursor variables in Xresources db --- src/config/mod.rs | 42 ++++++++++++++++++------- src/shell/mod.rs | 4 --- src/state.rs | 2 +- src/xwayland.rs | 79 ++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 99b8780f..5734caf1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -180,8 +180,10 @@ impl Config { let cosmic_comp_config = CosmicCompConfig::get_entry(&config).unwrap_or_else(|(errs, c)| { - for err in errs { - error!(?err, ""); + if cfg!(debug_assertions) { + for err in errs { + warn!(?err, ""); + } } c }); @@ -189,6 +191,11 @@ impl Config { // Listen for updates to the toolkit config if let Ok(tk_config) = cosmic_config::Config::new("com.system76.CosmicTk", 1) { fn handle_new_toolkit_config(config: CosmicTk, state: &mut State) { + if cosmic::icon_theme::default() != config.icon_theme { + cosmic::icon_theme::set_default(config.icon_theme.clone()); + state.common.update_xwayland_settings(); + } + let mut workspace_guard = state.common.workspace_state.update(); state.common.shell.write().update_toolkit( config, @@ -197,19 +204,32 @@ impl Config { ); } - if let Ok(config) = CosmicTk::get_entry(&tk_config) { - let _ = loop_handle.insert_idle(move |state| { - handle_new_toolkit_config(config, state); - }); - } + let config = CosmicTk::get_entry(&tk_config).unwrap_or_else(|(errs, c)| { + if cfg!(debug_assertions) { + for err in errs { + warn!(?err, ""); + } + } + c + }); + let _ = loop_handle.insert_idle(move |state| { + handle_new_toolkit_config(config, state); + }); match cosmic_config::calloop::ConfigWatchSource::new(&tk_config) { Ok(source) => { if let Err(err) = loop_handle.insert_source(source, |(config, _keys), (), state| { - if let Ok(config) = CosmicTk::get_entry(&config) { - handle_new_toolkit_config(config, state); - } + let config = + CosmicTk::get_entry(&config).unwrap_or_else(|(errs, c)| { + if cfg!(debug_assertions) { + for err in errs { + warn!(?err, ""); + } + } + c + }); + handle_new_toolkit_config(config, state); }) { warn!(?err, "Failed to watch com.system76.CosmicTk config"); @@ -879,7 +899,7 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut let new = get_config::(&config, "descale_xwayland"); if new != state.common.config.cosmic_conf.descale_xwayland { state.common.config.cosmic_conf.descale_xwayland = new; - state.common.update_xwayland_scale(); + state.common.update_xwayland_settings(); } } "xwayland_eavesdropping" => { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 7e06090f..f385dc07 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -4711,10 +4711,6 @@ impl Shell { xdg_activation_state: &XdgActivationState, workspace_state: &mut WorkspaceUpdateGuard<'_, State>, ) { - if cosmic::icon_theme::default() != toolkit.icon_theme { - cosmic::icon_theme::set_default(toolkit.icon_theme.clone()); - } - let mut container = cosmic::config::COSMIC_TK.write().unwrap(); if *container != toolkit { *container = toolkit; diff --git a/src/state.rs b/src/state.rs index 6d1c3217..00ff55c1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -579,7 +579,7 @@ impl LockedBackend<'_> { loop_handle.insert_idle(move |state| { state.update_inhibitor_locks(); - state.common.update_xwayland_scale(); + state.common.update_xwayland_settings(); state.common.update_xwayland_primary_output(); }); diff --git a/src/xwayland.rs b/src/xwayland.rs index 825a3839..9f65445c 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -1,4 +1,10 @@ -use std::{ffi::OsString, os::unix::io::OwnedFd, process::Stdio}; +use std::{ + ffi::OsString, + io::Write, + os::unix::io::OwnedFd, + process::Stdio, + sync::mpsc::{self, Receiver, Sender}, +}; use crate::{ backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme}, @@ -64,6 +70,39 @@ pub struct XWaylandState { pub last_modifier_state: Option, pub clipboard_selection_dirty: Option>, pub primary_selection_dirty: Option>, + pub xrdb_thread: Sender<(String, u32)>, +} + +fn xrdb_thread(rx: Receiver<(String, u32)>, display: u32) { + while let Ok((cursor_theme, cursor_size)) = rx.recv() { + if let Ok(mut child) = std::process::Command::new("xrdb") + .arg("-merge") + .env("DISPLAY", format!(":{}", display)) + .stdin(Stdio::piped()) + .spawn() + { + let resources = format!( + "Xcursor.theme: {}\nXcursor.size: {}\n", + cursor_theme, cursor_size, + ); + if let Some(mut stdin) = child.stdin.take() { + if let Err(err) = stdin.write_all(resources.as_bytes()) { + warn!("Failed to update xresources: {}", err); + } + } + match child.wait() { + Ok(code) if code.success() => {} + Ok(code) => { + warn!("xrdb failed with code: {}", code); + } + Err(err) => { + warn!("Failed to wait for child: {}", err); + } + } + } else { + warn!("`xrdb` not found, cannot update Xresources."); + } + } } impl State { @@ -101,6 +140,9 @@ impl State { x11_socket, display_number, } => { + let (tx, rx) = mpsc::channel(); + std::thread::spawn(move || xrdb_thread(rx, display_number)); + data.common.xwayland_state = Some(XWaylandState { client: client.clone(), xwm: None, @@ -110,6 +152,7 @@ impl State { last_modifier_state: None, clipboard_selection_dirty: None, primary_selection_dirty: None, + xrdb_thread: tx, }); let wm = match X11Wm::start_wm( @@ -130,7 +173,7 @@ impl State { xwayland_state.reload_cursor(1.); data.notify_ready(); - data.common.update_xwayland_scale(); + data.common.update_xwayland_settings(); data.common.update_xwayland_primary_output(); } XWaylandEvent::Error => { @@ -594,7 +637,7 @@ impl Common { } } - pub fn update_xwayland_scale(&mut self) { + pub fn update_xwayland_settings(&mut self) { let new_scale = match self.config.cosmic_conf.descale_xwayland { XwaylandDescaling::Disabled => 1., XwaylandDescaling::Enabled => { @@ -619,10 +662,11 @@ impl Common { val } }; + let (_, cursor_size) = load_cursor_env(); // compare with current scale - if Some(new_scale) != self.xwayland_scale { - if let Some(xwayland) = self.xwayland_state.as_mut() { + if let Some(xwayland) = self.xwayland_state.as_mut() { + let geometries = if Some(new_scale) != self.xwayland_scale { // backup geometries let geometries = self .shell @@ -632,8 +676,6 @@ impl Common { .filter_map(|s| s.0.x11_surface().map(|x| (x.clone(), x.geometry()))) .collect::>(); - let (_, cursor_size) = load_cursor_env(); - // update xorg dpi if let Some(xwm) = xwayland.xwm.as_mut() { let base = 96. * 1024.; @@ -644,10 +686,6 @@ impl Common { if let Err(err) = xwm.set_xsettings( [ ("Xft/DPI".into(), (dpi.round() as i32).into()), - ( - "Xcursor/size".into(), - ((new_scale * cursor_size as f64).round() as i32).into(), - ), ( "Gdk/UnscaledDPI".into(), (unscaled_dpi.round() as i32).into(), @@ -667,9 +705,22 @@ impl Common { } } - // update cursor - xwayland.reload_cursor(new_scale); + Some(geometries) + } else { + None + }; + if let Err(_) = xwayland.xrdb_thread.send(( + cosmic::icon_theme::default(), + (new_scale * cursor_size as f64).round() as u32, + )) { + warn!("xrdb thread died"); + } + + // update cursor + xwayland.reload_cursor(new_scale); + + if let Some(geometries) = geometries { // update client scale xwayland .client @@ -1192,7 +1243,7 @@ impl XwmHandler for State { output_name.as_deref().is_some_and(|o| o == output.name()); } self.common.output_configuration_state.update(); - self.common.update_xwayland_scale(); + self.common.update_xwayland_settings(); self.common .config .write_outputs(self.common.output_configuration_state.outputs());