From 29b4ccd1dd475f5e5c4b5b0105c54ac6c5ce3d9c Mon Sep 17 00:00:00 2001 From: Peter Fajdiga Date: Sat, 9 Mar 2024 13:42:39 +0100 Subject: [PATCH] port key bindings to the kwin6 ShortcutHandler system --- generators/docs/keyBindings.ts | 2 - src/Actions.ts | 148 +++++++++++++++++---------------- src/extern/kwin.d.ts | 6 +- src/keyBindings/definition.ts | 33 -------- src/keyBindings/loader.ts | 56 ++++++------- src/main.ts | 5 +- src/utils/ShortcutAction.ts | 25 ++++++ src/world/World.ts | 9 ++ 8 files changed, 142 insertions(+), 142 deletions(-) create mode 100644 src/utils/ShortcutAction.ts diff --git a/generators/docs/keyBindings.ts b/generators/docs/keyBindings.ts index e475c16..ff8bbaa 100644 --- a/generators/docs/keyBindings.ts +++ b/generators/docs/keyBindings.ts @@ -3,7 +3,6 @@ type KeyBinding = { description: string; comment?: string; defaultKeySequence: string; - action: string; } type NumKeyBinding = { @@ -12,7 +11,6 @@ type NumKeyBinding = { comment?: string; defaultModifiers: string; fKeys: boolean; - action: string; } function formatComment(comment: string | undefined) { diff --git a/src/Actions.ts b/src/Actions.ts index 3d5f1a6..9c7aa2d 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -1,7 +1,7 @@ namespace Actions { - export function init(world: World, config: Config) { - return { - focusLeft: () => { + export function getAction(world: World, config: Config, name: string) { + switch (name) { + case "focus-left": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const prevColumn = grid.getPrevColumn(column); if (prevColumn === null) { @@ -9,9 +9,9 @@ namespace Actions { } prevColumn.focus(); }); - }, + }; - focusRight: () => { + case "focus-right": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const nextColumn = grid.getNextColumn(column); if (nextColumn === null) { @@ -19,9 +19,9 @@ namespace Actions { } nextColumn.focus(); }); - }, + }; - focusUp: () => { + case "focus-up": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const prevWindow = column.getPrevWindow(window); if (prevWindow === null) { @@ -29,9 +29,9 @@ namespace Actions { } prevWindow.focus(); }); - }, + }; - focusDown: () => { + case "focus-down": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const nextWindow = column.getNextWindow(window); if (nextWindow === null) { @@ -39,9 +39,9 @@ namespace Actions { } nextWindow.focus(); }); - }, + }; - focusStart: () => { + case "focus-start": return () => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const firstColumn = grid.getFirstColumn(); @@ -50,9 +50,9 @@ namespace Actions { } firstColumn.focus(); }); - }, + }; - focusEnd: () => { + case "focus-end": return () => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const lastColumn = grid.getLastColumn(); @@ -61,9 +61,9 @@ namespace Actions { } lastColumn.focus(); }); - }, + }; - windowMoveLeft: () => { + case "window-move-left": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { if (column.getWindowCount() === 1) { // move from own column into existing column @@ -79,9 +79,9 @@ namespace Actions { window.moveToColumn(newColumn); } }); - }, + }; - windowMoveRight: () => { + case "window-move-right": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { if (column.getWindowCount() === 1) { // move from own column into existing column @@ -97,100 +97,100 @@ namespace Actions { window.moveToColumn(newColumn); } }); - }, + }; - windowMoveUp: () => { + case "window-move-up": return () => { // TODO (optimization): only arrange moved windows world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { column.moveWindowUp(window); }); - }, + }; - windowMoveDown: () => { + case "window-move-down": return () => { // TODO (optimization): only arrange moved windows world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { column.moveWindowDown(window); }); - }, + }; - windowMoveStart: () => { + case "window-move-start": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const newColumn = new Column(grid, null); window.moveToColumn(newColumn); }); - }, + }; - windowMoveEnd: () => { + case "window-move-end": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const newColumn = new Column(grid, grid.getLastColumn()); window.moveToColumn(newColumn); }); - }, + }; - windowToggleFloating: () => { + case "window-toggle-floating": return () => { const kwinClient = Workspace.activeWindow; world.do((clientManager, desktopManager) => { clientManager.toggleFloatingClient(kwinClient); }); - }, + }; - columnMoveLeft: () => { + case "column-move-left": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { grid.moveColumnLeft(column); }); - }, + }; - columnMoveRight: () => { + case "column-move-right": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { grid.moveColumnRight(column); }); - }, + }; - columnMoveStart: () => { + case "column-move-start": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { column.moveAfter(null); }); - }, + }; - columnMoveEnd: () => { + case "column-move-end": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { column.moveAfter(grid.getLastColumn()); }); - }, + }; - columnToggleStacked: () => { + case "column-toggle-stacked": return () => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => { column.toggleStacked(); }); - }, + }; - columnWidthIncrease: () => { + case "column-width-increase": return () => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => { config.columnResizer.increaseWidth(column, config.manualResizeStep); }); - }, + }; - columnWidthDecrease: () => { + case "column-width-decrease": return () => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => { config.columnResizer.decreaseWidth(column, config.manualResizeStep); }); - }, + }; - columnsWidthEqualize: () => { + case "columns-width-equalize": return () => { world.do((clientManager, desktopManager) => { desktopManager.getCurrentDesktop().equalizeVisibleColumnsWidths(); }); - }, + }; - gridScrollLeft: () => { + case "grid-scroll-left": return () => { gridScroll(world, -config.manualScrollStep); - }, + }; - gridScrollRight: () => { + case "grid-scroll-right": return () => { gridScroll(world, config.manualScrollStep); - }, + }; - gridScrollStart: () => { + case "grid-scroll-start": return () => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const firstColumn = grid.getFirstColumn(); @@ -199,9 +199,9 @@ namespace Actions { } grid.desktop.scrollToColumn(firstColumn); }); - }, + }; - gridScrollEnd: () => { + case "grid-scroll-end": return () => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const lastColumn = grid.getLastColumn(); @@ -210,15 +210,15 @@ namespace Actions { } grid.desktop.scrollToColumn(lastColumn); }); - }, + }; - gridScrollFocused: () => { + case "grid-scroll-focused": return () => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { grid.desktop.scrollCenterRange(column); }) - }, + }; - gridScrollLeftColumn: () => { + case "grid-scroll-left-column": return () => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true); @@ -233,9 +233,9 @@ namespace Actions { grid.desktop.scrollToColumn(prevColumn); }); - }, + }; - gridScrollRightColumn: () => { + case "grid-scroll-right-column": return () => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true); @@ -250,13 +250,15 @@ namespace Actions { grid.desktop.scrollToColumn(nextColumn); }); - }, - }; + }; + + default: throw new Error("unknown action: " + name); + } } - export function initNum(world: World) { - return { - focusColumn: (columnIndex: number) => { + export function getNumAction(world: World, name: string) { + switch (name) { + case "focus-": return (columnIndex: number) => { world.do((clientManager, desktopManager) => { const grid = desktopManager.getCurrentDesktop().grid; const targetColumn = grid.getColumnAtIndex(columnIndex); @@ -265,9 +267,9 @@ namespace Actions { } targetColumn.focus(); }); - }, + }; - windowMoveToColumn: (columnIndex: number) => { + case "window-move-to-column-": return (columnIndex: number) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const targetColumn = grid.getColumnAtIndex(columnIndex); if (targetColumn === null) { @@ -276,9 +278,9 @@ namespace Actions { window.moveToColumn(targetColumn); grid.desktop.autoAdjustScroll(); }); - }, + }; - columnMoveToColumn: (columnIndex: number) => { + case "column-move-to-column-": return (columnIndex: number) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { const targetColumn = grid.getColumnAtIndex(columnIndex); if (targetColumn === null || targetColumn === column) { @@ -290,9 +292,9 @@ namespace Actions { column.moveAfter(grid.getPrevColumn(targetColumn)); } }); - }, + }; - columnMoveToDesktop: (desktopIndex: number) => { + case "column-move-to-desktop-": return (desktopIndex: number) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => { const kwinDesktop = Workspace.desktops[desktopIndex]; if (kwinDesktop === undefined) { @@ -304,9 +306,9 @@ namespace Actions { } column.moveToGrid(newGrid, newGrid.getLastColumn()); }); - }, + }; - tailMoveToDesktop: (desktopIndex: number) => { + case "tail-move-to-desktop-": return (desktopIndex: number) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => { const kwinDesktop = Workspace.desktops[desktopIndex]; if (kwinDesktop === undefined) { @@ -318,8 +320,10 @@ namespace Actions { } oldGrid.evacuateTail(newGrid, column); }); - }, - }; + }; + + default: throw new Error("unknown num action: " + name); + } } function gridScroll(world: World, amount: number) { diff --git a/src/extern/kwin.d.ts b/src/extern/kwin.d.ts index 688b8c3..bca0f76 100644 --- a/src/extern/kwin.d.ts +++ b/src/extern/kwin.d.ts @@ -1,6 +1,5 @@ declare const KWin: { readConfig(key: string, defaultValue: any): any; - registerShortcut(name: string, description: string, keySequence: string, callback: () => void): void; }; declare const Workspace: { @@ -83,3 +82,8 @@ interface KwinClient { interface KwinDesktop { readonly id: string; } + +type ShortcutHandler = { + readonly activated: QSignal<[void]>; + destroy(): void; +}; diff --git a/src/keyBindings/definition.ts b/src/keyBindings/definition.ts index 3527280..2d1f69e 100644 --- a/src/keyBindings/definition.ts +++ b/src/keyBindings/definition.ts @@ -3,176 +3,148 @@ const keyBindings: KeyBinding[] = [ name: "window-toggle-floating", description: "Toggle floating", defaultKeySequence: "Meta+Space", - action: "windowToggleFloating", }, { name: "focus-left", description: "Move focus left", defaultKeySequence: "Meta+A", - action: "focusLeft", }, { name: "focus-right", description: "Move focus right", comment: "Clashes with default KDE shortcuts, may require manual remapping", defaultKeySequence: "Meta+D", - action: "focusRight", }, { name: "focus-up", description: "Move focus up", comment: "Clashes with default KDE shortcuts, may require manual remapping", defaultKeySequence: "Meta+W", - action: "focusUp", }, { name: "focus-down", description: "Move focus down", comment: "Clashes with default KDE shortcuts, may require manual remapping", defaultKeySequence: "Meta+S", - action: "focusDown", }, { name: "focus-start", description: "Move focus to start", defaultKeySequence: "Meta+Home", - action: "focusStart", }, { name: "focus-end", description: "Move focus to end", defaultKeySequence: "Meta+End", - action: "focusEnd", }, { name: "window-move-left", description: "Move window left", comment: "Moves window out of and into columns", defaultKeySequence: "Meta+Shift+A", - action: "windowMoveLeft", }, { name: "window-move-right", description: "Move window right", comment: "Moves window out of and into columns", defaultKeySequence: "Meta+Shift+D", - action: "windowMoveRight", }, { name: "window-move-up", description: "Move window up", defaultKeySequence: "Meta+Shift+W", - action: "windowMoveUp", }, { name: "window-move-down", description: "Move window down", defaultKeySequence: "Meta+Shift+S", - action: "windowMoveDown", }, { name: "window-move-start", description: "Move window to start", defaultKeySequence: "Meta+Shift+Home", - action: "windowMoveStart", }, { name: "window-move-end", description: "Move window to end", defaultKeySequence: "Meta+Shift+End", - action: "windowMoveEnd", }, { name: "column-toggle-stacked", description: "Toggle stacked layout for focused column", comment: "One window in the column visible, others shaded; not supported on Wayland", defaultKeySequence: "Meta+X", - action: "columnToggleStacked", }, { name: "column-move-left", description: "Move column left", defaultKeySequence: "Meta+Ctrl+Shift+A", - action: "columnMoveLeft", }, { name: "column-move-right", description: "Move column right", defaultKeySequence: "Meta+Ctrl+Shift+D", - action: "columnMoveRight", }, { name: "column-move-start", description: "Move column to start", defaultKeySequence: "Meta+Ctrl+Shift+Home", - action: "columnMoveStart", }, { name: "column-move-end", description: "Move column to end", defaultKeySequence: "Meta+Ctrl+Shift+End", - action: "columnMoveEnd", }, { name: "column-width-increase", description: "Increase column width", defaultKeySequence: "Meta+Ctrl++", - action: "columnWidthIncrease", }, { name: "column-width-decrease", description: "Decrease column width", defaultKeySequence: "Meta+Ctrl+-", - action: "columnWidthDecrease", }, { name: "columns-width-equalize", description: "Equalize widths of visible columns", defaultKeySequence: "Meta+Ctrl+X", - action: "columnsWidthEqualize", }, { name: "grid-scroll-focused", description: "Center focused window", comment: "Scrolls so that the focused window is centered in the screen", defaultKeySequence: "Meta+Alt+Return", - action: "gridScrollFocused", }, { name: "grid-scroll-left-column", description: "Scroll one column to the left", defaultKeySequence: "Meta+Alt+A", - action: "gridScrollLeftColumn", }, { name: "grid-scroll-right-column", description: "Scroll one column to the right", defaultKeySequence: "Meta+Alt+D", - action: "gridScrollRightColumn", }, { name: "grid-scroll-left", description: "Scroll left", defaultKeySequence: "Meta+Alt+PgUp", - action: "gridScrollLeft", }, { name: "grid-scroll-right", description: "Scroll right", defaultKeySequence: "Meta+Alt+PgDown", - action: "gridScrollRight", }, { name: "grid-scroll-start", description: "Scroll to start", defaultKeySequence: "Meta+Alt+Home", - action: "gridScrollStart", }, { name: "grid-scroll-end", description: "Scroll to end", defaultKeySequence: "Meta+Alt+End", - action: "gridScrollEnd", }, ]; @@ -183,7 +155,6 @@ const numKeyBindings: NumKeyBinding[] = [ comment: "Clashes with default KDE shortcuts, may require manual remapping", defaultModifiers: "Meta", fKeys: false, - action: "focusColumn", }, { name: "window-move-to-column-", @@ -191,7 +162,6 @@ const numKeyBindings: NumKeyBinding[] = [ comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!", defaultModifiers: "Meta+Shift", fKeys: false, - action: "windowMoveToColumn", }, { name: "column-move-to-column-", @@ -199,20 +169,17 @@ const numKeyBindings: NumKeyBinding[] = [ comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!", defaultModifiers: "Meta+Ctrl+Shift", fKeys: false, - action: "columnMoveToColumn", }, { name: "column-move-to-desktop-", description: "Move column to desktop ", defaultModifiers: "Meta+Ctrl+Shift", fKeys: true, - action: "columnMoveToDesktop", }, { name: "tail-move-to-desktop-", description: "Move this and all following columns to desktop ", defaultModifiers: "Meta+Ctrl+Shift+Alt", fKeys: true, - action: "tailMoveToDesktop", }, ]; diff --git a/src/keyBindings/loader.ts b/src/keyBindings/loader.ts index 7895172..5bb1e66 100644 --- a/src/keyBindings/loader.ts +++ b/src/keyBindings/loader.ts @@ -3,7 +3,6 @@ type KeyBinding = { description: string; comment?: string; defaultKeySequence: string; - action: keyof ReturnType; }; type NumKeyBinding = { @@ -12,7 +11,6 @@ type NumKeyBinding = { comment?: string; defaultModifiers: string; fKeys: boolean; - action: keyof ReturnType; }; function catchWrap(f: () => void) { @@ -26,45 +24,43 @@ function catchWrap(f: () => void) { }; } -function registerKeyBinding(name: string, description: string, keySequence: string, callback: () => void) { - KWin.registerShortcut( - "karousel-" + name, - "Karousel: " + description, - keySequence, - catchWrap(callback), - ); +function registerKeyBinding(world: World, config: Actions.Config, shortcutActions: ShortcutAction[], keyBinding: KeyBinding) { + shortcutActions.push(new ShortcutAction( + keyBinding, + catchWrap(Actions.getAction(world, config, keyBinding.name)), + )); } -function registerNumKeyBindings(name: string, description: string, modifiers: string, fKeys: boolean, callback: (i: number) => void) { - const numPrefix = fKeys ? "F" : ""; - const n = fKeys ? 12 : 9; +function registerNumKeyBindings(world: World, shortcutActions: ShortcutAction[], numKeyBinding: NumKeyBinding) { + const numPrefix = numKeyBinding.fKeys ? "F" : ""; + const n = numKeyBinding.fKeys ? 12 : 9; for (let i = 0; i < 12; i++) { const numKey = String(i + 1); const keySequence = i < n ? - modifiers + "+" + numPrefix + numKey : + numKeyBinding.defaultModifiers + "+" + numPrefix + numKey : ""; - registerKeyBinding( - name + numKey, - description + numKey, - keySequence, - () => callback(i), - ); + const action = Actions.getNumAction(world, numKeyBinding.name); + shortcutActions.push(new ShortcutAction( + { + name: numKeyBinding.name + numKey, + description: numKeyBinding.description + numKey, + defaultKeySequence: keySequence, + }, + catchWrap(() => action(i)), + )); } } -function registerKeyBindings(world: World, config: Config) { - const actions = Actions.init(world, { - manualScrollStep: config.manualScrollStep, - manualResizeStep: config.manualResizeStep, - columnResizer: config.scrollingCentered ? new RawResizer() : new ContextualResizer(), - }); +function registerKeyBindings(world: World, config: Actions.Config) { + const shortcutActions: ShortcutAction[] = []; - for (const binding of keyBindings) { - registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]); + for (const keyBinding of keyBindings) { + registerKeyBinding(world, config, shortcutActions, keyBinding); } - const numActions = Actions.initNum(world); - for (const binding of numKeyBindings) { - registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]); + for (const numKeyBinding of numKeyBindings) { + registerNumKeyBindings(world, shortcutActions, numKeyBinding); } + + return shortcutActions; } diff --git a/src/main.ts b/src/main.ts index 1d1a427..9bf632b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,3 @@ function init() { - const config = loadConfig(); - const world = new World(config); - registerKeyBindings(world, config); - return world; + return new World(loadConfig()); } diff --git a/src/utils/ShortcutAction.ts b/src/utils/ShortcutAction.ts new file mode 100644 index 0000000..1b06534 --- /dev/null +++ b/src/utils/ShortcutAction.ts @@ -0,0 +1,25 @@ +class ShortcutAction { + private readonly shortcutHandler: ShortcutHandler; + + constructor(keyBinding: KeyBinding, f: () => void) { + this.shortcutHandler = ShortcutAction.initShortcutHandler(keyBinding); + this.shortcutHandler.activated.connect(f); + } + + public destroy() { + this.shortcutHandler.destroy(); + } + + private static initShortcutHandler(keyBinding: KeyBinding) { + return Qt.createQmlObject( + `import QtQuick 6.0 +import org.kde.kwin 3.0 +ShortcutHandler { + name: "karousel-${keyBinding.name}"; + text: "Karousel: ${keyBinding.description}"; + sequence: "${keyBinding.defaultKeySequence}"; +}`, + qmlBase, + ); + } +} diff --git a/src/world/World.ts b/src/world/World.ts index 203507c..cb306c7 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -4,11 +4,17 @@ class World { public readonly clientManager: ClientManager; private readonly pinManager: PinManager; private readonly workspaceSignalManager: SignalManager; + private readonly shortcutActions: ShortcutAction[]; private readonly screenResizedDelayer: Delayer; constructor(config: Config) { this.untileOnDrag = config.untileOnDrag; this.workspaceSignalManager = initWorkspaceSignalHandlers(this); + this.shortcutActions = registerKeyBindings(this, { + manualScrollStep: config.manualScrollStep, + manualResizeStep: config.manualResizeStep, + columnResizer: config.scrollingCentered ? new RawResizer() : new ContextualResizer(), + }); this.screenResizedDelayer = new Delayer(1000, () => { // this delay ensures that docks are taken into account by `Workspace.clientArea` @@ -96,6 +102,9 @@ class World { public destroy() { this.workspaceSignalManager.destroy(); + for (const shortcutAction of this.shortcutActions) { + shortcutAction.destroy(); + } this.clientManager.destroy(); this.desktopManager.destroy(); }