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 1. Set up dual monitors with 2x HiDPI scaling on at least one monitor 2. Enable xfwm4 compositor 3. Configure xfce4-screensaver with an animated screensaver (e.g., popsquares) 4. Set screensaver delay to 1 minute 5. 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): ```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 1. **Disable xfwm4 compositor** entirely 2. **Use picom compositor** instead of xfwm4 compositor (works perfectly with no issues) 3. **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: ```bash 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 1. **xfce4-screensaver creates windows** in logical pixels (2731×1440 for HDMI-0) 2. **GTK/GDK maps to X11** which uses X11/RandR geometry → window becomes 5461 pixels wide 3. **xfwm4 compositor checks fullscreen** in `src/compositor.c:254` (`is_fullscreen()` function) 4. **`is_fullscreen()` compares:** - Window attributes from X11: `cw->attr` (XGetWindowAttributes) = 5461 width - Monitor geometry from GDK: `rect` from `myScreenFindMonitorAtPoint()` = 5462 width 5. **Exact comparison fails:** ```c 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 6. **Window not recognized as fullscreen** → compositor sends incorrect `GDK_VISIBILITY_FULLY_OBSCURED` 7. **xfce4-screensaver stops animation** ### Code Analysis **`common/xfwm-common.c:87`** - `xfwm_get_monitor_geometry()`: ```c 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** :white_check_mark: 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()` and `XRRGetCrtcInfo()` 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` - New `myScreenFindMonitorAtPointPhysical()` function - `src/screen.h:301-304` - Function declaration added - `src/compositor.c:264-269` - Modified `is_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_OBSCURED` events 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.
issue