37 Commits

Author SHA1 Message Date
Peter Fajdiga
752df86db5 bump version to 0.8.1 2024-03-19 10:46:33 +01:00
Peter Fajdiga
f05eefe19b config: add plasmashell to window rules (fixes #38) 2024-03-19 10:17:37 +01:00
Peter Fajdiga
f550285778 bump version to 0.8 2024-03-18 19:56:14 +01:00
Peter Fajdiga
5247a6a0d3 don't tile immovable windows 2024-03-18 19:31:22 +01:00
Peter Fajdiga
2b114a63dc refactor TiledMinimized unminimization 2024-03-18 19:31:22 +01:00
Peter Fajdiga
63e4015f3a don't tile popup windows 2024-03-18 19:31:22 +01:00
Peter Fajdiga
02db31266b ClientWrapper: don't try to maximize/fullscreenify unsupporting windows 2024-03-18 19:31:22 +01:00
Peter Fajdiga
67d4d89700 World: remove workaround for Qt5 bug 2024-03-18 19:31:22 +01:00
Peter Fajdiga
755cf90b1a DesktopManager: destroy removed desktops 2024-03-18 19:31:22 +01:00
Peter Fajdiga
e6a01217a5 DesktopManager.getDesktopsForClient: call getDesktops 2024-03-18 19:31:22 +01:00
Peter Fajdiga
21d7bbd6c4 DesktopManager: don't yield previously unconstructed desktops 2024-03-18 19:31:22 +01:00
Peter Fajdiga
605215acdc workspace.ts: use Clients.makeTileable 2024-03-18 19:31:22 +01:00
Peter Fajdiga
4b6808dba1 ClientWrapper: set maximizedMode in setMaximize 2024-03-18 19:31:22 +01:00
Peter Fajdiga
f9749c6f56 Tiled: use interactiveMoveResizeStarted and interactiveMoveResizeFinished 2024-03-18 19:31:22 +01:00
Peter Fajdiga
9b40b2f777 Tiled: use cursorPos to distinguish between border and single-column resize 2024-03-18 19:31:22 +01:00
Peter Fajdiga
33470b4d7b move kwinClient.tile = null to ClientWrapper 2024-03-18 19:31:22 +01:00
Peter Fajdiga
8947719621 ClientWrapper: store maximized state 2024-03-18 19:31:22 +01:00
Peter Fajdiga
4bf4f8e8a1 Tiled: use maximizedAboutToChange instead of maximizedChanged 2024-03-18 19:31:22 +01:00
Peter Fajdiga
080de7cf97 kwin.d.ts: add signal maximizedAboutToChange 2024-03-18 19:31:22 +01:00
Peter Fajdiga
c29902dc15 Pinned: change condition for un-tile on maximixedChanged 2024-03-18 19:31:22 +01:00
Peter Fajdiga
1736b0a398 kwin.d.ts: update client signals 2024-03-18 19:31:22 +01:00
Peter Fajdiga
a1c44647ca TiledMinimized: support un-minimization 2024-03-18 19:31:22 +01:00
Peter Fajdiga
0ea75d6348 confirm all desktops == empty array 2024-03-18 19:31:22 +01:00
Peter Fajdiga
12901e45ce src/keyBindings: add TODO 2024-03-18 19:31:22 +01:00
Peter Fajdiga
29b4ccd1dd port key bindings to the kwin6 ShortcutHandler system 2024-03-18 19:31:22 +01:00
Peter Fajdiga
7b547bc5b8 pass desktop to Workspace.clientArea 2024-03-18 19:31:22 +01:00
Peter Fajdiga
78a127111b pass output to Workspace.clientArea 2024-03-18 19:31:22 +01:00
Peter Fajdiga
333b7601b2 refactor desktops (WIP) 2024-03-18 19:31:22 +01:00
Peter Fajdiga
1927ae445d kwin.d.ts: add KwinDesktop (WIP) 2024-03-18 19:31:22 +01:00
Peter Fajdiga
1f563dae01 kwin.d.ts: update KwinClient properties 2024-03-18 19:31:22 +01:00
Peter Fajdiga
6b82eedbfe kwin.d.ts: move removed Workspace signals to KwinClient (WIP) 2024-03-18 19:31:22 +01:00
Peter Fajdiga
b479735130 kwin.d.ts: update Workspace properties 2024-03-18 19:31:22 +01:00
Peter Fajdiga
c8f022d66f kwin.d.ts: rename Workspace 2024-03-18 19:31:22 +01:00
Peter Fajdiga
7f71750a8e use QtQuick 6.0 2024-03-18 19:31:22 +01:00
Peter Fajdiga
13ebf24732 Makefile: fix uninstall recipe 2024-03-18 19:31:22 +01:00
Peter Fajdiga
ec6b3247b7 Makefile: use kpackagetool6 2024-03-18 19:31:22 +01:00
Peter Fajdiga
50681d3a07 update package structure for kde 6 2024-03-18 19:31:22 +01:00
29 changed files with 519 additions and 433 deletions

View File

@@ -11,10 +11,10 @@ build:
tsc --outFile ./package/contents/code/main.js tsc --outFile ./package/contents/code/main.js
install: build config install: build config
kpackagetool5 --type=KWin/Script -i ./package || kpackagetool5 --type=KWin/Script -u ./package kpackagetool6 --type=KWin/Script -i ./package || kpackagetool6 --type=KWin/Script -u ./package
uninstall: uninstall:
kpackagetool5 --type=KWin/Script -r ./package kpackagetool6 --type=KWin/Script -r karousel
package: build config package: build config
tar -czf ./karousel_${subst .,_,${VERSION}}.tar.gz ./package tar -czf ./karousel_${subst .,_,${VERSION}}.tar.gz ./package

View File

@@ -3,7 +3,6 @@ type KeyBinding = {
description: string; description: string;
comment?: string; comment?: string;
defaultKeySequence: string; defaultKeySequence: string;
action: string;
} }
type NumKeyBinding = { type NumKeyBinding = {
@@ -12,7 +11,6 @@ type NumKeyBinding = {
comment?: string; comment?: string;
defaultModifiers: string; defaultModifiers: string;
fKeys: boolean; fKeys: boolean;
action: string;
} }
function formatComment(comment: string | undefined) { function formatComment(comment: string | undefined) {

View File

@@ -1,7 +1,7 @@
import QtQuick 2.15 import QtQuick 6.0
import org.kde.kwin 3.0 import org.kde.kwin 3.0
import org.kde.notification 1.0 import org.kde.notification 1.0
import "./main.js" as Karousel import "../code/main.js" as Karousel
Item { Item {
id: qmlBase id: qmlBase

View File

@@ -1,4 +1,5 @@
{ {
"KPackageStructure": "KWin/Script",
"KPlugin": { "KPlugin": {
"Name": "Karousel", "Name": "Karousel",
"Description": "Scrollable tiling extension for KWin", "Description": "Scrollable tiling extension for KWin",
@@ -8,13 +9,13 @@
"Name": "Peter Fajdiga" "Name": "Peter Fajdiga"
}], }],
"Id": "karousel", "Id": "karousel",
"ServiceTypes": ["KWin/Script"], "Version": "0.8.1",
"Version": "0.7.2",
"License": "GPLv3", "License": "GPLv3",
"Website": "https://github.com/peterfajdiga/karousel", "Website": "https://github.com/peterfajdiga/karousel",
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues" "BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"
}, },
"X-Plasma-API": "declarativescript", "X-Plasma-API": "declarativescript",
"X-Plasma-MainScript": "code/main.qml", "X-Plasma-API-Minimum-Version": "6.0",
"X-Plasma-MainScript": "ui/main.qml",
"X-KDE-ConfigModule": "kwin/effects/configs/kcm_kwin4_genericscripted" "X-KDE-ConfigModule": "kwin/effects/configs/kcm_kwin4_genericscripted"
} }

View File

@@ -1,7 +1,7 @@
namespace Actions { namespace Actions {
export function init(world: World, config: Config) { export function getAction(world: World, config: Config, name: string) {
return { switch (name) {
focusLeft: () => { case "focus-left": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const prevColumn = grid.getPrevColumn(column); const prevColumn = grid.getPrevColumn(column);
if (prevColumn === null) { if (prevColumn === null) {
@@ -9,9 +9,9 @@ namespace Actions {
} }
prevColumn.focus(); prevColumn.focus();
}); });
}, };
focusRight: () => { case "focus-right": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const nextColumn = grid.getNextColumn(column); const nextColumn = grid.getNextColumn(column);
if (nextColumn === null) { if (nextColumn === null) {
@@ -19,9 +19,9 @@ namespace Actions {
} }
nextColumn.focus(); nextColumn.focus();
}); });
}, };
focusUp: () => { case "focus-up": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const prevWindow = column.getPrevWindow(window); const prevWindow = column.getPrevWindow(window);
if (prevWindow === null) { if (prevWindow === null) {
@@ -29,9 +29,9 @@ namespace Actions {
} }
prevWindow.focus(); prevWindow.focus();
}); });
}, };
focusDown: () => { case "focus-down": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const nextWindow = column.getNextWindow(window); const nextWindow = column.getNextWindow(window);
if (nextWindow === null) { if (nextWindow === null) {
@@ -39,9 +39,9 @@ namespace Actions {
} }
nextWindow.focus(); nextWindow.focus();
}); });
}, };
focusStart: () => { case "focus-start": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const firstColumn = grid.getFirstColumn(); const firstColumn = grid.getFirstColumn();
@@ -50,9 +50,9 @@ namespace Actions {
} }
firstColumn.focus(); firstColumn.focus();
}); });
}, };
focusEnd: () => { case "focus-end": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const lastColumn = grid.getLastColumn(); const lastColumn = grid.getLastColumn();
@@ -61,9 +61,9 @@ namespace Actions {
} }
lastColumn.focus(); lastColumn.focus();
}); });
}, };
windowMoveLeft: () => { case "window-move-left": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
if (column.getWindowCount() === 1) { if (column.getWindowCount() === 1) {
// move from own column into existing column // move from own column into existing column
@@ -79,9 +79,9 @@ namespace Actions {
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
} }
}); });
}, };
windowMoveRight: () => { case "window-move-right": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
if (column.getWindowCount() === 1) { if (column.getWindowCount() === 1) {
// move from own column into existing column // move from own column into existing column
@@ -97,100 +97,100 @@ namespace Actions {
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
} }
}); });
}, };
windowMoveUp: () => { case "window-move-up": return () => {
// TODO (optimization): only arrange moved windows // TODO (optimization): only arrange moved windows
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveWindowUp(window); column.moveWindowUp(window);
}); });
}, };
windowMoveDown: () => { case "window-move-down": return () => {
// TODO (optimization): only arrange moved windows // TODO (optimization): only arrange moved windows
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveWindowDown(window); column.moveWindowDown(window);
}); });
}, };
windowMoveStart: () => { case "window-move-start": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const newColumn = new Column(grid, null); const newColumn = new Column(grid, null);
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
}); });
}, };
windowMoveEnd: () => { case "window-move-end": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const newColumn = new Column(grid, grid.getLastColumn()); const newColumn = new Column(grid, grid.getLastColumn());
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
}); });
}, };
windowToggleFloating: () => { case "window-toggle-floating": return () => {
const kwinClient = workspace.activeClient; const kwinClient = Workspace.activeWindow;
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
clientManager.toggleFloatingClient(kwinClient); clientManager.toggleFloatingClient(kwinClient);
}); });
}, };
columnMoveLeft: () => { case "column-move-left": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.moveColumnLeft(column); grid.moveColumnLeft(column);
}); });
}, };
columnMoveRight: () => { case "column-move-right": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.moveColumnRight(column); grid.moveColumnRight(column);
}); });
}, };
columnMoveStart: () => { case "column-move-start": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveAfter(null); column.moveAfter(null);
}); });
}, };
columnMoveEnd: () => { case "column-move-end": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveAfter(grid.getLastColumn()); column.moveAfter(grid.getLastColumn());
}); });
}, };
columnToggleStacked: () => { case "column-toggle-stacked": return () => {
world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
column.toggleStacked(); column.toggleStacked();
}); });
}, };
columnWidthIncrease: () => { case "column-width-increase": return () => {
world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
config.columnResizer.increaseWidth(column, config.manualResizeStep); config.columnResizer.increaseWidth(column, config.manualResizeStep);
}); });
}, };
columnWidthDecrease: () => { case "column-width-decrease": return () => {
world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
config.columnResizer.decreaseWidth(column, config.manualResizeStep); config.columnResizer.decreaseWidth(column, config.manualResizeStep);
}); });
}, };
columnsWidthEqualize: () => { case "columns-width-equalize": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
desktopManager.getCurrentDesktop().equalizeVisibleColumnsWidths(); desktopManager.getCurrentDesktop().equalizeVisibleColumnsWidths();
}); });
}, };
gridScrollLeft: () => { case "grid-scroll-left": return () => {
gridScroll(world, -config.manualScrollStep); gridScroll(world, -config.manualScrollStep);
}, };
gridScrollRight: () => { case "grid-scroll-right": return () => {
gridScroll(world, config.manualScrollStep); gridScroll(world, config.manualScrollStep);
}, };
gridScrollStart: () => { case "grid-scroll-start": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const firstColumn = grid.getFirstColumn(); const firstColumn = grid.getFirstColumn();
@@ -199,9 +199,9 @@ namespace Actions {
} }
grid.desktop.scrollToColumn(firstColumn); grid.desktop.scrollToColumn(firstColumn);
}); });
}, };
gridScrollEnd: () => { case "grid-scroll-end": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const lastColumn = grid.getLastColumn(); const lastColumn = grid.getLastColumn();
@@ -210,15 +210,15 @@ namespace Actions {
} }
grid.desktop.scrollToColumn(lastColumn); grid.desktop.scrollToColumn(lastColumn);
}); });
}, };
gridScrollFocused: () => { case "grid-scroll-focused": return () => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.desktop.scrollCenterRange(column); grid.desktop.scrollCenterRange(column);
}) })
}, };
gridScrollLeftColumn: () => { case "grid-scroll-left-column": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true); const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
@@ -233,9 +233,9 @@ namespace Actions {
grid.desktop.scrollToColumn(prevColumn); grid.desktop.scrollToColumn(prevColumn);
}); });
}, };
gridScrollRightColumn: () => { case "grid-scroll-right-column": return () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true); const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
@@ -250,13 +250,15 @@ namespace Actions {
grid.desktop.scrollToColumn(nextColumn); grid.desktop.scrollToColumn(nextColumn);
}); });
}, };
};
default: throw new Error("unknown action: " + name);
}
} }
export function initNum(world: World) { export function getNumAction(world: World, name: string) {
return { switch (name) {
focusColumn: (columnIndex: number) => { case "focus-": return (columnIndex: number) => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
@@ -265,9 +267,9 @@ namespace Actions {
} }
targetColumn.focus(); targetColumn.focus();
}); });
}, };
windowMoveToColumn: (columnIndex: number) => { case "window-move-to-column-": return (columnIndex: number) => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null) { if (targetColumn === null) {
@@ -276,9 +278,9 @@ namespace Actions {
window.moveToColumn(targetColumn); window.moveToColumn(targetColumn);
grid.desktop.autoAdjustScroll(); grid.desktop.autoAdjustScroll();
}); });
}, };
columnMoveToColumn: (columnIndex: number) => { case "column-move-to-column-": return (columnIndex: number) => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null || targetColumn === column) { if (targetColumn === null || targetColumn === column) {
@@ -290,30 +292,38 @@ namespace Actions {
column.moveAfter(grid.getPrevColumn(targetColumn)); column.moveAfter(grid.getPrevColumn(targetColumn));
} }
}); });
}, };
columnMoveToDesktop: (desktopIndex: number) => { case "column-move-to-desktop-": return (desktopIndex: number) => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => {
const desktopNumber = desktopIndex + 1; const kwinDesktop = Workspace.desktops[desktopIndex];
const newGrid = desktopManager.getDesktopInCurrentActivity(desktopNumber).grid; if (kwinDesktop === undefined) {
return;
}
const newGrid = desktopManager.getDesktopInCurrentActivity(kwinDesktop).grid;
if (newGrid === null || newGrid === oldGrid) { if (newGrid === null || newGrid === oldGrid) {
return; return;
} }
column.moveToGrid(newGrid, newGrid.getLastColumn()); column.moveToGrid(newGrid, newGrid.getLastColumn());
}); });
}, };
tailMoveToDesktop: (desktopIndex: number) => { case "tail-move-to-desktop-": return (desktopIndex: number) => {
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => {
const desktopNumber = desktopIndex + 1; const kwinDesktop = Workspace.desktops[desktopIndex];
const newGrid = desktopManager.getDesktopInCurrentActivity(desktopNumber).grid; if (kwinDesktop === undefined) {
return;
}
const newGrid = desktopManager.getDesktopInCurrentActivity(kwinDesktop).grid;
if (newGrid === null || newGrid === oldGrid) { if (newGrid === null || newGrid === oldGrid) {
return; return;
} }
oldGrid.evacuateTail(newGrid, column); oldGrid.evacuateTail(newGrid, column);
}); });
}, };
};
default: throw new Error("unknown num action: " + name);
}
} }
function gridScroll(world: World, amount: number) { function gridScroll(world: World, amount: number) {

View File

@@ -3,6 +3,14 @@ const defaultWindowRules = `[
"class": "ksmserver-logout-greeter", "class": "ksmserver-logout-greeter",
"tile": false "tile": false
}, },
{
"class": "plasmashell",
"tile": false
},
{
"class": "org.kde.plasmashell",
"tile": false
},
{ {
"class": "kcalc", "class": "kcalc",
"tile": false "tile": false

67
src/extern/kwin.d.ts vendored
View File

@@ -1,28 +1,28 @@
declare const KWin: { declare const KWin: {
readConfig(key: string, defaultValue: any): any; readConfig(key: string, defaultValue: any): any;
registerShortcut(name: string, description: string, keySequence: string, callback: () => void): void;
}; };
declare const workspace: { declare const Workspace: {
readonly desktops: number; readonly activities: string[];
readonly currentDesktop: number; readonly desktops: KwinDesktop[];
readonly currentDesktop: KwinDesktop;
readonly currentActivity: string; readonly currentActivity: string;
readonly activeScreen: Output;
readonly windows: KwinClient[];
readonly cursorPos: QmlPoint;
activeClient: KwinClient; activeWindow: KwinClient;
readonly currentDesktopChanged: QSignal<[]> readonly currentDesktopChanged: QSignal<[]>
readonly clientAdded: QSignal<[KwinClient]>; readonly windowAdded: QSignal<[KwinClient]>;
readonly clientRemoved: QSignal<[KwinClient]>; readonly windowRemoved: QSignal<[KwinClient]>;
readonly clientMinimized: QSignal<[KwinClient]>; readonly windowActivated: QSignal<[KwinClient]>;
readonly clientUnminimized: QSignal<[KwinClient]>; readonly desktopsChanged: QSignal<[]>;
readonly clientMaximizeSet: QSignal<[KwinClient, horizontally: boolean, vertically: boolean]>; readonly activitiesChanged: QSignal<[]>;
readonly clientActivated: QSignal<[KwinClient]>;
readonly numberDesktopsChanged: QSignal<[]>;
readonly currentActivityChanged: QSignal<[]>; readonly currentActivityChanged: QSignal<[]>;
readonly virtualScreenSizeChanged: QSignal<[]>; readonly virtualScreenSizeChanged: QSignal<[]>;
clientArea(option: ClientAreaOption, screenNumber: number, desktopNumber: number); clientArea(option: ClientAreaOption, output: Output, kwinDesktop: KwinDesktop);
clientList(): KwinClient[];
}; };
const enum ClientAreaOption { const enum ClientAreaOption {
@@ -36,7 +36,15 @@ const enum ClientAreaOption {
ScreenArea, ScreenArea,
} }
const enum MaximizedMode {
Unmaximized,
Vertically,
Horizontally,
Maximized,
}
type Tile = unknown; type Tile = unknown;
type Output = unknown;
interface KwinClient { interface KwinClient {
readonly shadeable: boolean; readonly shadeable: boolean;
@@ -46,14 +54,17 @@ interface KwinClient {
readonly transientFor: KwinClient; readonly transientFor: KwinClient;
readonly move: boolean; readonly move: boolean;
readonly resize: boolean; readonly resize: boolean;
readonly moveable: boolean;
readonly resizeable: boolean; readonly resizeable: boolean;
readonly screen: number; readonly fullScreenable: boolean;
readonly maximizable: boolean;
readonly output: Output;
readonly resourceClass: QByteArray; readonly resourceClass: QByteArray;
readonly dock: boolean; readonly dock: boolean;
readonly normalWindow: boolean; readonly normalWindow: boolean;
readonly managed: boolean; readonly managed: boolean;
readonly popupWindow: boolean;
opacity: number;
fullScreen: boolean; fullScreen: boolean;
activities: string[]; // empty array means all activities activities: string[]; // empty array means all activities
skipSwitcher: boolean; skipSwitcher: boolean;
@@ -62,18 +73,30 @@ interface KwinClient {
shade: boolean; shade: boolean;
minimized: boolean; minimized: boolean;
frameGeometry: QmlRect; frameGeometry: QmlRect;
desktop: number; // -1 means all desktops desktops: KwinDesktop[]; // empty array means all desktops
tile: Tile; tile: Tile;
opacity: number;
readonly fullScreenChanged: QSignal<[]>; readonly fullScreenChanged: QSignal<[]>;
readonly desktopChanged: QSignal<[]>; readonly desktopsChanged: QSignal<[]>;
readonly activitiesChanged: QSignal<[]>; readonly activitiesChanged: QSignal<[]>;
readonly minimizedChanged: QSignal<[]>;
readonly maximizedChanged: QSignal<[]>
readonly maximizedAboutToChange: QSignal<[MaximizedMode]>
readonly captionChanged: QSignal<[]>; readonly captionChanged: QSignal<[]>;
readonly tileChanged: QSignal<[]>; readonly tileChanged: QSignal<[]>;
readonly moveResizedChanged: QSignal<[]>; readonly interactiveMoveResizeStarted: QSignal<[]>;
readonly moveResizeCursorChanged: QSignal<[]>; readonly interactiveMoveResizeFinished: QSignal<[]>;
readonly clientStartUserMovedResized: QSignal<[]>; readonly frameGeometryChanged: QSignal<[oldGeometry: QmlRect]>;
readonly frameGeometryChanged: QSignal<[KwinClient, oldGeometry: QmlRect]>;
setMaximize(vertically: boolean, horizontally: boolean): void; setMaximize(vertically: boolean, horizontally: boolean): void;
} }
interface KwinDesktop {
readonly id: string;
}
type ShortcutHandler = {
readonly activated: QSignal<[]>;
destroy(): void;
};

5
src/extern/qt.d.ts vendored
View File

@@ -13,6 +13,11 @@ type QmlObject = unknown;
type QByteArray = string; type QByteArray = string;
type QmlPoint = {
x: number;
y: number;
}
type QmlRect = { type QmlRect = {
x: number; x: number;
y: number; y: number;

View File

@@ -3,176 +3,148 @@ const keyBindings: KeyBinding[] = [
name: "window-toggle-floating", name: "window-toggle-floating",
description: "Toggle floating", description: "Toggle floating",
defaultKeySequence: "Meta+Space", defaultKeySequence: "Meta+Space",
action: "windowToggleFloating",
}, },
{ {
name: "focus-left", name: "focus-left",
description: "Move focus left", description: "Move focus left",
defaultKeySequence: "Meta+A", defaultKeySequence: "Meta+A",
action: "focusLeft",
}, },
{ {
name: "focus-right", name: "focus-right",
description: "Move focus right", description: "Move focus right",
comment: "Clashes with default KDE shortcuts, may require manual remapping", comment: "Clashes with default KDE shortcuts, may require manual remapping",
defaultKeySequence: "Meta+D", defaultKeySequence: "Meta+D",
action: "focusRight",
}, },
{ {
name: "focus-up", name: "focus-up",
description: "Move focus up", description: "Move focus up",
comment: "Clashes with default KDE shortcuts, may require manual remapping", comment: "Clashes with default KDE shortcuts, may require manual remapping",
defaultKeySequence: "Meta+W", defaultKeySequence: "Meta+W",
action: "focusUp",
}, },
{ {
name: "focus-down", name: "focus-down",
description: "Move focus down", description: "Move focus down",
comment: "Clashes with default KDE shortcuts, may require manual remapping", comment: "Clashes with default KDE shortcuts, may require manual remapping",
defaultKeySequence: "Meta+S", defaultKeySequence: "Meta+S",
action: "focusDown",
}, },
{ {
name: "focus-start", name: "focus-start",
description: "Move focus to start", description: "Move focus to start",
defaultKeySequence: "Meta+Home", defaultKeySequence: "Meta+Home",
action: "focusStart",
}, },
{ {
name: "focus-end", name: "focus-end",
description: "Move focus to end", description: "Move focus to end",
defaultKeySequence: "Meta+End", defaultKeySequence: "Meta+End",
action: "focusEnd",
}, },
{ {
name: "window-move-left", name: "window-move-left",
description: "Move window left", description: "Move window left",
comment: "Moves window out of and into columns", comment: "Moves window out of and into columns",
defaultKeySequence: "Meta+Shift+A", defaultKeySequence: "Meta+Shift+A",
action: "windowMoveLeft",
}, },
{ {
name: "window-move-right", name: "window-move-right",
description: "Move window right", description: "Move window right",
comment: "Moves window out of and into columns", comment: "Moves window out of and into columns",
defaultKeySequence: "Meta+Shift+D", defaultKeySequence: "Meta+Shift+D",
action: "windowMoveRight",
}, },
{ {
name: "window-move-up", name: "window-move-up",
description: "Move window up", description: "Move window up",
defaultKeySequence: "Meta+Shift+W", defaultKeySequence: "Meta+Shift+W",
action: "windowMoveUp",
}, },
{ {
name: "window-move-down", name: "window-move-down",
description: "Move window down", description: "Move window down",
defaultKeySequence: "Meta+Shift+S", defaultKeySequence: "Meta+Shift+S",
action: "windowMoveDown",
}, },
{ {
name: "window-move-start", name: "window-move-start",
description: "Move window to start", description: "Move window to start",
defaultKeySequence: "Meta+Shift+Home", defaultKeySequence: "Meta+Shift+Home",
action: "windowMoveStart",
}, },
{ {
name: "window-move-end", name: "window-move-end",
description: "Move window to end", description: "Move window to end",
defaultKeySequence: "Meta+Shift+End", defaultKeySequence: "Meta+Shift+End",
action: "windowMoveEnd",
}, },
{ {
name: "column-toggle-stacked", name: "column-toggle-stacked",
description: "Toggle stacked layout for focused column", description: "Toggle stacked layout for focused column",
comment: "One window in the column visible, others shaded; not supported on Wayland", comment: "One window in the column visible, others shaded; not supported on Wayland",
defaultKeySequence: "Meta+X", defaultKeySequence: "Meta+X",
action: "columnToggleStacked",
}, },
{ {
name: "column-move-left", name: "column-move-left",
description: "Move column left", description: "Move column left",
defaultKeySequence: "Meta+Ctrl+Shift+A", defaultKeySequence: "Meta+Ctrl+Shift+A",
action: "columnMoveLeft",
}, },
{ {
name: "column-move-right", name: "column-move-right",
description: "Move column right", description: "Move column right",
defaultKeySequence: "Meta+Ctrl+Shift+D", defaultKeySequence: "Meta+Ctrl+Shift+D",
action: "columnMoveRight",
}, },
{ {
name: "column-move-start", name: "column-move-start",
description: "Move column to start", description: "Move column to start",
defaultKeySequence: "Meta+Ctrl+Shift+Home", defaultKeySequence: "Meta+Ctrl+Shift+Home",
action: "columnMoveStart",
}, },
{ {
name: "column-move-end", name: "column-move-end",
description: "Move column to end", description: "Move column to end",
defaultKeySequence: "Meta+Ctrl+Shift+End", defaultKeySequence: "Meta+Ctrl+Shift+End",
action: "columnMoveEnd",
}, },
{ {
name: "column-width-increase", name: "column-width-increase",
description: "Increase column width", description: "Increase column width",
defaultKeySequence: "Meta+Ctrl++", defaultKeySequence: "Meta+Ctrl++",
action: "columnWidthIncrease",
}, },
{ {
name: "column-width-decrease", name: "column-width-decrease",
description: "Decrease column width", description: "Decrease column width",
defaultKeySequence: "Meta+Ctrl+-", defaultKeySequence: "Meta+Ctrl+-",
action: "columnWidthDecrease",
}, },
{ {
name: "columns-width-equalize", name: "columns-width-equalize",
description: "Equalize widths of visible columns", description: "Equalize widths of visible columns",
defaultKeySequence: "Meta+Ctrl+X", defaultKeySequence: "Meta+Ctrl+X",
action: "columnsWidthEqualize",
}, },
{ {
name: "grid-scroll-focused", name: "grid-scroll-focused",
description: "Center focused window", description: "Center focused window",
comment: "Scrolls so that the focused window is centered in the screen", comment: "Scrolls so that the focused window is centered in the screen",
defaultKeySequence: "Meta+Alt+Return", defaultKeySequence: "Meta+Alt+Return",
action: "gridScrollFocused",
}, },
{ {
name: "grid-scroll-left-column", name: "grid-scroll-left-column",
description: "Scroll one column to the left", description: "Scroll one column to the left",
defaultKeySequence: "Meta+Alt+A", defaultKeySequence: "Meta+Alt+A",
action: "gridScrollLeftColumn",
}, },
{ {
name: "grid-scroll-right-column", name: "grid-scroll-right-column",
description: "Scroll one column to the right", description: "Scroll one column to the right",
defaultKeySequence: "Meta+Alt+D", defaultKeySequence: "Meta+Alt+D",
action: "gridScrollRightColumn",
}, },
{ {
name: "grid-scroll-left", name: "grid-scroll-left",
description: "Scroll left", description: "Scroll left",
defaultKeySequence: "Meta+Alt+PgUp", defaultKeySequence: "Meta+Alt+PgUp",
action: "gridScrollLeft",
}, },
{ {
name: "grid-scroll-right", name: "grid-scroll-right",
description: "Scroll right", description: "Scroll right",
defaultKeySequence: "Meta+Alt+PgDown", defaultKeySequence: "Meta+Alt+PgDown",
action: "gridScrollRight",
}, },
{ {
name: "grid-scroll-start", name: "grid-scroll-start",
description: "Scroll to start", description: "Scroll to start",
defaultKeySequence: "Meta+Alt+Home", defaultKeySequence: "Meta+Alt+Home",
action: "gridScrollStart",
}, },
{ {
name: "grid-scroll-end", name: "grid-scroll-end",
description: "Scroll to end", description: "Scroll to end",
defaultKeySequence: "Meta+Alt+End", defaultKeySequence: "Meta+Alt+End",
action: "gridScrollEnd",
}, },
]; ];
@@ -183,7 +155,6 @@ const numKeyBindings: NumKeyBinding[] = [
comment: "Clashes with default KDE shortcuts, may require manual remapping", comment: "Clashes with default KDE shortcuts, may require manual remapping",
defaultModifiers: "Meta", defaultModifiers: "Meta",
fKeys: false, fKeys: false,
action: "focusColumn",
}, },
{ {
name: "window-move-to-column-", 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+!", comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!",
defaultModifiers: "Meta+Shift", defaultModifiers: "Meta+Shift",
fKeys: false, fKeys: false,
action: "windowMoveToColumn",
}, },
{ {
name: "column-move-to-column-", 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+!", comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!",
defaultModifiers: "Meta+Ctrl+Shift", defaultModifiers: "Meta+Ctrl+Shift",
fKeys: false, fKeys: false,
action: "columnMoveToColumn",
}, },
{ {
name: "column-move-to-desktop-", name: "column-move-to-desktop-",
description: "Move column to desktop ", description: "Move column to desktop ",
defaultModifiers: "Meta+Ctrl+Shift", defaultModifiers: "Meta+Ctrl+Shift",
fKeys: true, fKeys: true,
action: "columnMoveToDesktop",
}, },
{ {
name: "tail-move-to-desktop-", name: "tail-move-to-desktop-",
description: "Move this and all following columns to desktop ", description: "Move this and all following columns to desktop ",
defaultModifiers: "Meta+Ctrl+Shift+Alt", defaultModifiers: "Meta+Ctrl+Shift+Alt",
fKeys: true, fKeys: true,
action: "tailMoveToDesktop",
}, },
]; ];

View File

@@ -3,7 +3,6 @@ type KeyBinding = {
description: string; description: string;
comment?: string; comment?: string;
defaultKeySequence: string; defaultKeySequence: string;
action: keyof ReturnType<typeof Actions.init>;
}; };
type NumKeyBinding = { type NumKeyBinding = {
@@ -12,7 +11,6 @@ type NumKeyBinding = {
comment?: string; comment?: string;
defaultModifiers: string; defaultModifiers: string;
fKeys: boolean; fKeys: boolean;
action: keyof ReturnType<typeof Actions.initNum>;
}; };
function catchWrap(f: () => void) { function catchWrap(f: () => void) {
@@ -26,45 +24,44 @@ function catchWrap(f: () => void) {
}; };
} }
function registerKeyBinding(name: string, description: string, keySequence: string, callback: () => void) { function registerKeyBinding(world: World, config: Actions.Config, shortcutActions: ShortcutAction[], keyBinding: KeyBinding) {
KWin.registerShortcut( shortcutActions.push(new ShortcutAction(
"karousel-" + name, keyBinding,
"Karousel: " + description, catchWrap(Actions.getAction(world, config, keyBinding.name)),
keySequence, ));
catchWrap(callback),
);
} }
function registerNumKeyBindings(name: string, description: string, modifiers: string, fKeys: boolean, callback: (i: number) => void) { function registerNumKeyBindings(world: World, shortcutActions: ShortcutAction[], numKeyBinding: NumKeyBinding) {
const numPrefix = fKeys ? "F" : ""; const numPrefix = numKeyBinding.fKeys ? "F" : "";
const n = fKeys ? 12 : 9; const n = numKeyBinding.fKeys ? 12 : 9;
for (let i = 0; i < 12; i++) { for (let i = 0; i < 12; i++) {
const numKey = String(i + 1); const numKey = String(i + 1);
const keySequence = i < n ? const keySequence = i < n ?
modifiers + "+" + numPrefix + numKey : numKeyBinding.defaultModifiers + "+" + numPrefix + numKey :
""; "";
registerKeyBinding( const action = Actions.getNumAction(world, numKeyBinding.name);
name + numKey, shortcutActions.push(new ShortcutAction(
description + numKey, {
keySequence, name: numKeyBinding.name + numKey,
() => callback(i), description: numKeyBinding.description + numKey,
); defaultKeySequence: keySequence,
},
catchWrap(() => action(i)),
));
} }
} }
function registerKeyBindings(world: World, config: Config) { // TODO: refactor
const actions = Actions.init(world, { function registerKeyBindings(world: World, config: Actions.Config) {
manualScrollStep: config.manualScrollStep, const shortcutActions: ShortcutAction[] = [];
manualResizeStep: config.manualResizeStep,
columnResizer: config.scrollingCentered ? new RawResizer() : new ContextualResizer(),
});
for (const binding of keyBindings) { for (const keyBinding of keyBindings) {
registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]); registerKeyBinding(world, config, shortcutActions, keyBinding);
} }
const numActions = Actions.initNum(world); for (const numKeyBinding of numKeyBindings) {
for (const binding of numKeyBindings) { registerNumKeyBindings(world, shortcutActions, numKeyBinding);
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
} }
return shortcutActions;
} }

View File

@@ -25,7 +25,7 @@ class Column {
this.grid = targetGrid; this.grid = targetGrid;
targetGrid.onColumnAdded(this, prevColumn); targetGrid.onColumnAdded(this, prevColumn);
for (const window of this.windows.iterator()) { for (const window of this.windows.iterator()) {
window.client.kwinClient.desktop = targetGrid.desktop.desktopNumber; window.client.kwinClient.desktops = [targetGrid.desktop.kwinDesktop];
} }
} }
} }

View File

@@ -1,6 +1,6 @@
class Desktop { class Desktop {
public readonly grid: Grid; public readonly grid: Grid;
public readonly desktopNumber: number; public readonly kwinDesktop: KwinDesktop;
private readonly pinManager: PinManager; private readonly pinManager: PinManager;
private readonly config: Desktop.Config; private readonly config: Desktop.Config;
private scrollX: number; private scrollX: number;
@@ -10,26 +10,26 @@ class Desktop {
public clientArea: QmlRect; public clientArea: QmlRect;
public tilingArea: QmlRect; public tilingArea: QmlRect;
constructor(desktopNumber: number, pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig) { constructor(kwinDesktop: KwinDesktop, pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig) {
this.pinManager = pinManager; this.pinManager = pinManager;
this.config = config; this.config = config;
this.scrollX = 0; this.scrollX = 0;
this.dirty = true; this.dirty = true;
this.dirtyScroll = true; this.dirtyScroll = true;
this.dirtyPins = true; this.dirtyPins = true;
this.desktopNumber = desktopNumber; this.kwinDesktop = kwinDesktop;
this.grid = new Grid(this, layoutConfig); this.grid = new Grid(this, layoutConfig);
this.clientArea = Desktop.getClientArea(desktopNumber); this.clientArea = Desktop.getClientArea(kwinDesktop);
this.tilingArea = Desktop.getTilingArea(this.clientArea, desktopNumber, pinManager, config); this.tilingArea = Desktop.getTilingArea(this.clientArea, kwinDesktop, pinManager, config);
} }
private updateArea() { private updateArea() {
const newClientArea = Desktop.getClientArea(this.desktopNumber); const newClientArea = Desktop.getClientArea(this.kwinDesktop);
if (newClientArea === this.clientArea && !this.dirtyPins) { if (newClientArea === this.clientArea && !this.dirtyPins) {
return; return;
} }
this.clientArea = newClientArea; this.clientArea = newClientArea;
this.tilingArea = Desktop.getTilingArea(newClientArea, this.desktopNumber, this.pinManager, this.config); this.tilingArea = Desktop.getTilingArea(newClientArea, this.kwinDesktop, this.pinManager, this.config);
this.dirty = true; this.dirty = true;
this.dirtyScroll = true; this.dirtyScroll = true;
this.dirtyPins = false; this.dirtyPins = false;
@@ -37,12 +37,12 @@ class Desktop {
this.autoAdjustScroll(); this.autoAdjustScroll();
} }
private static getClientArea(desktopNumber: number) { private static getClientArea(kwinDesktop: KwinDesktop) {
return workspace.clientArea(ClientAreaOption.PlacementArea, 0, desktopNumber); return Workspace.clientArea(ClientAreaOption.PlacementArea, Workspace.activeScreen, kwinDesktop);
} }
private static getTilingArea(clientArea: QmlRect, desktopNumber: number, pinManager: PinManager, config: Desktop.Config) { private static getTilingArea(clientArea: QmlRect, kwinDesktop: KwinDesktop, pinManager: PinManager, config: Desktop.Config) {
const availableSpace = pinManager.getAvailableSpace(desktopNumber, clientArea); const availableSpace = pinManager.getAvailableSpace(kwinDesktop, clientArea);
const top = availableSpace.top + config.marginTop; const top = availableSpace.top + config.marginTop;
const bottom = availableSpace.bottom - config.marginBottom; const bottom = availableSpace.bottom - config.marginBottom;
const left = availableSpace.left + config.marginLeft; const left = availableSpace.left + config.marginLeft;

View File

@@ -10,8 +10,7 @@ class Window {
this.height = client.kwinClient.frameGeometry.height; this.height = client.kwinClient.frameGeometry.height;
this.focusedState = { this.focusedState = {
fullScreen: false, fullScreen: false,
maximizedHorizontally: false, maximizedMode: MaximizedMode.Unmaximized,
maximizedVertically: false,
}; };
this.skipArrange = false; this.skipArrange = false;
this.column = column; this.column = column;
@@ -37,8 +36,11 @@ class Window {
if (this.column.grid.config.reMaximize && this.isFocused()) { if (this.column.grid.config.reMaximize && this.isFocused()) {
// do this here rather than in `onFocused` to ensure it happens after placement // do this here rather than in `onFocused` to ensure it happens after placement
// (otherwise placement may not happen at all) // (otherwise placement may not happen at all)
if (this.focusedState.maximizedVertically || this.focusedState.maximizedHorizontally) { if (this.focusedState.maximizedMode > MaximizedMode.Unmaximized) {
this.client.setMaximize(this.focusedState.maximizedVertically, this.focusedState.maximizedHorizontally); this.client.setMaximize(
this.focusedState.maximizedMode === MaximizedMode.Horizontally || this.focusedState.maximizedMode === MaximizedMode.Maximized,
this.focusedState.maximizedMode === MaximizedMode.Vertically || this.focusedState.maximizedMode === MaximizedMode.Maximized,
);
maximized = true; maximized = true;
} }
if (this.focusedState.fullScreen) { if (this.focusedState.fullScreen) {
@@ -76,8 +78,8 @@ class Window {
this.column.grid.desktop.onLayoutChanged(); this.column.grid.desktop.onLayoutChanged();
} }
public onMaximizedChanged(horizontally: boolean, vertically: boolean) { public onMaximizedChanged(maximizedMode: MaximizedMode) {
const maximized = horizontally || vertically; const maximized = maximizedMode > MaximizedMode.Unmaximized;
this.skipArrange = maximized; this.skipArrange = maximized;
if (this.column.grid.config.tiledKeepBelow) { if (this.column.grid.config.tiledKeepBelow) {
this.client.kwinClient.keepBelow = !maximized; this.client.kwinClient.keepBelow = !maximized;
@@ -86,8 +88,7 @@ class Window {
this.client.kwinClient.keepAbove = maximized; this.client.kwinClient.keepAbove = maximized;
} }
if (this.isFocused()) { if (this.isFocused()) {
this.focusedState.maximizedHorizontally = horizontally; this.focusedState.maximizedMode = maximizedMode;
this.focusedState.maximizedVertically = vertically;
} }
this.column.grid.desktop.onLayoutChanged(); this.column.grid.desktop.onLayoutChanged();
} }
@@ -145,7 +146,6 @@ class Window {
namespace Window { namespace Window {
export type State = { export type State = {
fullScreen: boolean, fullScreen: boolean,
maximizedHorizontally: boolean, maximizedMode: MaximizedMode,
maximizedVertically: boolean,
} }
} }

View File

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

View File

@@ -33,9 +33,9 @@ class WindowRuleEnforcer {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const desktop = desktopManager.getDesktopForClient(kwinClient); const desktop = desktopManager.getDesktopForClient(kwinClient);
if (shouldTile && desktop !== undefined) { if (shouldTile && desktop !== undefined) {
clientManager.tileClient(kwinClient, desktop.grid); clientManager.tileKwinClient(kwinClient, desktop.grid);
} else { } else {
clientManager.untileClient(kwinClient); clientManager.floatKwinClient(kwinClient);
} }
}); });
}); });

View File

@@ -18,7 +18,7 @@ class Delayer {
function initQmlTimer() { function initQmlTimer() {
return Qt.createQmlObject( return Qt.createQmlObject(
`import QtQuick 2.15 `import QtQuick 6.0
Timer {}`, Timer {}`,
qmlBase qmlBase
); );

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

@@ -1,49 +1,23 @@
function initWorkspaceSignalHandlers(world: World) { function initWorkspaceSignalHandlers(world: World) {
const manager = new SignalManager(); const manager = new SignalManager();
manager.connect(workspace.clientAdded, (kwinClient: KwinClient) => { manager.connect(Workspace.windowAdded, (kwinClient: KwinClient) => {
if (Clients.canTileEver(kwinClient)) { if (Clients.canTileEver(kwinClient)) {
// never open new tileable clients on all desktops or activities // never open new tileable clients on all desktops or activities
if (kwinClient.desktop <= 0) { Clients.makeTileable(kwinClient);
kwinClient.desktop = workspace.currentDesktop;
}
if (kwinClient.activities.length !== 1) {
kwinClient.activities = [workspace.currentActivity];
}
} }
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
clientManager.addClient(kwinClient) clientManager.addClient(kwinClient)
}); });
}); });
manager.connect(workspace.clientRemoved, (kwinClient: KwinClient) => { manager.connect(Workspace.windowRemoved, (kwinClient: KwinClient) => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
clientManager.removeClient(kwinClient, true); clientManager.removeClient(kwinClient, true);
}); });
}); });
manager.connect(workspace.clientMinimized, (kwinClient: KwinClient) => { manager.connect(Workspace.windowActivated, (kwinClient: KwinClient) => {
world.do((clientManager, desktopManager) => {
clientManager.minimizeClient(kwinClient);
});
});
manager.connect(workspace.clientUnminimized, (kwinClient: KwinClient) => {
world.do((clientManager, desktopManager) => {
clientManager.unminimizeClient(kwinClient);
});
});
manager.connect(workspace.clientMaximizeSet, (kwinClient: KwinClient, horizontally: boolean, vertically: boolean) => {
if ((horizontally || vertically) && kwinClient.tile !== null) {
kwinClient.tile = null;
}
world.doIfTiled(kwinClient, false, (clientManager, desktopManager, window, column, grid) => {
window.onMaximizedChanged(horizontally, vertically);
});
});
manager.connect(workspace.clientActivated, (kwinClient: KwinClient) => {
if (kwinClient === null) { if (kwinClient === null) {
return; return;
} }
@@ -52,19 +26,27 @@ function initWorkspaceSignalHandlers(world: World) {
}); });
}); });
manager.connect(workspace.currentDesktopChanged, () => { manager.connect(Workspace.currentDesktopChanged, () => {
world.do(() => {}); // re-arrange desktop world.do(() => {}); // re-arrange desktop
}); });
manager.connect(workspace.currentActivityChanged, () => { manager.connect(Workspace.currentActivityChanged, () => {
world.do(() => {}); // re-arrange desktop world.do(() => {}); // re-arrange desktop
}); });
manager.connect(workspace.numberDesktopsChanged, () => { manager.connect(Workspace.desktopsChanged, () => {
world.updateDesktops(); world.do((clientManager, desktopManager) => {
desktopManager.updateDesktops();
})
}); });
manager.connect(workspace.virtualScreenSizeChanged, () => { manager.connect(Workspace.activitiesChanged, () => {
world.do((clientManager, desktopManager) => {
desktopManager.updateActivities();
})
});
manager.connect(Workspace.virtualScreenSizeChanged, () => {
world.onScreenResized(); world.onScreenResized();
}); });

View File

@@ -76,44 +76,41 @@ class ClientManager {
return; return;
} }
if (client.stateManager.getState() instanceof ClientState.Tiled) { if (client.stateManager.getState() instanceof ClientState.Tiled) {
client.stateManager.setState(() => new ClientState.TiledMinimized(), kwinClient === this.lastFocusedClient); client.stateManager.setState(
() => new ClientState.TiledMinimized(this.world, client),
kwinClient === this.lastFocusedClient,
);
} }
} }
public unminimizeClient(kwinClient: KwinClient) { public tileClient(client: ClientWrapper, grid: Grid) {
const client = this.clientMap.get(kwinClient);
if (client === undefined) {
return;
}
if (client.stateManager.getState() instanceof ClientState.TiledMinimized) {
const desktop = this.desktopManager.getDesktopForClient(kwinClient);
if (desktop !== undefined) {
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, desktop.grid), false);
} else {
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, false), false);
}
}
}
public tileClient(kwinClient: KwinClient, grid: Grid) {
const client = this.clientMap.get(kwinClient);
if (client === undefined) {
return;
}
if (client.stateManager.getState() instanceof ClientState.Tiled) { if (client.stateManager.getState() instanceof ClientState.Tiled) {
return; return;
} }
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false); client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
} }
public untileClient(kwinClient: KwinClient) { public floatClient(client: ClientWrapper) {
if (client.stateManager.getState() instanceof ClientState.Floating) {
return;
}
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false);
}
public tileKwinClient(kwinClient: KwinClient, grid: Grid) {
const client = this.clientMap.get(kwinClient); const client = this.clientMap.get(kwinClient);
if (client === undefined) { if (client === undefined) {
return; return;
} }
if (client.stateManager.getState() instanceof ClientState.Tiled) { this.tileClient(client, grid);
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false); }
public floatKwinClient(kwinClient: KwinClient) {
const client = this.clientMap.get(kwinClient);
if (client === undefined) {
return;
} }
this.floatClient(client);
} }
public pinClient(kwinClient: KwinClient) { public pinClient(kwinClient: KwinClient) {

View File

@@ -3,8 +3,10 @@ class ClientWrapper {
public readonly stateManager: ClientState.Manager; public readonly stateManager: ClientState.Manager;
public transientFor: ClientWrapper | null; public transientFor: ClientWrapper | null;
private readonly transients: ClientWrapper[]; private readonly transients: ClientWrapper[];
private readonly signalManager: SignalManager;
private readonly rulesSignalManager: SignalManager | null; private readonly rulesSignalManager: SignalManager | null;
public preferredWidth: number; public preferredWidth: number;
private maximizedMode: MaximizedMode | undefined;
private readonly manipulatingGeometry: Doer; private readonly manipulatingGeometry: Doer;
private lastPlacement: QmlRect | null; // workaround for issue #19 private lastPlacement: QmlRect | null; // workaround for issue #19
@@ -20,6 +22,7 @@ class ClientWrapper {
if (transientFor !== null) { if (transientFor !== null) {
transientFor.addTransient(this); transientFor.addTransient(this);
} }
this.signalManager = ClientWrapper.initSignalManager(this);
this.rulesSignalManager = rulesSignalManager; this.rulesSignalManager = rulesSignalManager;
this.preferredWidth = kwinClient.frameGeometry.width; this.preferredWidth = kwinClient.frameGeometry.width;
this.manipulatingGeometry = new Doer(); this.manipulatingGeometry = new Doer();
@@ -33,21 +36,20 @@ class ClientWrapper {
// window is being manually resized, prevent fighting with the user // window is being manually resized, prevent fighting with the user
return; return;
} }
const clientWrapper = this; // workaround for bug in Qt5's JS engine this.lastPlacement = Qt.rect(x, y, width, height);
clientWrapper.lastPlacement = Qt.rect(x, y, width, height); this.kwinClient.frameGeometry = this.lastPlacement;
clientWrapper.kwinClient.frameGeometry = clientWrapper.lastPlacement; if (this.kwinClient.frameGeometry !== this.lastPlacement) {
if (clientWrapper.kwinClient.frameGeometry !== clientWrapper.lastPlacement) {
// frameGeometry assignment failed. This sometimes happens on Wayland // frameGeometry assignment failed. This sometimes happens on Wayland
// when a window is off-screen, effectively making it stuck there. // when a window is off-screen, effectively making it stuck there.
clientWrapper.kwinClient.frameGeometry.x = x; // This makes it unstuck. this.kwinClient.frameGeometry.x = x; // This makes it unstuck.
clientWrapper.kwinClient.frameGeometry = clientWrapper.lastPlacement; this.kwinClient.frameGeometry = this.lastPlacement;
} }
}); });
} }
private moveTransient(dx: number, dy: number, desktopNumber: number) { private moveTransient(dx: number, dy: number, kwinDesktops: KwinDesktop[]) {
if (this.stateManager.getState() instanceof ClientState.Floating) { if (this.stateManager.getState() instanceof ClientState.Floating) {
if (this.kwinClient.desktop === desktopNumber) { if (Clients.isOnOneOfVirtualDesktops(this.kwinClient, kwinDesktops)) {
const frame = this.kwinClient.frameGeometry; const frame = this.kwinClient.frameGeometry;
this.kwinClient.frameGeometry = Qt.rect( this.kwinClient.frameGeometry = Qt.rect(
frame.x + dx, frame.x + dx,
@@ -58,32 +60,52 @@ class ClientWrapper {
} }
for (const transient of this.transients) { for (const transient of this.transients) {
transient.moveTransient(dx, dy, desktopNumber); transient.moveTransient(dx, dy, kwinDesktops);
} }
} }
} }
public moveTransients(dx: number, dy: number) { public moveTransients(dx: number, dy: number) {
for (const transient of this.transients) { for (const transient of this.transients) {
transient.moveTransient(dx, dy, this.kwinClient.desktop); transient.moveTransient(dx, dy, this.kwinClient.desktops);
} }
} }
public focus() { public focus() {
workspace.activeClient = this.kwinClient; Workspace.activeWindow = this.kwinClient;
} }
public isFocused() { public isFocused() {
return workspace.activeClient === this.kwinClient; return Workspace.activeWindow === this.kwinClient;
} }
public setMaximize(horizontally: boolean, vertically: boolean) { public setMaximize(horizontally: boolean, vertically: boolean) {
if (!this.kwinClient.maximizable) {
return;
}
if (this.maximizedMode === undefined) {
if (horizontally && vertically) {
this.maximizedMode = MaximizedMode.Maximized;
} else if (horizontally) {
this.maximizedMode = MaximizedMode.Horizontally;
} else if (vertically) {
this.maximizedMode = MaximizedMode.Vertically;
} else {
this.maximizedMode = MaximizedMode.Unmaximized;
}
}
this.manipulatingGeometry.do(() => { this.manipulatingGeometry.do(() => {
this.kwinClient.setMaximize(vertically, horizontally); this.kwinClient.setMaximize(vertically, horizontally);
}); });
} }
public setFullScreen(fullScreen: boolean) { public setFullScreen(fullScreen: boolean) {
if (!this.kwinClient.fullScreenable) {
return;
}
this.manipulatingGeometry.do(() => { this.manipulatingGeometry.do(() => {
this.kwinClient.fullScreen = fullScreen; this.kwinClient.fullScreen = fullScreen;
}); });
@@ -99,6 +121,10 @@ class ClientWrapper {
return this.kwinClient.shade; return this.kwinClient.shade;
} }
public getMaximizedMode() {
return this.maximizedMode;
}
public isManipulatingGeometry(newGeometry: QmlRect | null) { public isManipulatingGeometry(newGeometry: QmlRect | null) {
if (newGeometry !== null && newGeometry === this.lastPlacement) { if (newGeometry !== null && newGeometry === this.lastPlacement) {
return true; return true;
@@ -125,7 +151,7 @@ class ClientWrapper {
} }
public ensureVisible(screenSize: QmlRect) { public ensureVisible(screenSize: QmlRect) {
if (this.kwinClient.desktop !== workspace.currentDesktop) { if (!Clients.isOnVirtualDesktop(this.kwinClient, Workspace.currentDesktop)) {
return; return;
} }
const frame = this.kwinClient.frameGeometry; const frame = this.kwinClient.frameGeometry;
@@ -138,6 +164,7 @@ class ClientWrapper {
public destroy(passFocus: boolean) { public destroy(passFocus: boolean) {
this.stateManager.destroy(passFocus); this.stateManager.destroy(passFocus);
this.signalManager.destroy();
if (this.rulesSignalManager !== null) { if (this.rulesSignalManager !== null) {
this.rulesSignalManager.destroy(); this.rulesSignalManager.destroy();
} }
@@ -148,4 +175,17 @@ class ClientWrapper {
transient.transientFor = null; transient.transientFor = null;
} }
} }
private static initSignalManager(client: ClientWrapper) {
const manager = new SignalManager();
manager.connect(client.kwinClient.maximizedAboutToChange, (maximizedMode: MaximizedMode) => {
if (maximizedMode > MaximizedMode.Unmaximized && client.kwinClient.tile !== null) {
client.kwinClient.tile = null;
}
client.maximizedMode = maximizedMode;
});
return manager;
}
} }

View File

@@ -1,35 +1,49 @@
namespace Clients { namespace Clients {
export function canTileEver(kwinClient: KwinClient) { export function canTileEver(kwinClient: KwinClient) {
return kwinClient.resizeable; return kwinClient.moveable && kwinClient.resizeable && !kwinClient.popupWindow;
} }
export function canTileNow(kwinClient: KwinClient) { export function canTileNow(kwinClient: KwinClient) {
return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktop > 0 && kwinClient.activities.length === 1; return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktops.length === 1 && kwinClient.activities.length === 1;
} }
export function makeTileable(kwinClient: KwinClient) { export function makeTileable(kwinClient: KwinClient) {
if (kwinClient.minimized) { if (kwinClient.minimized) {
kwinClient.minimized = false; kwinClient.minimized = false;
} }
if (kwinClient.desktop <= 0) { if (kwinClient.desktops.length !== 1) {
kwinClient.desktop = workspace.currentDesktop; kwinClient.desktops = [Workspace.currentDesktop];
} }
if (kwinClient.activities.length !== 1) { if (kwinClient.activities.length !== 1) {
kwinClient.activities = [workspace.currentActivity]; kwinClient.activities = [Workspace.currentActivity];
} }
} }
export function isMaximizedGeometry(kwinClient: KwinClient) { export function getKwinDesktopApprox(kwinClient: KwinClient) {
const maximizeArea = workspace.clientArea(ClientAreaOption.MaximizeArea, kwinClient.screen, kwinClient.desktop); switch (kwinClient.desktops.length) {
return kwinClient.frameGeometry === maximizeArea; case 0:
return Workspace.currentDesktop;
case 1:
return kwinClient.desktops[0];
default:
if (kwinClient.desktops.includes(Workspace.currentDesktop)) {
return Workspace.currentDesktop;
} else {
return kwinClient.desktops[0];
}
}
} }
export function isFullScreenGeometry(kwinClient: KwinClient) { export function isFullScreenGeometry(kwinClient: KwinClient) {
const fullScreenArea = workspace.clientArea(ClientAreaOption.FullScreenArea, kwinClient.screen, kwinClient.desktop); const fullScreenArea = Workspace.clientArea(ClientAreaOption.FullScreenArea, kwinClient.output, getKwinDesktopApprox(kwinClient));
return kwinClient.frameGeometry === fullScreenArea; return kwinClient.frameGeometry === fullScreenArea;
} }
export function isOnVirtualDesktop(kwinClient: KwinClient, desktopNumber: number) { export function isOnVirtualDesktop(kwinClient: KwinClient, kwinDesktop: KwinDesktop) {
return kwinClient.desktop === desktopNumber || kwinClient.desktop === -1; return kwinClient.desktops.length === 0 || kwinClient.desktops.includes(kwinDesktop);
}
export function isOnOneOfVirtualDesktops(kwinClient: KwinClient, kwinDesktops: KwinDesktop[]) {
return kwinClient.desktops.length === 0 || kwinClient.desktops.some(d => kwinDesktops.includes(d));
} }
} }

View File

@@ -2,147 +2,125 @@ class DesktopManager {
private readonly pinManager: PinManager; private readonly pinManager: PinManager;
private readonly config: Desktop.Config; private readonly config: Desktop.Config;
public readonly layoutConfig: LayoutConfig; public readonly layoutConfig: LayoutConfig;
private readonly desktopsPerActivity: Map<string, Desktop[]>; private readonly desktops: Map<string, Desktop>; // key is activityId|desktopId
private nVirtualDesktops: number; private kwinActivities: Set<string>;
private kwinDesktops: Set<KwinDesktop>;
constructor(pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig, currentActivity: string) { constructor(pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig, currentActivity: string, currentDesktop: KwinDesktop) {
this.pinManager = pinManager; this.pinManager = pinManager;
this.config = config; this.config = config;
this.layoutConfig = layoutConfig; this.layoutConfig = layoutConfig;
this.desktopsPerActivity = new Map(); this.desktops = new Map();
this.nVirtualDesktops = 0; this.kwinActivities = new Set(Workspace.activities);
this.update() this.kwinDesktops = new Set(Workspace.desktops);
this.addActivity(currentActivity); this.addDesktop(currentActivity, currentDesktop);
} }
public update() { public getDesktop(activity: string, kwinDesktop: KwinDesktop) {
this.setNVirtualDesktops(workspace.desktops); const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
} const desktop = this.desktops.get(desktopKey);
if (desktop !== undefined) {
public getDesktop(activity: string, desktopNumber: number) { return desktop;
const desktopIndex = desktopNumber - 1; } else {
if (desktopIndex >= this.nVirtualDesktops || desktopIndex < 0) { return this.addDesktop(activity, kwinDesktop);
throw new Error("invalid desktop number: " + String(desktopNumber));
} }
if (!this.desktopsPerActivity.has(activity)) {
this.addActivity(activity);
}
return this.desktopsPerActivity.get(activity)![desktopIndex];
} }
public getCurrentDesktop() { public getCurrentDesktop() {
return this.getDesktop(workspace.currentActivity, workspace.currentDesktop); return this.getDesktop(Workspace.currentActivity, Workspace.currentDesktop);
} }
public getDesktopInCurrentActivity(desktopNumber: number) { public getDesktopInCurrentActivity(kwinDesktop: KwinDesktop) {
return this.getDesktop(workspace.currentActivity, desktopNumber); return this.getDesktop(Workspace.currentActivity, kwinDesktop);
} }
public getDesktopForClient(kwinClient: KwinClient) { public getDesktopForClient(kwinClient: KwinClient) {
if (kwinClient.activities.length !== 1 || kwinClient.desktop <= 0) { if (kwinClient.activities.length !== 1 || kwinClient.desktops.length !== 1) {
return undefined; return undefined;
} }
return this.getDesktop(kwinClient.activities[0], kwinClient.desktop); return this.getDesktop(kwinClient.activities[0], kwinClient.desktops[0]);
} }
private setNVirtualDesktops(nVirtualDesktops: number) { private addDesktop(activity: string, kwinDesktop: KwinDesktop) {
if (nVirtualDesktops > this.nVirtualDesktops) { const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
this.addDesktopsToActivities(nVirtualDesktops - this.nVirtualDesktops); const desktop = new Desktop(kwinDesktop, this.pinManager, this.config, this.layoutConfig);
} else if (nVirtualDesktops < this.nVirtualDesktops) { this.desktops.set(desktopKey, desktop);
this.removeDesktopsFromActivities(this.nVirtualDesktops - nVirtualDesktops); return desktop;
}
this.nVirtualDesktops = nVirtualDesktops;
} }
private addDesktopsToActivities(n: number) { private static getDesktopKey(activity: string, kwinDesktop: KwinDesktop) {
for (const desktops of this.desktopsPerActivity.values()) { return activity + "|" + kwinDesktop.id;
this.addDesktops(desktops, n);
}
} }
private addDesktops(desktops: Desktop[], n: number) { public updateActivities() {
const nStart = desktops.length; const newActivities = new Set(Workspace.activities);
for (let i = 0; i < n; i++) { for (const activity of this.kwinActivities) {
const desktopNumber = nStart + i + 1; if (!newActivities.has(activity)) {
desktops.push(new Desktop(desktopNumber, this.pinManager, this.config, this.layoutConfig)); this.removeActivity(activity);
}
}
private removeDesktopsFromActivities(n: number) {
const lastRemainingDesktopIndex = this.nVirtualDesktops - n - 1;
for (const desktops of this.desktopsPerActivity.values()) {
const targetDesktop = desktops[lastRemainingDesktopIndex];
for (let i = 0; i < n; i++) {
const removedDesktop = desktops.pop()!;
removedDesktop.grid.evacuate(targetDesktop.grid);
} }
} }
this.kwinActivities = newActivities;
} }
private addActivity(activity: string) { public updateDesktops() {
const desktops: Desktop[] = []; const newDesktops = new Set(Workspace.desktops);
this.addDesktops(desktops, this.nVirtualDesktops); for (const desktop of this.kwinDesktops) {
this.desktopsPerActivity.set(activity, desktops); if (!newDesktops.has(desktop)) {
this.removeKwinDesktop(desktop);
}
}
this.kwinDesktops = newDesktops;
} }
private removeActivity(activity: string) { private removeActivity(activity: string) {
const removedDesktops = this.desktopsPerActivity.get(activity)!; for (const kwinDesktop of this.kwinDesktops) {
this.desktopsPerActivity.delete(activity); this.destroyDesktop(activity, kwinDesktop);
const targetActivityDesktops = this.desktopsPerActivity.values().next().value; }
for (let i = 0; i < removedDesktops.length; i++) { }
removedDesktops[i].grid.evacuate(targetActivityDesktops[i]);
private removeKwinDesktop(kwinDesktop: KwinDesktop) {
for (const activity of this.kwinActivities) {
this.destroyDesktop(activity, kwinDesktop);
}
}
private destroyDesktop(activity: string, kwinDesktop: KwinDesktop) {
const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
const desktop = this.desktops.get(desktopKey);
if (desktop !== undefined) {
desktop.destroy();
this.desktops.delete(desktopKey);
} }
} }
public destroy() { public destroy() {
for (const desktop of this.desktops()) { for (const desktop of this.desktops.values()) {
desktop.destroy(); desktop.destroy();
} }
} }
public *desktops() { public *getAllDesktops() {
for (const desktops of this.desktopsPerActivity.values()) { for (const desktop of this.desktops.values()) {
for (const desktop of desktops) { yield desktop;
yield desktop;
}
} }
} }
public *getDesktopsForClient(kwinClient: KwinClient) { public getDesktopsForClient(kwinClient: KwinClient) {
const activities = kwinClient.activities.length > 0 ? kwinClient.activities : this.desktopsPerActivity.keys(); const desktops = this.getDesktops(kwinClient.activities, kwinClient.desktops); // workaround for QTBUG-109880
for (const activity of activities) { return desktops;
if (!this.desktopsPerActivity.has(activity)) {
this.addActivity(activity);
}
const activityDesktops = this.desktopsPerActivity.get(activity)!;
if (kwinClient.desktop === -1) {
for (const desktop of activityDesktops) {
yield desktop;
}
} else {
const desktopIndex = kwinClient.desktop - 1;
yield activityDesktops[desktopIndex];
}
}
} }
// empty array means all // empty array means all
public *getDesktops(desktopNumbers: number[], inputActivities: string[]) { public *getDesktops(activities: string[], kwinDesktops: KwinDesktop[]) {
const activities = inputActivities.length > 0 ? inputActivities : this.desktopsPerActivity.keys(); const matchedActivities = activities.length > 0 ? activities : this.kwinActivities.keys();
for (const activity of activities) { const matchedDesktops = kwinDesktops.length > 0 ? kwinDesktops : this.kwinDesktops.keys();
if (!this.desktopsPerActivity.has(activity)) { for (const matchedActivity of matchedActivities) {
this.addActivity(activity); for (const matchedDesktop of matchedDesktops) {
} const desktopKey = DesktopManager.getDesktopKey(matchedActivity, matchedDesktop);
const activityDesktops = this.desktopsPerActivity.get(activity)!; const desktop = this.desktops.get(desktopKey);
if (desktopNumbers.length === 0) { if (desktop !== undefined) {
for (const desktop of activityDesktops) {
yield desktop; yield desktop;
} }
} else {
for (const desktopNumber of desktopNumbers) {
const desktopIndex = desktopNumber - 1;
yield activityDesktops[desktopIndex];
}
} }
} }
} }

View File

@@ -13,11 +13,11 @@ class PinManager {
this.pinnedClients.delete(kwinClient); this.pinnedClients.delete(kwinClient);
} }
public getAvailableSpace(desktopNumber: number, screen: QmlRect) { public getAvailableSpace(kwinDesktop: KwinDesktop, screen: QmlRect) {
const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right); const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right);
let lots = [baseLot]; let lots = [baseLot];
for (const client of this.pinnedClients) { for (const client of this.pinnedClients) {
if (!Clients.isOnVirtualDesktop(client, desktopNumber)) { if (!Clients.isOnVirtualDesktop(client, kwinDesktop)) {
continue; continue;
} }

View File

@@ -4,16 +4,21 @@ class World {
public readonly clientManager: ClientManager; public readonly clientManager: ClientManager;
private readonly pinManager: PinManager; private readonly pinManager: PinManager;
private readonly workspaceSignalManager: SignalManager; private readonly workspaceSignalManager: SignalManager;
private readonly shortcutActions: ShortcutAction[];
private readonly screenResizedDelayer: Delayer; private readonly screenResizedDelayer: Delayer;
constructor(config: Config) { constructor(config: Config) {
this.untileOnDrag = config.untileOnDrag; this.untileOnDrag = config.untileOnDrag;
this.workspaceSignalManager = initWorkspaceSignalHandlers(this); 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.screenResizedDelayer = new Delayer(1000, () => {
// this delay ensures that docks are taken into account by `workspace.clientArea` // this delay ensures that docks are taken into account by `Workspace.clientArea`
const desktopManager = this.desktopManager; // workaround for bug in Qt5's JS engine for (const desktop of this.desktopManager.getAllDesktops()) {
for (const desktop of desktopManager.desktops()) {
desktop.onLayoutChanged(); desktop.onLayoutChanged();
} }
this.update(); this.update();
@@ -47,7 +52,8 @@ class World {
clamper: config.scrollingLazy ? new EdgeClamper() : new CenterClamper(), clamper: config.scrollingLazy ? new EdgeClamper() : new CenterClamper(),
}, },
layoutConfig, layoutConfig,
workspace.currentActivity, Workspace.currentActivity,
Workspace.currentDesktop,
); );
this.clientManager = new ClientManager(config, this, this.desktopManager, this.pinManager); this.clientManager = new ClientManager(config, this, this.desktopManager, this.pinManager);
this.addExistingClients(); this.addExistingClients();
@@ -55,17 +61,13 @@ class World {
} }
private addExistingClients() { private addExistingClients() {
const kwinClients = workspace.clientList(); const kwinClients = Workspace.windows;
for (let i = 0; i < kwinClients.length; i++) { for (let i = 0; i < kwinClients.length; i++) {
const kwinClient = kwinClients[i]; const kwinClient = kwinClients[i];
this.clientManager.addClient(kwinClient); this.clientManager.addClient(kwinClient);
} }
} }
public updateDesktops() {
this.desktopManager.update();
}
private update() { private update() {
this.desktopManager.getCurrentDesktop().arrange(); this.desktopManager.getCurrentDesktop().arrange();
} }
@@ -94,11 +96,14 @@ class World {
followTransient: boolean, followTransient: boolean,
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void, f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
) { ) {
this.doIfTiled(workspace.activeClient, followTransient, f); this.doIfTiled(Workspace.activeWindow, followTransient, f);
} }
public destroy() { public destroy() {
this.workspaceSignalManager.destroy(); this.workspaceSignalManager.destroy();
for (const shortcutAction of this.shortcutActions) {
shortcutAction.destroy();
}
this.clientManager.destroy(); this.clientManager.destroy();
this.desktopManager.destroy(); this.desktopManager.destroy();
} }

View File

@@ -16,7 +16,7 @@ namespace ClientState {
private static initSignalManager(world: World, kwinClient: KwinClient) { private static initSignalManager(world: World, kwinClient: KwinClient) {
const manager = new SignalManager(); const manager = new SignalManager();
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QmlRect) => { manager.connect(kwinClient.frameGeometryChanged, () => {
world.onScreenResized(); world.onScreenResized();
}); });
return manager; return manager;

View File

@@ -24,7 +24,11 @@ namespace ClientState {
} }
private static limitHeight(client: ClientWrapper) { private static limitHeight(client: ClientWrapper) {
const placementArea = workspace.clientArea(ClientAreaOption.PlacementArea, client.kwinClient.screen, client.kwinClient.desktop); const placementArea = Workspace.clientArea(
ClientAreaOption.PlacementArea,
client.kwinClient.output,
Clients.getKwinDesktopApprox(client.kwinClient),
);
const clientRect = client.kwinClient.frameGeometry; const clientRect = client.kwinClient.frameGeometry;
const width = client.preferredWidth; const width = client.preferredWidth;
client.place( client.place(

View File

@@ -30,8 +30,8 @@ namespace ClientState {
private static initSignalManager(world: World, pinManager: PinManager, kwinClient: KwinClient) { private static initSignalManager(world: World, pinManager: PinManager, kwinClient: KwinClient) {
const manager = new SignalManager(); const manager = new SignalManager();
let oldDesktopNumber = kwinClient.desktop;
let oldActivities = kwinClient.activities; let oldActivities = kwinClient.activities;
let oldDesktops = kwinClient.desktops;
manager.connect(kwinClient.tileChanged, () => { manager.connect(kwinClient.tileChanged, () => {
if (kwinClient.tile === null) { if (kwinClient.tile === null) {
@@ -41,7 +41,7 @@ namespace ClientState {
} }
}); });
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QmlRect) => { manager.connect(kwinClient.frameGeometryChanged, () => {
if (kwinClient.tile === null) { if (kwinClient.tile === null) {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
clientManager.unpinClient(kwinClient); clientManager.unpinClient(kwinClient);
@@ -56,25 +56,24 @@ namespace ClientState {
}) })
}); });
manager.connect(kwinClient.desktopChanged, () => { manager.connect(kwinClient.desktopsChanged, () => {
const changedDesktops = oldDesktopNumber === -1 || kwinClient.desktop === -1 ? const changedDesktops = oldDesktops.length === 0 || kwinClient.desktops.length === 0 ?
[] : [] :
[oldDesktopNumber, kwinClient.desktop]; union(oldDesktops, kwinClient.desktops);
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
for (const desktop of desktopManager.getDesktops(changedDesktops, kwinClient.activities)) { for (const desktop of desktopManager.getDesktops(kwinClient.activities, changedDesktops)) {
desktop.onPinsChanged(); desktop.onPinsChanged();
} }
}); });
oldDesktopNumber = kwinClient.desktop; oldDesktops = kwinClient.desktops;
}); });
manager.connect(kwinClient.activitiesChanged, () => { manager.connect(kwinClient.activitiesChanged, () => {
const desktops = kwinClient.desktop === -1 ? [] : [kwinClient.desktop];
const changedActivities = oldActivities.length === 0 || kwinClient.activities.length === 0 ? const changedActivities = oldActivities.length === 0 || kwinClient.activities.length === 0 ?
[] : [] :
union(oldActivities, kwinClient.activities); union(oldActivities, kwinClient.activities);
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
for (const desktop of desktopManager.getDesktops(desktops, changedActivities)) { for (const desktop of desktopManager.getDesktops(changedActivities, kwinClient.desktops)) {
desktop.onPinsChanged(); desktop.onPinsChanged();
} }
}); });

View File

@@ -31,12 +31,12 @@ namespace ClientState {
const kwinClient = client.kwinClient; const kwinClient = client.kwinClient;
const manager = new SignalManager(); const manager = new SignalManager();
manager.connect(kwinClient.desktopChanged, () => { manager.connect(kwinClient.desktopsChanged, () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const desktop = desktopManager.getDesktopForClient(kwinClient); const desktop = desktopManager.getDesktopForClient(kwinClient);
if (desktop === undefined) { if (desktop === undefined) {
// windows on all desktops are not supported // windows on multiple desktops are not supported
clientManager.untileClient(kwinClient); clientManager.floatKwinClient(kwinClient);
return; return;
} }
Tiled.moveWindowToGrid(window, desktop.grid); Tiled.moveWindowToGrid(window, desktop.grid);
@@ -48,44 +48,54 @@ namespace ClientState {
const desktop = desktopManager.getDesktopForClient(kwinClient); const desktop = desktopManager.getDesktopForClient(kwinClient);
if (desktop === undefined) { if (desktop === undefined) {
// windows on multiple activities are not supported // windows on multiple activities are not supported
clientManager.untileClient(kwinClient); clientManager.floatKwinClient(kwinClient);
return; return;
} }
Tiled.moveWindowToGrid(window, desktop.grid); Tiled.moveWindowToGrid(window, desktop.grid);
}); });
}) })
let lastResize = false; manager.connect(kwinClient.minimizedChanged, () => {
manager.connect(kwinClient.moveResizedChanged, () => { console.assert(kwinClient.minimized);
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
if (kwinClient.move) { clientManager.minimizeClient(kwinClient);
if (world.untileOnDrag) {
clientManager.untileClient(kwinClient);
}
return;
}
const grid = window.column.grid;
const resize = kwinClient.resize;
if (!lastResize && resize) {
grid.onUserResizeStarted();
}
if (lastResize && !resize) {
grid.onUserResizeFinished();
}
lastResize = resize;
}); });
}); });
let cursorChangedAfterResizeStart = false; manager.connect(kwinClient.maximizedAboutToChange, (maximizedMode: MaximizedMode) => {
manager.connect(kwinClient.moveResizeCursorChanged, () => { world.do(() => {
cursorChangedAfterResizeStart = true; window.onMaximizedChanged(maximizedMode);
}); });
manager.connect(kwinClient.clientStartUserMovedResized, () => {
cursorChangedAfterResizeStart = false;
}); });
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QmlRect) => { let resizing = false;
let resizingBorder = false;
manager.connect(kwinClient.interactiveMoveResizeStarted, () => {
if (kwinClient.move) {
if (world.untileOnDrag) {
world.do((clientManager, desktopManager) => {
clientManager.floatKwinClient(kwinClient);
});
}
return;
}
if (kwinClient.resize) {
resizing = true;
resizingBorder = Workspace.cursorPos.x > kwinClient.frameGeometry.right ||
Workspace.cursorPos.x < kwinClient.frameGeometry.left;
window.column.grid.onUserResizeStarted();
}
});
manager.connect(kwinClient.interactiveMoveResizeFinished, () => {
if (resizing) {
resizing = false;
window.column.grid.onUserResizeFinished();
}
});
manager.connect(kwinClient.frameGeometryChanged, (oldGeometry: QmlRect) => {
// on Wayland, this fires after `tileChanged` // on Wayland, this fires after `tileChanged`
if (kwinClient.tile !== null) { if (kwinClient.tile !== null) {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
@@ -106,11 +116,11 @@ namespace ClientState {
} }
if (kwinClient.resize) { if (kwinClient.resize) {
world.do(() => window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart)); world.do(() => window.onUserResize(oldGeometry, resizingBorder));
} else if ( } else if (
!window.column.grid.isUserResizing() && !window.column.grid.isUserResizing() &&
!client.isManipulatingGeometry(newGeometry) && !client.isManipulatingGeometry(newGeometry) &&
!Clients.isMaximizedGeometry(kwinClient) && client.getMaximizedMode() === MaximizedMode.Unmaximized &&
!Clients.isFullScreenGeometry(kwinClient) // not using `kwinClient.fullScreen` because it may not be set yet at this point !Clients.isFullScreenGeometry(kwinClient) // not using `kwinClient.fullScreen` because it may not be set yet at this point
) { ) {
world.do(() => window.onFrameGeometryChanged()); world.do(() => window.onFrameGeometryChanged());

View File

@@ -1,5 +1,31 @@
namespace ClientState { namespace ClientState {
export class TiledMinimized implements State { export class TiledMinimized implements State {
public destroy(passFocus: boolean) {} private readonly signalManager: SignalManager;
constructor(world: World, client: ClientWrapper) {
this.signalManager = TiledMinimized.initSignalManager(world, client);
}
public destroy(passFocus: boolean) {
this.signalManager.destroy();
}
private static initSignalManager(world: World, client: ClientWrapper) {
const manager = new SignalManager();
manager.connect(client.kwinClient.minimizedChanged, () => {
console.assert(!client.kwinClient.minimized);
world.do((clientManager, desktopManager) => {
const desktop = desktopManager.getDesktopForClient(client.kwinClient);
if (desktop !== undefined) {
clientManager.tileClient(client, desktop.grid);
} else {
clientManager.floatClient(client);
}
});
});
return manager;
}
} }
} }