Compositor incorrectly marks screensaver window as obscured on HiDPI multi-monitor, breaking animation
To be transparent, this issue report was written by Claude Code (it is obviously AI). I have examined the report myself and it seems to me to be accurate. I have a PR that fixes the issue ready — will submit momentarily.
Summary
xfwm4 compositor incorrectly marks fullscreen screensaver window as obscured on HiDPI scaled multi-monitor setup, causing xfce4-screensaver animation to only display on one monitor.
Environment
- xfwm4 version: 4.20.0 (tested both packaged and latest git master from 2025-11-15)
- xfce4-screensaver version: 4.20.1-1
- OS: Debian testing
- Desktop: XFCE 4.20
-
Display configuration:
- HDMI-0: 5461x2880 (primary) at position 3840+0
- DP-2: 3840x2160 at position 0+343
-
HiDPI scaling: 2x (
/xsettings/Gdk/WindowScalingFactor: 2)
Bug Description
When xfce4-screensaver activates with an animated screensaver, the compositor incorrectly sends GDK_VISIBILITY_FULLY_OBSCURED events to the fullscreen screensaver window on the primary monitor (HDMI-0). This causes xfce4-screensaver to immediately stop the animation job for that window, leaving only the secondary monitor (DP-2) displaying the screensaver animation. The primary monitor shows only a black screen.
Steps to Reproduce
- Set up dual monitors with 2x HiDPI scaling on at least one monitor
- Enable xfwm4 compositor
- Configure xfce4-screensaver with an animated screensaver (e.g., popsquares)
- Set screensaver delay to 1 minute
- Wait for screensaver to activate
Expected: Both monitors show screensaver animation Actual: Secondary monitor shows animation, primary monitor is black
Debug Evidence
From xfce4-screensaver debug output (xfce4-screensaver --debug):
[gs_manager_create_window_for_monitor] Creating a Window [1920,0] (2731x1440) for monitor HDMI-0
[gs_manager_create_window_for_monitor] Creating a Window [0,171] (1920x1080) for monitor DP-2
[window_show_cb] Handling window show
[gs_job_set_command] Setting command for job: '/usr/libexec/xfce4-screensaver/popsquares'
[window_map_event_cb] Handling window map_event event
[manager_maybe_start_job_for_window] Starting job for window
[gs_job_start] Starting job
[window_obscured_cb] Handling window obscured: obscured
[gs_job_stop] Stopping job
[gs_job_died] Job finished
The critical issue: immediately after starting the job for the HDMI-0 window, a window_obscured_cb event marks it as obscured, stopping the animation.
Root Cause Analysis
xfce4-screensaver uses GDK visibility events (GDK_VISIBILITY_FULLY_OBSCURED) via gs_window_real_visibility_notify_event() in src/gs-window.c to detect if its fullscreen windows are covered. The xfwm4 compositor appears to be incorrectly reporting one of the fullscreen screensaver windows as obscured in HiDPI scaled multi-monitor configurations.
The visibility detection code in xfce4-screensaver (gs-window.c):
static gboolean
gs_window_real_visibility_notify_event (GtkWidget *widget,
GdkEventVisibility *event) {
switch (event->state) {
case GDK_VISIBILITY_FULLY_OBSCURED:
gs_window_set_obscured (GS_WINDOW (widget), TRUE);
break;
case GDK_VISIBILITY_PARTIAL:
break;
case GDK_VISIBILITY_UNOBSCURED:
gs_window_set_obscured (GS_WINDOW (widget), FALSE);
break;
}
return FALSE;
}
Workarounds That Work
- Disable xfwm4 compositor entirely
- Use picom compositor instead of xfwm4 compositor (works perfectly with no issues)
- Use blank screen mode instead of animated screensaver
Additional Information
- The issue is reproducible with latest git master (commit as of 2025-11-15)
- Window geometries are correctly detected (scaled coordinates match expected values)
- Both screensaver windows are created and mapped properly
- The issue appears to be compositor-specific as picom does not trigger the false obscured event
- xdotool confirms both fullscreen windows exist at correct positions and sizes:
- Window at X=3840 Y=0 WIDTH=5462 HEIGHT=2880 (HDMI-0)
- Window at X=0 Y=342 WIDTH=3840 HEIGHT=2160 (DP-2)
Root Cause - Coordinate Space Mismatch (2025-11-15 Investigation)
The bug is caused by a 1-pixel coordinate mismatch between GDK and X11/RandR in HiDPI configurations:
The Mismatch
GDK reports (after scaling by 2):
- HDMI-0: 3840,0 5462×2880
- DP-2: 0,342 3840×2160
X11/RandR reports:
- HDMI-0: 3840+0 5461×2880 (1 pixel narrower!)
- DP-2: 0+343 3840×2160 (1 pixel lower!)
Verified with:
python3 -c "
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
display = Gdk.Display.get_default()
for i in range(display.get_n_monitors()):
monitor = display.get_monitor(i)
geo = monitor.get_geometry()
scale = monitor.get_scale_factor()
print(f'Monitor {i}: Logical {geo.x},{geo.y} {geo.width}x{geo.height} → Physical {geo.x*scale},{geo.y*scale} {geo.width*scale}x{geo.height*scale}')
"
The Bug Flow
-
xfce4-screensaver creates windows in logical pixels (2731×1440 for HDMI-0)
-
GTK/GDK maps to X11 which uses X11/RandR geometry → window becomes 5461 pixels wide
-
xfwm4 compositor checks fullscreen in
src/compositor.c:254(is_fullscreen()function) -
is_fullscreen()compares:- Window attributes from X11:
cw->attr(XGetWindowAttributes) = 5461 width - Monitor geometry from GDK:
rectfrommyScreenFindMonitorAtPoint()= 5462 width
- Window attributes from X11:
-
Exact comparison fails:
return ((cw->attr.x == rect.x) && (cw->attr.y == rect.y) && (cw->attr.width + 2 * cw->attr.border_width == rect.width) && (cw->attr.height + 2 * cw->attr.border_width == rect.height));- HDMI-0: 5461 ≠ 5462 → returns FALSE
-
Window not recognized as fullscreen → compositor sends incorrect
GDK_VISIBILITY_FULLY_OBSCURED -
xfce4-screensaver stops animation
Code Analysis
common/xfwm-common.c:87 - xfwm_get_monitor_geometry():
gboolean xfwm_get_monitor_geometry (GdkScreen *screen, gint monitor_num,
GdkRectangle *geometry, gboolean scaled) {
scale = gdk_monitor_get_scale_factor (monitor);
gdk_monitor_get_geometry (monitor, geometry); // Returns LOGICAL pixels
if (scaled && scale != 1)
xfwm_geometry_convert_to_device_pixels (geometry, scale); // Multiply by scale
return TRUE;
}
When scaled=TRUE, this multiplies GDK's logical pixels by the scale factor, but GDK's logical pixels don't always match X11/RandR's physical pixels exactly due to rounding.
src/screen.c:804 - myScreenFindMonitorAtPoint() calls with scaled=TRUE src/compositor.c:3324 - Window attributes from XGetWindowAttributes() (always physical pixels)
Proper Fix Implemented (2025-11-15)
Status: FIXED
A proper fix has been implemented that uses X11 RandR to query monitor geometry directly in physical pixels, avoiding GDK's rounding errors entirely.
Solution: Created new function myScreenFindMonitorAtPointPhysical() in src/screen.c that:
- Uses
XRRGetScreenResources()andXRRGetCrtcInfo()to query CRTC geometry - Returns exact physical pixel coordinates matching X11/RandR
- Avoids GDK's logical pixel calculation and rounding
- Falls back to GDK method if RandR query fails
Changes:
-
src/screen.c:838-917- NewmyScreenFindMonitorAtPointPhysical()function -
src/screen.h:301-304- Function declaration added -
src/compositor.c:264-269- Modifiedis_fullscreen()to use new function
Testing: Tested successfully on dual HiDPI (2x scaling) setup:
- Both monitors now display screensaver animation correctly
- No
GDK_VISIBILITY_FULLY_OBSCUREDevents sent incorrectly - Screensaver jobs run on both monitors without being stopped
Branch: fix-hidpi-screensaver-rounding Commit: 06cb5a057
The root issue was mixing coordinate spaces: X11 window attributes (always physical) vs GDK monitor geometry (logical, then scaled). The fix ensures both use physical pixels from X11/RandR.