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