1
0
Fork 0
forked from wry/wry

Merge pull request #287 from mahkoh/jorth/xwayland-downscaling

xwayland: allow windows to scale themselves
This commit is contained in:
mahkoh 2024-10-10 12:10:24 +02:00 committed by GitHub
commit 3f9b75e470
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 800 additions and 80 deletions

View file

@ -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<Connector>, mode: VrrMode) {
self.send(&ClientMessage::SetVrrMode { connector, mode })
}

View file

@ -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)]

View file

@ -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)]

View file

@ -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)
}

View file

@ -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)

View file

@ -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(),
}

106
src/cli/xwayland.rs Normal file
View file

@ -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<XwaylandCmd>,
}
#[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<ToolClient>,
}
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;
}
}

View file

@ -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<u64, Rc<WlSurface>>,
pub activation_tokens: RefCell<VecDeque<ActivationToken>>,
pub commit_timelines: Rc<CommitTimelines>,
pub wire_scale: Cell<Option<i32>>,
}
pub const NUM_CACHED_SERIAL_RANGES: usize = 64;

View file

@ -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(),

View file

@ -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 {

View file

@ -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<i32> for Fixed {
}
}
impl Mul<i32> for Fixed {
type Output = Self;
fn mul(self, rhs: i32) -> Self::Output {
Self(self.0 * rhs)
}
}
impl Div<i32> 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;

View file

@ -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;

View file

@ -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<Self>) -> 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! {

84
src/ifs/jay_xwayland.rs Normal file
View file

@ -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<Client>,
pub tracker: Tracker<Self>,
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<Self>) -> Result<(), Self::Error> {
self.send_scaling_mode();
self.send_implied_scale();
Ok(())
}
fn set_scaling_mode(&self, req: SetScalingMode, _slf: &Rc<Self>) -> 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<ClientError>),
#[error("Unknown scaling mode {}", .0)]
UnknownMode(u32),
}
efrom!(JayXwaylandError, ClientError);

View file

@ -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<Self>) {
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);
}

View file

@ -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<Self>) -> Result<(), Self::Error> {
fn set_cursor(&self, mut req: SetCursor, _slf: &Rc<Self>) -> 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);

View file

@ -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<Self>) -> Result<(), Self::Error> {
fn set_cursor(&self, mut req: SetCursor, _slf: &Rc<Self>) -> 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);

View file

@ -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,

View file

@ -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<Option<Rc<Region>>, 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,

View file

@ -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,

View file

@ -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;
}

View file

@ -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();

View file

@ -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(),
});
}
}

View file

@ -140,13 +140,12 @@ pub struct Xwindow {
impl XwindowData {
pub fn new(state: &Rc<State>, event: &CreateNotify, client: &Rc<Client>) -> 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(),

View file

@ -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,

View file

@ -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;
)+
}
};
}

View file

@ -244,6 +244,8 @@ pub struct XWaylandState {
pub handler: RefCell<Option<SpawnedFuture<()>>>,
pub queue: Rc<AsyncQueue<XWaylandEvent>>,
pub ipc_device_ids: XIpcDeviceIds,
pub use_wire_scale: Cell<bool>,
pub wire_scale: Cell<Option<i32>>,
}
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)]

View file

@ -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));

View file

@ -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();
{

View file

@ -913,13 +913,18 @@ impl Wm {
async fn send_configure(&mut self, window: Rc<Xwindow>) {
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

View file

@ -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<f64>,
}
#[derive(Debug, Clone)]
pub struct Xwayland {
pub scaling_mode: Option<XScalingMode>,
}
#[derive(Debug, Clone)]
pub struct Tearing {
pub mode: Option<TearingMode>,
@ -349,6 +355,7 @@ pub struct Config {
pub tearing: Option<Tearing>,
pub libei: Libei,
pub ui_drag: UiDrag,
pub xwayland: Option<Xwayland>,
}
#[derive(Debug, Error)]

View file

@ -34,6 +34,7 @@ mod tearing;
mod theme;
mod ui_drag;
mod vrr;
mod xwayland;
#[derive(Debug, Error)]
pub enum StringParserError {

View file

@ -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,
})
}
}

View file

@ -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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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<Self> {
let mode = match string {
"default" => XScalingMode::DEFAULT,
"downscaled" => XScalingMode::DOWNSCALED,
_ => return Err(XScalingModeParserError::UnknownMode(string.to_string()).spanned(span)),
};
Ok(mode)
}
}

View file

@ -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<PersistentState>) {
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 {

View file

@ -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": []
}
}
}

View file

@ -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).
<a name="types-Connector"></a>
### `Connector`
@ -3122,3 +3134,64 @@ The string should have one of the following values:
<a name="types-XScalingMode"></a>
### `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`.
<a name="types-Xwayland"></a>
### `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).

View file

@ -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`.

View file

@ -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 {

18
wire/jay_xwayland.txt Normal file
View file

@ -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,
}