From cd191a098683332b231b09647ac51ef88cfdef5d Mon Sep 17 00:00:00 2001 From: Fabio Fantoni Date: Sun, 6 Sep 2026 16:35:06 +0200 Subject: [PATCH] desklets: stop grabbing the pointer where a window covers them Whether desklets belong to the stage input region was decided by polling, every 500 ms, whether the pointer was sitting on a window, and the answer was applied to every desklet at once. The stacking at the pointer position was never consulted, so a desklet under a window could stay in the input region and take the pointer events aimed at that window: the event is either swallowed by the window actor picked above the desklet, or handed to the desklet itself while the window actor above is being animated away, which is why the desklet sometimes ends up stuck to the pointer as if dragged. Decide it per desklet from the window geometry instead: a desklet a window overlaps is taken out of the input region. The input region is made of rectangles, so a desklet partly behind a window can only be all in or all out of it, and keeping it in is what takes the events away from that window; the visible part of such a desklet is no longer clickable, which is a fair trade for the window above it staying usable. Check on 'restacked' as well as on the timeout, so that raising, minimizing or unminimizing a window is noticed right away instead of up to half a second later. This also drops the mouseTrackEnabled cache, which had to be poisoned by hand after a desklet drag to force a refresh, and which left a desklet permanently in the input region when it was recreated while the cached state said the desklets were tracked - the likely reason some reports describe a window that stays uninteractive over a desklet until the session is restarted. Assisted-by: Claude Code:claude-opus-5 --- js/ui/deskletManager.js | 68 ++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/js/ui/deskletManager.js b/js/ui/deskletManager.js index 8676b9edea..5cf59c202e 100644 --- a/js/ui/deskletManager.js +++ b/js/ui/deskletManager.js @@ -28,8 +28,8 @@ var deskletsDragging = false; var userDeskletsDir; -var mouseTrackEnabled = false; var mouseTrackTimoutId = 0; +var mouseTrackRestackedId = 0; var promises = []; var deskletChangeKey = 0; @@ -92,9 +92,14 @@ function updateMouseTracking() { let enable = definitions.length > 0; if (enable && !mouseTrackTimoutId) { mouseTrackTimoutId = Mainloop.timeout_add(500, checkMouseTracking); + // A window can be raised, minimized or unminimized between two timeouts, + // so don't wait for the next one to notice a desklet got covered. + mouseTrackRestackedId = global.display.connect('restacked', checkMouseTracking); } else if (!enable && mouseTrackTimoutId) { Mainloop.source_remove(mouseTrackTimoutId); mouseTrackTimoutId = 0; + global.display.disconnect(mouseTrackRestackedId); + mouseTrackRestackedId = 0; for (let i = 0; i < definitions.length; i++) { if (definitions[i].desklet) { @@ -104,26 +109,55 @@ function updateMouseTracking() { } } -function hasMouseWindow(){ - let window = global.display.get_pointer_window(null); - return window && window.window_type !== Meta.WindowType.DESKTOP; +function isCoveredByWindow(desklet) { + let [x, y] = desklet.actor.get_transformed_position(); + let [width, height] = desklet.actor.get_transformed_size(); + + if (isNaN(x) || isNaN(y) || isNaN(width) || isNaN(height)) { + return true; + } + + let rect = new Meta.Rectangle({ x: Math.round(x), + y: Math.round(y), + width: Math.round(width), + height: Math.round(height) }); + + let windows = global.workspace_manager.get_active_workspace().list_windows(); + for (let i = 0; i < windows.length; i++) { + let window = windows[i]; + if (window.window_type === Meta.WindowType.DESKTOP + || !window.showing_on_its_workspace()) { + continue; + } + + // Any overlap is enough: the input region is a rectangle, so a desklet + // partially behind a window can only be all in or all out of it, and + // keeping it in would take the pointer away from that window. + if (window.get_frame_rect().overlap(rect)) { + return true; + } + } + + return false; } function checkMouseTracking() { - let enable = !hasMouseWindow(); - if (mouseTrackEnabled !== enable) { - mouseTrackEnabled = enable; - for (let i = 0; i < definitions.length; i++) { - if (!definitions[i].desklet) { - continue; - } - if (enable) { - definitions[i].desklet._trackMouse(); - } else { - definitions[i].desklet._untrackMouse(); - } + // When desklets are raised on top of the windows they are given a modal + // grab, and the input region doesn't matter anymore. + let deskletsAbove = global.display.get_desklets_above(); + + for (let i = 0; i < definitions.length; i++) { + let desklet = definitions[i].desklet; + if (!desklet) { + continue; + } + if (deskletsAbove || !isCoveredByWindow(desklet)) { + desklet._trackMouse(); + } else { + desklet._untrackMouse(); } } + return true; } @@ -551,7 +585,6 @@ DeskletContainer.prototype = { if (!(source instanceof Desklet.Desklet)) return false; Main.uiGroup.remove_actor(actor); this.actor.add_actor(actor); - mouseTrackEnabled = -1; // forces an update of all desklet mouse tracks checkMouseTracking(); // Update GSettings @@ -588,7 +621,6 @@ DeskletContainer.prototype = { if (!(source instanceof Desklet.Desklet)) return false; Main.uiGroup.remove_actor(actor); this.actor.add_actor(actor); - mouseTrackEnabled = -1; checkMouseTracking(); this._dragPlaceholder.hide(); this.last_x = -1;