From 9ef570add4d5aa4a4f112d61852eb190755d8175 Mon Sep 17 00:00:00 2001 From: Christopher Harrison Date: Thu, 9 Oct 2025 22:39:57 -0400 Subject: [PATCH] Keep focus on pointer monitor after workspace switch - track the monitor/workspace of the last focused window whenever the pointer is warped - after an active-workspace change, refocus the tiled window on the pointer's monitor instead of leaving focus on an unrelated display - sort pointer-tracking windows by stacking order so keyboard navigation still works when the pointer stays put --- lib/extension/window.js | 81 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/lib/extension/window.js b/lib/extension/window.js index 24356f5..7323159 100644 --- a/lib/extension/window.js +++ b/lib/extension/window.js @@ -74,6 +74,8 @@ export class WindowManager extends GObject.Object { this.eventQueue = new Queue(); this.theme = this.ext.theme; this.lastFocusedWindow = null; + this.lastFocusedWindowMonitor = undefined; + this.lastFocusedWindowWorkspace = undefined; this.shouldFocusOnHover = this.ext.settings.get_boolean("focus-on-hover-enabled"); Logger.info("forge initialized"); @@ -267,10 +269,27 @@ export class WindowManager extends GObject.Object { this.renderTree("workspace-removed"); }), globalWsm.connect("active-workspace-changed", () => { + const pointerMonitor = global.display.get_current_monitor(); + const pointerWorkspace = global.display + .get_workspace_manager() + .get_active_workspace_index(); + this.hideWindowBorders(); this.trackCurrentMonWs(); this.updateDecorationLayout(); this.renderTree("active-workspace-changed"); + + if (this.ext.settings.get_boolean("move-pointer-focus-enabled")) { + this.queueEvent( + { + name: "refocus-pointer-monitor", + callback: () => { + this.refocusPointerMonitor(pointerMonitor, pointerWorkspace); + }, + }, + 16 + ); + } }), ]; @@ -400,9 +419,25 @@ export class WindowManager extends GObject.Object { trackCurrentMonWs() { let metaWindow = this.focusMetaWindow; if (!metaWindow) return; - const currentMonitor = global.display.get_current_monitor(); const currentWorkspace = global.display.get_workspace_manager().get_active_workspace_index(); + if (this.lastFocusedWindowMonitor !== undefined) { + const pointerMonitor = this.lastFocusedWindowMonitor; + let pointerMonWs = `mo${pointerMonitor}ws${currentWorkspace}`; + let pointerMonWsNode = this.tree.findNode(pointerMonWs); + if (pointerMonWsNode) { + const pointerWindows = pointerMonWsNode + .getNodeByType(NODE_TYPES.WINDOW) + .filter((w) => w.isTile()) + .map((w) => w.nodeValue); + if (pointerWindows.length > 0) { + this.sortedWindows = global.display.sort_windows_by_stacking(pointerWindows).reverse(); + return; + } + } + } + + const currentMonitor = global.display.get_current_monitor(); let currentMonWs = `mo${currentMonitor}ws${currentWorkspace}`; let activeMetaMonWs = `mo${metaWindow.get_monitor()}ws${metaWindow.get_workspace().index()}`; let currentWsNode = this.tree.findNode(`ws${currentWorkspace}`); @@ -1872,10 +1907,54 @@ export class WindowManager extends GObject.Object { this.warpPointerToNodeWindow(nodeWindow); } } + + if (nodeWindow && nodeWindow.nodeValue) { + this.lastFocusedWindowMonitor = nodeWindow.nodeValue.get_monitor(); + let workspace = nodeWindow.nodeValue.get_workspace(); + this.lastFocusedWindowWorkspace = workspace ? workspace.index() : undefined; + } else { + this.lastFocusedWindowMonitor = undefined; + this.lastFocusedWindowWorkspace = undefined; + } this.lastFocusedWindow = nodeWindow; this.tree.debugParentNodes(nodeWindow); } + refocusPointerMonitor(monitor, workspace) { + if (monitor === undefined || workspace === undefined) return; + + const focusWindow = global.display.get_focus_window(); + if (focusWindow && focusWindow.get_monitor && focusWindow.get_monitor() === monitor) { + return; + } + + const candidateNodes = this.tree + .getNodeByType(NODE_TYPES.WINDOW) + .filter((node) => { + if (!node.isTile()) return false; + const meta = node.nodeValue; + if (!meta || meta.minimized) return false; + const metaWorkspace = meta.get_workspace(); + return meta.get_monitor() === monitor && metaWorkspace && metaWorkspace.index() === workspace; + }); + + if (candidateNodes.length === 0) return; + + const sortedMetas = global.display + .sort_windows_by_stacking(candidateNodes.map((node) => node.nodeValue)) + .reverse(); + const targetMeta = sortedMetas.find(Boolean); + if (!targetMeta) return; + + const targetNode = candidateNodes.find((node) => node.nodeValue === targetMeta); + if (!targetNode) return; + + targetMeta.raise(); + targetMeta.focus(global.display.get_current_time()); + targetMeta.activate(global.display.get_current_time()); + this.movePointerWith(targetNode, { force: true }); + } + warpPointerToNodeWindow(nodeWindow) { const newCoord = this.getPointerPositionInside(nodeWindow); if (newCoord && newCoord.x && newCoord.y) {