--- title: Chromium window drag warps the window (xfwm4) or the cursor (KWin) on X11 date: 2026-10-03 summary: Dragging a Chromium window by its own title bar on X11 makes the window jump hundreds of pixels (often off-screen) under xfwm4, or makes the mouse cursor jump under KWin. Chromium sends a _NET_WM_MOVERESIZE request with wrong root coordinates; turning on "Use system title bar and borders" avoids it. tags: x11, chromium, xfwm4, kwin, window-manager, csd environment: Arch Linux; Chromium 152.0.7977.82 with its own title bar (client-side decorations); xfwm4 4.20.0 + picom, then KWin 6.7.5 (kwin_x11); NVIDIA driver 610 on X11 author: shaun, written up with Claude status: published --- ## Symptoms On an X11 desktop, Chromium windows "get lost": - You start dragging a Chromium window by its tab strip or title area and the window **jumps** by several hundred pixels, often mostly **off-screen** (only a thin strip left visible, sometimes behind the panel). - Clicks land in the wrong place for a while, as if the window were somewhere other than where it is drawn. - It looks random: a given window does it **once**, then behaves until some later point. - After switching the window manager from xfwm4 to KWin, the window stops jumping, but now the **mouse cursor** jumps 10–350 px at the start of a drag. Other applications that use the window manager's own title bar are not affected. ## Root cause Chromium draws its own title bar (client-side decorations, the default). When you drag that title bar, Chromium does not move the window itself. It asks the window manager to start an interactive move by sending an EWMH [`_NET_WM_MOVERESIZE`](https://specifications.freedesktop.org/wm/latest/ar01s04.html#id-1.5.4) client message. That message contains `x_root, y_root`: the pointer position in root (screen) coordinates, as the client believes it to be. Chromium sometimes sends **wrong** root coordinates. Our capture under KWin: ```text SendEvent sender=Chromium type=_NET_WM_MOVERESIZE data=(1413, 203, 8, 0, 0) WarpPointer sender=KWin dst=(1413, 203) pointer motion (1606,52) -> (1413,203) 344 px in 1 ms ``` Direction `8` is `_NET_WM_MOVERESIZE_MOVE`. The real pointer was at **(1606, 52)**. Chromium claimed **(1413, 203)**, an error of (193, 151) px. The most likely explanation is that Chromium works out the root position from its **cached window bounds** plus the pointer's offset inside the window, and the cache is stale (for example after the window manager has moved or placed the window). That fits the "each window does it once" pattern: after the jump, the cache matches reality again. The two window managers handle the bad coordinates differently: | Window manager | What it does with `x_root, y_root` | What you see | |---|---|---| | **xfwm4** 4.20 | Uses them as the grab origin for the move: window x = start x + (pointer x − press x), with a wrong start | The **window** jumps by the error (we measured −316, −332 and −649 px) | | **KWin** 6.7 (X11) | Warps the pointer to the given position before starting the move (`X11Window::NETMoveResize`: *"move cursor to the provided position to prevent the window jumping there on first movement"*) | The **cursor** jumps by the error; the window stays put | So the bug is in the client's request, and each window manager shows it in its own way. KWin's behaviour is documented in its source. The xfwm4 part was measured from window geometry before and after each jump. Our early request tracer under xfwm4 did not log `SendEvent`, so the bad payload was only captured directly under KWin. ## Fix Turn off Chromium's client-side title bar so that moves are started by the window manager from the real pointer event: **Chromium → Settings → Appearance → "Use system title bar and borders" → On** This is a per-profile setting, so turn it on in every profile you use. After the change, the window manager draws the title bar (`xprop _NET_FRAME_EXTENTS` on the window shows a non-zero top extent), no `_NET_WM_MOVERESIZE` is sent for ordinary drags, and the jumps stop. This was confirmed working on the affected machine. The same reasoning applies to other client-side-decorated (GTK/libadwaita) apps that show the same symptom: anything that makes the window manager draw the title bar avoids the client-supplied coordinates. Changing the window manager alone is not a fix. It only changes which thing jumps. ### Recovering a window that is already off-screen ```bash xdotool search --name 'Chromium' | while read w; do xdotool getwindowname "$w"; xdotool getwindowgeometry "$w" done xdotool windowmove 100 100 ``` ## How it was found The jumps were rare, so it took permanent watchers, run as systemd user units on both X displays, until one was caught with numbers: 1. **Window geometry** watcher: logs every move of top-level windows and flags any window that ends up mostly off-screen. 2. **Pointer** watcher: logs button presses and motion during drags (no keys). 3. **Request** watcher: uses the XRecord extension to log every `SendEvent` ClientMessage any client sends, with the sender's resource-ID base, so you can see *who* asked for a move and with what coordinates. 4. A short-lived probe logging every `XWarpPointer` request and its sender, which is what showed KWin moving the cursor ~150 ms after button press (Chromium's drag threshold). The request watcher is the useful one. It is short and needs only `python-xlib`: ```python #!/usr/bin/env python3 """Log SendEvent ClientMessages (e.g. _NET_WM_MOVERESIZE) via XRecord. Usage: xreqlog.py DISPLAY OUTFILE""" import struct, sys from datetime import datetime from Xlib import display from Xlib.ext import record SEND_EVENT, CLIENT_MESSAGE = 25, 33 def main(): d, d2 = display.Display(sys.argv[1]), display.Display(sys.argv[1]) f = open(sys.argv[2], "a", buffering=1) names = {} def aname(a): if a not in names: try: names[a] = d2.get_atom_name(a) except Exception: names[a] = hex(a) return names[a] def callback(reply): if reply.category != record.FromClient or reply.client_swapped: return data = reply.data while len(data) >= 4: n = int.from_bytes(data[2:4], "little") * 4 if n < 4 or n > len(data): break req, data = data[:n], data[n:] if req[0] != SEND_EVENT or len(req) < 44: continue ev = req[12:] if ev[0] != CLIENT_MESSAGE: continue win = int.from_bytes(ev[4:8], "little") msg = int.from_bytes(ev[8:12], "little") vals = struct.unpack("<5i", ev[12:32]) if ev[1] == 32 else tuple(ev[12:17]) t = datetime.now().isoformat(timespec="milliseconds") f.write(f"{t} id_base={hex(reply.id_base)} win={hex(win)} " f"type={aname(msg)} data={vals}\n") ctx = d.record_create_context(0, [record.AllClients], [{ "core_requests": (0, 127), "core_replies": (0, 0), "ext_requests": (0, 0, 0, 0), "ext_replies": (0, 0, 0, 0), "delivered_events": (0, 0), "device_events": (0, 0), "errors": (0, 0), "client_started": False, "client_died": False}]) try: d.record_enable_context(ctx, callback) finally: d.record_free_context(ctx) main() ``` Compare the `data=(x, y, 8, …)` of a `_NET_WM_MOVERESIZE` line with where the pointer actually was at that moment (`xdotool getmouselocation`, or a pointer logger). If they differ, you have this bug. Things that were checked and ruled out along the way: X Shape input regions (none on the Chromium windows), compositor drawing offsets (screenshots matched the X window tree), and Chromium's saved window state (it never contained the off-screen positions). ## References - EWMH spec, [`_NET_WM_MOVERESIZE`](https://specifications.freedesktop.org/wm/latest/ar01s04.html#id-1.5.4). - KDE bug [449105](https://bugs.kde.org/show_bug.cgi?id=449105), "Dragging a window that was opened maximized moves the mouse cursor to the top left corner of the window" (Wayland; fixed in Plasma 6.1). A related cursor-jump issue, different trigger. - KWin merge requests [5399](https://invent.kde.org/plasma/kwin/-/merge_requests/5399) ("Make Window::interactiveMoveOffset() proportional") and [5421](https://invent.kde.org/plasma/kwin/-/merge_requests/5421) ("x11: Fix interactive move offset"). - Unrelated noise you may see at the same time with xfwm4 4.20 + picom: a flood of `GLib-CRITICAL: g_hash_table_lookup` in `~/.xsession-errors` (xfwm4 issue #855, cosmetic).