From 195f4e6d30b5991206e31b17363dbf367eb4252e Mon Sep 17 00:00:00 2001 From: Peter Fajdiga Date: Sun, 7 Sep 2025 16:48:02 +0200 Subject: [PATCH] don't pass focus when window is moved and followed to a different desktop (issue 116) --- src/lib/layout/Column.ts | 2 +- src/lib/layout/Desktop.ts | 3 ++- src/lib/layout/Grid.ts | 6 +++-- src/lib/workspace.ts | 12 +++++---- src/lib/world/DesktopManager.ts | 4 ++- src/lib/world/FocusPassing.ts | 44 +++++++++++++++++++++++++++++++++ src/lib/world/World.ts | 4 ++- 7 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 src/lib/world/FocusPassing.ts diff --git a/src/lib/layout/Column.ts b/src/lib/layout/Column.ts index b704a05..56c0827 100644 --- a/src/lib/layout/Column.ts +++ b/src/lib/layout/Column.ts @@ -289,7 +289,7 @@ class Column { } else { this.resizeWindows(); if (passFocus && windowToFocus !== null) { - windowToFocus.focus(); + this.grid.focusPasser.request(windowToFocus); } } diff --git a/src/lib/layout/Desktop.ts b/src/lib/layout/Desktop.ts index 6c3be02..42a88c4 100644 --- a/src/lib/layout/Desktop.ts +++ b/src/lib/layout/Desktop.ts @@ -14,13 +14,14 @@ class Desktop { private readonly config: Desktop.Config, private readonly getScreen: () => Output, layoutConfig: LayoutConfig, + focusPasser: FocusPassing.Passer, ) { this.scrollX = 0; this.gestureScrollXInitial = null; this.dirty = true; this.dirtyScroll = true; this.dirtyPins = true; - this.grid = new Grid(this, layoutConfig); + this.grid = new Grid(this, layoutConfig, focusPasser); this.clientArea = Desktop.getClientArea(this.getScreen(), kwinDesktop); this.tilingArea = Desktop.getTilingArea(this.clientArea, kwinDesktop, pinManager, config); } diff --git a/src/lib/layout/Grid.ts b/src/lib/layout/Grid.ts index 4cd0fbd..588b7d5 100644 --- a/src/lib/layout/Grid.ts +++ b/src/lib/layout/Grid.ts @@ -1,15 +1,17 @@ class Grid { public readonly desktop: Desktop; public readonly config: LayoutConfig; + public readonly focusPasser: FocusPassing.Passer; private readonly columns: LinkedList; private lastFocusedColumn: Column|null; private width: number; private userResize: boolean; // is any part of the grid being resized by the user private readonly userResizeFinishedDelayer: Delayer; - constructor(desktop: Desktop, config: LayoutConfig) { + constructor(desktop: Desktop, config: LayoutConfig, focusPasser: FocusPassing.Passer) { this.desktop = desktop; this.config = config; + this.focusPasser = focusPasser; this.columns = new LinkedList(); this.lastFocusedColumn = null; this.width = 0; @@ -169,7 +171,7 @@ class Grid { this.desktop.onLayoutChanged(); if (passFocus && columnToFocus !== null) { - columnToFocus.focus(); + this.focusPasser.request(columnToFocus); } else { this.desktop.autoAdjustScroll(); } diff --git a/src/lib/workspace.ts b/src/lib/workspace.ts index 9a4609a..92f634f 100644 --- a/src/lib/workspace.ts +++ b/src/lib/workspace.ts @@ -1,4 +1,4 @@ -function initWorkspaceSignalHandlers(world: World) { +function initWorkspaceSignalHandlers(world: World, focusPasser: FocusPassing.Passer) { const manager = new SignalManager(); manager.connect(Workspace.windowAdded, (kwinClient: KwinClient) => { @@ -15,11 +15,13 @@ function initWorkspaceSignalHandlers(world: World) { manager.connect(Workspace.windowActivated, (kwinClient: KwinClient|null) => { if (kwinClient === null) { - return; + focusPasser.activate(); + } else { + focusPasser.clear(); + world.do((clientManager, desktopManager) => { + clientManager.onClientFocused(kwinClient); + }); } - world.do((clientManager, desktopManager) => { - clientManager.onClientFocused(kwinClient); - }); }); manager.connect(Workspace.currentDesktopChanged, () => { diff --git a/src/lib/world/DesktopManager.ts b/src/lib/world/DesktopManager.ts index 2645512..30f83cb 100644 --- a/src/lib/world/DesktopManager.ts +++ b/src/lib/world/DesktopManager.ts @@ -7,7 +7,8 @@ class DesktopManager { constructor( private readonly pinManager: PinManager, private readonly config: Desktop.Config, - public readonly layoutConfig: LayoutConfig, + private readonly layoutConfig: LayoutConfig, + private readonly focusPasser: FocusPassing.Passer, currentActivity: string, currentDesktop: KwinDesktop, ) { @@ -54,6 +55,7 @@ class DesktopManager { this.config, () => this.selectedScreen, this.layoutConfig, + this.focusPasser, ); this.desktops.set(desktopKey, desktop); return desktop; diff --git a/src/lib/world/FocusPassing.ts b/src/lib/world/FocusPassing.ts new file mode 100644 index 0000000..70be73d --- /dev/null +++ b/src/lib/world/FocusPassing.ts @@ -0,0 +1,44 @@ +namespace FocusPassing { + export class Passer { + private currentRequest: Request | null = null; + + public request(target: Focuser) { + this.currentRequest = new Request(target, Date.now()); + } + + public clear() { + this.currentRequest = null; + } + + public activate() { + if (this.currentRequest === null) { + return; + } + + if (this.currentRequest.isExpired()) { + this.clear(); + return; + } + + this.currentRequest.target.focus(); + this.clear(); + } + } + + class Request { + private static readonly validMs = 200; + + constructor( + public readonly target: Focuser, + private readonly time: number, + ) {} + + public isExpired() { + return Date.now() - this.time > Request.validMs; + } + } + + interface Focuser { + focus(): void; + } +} diff --git a/src/lib/world/World.ts b/src/lib/world/World.ts index a7b5261..0bd9ddb 100644 --- a/src/lib/world/World.ts +++ b/src/lib/world/World.ts @@ -8,7 +8,8 @@ class World { private readonly cursorFollowsFocus: boolean; constructor(config: Config) { - this.workspaceSignalManager = initWorkspaceSignalHandlers(this); + const focusPasser = new FocusPassing.Passer(); + this.workspaceSignalManager = initWorkspaceSignalHandlers(this, focusPasser); this.cursorFollowsFocus = config.cursorFollowsFocus; let presetWidths = { @@ -68,6 +69,7 @@ class World { gestureScrollStep: config.gestureScrollStep, }, layoutConfig, + focusPasser, Workspace.currentActivity, Workspace.currentDesktop, );