port key bindings to the kwin6 ShortcutHandler system

This commit is contained in:
Peter Fajdiga
2024-03-09 13:42:39 +01:00
parent 7b547bc5b8
commit 29b4ccd1dd
8 changed files with 142 additions and 142 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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;
};

View File

@@ -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",
},
];

View File

@@ -3,7 +3,6 @@ type KeyBinding = {
description: string;
comment?: string;
defaultKeySequence: string;
action: keyof ReturnType<typeof Actions.init>;
};
type NumKeyBinding = {
@@ -12,7 +11,6 @@ type NumKeyBinding = {
comment?: string;
defaultModifiers: string;
fKeys: boolean;
action: keyof ReturnType<typeof Actions.initNum>;
};
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;
}

View File

@@ -1,6 +1,3 @@
function init() {
const config = loadConfig();
const world = new World(config);
registerKeyBindings(world, config);
return world;
return new World(loadConfig());
}

View File

@@ -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,
);
}
}

View File

@@ -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();
}