Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package xwayland-satellite for
openSUSE:Factory checked in at 2026-09-29 17:50:35
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/xwayland-satellite (Old)
and /work/SRC/openSUSE:Factory/.xwayland-satellite.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "xwayland-satellite"
Tue Sep 29 17:50:35 2026 rev:9 rq:1381270 version:0.8.3
Changes:
--------
--- /work/SRC/openSUSE:Factory/xwayland-satellite/xwayland-satellite.changes
2026-07-23 23:12:26.304694378 +0200
+++
/work/SRC/openSUSE:Factory/.xwayland-satellite.new.383539/xwayland-satellite.changes
2026-09-29 17:51:54.561542631 +0200
@@ -1,0 +2,7 @@
+Mon Sep 28 23:34:32 UTC 2026 - Muhammad Akbar Yanuar Mantari <[email protected]>
+
+- Update to version 0.8.3:
+ * Fix new Steam popups
+ * Fix mouse cursor offset and blur on windows with decorations
+
+-------------------------------------------------------------------
Old:
----
xwayland-satellite-0.8.2.tar.gz
New:
----
xwayland-satellite-0.8.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ xwayland-satellite.spec ++++++
--- /var/tmp/diff_new_pack.ic9Rtm/_old 2026-09-29 17:51:55.446579588 +0200
+++ /var/tmp/diff_new_pack.ic9Rtm/_new 2026-09-29 17:51:55.448579672 +0200
@@ -18,7 +18,7 @@
%bcond_with test
Name: xwayland-satellite
-Version: 0.8.2
+Version: 0.8.3
Release: 0
Summary: Rootless Xwayland integration for Wayland compositors
License: MPL-2.0
++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/xwayland-satellite/vendor.tar.zst
/work/SRC/openSUSE:Factory/.xwayland-satellite.new.383539/vendor.tar.zst
differ: char 6, line 1
++++++ xwayland-satellite-0.8.2.tar.gz -> xwayland-satellite-0.8.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/xwayland-satellite-0.8.2/.github/ISSUE_TEMPLATE/bug_report.yaml
new/xwayland-satellite-0.8.3/.github/ISSUE_TEMPLATE/bug_report.yaml
--- old/xwayland-satellite-0.8.2/.github/ISSUE_TEMPLATE/bug_report.yaml
2026-07-22 02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/.github/ISSUE_TEMPLATE/bug_report.yaml
2026-09-24 07:14:09.000000000 +0200
@@ -20,8 +20,16 @@
id: problem
attributes:
label: Describe the issue.
- value: |
- **Please attach a log if applicable.**
+ description: |
+ Provide a debug log file.
+ If integrated with the compositor with `systemd` as the init system,
logs can be retrieved by running
+
+ `journalctl --grep=xwayland-sat --output=short-iso-precise -b >
"$(date -Iseconds).log"`.
+
+ If integrated with a different init system, the logs will be collected
by the configured `syslog` daemon.
+ See your distribution's/`syslog` daemon's documentation for where the
logs are kept (likely somewhere in `/var/log`).
+
+ If run directly, the logs are output to the console.
validations:
required: true
- type: textarea
@@ -30,7 +38,14 @@
label: Steps to reproduce
- type: checkboxes
attributes:
- label: Please confirm you have reproduced this on the latest commit.
+ label: Confirm you have reproduced this on the latest commit and
attached log files.
+ description: "
+ To test the latest commit, build it according to the directions in the
README, then launch it manually with
+ ```
+ ./xwayland-satellite -- :12
+ DISPLAY=:12 your-application
+ ```
+ "
options:
- label: I have reproduced this on the latest commit
required: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/Cargo.lock
new/xwayland-satellite-0.8.3/Cargo.lock
--- old/xwayland-satellite-0.8.2/Cargo.lock 2026-07-22 02:24:52.000000000
+0200
+++ new/xwayland-satellite-0.8.3/Cargo.lock 2026-09-24 07:14:09.000000000
+0200
@@ -400,6 +400,17 @@
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
+name = "hostname"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "windows-link",
+]
+
+[[package]]
name = "humantime"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -832,6 +843,18 @@
]
[[package]]
+name = "syslog"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "019f1500a13379b7d051455df397c75770de6311a7a188a699499502704d9f10"
+dependencies = [
+ "hostname",
+ "libc",
+ "log",
+ "time",
+]
+
+[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1241,7 +1264,7 @@
[[package]]
name = "xwayland-satellite"
-version = "0.8.2"
+version = "0.8.3"
dependencies = [
"ab_glyph",
"anyhow",
@@ -1256,6 +1279,7 @@
"rustix",
"sd-notify",
"smithay-client-toolkit",
+ "syslog",
"testwl",
"tiny-skia",
"vergen-gitcl",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/Cargo.toml
new/xwayland-satellite-0.8.3/Cargo.toml
--- old/xwayland-satellite-0.8.2/Cargo.toml 2026-07-22 02:24:52.000000000
+0200
+++ new/xwayland-satellite-0.8.3/Cargo.toml 2026-09-24 07:14:09.000000000
+0200
@@ -17,7 +17,7 @@
[package]
name = "xwayland-satellite"
-version = "0.8.2"
+version = "0.8.3"
authors = ["Shawn Wallace"]
license = "MPL-2.0"
description = "xwayland-satellite grants rootless Xwayland integration to any
Wayland compositor implementing xdg_wm_base and viewporter. This is
particularly useful for compositors that (understandably) do not want to go
through implementing support for rootless Xwayland themselves."
@@ -40,6 +40,7 @@
wl_drm = { path = "wl_drm" }
log = "0.4.21"
pretty_env_logger = "0.5.0"
+syslog = "7.0.0"
xcb-util-cursor = "0.4"
smithay-client-toolkit = { version = "0.20.0", default-features = false }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/README.md
new/xwayland-satellite-0.8.3/README.md
--- old/xwayland-satellite-0.8.2/README.md 2026-07-22 02:24:52.000000000
+0200
+++ new/xwayland-satellite-0.8.3/README.md 2026-09-24 07:14:09.000000000
+0200
@@ -4,38 +4,41 @@
Found a bug? [Open a bug
report.](https://github.com/Supreeeme/xwayland-satellite/issues/new?template=bug_report.yaml)
-Need help troubleshooting, or have some other general question? [Ask on GitHub
Discussions.](https://github.com/Supreeeme/xwayland-satellite/discussions)
+## Building
-## Dependencies
+xwayland-satellite has the following dependencies:
- Xwayland >=23.1
- xcb
- xcb-util-cursor
+- cargo (building only)
- clang (building only)
+- git (building only, optional)
-## Usage
-Run `xwayland-satellite`. You can specify an X display to use (i.e. `:12`). Be
sure to set the same `DISPLAY` environment variable for any X11 clients.
-Because xwayland-satellite is a Wayland client (in addition to being a Wayland
compositor), it will need to launch after your compositor launches, but
obviously before any X11 applications.
-
-## Java applications
-Some (most?) Java applications may present themselves as a blank screen by
default with satellite. To fix this, simply set the environment variable
-`_JAVA_AWT_WM_NONREPARENTING=1` before launching it to fix this. Unfortunately
there is not a way for satellite to automatically do this.
-
-## Building
```
# dev build
cargo build
# release build
cargo build --release
-
-# run - will also build if not already built
-cargo run # --release
```
-## Systemd support
-xwayland-satellite can be built with systemd support - simply add `-F systemd`
to your build command - i.e. `cargo build --release -F systemd`.
+In addition, xwayland-satellite also has the following optional features,
which can be enabled by adding the `-F <feature>` flag to the `cargo build`
command:
+
+### `systemd`
+With the `systemd` feature enabled, xwayland-satellite will send a state
change notification when Xwayland has been initialized, allowing for services
to depend on satellite's startup. This feature can be enabled without systemd
installed, though it does nothing.
+
+### `fontconfig`
+The `fontconfig` feature uses fontconfig to load Open Sans at runtime instead
of compiling Open Sans directly into the binary. The Open Sans font and
`fontconfig` are runtime depenedencies when this feature is enabled.
+
+## Running
-With systemd support, satellite will send a state change notification when
Xwayland has been initialized, allowing for having services dependent on
satellite's startup.
+Because xwayland-satellite is a Wayland client (in addition to being a Wayland
compositor), it will need to launch after your compositor launches, but
obviously before any X11 applications.
+
+The `RUST_LOG` environment variable can be set to `debug` to output higher
levels of logging.
+
+### Manually
+Run the `xwayland-satellite` binary, or use `cargo run` (with or without
`--release`). You can specify an X display to use (i.e. `:12`). Be sure to set
the same `DISPLAY` environment variable for any X11 clients. Setting the
display allows you to choose which instance of `satellite` the X11 client
communicates with, which can be useful for debugging crashes or bypassing
automatically started instances (see below).
+### Systemd support
An example service file is located in `resources/xwayland-satellite.service` -
be sure to replace the `ExecStart` line with the proper location before using
it.
It can be placed in a systemd user unit directory (i.e.
`$XDG_CONFIG_HOME/systemd/user` or `/etc/systemd/user`),
and be launched and enabled with `systemctl --user enable --now
xwayland-satellite`.
@@ -44,17 +47,17 @@
Note that you *do not* need the systemd service if the compositor already
integrates xwayland-satellite (see below).
-## Scaling/HiDPI
-For most GTK and Qt apps, xwayland-satellite should automatically scale them
properly. Note that for mixed DPI monitor setups, satellite will choose
-the smallest monitor's DPI, meaning apps may have small text on other monitors.
+### Compositor integration
+Satellite supports passing through the `-listenfd` Xwayland argument. What
this means is you can integrate satellite
+(and by extension Xwayland) into your compositor, and do things like on demand
activation. Note that you *must* pass
+a display number to satellite as the first argument, and then the `-listenfd`
argument.
-Other miscellaneous apps (such as Wine apps) may have small text on HiDPI
displays. It is application dependent on getting apps to scale properly with
satellite,
-so you will have to figure out what app specific config needs to be set. See
[the Arch Wiki on HiDPI](https://wiki.archlinux.org/title/HiDPI) for a good
place start.
+You can view [Niri's implementation of this
integration](https://github.com/YaLTeR/niri/pull/1728/files) for understanding
+how it should work.
-Satellite acts as an Xsettings manager for setting scaling related settings,
but will get out of the way of other Xsettings managers.
-To manually set these settings, try
[xsettingsd](https://codeberg.org/derat/xsettingsd) or another Xsettings
manager.
+If `satellite` is integrated into your compositor, updating either
`xwayland-satellite` or your compositor's satellite settings requires
restarting the compositor. To test latest commit and/or patches without
restarting the compositor, add a display when launching `xwayland-satellite`
and use the same `DISPLAY` environment variables for applications you wish to
test.
-## Wayland protocols used
+## Wayland protocols
The host compositor **must** implement the following protocols/interfaces for
satellite to function:
- Core interfaces (wl_output, wl_surface, wl_compositor, etc)
- xdg_shell (xdg_wm_base, xdg_surface, xdg_popup, xdg_toplevel)
@@ -63,15 +66,27 @@
Additionally, satellite can *optionally* take advantage of the following
protocols:
- Linux dmabuf
- XDG activation
+- XDG decoration
- XDG foreign
- Pointer constraints
+- Primary selection
- Tablet input
- Fractional scale
-## Compositor integration
-Satellite supports passing through the `-listenfd` Xwayland argument. What
this means is you can integrate satellite
-(and by extension Xwayland) into your compositor, and do things like on demand
activation. Note that you *must* pass
-a display number to satellite as the first argument, and then the `-listenfd`
argument.
+## Troubleshooting
-You can view [Niri's implementation of this
integration](https://github.com/YaLTeR/niri/pull/1728/files) for understanding
-how it should work.
+Need help troubleshooting, or have some other general question? [Ask on GitHub
Discussions.](https://github.com/Supreeeme/xwayland-satellite/discussions)
+
+### Java applications
+Some Java applications may present themselves as a blank screen by default
with satellite. To fix this, simply set the environment variable
+`_JAVA_AWT_WM_NONREPARENTING=1` before launching it to fix this. Although
`satellite` cannot automatically do this, you can configure Java to do this by
adding `set _JAVA_AWT_WM_NONREPARENTING=1` to `/etc/profile.d/jre.sh`.
+
+### Scaling/HiDPI
+For most GTK and Qt apps, xwayland-satellite should automatically scale them
properly. Note that for mixed DPI monitor setups, satellite will choose
+the smallest monitor's DPI, meaning apps may have small text on other monitors.
+
+Other miscellaneous apps (such as Wine apps) may have small text on HiDPI
displays. It is application dependent on getting apps to scale properly with
satellite,
+so you will have to figure out what app specific config needs to be set. See
[the Arch Wiki on HiDPI](https://wiki.archlinux.org/title/HiDPI) for a good
place start.
+
+Satellite acts as an Xsettings manager for setting scaling related settings,
but will get out of the way of other Xsettings managers.
+To manually set these settings, try
[xsettingsd](https://codeberg.org/derat/xsettingsd) or another Xsettings
manager.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/lib.rs
new/xwayland-satellite-0.8.3/src/lib.rs
--- old/xwayland-satellite-0.8.2/src/lib.rs 2026-07-22 02:24:52.000000000
+0200
+++ new/xwayland-satellite-0.8.3/src/lib.rs 2026-09-24 07:14:09.000000000
+0200
@@ -20,6 +20,7 @@
fn set_window_dims(&mut self, window: x::Window, dims:
PendingSurfaceState) -> bool;
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool);
fn focus_window(&mut self, window: x::Window, output_name: Option<String>);
+ fn send_take_focus(&mut self, window: x::Window);
fn close_window(&mut self, window: x::Window);
fn unmap_window(&mut self, window: x::Window);
fn raise_to_top(&mut self, window: x::Window);
@@ -110,6 +111,8 @@
// Now that Xwayland spawned and got the listenfds, we can close them here.
drop(fds);
+ // Close our copy of the WM socket so Xwayland exiting produces EOF on
xsock_wl.
+ drop(xsock_xwl);
let xwl_pid = xwayland.id();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/main.rs
new/xwayland-satellite-0.8.3/src/main.rs
--- old/xwayland-satellite-0.8.2/src/main.rs 2026-07-22 02:24:52.000000000
+0200
+++ new/xwayland-satellite-0.8.3/src/main.rs 2026-09-24 07:14:09.000000000
+0200
@@ -1,11 +1,9 @@
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
fn main() {
- pretty_env_logger::formatted_timed_builder()
- .filter_level(log::LevelFilter::Info)
- .parse_default_env()
- .init();
- xwayland_satellite::main(parse_args());
+ let arg_data = parse_args();
+ init_logger(!arg_data.listenfds.is_empty());
+ xwayland_satellite::main(arg_data);
}
#[derive(Default)]
@@ -246,3 +244,33 @@
data
}
+
+fn init_logger(integrated: bool) {
+ if integrated {
+ match syslog::unix(syslog::Formatter3164::default()) {
+ Ok(syslog) => {
+
log::set_boxed_logger(Box::new(syslog::BasicLogger::new(syslog))).unwrap();
+ log::set_max_level(log::LevelFilter::Debug);
+ std::panic::set_hook(Box::new(|info| {
+ let thread = std::thread::current();
+ let name = thread.name().unwrap_or("<unnamed");
+ let location = info.location().unwrap();
+ log::error!("thread '{name}' panicked at {location}:");
+ if let Some(s) = info.payload().downcast_ref::<&str>() {
+ log::error!("{s}");
+ } else if let Some(s) =
info.payload().downcast_ref::<String>() {
+ log::error!("{s}");
+ }
+ }));
+ return;
+ }
+ Err(e) => {
+ eprintln!("failed to start syslog: {e:?}");
+ }
+ };
+ }
+ pretty_env_logger::formatted_timed_builder()
+ .filter_level(log::LevelFilter::Info)
+ .parse_default_env()
+ .init();
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/server/dispatch.rs
new/xwayland-satellite-0.8.3/src/server/dispatch.rs
--- old/xwayland-satellite-0.8.2/src/server/dispatch.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/server/dispatch.rs 2026-09-24
07:14:09.000000000 +0200
@@ -234,7 +234,7 @@
client,
server,
viewport,
- scale: SurfaceScaleFactor(1.0),
+ scale: SurfaceScaleFactor(state.current_scale),
},
);
if let Some(f) = fractional {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/server/event.rs
new/xwayland-satellite-0.8.3/src/server/event.rs
--- old/xwayland-satellite-0.8.2/src/server/event.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/server/event.rs 2026-09-24
07:14:09.000000000 +0200
@@ -295,7 +295,7 @@
&SurfaceScaleFactor,
&x::Window,
&mut WindowData,
- &mut SurfaceRole,
+ &SurfaceRole,
)>();
let (scale_factor, window, window_data, role) =
query.get().unwrap();
@@ -307,8 +307,19 @@
} else {
window_data.attrs.dims.width
};
- let mut height = if pending.height > 0 {
- (pending.height as f64 * scale_factor.0) as u16
+ let height = if pending.height > 0 {
+ let mut logical_height = pending.height;
+ if let SurfaceRole::Toplevel(Some(toplevel)) = role {
+ if let Some(d) = &toplevel.decoration.satellite {
+ let surface_width = (width as f64 /
scale_factor.0).ceil() as i32;
+ if d.will_draw_decorations(surface_width) {
+ logical_height = (logical_height
+ - DecorationsDataSatellite::TITLEBAR_HEIGHT)
+
.max(DecorationsDataSatellite::TITLEBAR_HEIGHT);
+ }
+ }
+ }
+ (logical_height as f64 * scale_factor.0) as u16
} else {
window_data.attrs.dims.height
};
@@ -317,20 +328,6 @@
data.get::<&WlSurface>().unwrap().id(),
);
- if let SurfaceRole::Toplevel(Some(toplevel)) = &*role {
- if let Some(d) = &toplevel.decoration.satellite {
- let surface_width = (width as f64 / scale_factor.0) as i32;
- if d.will_draw_decorations(surface_width) {
- height = height
- .saturating_sub(
- (DecorationsDataSatellite::TITLEBAR_HEIGHT as
f64 * scale_factor.0)
- as u16,
- )
- .max(DecorationsDataSatellite::TITLEBAR_HEIGHT as
u16);
- }
- }
- }
-
window_data.attrs.dims = WindowDims {
x: x as i16,
y: y as i16,
@@ -457,6 +454,7 @@
window,
output_name: None,
is_popup: true,
+ has_take_focus: window_data.attrs.has_take_focus,
});
}
}
@@ -505,32 +503,33 @@
debug!("{} viewport: {width}x{height}", surface.id());
if let Some(hints) = size_hints {
- let Some(data) = toplevel_data else {
- return;
- };
-
- let decorations_height = if data.decoration.satellite.is_some() {
- DecorationsDataSatellite::TITLEBAR_HEIGHT
- } else {
- 0
+ if let Some(data) = toplevel_data {
+ update_size_hints(data, hints, scale_factor.0);
};
+ }
+}
- if let Some(min) = hints.min_size {
- debug!(
- "updated min height: {}",
- (min.height as f64 / scale_factor.0) as i32 +
decorations_height
- );
- data.toplevel.set_min_size(
- (min.width as f64 / scale_factor.0) as i32,
- (min.height as f64 / scale_factor.0) as i32 +
decorations_height,
- );
- }
- if let Some(max) = hints.max_size {
- data.toplevel.set_max_size(
- (max.width as f64 / scale_factor.0) as i32,
- (max.height as f64 / scale_factor.0) as i32 +
decorations_height,
- );
- }
+pub(super) fn update_size_hints(data: &ToplevelData, hints: &WmNormalHints,
scale: f64) {
+ let decorations_height = if data.decoration.satellite.is_some() {
+ DecorationsDataSatellite::TITLEBAR_HEIGHT
+ } else {
+ 0
+ };
+ if let Some(min_size) = &hints.min_size {
+ data.toplevel.set_min_size(
+ (min_size.width as f64 / scale) as i32,
+ ((min_size.height as f64 / scale) as
i32).saturating_add(decorations_height),
+ );
+ } else {
+ data.toplevel.set_min_size(0, 0);
+ }
+ if let Some(max_size) = &hints.max_size {
+ data.toplevel.set_max_size(
+ (max_size.width as f64 / scale) as i32,
+ ((max_size.height as f64 / scale) as
i32).saturating_add(decorations_height),
+ );
+ } else {
+ data.toplevel.set_max_size(0, 0);
}
}
@@ -869,10 +868,13 @@
serial,
));
let output_name = get_output_name(output, &state.world);
+ let window_data = data.get::<&WindowData>();
+ let has_take_focus = window_data.as_ref().is_some_and(|d|
d.attrs.has_take_focus);
state.to_focus = Some(FocusData {
window: *window,
output_name,
is_popup: false,
+ has_take_focus,
});
keyboard.enter(serial, surface, keys);
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/server/mod.rs
new/xwayland-satellite-0.8.3/src/server/mod.rs
--- old/xwayland-satellite-0.8.2/src/server/mod.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/server/mod.rs 2026-09-24
07:14:09.000000000 +0200
@@ -101,9 +101,10 @@
#[derive(Default, Debug)]
struct WindowAttributes {
- acquire_input_via_wm: bool,
+ accepts_input: bool,
has_take_focus: bool,
role: WindowRole,
+ override_redirect: bool,
dims: WindowDims,
size_hints: Option<WmNormalHints>,
title: Option<WmName>,
@@ -114,9 +115,8 @@
}
impl WindowAttributes {
- /// AKA "Passive" input model
fn require_wm_focus(&self) -> bool {
- self.acquire_input_via_wm && !self.has_take_focus
+ !self.override_redirect && (self.has_take_focus || self.accepts_input)
}
}
@@ -140,6 +140,7 @@
mapped: false,
attrs: WindowAttributes {
role: WindowRole::new_basic(override_redirect),
+ override_redirect,
dims,
..Default::default()
},
@@ -398,6 +399,7 @@
window: x::Window,
output_name: Option<String>,
is_popup: bool,
+ has_take_focus: bool,
}
#[derive(Copy, Clone, Default)]
@@ -422,6 +424,9 @@
fn focus_window(&mut self, _: x::Window, _: Option<String>) {
debug!("could not focus window without XWayland initialized");
}
+ fn send_take_focus(&mut self, _: x::Window) {
+ debug!("could not send take focus without XWayland initialized");
+ }
fn close_window(&mut self, _: x::Window) {
debug!("could not close window without XWayland initialized");
}
@@ -485,6 +490,7 @@
global_offset_updated: bool,
updated_outputs: Vec<Entity>,
new_scale: Option<f64>,
+ current_scale: f64,
}
impl<S: X11Selection> ServerState<NoConnection<S>> {
@@ -593,6 +599,7 @@
global_offset_updated: false,
updated_outputs: Vec::new(),
new_scale: None,
+ current_scale: 1.0,
decoration_manager,
world,
};
@@ -731,6 +738,7 @@
debug!("Using new scale {scale}");
self.new_scale = Some(scale);
+ self.current_scale = scale;
}
}
@@ -739,15 +747,20 @@
window,
output_name,
is_popup,
+ has_take_focus,
}) = self.to_focus.take()
{
debug!(
- "focusing {} {window:?}",
+ "focusing (take_focus={has_take_focus:?}) {} {window:?}",
if is_popup { "popup" } else { "window" }
);
- self.connection.focus_window(window, output_name);
- if !is_popup {
- self.last_focused_toplevel = Some(window);
+ if has_take_focus {
+ self.connection.send_take_focus(window);
+ } else {
+ self.connection.focus_window(window, output_name);
+ if !is_popup {
+ self.last_focused_toplevel = Some(window);
+ }
}
} else if self.unfocus {
self.connection.focus_window(x::WINDOW_NONE, None);
@@ -885,9 +898,24 @@
self.windows.insert(window, id);
}
+ pub fn set_override_redirect(&mut self, window: x::Window,
override_redirect: bool) {
+ let Some(id) = self.windows.get(&window).copied() else {
+ debug!(
+ "not setting override_redirect {override_redirect} for unknown
window {window:?}"
+ );
+ return;
+ };
+
+ self.world
+ .get::<&mut WindowData>(id)
+ .unwrap()
+ .attrs
+ .override_redirect = override_redirect;
+ }
+
pub fn set_window_role(&mut self, window: x::Window, role: WindowRole) {
let Some(id) = self.windows.get(&window).copied() else {
- debug!("not setting popup for unknown window {window:?}");
+ debug!("not setting role {role:?} for unknown window {window:?}");
return;
};
@@ -966,7 +994,7 @@
let attrs = &mut self.world.get::<&mut WindowData>(id).unwrap().attrs;
attrs.group = hints.window_group;
- attrs.acquire_input_via_wm = hints.acquire_input_via_wm;
+ attrs.accepts_input = hints.accepts_input;
}
pub fn set_take_focus(&mut self, window: x::Window, has_take_focus: bool) {
@@ -996,27 +1024,7 @@
debug!("setting {window:?} hints {hints:?}");
let mut query = data.query::<(&SurfaceRole,
&SurfaceScaleFactor)>();
if let Some((SurfaceRole::Toplevel(Some(data)), scale_factor)) =
query.get() {
- let decorations_height = if
data.decoration.satellite.is_some() {
- DecorationsDataSatellite::TITLEBAR_HEIGHT
- } else {
- 0
- };
- if let Some(min_size) = &hints.min_size {
- data.toplevel.set_min_size(
- (min_size.width as f64 / scale_factor.0) as i32,
- (min_size.height as f64 / scale_factor.0) as i32 +
decorations_height,
- );
- } else {
- data.toplevel.set_min_size(0, 0);
- }
- if let Some(max_size) = &hints.max_size {
- data.toplevel.set_max_size(
- (max_size.width as f64 / scale_factor.0) as i32,
- (max_size.height as f64 / scale_factor.0) as i32 +
decorations_height,
- );
- } else {
- data.toplevel.set_max_size(0, 0);
- }
+ event::update_size_hints(data, &hints, scale_factor.0);
}
win.attrs.size_hints = Some(hints);
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/server/tests.rs
new/xwayland-satellite-0.8.3/src/server/tests.rs
--- old/xwayland-satellite-0.8.2/src/server/tests.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/server/tests.rs 2026-09-24
07:14:09.000000000 +0200
@@ -160,6 +160,7 @@
#[derive(Default)]
struct FakeXConnection {
focused_window: Option<Window>,
+ send_take_focus_window: Option<Window>,
windows: HashMap<Window, WindowData>,
set_window_dims_counter: usize,
}
@@ -234,6 +235,15 @@
self.focused_window = window.into();
}
+ #[track_caller]
+ fn send_take_focus(&mut self, window: x::Window) {
+ assert!(
+ self.windows.contains_key(&window),
+ "Unknown window: {window:?}"
+ );
+ self.send_take_focus_window = window.into();
+ }
+
fn raise_to_top(&mut self, window: Window) {
assert!(
self.windows.contains_key(&window),
@@ -741,6 +751,10 @@
assert!(surface_data.buffer.is_some());
}
+ let scale = self.satellite.current_scale;
+ let expected_width = (100.0 * scale) as u16;
+ let expected_height = (100.0 * scale) as u16;
+
let win_data = self.connection().windows.get(&window).map(|d| &d.dims);
assert!(
matches!(
@@ -748,9 +762,9 @@
Some(&super::WindowDims {
x: 0,
y: 0,
- width: 100,
- height: 100
- })
+ width,
+ height
+ }) if width == expected_width && height == expected_height
),
"Incorrect window geometry: {win_data:?}"
);
@@ -855,6 +869,55 @@
}
#[track_caller]
+ fn setup_popup_focus_case(
+ &mut self,
+ comp: &Compositor,
+ override_redirect: bool,
+ accepts_input: bool,
+ take_focus: bool,
+ ) -> (Window, Window) {
+ let win_toplevel = Window::new(1);
+ self.create_toplevel(comp, win_toplevel);
+
+ let win_popup = Window::new(2);
+ let (buffer, surface) = comp.create_surface();
+ let data = WindowData {
+ mapped: true,
+ dims: WindowDims {
+ x: 10,
+ y: 20,
+ width: 100,
+ height: 50,
+ },
+ fullscreen: false,
+ };
+ self.new_window(win_popup, override_redirect, data);
+ if !override_redirect {
+ self.satellite
+ .set_window_role(win_popup, crate::xstate::WindowRole::Popup);
+ }
+
+ self.satellite.set_win_hints(
+ win_popup,
+ super::WmHints {
+ window_group: None,
+ accepts_input,
+ },
+ );
+ if take_focus {
+ self.satellite.set_take_focus(win_popup, true);
+ }
+ self.map_window(comp, win_popup, &surface.obj, &buffer);
+ self.run();
+
+ let popup_id = self.check_new_surface();
+ self.testwl.configure_popup(popup_id);
+ self.run();
+
+ (win_toplevel, win_popup)
+ }
+
+ #[track_caller]
fn assert_window_dimensions(
&self,
window: x::Window,
@@ -1338,7 +1401,7 @@
win,
super::WmHints {
window_group: Some(prop_win),
- acquire_input_via_wm: false,
+ accepts_input: false,
},
);
f.satellite.map_window(win);
@@ -1643,103 +1706,51 @@
}
#[test]
-fn popup_no_focus_without_input_hint() {
- let (mut f, comp) = TestFixture::new_with_compositor();
-
- let win_toplevel = Window::new(1);
- let (_, toplevel_id) = f.create_toplevel(&comp, win_toplevel);
+fn popup_override_redirect_never_focused_nor_offered() {
+ for accepts_input in [true, false] {
+ for take_focus in [false, true] {
+ let (mut f, comp) = TestFixture::new_with_compositor();
+
+ let (win_toplevel, _) =
+ f.setup_popup_focus_case(&comp, true, accepts_input,
take_focus);
+ assert_eq!(
+ f.connection().focused_window,
+ Some(win_toplevel),
+ "accepts_input={accepts_input:?} take_focus={take_focus}"
+ );
+ assert_eq!(
+ f.connection().send_take_focus_window,
+ None,
+ "accepts_input={accepts_input:?} take_focus={take_focus}"
+ );
+ }
+ }
+}
- // A popup without acquire_input_via_wm should not receive focus.
- let win_popup = Window::new(2);
- f.create_popup(
- &comp,
- PopupBuilder::new(win_popup, win_toplevel, toplevel_id),
- );
- assert_eq!(f.connection().focused_window, Some(win_toplevel));
+#[test]
+fn popup_send_take_focus_when_advertised() {
+ for accepts_input in [true, false] {
+ let (mut f, comp) = TestFixture::new_with_compositor();
+
+ let (win_toplevel, win_popup) = f.setup_popup_focus_case(&comp, false,
accepts_input, true);
+ assert_eq!(f.connection().focused_window, Some(win_toplevel));
+ assert_eq!(f.connection().send_take_focus_window, Some(win_popup));
+ }
}
#[test]
fn popup_focus_on_map_with_input_hint() {
let (mut f, comp) = TestFixture::new_with_compositor();
- let win_toplevel = Window::new(1);
- let (_, toplevel_id) = f.create_toplevel(&comp, win_toplevel);
-
- // A popup with acquire_input_via_wm should receive X11 keyboard focus
- // at map time, matching what a real X11 WM does for windows with
- // WM_HINTS input=True.
- let win_popup = Window::new(2);
- let (buffer, surface) = comp.create_surface();
- let dims = WindowDims {
- x: 10,
- y: 20,
- width: 100,
- height: 50,
- };
- let data = WindowData {
- mapped: true,
- dims,
- fullscreen: false,
- };
- f.new_window(win_popup, true, data);
- f.satellite.set_win_hints(
- win_popup,
- super::WmHints {
- window_group: None,
- acquire_input_via_wm: true,
- },
- );
- f.map_window(&comp, win_popup, &surface.obj, &buffer);
- f.run();
-
- let popup_id = f.check_new_surface();
- assert_ne!(popup_id, toplevel_id);
-
- f.testwl.configure_popup(popup_id);
- f.run();
-
- // Focus should have been given directly to the popup at map time.
+ let (_, win_popup) = f.setup_popup_focus_case(&comp, false, true, false);
assert_eq!(f.connection().focused_window, Some(win_popup));
}
#[test]
-fn popup_no_focus_input_hint_wm_take_focus() {
+fn popup_no_focus_without_input_hint() {
let (mut f, comp) = TestFixture::new_with_compositor();
- let win_toplevel = Window::new(1);
- let (_, toplevel_id) = f.create_toplevel(&comp, win_toplevel);
-
- let win_popup = Window::new(2);
- let (buffer, surface) = comp.create_surface();
- let dims = WindowDims {
- x: 10,
- y: 20,
- width: 100,
- height: 50,
- };
- let data = WindowData {
- mapped: true,
- dims,
- fullscreen: false,
- };
- f.new_window(win_popup, true, data);
- f.satellite.set_win_hints(
- win_popup,
- super::WmHints {
- window_group: None,
- acquire_input_via_wm: true,
- },
- );
- f.satellite.set_take_focus(win_popup, true);
- f.map_window(&comp, win_popup, &surface.obj, &buffer);
- f.run();
-
- let popup_id = f.check_new_surface();
- assert_ne!(popup_id, toplevel_id);
-
- f.testwl.configure_popup(popup_id);
- f.run();
-
+ let (win_toplevel, _) = f.setup_popup_focus_case(&comp, false, false,
false);
assert_eq!(f.connection().focused_window, Some(win_toplevel));
}
@@ -3069,6 +3080,25 @@
assert_eq!(f.satellite.inner.new_scale, Some(2.0));
}
+#[track_caller]
+fn check_window_and_viewport_size(
+ f: &TestFixture<FakeXConnection>,
+ window: Window,
+ id: testwl::SurfaceId,
+ width: u16,
+ height: u16,
+) {
+ let dims = f.connection().window(window).dims;
+ assert_eq!((dims.width, dims.height), (width, height), "X window size");
+ let data = f.testwl.get_surface_data(id).unwrap();
+ let viewport = data.viewport.as_ref().unwrap();
+ assert_eq!(
+ (viewport.width, viewport.height),
+ (width as i32, height as i32),
+ "viewport size"
+ );
+}
+
#[test]
fn client_side_decorations() {
let (mut f, compositor) = TestFixture::new_with_compositor();
@@ -3079,16 +3109,8 @@
f.testwl.configure_toplevel(id, 100, 100, vec![]);
f.run();
- let data = f.connection().window(window);
- assert_eq!(
- data.dims,
- WindowDims {
- x: 0,
- y: 0,
- width: 100,
- height: 75
- }
- );
+ check_window_and_viewport_size(&f, window, id, 100, 75);
+
let subsurface_id = f.testwl.last_created_surface_id().unwrap();
assert_ne!(subsurface_id, id);
let data = f.testwl.get_surface_data(subsurface_id).unwrap();
@@ -3103,16 +3125,8 @@
f.testwl
.configure_toplevel(id, 100, 100,
vec![xdg_toplevel::State::Fullscreen]);
f.run();
- let data = f.connection().window(window);
- assert_eq!(
- data.dims,
- WindowDims {
- x: 0,
- y: 0,
- width: 100,
- height: 100
- }
- );
+ check_window_and_viewport_size(&f, window, id, 100, 100);
+
let data = f.testwl.get_surface_data(subsurface_id).unwrap();
assert!(data.buffer.is_none());
@@ -3128,21 +3142,58 @@
f.testwl.configure_toplevel(id, 100, 100, vec![]);
f.run();
- let data = f.connection().window(window);
- assert_eq!(
- data.dims,
- WindowDims {
- x: 0,
- y: 0,
- width: 100,
- height: 100
- }
- );
+ check_window_and_viewport_size(&f, window, id, 100, 100);
assert!(f.testwl.get_surface_data(subsurface_id).is_none());
assert!(!subsurface.is_alive());
}
#[test]
+fn client_side_decorations_fractional_scale() {
+ use super::decoration::DecorationsDataSatellite;
+
+ let mut f = TestFixture::new_pre_connect(|testwl| {
+ testwl.enable_fractional_scale();
+ });
+ let compositor = f.compositor();
+ let (_, output) = f.new_output(0, 0);
+ let window = Window::new(1);
+ let (_, id) = f.create_toplevel(&compositor, window);
+ f.testwl.move_surface_to_output(id, &output);
+ f.testwl
+ .force_decoration_mode(id,
zxdg_toplevel_decoration_v1::Mode::ClientSide);
+ f.run();
+
+ let content_height = 100.0 -
f64::from(DecorationsDataSatellite::TITLEBAR_HEIGHT);
+ let scales: [(f64, u32); 2] = [(1.5, 180), (1.25, 150)];
+ for (scale, preferred) in scales {
+ let data = f.testwl.get_surface_data(id).unwrap();
+ data.fractional
+ .as_ref()
+ .expect("Missing fractional scale")
+ .preferred_scale(preferred);
+ f.testwl.configure_toplevel(id, 100, 100, vec![]);
+ f.run();
+ f.run();
+
+ let x_width = (100.0 * scale).floor() as u16;
+ let x_height = (content_height * scale) as u16;
+ let dims = f.connection().window(window).dims;
+ assert_eq!(
+ (dims.width, dims.height),
+ (x_width, x_height),
+ "X window size at scale {scale}"
+ );
+ let data = f.testwl.get_surface_data(id).unwrap();
+ let viewport = data.viewport.as_ref().unwrap();
+ assert_eq!(
+ (viewport.width, viewport.height),
+ (100, content_height as i32),
+ "viewport size at scale {scale}"
+ );
+ }
+}
+
+#[test]
fn client_side_decorations_no_global() {
let mut f = TestFixture::new_pre_connect(|testwl| {
testwl.disable_decorations_global();
@@ -3201,16 +3252,8 @@
f.testwl.configure_toplevel(id, 100, 100, vec![]);
f.run();
- let data = f.connection().window(window);
- assert_eq!(
- data.dims,
- WindowDims {
- x: 0,
- y: 0,
- width: 100,
- height: 75
- }
- );
+ check_window_and_viewport_size(&f, window, id, 100, 75);
+
let subsurface_id = f.testwl.last_created_surface_id().unwrap();
assert_ne!(subsurface_id, id);
let data = f.testwl.get_surface_data(subsurface_id).unwrap();
@@ -3224,6 +3267,12 @@
data.role
);
+ // Weston seems to do this when a window is deactivated. Since the stored
window data's height
+ // is used as a fallback in this case, do not reapply the height reduction
from the titlebar.
+ f.testwl.configure_toplevel(id, 0, 0, vec![]);
+ f.run();
+ check_window_and_viewport_size(&f, window, id, 100, 75);
+
let dims = WindowDims {
x: 0,
y: 0,
@@ -3256,6 +3305,41 @@
.set_win_title(window, WmName::WmName("window".into()));
}
+#[test]
+fn decorations_max_height_int_max() {
+ let (mut f, compositor) = TestFixture::new_with_compositor();
+ let window = Window::new(1);
+ let (_, id) = f.create_toplevel(&compositor, window);
+ f.testwl
+ .force_decoration_mode(id,
zxdg_toplevel_decoration_v1::Mode::ClientSide);
+ f.testwl.configure_toplevel(id, 1, 100, vec![]);
+ f.run();
+
+ // When calculating the Wayland max size from the X size hints, the
titlebar height is added to
+ // the height hint, but obviously this should not overflow the size hint.
+ f.satellite.set_size_hints(
+ window,
+ super::WmNormalHints {
+ min_size: None,
+ max_size: Some(WinSize {
+ width: i32::MAX,
+ height: i32::MAX,
+ }),
+ },
+ );
+ f.run();
+
+ let data = f.testwl.get_surface_data(id).unwrap();
+ let toplevel = data.toplevel();
+ assert_eq!(
+ toplevel.max_size,
+ Some(testwl::Vec2 {
+ x: i32::MAX,
+ y: i32::MAX
+ })
+ );
+}
+
/// See Pointer::handle_event for an explanation.
#[test]
fn popup_pointer_motion_workaround() {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/xstate/mod.rs
new/xwayland-satellite-0.8.3/src/xstate/mod.rs
--- old/xwayland-satellite-0.8.2/src/xstate/mod.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/xstate/mod.rs 2026-09-24
07:14:09.000000000 +0200
@@ -4,6 +4,7 @@
use selection::{Selection, SelectionState};
#[cfg(test)]
mod tests;
+mod xresources;
use crate::XConnection;
use bitflags::bitflags;
@@ -653,6 +654,7 @@
let transient_for = self.get_transient_for(window)?;
let override_redirect = self.get_override_redirect(window)?;
+ server_state.set_override_redirect(window, override_redirect);
let window_types = self
.get_net_wm_window_types(window)?
@@ -917,6 +919,7 @@
incr => b"INCR" only_if_exists = false,
xsettings => b"_XSETTINGS_S0" only_if_exists = false,
xsettings_settings => b"_XSETTINGS_SETTINGS" only_if_exists = false,
+ resource_manager => b"RESOURCE_MANAGER" only_if_exists = false,
primary => b"PRIMARY" only_if_exists = false,
primary_targets => b"_primary_targets" only_if_exists = false,
moveresize => b"_NET_WM_MOVERESIZE" only_if_exists = false,
@@ -1010,7 +1013,7 @@
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct WmHints {
pub window_group: Option<x::Window>,
- pub acquire_input_via_wm: bool,
+ pub accepts_input: bool,
}
impl From<&[u32]> for WmHints {
@@ -1022,8 +1025,11 @@
let window = x::Window::new(value[8]);
ret.window_group = Some(window);
}
- if flags.contains(WmHintsFlags::Input) {
- ret.acquire_input_via_wm = value[1] == 1;
+
+ // if input hint isn't present, set it to true anyways in case the
client is misbehaving
+ ret.accepts_input = true;
+ if flags.contains(WmHintsFlags::Input) && value[1] == 0 {
+ ret.accepts_input = false;
}
ret
@@ -1189,7 +1195,9 @@
match ty {
x if x == window_atoms.normal => return WindowRole::Toplevel,
x if x == window_atoms.dialog => {
- return WindowRole::new_basic(self.has_transient_for &&
motif_no_decor);
+ return WindowRole::new_basic(
+ self.has_transient_for && motif_no_decor &&
forced_size,
+ );
}
x if x == window_atoms.utility => {
return WindowRole::new_basic(motif_no_decor &&
forced_size);
@@ -1429,6 +1437,22 @@
}
}
+ fn send_take_focus(&mut self, window: x::Window) {
+ let resource_id = self.atoms.wm_take_focus.resource_id();
+ let data = [resource_id, x::CURRENT_TIME, 0, 0, 0];
+ let event = &x::ClientMessageEvent::new(
+ window,
+ self.atoms.wm_protocols,
+ x::ClientMessageData::Data32(data),
+ );
+
unwrap_or_skip_bad_window_ret!(self.connection.send_and_check_request(&x::SendEvent
{
+ destination: x::SendEventDest::Window(window),
+ propagate: false,
+ event_mask: x::EventMask::empty(),
+ event,
+ }));
+ }
+
fn close_window(&mut self, window: x::Window) {
let cookie = self.connection.send_request(&x::GetProperty {
window,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/xstate/settings.rs
new/xwayland-satellite-0.8.3/src/xstate/settings.rs
--- old/xwayland-satellite-0.8.2/src/xstate/settings.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/xstate/settings.rs 2026-09-24
07:14:09.000000000 +0200
@@ -38,6 +38,7 @@
data: &self.settings.as_data(),
})
.unwrap();
+ self.update_xft_dpi_resource(self.settings.xft_dpi());
}
}
@@ -185,4 +186,9 @@
last_change_serial: self.serial,
});
}
+
+ fn xft_dpi(&self) -> i32 {
+ let setting = self.settings.get(XFT_DPI).unwrap();
+ (setting.value as f64 / DPI_SCALE_FACTOR as f64).round() as i32
+ }
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/xstate/tests.rs
new/xwayland-satellite-0.8.3/src/xstate/tests.rs
--- old/xwayland-satellite-0.8.2/src/xstate/tests.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/src/xstate/tests.rs 2026-09-24
07:14:09.000000000 +0200
@@ -473,6 +473,33 @@
assert_eq!(win.guess_window_role(&win_types), WindowRole::Popup);
}
+ // https://github.com/Supreeeme/xwayland-satellite/issues/470
+ // A DIALOG with TRANSIENT_FOR and no Motif decorations which is resizable
+ // (min_size != max_size) should be a top-level, mirroring the UTILITY fix
in #383.
+ // Otherwise the popup parent resolves to a hidden WM_CLIENT_LEADER and the
+ // window never appears (e.g. DaVinci Resolve's Project Manager).
+ // Window attributes are taken from the full xprop posted in #470 (DIALOG +
+ // NORMAL window types, _MOTIF_WM_HINTS decorations=0, resizable normal
hints).
+ #[test]
+ fn davinci_resolve_project_manager() {
+ let win_types = WindowTypes::new();
+ let wm_normal_hints = WmNormalHints::new()
+ .user_pos(978, 270)
+ .user_size(1884, 1620)
+ .min_size(910, 640)
+ .max_size(2880, 1620)
+ .win_gravity(Gravity::Static);
+ let win = WindowRoleHeuristics {
+ has_transient_for: true,
+ motif_wm_hints: Some(motif::Hints::from([0x2_u32, 0x1, 0, 0,
0].as_slice())),
+ window_types: vec![win_types.dialog, win_types.normal],
+ wm_normal_hints: Some(wm_normal_hints.into()),
+ wm_class: Some("resolve".into()),
+ ..Default::default()
+ };
+ assert_eq!(win.guess_window_role(&win_types), WindowRole::Toplevel);
+ }
+
// https://github.com/Supreeeme/xwayland-satellite/pull/390
// With Motif decorations, even a dialog with TRANSIENT_FOR is better
treated as a top-level
#[test]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/src/xstate/xresources.rs
new/xwayland-satellite-0.8.3/src/xstate/xresources.rs
--- old/xwayland-satellite-0.8.2/src/xstate/xresources.rs 1970-01-01
01:00:00.000000000 +0100
+++ new/xwayland-satellite-0.8.3/src/xstate/xresources.rs 2026-09-24
07:14:09.000000000 +0200
@@ -0,0 +1,70 @@
+use super::XState;
+use log::warn;
+use xcb::x;
+
+impl XState {
+ pub(super) fn update_xft_dpi_resource(&self, dpi: i32) {
+ // Other clients may replace the resource database so don't cache its
contents
+ let reply = self
+ .connection
+ .wait_for_reply(self.connection.send_request(&x::GetProperty {
+ delete: false,
+ window: self.root,
+ property: self.atoms.resource_manager,
+ r#type: x::ATOM_STRING,
+ long_offset: 0,
+ long_length: u32::MAX,
+ }))
+ .unwrap();
+
+ let resources = match reply.r#type() {
+ x::ATOM_NONE => &[],
+ x::ATOM_STRING => reply.value::<u8>(),
+ other => {
+ warn!("RESOURCE_MANAGER has unexpected type {other:?}");
+ return;
+ }
+ };
+
+ let xft_dpi = format!("Xft.dpi:\t{dpi}");
+ let mut updated = Vec::with_capacity(resources.len() + xft_dpi.len() +
1);
+ let mut replaced = false;
+
+ for line in resources.split_inclusive(|byte| *byte == b'\n') {
+ let resource_name = line
+ .iter()
+ .position(|byte| *byte == b':')
+ .map(|separator| line[..separator].trim_ascii());
+ match resource_name {
+ Some(b"Xft.dpi") => {
+ if !replaced {
+ updated.extend_from_slice(xft_dpi.as_bytes());
+ if line.ends_with(b"\n") {
+ updated.push(b'\n');
+ }
+ replaced = true;
+ }
+ }
+ _ => updated.extend_from_slice(line),
+ }
+ }
+
+ if !replaced {
+ if !updated.is_empty() && !updated.ends_with(b"\n") {
+ updated.push(b'\n');
+ }
+ updated.extend_from_slice(xft_dpi.as_bytes());
+ updated.push(b'\n');
+ }
+
+ self.connection
+ .send_and_check_request(&x::ChangeProperty {
+ window: self.root,
+ mode: x::PropMode::Replace,
+ property: self.atoms.resource_manager,
+ r#type: x::ATOM_STRING,
+ data: &updated,
+ })
+ .unwrap();
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/tests/integration.rs
new/xwayland-satellite-0.8.3/tests/integration.rs
--- old/xwayland-satellite-0.8.2/tests/integration.rs 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/tests/integration.rs 2026-09-24
07:14:09.000000000 +0200
@@ -223,6 +223,21 @@
window: x::Window,
surface: testwl::SurfaceId,
) {
+ self.configure_and_verify_new_toplevel_with_scale(connection, window,
surface, 1.0);
+ }
+
+ fn configure_and_verify_new_toplevel_with_scale(
+ &mut self,
+ connection: &mut Connection,
+ window: x::Window,
+ surface: testwl::SurfaceId,
+ scale: f32,
+ ) {
+ let unscaled_geo = connection.get_reply(&x::GetGeometry {
+ drawable: x::Drawable::Window(window),
+ });
+ let (u_width, u_height) = (unscaled_geo.width(),
unscaled_geo.height());
+
let data = self.testwl.get_surface_data(surface).unwrap();
assert!(
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
@@ -230,18 +245,22 @@
data.role
);
- self.testwl
- .configure_toplevel(surface, 100, 100,
vec![xdg_toplevel::State::Activated]);
+ self.testwl.configure_toplevel(
+ surface,
+ u_width as _,
+ u_height as _,
+ vec![xdg_toplevel::State::Activated],
+ );
self.testwl.focus_toplevel(surface);
self.wait_and_dispatch();
+
let geometry = connection.get_reply(&x::GetGeometry {
drawable: x::Drawable::Window(window),
});
-
assert_eq!(geometry.x(), 0);
assert_eq!(geometry.y(), 0);
- assert_eq!(geometry.width(), 100);
- assert_eq!(geometry.height(), 100);
+ assert_eq!(geometry.width(), (u_width as f32 * scale) as _);
+ assert_eq!(geometry.height(), (u_height as f32 * scale) as _);
}
#[track_caller]
@@ -250,13 +269,23 @@
connection: &mut Connection,
window: x::Window,
) -> testwl::SurfaceId {
+ self.map_as_toplevel_with_scale(connection, window, 1.0)
+ }
+
+ #[track_caller]
+ fn map_as_toplevel_with_scale(
+ &mut self,
+ connection: &mut Connection,
+ window: x::Window,
+ scale: f32,
+ ) -> testwl::SurfaceId {
connection.map_window(window);
self.wait_and_dispatch();
let surface = self
.testwl
.last_created_surface_id()
.expect("No surface created");
- self.configure_and_verify_new_toplevel(connection, window, surface);
+ self.configure_and_verify_new_toplevel_with_scale(connection, window,
surface, scale);
surface
}
@@ -352,6 +381,7 @@
incr => b"INCR",
xsettings => b"_XSETTINGS_S0",
xsettings_setting => b"_XSETTINGS_SETTINGS",
+ resource_manager => b"RESOURCE_MANAGER",
moveresize => b"_NET_WM_MOVERESIZE",
}
}
@@ -672,6 +702,19 @@
data: settings,
}
}
+
+ fn get_resource_manager(&self) -> Vec<u8> {
+ self.get_reply(&x::GetProperty {
+ delete: false,
+ window: self.root,
+ property: self.atoms.resource_manager,
+ r#type: x::ATOM_STRING,
+ long_offset: 0,
+ long_length: u32::MAX,
+ })
+ .value()
+ .to_vec()
+ }
}
#[test]
@@ -1986,7 +2029,7 @@
let mut conn = Connection::new(&f.display);
let window = conn.new_window(conn.root, 0, 0, 200, 200, false);
- let surface = f.map_as_toplevel(&mut conn, window);
+ let surface = f.map_as_toplevel_with_scale(&mut conn, window, 2.0);
f.testwl.move_surface_to_output(surface, &output);
f.testwl.move_pointer_to(surface, 30.0, 40.0);
f.wait_and_dispatch();
@@ -2150,6 +2193,46 @@
);
}
+#[test]
+fn resource_manager_scale() {
+ let mut f = Fixture::new_preset(|testwl| {
+ testwl.enable_fractional_scale();
+ });
+ let mut connection = Connection::new(&f.display);
+ f.testwl.enable_xdg_output_manager();
+ let output = f.create_output(0, 0);
+
+ assert_eq!(connection.get_resource_manager(), b"Xft.dpi:\t96\n");
+ connection.set_property(
+ connection.root,
+ x::ATOM_STRING,
+ connection.atoms.resource_manager,
+ b"Xcursor.theme:\tAdwaita\nXft.dpi:\t96\n",
+ );
+
+ let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
+ let surface = f.map_as_toplevel(&mut connection, window);
+ let data = f.testwl.get_surface_data(surface).unwrap();
+ let fractional = data.fractional.as_ref().unwrap();
+
+ fractional.preferred_scale(180); // 1.5 scale
+ f.testwl.move_surface_to_output(surface, &output);
+ f.wait_and_dispatch();
+ assert_eq!(
+ connection.get_resource_manager(),
+ b"Xcursor.theme:\tAdwaita\nXft.dpi:\t144\n"
+ );
+
+ let data = f.testwl.get_surface_data(surface).unwrap();
+ let fractional = data.fractional.as_ref().unwrap();
+ fractional.preferred_scale(300); // 2.5 scale
+ f.wait_and_dispatch();
+ assert_eq!(
+ connection.get_resource_manager(),
+ b"Xcursor.theme:\tAdwaita\nXft.dpi:\t240\n"
+ );
+}
+
#[test]
fn xsettings_switch_owner() {
let f = Fixture::new();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xwayland-satellite-0.8.2/xwayland-satellite.man
new/xwayland-satellite-0.8.3/xwayland-satellite.man
--- old/xwayland-satellite-0.8.2/xwayland-satellite.man 2026-07-22
02:24:52.000000000 +0200
+++ new/xwayland-satellite-0.8.3/xwayland-satellite.man 2026-09-24
07:14:09.000000000 +0200
@@ -66,6 +66,7 @@
Force the use of the given rendering API for Glamor acceleration. Valid
parameters are \fIgl\fR to use OpenGL, \fIes\fR to use GL ES, and \fInone\fR to
use the shared memory backend. For maximum compatibility with
.BR Xwayland (1)
builds without the Glamor backend, the \fInone\fR option is converted to the
\fI\-shm\fR flag under the hood.
+.TP
\fB\-help\fR
Prints a basic usage message and list of accepted flags and returns 0.
.TP