From 19b07fa7dc99a64b1d163c59defebb081feee6d0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 8 Oct 2024 11:14:13 +0200 Subject: [PATCH] xwayland: allow windows to scale themselves --- jay-config/src/_private/client.rs | 5 + jay-config/src/_private/ipc.rs | 4 + jay-config/src/lib.rs | 1 + jay-config/src/xwayland.rs | 33 ++++++ release-notes.md | 1 + src/cli.rs | 9 +- src/cli/xwayland.rs | 106 ++++++++++++++++++ src/client.rs | 2 + src/compositor.rs | 2 + src/config/handler.rs | 17 +++ src/fixed.rs | 18 ++- src/ifs.rs | 1 + src/ifs/jay_compositor.rs | 15 ++- src/ifs/jay_xwayland.rs | 84 ++++++++++++++ src/ifs/wl_output.rs | 48 +++++--- src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs | 6 +- src/ifs/wl_seat/wl_pointer.rs | 12 +- src/ifs/wl_seat/wl_touch.rs | 8 +- src/ifs/wl_seat/zwp_pointer_constraints_v1.rs | 43 +++++-- src/ifs/wl_seat/zwp_relative_pointer_v1.rs | 5 +- src/ifs/wl_surface.rs | 42 ++++++- src/ifs/wl_surface/wl_subsurface.rs | 3 +- src/ifs/wl_surface/wp_fractional_scale_v1.rs | 16 ++- src/ifs/wl_surface/x_surface/xwindow.rs | 13 +-- src/ifs/zxdg_output_v1.rs | 6 +- src/macros.rs | 20 ++++ src/state.rs | 33 ++++++ src/tools/tool_client.rs | 2 +- src/xwayland.rs | 1 + src/xwayland/xwm.rs | 43 ++++--- toml-config/src/config.rs | 7 ++ toml-config/src/config/parsers.rs | 1 + toml-config/src/config/parsers/config.rs | 13 +++ toml-config/src/config/parsers/xwayland.rs | 75 +++++++++++++ toml-config/src/lib.rs | 6 + toml-spec/spec/spec.generated.json | 23 ++++ toml-spec/spec/spec.generated.md | 73 ++++++++++++ toml-spec/spec/spec.yaml | 61 ++++++++++ wire/jay_compositor.txt | 4 + wire/jay_xwayland.txt | 18 +++ 40 files changed, 800 insertions(+), 80 deletions(-) create mode 100644 jay-config/src/xwayland.rs create mode 100644 src/cli/xwayland.rs create mode 100644 src/ifs/jay_xwayland.rs create mode 100644 toml-config/src/config/parsers/xwayland.rs create mode 100644 wire/jay_xwayland.txt diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index c396ba03..92d6e2ef 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -27,6 +27,7 @@ use { connector_type::{ConnectorType, CON_UNKNOWN}, Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, Transform, VrrMode, }, + xwayland::XScalingMode, Axis, Direction, ModifiedKeySym, PciId, Workspace, }, bincode::Options, @@ -816,6 +817,10 @@ impl Client { (width, height) } + pub fn set_x_scaling_mode(&self, mode: XScalingMode) { + self.send(&ClientMessage::SetXScalingMode { mode }) + } + pub fn set_vrr_mode(&self, connector: Option, mode: VrrMode) { self.send(&ClientMessage::SetVrrMode { connector, mode }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index e43d3a1a..dc04ae33 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -14,6 +14,7 @@ use { }, Axis, Direction, PciId, Workspace, _private::{PollableId, WireMode}, + xwayland::XScalingMode, }, serde::{Deserialize, Serialize}, std::time::Duration, @@ -523,6 +524,9 @@ pub enum ClientMessage<'a> { SetUiDragThreshold { threshold: i32, }, + SetXScalingMode { + mode: XScalingMode, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 3fd88169..bb831d12 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -66,6 +66,7 @@ pub mod tasks; pub mod theme; pub mod timer; pub mod video; +pub mod xwayland; /// A planar direction. #[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] diff --git a/jay-config/src/xwayland.rs b/jay-config/src/xwayland.rs new file mode 100644 index 00000000..bcfbed99 --- /dev/null +++ b/jay-config/src/xwayland.rs @@ -0,0 +1,33 @@ +//! Tools for configuring Xwayland. + +use serde::{Deserialize, Serialize}; + +/// The scaling mode of X windows. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub struct XScalingMode(pub u32); + +impl XScalingMode { + /// The default mode. + /// + /// Currently this means that windows are rendered at the lowest scale and then + /// upscaled if necessary. + pub const DEFAULT: Self = Self(0); + /// Windows are rendered at the highest integer scale and then downscaled. + /// + /// This has significant performance implications unless the window is running on the + /// output with the highest scale and that scale is an integer scale. + /// + /// For example, on a 3840x2160 output with a 1.5 scale, a fullscreen window will be + /// rendered at 3840x2160 * 2 / 1.5 = 5120x2880 pixels and then downscaled to + /// 3840x2160. This overhead gets worse the lower the scale of the output is. + /// + /// Additionally, this mode requires the X window to scale its contents itself. In the + /// example above, you might achieve this by setting the environment variable + /// `GDK_SCALE=2`. + pub const DOWNSCALED: Self = Self(1); +} + +/// Sets the scaling mode for X windows. +pub fn set_x_scaling_mode(mode: XScalingMode) { + get!().set_x_scaling_mode(mode) +} diff --git a/release-notes.md b/release-notes.md index ec359474..5ce19817 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ - Tiles and workspaces can now be dragged with the mouse. - Vulkan is now the default renderer. - Emulate vblank events on the nvidia driver. +- Allow X windows to scale themselves. # 1.6.0 (2024-09-25) diff --git a/src/cli.rs b/src/cli.rs index 4a25dbb5..a5fdbf53 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,10 +12,14 @@ pub mod screenshot; mod seat_test; mod set_log_level; mod unlock; +mod xwayland; use { crate::{ - cli::{damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs}, + cli::{ + damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs, + xwayland::XwaylandArgs, + }, compositor::start_compositor, format::{ref_formats, Format}, portal, @@ -72,6 +76,8 @@ pub enum Cmd { /// Modify damage tracking settings. (Only for debugging.) #[clap(hide = true)] DamageTracking(DamageTrackingArgs), + /// Inspect/modify xwayland settings. + Xwayland(XwaylandArgs), #[cfg(feature = "it")] RunTests, } @@ -259,6 +265,7 @@ pub fn main() { Cmd::Randr(a) => randr::main(cli.global, a), Cmd::Input(a) => input::main(cli.global, a), Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a), + Cmd::Xwayland(a) => xwayland::main(cli.global, a), #[cfg(feature = "it")] Cmd::RunTests => crate::it::run_tests(), } diff --git a/src/cli/xwayland.rs b/src/cli/xwayland.rs new file mode 100644 index 00000000..8bc81e98 --- /dev/null +++ b/src/cli/xwayland.rs @@ -0,0 +1,106 @@ +use { + crate::{ + cli::GlobalArgs, + tools::tool_client::{with_tool_client, Handle, ToolClient}, + wire::{jay_compositor, jay_xwayland, JayXwaylandId}, + }, + clap::{Args, Subcommand, ValueEnum}, + jay_config::xwayland::XScalingMode, + std::{cell::Cell, rc::Rc}, +}; + +#[derive(Args, Debug)] +pub struct XwaylandArgs { + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Subcommand, Debug, Default)] +pub enum XwaylandCmd { + /// Print the Xwayland status. + #[default] + Status, + /// Set the Xwayland scaling mode. + SetScalingMode(SetScalingModeArgs), +} + +#[derive(Args, Debug)] +pub struct SetScalingModeArgs { + #[clap(value_enum)] + pub mode: CliScalingMode, +} + +#[derive(ValueEnum, Debug, Copy, Clone, Hash, PartialEq)] +pub enum CliScalingMode { + /// The default mode. + Default, + /// Windows are rendered at the highest integer scale and then downscaled. + Downscaled, +} + +pub fn main(global: GlobalArgs, args: XwaylandArgs) { + with_tool_client(global.log_level.into(), |tc| async move { + let xwayland = Xwayland { tc: tc.clone() }; + xwayland.run(args).await; + }); +} + +struct Xwayland { + tc: Rc, +} + +impl Xwayland { + async fn run(self, args: XwaylandArgs) { + let tc = &self.tc; + let comp = tc.jay_compositor().await; + let xwayland = tc.id(); + tc.send(jay_compositor::GetXwayland { + self_id: comp, + id: xwayland, + }); + match args.command.unwrap_or_default() { + XwaylandCmd::Status => self.status(xwayland).await, + XwaylandCmd::SetScalingMode(args) => self.set_scaling_mode(xwayland, args).await, + } + } + + async fn status(self, xwayland: JayXwaylandId) { + let tc = &self.tc; + tc.send(jay_xwayland::GetScaling { self_id: xwayland }); + let mode = Rc::new(Cell::new(0)); + let scale = Rc::new(Cell::new(None)); + jay_xwayland::ScalingMode::handle(tc, xwayland, mode.clone(), |iv, msg| { + iv.set(msg.mode); + }); + jay_xwayland::ImpliedScale::handle(tc, xwayland, scale.clone(), |iv, msg| { + iv.set(Some(msg.scale)); + }); + tc.round_trip().await; + let mode_str; + let mode = match XScalingMode(mode.get()) { + XScalingMode::DEFAULT => "default", + XScalingMode::DOWNSCALED => "downscaled", + o => { + mode_str = format!("unknown ({})", o.0); + &mode_str + } + }; + println!("scaling mode: {}", mode); + if let Some(scale) = scale.get() { + println!("implied scale: {}", scale); + } + } + + async fn set_scaling_mode(self, xwayland: JayXwaylandId, args: SetScalingModeArgs) { + let tc = &self.tc; + let mode = match args.mode { + CliScalingMode::Default => XScalingMode::DEFAULT, + CliScalingMode::Downscaled => XScalingMode::DOWNSCALED, + }; + tc.send(jay_xwayland::SetScalingMode { + self_id: xwayland, + mode: mode.0, + }); + tc.round_trip().await; + } +} diff --git a/src/client.rs b/src/client.rs index 7219806a..2913c749 100644 --- a/src/client.rs +++ b/src/client.rs @@ -172,6 +172,7 @@ impl Clients { &global.wait_for_sync_obj, &global.ring, )), + wire_scale: Default::default(), }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); @@ -282,6 +283,7 @@ pub struct Client { pub surfaces_by_xwayland_serial: CopyHashMap>, pub activation_tokens: RefCell>, pub commit_timelines: Rc, + pub wire_scale: Cell>, } pub const NUM_CACHED_SERIAL_RANGES: usize = 64; diff --git a/src/compositor.rs b/src/compositor.rs index 61e02dce..0803a0dd 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -211,6 +211,8 @@ fn start_compositor2( handler: Default::default(), queue: Default::default(), ipc_device_ids: Default::default(), + use_wire_scale: Default::default(), + wire_scale: Default::default(), }, acceptor: Default::default(), serial: Default::default(), diff --git a/src/config/handler.rs b/src/config/handler.rs index 7d0ca3f2..abbe736a 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -53,6 +53,7 @@ use { Connector, DrmDevice, Format as ConfigFormat, GfxApi, TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode, }, + xwayland::XScalingMode, Axis, Direction, Workspace, }, libloading::Library, @@ -759,6 +760,17 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_x_scaling_mode(&self, mode: XScalingMode) -> Result<(), CphError> { + let use_wire_scale = match mode { + XScalingMode::DEFAULT => false, + XScalingMode::DOWNSCALED => true, + _ => return Err(CphError::UnknownXScalingMode(mode)), + }; + self.state.xwayland.use_wire_scale.set(use_wire_scale); + self.state.update_xwayland_wire_scale(); + Ok(()) + } + fn handle_set_ui_drag_enabled(&self, enabled: bool) { self.state.ui_drag_enabled.set(enabled); } @@ -1965,6 +1977,9 @@ impl ConfigProxyHandler { ClientMessage::SetUiDragThreshold { threshold } => { self.handle_set_ui_drag_threshold(threshold) } + ClientMessage::SetXScalingMode { mode } => self + .handle_set_x_scaling_mode(mode) + .wrn("set_x_scaling_mode")?, } Ok(()) } @@ -2034,6 +2049,8 @@ enum CphError { UnknownTearingMode(ConfigTearingMode), #[error("The format {0:?} is unknown")] UnknownFormat(ConfigFormat), + #[error("Unknown x scaling mode {0:?}")] + UnknownXScalingMode(XScalingMode), } trait WithRequestName { diff --git a/src/fixed.rs b/src/fixed.rs index c758f637..5ff30c05 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -1,7 +1,7 @@ use std::{ cmp::Ordering, fmt::{Debug, Display, Formatter}, - ops::{Add, AddAssign, Sub, SubAssign}, + ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}, }; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] @@ -108,6 +108,22 @@ impl Add for Fixed { } } +impl Mul for Fixed { + type Output = Self; + + fn mul(self, rhs: i32) -> Self::Output { + Self(self.0 * rhs) + } +} + +impl Div for Fixed { + type Output = Self; + + fn div(self, rhs: i32) -> Self::Output { + Self(self.0 / rhs) + } +} + impl AddAssign for Fixed { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; diff --git a/src/ifs.rs b/src/ifs.rs index 3d81f770..173493d7 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -24,6 +24,7 @@ pub mod jay_select_workspace; pub mod jay_toplevel; pub mod jay_workspace; pub mod jay_workspace_watcher; +pub mod jay_xwayland; pub mod org_kde_kwin_server_decoration; pub mod org_kde_kwin_server_decoration_manager; pub mod wl_buffer; diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 2d3e41d6..a80a6814 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -18,6 +18,7 @@ use { jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector}, jay_select_workspace::{JaySelectWorkspace, JayWorkspaceSelector}, jay_workspace_watcher::JayWorkspaceWatcher, + jay_xwayland::JayXwayland, }, leaks::Tracker, object::{Object, Version}, @@ -70,7 +71,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 10 + 11 } fn required_caps(&self) -> ClientCaps { @@ -409,6 +410,18 @@ impl JayCompositorRequestHandler for JayCompositor { self.client.add_client_obj(&obj)?; Ok(()) } + + fn get_xwayland(&self, req: GetXwayland, _slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(JayXwayland { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } } object_base! { diff --git a/src/ifs/jay_xwayland.rs b/src/ifs/jay_xwayland.rs new file mode 100644 index 00000000..709a7d4a --- /dev/null +++ b/src/ifs/jay_xwayland.rs @@ -0,0 +1,84 @@ +use { + crate::{ + client::{Client, ClientError}, + leaks::Tracker, + object::{Object, Version}, + wire::{jay_xwayland::*, JayXwaylandId}, + }, + jay_config::xwayland::XScalingMode, + std::rc::Rc, + thiserror::Error, +}; + +pub struct JayXwayland { + pub id: JayXwaylandId, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl JayXwayland { + pub fn send_scaling_mode(&self) { + let xw = &self.client.state.xwayland; + self.client.event(ScalingMode { + self_id: self.id, + mode: match xw.use_wire_scale.get() { + false => XScalingMode::DEFAULT.0, + true => XScalingMode::DOWNSCALED.0, + }, + }); + } + + pub fn send_implied_scale(&self) { + let xw = &self.client.state.xwayland; + if let Some(scale) = xw.wire_scale.get() { + self.client.event(ImpliedScale { + self_id: self.id, + scale, + }); + } + } +} + +impl JayXwaylandRequestHandler for JayXwayland { + type Error = JayXwaylandError; + + fn get_scaling(&self, _req: GetScaling, _slf: &Rc) -> Result<(), Self::Error> { + self.send_scaling_mode(); + self.send_implied_scale(); + Ok(()) + } + + fn set_scaling_mode(&self, req: SetScalingMode, _slf: &Rc) -> Result<(), Self::Error> { + let use_wire_scale = match XScalingMode(req.mode) { + XScalingMode::DEFAULT => false, + XScalingMode::DOWNSCALED => true, + _ => return Err(JayXwaylandError::UnknownMode(req.mode)), + }; + self.client + .state + .xwayland + .use_wire_scale + .set(use_wire_scale); + self.client.state.update_xwayland_wire_scale(); + Ok(()) + } +} + +object_base! { + self = JayXwayland; + version = self.version; +} + +impl Object for JayXwayland {} + +simple_add_obj!(JayXwayland); + +#[derive(Debug, Error)] +pub enum JayXwaylandError { + #[error(transparent)] + ClientError(Box), + #[error("Unknown scaling mode {}", .0)] + UnknownMode(u32), +} +efrom!(JayXwaylandError, ClientError); diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index e274f43b..486d5178 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -12,7 +12,10 @@ use { rect::Rect, state::{ConnectorData, State}, tree::{calculate_logical_size, OutputNode, TearingMode, VrrMode}, - utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, transform_ext::TransformExt}, + utils::{ + cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + transform_ext::TransformExt, + }, wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id}, }, ahash::AHashMap, @@ -196,15 +199,7 @@ impl WlOutputGlobal { let bindings = self.bindings.borrow_mut(); for binding in bindings.values() { for binding in binding.values() { - binding.send_geometry(); - binding.send_mode(); - binding.send_scale(); - binding.send_done(); - let xdg = binding.xdg_outputs.lock(); - for xdg in xdg.values() { - xdg.send_updates(); - } - // binding.client.flush(); + binding.send_updates(); } } } @@ -283,15 +278,33 @@ pub const SEND_SCALE_SINCE: Version = Version(2); pub const SEND_NAME_SINCE: Version = Version(4); impl WlOutput { + pub fn send_updates(&self) { + self.send_geometry(); + self.send_mode(); + if self.version >= SEND_SCALE_SINCE { + self.send_scale(); + } + if self.version >= SEND_DONE_SINCE { + self.send_done(); + } + let xdg = self.xdg_outputs.lock(); + for xdg in xdg.values() { + xdg.send_updates(); + } + } + fn send_geometry(&self) { let Some(global) = self.global.get() else { return; }; let pos = global.pos.get(); + let mut x = pos.x1(); + let mut y = pos.y1(); + logical_to_client_wire_scale!(self.client, x, y); let event = Geometry { self_id: self.id, - x: pos.x1(), - y: pos.y1(), + x, + y, physical_width: global.width_mm, physical_height: global.height_mm, subpixel: SP_UNKNOWN, @@ -306,7 +319,8 @@ impl WlOutput { let Some(global) = self.global.get() else { return; }; - let mode = global.mode.get(); + let mut mode = global.mode.get(); + logical_to_client_wire_scale!(self.client, mode.width, mode.height); let event = Mode { self_id: self.id, flags: MODE_CURRENT, @@ -317,13 +331,17 @@ impl WlOutput { self.client.event(event); } - fn send_scale(self: &Rc) { + fn send_scale(&self) { let Some(global) = self.global.get() else { return; }; + let factor = match self.client.wire_scale.is_some() { + true => 1, + false => global.legacy_scale.get() as _, + }; let event = Scale { self_id: self.id, - factor: global.legacy_scale.get() as _, + factor, }; self.client.event(event); } diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs index 199732bb..4bd2b5c9 100644 --- a/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs +++ b/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs @@ -124,7 +124,8 @@ impl ZwpTabletToolV2 { self.client.event(Up { self_id: self.id }); } - pub fn send_motion(&self, x: Fixed, y: Fixed) { + pub fn send_motion(&self, mut x: Fixed, mut y: Fixed) { + logical_to_client_wire_scale!(self.client, x, y); self.client.event(Motion { self_id: self.id, x, @@ -199,7 +200,7 @@ impl ZwpTabletToolV2 { impl ZwpTabletToolV2RequestHandler for ZwpTabletToolV2 { type Error = ZwpTabletToolV2Error; - fn set_cursor(&self, req: SetCursor, _slf: &Rc) -> Result<(), Self::Error> { + fn set_cursor(&self, mut req: SetCursor, _slf: &Rc) -> Result<(), Self::Error> { let Some(tool) = self.tool.get() else { return Ok(()); }; @@ -209,6 +210,7 @@ impl ZwpTabletToolV2RequestHandler for ZwpTabletToolV2 { } let mut cursor_opt = None; if req.surface.is_some() { + client_wire_scale_to_logical!(self.client, req.hotspot_x, req.hotspot_y); let surface = self.seat.client.lookup(req.surface)?; let cursor = surface.get_cursor(&tool.cursor)?; cursor.set_hotspot(req.hotspot_x, req.hotspot_y); diff --git a/src/ifs/wl_seat/wl_pointer.rs b/src/ifs/wl_seat/wl_pointer.rs index d5b00890..03ced6a9 100644 --- a/src/ifs/wl_seat/wl_pointer.rs +++ b/src/ifs/wl_seat/wl_pointer.rs @@ -86,8 +86,9 @@ impl WlPointer { } } - pub fn send_enter(&self, serial: u32, surface: WlSurfaceId, x: Fixed, y: Fixed) { + pub fn send_enter(&self, serial: u32, surface: WlSurfaceId, mut x: Fixed, mut y: Fixed) { self.last_motion.set((x, y)); + logical_to_client_wire_scale!(self.seat.client, x, y); self.seat.client.event(Enter { self_id: self.id, serial, @@ -105,10 +106,11 @@ impl WlPointer { }) } - pub fn send_motion(&self, time: u32, x: Fixed, y: Fixed) { + pub fn send_motion(&self, time: u32, mut x: Fixed, mut y: Fixed) { if self.last_motion.replace((x, y)) == (x, y) { return; } + logical_to_client_wire_scale!(self.seat.client, x, y); self.seat.client.event(Motion { self_id: self.id, time, @@ -135,7 +137,8 @@ impl WlPointer { }) } - pub fn send_axis(&self, time: u32, axis: u32, value: Fixed) { + pub fn send_axis(&self, time: u32, axis: u32, mut value: Fixed) { + logical_to_client_wire_scale!(self.seat.client, value); self.seat.client.event(Axis { self_id: self.id, time, @@ -183,13 +186,14 @@ impl WlPointer { impl WlPointerRequestHandler for WlPointer { type Error = WlPointerError; - fn set_cursor(&self, req: SetCursor, _slf: &Rc) -> Result<(), Self::Error> { + fn set_cursor(&self, mut req: SetCursor, _slf: &Rc) -> Result<(), Self::Error> { if !self.seat.client.valid_serial(req.serial) { log::warn!("Client tried to set_cursor with an invalid serial"); return Ok(()); } let mut cursor_opt = None; if req.surface.is_some() { + client_wire_scale_to_logical!(self.seat.client, req.hotspot_x, req.hotspot_y); let surface = self.seat.client.lookup(req.surface)?; let cursor = surface.get_cursor(&self.seat.global.pointer_cursor)?; cursor.set_hotspot(req.hotspot_x, req.hotspot_y); diff --git a/src/ifs/wl_seat/wl_touch.rs b/src/ifs/wl_seat/wl_touch.rs index 3672c740..5ecdf12e 100644 --- a/src/ifs/wl_seat/wl_touch.rs +++ b/src/ifs/wl_seat/wl_touch.rs @@ -37,9 +37,10 @@ impl WlTouch { time: u32, surface: WlSurfaceId, id: i32, - x: Fixed, - y: Fixed, + mut x: Fixed, + mut y: Fixed, ) { + logical_to_client_wire_scale!(self.seat.client, x, y); self.seat.client.event(Down { self_id: self.id, serial, @@ -60,7 +61,8 @@ impl WlTouch { }) } - pub fn send_motion(&self, time: u32, id: i32, x: Fixed, y: Fixed) { + pub fn send_motion(&self, time: u32, id: i32, mut x: Fixed, mut y: Fixed) { + logical_to_client_wire_scale!(self.seat.client, x, y); self.seat.client.event(Motion { self_id: self.id, time, diff --git a/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs b/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs index 98a2025f..2c1b83b8 100644 --- a/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs +++ b/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs @@ -12,7 +12,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Region, + rect::{Rect, Region}, utils::clonecell::CloneCell, wire::{ zwp_pointer_constraints_v1::*, WlPointerId, WlRegionId, WlSurfaceId, @@ -125,11 +125,7 @@ impl SeatConstraint { } fn set_region(&self, region: WlRegionId) -> Result<(), ZwpPointerConstraintsV1Error> { - let region = if region.is_some() { - Some(self.client.lookup(region)?.region()) - } else { - None - }; + let region = get_region(&self.client, region)?; self.region.set(region); Ok(()) } @@ -166,6 +162,35 @@ impl ZwpPointerConstraintsV1Global { } } +fn get_region( + client: &Client, + region: WlRegionId, +) -> Result>, ZwpPointerConstraintsV1Error> { + let region = if region.is_some() { + let mut region = client.lookup(region)?.region(); + if let Some(scale) = client.wire_scale.get() { + let rects: Vec<_> = region + .rects() + .iter() + .map(|r| { + Rect::new_sized( + r.x1() / scale, + r.y1() / scale, + r.width() / scale, + r.height() / scale, + ) + .unwrap() + }) + .collect(); + region = Region::from_rects(&rects); + } + Some(region) + } else { + None + }; + Ok(region) +} + impl ZwpPointerConstraintsV1 { fn create_constraint( &self, @@ -181,11 +206,7 @@ impl ZwpPointerConstraintsV1 { if surface.constraints.contains(&seat.id) { return Err(ZwpPointerConstraintsV1Error::AlreadyConstrained); } - let region = if region.is_some() { - Some(self.client.lookup(region)?.region()) - } else { - None - }; + let region = get_region(&self.client, region)?; let one_shot = match lifetime { LT_ONESHOT => true, LT_PERSISTENT => false, diff --git a/src/ifs/wl_seat/zwp_relative_pointer_v1.rs b/src/ifs/wl_seat/zwp_relative_pointer_v1.rs index a24cd8e9..4b869f7a 100644 --- a/src/ifs/wl_seat/zwp_relative_pointer_v1.rs +++ b/src/ifs/wl_seat/zwp_relative_pointer_v1.rs @@ -23,11 +23,12 @@ impl ZwpRelativePointerV1 { pub fn send_relative_motion( &self, time_usec: u64, - dx: Fixed, - dy: Fixed, + mut dx: Fixed, + mut dy: Fixed, dx_unaccelerated: Fixed, dy_unaccelerated: Fixed, ) { + logical_to_client_wire_scale!(self.client, dx, dy); self.client.event(RelativeMotion { self_id: self.id, utime_hi: (time_usec >> 32) as u32, diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index f8559cb2..9576067f 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -769,9 +769,13 @@ impl WlSurface { pub fn send_preferred_buffer_scale(&self) { if self.version >= BUFFER_SCALE_SINCE { + let factor = match self.client.wire_scale.is_some() { + true => 1, + false => self.output.get().global.legacy_scale.get() as _, + }; self.client.event(PreferredBufferScale { self_id: self.id, - factor: self.output.get().global.legacy_scale.get() as _, + factor, }); } } @@ -909,6 +913,22 @@ impl WlSurface { } Ok(()) } + + pub fn handle_xwayland_wire_scale_change(&self) { + self.send_preferred_buffer_scale(); + if let Some(fs) = self.fractional_scale.get() { + fs.send_preferred_scale(); + } + if let Some(xsurface) = self.ext.get().into_xsurface() { + if let Some(window) = xsurface.xwindow.get() { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::Configure(window)); + } + } + } } const MAX_DAMAGE: usize = 32; @@ -1093,7 +1113,7 @@ impl WlSurface { scale_changed || buffer_transform_changed || viewport_changed || alpha_changed; let mut buffer_changed = false; let mut old_raw_size = None; - let (dx, dy) = mem::take(&mut pending.offset); + let (mut dx, mut dy) = mem::take(&mut pending.offset); if let Some(buffer_change) = pending.buffer.take() { buffer_changed = true; if let Some(buffer) = self.buffer.take() { @@ -1132,6 +1152,8 @@ impl WlSurface { } } if self.buffer.is_some() && (dx, dy) != (0, 0) { + // This is somewhat problematic since we don't accumulate small changes. + client_wire_scale_to_logical!(self.client, dx, dy); self.buf_x.fetch_add(dx); self.buf_y.fetch_add(dy); self.need_extents_update.set(true); @@ -1210,7 +1232,8 @@ impl WlSurface { buffer_transform: self.buffer_transform.get(), }; let (buffer_width, buffer_height) = buffer.buffer.rect.size(); - let (dst_width, dst_height) = new_size.unwrap_or_default(); + let (mut dst_width, mut dst_height) = new_size.unwrap_or_default(); + client_wire_scale_to_logical!(self.client, dst_width, dst_height); let damage_matrix = DamageMatrix::new( self.buffer_transform.get(), self.buffer_scale.get(), @@ -1223,7 +1246,8 @@ impl WlSurface { self.damage_matrix.set(damage_matrix); } } - let (width, height) = new_size.unwrap_or_default(); + let (mut width, mut height) = new_size.unwrap_or_default(); + client_wire_scale_to_logical!(self.client, width, height); let (old_width, old_height) = buffer_abs_pos.size(); if (width, height) != (old_width, old_height) { self.need_extents_update.set(true); @@ -1360,6 +1384,13 @@ impl WlSurface { } for damage in &pending.surface_damage { let mut damage = damage.move_(pos.x1(), pos.y1()); + if let Some(scale) = self.client.wire_scale.get() { + let x1 = damage.x1() / scale; + let y1 = damage.y1() / scale; + let x2 = (damage.x2() + scale - 1) / scale; + let y2 = (damage.y2() + scale - 1) / scale; + damage = Rect::new(x1, y1, x2, y2).unwrap(); + } damage = damage.intersect(bounds.unwrap_or(pos)); self.client.state.damage(damage); } @@ -1406,12 +1437,13 @@ impl WlSurface { } } - fn accepts_input_at(&self, x: i32, y: i32) -> bool { + fn accepts_input_at(&self, mut x: i32, mut y: i32) -> bool { let rect = self.buffer_abs_pos.get().at_point(0, 0); if !rect.contains(x, y) { return false; } if let Some(ir) = self.input_region.get() { + logical_to_client_wire_scale!(self.client, x, y); if !ir.contains(x, y) { return false; } diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index 461dd84b..db788222 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -135,7 +135,8 @@ impl WlSubsurface { v.pending.set(false); self.node.borrow_mut().replace(v); } - if let Some((x, y)) = pending.position.take() { + if let Some((mut x, mut y)) = pending.position.take() { + client_wire_scale_to_logical!(self.surface.client, x, y); self.position .set(self.surface.buffer_abs_pos.get().at_point(x, y)); let (parent_x, parent_y) = self.parent.buffer_abs_pos.get().position(); diff --git a/src/ifs/wl_surface/wp_fractional_scale_v1.rs b/src/ifs/wl_surface/wp_fractional_scale_v1.rs index e3c841b0..0e95ff07 100644 --- a/src/ifs/wl_surface/wp_fractional_scale_v1.rs +++ b/src/ifs/wl_surface/wp_fractional_scale_v1.rs @@ -4,6 +4,8 @@ use { ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, + scale::Scale, + utils::cell_ext::CellExt, wire::{wp_fractional_scale_v1::*, WpFractionalScaleV1Id}, }, std::rc::Rc, @@ -38,17 +40,13 @@ impl WpFractionalScaleV1 { } pub fn send_preferred_scale(&self) { + let scale = match self.client.wire_scale.is_some() { + true => Scale::from_int(1), + false => self.surface.output.get().global.persistent.scale.get(), + }; self.client.event(PreferredScale { self_id: self.id, - scale: self - .surface - .output - .get() - .global - .persistent - .scale - .get() - .to_wl(), + scale: scale.to_wl(), }); } } diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 06416b8c..cc4b48f1 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -140,13 +140,12 @@ pub struct Xwindow { impl XwindowData { pub fn new(state: &Rc, event: &CreateNotify, client: &Rc) -> Self { - let extents = Rect::new_sized( - event.x as _, - event.y as _, - event.width as _, - event.height as _, - ) - .unwrap(); + let mut x = event.x as i32; + let mut y = event.y as i32; + let mut width = event.width as i32; + let mut height = event.height as i32; + client_wire_scale_to_logical!(client, x, y, width, height); + let extents = Rect::new_sized(x, y, width, height).unwrap(); // log::info!("xwin {} new {:?} or {}", event.window, extents, event.override_redirect); Self { state: state.clone(), diff --git a/src/ifs/zxdg_output_v1.rs b/src/ifs/zxdg_output_v1.rs index a698fb6e..6a45fd7e 100644 --- a/src/ifs/zxdg_output_v1.rs +++ b/src/ifs/zxdg_output_v1.rs @@ -24,7 +24,8 @@ pub struct ZxdgOutputV1 { } impl ZxdgOutputV1 { - pub fn send_logical_position(&self, x: i32, y: i32) { + pub fn send_logical_position(&self, mut x: i32, mut y: i32) { + logical_to_client_wire_scale!(self.client, x, y); self.client.event(LogicalPosition { self_id: self.id, x, @@ -32,7 +33,8 @@ impl ZxdgOutputV1 { }); } - pub fn send_logical_size(&self, width: i32, height: i32) { + pub fn send_logical_size(&self, mut width: i32, mut height: i32) { + logical_to_client_wire_scale!(self.client, width, height); self.client.event(LogicalSize { self_id: self.id, width, diff --git a/src/macros.rs b/src/macros.rs index 0761ecbf..5fed533d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -737,3 +737,23 @@ macro_rules! ei_object_base { } }; } + +macro_rules! logical_to_client_wire_scale { + ($client:expr, $($field:expr),+ $(,)?) => { + if let Some(scale) = $client.wire_scale.get() { + $( + $field = $field * scale; + )+ + } + }; +} + +macro_rules! client_wire_scale_to_logical { + ($client:expr, $($field:expr),+ $(,)?) => { + if let Some(scale) = $client.wire_scale.get() { + $( + $field = $field / scale; + )+ + } + }; +} diff --git a/src/state.rs b/src/state.rs index f9222f65..43ada8c1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -244,6 +244,8 @@ pub struct XWaylandState { pub handler: RefCell>>, pub queue: Rc>, pub ipc_device_ids: XIpcDeviceIds, + pub use_wire_scale: Cell, + pub wire_scale: Cell>, } pub struct IdleState { @@ -415,6 +417,7 @@ impl State { fn output_scales_changed(&self) { UpdateTextTexturesVisitor.visit_display(&self.root); self.reload_cursors(); + self.update_xwayland_wire_scale(); } fn cursor_sizes_changed(&self) { @@ -1215,6 +1218,36 @@ impl State { let dy = y1 - y2; dx * dx + dy * dy > self.ui_drag_threshold_squared.get() } + + pub fn update_xwayland_wire_scale(&self) { + let scale = self + .scales + .lock() + .iter() + .map(|v| v.0.round_up()) + .max() + .unwrap_or(1); + let wire_scale = match self.xwayland.use_wire_scale.get() { + true => Some(scale as i32), + false => None, + }; + self.xwayland.wire_scale.set(wire_scale); + for client in self.clients.clients.borrow().values() { + let client = &client.data; + if !client.is_xwayland { + continue; + } + if client.wire_scale.replace(wire_scale) == wire_scale { + continue; + } + for output in client.objects.outputs.lock().values() { + output.send_updates(); + } + for surface in client.objects.surfaces.lock().values() { + surface.handle_xwayland_wire_scale_change(); + } + } + } } #[derive(Debug, Error)] diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 38fdac8f..bb83e60a 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -332,7 +332,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(10), + version: s.jay_compositor.1.min(11), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/src/xwayland.rs b/src/xwayland.rs index db27efa3..2d64fa8a 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -182,6 +182,7 @@ async fn run( Ok(c) => c, Err(e) => return Err(XWaylandError::SpawnClient(e)), }; + state.update_xwayland_wire_scale(); state.ring.readable(&Rc::new(dfdread)).await?; state.xwayland.queue.clear(); { diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 1e635c4d..21e90132 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -913,13 +913,18 @@ impl Wm { async fn send_configure(&mut self, window: Rc) { let extents = window.data.info.extents.get(); // log::info!("xwin {} send_configure {:?}", window.data.window_id, extents); + let mut x = extents.x1(); + let mut y = extents.y1(); + let mut width = extents.width(); + let mut height = extents.height(); + logical_to_client_wire_scale!(self.client, x, y, width, height); let cw = ConfigureWindow { window: window.data.window_id, values: ConfigureWindowValues { - x: Some(extents.x1()), - y: Some(extents.y1()), - width: Some(extents.width() as u32), - height: Some(extents.height() as u32), + x: Some(x), + y: Some(y), + width: Some(width as u32), + height: Some(height as u32), border_width: Some(0), ..Default::default() }, @@ -2134,13 +2139,18 @@ impl Wm { if pending.width() > 0 && pending.height() > 0 { let dummy = Rect::new_sized(0, 0, 1, 1).unwrap(); for rect in [dummy, pending] { + let mut x = rect.x1(); + let mut y = rect.y1(); + let mut width = rect.width(); + let mut height = rect.height(); + logical_to_client_wire_scale!(self.client, x, y, width, height); let cw = ConfigureWindow { window: data.window_id, values: ConfigureWindowValues { - x: Some(rect.x1()), - y: Some(rect.y1()), - width: Some(rect.width() as _), - height: Some(rect.height() as _), + x: Some(x), + y: Some(y), + width: Some(width as _), + height: Some(height as _), ..Default::default() }, }; @@ -2213,13 +2223,12 @@ impl Wm { }; self.update_override_redirect(data, event.override_redirect); if data.info.override_redirect.get() { - let extents = Rect::new_sized( - event.x as _, - event.y as _, - event.width as _, - event.height as _, - ) - .unwrap(); + let mut x = event.x as i32; + let mut y = event.y as i32; + let mut width = event.width as i32; + let mut height = event.height as i32; + client_wire_scale_to_logical!(self.client, x, y, width, height); + let extents = Rect::new_sized(x, y, width, height).unwrap(); if let Some(window) = data.window.get() { window.tl_change_extents(&extents); self.state.tree_changed(); @@ -2248,15 +2257,19 @@ impl Wm { let mut height = de.height(); if event.value_mask.contains(CONFIG_WINDOW_X) { x1 = event.x as _; + client_wire_scale_to_logical!(self.client, x1); } if event.value_mask.contains(CONFIG_WINDOW_Y) { y1 = event.y as _; + client_wire_scale_to_logical!(self.client, y1); } if event.value_mask.contains(CONFIG_WINDOW_WIDTH) { width = event.width as _; + client_wire_scale_to_logical!(self.client, width); } if event.value_mask.contains(CONFIG_WINDOW_HEIGHT) { height = event.height as _; + client_wire_scale_to_logical!(self.client, height); } data.info .pending_extents diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index e5ce4e1a..563292a1 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -23,6 +23,7 @@ use { status::MessageFormat, theme::Color, video::{Format, GfxApi, TearingMode, Transform, VrrMode}, + xwayland::XScalingMode, Axis, Direction, Workspace, }, std::{ @@ -302,6 +303,11 @@ pub struct Vrr { pub cursor_hz: Option, } +#[derive(Debug, Clone)] +pub struct Xwayland { + pub scaling_mode: Option, +} + #[derive(Debug, Clone)] pub struct Tearing { pub mode: Option, @@ -349,6 +355,7 @@ pub struct Config { pub tearing: Option, pub libei: Libei, pub ui_drag: UiDrag, + pub xwayland: Option, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index ba16087c..57a8d41d 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -34,6 +34,7 @@ mod tearing; mod theme; mod ui_drag; mod vrr; +mod xwayland; #[derive(Debug, Error)] pub enum StringParserError { diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index efc9868a..3dfe32c5 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -27,6 +27,7 @@ use { theme::ThemeParser, ui_drag::UiDragParser, vrr::VrrParser, + xwayland::XwaylandParser, }, spanned::SpannedErrorExt, Action, Config, Libei, Theme, UiDrag, @@ -114,6 +115,7 @@ impl Parser for ConfigParser<'_> { tearing_val, libei_val, ui_drag_val, + xwayland_val, ), ) = ext.extract(( ( @@ -150,6 +152,7 @@ impl Parser for ConfigParser<'_> { opt(val("tearing")), opt(val("libei")), opt(val("ui-drag")), + opt(val("xwayland")), ), ))?; let mut keymap = None; @@ -350,6 +353,15 @@ impl Parser for ConfigParser<'_> { } } } + let mut xwayland = None; + if let Some(value) = xwayland_val { + match value.parse(&mut XwaylandParser(self.0)) { + Ok(v) => xwayland = Some(v), + Err(e) => { + log::warn!("Could not parse Xwayland setting: {}", self.0.error(e)); + } + } + } Ok(Config { keymap, repeat_rate, @@ -378,6 +390,7 @@ impl Parser for ConfigParser<'_> { tearing, libei, ui_drag, + xwayland, }) } } diff --git a/toml-config/src/config/parsers/xwayland.rs b/toml-config/src/config/parsers/xwayland.rs new file mode 100644 index 00000000..899e01d3 --- /dev/null +++ b/toml-config/src/config/parsers/xwayland.rs @@ -0,0 +1,75 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{opt, val, Extractor, ExtractorError}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + Xwayland, + }, + toml::{ + toml_span::{Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + jay_config::xwayland::XScalingMode, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum XwaylandParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), +} + +pub struct XwaylandParser<'a>(pub &'a Context<'a>); + +impl Parser for XwaylandParser<'_> { + type Value = Xwayland; + type Error = XwaylandParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let scaling_mode = ext.extract(opt(val("scaling-mode")))?; + let scaling_mode = scaling_mode.and_then(|m| match m.parse(&mut XScalingModeParser) { + Ok(m) => Some(m), + Err(e) => { + log::error!("Could not parse scaling mode: {}", self.0.error(e)); + None + } + }); + Ok(Xwayland { scaling_mode }) + } +} + +#[derive(Debug, Error)] +pub enum XScalingModeParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error("Unknown mode {0}")] + UnknownMode(String), +} + +struct XScalingModeParser; + +impl Parser for XScalingModeParser { + type Value = XScalingMode; + type Error = XScalingModeParserError; + const EXPECTED: &'static [DataType] = &[DataType::String]; + + fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { + let mode = match string { + "default" => XScalingMode::DEFAULT, + "downscaled" => XScalingMode::DOWNSCALED, + _ => return Err(XScalingModeParserError::UnknownMode(string.to_string()).spanned(span)), + }; + Ok(mode) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index ff3f5a9f..54ff7471 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -34,6 +34,7 @@ use { set_direct_scanout_enabled, set_gfx_api, set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode, Connector, DrmDevice, }, + xwayland::set_x_scaling_mode, }, std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc, time::Duration}, }; @@ -1061,6 +1062,11 @@ fn load_config(initial_load: bool, persistent: &Rc) { if let Some(threshold) = config.ui_drag.threshold { set_ui_drag_threshold(threshold); } + if let Some(xwayland) = config.xwayland { + if let Some(mode) = xwayland.scaling_mode { + set_x_scaling_mode(mode); + } + } } fn create_command(exec: &Exec) -> Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index ef77ef77..8c1fbfea 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -593,6 +593,10 @@ "ui-drag": { "description": "Configures the ui-drag settings.\n\n- Example:\n\n ```toml\n ui-drag = { enabled = false, threshold = 20 }\n ```\n", "$ref": "#/$defs/UiDrag" + }, + "xwayland": { + "description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n", + "$ref": "#/$defs/Xwayland" } }, "required": [] @@ -1399,6 +1403,25 @@ "variant2", "variant3" ] + }, + "XScalingMode": { + "type": "string", + "description": "The scaling mode of X windows.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n", + "enum": [ + "default", + "downscaled" + ] + }, + "Xwayland": { + "description": "Describes Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n", + "type": "object", + "properties": { + "scaling-mode": { + "description": "The scaling mode of X windows.", + "$ref": "#/$defs/XScalingMode" + } + }, + "required": [] } } } \ No newline at end of file diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 14d29ebf..5e8b3f44 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1166,6 +1166,18 @@ The table has the following fields: The value of this field should be a [UiDrag](#types-UiDrag). +- `xwayland` (optional): + + Configures the Xwayland settings. + + - Example: + + ```toml + xwayland = { scaling-mode = "downscaled" } + ``` + + The value of this field should be a [Xwayland](#types-Xwayland). + ### `Connector` @@ -3122,3 +3134,64 @@ The string should have one of the following values: + +### `XScalingMode` + +The scaling mode of X windows. + +- Example: + + ```toml + xwayland = { scaling-mode = "downscaled" } + ``` + +Values of this type should be strings. + +The string should have one of the following values: + +- `default`: + + The default mode. + + Currently this means that windows are rendered at the lowest scale and then upscaled + if necessary. + +- `downscaled`: + + Windows are rendered at the highest integer scale and then downscaled. + + This has significant performance implications unless the window is running on the + output with the highest scale and that scale is an integer scale. + + For example, on a 3840x2160 output with a 1.5 scale, a fullscreen window will be + rendered at 3840x2160 * 2 / 1.5 = 5120x2880 pixels and then downscaled to + 3840x2160. This overhead gets worse the lower the scale of the output is. + + Additionally, this mode requires the X window to scale its contents itself. In the + example above, you might achieve this by setting the environment variable + `GDK_SCALE=2`. + + + + +### `Xwayland` + +Describes Xwayland settings. + +- Example: + + ```toml + xwayland = { scaling-mode = "downscaled" } + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `scaling-mode` (optional): + + The scaling mode of X windows. + + The value of this field should be a [XScalingMode](#types-XScalingMode). + + diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index dd65fafc..2478b8a1 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -2275,6 +2275,17 @@ Config: ```toml ui-drag = { enabled = false, threshold = 20 } ``` + xwayland: + ref: Xwayland + required: false + description: | + Configures the Xwayland settings. + + - Example: + + ```toml + xwayland = { scaling-mode = "downscaled" } + ``` Idle: @@ -2627,3 +2638,53 @@ UiDrag: Sets the distance at which ui dragging starts. The default is `10`. + + +Xwayland: + kind: table + description: | + Describes Xwayland settings. + + - Example: + + ```toml + xwayland = { scaling-mode = "downscaled" } + ``` + fields: + scaling-mode: + ref: XScalingMode + required: false + description: The scaling mode of X windows. + + +XScalingMode: + description: | + The scaling mode of X windows. + + - Example: + + ```toml + xwayland = { scaling-mode = "downscaled" } + ``` + kind: string + values: + - value: default + description: | + The default mode. + + Currently this means that windows are rendered at the lowest scale and then upscaled + if necessary. + - value: downscaled + description: | + Windows are rendered at the highest integer scale and then downscaled. + + This has significant performance implications unless the window is running on the + output with the highest scale and that scale is an integer scale. + + For example, on a 3840x2160 output with a 1.5 scale, a fullscreen window will be + rendered at 3840x2160 * 2 / 1.5 = 5120x2880 pixels and then downscaled to + 3840x2160. This overhead gets worse the lower the scale of the output is. + + Additionally, this mode requires the X window to scale its contents itself. In the + example above, you might achieve this by setting the environment variable + `GDK_SCALE=2`. diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index c70e9fcc..5116178f 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -92,6 +92,10 @@ request create_ei_session (since = 5) { id: id(jay_ei_session_builder), } +request get_xwayland (since = 11) { + id: id(jay_xwayland), +} + # events event client_id { diff --git a/wire/jay_xwayland.txt b/wire/jay_xwayland.txt new file mode 100644 index 00000000..3018c9da --- /dev/null +++ b/wire/jay_xwayland.txt @@ -0,0 +1,18 @@ +# requests + +request get_scaling { +} + +request set_scaling_mode { + mode: u32, +} + +# events + +event scaling_mode { + mode: u32, +} + +event implied_scale { + scale: i32, +}