Multi-monitor: window position corrupted on secondary screens when _NET_WORKAREA excludes upper desktop area (XFCE/X11)
- Dominant language
- C++
- Stars
- 12.3k
- Forks
- 593
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 9
Description
## Title
Multi-monitor: window position corrupted on secondary screens when _NET_WORKAREA excludes upper desktop area (XFCE/X11)
## Environment
- CopyQ version: 6.0.1 (confirmed via `copyq --version`)
- Qt: 5.15.2
- OS: Pop!_OS 22.04 LTS (Ubuntu 22.04 base)
- Desktop: XFCE 4 with xfwm4
- Display setup: 4 monitors
## Symptom
On a 4-monitor setup where the laptop screen is not at virtual desktop position (0,0),
the CopyQ main window placed in the upper area of the 4K monitor always reopens at the
bottom of that monitor, height clamped to roughly the laptop screen height.
Horizontal position and width are preserved correctly — only vertical position and
height are wrong.
## Screen Layout
```
$ xrandr --query (relevant outputs):
DP-1-3 1920x1200 at (0, 0)
DP-1-1 3840x2160 at (1920, 120) ← 4K, affected screen
DP-1-2 1920x1200 at (5760, 120)
eDP-1 1920x1080 at (0, 1200) ← laptop, has XFCE panel
```
The laptop (eDP-1) sits below the other three screens in the virtual desktop.
## Root Cause
> **Note:** The `_NET_WORKAREA` value and screen layout below are confirmed by running
> `xprop` and `xrandr`. The code path analysis is based on source inspection, not
> runtime-confirmed with debug logging.
### _NET_WORKAREA is wrong for secondary monitors
```
$ xprop -root _NET_WORKAREA
_NET_WORKAREA(CARDINAL) = 0, 1227, 7680, 1053
```
`_NET_WORKAREA` is a **single rectangle per the EWMH spec** — it cannot represent
per-monitor available areas. XFCE sets it to the laptop screen's usable area:
`y=1227` (laptop top minus panel), `height=1053` (1080 minus 27px panel).
**This is not an XFCE misconfiguration.** The panels are already set to individual
screens (not spanning). `_NET_WORKAREA` has no per-monitor variant in the spec —
there is nothing to fix on the XFCE side.
### How Qt computes availableGeometry()
Qt's `QScreen::availableGeometry()` intersects `_NET_WORKAREA` with each screen's
physical rect. For the 4K monitor (physical: x=1920, y=120, w=3840, h=2160):
```
workarea bottom = 1227 + 1053 = 2280
screen bottom = 120 + 2160 = 2280
intersection top = max(120, 1227) = 1227
intersection bottom = min(2280, 2280) = 2280
intersection height = 2280 - 1227 = 1053
```
Qt reports `availableGeometry = (1920, 1227, 3840, 1053)` for the 4K screen.
The entire upper portion of the 4K (y=120 to y=1227) appears "unavailable".
### How CopyQ uses this — `ensureWindowOnScreen()` (geometry.cpp:114)
`ensureWindowOnScreen()` calls `screenAvailableGeometry()` (screen.cpp:39) which
returns `screen->availableGeometry()`. The clamping then in `ensureWindowOnScreen`:
Height clamp (geometry.cpp:127):
```cpp
h = qMin(h, availableGeometry.height()); // → h clamped to 1053
```
Y floor (geometry.cpp:138-139):
```cpp
if ( y < availableGeometry.top() )
y = availableGeometry.top(); // → y forced to 1227
```
Any saved Y position above 1227 (the entire upper portion of the 4K screen) gets
pushed to y=1227, and height is clamped to 1053. This is deterministic — it happens
every single time.
### Secondary issue: unstable resolution key tags
`screenGeometry()` (screen.cpp:33) also calls `screen->availableGeometry()` and is
used exclusively by `resolutionTagForScreen()` (geometry.cpp:88-93, only caller) to
build the geometry key suffix (e.g. `_3840x1053`).
Because the intersection height depends on both the workarea and the XFCE panel size
(which may vary between sessions as system tray icon count changes), the tag suffix
changes and the correct saved key is not found, triggering fallback to stale geometry.
This is visible in the user's `copyq_geometry.ini` which contains entries for the
same 4K screen at many different "heights" across sessions:
`_3840x2160`, `_3840x2139`, `_3840x2133`, `_3840x1173`, `_3840x1059`, `_3840x1053`,
`_3840x1052`, `_3840x939`, `_3840x938` — all caused by the fluctuating
workarea/panel intersection.
## Proposed Fix
In `src/gui/screen.cpp`, use physical screen geometry (`screen->geometry()`) instead
of `screen->availableGeometry()`:
```cpp
QRect screenGeometry(int i)
{
// Use physical geometry so the resolution tag used in geometry keys is stable
// across sessions regardless of panel size or _NET_WORKAREA changes.
auto screen = screenFromNumber(i);
return screen ? screen->geometry() : QRect();
}
QRect screenAvailableGeometry(const QWidget &w)
{
// Use physical geometry to avoid _NET_WORKAREA misreporting secondary monitors
// as partially unavailable on multi-monitor XFCE/X11 setups.
auto screen = QGuiApplication::screenAt(w.pos());
if (!screen)
screen = QGuiApplication::primaryScreen();
return screen ? screen->geometry() : QRect();
}
```
Note: the fallback in `screenAvailableGeometry` changes from `screenGeometry(0)`
(screen 0's availableGeometry) to `primaryScreen()->geometry()` (physical). This
avoids returning a potentially broken availableGeometry as a fallback.
**Trade-off:** Windows may be placed a few pixels behind panels on the primary screen
in edge cases. This is far less severe than forcing all secondary-monitor windows to
the bottom half of the screen every time.
## To Reproduce
1. Multi-monitor setup where at least one non-primary screen starts at a virtual Y
coordinate below the value of `_NET_WORKAREA`'s y origin
2. XFCE (or any DE that sets _NET_WORKAREA to only the primary monitor's workarea)
3. Place CopyQ main window in the upper half of a secondary monitor
4. Hide and reopen CopyQ
**Expected:** window reopens where it was placed
**Actual:** window appears at the bottom of the secondary monitor with height clamped
## Quick Diagnostic
```bash
xprop -root _NET_WORKAREA
# If y > 0 or the rectangle doesn't cover all your screens, this bug likely applies.
```
To capture runtime geometry decisions:
```bash
COPYQ_LOG_LEVEL=DEBUG copyq 2>&1 | grep -i geometry
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.