diff --git a/cosmic-comp-config/src/lib.rs b/cosmic-comp-config/src/lib.rs index 83d3680f..dcc0af6b 100644 --- a/cosmic-comp-config/src/lib.rs +++ b/cosmic-comp-config/src/lib.rs @@ -23,6 +23,8 @@ pub struct CosmicCompConfig { pub autotile_behavior: TileBehavior, /// Active hint enabled pub active_hint: bool, + /// Let X11 applications scale themselves + pub descale_xwayland: bool, } impl Default for CosmicCompConfig { @@ -48,6 +50,7 @@ impl Default for CosmicCompConfig { autotile: Default::default(), autotile_behavior: Default::default(), active_hint: true, + descale_xwayland: false, } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 01c7cd0e..572ea6d5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -651,6 +651,13 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut state.common.update_config(); } } + "descale_xwayland" => { + 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(); + } + } _ => {} } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 7efa8fba..37e6ebfb 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -3422,6 +3422,18 @@ impl Shell { output_presentation_feedback } + + pub fn mapped(&self) -> impl Iterator { + self.workspaces.iter().flat_map(|(_, set)| { + set.sticky_layer + .mapped() + .chain(set.minimized_windows.iter().map(|m| &m.window)) + .chain(set.workspaces.iter().flat_map(|w| { + w.mapped() + .chain(w.minimized_windows.iter().map(|m| &m.window)) + })) + }) + } } fn workspace_set_idx( diff --git a/src/state.rs b/src/state.rs index a8572269..7b817947 100644 --- a/src/state.rs +++ b/src/state.rs @@ -223,6 +223,7 @@ pub struct Common { pub xdg_activation_state: XdgActivationState, pub xdg_foreign_state: XdgForeignState, pub workspace_state: WorkspaceState, + pub xwayland_scale: Option, pub xwayland_state: Option, pub xwayland_shell_state: XWaylandShellState, } @@ -354,6 +355,8 @@ impl BackendData { self.schedule_render(&output); } + loop_handle.insert_idle(|state| state.common.update_xwayland_scale()); + Ok(()) } @@ -615,6 +618,7 @@ impl State { xdg_activation_state, xdg_foreign_state, workspace_state, + xwayland_scale: None, xwayland_state: None, xwayland_shell_state, }, diff --git a/src/xwayland.rs b/src/xwayland.rs index ab10fe8c..4262b3ca 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -15,7 +15,7 @@ use crate::{ use smithay::{ backend::drm::DrmNode, desktop::space::SpaceElement, - reexports::x11rb::protocol::xproto::Window as X11Window, + reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window}, utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER}, wayland::{ selection::{ @@ -33,13 +33,14 @@ use smithay::{ }, xwayland::{ xwm::{Reorder, X11Relatable, XwmId}, - X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler, + X11Surface, X11Wm, XWayland, XWaylandClientData, XWaylandEvent, XwmHandler, }, }; use tracing::{error, trace, warn}; #[derive(Debug)] pub struct XWaylandState { + pub client: Client, pub xwm: Option, pub display: u32, } @@ -80,6 +81,7 @@ impl State { display_number, } => { data.common.xwayland_state = Some(XWaylandState { + client: client.clone(), xwm: None, display: display_number, }); @@ -114,6 +116,8 @@ impl State { let xwayland_state = data.common.xwayland_state.as_mut().unwrap(); xwayland_state.xwm = Some(wm); data.notify_ready(); + + data.common.update_xwayland_scale(); } XWaylandEvent::Error => { if let Some(mut xwayland_state) = data.common.xwayland_state.take() { @@ -242,6 +246,72 @@ impl Common { } } } + + pub fn update_xwayland_scale(&mut self) { + let new_scale = if self.config.cosmic_conf.descale_xwayland { + let shell = self.shell.read().unwrap(); + shell + .outputs() + .map(|o| o.current_scale().integer_scale()) + .max() + .unwrap_or(1) as i32 + } else { + 1 + }; + + // compare with current scale + if Some(new_scale) != self.xwayland_scale { + if let Some(xwayland) = self.xwayland_state.as_mut() { + // backup geometries + let geometries = self + .shell + .read() + .unwrap() + .mapped() + .flat_map(|m| m.windows().map(|(s, _)| s)) + .filter_map(|s| s.0.x11_surface().map(|x| (x.clone(), x.geometry()))) + .collect::>(); + + // update xorg dpi + if let Some(xwm) = xwayland.xwm.as_mut() { + let dpi = new_scale.abs() * 96 * 1024; + if let Err(err) = xwm.set_xsettings( + [ + ("Xft/DPI".into(), dpi.into()), + ("Gdk/UnscaledDPI".into(), (dpi / new_scale).into()), + ("Gdk/WindowScalingFactor".into(), new_scale.into()), + ] + .into_iter(), + ) { + warn!(wm_id = ?xwm.id(), ?err, "Failed to update XSETTINGS."); + } + } + + // update client scale + xwayland + .client + .get_data::() + .unwrap() + .compositor_state + .set_client_scale(new_scale as u32); + + // update wl/xdg_outputs + for output in self.shell.read().unwrap().outputs() { + output.change_current_state(None, None, None, None); + } + + // update geometries + for (surface, geometry) in geometries.iter() { + if let Err(err) = surface.configure(*geometry) { + warn!(?err, surface = ?surface.window_id(), "Failed to update geometry after scale change"); + } + } + self.update_x11_stacking_order(); + + self.xwayland_scale = Some(new_scale); + } + } + } } impl XwmHandler for State {