Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb43f45287 | ||
|
|
e61d7538b2 | ||
|
|
a3c0976f55 | ||
|
|
b372489eb5 | ||
|
|
fa974a68aa | ||
|
|
20517aed7f | ||
|
|
b49082d51d | ||
|
|
08135a4ad4 | ||
|
|
681ae38d85 | ||
|
|
9912a8d917 | ||
|
|
1bcf768588 | ||
|
|
c84fddc618 | ||
|
|
ad62dafdc7 | ||
|
|
425c5c9e5b | ||
|
|
4b0f259c6d | ||
|
|
a5ecc94479 | ||
|
|
a3f479e2e6 | ||
|
|
daef95731b | ||
|
|
3b3bb679de | ||
|
|
566c8fe53d | ||
|
|
07ce7d4e60 | ||
|
|
0dfc29b1eb | ||
|
|
4d784c5d01 | ||
|
|
a4d27a2885 | ||
|
|
f703f0655a | ||
|
|
963949b039 | ||
|
|
22ee707207 | ||
|
|
8d6e4f9bc7 | ||
|
|
075f6c7e3d | ||
|
|
5404b61d20 | ||
|
|
76b0016055 | ||
|
|
64cdb90f4a | ||
|
|
80ecc7e6c9 | ||
|
|
0e59f382b4 | ||
|
|
6001dd5b02 | ||
|
|
201dd4463e | ||
|
|
10718bc2c7 | ||
|
|
b15bb85037 | ||
|
|
4904d075ae | ||
|
|
7871bbbe6d | ||
|
|
d91ea7b412 | ||
|
|
37e9b85279 | ||
|
|
0bdb4af0e6 | ||
|
|
4f06f17ba7 | ||
|
|
382cbe101b | ||
|
|
e580acf979 | ||
|
|
453c4ece2c | ||
|
|
15b77d0207 | ||
|
|
d23c13c344 | ||
|
|
c4307e187f | ||
|
|
463da59197 | ||
|
|
c5ec40e5ea | ||
|
|
2f4268fc94 | ||
|
|
f1911b1247 | ||
|
|
5b71f1c48f | ||
|
|
c7e7b91f3f | ||
|
|
4b3a403559 | ||
|
|
9477b7e337 | ||
|
|
c87ef982ae | ||
|
|
de3e78424a | ||
|
|
3039033ea9 | ||
|
|
de0f89062a | ||
|
|
0831c1be8b | ||
|
|
d6bfe2fd03 | ||
|
|
048bf2a51a | ||
|
|
a949dca458 | ||
|
|
57c4643098 | ||
|
|
e92563b424 | ||
|
|
671326bdd7 | ||
|
|
5e9db7d2cd | ||
|
|
b447eacdfd | ||
|
|
94f6e6f33b | ||
|
|
85b0221220 | ||
|
|
1894b055f7 | ||
|
|
05f7550a3b | ||
|
|
a04f629de0 | ||
|
|
4bda4d0d7c | ||
|
|
8bf076948a | ||
|
|
04bd85a287 |
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
|||||||
.PHONY: *
|
.PHONY: *
|
||||||
|
|
||||||
TSC_SCRIPT_FLAGS = --lib es2020 ./src/extern.d.ts
|
TSC_SCRIPT_FLAGS = --lib es2020 ./src/extern/qt.d.ts
|
||||||
|
|
||||||
config:
|
config:
|
||||||
mkdir -p ./package/contents/config
|
mkdir -p ./package/contents/config
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
|
|||||||
- Doesn't support windows on multiple activities
|
- Doesn't support windows on multiple activities
|
||||||
|
|
||||||
## Key bindings
|
## Key bindings
|
||||||
|
The key bindings can be configured in KDE System Settings among KWin's own keyboard shortcuts.
|
||||||
|
Here's the default ones:
|
||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Meta+Space | Toggle floating |
|
| Meta+Space | Toggle floating |
|
||||||
@@ -37,14 +39,13 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
|
|||||||
| Meta+Shift+S | Move window down |
|
| Meta+Shift+S | Move window down |
|
||||||
| Meta+Shift+Home | Move window to start |
|
| Meta+Shift+Home | Move window to start |
|
||||||
| Meta+Shift+End | Move window to end |
|
| Meta+Shift+End | Move window to end |
|
||||||
| Meta+X | Expand window (Expands focused window vertically; toggles stacked layout for focused column) |
|
| Meta+X | Toggle stacked layout for focused column |
|
||||||
| Meta+Ctrl+Shift+A | Move column left |
|
| Meta+Ctrl+Shift+A | Move column left |
|
||||||
| Meta+Ctrl+Shift+D | Move column right |
|
| Meta+Ctrl+Shift+D | Move column right |
|
||||||
| Meta+Ctrl+Shift+Home | Move column to start |
|
| Meta+Ctrl+Shift+Home | Move column to start |
|
||||||
| Meta+Ctrl+Shift+End | Move column to end |
|
| Meta+Ctrl+Shift+End | Move column to end |
|
||||||
| Meta+Ctrl+X | Expand column (Expands focused column horizontally to fill the screen) |
|
| Meta+Ctrl++ | Increase column width |
|
||||||
| Meta+Alt++ | Expand fully visible columns (Expands fully visible columns to fill the screen) |
|
| Meta+Ctrl+- | Decrease column width |
|
||||||
| Meta+Alt+- | Shrink visible columns (Shrinks fully and partially visible columns, making them fully visible and filling the screen) |
|
|
||||||
| Meta+Alt+Return | Center focused window (Scrolls so that the focused window is centered in the screen) |
|
| Meta+Alt+Return | Center focused window (Scrolls so that the focused window is centered in the screen) |
|
||||||
| Meta+Alt+A | Scroll one column to the left |
|
| Meta+Alt+A | Scroll one column to the left |
|
||||||
| Meta+Alt+D | Scroll one column to the right |
|
| Meta+Alt+D | Scroll one column to the right |
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface KeyBinding {
|
type KeyBinding = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
@@ -6,7 +6,7 @@ interface KeyBinding {
|
|||||||
action: string;
|
action: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NumKeyBinding {
|
type NumKeyBinding = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import org.kde.kwin 3.0
|
import org.kde.kwin 3.0
|
||||||
|
import org.kde.notification 1.0
|
||||||
import "./main.js" as Karousel
|
import "./main.js" as Karousel
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -9,11 +10,19 @@ Item {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
qmlBase.karouselInstance = Karousel.init();
|
qmlBase.karouselInstance = Karousel.init();
|
||||||
print("script started");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
qmlBase.karouselInstance.destroy();
|
qmlBase.karouselInstance.destroy();
|
||||||
print("script stopped");
|
}
|
||||||
|
|
||||||
|
Notification {
|
||||||
|
id: notificationInvalidWindowRules
|
||||||
|
componentName: "plasma_workspace"
|
||||||
|
eventId: "notification"
|
||||||
|
title: "Karousel"
|
||||||
|
text: "Your Window Rules JSON is malformed, please review your Karousel configuration"
|
||||||
|
flags: Notification.Persistent
|
||||||
|
urgency: Notification.HighUrgency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,31 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_manualScrollStep">
|
||||||
|
<property name="text">
|
||||||
|
<string>Manual scroll step size:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="7" column="1">
|
||||||
|
<widget class="QSpinBox" name="kcfg_manualScrollStep">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> px</string>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>999</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item row="8" column="1">
|
||||||
<widget class="QCheckBox" name="kcfg_untileOnDrag">
|
<widget class="QCheckBox" name="kcfg_untileOnDrag">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Un-tile windows by dragging them</string>
|
<string>Un-tile windows by dragging them</string>
|
||||||
@@ -192,7 +216,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="8" column="1">
|
<item row="9" column="1">
|
||||||
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
|
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Stack columns by default</string>
|
<string>Stack columns by default</string>
|
||||||
@@ -200,7 +224,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="9" column="1">
|
<item row="10" column="1">
|
||||||
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
|
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Resize neighbor column on edge resize</string>
|
<string>Resize neighbor column on edge resize</string>
|
||||||
@@ -208,7 +232,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="10" column="0" colspan="2">
|
<item row="11" column="0" colspan="2">
|
||||||
<spacer name="bottomSpacer_tab_general">
|
<spacer name="bottomSpacer_tab_general">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@@ -219,7 +243,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab_windowRules">
|
<widget class="QWidget" name="tab_windowRules">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Window rules</string>
|
<string>Window Rules</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="layout_tab_windowRules">
|
<layout class="QVBoxLayout" name="layout_tab_windowRules">
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
}],
|
}],
|
||||||
"Id": "karousel",
|
"Id": "karousel",
|
||||||
"ServiceTypes": ["KWin/Script"],
|
"ServiceTypes": ["KWin/Script"],
|
||||||
"Version": "0.3",
|
"Version": "0.4",
|
||||||
"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"
|
||||||
|
|||||||
323
src/Actions.ts
Normal file
323
src/Actions.ts
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
namespace Actions {
|
||||||
|
export function init(world: World, config: Config) {
|
||||||
|
return {
|
||||||
|
focusLeft: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const prevColumn = grid.getPrevColumn(column);
|
||||||
|
if (prevColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevColumn.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusRight: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const nextColumn = grid.getNextColumn(column);
|
||||||
|
if (nextColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextColumn.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusUp: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const prevWindow = column.getPrevWindow(window);
|
||||||
|
if (prevWindow === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevWindow.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusDown: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const nextWindow = column.getNextWindow(window);
|
||||||
|
if (nextWindow === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextWindow.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusStart: () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const firstColumn = grid.getFirstColumn();
|
||||||
|
if (firstColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
firstColumn.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusEnd: () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const lastColumn = grid.getLastColumn();
|
||||||
|
if (lastColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastColumn.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveLeft: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
if (column.getWindowCount() === 1) {
|
||||||
|
// move from own column into existing column
|
||||||
|
const prevColumn = grid.getPrevColumn(column);
|
||||||
|
if (prevColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.moveToColumn(prevColumn);
|
||||||
|
grid.desktop.autoAdjustScroll();
|
||||||
|
} else {
|
||||||
|
// move from shared column into own column
|
||||||
|
const newColumn = new Column(grid, grid.getPrevColumn(column));
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveRight: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
if (column.getWindowCount() === 1) {
|
||||||
|
// move from own column into existing column
|
||||||
|
const nextColumn = grid.getNextColumn(column);
|
||||||
|
if (nextColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.moveToColumn(nextColumn);
|
||||||
|
grid.desktop.autoAdjustScroll();
|
||||||
|
} else {
|
||||||
|
// move from shared column into own column
|
||||||
|
const newColumn = new Column(grid, column);
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveUp: () => {
|
||||||
|
// TODO (optimization): only arrange moved windows
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
column.moveWindowUp(window);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveDown: () => {
|
||||||
|
// TODO (optimization): only arrange moved windows
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
column.moveWindowDown(window);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveStart: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const newColumn = new Column(grid, null);
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveEnd: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const newColumn = new Column(grid, grid.getLastColumn());
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowToggleFloating: () => {
|
||||||
|
const kwinClient = workspace.activeClient;
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.toggleFloatingClient(kwinClient);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnMoveLeft: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
grid.moveColumnLeft(column);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnMoveRight: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
grid.moveColumnRight(column);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnMoveStart: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
column.moveAfter(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnMoveEnd: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
column.moveAfter(grid.getLastColumn());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnToggleStacked: () => {
|
||||||
|
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => {
|
||||||
|
column.toggleStacked();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnWidthIncrease: () => {
|
||||||
|
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => {
|
||||||
|
grid.increaseColumnWidth(column);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnWidthDecrease: () => {
|
||||||
|
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => {
|
||||||
|
grid.decreaseColumnWidth(column);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollLeft: () => {
|
||||||
|
gridScroll(world, -config.manualScrollStep);
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollRight: () => {
|
||||||
|
gridScroll(world, config.manualScrollStep);
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollStart: () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const firstColumn = grid.getFirstColumn();
|
||||||
|
if (firstColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid.desktop.scrollToColumn(firstColumn);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollEnd: () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const lastColumn = grid.getLastColumn();
|
||||||
|
if (lastColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid.desktop.scrollToColumn(lastColumn);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollFocused: () => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
grid.desktop.scrollCenterColumn(column);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollLeftColumn: () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentScrollPos(), true);
|
||||||
|
if (column === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevColumn = grid.getPrevColumn(column);
|
||||||
|
if (prevColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.desktop.scrollToColumn(prevColumn);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
gridScrollRightColumn: () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentScrollPos(), true);
|
||||||
|
if (column === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextColumn = grid.getNextColumn(column);
|
||||||
|
if (nextColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.desktop.scrollToColumn(nextColumn);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initNum(world: World) {
|
||||||
|
return {
|
||||||
|
focusColumn: (columnIndex: number) => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||||
|
if (targetColumn === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
targetColumn.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
windowMoveToColumn: (columnIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||||
|
if (targetColumn === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
window.moveToColumn(targetColumn);
|
||||||
|
grid.desktop.autoAdjustScroll();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnMoveToColumn: (columnIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => {
|
||||||
|
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||||
|
if (targetColumn === null || targetColumn === column) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (targetColumn.isAfter(column)) {
|
||||||
|
column.moveAfter(targetColumn);
|
||||||
|
} else {
|
||||||
|
column.moveAfter(grid.getPrevColumn(targetColumn));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
columnMoveToDesktop: (desktopIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => {
|
||||||
|
const desktopNumber = desktopIndex + 1;
|
||||||
|
const newGrid = desktopManager.getDesktopInCurrentActivity(desktopNumber).grid;
|
||||||
|
if (newGrid === null || newGrid === oldGrid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
column.moveToGrid(newGrid, newGrid.getLastColumn());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
tailMoveToDesktop: (desktopIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => {
|
||||||
|
const desktopNumber = desktopIndex + 1;
|
||||||
|
const newGrid = desktopManager.getDesktopInCurrentActivity(desktopNumber).grid;
|
||||||
|
if (newGrid === null || newGrid === oldGrid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
oldGrid.evacuateTail(newGrid, column);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function gridScroll(world: World, amount: number) {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
grid.desktop.adjustScroll(amount, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Config = {
|
||||||
|
manualScrollStep: number,
|
||||||
|
};
|
||||||
|
}
|
||||||
10
src/ClientAreaOption.ts
Normal file
10
src/ClientAreaOption.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
enum ClientAreaOption {
|
||||||
|
PlacementArea,
|
||||||
|
MovementArea,
|
||||||
|
MaximizeArea,
|
||||||
|
MaximizeFullArea,
|
||||||
|
FullScreenArea,
|
||||||
|
WorkArea,
|
||||||
|
FullArea,
|
||||||
|
ScreenArea,
|
||||||
|
}
|
||||||
341
src/actions.ts
341
src/actions.ts
@@ -1,341 +0,0 @@
|
|||||||
function initActions(world: World) {
|
|
||||||
return {
|
|
||||||
focusLeft: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const prevColumn = grid.getPrevColumn(column);
|
|
||||||
if (prevColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prevColumn.focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
focusRight: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const nextColumn = grid.getNextColumn(column);
|
|
||||||
if (nextColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nextColumn.focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
focusUp: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const prevWindow = column.getPrevWindow(window);
|
|
||||||
if (prevWindow === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prevWindow.focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
focusDown: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const nextWindow = column.getNextWindow(window);
|
|
||||||
if (nextWindow === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nextWindow.focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
focusStart: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const firstColumn = grid.getFirstColumn();
|
|
||||||
if (firstColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
firstColumn.focus();
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
focusEnd: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const lastColumn = grid.getLastColumn();
|
|
||||||
if (lastColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastColumn.focus();
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveLeft: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
if (column.getWindowCount() === 1) {
|
|
||||||
// move from own column into existing column
|
|
||||||
const prevColumn = grid.getPrevColumn(column);
|
|
||||||
if (prevColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.moveToColumn(prevColumn);
|
|
||||||
grid.container.onGridReordered();
|
|
||||||
} else {
|
|
||||||
// move from shared column into own column
|
|
||||||
const newColumn = new Column(grid, grid.getPrevColumn(column));
|
|
||||||
window.moveToColumn(newColumn);
|
|
||||||
}
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveRight: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
if (column.getWindowCount() === 1) {
|
|
||||||
// move from own column into existing column
|
|
||||||
const nextColumn = grid.getNextColumn(column);
|
|
||||||
if (nextColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.moveToColumn(nextColumn);
|
|
||||||
grid.container.onGridReordered();
|
|
||||||
} else {
|
|
||||||
// move from shared column into own column
|
|
||||||
const newColumn = new Column(grid, column);
|
|
||||||
window.moveToColumn(newColumn);
|
|
||||||
}
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveUp: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
column.moveWindowUp(window);
|
|
||||||
grid.container.arrange(); // TODO (optimization): only arrange moved windows
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveDown: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
column.moveWindowDown(window);
|
|
||||||
grid.container.arrange(); // TODO (optimization): only arrange moved windows
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveStart: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const newColumn = new Column(grid, null);
|
|
||||||
window.moveToColumn(newColumn);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveEnd: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const newColumn = new Column(grid, grid.getLastColumn());
|
|
||||||
window.moveToColumn(newColumn);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
windowToggleFloating: () => {
|
|
||||||
const kwinClient = workspace.activeClient;
|
|
||||||
world.toggleFloatingClient(kwinClient);
|
|
||||||
},
|
|
||||||
|
|
||||||
columnMoveLeft: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
grid.moveColumnLeft(column);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnMoveRight: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
grid.moveColumnRight(column);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnMoveStart: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
column.moveAfter(null);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnMoveEnd: () => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
column.moveAfter(grid.getLastColumn());
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnToggleStacked: () => {
|
|
||||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
|
||||||
column.toggleStacked();
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnWidthIncrease: () => {
|
|
||||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
|
||||||
grid.increaseColumnWidth(column);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnWidthDecrease: () => {
|
|
||||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
|
||||||
grid.decreaseColumnWidth(column);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
gridScrollStart: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const firstColumn = grid.getFirstColumn();
|
|
||||||
if (firstColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
grid.container.scrollToColumn(firstColumn);
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
gridScrollEnd: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const lastColumn = grid.getLastColumn();
|
|
||||||
if (lastColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
grid.container.scrollToColumn(lastColumn);
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
gridScrollFocused: () => {
|
|
||||||
const focusedWindow = world.getFocusedWindow();
|
|
||||||
if (focusedWindow === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const column = focusedWindow.column;
|
|
||||||
const grid = column.grid;
|
|
||||||
grid.container.scrollCenterColumn(column);
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
gridScrollLeftColumn: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const column = grid.getLeftmostVisibleColumn(grid.container.getCurrentScrollPos(), true);
|
|
||||||
if (column === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevColumn = grid.getPrevColumn(column);
|
|
||||||
if (prevColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.container.scrollToColumn(prevColumn);
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
gridScrollRightColumn: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const column = grid.getRightmostVisibleColumn(grid.container.getCurrentScrollPos(), true);
|
|
||||||
if (column === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextColumn = grid.getNextColumn(column);
|
|
||||||
if (nextColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.container.scrollToColumn(nextColumn);
|
|
||||||
grid.container.arrange();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNumActions(world: World) {
|
|
||||||
return {
|
|
||||||
focusColumn: (columnIndex: number) => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
|
||||||
if (targetColumn === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
targetColumn.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
windowMoveToColumn: (columnIndex: number) => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
|
||||||
if (targetColumn === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
window.moveToColumn(targetColumn);
|
|
||||||
grid.container.onGridReordered();
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnMoveToColumn: (columnIndex: number) => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
|
||||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
|
||||||
if (targetColumn === null || targetColumn === column) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (targetColumn.isAfter(column)) {
|
|
||||||
column.moveAfter(targetColumn);
|
|
||||||
} else {
|
|
||||||
column.moveAfter(grid.getPrevColumn(targetColumn));
|
|
||||||
}
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
columnMoveToDesktop: (desktopIndex: number) => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, oldGrid) => {
|
|
||||||
const desktopNumber = desktopIndex + 1;
|
|
||||||
const newGrid = world.getGridInCurrentActivity(desktopNumber);
|
|
||||||
if (newGrid === null || newGrid === oldGrid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
column.moveToGrid(newGrid, newGrid.getLastColumn());
|
|
||||||
oldGrid.container.arrange();
|
|
||||||
newGrid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
tailMoveToDesktop: (desktopIndex: number) => {
|
|
||||||
world.doIfTiledFocused(true, (window, column, oldGrid) => {
|
|
||||||
const desktopNumber = desktopIndex + 1;
|
|
||||||
const newGrid = world.getGridInCurrentActivity(desktopNumber);
|
|
||||||
if (newGrid === null || newGrid === oldGrid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
oldGrid.evacuateTail(newGrid, column);
|
|
||||||
oldGrid.container.arrange();
|
|
||||||
newGrid.container.arrange();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function gridScroll(world: World, amount: number) {
|
|
||||||
const scrollAmount = amount;
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
grid.container.adjustScroll(scrollAmount, false);
|
|
||||||
grid.container.arrange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function canTileEver(kwinClient: AbstractClient) {
|
|
||||||
return kwinClient.resizeable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function canTileNow(kwinClient: AbstractClient) {
|
|
||||||
return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktop > 0 && kwinClient.activities.length === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeTileable(kwinClient: AbstractClient) {
|
|
||||||
if (kwinClient.minimized) {
|
|
||||||
kwinClient.minimized = false;
|
|
||||||
}
|
|
||||||
if (kwinClient.desktop <= 0) {
|
|
||||||
kwinClient.desktop = workspace.currentDesktop;
|
|
||||||
}
|
|
||||||
if (kwinClient.activities.length !== 1) {
|
|
||||||
kwinClient.activities = [workspace.currentActivity];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,9 @@ type Config = {
|
|||||||
gapsInnerHorizontal: number,
|
gapsInnerHorizontal: number,
|
||||||
gapsInnerVertical: number,
|
gapsInnerVertical: number,
|
||||||
overscroll: number,
|
overscroll: number,
|
||||||
|
manualScrollStep: number,
|
||||||
untileOnDrag: boolean,
|
untileOnDrag: boolean,
|
||||||
stackColumnsByDefault: boolean,
|
stackColumnsByDefault: boolean,
|
||||||
resizeNeighborColumn: boolean,
|
resizeNeighborColumn: boolean,
|
||||||
windowRules: string,
|
windowRules: string,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ const defaultWindowRules = `[
|
|||||||
"class": "kruler",
|
"class": "kruler",
|
||||||
"tile": false
|
"tile": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"class": "krunner",
|
||||||
|
"tile": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"class": "zoom",
|
"class": "zoom",
|
||||||
"caption": "Zoom Cloud Meetings",
|
"caption": "Zoom Cloud Meetings",
|
||||||
@@ -83,6 +87,11 @@ const configDef = [
|
|||||||
"type": "UInt",
|
"type": "UInt",
|
||||||
"default": 18
|
"default": 18
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "manualScrollStep",
|
||||||
|
"type": "UInt",
|
||||||
|
"default": 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "untileOnDrag",
|
"name": "untileOnDrag",
|
||||||
"type": "Bool",
|
"type": "Bool",
|
||||||
|
|||||||
13
src/extern.d.ts
vendored
13
src/extern.d.ts
vendored
@@ -1,13 +0,0 @@
|
|||||||
declare const qmlBase;
|
|
||||||
declare const console;
|
|
||||||
declare const KWin;
|
|
||||||
declare const Qt;
|
|
||||||
declare const workspace;
|
|
||||||
declare const options;
|
|
||||||
|
|
||||||
type AbstractClient = any;
|
|
||||||
type TopLevel = any;
|
|
||||||
type X11Client = any;
|
|
||||||
type QRect = any;
|
|
||||||
type QSignal = any;
|
|
||||||
type QQmlTimer = any;
|
|
||||||
6
src/extern/global.d.ts
vendored
Normal file
6
src/extern/global.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
declare const qmlBase: QmlObject;
|
||||||
|
declare const notificationInvalidWindowRules: Notification;
|
||||||
|
|
||||||
|
type Notification = {
|
||||||
|
sendEvent(): void;
|
||||||
|
};
|
||||||
83
src/extern/kwin.d.ts
vendored
Normal file
83
src/extern/kwin.d.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
declare const KWin: {
|
||||||
|
// Functions
|
||||||
|
readConfig(key: string, defaultValue: any): any;
|
||||||
|
registerShortcut(name: string, description: string, keySequence: string, callback: () => void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare const workspace: {
|
||||||
|
// Read-write Properties
|
||||||
|
readonly desktops: number;
|
||||||
|
readonly currentDesktop: number;
|
||||||
|
readonly currentActivity: string;
|
||||||
|
|
||||||
|
// Read-write Properties
|
||||||
|
activeClient: KwinClient;
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
currentDesktopChanged: QSignal<[oldDesktopNumber: number]>
|
||||||
|
clientAdded: QSignal<[KwinClient]>;
|
||||||
|
clientRemoved: QSignal<[AbstractClient]>;
|
||||||
|
clientMinimized: QSignal<[AbstractClient]>;
|
||||||
|
clientUnminimized: QSignal<[AbstractClient]>;
|
||||||
|
clientMaximizeSet: QSignal<[AbstractClient, horizontally: boolean, vertically: boolean]>;
|
||||||
|
clientActivated: QSignal<[AbstractClient]>;
|
||||||
|
numberDesktopsChanged: QSignal<[oldNumberOfVirtualDesktops: number]>;
|
||||||
|
currentActivityChanged: QSignal<[newActivity: string]>;
|
||||||
|
virtualScreenSizeChanged: QSignal<[void]>;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
clientArea(option: ClientAreaOption, screenNumber: number, desktopNumber: number);
|
||||||
|
clientList(): TopLevel[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Tile = any;
|
||||||
|
|
||||||
|
interface AbstractClient {
|
||||||
|
// Read-only Properties
|
||||||
|
readonly caption: string;
|
||||||
|
readonly minSize: QSize;
|
||||||
|
readonly transient: boolean;
|
||||||
|
readonly transientFor: AbstractClient;
|
||||||
|
readonly move: boolean;
|
||||||
|
readonly resize: boolean;
|
||||||
|
readonly resizeable: boolean;
|
||||||
|
|
||||||
|
// Read-write Properties
|
||||||
|
fullScreen: boolean;
|
||||||
|
activities: string[]; // empty array means all activities
|
||||||
|
keepBelow: boolean;
|
||||||
|
shade: boolean;
|
||||||
|
minimized: boolean;
|
||||||
|
tile: Tile;
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
fullScreenChanged: QSignal<[void]>;
|
||||||
|
desktopChanged: QSignal<[void]>;
|
||||||
|
activitiesChanged: QSignal<[AbstractClient]>;
|
||||||
|
captionChanged: QSignal<[void]>;
|
||||||
|
tileChanged: QSignal<[Tile]>;
|
||||||
|
moveResizedChanged: QSignal<[void]>;
|
||||||
|
moveResizeCursorChanged: QSignal<[void]>;
|
||||||
|
clientStartUserMovedResized: QSignal<[void]>;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
setMaximize(vertically: boolean, horizontally: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TopLevel extends AbstractClient {
|
||||||
|
// Read-only Properties
|
||||||
|
readonly screen: number;
|
||||||
|
readonly resourceClass: QByteArray;
|
||||||
|
readonly dock: boolean;
|
||||||
|
readonly normalWindow: boolean;
|
||||||
|
readonly managed: boolean;
|
||||||
|
|
||||||
|
// Read-write Properties
|
||||||
|
frameGeometry: QRect;
|
||||||
|
desktop: number; // -1 means all desktops
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
frameGeometryChanged: QSignal<[TopLevel, oldGeometry: QRect]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KwinClient extends TopLevel {}
|
||||||
41
src/extern/qt.d.ts
vendored
Normal file
41
src/extern/qt.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
declare const console: {
|
||||||
|
log(...args: any[]);
|
||||||
|
assert(boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
declare const Qt: {
|
||||||
|
rect(x: number, y: number, width: number, height: number): QRect;
|
||||||
|
createQmlObject(qml: string, parent: QmlObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
type QmlObject = unknown;
|
||||||
|
|
||||||
|
type QByteArray = string;
|
||||||
|
|
||||||
|
type QRect = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
top: number;
|
||||||
|
bottom: number;
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QSize = {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QSignal<T extends unknown[]> = {
|
||||||
|
connect(handler: (...args: [...T]) => void): void;
|
||||||
|
disconnect(handler: (...args: [...T]) => void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QQmlTimer = {
|
||||||
|
interval: number;
|
||||||
|
triggered: QSignal<[void]>;
|
||||||
|
restart(): void;
|
||||||
|
destroy(): void;
|
||||||
|
};
|
||||||
@@ -14,18 +14,21 @@ const keyBindings: KeyBinding[] = [
|
|||||||
{
|
{
|
||||||
"name": "focus-right",
|
"name": "focus-right",
|
||||||
"description": "Move focus right",
|
"description": "Move focus right",
|
||||||
|
"comment": "Clashes with default KDE shortcuts, may require manual remapping",
|
||||||
"defaultKeySequence": "Meta+D",
|
"defaultKeySequence": "Meta+D",
|
||||||
"action": "focusRight",
|
"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",
|
||||||
"defaultKeySequence": "Meta+W",
|
"defaultKeySequence": "Meta+W",
|
||||||
"action": "focusUp",
|
"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",
|
||||||
"defaultKeySequence": "Meta+S",
|
"defaultKeySequence": "Meta+S",
|
||||||
"action": "focusDown",
|
"action": "focusDown",
|
||||||
},
|
},
|
||||||
@@ -140,6 +143,18 @@ const keyBindings: KeyBinding[] = [
|
|||||||
"defaultKeySequence": "Meta+Alt+D",
|
"defaultKeySequence": "Meta+Alt+D",
|
||||||
"action": "gridScrollRightColumn",
|
"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",
|
"name": "grid-scroll-start",
|
||||||
"description": "Scroll to start",
|
"description": "Scroll to start",
|
||||||
@@ -158,6 +173,7 @@ const numKeyBindings: NumKeyBinding[] = [
|
|||||||
{
|
{
|
||||||
"name": "focus-",
|
"name": "focus-",
|
||||||
"description": "Move focus to column ",
|
"description": "Move focus to column ",
|
||||||
|
"comment": "Clashes with default KDE shortcuts, may require manual remapping",
|
||||||
"defaultModifiers": "Meta",
|
"defaultModifiers": "Meta",
|
||||||
"fKeys": false,
|
"fKeys": false,
|
||||||
"action": "focusColumn",
|
"action": "focusColumn",
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
interface KeyBinding {
|
type KeyBinding = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
defaultKeySequence: string;
|
defaultKeySequence: string;
|
||||||
action: keyof ReturnType<typeof initActions>;
|
action: keyof ReturnType<typeof Actions.init>;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface NumKeyBinding {
|
type NumKeyBinding = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
defaultModifiers: string;
|
defaultModifiers: string;
|
||||||
fKeys: boolean;
|
fKeys: boolean;
|
||||||
action: keyof ReturnType<typeof initNumActions>;
|
action: keyof ReturnType<typeof Actions.initNum>;
|
||||||
}
|
};
|
||||||
|
|
||||||
function catchWrap(f: () => void) {
|
function catchWrap(f: () => void) {
|
||||||
return () => {
|
return () => {
|
||||||
try {
|
try {
|
||||||
f();
|
f();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(error);
|
log(error);
|
||||||
console.log(error.stack);
|
log(error.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -38,24 +38,27 @@ function registerKeyBinding(name: string, description: string, keySequence: stri
|
|||||||
function registerNumKeyBindings(name: string, description: string, modifiers: string, fKeys: boolean, callback: (i: number) => void) {
|
function registerNumKeyBindings(name: string, description: string, modifiers: string, fKeys: boolean, callback: (i: number) => void) {
|
||||||
const numPrefix = fKeys ? "F" : "";
|
const numPrefix = fKeys ? "F" : "";
|
||||||
const n = fKeys ? 12 : 9;
|
const n = fKeys ? 12 : 9;
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
const numKey = String(i + 1);
|
const numKey = String(i + 1);
|
||||||
|
const keySequence = i < n ?
|
||||||
|
modifiers + "+" + numPrefix + numKey :
|
||||||
|
"";
|
||||||
registerKeyBinding(
|
registerKeyBinding(
|
||||||
name + numKey,
|
name + numKey,
|
||||||
description + numKey,
|
description + numKey,
|
||||||
modifiers + "+" + numPrefix + numKey,
|
keySequence,
|
||||||
() => callback(i),
|
() => callback(i),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerKeyBindings(world: World) {
|
function registerKeyBindings(world: World, config: Config) {
|
||||||
const actions = initActions(world);
|
const actions = Actions.init(world, config);
|
||||||
for (const binding of keyBindings) {
|
for (const binding of keyBindings) {
|
||||||
registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]);
|
registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const numActions = initNumActions(world);
|
const numActions = Actions.initNum(world);
|
||||||
for (const binding of numKeyBindings) {
|
for (const binding of numKeyBindings) {
|
||||||
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
|
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class Column {
|
class Column {
|
||||||
public grid: Grid;
|
public grid: Grid;
|
||||||
public gridX: number;
|
public gridX: number;
|
||||||
public width: number; // TODO: increase column width to contain transients
|
private width: number; // TODO: increase column width to contain transients
|
||||||
private readonly windows: LinkedList<Window>;
|
private readonly windows: LinkedList<Window>;
|
||||||
private stacked: boolean;
|
private stacked: boolean;
|
||||||
private focusTaker: Window|null;
|
private focusTaker: Window|null;
|
||||||
@@ -17,7 +17,7 @@ class Column {
|
|||||||
this.grid.onColumnAdded(this, prevColumn);
|
this.grid.onColumnAdded(this, prevColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToGrid(targetGrid: Grid, prevColumn: Column|null) {
|
public moveToGrid(targetGrid: Grid, prevColumn: Column|null) {
|
||||||
if (targetGrid === this.grid) {
|
if (targetGrid === this.grid) {
|
||||||
this.grid.onColumnMoved(this, prevColumn);
|
this.grid.onColumnMoved(this, prevColumn);
|
||||||
} else {
|
} else {
|
||||||
@@ -25,55 +25,57 @@ 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.container.desktop;
|
window.client.kwinClient.desktop = targetGrid.desktop.desktopNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAfter(prevColumn: Column|null) {
|
public moveAfter(prevColumn: Column|null) {
|
||||||
if (prevColumn === this) {
|
if (prevColumn === this) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.grid.onColumnMoved(this, prevColumn);
|
this.grid.onColumnMoved(this, prevColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
isAfter(other: Column) {
|
public isAfter(other: Column) {
|
||||||
return this.gridX > other.gridX;
|
return this.gridX > other.gridX;
|
||||||
}
|
}
|
||||||
|
|
||||||
isBefore(other: Column) {
|
public isBefore(other: Column) {
|
||||||
return this.gridX < other.gridX;
|
return this.gridX < other.gridX;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveWindowUp(window: Window) {
|
public moveWindowUp(window: Window) {
|
||||||
this.windows.moveBack(window);
|
this.windows.moveBack(window);
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveWindowDown(window: Window) {
|
public moveWindowDown(window: Window) {
|
||||||
this.windows.moveForward(window);
|
this.windows.moveForward(window);
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindowCount() {
|
public getWindowCount() {
|
||||||
return this.windows.length();
|
return this.windows.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
public isEmpty() {
|
||||||
return this.getWindowCount() === 0;
|
return this.getWindowCount() === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrevWindow(window: Window) {
|
public getPrevWindow(window: Window) {
|
||||||
return this.windows.getPrev(window);
|
return this.windows.getPrev(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextWindow(window: Window) {
|
public getNextWindow(window: Window) {
|
||||||
return this.windows.getNext(window);
|
return this.windows.getNext(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidth() {
|
public getWidth() {
|
||||||
return this.width;
|
return this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMinWidth() {
|
public getMinWidth() {
|
||||||
let maxMinWidth = Column.minWidth;
|
let maxMinWidth = Column.minWidth;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
const minWidth = window.client.kwinClient.minSize.width;
|
const minWidth = window.client.kwinClient.minSize.width;
|
||||||
@@ -84,11 +86,11 @@ class Column {
|
|||||||
return maxMinWidth;
|
return maxMinWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxWidth() {
|
public getMaxWidth() {
|
||||||
return this.grid.container.tilingArea.width;
|
return this.grid.desktop.tilingArea.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidth(width: number, setPreferred: boolean) {
|
public setWidth(width: number, setPreferred: boolean) {
|
||||||
width = clamp(width, this.getMinWidth(), this.getMaxWidth());
|
width = clamp(width, this.getMinWidth(), this.getMaxWidth());
|
||||||
const oldWidth = this.width;
|
const oldWidth = this.width;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
@@ -102,21 +104,34 @@ class Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustWidth(widthDelta: number, setPreferred: boolean) {
|
public adjustWidth(widthDelta: number, setPreferred: boolean) {
|
||||||
this.setWidth(this.width + widthDelta, setPreferred);
|
this.setWidth(this.width + widthDelta, setPreferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateWidth() {
|
||||||
|
let minErr = Infinity;
|
||||||
|
let closestPreferredWidth = this.width;
|
||||||
|
for (const window of this.windows.iterator()) {
|
||||||
|
const err = Math.abs(window.client.preferredWidth - this.width);
|
||||||
|
if (err < minErr) {
|
||||||
|
minErr = err;
|
||||||
|
closestPreferredWidth = window.client.preferredWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setWidth(closestPreferredWidth, false);
|
||||||
|
}
|
||||||
|
|
||||||
// returns x position of left edge in grid space
|
// returns x position of left edge in grid space
|
||||||
getLeft() {
|
public getLeft() {
|
||||||
return this.gridX;
|
return this.gridX;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns x position of right edge in grid space
|
// returns x position of right edge in grid space
|
||||||
getRight() {
|
public getRight() {
|
||||||
return this.gridX + this.width;
|
return this.gridX + this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustWindowHeight(window: Window, heightDelta: number, top: boolean) {
|
public adjustWindowHeight(window: Window, heightDelta: number, top: boolean) {
|
||||||
const otherWindow = top ? this.windows.getPrev(window) : this.windows.getNext(window);
|
const otherWindow = top ? this.windows.getPrev(window) : this.windows.getNext(window);
|
||||||
if (otherWindow === null) {
|
if (otherWindow === null) {
|
||||||
return;
|
return;
|
||||||
@@ -124,9 +139,11 @@ class Column {
|
|||||||
|
|
||||||
window.height += heightDelta;
|
window.height += heightDelta;
|
||||||
otherWindow.height -= heightDelta;
|
otherWindow.height -= heightDelta;
|
||||||
|
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeWindows() {
|
public resizeWindows() {
|
||||||
const nWindows = this.windows.length();
|
const nWindows = this.windows.length();
|
||||||
if (nWindows === 0) {
|
if (nWindows === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -135,7 +152,7 @@ class Column {
|
|||||||
this.stacked = this.grid.config.stackColumnsByDefault;
|
this.stacked = this.grid.config.stackColumnsByDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
let remainingPixels = this.grid.container.tilingArea.height - (nWindows-1) * this.grid.config.gapsInnerVertical;
|
let remainingPixels = this.grid.desktop.tilingArea.height - (nWindows-1) * this.grid.config.gapsInnerVertical;
|
||||||
let remainingWindows = nWindows;
|
let remainingWindows = nWindows;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
const windowHeight = Math.round(remainingPixels / remainingWindows);
|
const windowHeight = Math.round(remainingPixels / remainingWindows);
|
||||||
@@ -144,16 +161,18 @@ class Column {
|
|||||||
remainingWindows--;
|
remainingWindows--;
|
||||||
}
|
}
|
||||||
// TODO: respect min height
|
// TODO: respect min height
|
||||||
|
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFocusTaker() {
|
public getFocusTaker() {
|
||||||
if (this.focusTaker === null || !this.windows.contains(this.focusTaker)) {
|
if (this.focusTaker === null || !this.windows.contains(this.focusTaker)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.focusTaker;
|
return this.focusTaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
public focus() {
|
||||||
const window = this.getFocusTaker() ?? this.windows.getFirst();
|
const window = this.getFocusTaker() ?? this.windows.getFirst();
|
||||||
if (window === null) {
|
if (window === null) {
|
||||||
return;
|
return;
|
||||||
@@ -161,12 +180,12 @@ class Column {
|
|||||||
window.focus();
|
window.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
arrange(x: number) {
|
public arrange(x: number) {
|
||||||
if (this.stacked && this.windows.length() >= 2) {
|
if (this.stacked && this.windows.length() >= 2) {
|
||||||
this.arrangeStacked(x);
|
this.arrangeStacked(x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let y = this.grid.container.tilingArea.y;
|
let y = this.grid.desktop.tilingArea.y;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
window.client.setShade(false);
|
window.client.setShade(false);
|
||||||
window.arrange(x, y, this.width, window.height);
|
window.arrange(x, y, this.width, window.height);
|
||||||
@@ -174,7 +193,7 @@ class Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arrangeStacked(x: number) {
|
public arrangeStacked(x: number) {
|
||||||
const expandedWindow = this.getFocusTaker();
|
const expandedWindow = this.getFocusTaker();
|
||||||
let collapsedHeight;
|
let collapsedHeight;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
@@ -187,28 +206,29 @@ class Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nCollapsed = this.getWindowCount() - 1;
|
const nCollapsed = this.getWindowCount() - 1;
|
||||||
const expandedHeight = this.grid.container.tilingArea.height - nCollapsed * (collapsedHeight + this.grid.config.gapsInnerVertical);
|
const expandedHeight = this.grid.desktop.tilingArea.height - nCollapsed * (collapsedHeight! + this.grid.config.gapsInnerVertical);
|
||||||
let y = this.grid.container.tilingArea.y;
|
let y = this.grid.desktop.tilingArea.y;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
if (window === expandedWindow) {
|
if (window === expandedWindow) {
|
||||||
window.arrange(x, y, this.width, expandedHeight);
|
window.arrange(x, y, this.width, expandedHeight);
|
||||||
y += expandedHeight;
|
y += expandedHeight;
|
||||||
} else {
|
} else {
|
||||||
window.arrange(x, y, this.width, window.height);
|
window.arrange(x, y, this.width, window.height);
|
||||||
y += collapsedHeight;
|
y += collapsedHeight!;
|
||||||
}
|
}
|
||||||
y += this.grid.config.gapsInnerVertical;
|
y += this.grid.config.gapsInnerVertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleStacked() {
|
public toggleStacked() {
|
||||||
if (this.windows.length() < 2) {
|
if (this.windows.length() < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.stacked = !this.stacked;
|
this.stacked = !this.stacked;
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVisible(scrollPos: ScrollPos, fullyVisible: boolean) {
|
public isVisible(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) {
|
||||||
if (fullyVisible) {
|
if (fullyVisible) {
|
||||||
return this.getLeft() >= scrollPos.getLeft() &&
|
return this.getLeft() >= scrollPos.getLeft() &&
|
||||||
this.getRight() <= scrollPos.getRight();
|
this.getRight() <= scrollPos.getRight();
|
||||||
@@ -218,7 +238,7 @@ class Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowAdded(window: Window) {
|
public onWindowAdded(window: Window) {
|
||||||
this.windows.insertEnd(window);
|
this.windows.insertEnd(window);
|
||||||
if (this.width === 0) {
|
if (this.width === 0) {
|
||||||
this.setWidth(window.client.preferredWidth, false);
|
this.setWidth(window.client.preferredWidth, false);
|
||||||
@@ -230,9 +250,11 @@ class Column {
|
|||||||
if (window.isFocused()) {
|
if (window.isFocused()) {
|
||||||
this.onWindowFocused(window);
|
this.onWindowFocused(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowRemoved(window: Window, passFocus: boolean) {
|
public onWindowRemoved(window: Window, passFocus: boolean) {
|
||||||
const lastWindow = this.windows.length() === 1;
|
const lastWindow = this.windows.length() === 1;
|
||||||
const windowToFocus = this.getPrevWindow(window) ?? this.getNextWindow(window);
|
const windowToFocus = this.getPrevWindow(window) ?? this.getNextWindow(window);
|
||||||
|
|
||||||
@@ -251,21 +273,23 @@ class Column {
|
|||||||
windowToFocus.focus();
|
windowToFocus.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowFocused(window: Window) {
|
public onWindowFocused(window: Window) {
|
||||||
this.grid.onColumnFocused(this);
|
this.grid.onColumnFocused(this);
|
||||||
this.focusTaker = window;
|
this.focusTaker = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreToTiled() {
|
public restoreToTiled() {
|
||||||
const lastFocusedWindow = this.getFocusTaker();
|
const lastFocusedWindow = this.getFocusTaker();
|
||||||
if (lastFocusedWindow !== null) {
|
if (lastFocusedWindow !== null) {
|
||||||
lastFocusedWindow.restoreToTiled();
|
lastFocusedWindow.restoreToTiled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(passFocus: boolean) {
|
private destroy(passFocus: boolean) {
|
||||||
this.grid.onColumnRemoved(this, passFocus);
|
this.grid.onColumnRemoved(this, passFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,48 @@
|
|||||||
class ScrollView {
|
class Desktop {
|
||||||
public readonly world: World;
|
|
||||||
public readonly grid: Grid;
|
public readonly grid: Grid;
|
||||||
public readonly desktop: number;
|
public readonly desktopNumber: number;
|
||||||
private readonly config: ScrollView.Config;
|
private readonly config: Desktop.Config;
|
||||||
private scrollX: number;
|
private scrollX: number;
|
||||||
|
private dirty: boolean;
|
||||||
public clientArea: QRect;
|
public clientArea: QRect;
|
||||||
public tilingArea: QRect;
|
public tilingArea: QRect;
|
||||||
|
|
||||||
constructor(world: World, desktop: number, config: ScrollView.Config, layoutConfig: LayoutConfig) {
|
constructor(desktopNumber: number, config: Desktop.Config, layoutConfig: LayoutConfig) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.world = world;
|
|
||||||
this.scrollX = 0;
|
this.scrollX = 0;
|
||||||
this.desktop = desktop;
|
this.dirty = false;
|
||||||
|
this.desktopNumber = desktopNumber;
|
||||||
this.grid = new Grid(this, layoutConfig);
|
this.grid = new Grid(this, layoutConfig);
|
||||||
this.updateArea();
|
this.clientArea = Desktop.getClientArea(desktopNumber);
|
||||||
|
this.tilingArea = Desktop.getTilingArea(this.clientArea, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateArea() {
|
private updateArea() {
|
||||||
const newClientArea = workspace.clientArea(workspace.PlacementArea, 0, this.desktop);
|
const newClientArea = Desktop.getClientArea(this.desktopNumber);
|
||||||
if (newClientArea === this.clientArea) {
|
if (newClientArea === this.clientArea) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clientArea = newClientArea;
|
this.clientArea = newClientArea;
|
||||||
this.tilingArea = Qt.rect(
|
this.tilingArea = Desktop.getTilingArea(newClientArea, this.config);
|
||||||
newClientArea.x + this.config.marginLeft,
|
this.dirty = true;
|
||||||
newClientArea.y + this.config.marginTop,
|
|
||||||
newClientArea.width - this.config.marginLeft - this.config.marginRight,
|
|
||||||
newClientArea.height - this.config.marginTop - this.config.marginBottom,
|
|
||||||
)
|
|
||||||
this.grid.onScreenSizeChanged();
|
this.grid.onScreenSizeChanged();
|
||||||
|
|
||||||
this.autoAdjustScroll();
|
this.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculates ScrollPos that scrolls the column into view
|
private static getClientArea(desktopNumber: number) {
|
||||||
|
return workspace.clientArea(ClientAreaOption.PlacementArea, 0, desktopNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getTilingArea(clientArea: QRect, config: Desktop.Config) {
|
||||||
|
return Qt.rect(
|
||||||
|
clientArea.x + config.marginLeft,
|
||||||
|
clientArea.y + config.marginTop,
|
||||||
|
clientArea.width - config.marginLeft - config.marginRight,
|
||||||
|
clientArea.height - config.marginTop - config.marginBottom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculates Desktop.Pos that scrolls the column into view
|
||||||
public getScrollPosForColumn(column: Column) {
|
public getScrollPosForColumn(column: Column) {
|
||||||
const left = column.getLeft();
|
const left = column.getLeft();
|
||||||
const right = column.getRight();
|
const right = column.getRight();
|
||||||
@@ -42,9 +50,9 @@ class ScrollView {
|
|||||||
|
|
||||||
let targetScrollX: number;
|
let targetScrollX: number;
|
||||||
if (left < initialScrollPos.getLeft()) {
|
if (left < initialScrollPos.getLeft()) {
|
||||||
targetScrollX = this.clampScrollX(left);
|
targetScrollX = left;
|
||||||
} else if (right > initialScrollPos.getRight()) {
|
} else if (right > initialScrollPos.getRight()) {
|
||||||
targetScrollX = this.clampScrollX(right - this.tilingArea.width);
|
targetScrollX = right - this.tilingArea.width;
|
||||||
} else {
|
} else {
|
||||||
return this.getScrollPos(this.clampScrollX(this.scrollX));
|
return this.getScrollPos(this.clampScrollX(this.scrollX));
|
||||||
}
|
}
|
||||||
@@ -64,32 +72,32 @@ class ScrollView {
|
|||||||
return overscrollX * direction;
|
return overscrollX * direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToColumn(column: Column) {
|
public scrollToColumn(column: Column) {
|
||||||
this.scrollX = this.getScrollPosForColumn(column).x;
|
this.setScroll(this.getScrollPosForColumn(column).x, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollCenterColumn(column: Column) {
|
public scrollCenterColumn(column: Column) {
|
||||||
const windowCenter = column.getLeft() + column.width / 2;
|
const windowCenter = column.getLeft() + column.getWidth() / 2;
|
||||||
const screenCenter = this.scrollX + this.tilingArea.width / 2;
|
const screenCenter = this.scrollX + this.tilingArea.width / 2;
|
||||||
this.adjustScroll(Math.round(windowCenter - screenCenter), false);
|
this.adjustScroll(Math.round(windowCenter - screenCenter), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private autoAdjustScroll() {
|
public autoAdjustScroll() {
|
||||||
const focusedWindow = this.world.getFocusedWindow();
|
const focusedColumn = this.grid.getLastFocusedColumn();
|
||||||
if (focusedWindow === null) {
|
if (focusedColumn === null) {
|
||||||
this.removeOverscroll();
|
this.removeOverscroll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const column = focusedWindow.column;
|
if (focusedColumn.grid !== this.grid) {
|
||||||
if (column.grid !== this.grid) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scrollToColumn(column);
|
|
||||||
|
this.scrollToColumn(focusedColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getScrollPos(scrollX: number) {
|
private getScrollPos(scrollX: number) {
|
||||||
return new ScrollPos(scrollX, this.tilingArea.width);
|
return new Desktop.ScrollPos(scrollX, this.tilingArea.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentScrollPos() {
|
public getCurrentScrollPos() {
|
||||||
@@ -108,14 +116,18 @@ class ScrollView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setScroll(x: number, force: boolean) {
|
private setScroll(x: number, force: boolean) {
|
||||||
|
const oldScrollX = this.scrollX;
|
||||||
this.scrollX = force ? x : this.clampScrollX(x);
|
this.scrollX = force ? x : this.clampScrollX(x);
|
||||||
|
if (this.scrollX !== oldScrollX) {
|
||||||
|
this.onLayoutChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyScrollPos(scrollPos: ScrollPos) {
|
private applyScrollPos(scrollPos: Desktop.ScrollPos) {
|
||||||
this.scrollX = scrollPos.x;
|
this.setScroll(scrollPos.x, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustScroll(dx: number, force: boolean) {
|
public adjustScroll(dx: number, force: boolean) {
|
||||||
this.setScroll(this.scrollX + dx, force);
|
this.setScroll(this.scrollX + dx, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,15 +138,15 @@ class ScrollView {
|
|||||||
public arrange() {
|
public arrange() {
|
||||||
// TODO (optimization): only arrange visible windows
|
// TODO (optimization): only arrange visible windows
|
||||||
this.updateArea();
|
this.updateArea();
|
||||||
|
if (!this.dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.grid.arrange(this.tilingArea.x - this.scrollX);
|
this.grid.arrange(this.tilingArea.x - this.scrollX);
|
||||||
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGridWidthChanged() {
|
public onLayoutChanged() {
|
||||||
this.autoAdjustScroll();
|
this.dirty = true;
|
||||||
}
|
|
||||||
|
|
||||||
public onGridReordered() {
|
|
||||||
this.autoAdjustScroll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
@@ -142,12 +154,30 @@ class ScrollView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module ScrollView {
|
namespace Desktop {
|
||||||
export type Config = {
|
export type Config = {
|
||||||
marginTop: number,
|
marginTop: number,
|
||||||
marginBottom: number,
|
marginBottom: number,
|
||||||
marginLeft: number,
|
marginLeft: number,
|
||||||
marginRight: number,
|
marginRight: number,
|
||||||
overscroll: number,
|
overscroll: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ScrollPos {
|
||||||
|
public readonly x: number;
|
||||||
|
public readonly width: number;
|
||||||
|
|
||||||
|
constructor(x: number, width: number) {
|
||||||
|
this.x = x;
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLeft() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRight() {
|
||||||
|
return this.x + this.width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
class Grid {
|
class Grid {
|
||||||
public readonly container: ScrollView;
|
public readonly desktop: Desktop;
|
||||||
public readonly config: LayoutConfig;
|
public readonly config: LayoutConfig;
|
||||||
private readonly columns: LinkedList<Column>;
|
private readonly columns: LinkedList<Column>;
|
||||||
private lastFocusedColumn: Column|null;
|
private lastFocusedColumn: Column|null;
|
||||||
@@ -7,8 +7,8 @@ class Grid {
|
|||||||
private userResize: boolean; // is any part of the grid being resized by the user
|
private userResize: boolean; // is any part of the grid being resized by the user
|
||||||
private readonly userResizeFinishedDelayer: Delayer;
|
private readonly userResizeFinishedDelayer: Delayer;
|
||||||
|
|
||||||
constructor(container: ScrollView, config: LayoutConfig) {
|
constructor(desktop: Desktop, config: LayoutConfig) {
|
||||||
this.container = container;
|
this.desktop = desktop;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.columns = new LinkedList();
|
this.columns = new LinkedList();
|
||||||
this.lastFocusedColumn = null;
|
this.lastFocusedColumn = null;
|
||||||
@@ -16,18 +16,20 @@ class Grid {
|
|||||||
this.userResize = false;
|
this.userResize = false;
|
||||||
this.userResizeFinishedDelayer = new Delayer(50, () => {
|
this.userResizeFinishedDelayer = new Delayer(50, () => {
|
||||||
// this delay prevents windows' contents from freezing after resizing
|
// this delay prevents windows' contents from freezing after resizing
|
||||||
this.container.onGridWidthChanged();
|
this.desktop.onLayoutChanged();
|
||||||
this.container.arrange();
|
this.desktop.autoAdjustScroll();
|
||||||
|
this.desktop.arrange();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
moveColumnLeft(column: Column) {
|
public moveColumnLeft(column: Column) {
|
||||||
this.columns.moveBack(column);
|
this.columns.moveBack(column);
|
||||||
this.columnsSetX(column);
|
this.columnsSetX(column);
|
||||||
this.container.onGridWidthChanged();
|
this.desktop.onLayoutChanged();
|
||||||
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveColumnRight(column: Column) {
|
public moveColumnRight(column: Column) {
|
||||||
const nextColumn = this.columns.getNext(column);
|
const nextColumn = this.columns.getNext(column);
|
||||||
if (nextColumn === null) {
|
if (nextColumn === null) {
|
||||||
return;
|
return;
|
||||||
@@ -35,50 +37,58 @@ class Grid {
|
|||||||
this.moveColumnLeft(nextColumn);
|
this.moveColumnLeft(nextColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidth() {
|
public getWidth() {
|
||||||
return this.width;
|
return this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrevColumn(column: Column) {
|
public getPrevColumn(column: Column) {
|
||||||
return this.columns.getPrev(column);
|
return this.columns.getPrev(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextColumn(column: Column) {
|
public getNextColumn(column: Column) {
|
||||||
return this.columns.getNext(column);
|
return this.columns.getNext(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstColumn() {
|
public getFirstColumn() {
|
||||||
return this.columns.getFirst();
|
return this.columns.getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastColumn() {
|
public getLastColumn() {
|
||||||
return this.columns.getLast();
|
return this.columns.getLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumnAtIndex(i: number) {
|
public getColumnAtIndex(i: number) {
|
||||||
return this.columns.getItemAtIndex(i);
|
return this.columns.getItemAtIndex(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastFocusedColumn() {
|
public getLastFocusedColumn() {
|
||||||
if (this.lastFocusedColumn === null || this.lastFocusedColumn.grid !== this) {
|
if (this.lastFocusedColumn === null || this.lastFocusedColumn.grid !== this) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.lastFocusedColumn;
|
return this.lastFocusedColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getLastFocusedWindow() {
|
||||||
|
const lastFocusedColumn = this.getLastFocusedColumn();
|
||||||
|
if (lastFocusedColumn === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastFocusedColumn.getFocusTaker();
|
||||||
|
}
|
||||||
|
|
||||||
private columnsSetX(firstMovedColumn: Column|null) {
|
private columnsSetX(firstMovedColumn: Column|null) {
|
||||||
const lastUnmovedColumn = firstMovedColumn === null ? this.columns.getLast() : this.columns.getPrev(firstMovedColumn);
|
const lastUnmovedColumn = firstMovedColumn === null ? this.columns.getLast() : this.columns.getPrev(firstMovedColumn);
|
||||||
let x = lastUnmovedColumn === null ? 0 : lastUnmovedColumn.getRight() + this.config.gapsInnerHorizontal;
|
let x = lastUnmovedColumn === null ? 0 : lastUnmovedColumn.getRight() + this.config.gapsInnerHorizontal;
|
||||||
if (firstMovedColumn !== null) {
|
if (firstMovedColumn !== null) {
|
||||||
for (const column of this.columns.iteratorFrom(firstMovedColumn)) {
|
for (const column of this.columns.iteratorFrom(firstMovedColumn)) {
|
||||||
column.gridX = x;
|
column.gridX = x;
|
||||||
x += column.width + this.config.gapsInnerHorizontal;
|
x += column.getWidth() + this.config.gapsInnerHorizontal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.width = x - this.config.gapsInnerHorizontal;
|
this.width = x - this.config.gapsInnerHorizontal;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLeftmostVisibleColumn(scrollPos: ScrollPos, fullyVisible: boolean) {
|
public getLeftmostVisibleColumn(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) {
|
||||||
const scrollX = scrollPos.getLeft();
|
const scrollX = scrollPos.getLeft();
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
const x = fullyVisible ? column.getLeft() : column.getRight() + (this.config.gapsInnerHorizontal - 1);
|
const x = fullyVisible ? column.getLeft() : column.getRight() + (this.config.gapsInnerHorizontal - 1);
|
||||||
@@ -89,7 +99,7 @@ class Grid {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRightmostVisibleColumn(scrollPos: ScrollPos, fullyVisible: boolean) {
|
public getRightmostVisibleColumn(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) {
|
||||||
const scrollX = scrollPos.getRight();
|
const scrollX = scrollPos.getRight();
|
||||||
let last = null;
|
let last = null;
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
@@ -103,12 +113,12 @@ class Grid {
|
|||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVisibleColumnsWidth(scrollPos: ScrollPos, fullyVisible: boolean) {
|
public getVisibleColumnsWidth(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) {
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let nVisible = 0;
|
let nVisible = 0;
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
if (column.isVisible(scrollPos, fullyVisible)) {
|
if (column.isVisible(scrollPos, fullyVisible)) {
|
||||||
width += column.width;
|
width += column.getWidth();
|
||||||
nVisible++;
|
nVisible++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +130,7 @@ class Grid {
|
|||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLeftOffScreenColumn(scrollPos: ScrollPos) {
|
private getLeftOffScreenColumn(scrollPos: Desktop.ScrollPos) {
|
||||||
const leftVisible = this.getLeftmostVisibleColumn(scrollPos, true);
|
const leftVisible = this.getLeftmostVisibleColumn(scrollPos, true);
|
||||||
if (leftVisible === null) {
|
if (leftVisible === null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -128,7 +138,7 @@ class Grid {
|
|||||||
return this.getPrevColumn(leftVisible);
|
return this.getPrevColumn(leftVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRightOffScreenColumn(scrollPos: ScrollPos) {
|
private getRightOffScreenColumn(scrollPos: Desktop.ScrollPos) {
|
||||||
const rightVisible = this.getRightmostVisibleColumn(scrollPos, true);
|
const rightVisible = this.getRightmostVisibleColumn(scrollPos, true);
|
||||||
if (rightVisible === null) {
|
if (rightVisible === null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -136,11 +146,10 @@ class Grid {
|
|||||||
return this.getNextColumn(rightVisible);
|
return this.getNextColumn(rightVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
increaseColumnWidth(column: Column) {
|
public increaseColumnWidth(column: Column) {
|
||||||
const scrollPos = this.container.getScrollPosForColumn(column);
|
const scrollPos = this.desktop.getScrollPosForColumn(column);
|
||||||
if (this.width < scrollPos.width) {
|
if (this.width < scrollPos.width) {
|
||||||
column.adjustWidth(scrollPos.width - this.width, false);
|
column.adjustWidth(scrollPos.width - this.width, false);
|
||||||
this.container.arrange();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,16 +170,15 @@ class Grid {
|
|||||||
const expandLeft = leftVisibleWidth < rightVisibleWidth;
|
const expandLeft = leftVisibleWidth < rightVisibleWidth;
|
||||||
const widthDelta = (expandLeft ? leftVisibleWidth : rightVisibleWidth) + this.config.gapsInnerHorizontal;
|
const widthDelta = (expandLeft ? leftVisibleWidth : rightVisibleWidth) + this.config.gapsInnerHorizontal;
|
||||||
if (expandLeft) {
|
if (expandLeft) {
|
||||||
this.container.adjustScroll(widthDelta, false);
|
this.desktop.adjustScroll(widthDelta, false);
|
||||||
}
|
}
|
||||||
column.adjustWidth(widthDelta, true);
|
column.adjustWidth(widthDelta, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
decreaseColumnWidth(column: Column) {
|
public decreaseColumnWidth(column: Column) {
|
||||||
const scrollPos = this.container.getScrollPosForColumn(column);
|
const scrollPos = this.desktop.getScrollPosForColumn(column);
|
||||||
if (this.width <= scrollPos.width) {
|
if (this.width <= scrollPos.width) {
|
||||||
column.setWidth(Math.round(column.getWidth() / 2), false);
|
column.setWidth(Math.round(column.getWidth() / 2), false);
|
||||||
this.container.arrange();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,29 +199,36 @@ class Grid {
|
|||||||
const shrinkLeft = leftInvisibleWidth < rightInvisibleWidth;
|
const shrinkLeft = leftInvisibleWidth < rightInvisibleWidth;
|
||||||
const widthDelta = (shrinkLeft ? leftInvisibleWidth : rightInvisibleWidth);
|
const widthDelta = (shrinkLeft ? leftInvisibleWidth : rightInvisibleWidth);
|
||||||
if (shrinkLeft) {
|
if (shrinkLeft) {
|
||||||
this.container.adjustScroll(-widthDelta, false);
|
const maxDelta = column.getWidth() - column.getMinWidth();
|
||||||
|
this.desktop.adjustScroll(-Math.min(widthDelta, maxDelta), false);
|
||||||
}
|
}
|
||||||
column.adjustWidth(-widthDelta, true);
|
column.adjustWidth(-widthDelta, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
arrange(x: number) {
|
public arrange(x: number) {
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
column.arrange(x);
|
column.arrange(x);
|
||||||
x += column.getWidth() + this.config.gapsInnerHorizontal;
|
x += column.getWidth() + this.config.gapsInnerHorizontal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const focusedWindow = this.getLastFocusedWindow();
|
||||||
|
if (focusedWindow !== null) {
|
||||||
|
focusedWindow.client.ensureTransientsVisible(this.desktop.clientArea);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnAdded(column: Column, prevColumn: Column|null) {
|
public onColumnAdded(column: Column, prevColumn: Column|null) {
|
||||||
if (prevColumn === null) {
|
if (prevColumn === null) {
|
||||||
this.columns.insertStart(column);
|
this.columns.insertStart(column);
|
||||||
} else {
|
} else {
|
||||||
this.columns.insertAfter(column, prevColumn);
|
this.columns.insertAfter(column, prevColumn);
|
||||||
}
|
}
|
||||||
this.columnsSetX(column);
|
this.columnsSetX(column);
|
||||||
this.container.onGridWidthChanged();
|
this.desktop.onLayoutChanged();
|
||||||
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnRemoved(column: Column, passFocus: boolean) {
|
public onColumnRemoved(column: Column, passFocus: boolean) {
|
||||||
const isLastColumn = this.columns.length() === 1;
|
const isLastColumn = this.columns.length() === 1;
|
||||||
const nextColumn = this.getNextColumn(column);
|
const nextColumn = this.getNextColumn(column);
|
||||||
const columnToFocus = isLastColumn ? null : this.getPrevColumn(column) ?? nextColumn;
|
const columnToFocus = isLastColumn ? null : this.getPrevColumn(column) ?? nextColumn;
|
||||||
@@ -227,63 +242,67 @@ class Grid {
|
|||||||
if (passFocus && columnToFocus !== null) {
|
if (passFocus && columnToFocus !== null) {
|
||||||
columnToFocus.focus();
|
columnToFocus.focus();
|
||||||
} else {
|
} else {
|
||||||
this.container.onGridWidthChanged();
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
this.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnMoved(column: Column, prevColumn: Column|null) {
|
public onColumnMoved(column: Column, prevColumn: Column|null) {
|
||||||
const movedLeft = prevColumn === null ? true : column.isAfter(prevColumn);
|
const movedLeft = prevColumn === null ? true : column.isAfter(prevColumn);
|
||||||
const firstMovedColumn = movedLeft ? column : this.getNextColumn(column);
|
const firstMovedColumn = movedLeft ? column : this.getNextColumn(column);
|
||||||
this.columns.move(column, prevColumn);
|
this.columns.move(column, prevColumn);
|
||||||
this.columnsSetX(firstMovedColumn);
|
this.columnsSetX(firstMovedColumn);
|
||||||
this.container.onGridReordered();
|
this.desktop.onLayoutChanged();
|
||||||
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnWidthChanged(column: Column, oldWidth: number, width: number) {
|
public onColumnWidthChanged(column: Column, oldWidth: number, width: number) {
|
||||||
const nextColumn = this.columns.getNext(column);
|
const nextColumn = this.columns.getNext(column);
|
||||||
this.columnsSetX(nextColumn);
|
this.columnsSetX(nextColumn);
|
||||||
if (!this.userResize) {
|
if (!this.userResize) {
|
||||||
this.container.onGridWidthChanged();
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
this.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnFocused(column: Column) {
|
public onColumnFocused(column: Column) {
|
||||||
const lastFocusedColumn = this.getLastFocusedColumn();
|
const lastFocusedColumn = this.getLastFocusedColumn();
|
||||||
if (lastFocusedColumn !== null) {
|
if (lastFocusedColumn !== null) {
|
||||||
lastFocusedColumn.restoreToTiled();
|
lastFocusedColumn.restoreToTiled();
|
||||||
}
|
}
|
||||||
this.lastFocusedColumn = column;
|
this.lastFocusedColumn = column;
|
||||||
this.container.scrollToColumn(column);
|
this.desktop.scrollToColumn(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenSizeChanged() {
|
public onScreenSizeChanged() {
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
|
column.updateWidth();
|
||||||
column.resizeWindows();
|
column.resizeWindows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserResizeStarted() {
|
public onUserResizeStarted() {
|
||||||
this.userResize = true;
|
this.userResize = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserResizeFinished() {
|
public onUserResizeFinished() {
|
||||||
this.userResize = false;
|
this.userResize = false;
|
||||||
this.userResizeFinishedDelayer.run();
|
this.userResizeFinishedDelayer.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
evacuateTail(targetGrid: Grid, startColumn: Column) {
|
public evacuateTail(targetGrid: Grid, startColumn: Column) {
|
||||||
for (const column of this.columns.iteratorFrom(startColumn)) {
|
for (const column of this.columns.iteratorFrom(startColumn)) {
|
||||||
column.moveToGrid(targetGrid, targetGrid.getLastColumn());
|
column.moveToGrid(targetGrid, targetGrid.getLastColumn());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
evacuate(targetGrid: Grid) {
|
public evacuate(targetGrid: Grid) {
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
column.moveToGrid(targetGrid, targetGrid.getLastColumn());
|
column.moveToGrid(targetGrid, targetGrid.getLastColumn());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
public destroy() {
|
||||||
this.userResizeFinishedDelayer.destroy();
|
this.userResizeFinishedDelayer.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ type LayoutConfig = {
|
|||||||
gapsInnerVertical: number,
|
gapsInnerVertical: number,
|
||||||
stackColumnsByDefault: boolean,
|
stackColumnsByDefault: boolean,
|
||||||
resizeNeighborColumn: boolean,
|
resizeNeighborColumn: boolean,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
class ScrollPos {
|
|
||||||
public readonly x: number;
|
|
||||||
public readonly width: number;
|
|
||||||
|
|
||||||
constructor(x: number, width: number) {
|
|
||||||
this.x = x;
|
|
||||||
this.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getLeft() {
|
|
||||||
return this.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRight() {
|
|
||||||
return this.x + this.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,23 +2,17 @@ class Window {
|
|||||||
public column: Column;
|
public column: Column;
|
||||||
public readonly client: ClientWrapper;
|
public readonly client: ClientWrapper;
|
||||||
public height: number;
|
public height: number;
|
||||||
public readonly focusedState: WindowState;
|
|
||||||
private skipArrange: boolean;
|
private skipArrange: boolean;
|
||||||
|
|
||||||
constructor(client: ClientWrapper, column: Column) {
|
constructor(client: ClientWrapper, column: Column) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.height = client.kwinClient.frameGeometry.height;
|
this.height = client.kwinClient.frameGeometry.height;
|
||||||
this.focusedState = {
|
|
||||||
fullScreen: false,
|
|
||||||
maximizedHorizontally: false,
|
|
||||||
maximizedVertically: false,
|
|
||||||
};
|
|
||||||
this.skipArrange = false;
|
this.skipArrange = false;
|
||||||
this.column = column;
|
this.column = column;
|
||||||
column.onWindowAdded(this);
|
column.onWindowAdded(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToColumn(targetColumn: Column) {
|
public moveToColumn(targetColumn: Column) {
|
||||||
if (targetColumn === this.column) {
|
if (targetColumn === this.column) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -27,21 +21,15 @@ class Window {
|
|||||||
targetColumn.onWindowAdded(this);
|
targetColumn.onWindowAdded(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
arrange(x: number, y: number, width: number, height: number) {
|
public arrange(x: number, y: number, width: number, height: number) {
|
||||||
if (this.skipArrange) {
|
if (this.skipArrange) {
|
||||||
// window is being manually resized, prevent fighting with the user
|
// window is maximized, fullscreen, or being manually resized, prevent fighting with the user
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.client.place(x, y, width, height);
|
this.client.place(x, y, width, height);
|
||||||
if (this.isFocused()) {
|
|
||||||
// do this here rather than in `onFocused` to ensure it happens after placement
|
|
||||||
// (otherwise placement may not happen at all)
|
|
||||||
this.client.setMaximize(this.focusedState.maximizedVertically, this.focusedState.maximizedHorizontally);
|
|
||||||
this.client.setFullScreen(this.focusedState.fullScreen);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
public focus() {
|
||||||
if (this.client.isShaded()) {
|
if (this.client.isShaded()) {
|
||||||
// workaround for KWin deactivating clients when unshading immediately after activation
|
// workaround for KWin deactivating clients when unshading immediately after activation
|
||||||
this.client.setShade(false);
|
this.client.setShade(false);
|
||||||
@@ -49,41 +37,37 @@ class Window {
|
|||||||
this.client.focus();
|
this.client.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
isFocused() {
|
public isFocused() {
|
||||||
return this.client.isFocused();
|
return this.client.isFocused();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocused() {
|
public onFocused() {
|
||||||
this.column.onWindowFocused(this);
|
this.column.onWindowFocused(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreToTiled() {
|
public restoreToTiled() {
|
||||||
if (this.isFocused()) {
|
if (this.isFocused()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.client.setMaximize(false, false);
|
|
||||||
this.client.setFullScreen(false);
|
this.client.setFullScreen(false);
|
||||||
|
this.client.setMaximize(false, false);
|
||||||
|
this.column.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMaximizedChanged(horizontally: boolean, vertically: boolean) {
|
public onMaximizedChanged(horizontally: boolean, vertically: boolean) {
|
||||||
const maximized = horizontally || vertically;
|
const maximized = horizontally || vertically;
|
||||||
this.skipArrange = maximized;
|
this.skipArrange = maximized;
|
||||||
this.client.kwinClient.keepBelow = !maximized;
|
this.client.kwinClient.keepBelow = !maximized;
|
||||||
if (this.isFocused()) {
|
|
||||||
this.focusedState.maximizedHorizontally = horizontally;
|
|
||||||
this.focusedState.maximizedVertically = vertically;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFullScreenChanged(fullScreen: boolean) {
|
public onFullScreenChanged(fullScreen: boolean) {
|
||||||
this.skipArrange = fullScreen;
|
this.skipArrange = fullScreen;
|
||||||
if (this.isFocused()) {
|
if (this.isFocused()) {
|
||||||
this.client.kwinClient.keepBelow = !fullScreen;
|
this.client.kwinClient.keepBelow = !fullScreen;
|
||||||
this.focusedState.fullScreen = fullScreen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserResize(oldGeometry: QRect, resizeNeighborColumn: boolean) {
|
public onUserResize(oldGeometry: QRect, resizeNeighborColumn: boolean) {
|
||||||
const newGeometry = this.client.kwinClient.frameGeometry;
|
const newGeometry = this.client.kwinClient.frameGeometry;
|
||||||
const widthDelta = newGeometry.width - oldGeometry.width;
|
const widthDelta = newGeometry.width - oldGeometry.width;
|
||||||
const heightDelta = newGeometry.height - oldGeometry.height;
|
const heightDelta = newGeometry.height - oldGeometry.height;
|
||||||
@@ -94,32 +78,27 @@ class Window {
|
|||||||
if (resizeNeighborColumn && this.column.grid.config.resizeNeighborColumn) {
|
if (resizeNeighborColumn && this.column.grid.config.resizeNeighborColumn) {
|
||||||
const neighborColumn = resizingLeftSide ? this.column.grid.getPrevColumn(this.column) : this.column.grid.getNextColumn(this.column);
|
const neighborColumn = resizingLeftSide ? this.column.grid.getPrevColumn(this.column) : this.column.grid.getNextColumn(this.column);
|
||||||
if (neighborColumn !== null) {
|
if (neighborColumn !== null) {
|
||||||
const oldNeighborWidth = neighborColumn.width;
|
const oldNeighborWidth = neighborColumn.getWidth();
|
||||||
neighborColumn.adjustWidth(-widthDelta, true);
|
neighborColumn.adjustWidth(-widthDelta, true);
|
||||||
if (resizingLeftSide) {
|
if (resizingLeftSide) {
|
||||||
leftEdgeDelta -= neighborColumn.width - oldNeighborWidth;
|
leftEdgeDelta -= neighborColumn.getWidth() - oldNeighborWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.column.grid.container.adjustScroll(-leftEdgeDelta, true);
|
this.column.grid.desktop.adjustScroll(-leftEdgeDelta, true);
|
||||||
}
|
}
|
||||||
if (heightDelta !== 0) {
|
if (heightDelta !== 0) {
|
||||||
this.column.adjustWindowHeight(this, heightDelta, newGeometry.y !== oldGeometry.y);
|
this.column.adjustWindowHeight(this, heightDelta, newGeometry.y !== oldGeometry.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgrammaticResize(oldGeometry: QRect) {
|
public onFrameGeometryChanged() {
|
||||||
const newGeometry = this.client.kwinClient.frameGeometry;
|
const newGeometry = this.client.kwinClient.frameGeometry;
|
||||||
this.column.setWidth(newGeometry.width, true);
|
this.column.setWidth(newGeometry.width, true);
|
||||||
|
this.column.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(passFocus: boolean) {
|
public destroy(passFocus: boolean) {
|
||||||
this.column.onWindowRemoved(this, passFocus);
|
this.column.onWindowRemoved(this, passFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindowState = {
|
|
||||||
fullScreen: boolean,
|
|
||||||
maximizedHorizontally: boolean,
|
|
||||||
maximizedVertically: boolean,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
function init() {
|
function init() {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const world = new World(config);
|
const world = new World(config);
|
||||||
registerKeyBindings(world);
|
registerKeyBindings(world, config);
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ class ClientMatcher {
|
|||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
matches(kwinClient: AbstractClient) {
|
public matches(kwinClient: TopLevel) {
|
||||||
const rule = this.rules.get(String(kwinClient.resourceClass));
|
const rule = this.rules.get(kwinClient.resourceClass);
|
||||||
if (rule === undefined) {
|
if (rule === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ type WindowRule = {
|
|||||||
class: string,
|
class: string,
|
||||||
caption: string,
|
caption: string,
|
||||||
tile: boolean,
|
tile: boolean,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -3,21 +3,21 @@ class WindowRuleEnforcer {
|
|||||||
private readonly preferTiling: ClientMatcher;
|
private readonly preferTiling: ClientMatcher;
|
||||||
private readonly followCaption: Set<string>;
|
private readonly followCaption: Set<string>;
|
||||||
|
|
||||||
constructor(world: World, windowRules: WindowRule[]) {
|
constructor(windowRules: WindowRule[]) {
|
||||||
const [mapFloat, mapTile] = createWindowRuleMaps(windowRules);
|
const [mapFloat, mapTile] = createWindowRuleMaps(windowRules);
|
||||||
this.preferFloating = new ClientMatcher(mapFloat);
|
this.preferFloating = new ClientMatcher(mapFloat);
|
||||||
this.preferTiling = new ClientMatcher(mapTile);
|
this.preferTiling = new ClientMatcher(mapTile);
|
||||||
this.followCaption = new Set([...mapFloat.keys(), ...mapTile.keys()]);
|
this.followCaption = new Set([...mapFloat.keys(), ...mapTile.keys()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldTile(kwinClient: AbstractClient) {
|
public shouldTile(kwinClient: TopLevel) {
|
||||||
return canTileNow(kwinClient) && (
|
return Clients.canTileNow(kwinClient) && (
|
||||||
this.preferTiling.matches(kwinClient) ||
|
this.preferTiling.matches(kwinClient) ||
|
||||||
kwinClient.normalWindow && kwinClient.managed && !this.preferFloating.matches(kwinClient)
|
kwinClient.normalWindow && kwinClient.managed && !this.preferFloating.matches(kwinClient)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
initClientSignalManager(world: World, kwinClient: AbstractClient) {
|
public initClientSignalManager(world: World, kwinClient: TopLevel) {
|
||||||
if (!this.followCaption.has(kwinClient.resourceClass)) {
|
if (!this.followCaption.has(kwinClient.resourceClass)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -26,11 +26,13 @@ class WindowRuleEnforcer {
|
|||||||
const manager = new SignalManager();
|
const manager = new SignalManager();
|
||||||
manager.connect(kwinClient.captionChanged, () => {
|
manager.connect(kwinClient.captionChanged, () => {
|
||||||
const shouldTile = enforcer.shouldTile(kwinClient);
|
const shouldTile = enforcer.shouldTile(kwinClient);
|
||||||
if (shouldTile) {
|
world.do((clientManager, desktopManager) => {
|
||||||
world.tileClient(kwinClient);
|
if (shouldTile) {
|
||||||
} else {
|
clientManager.tileClient(kwinClient);
|
||||||
world.untileClient(kwinClient);
|
} else {
|
||||||
}
|
clientManager.untileClient(kwinClient);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ class Delayer {
|
|||||||
this.timer.triggered.connect(f);
|
this.timer.triggered.connect(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
public run() {
|
||||||
this.timer.restart();
|
this.timer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
public destroy() {
|
||||||
this.timer.destroy();
|
this.timer.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ class Doer {
|
|||||||
this.nCalls = 0;
|
this.nCalls = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
do (f: () => void) {
|
public do (f: () => void) {
|
||||||
this.nCalls++;
|
this.nCalls++;
|
||||||
f();
|
f();
|
||||||
this.nCalls--;
|
this.nCalls--;
|
||||||
}
|
}
|
||||||
|
|
||||||
isDoing() {
|
public isDoing() {
|
||||||
return this.nCalls > 0;
|
return this.nCalls > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class LinkedList<T> {
|
class LinkedList<T> {
|
||||||
private firstNode: LinkedListNode<T>|null;
|
private firstNode: LinkedList.Node<T>|null;
|
||||||
private lastNode: LinkedListNode<T>|null;
|
private lastNode: LinkedList.Node<T>|null;
|
||||||
private readonly itemMap: Map<T, LinkedListNode<T>>;
|
private readonly itemMap: Map<T, LinkedList.Node<T>>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.firstNode = null;
|
this.firstNode = null;
|
||||||
@@ -17,31 +17,31 @@ class LinkedList<T> {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertBefore(item: T, nextItem: T) {
|
public insertBefore(item: T, nextItem: T) {
|
||||||
const nextNode = this.getNode(nextItem);
|
const nextNode = this.getNode(nextItem);
|
||||||
this.insert(item, nextNode.prev, nextNode);
|
this.insert(item, nextNode.prev, nextNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertAfter(item: T, prevItem: T) {
|
public insertAfter(item: T, prevItem: T) {
|
||||||
const prevNode = this.getNode(prevItem);
|
const prevNode = this.getNode(prevItem);
|
||||||
this.insert(item, prevNode, prevNode.next);
|
this.insert(item, prevNode, prevNode.next);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertStart(item: T) {
|
public insertStart(item: T) {
|
||||||
this.insert(item, null, this.firstNode);
|
this.insert(item, null, this.firstNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertEnd(item: T) {
|
public insertEnd(item: T) {
|
||||||
this.insert(item, this.lastNode, null);
|
this.insert(item, this.lastNode, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private insert(item: T, prevNode: LinkedListNode<T>|null, nextNode: LinkedListNode<T>|null) {
|
private insert(item: T, prevNode: LinkedList.Node<T>|null, nextNode: LinkedList.Node<T>|null) {
|
||||||
const node = new LinkedListNode(item);
|
const node = new LinkedList.Node(item);
|
||||||
this.itemMap.set(item, node);
|
this.itemMap.set(item, node);
|
||||||
this.insertNode(node, prevNode, nextNode);
|
this.insertNode(node, prevNode, nextNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private insertNode(node: LinkedListNode<T>, prevNode: LinkedListNode<T>|null, nextNode: LinkedListNode<T>|null) {
|
private insertNode(node: LinkedList.Node<T>, prevNode: LinkedList.Node<T>|null, nextNode: LinkedList.Node<T>|null) {
|
||||||
node.prev = prevNode;
|
node.prev = prevNode;
|
||||||
node.next = nextNode;
|
node.next = nextNode;
|
||||||
if (nextNode !== null) {
|
if (nextNode !== null) {
|
||||||
@@ -60,31 +60,31 @@ class LinkedList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrev(item: T) {
|
public getPrev(item: T) {
|
||||||
const prevNode = this.getNode(item).prev;
|
const prevNode = this.getNode(item).prev;
|
||||||
return prevNode === null ? null : prevNode.item;
|
return prevNode === null ? null : prevNode.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNext(item: T) {
|
public getNext(item: T) {
|
||||||
const nextNode = this.getNode(item).next;
|
const nextNode = this.getNode(item).next;
|
||||||
return nextNode === null ? null : nextNode.item;
|
return nextNode === null ? null : nextNode.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirst() {
|
public getFirst() {
|
||||||
if (this.firstNode === null) {
|
if (this.firstNode === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.firstNode.item;
|
return this.firstNode.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLast() {
|
public getLast() {
|
||||||
if (this.lastNode === null) {
|
if (this.lastNode === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.lastNode.item;
|
return this.lastNode.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemAtIndex(index: number) {
|
public getItemAtIndex(index: number) {
|
||||||
let node = this.firstNode;
|
let node = this.firstNode;
|
||||||
if (node === null) {
|
if (node === null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -98,13 +98,13 @@ class LinkedList<T> {
|
|||||||
return node.item;
|
return node.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(item: T) {
|
public remove(item: T) {
|
||||||
const node = this.getNode(item);
|
const node = this.getNode(item);
|
||||||
this.itemMap.delete(item);
|
this.itemMap.delete(item);
|
||||||
this.removeNode(node);
|
this.removeNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeNode(node: LinkedListNode<T>) {
|
private removeNode(node: LinkedList.Node<T>) {
|
||||||
const prevNode = node.prev;
|
const prevNode = node.prev;
|
||||||
const nextNode = node.next;
|
const nextNode = node.next;
|
||||||
if (prevNode !== null) {
|
if (prevNode !== null) {
|
||||||
@@ -121,11 +121,11 @@ class LinkedList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contains(item: T) {
|
public contains(item: T) {
|
||||||
return this.itemMap.has(item);
|
return this.itemMap.has(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private swap(node0: LinkedListNode<T>, node1: LinkedListNode<T>) {
|
private swap(node0: LinkedList.Node<T>, node1: LinkedList.Node<T>) {
|
||||||
console.assert(node0.next === node1 && node1.prev === node0);
|
console.assert(node0.next === node1 && node1.prev === node0);
|
||||||
const prevNode = node0.prev;
|
const prevNode = node0.prev;
|
||||||
const nextNode = node1.next;
|
const nextNode = node1.next;
|
||||||
@@ -150,7 +150,7 @@ class LinkedList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
move(item: T, prevItem: T|null) {
|
public move(item: T, prevItem: T|null) {
|
||||||
const node = this.getNode(item);
|
const node = this.getNode(item);
|
||||||
this.removeNode(node);
|
this.removeNode(node);
|
||||||
if (prevItem === null) {
|
if (prevItem === null) {
|
||||||
@@ -161,7 +161,7 @@ class LinkedList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveBack(item: T) {
|
public moveBack(item: T) {
|
||||||
const node = this.getNode(item);
|
const node = this.getNode(item);
|
||||||
if (node.prev !== null) {
|
if (node.prev !== null) {
|
||||||
console.assert(node !== this.firstNode);
|
console.assert(node !== this.firstNode);
|
||||||
@@ -169,7 +169,7 @@ class LinkedList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveForward(item: T) {
|
public moveForward(item: T) {
|
||||||
const node = this.getNode(item);
|
const node = this.getNode(item);
|
||||||
if (node.next !== null) {
|
if (node.next !== null) {
|
||||||
console.assert(node !== this.lastNode);
|
console.assert(node !== this.lastNode);
|
||||||
@@ -177,32 +177,34 @@ class LinkedList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
length() {
|
public length() {
|
||||||
return this.itemMap.size;
|
return this.itemMap.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
*iterator() {
|
public *iterator() {
|
||||||
for (let node = this.firstNode; node !== null; node = node.next) {
|
for (let node = this.firstNode; node !== null; node = node.next) {
|
||||||
yield node.item;
|
yield node.item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*iteratorFrom(startItem: T) {
|
public *iteratorFrom(startItem: T) {
|
||||||
for (let node: LinkedListNode<T>|null = this.getNode(startItem); node !== null; node = node.next) {
|
for (let node: LinkedList.Node<T>|null = this.getNode(startItem); node !== null; node = node.next) {
|
||||||
yield node.item;
|
yield node.item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (optimization): reuse nodes
|
namespace LinkedList {
|
||||||
class LinkedListNode<T> {
|
// TODO (optimization): reuse nodes
|
||||||
public readonly item: T;
|
export class Node<T> {
|
||||||
public prev: LinkedListNode<T>|null;
|
public readonly item: T;
|
||||||
public next: LinkedListNode<T>|null;
|
public prev: Node<T> | null;
|
||||||
|
public next: Node<T> | null;
|
||||||
|
|
||||||
constructor(item: T) {
|
constructor(item: T) {
|
||||||
this.item = item;
|
this.item = item;
|
||||||
this.prev = null;
|
this.prev = null;
|
||||||
this.next = null;
|
this.next = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
class SignalManager {
|
class SignalManager {
|
||||||
private connections: { signal: QSignal, handler: (...args: any[]) => void }[];
|
private connections: { signal: QSignal<any>, handler: (...args: any) => void }[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.connections = [];
|
this.connections = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(signal: QSignal, handler: (...args: any[]) => void) {
|
public connect<T extends unknown[]>(signal: QSignal<T>, handler: (...args: [...T]) => void) {
|
||||||
signal.connect(handler);
|
signal.connect(handler);
|
||||||
this.connections.push({ signal: signal, handler: handler });
|
this.connections.push({ signal: signal, handler: handler });
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
public destroy() {
|
||||||
for (const connection of this.connections) {
|
for (const connection of this.connections) {
|
||||||
connection.signal.disconnect(connection.handler);
|
connection.signal.disconnect(connection.handler);
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/utils/log.ts
Normal file
3
src/utils/log.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
function log(...args: any[]) {
|
||||||
|
console.log("Karousel:", ...args);
|
||||||
|
}
|
||||||
@@ -7,10 +7,3 @@ function clamp(value: number, min: number, max: number) {
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rectEqual(a: QRect, b: QRect) {
|
|
||||||
return a.x === b.x &&
|
|
||||||
a.y === b.y &&
|
|
||||||
a.width === b.width &&
|
|
||||||
a.height === b.height;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
function initWorkspaceSignalHandlers(world: World) {
|
function initWorkspaceSignalHandlers(world: World) {
|
||||||
const manager = new SignalManager();
|
const manager = new SignalManager();
|
||||||
|
|
||||||
manager.connect(workspace.clientAdded, (kwinClient: AbstractClient) => {
|
manager.connect(workspace.clientAdded, (kwinClient: KwinClient) => {
|
||||||
console.assert(!world.hasClient(kwinClient));
|
if (Clients.canTileEver(kwinClient)) {
|
||||||
if (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) {
|
if (kwinClient.desktop <= 0) {
|
||||||
kwinClient.desktop = workspace.currentDesktop;
|
kwinClient.desktop = workspace.currentDesktop;
|
||||||
@@ -12,26 +11,35 @@ function initWorkspaceSignalHandlers(world: World) {
|
|||||||
kwinClient.activities = [workspace.currentActivity];
|
kwinClient.activities = [workspace.currentActivity];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
world.addClient(kwinClient);
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.addClient(kwinClient)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.clientRemoved, (kwinClient: AbstractClient) => {
|
manager.connect(workspace.clientRemoved, (kwinClient: AbstractClient) => {
|
||||||
console.assert(world.hasClient(kwinClient));
|
world.do((clientManager, desktopManager) => {
|
||||||
world.removeClient(kwinClient, true);
|
clientManager.removeClient(kwinClient, true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.clientMinimized, (kwinClient: AbstractClient) => {
|
manager.connect(workspace.clientMinimized, (kwinClient: AbstractClient) => {
|
||||||
world.minimizeClient(kwinClient);
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.minimizeClient(kwinClient);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.clientUnminimized, (kwinClient: AbstractClient) => {
|
manager.connect(workspace.clientUnminimized, (kwinClient: AbstractClient) => {
|
||||||
world.unminimizeClient(kwinClient);
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.unminimizeClient(kwinClient);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.clientMaximizeSet, (kwinClient: AbstractClient, horizontally: boolean, vertically: boolean) => {
|
manager.connect(workspace.clientMaximizeSet, (kwinClient: AbstractClient, horizontally: boolean, vertically: boolean) => {
|
||||||
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
if ((horizontally || vertically) && kwinClient.tile !== null) {
|
||||||
|
kwinClient.tile = null;
|
||||||
|
}
|
||||||
|
world.doIfTiled(kwinClient, false, (world, desktopManager, window, column, grid) => {
|
||||||
window.onMaximizedChanged(horizontally, vertically);
|
window.onMaximizedChanged(horizontally, vertically);
|
||||||
grid.container.arrange();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,21 +47,20 @@ function initWorkspaceSignalHandlers(world: World) {
|
|||||||
if (kwinClient === null) {
|
if (kwinClient === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
world.onClientFocused(kwinClient);
|
world.do((clientManager, desktopManager) => {
|
||||||
world.doIfTiled(kwinClient, true, (window, column, grid) => {
|
clientManager.onClientFocused(kwinClient);
|
||||||
window.onFocused();
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.clientFullScreenSet, (kwinClient: X11Client, fullScreen: boolean, user: boolean) => {
|
manager.connect(workspace.currentDesktopChanged, () => {
|
||||||
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
world.do(() => {}); // re-arrange desktop
|
||||||
window.onFullScreenChanged(fullScreen);
|
|
||||||
grid.container.arrange();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.numberDesktopsChanged, (oldNumberOfDesktops: number) => {
|
manager.connect(workspace.currentActivityChanged, () => {
|
||||||
|
world.do(() => {}); // re-arrange desktop
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.connect(workspace.numberDesktopsChanged, (oldNumberOfVirtualDesktops: number) => {
|
||||||
world.updateDesktops();
|
world.updateDesktops();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
165
src/world/ClientManager.ts
Normal file
165
src/world/ClientManager.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
class ClientManager {
|
||||||
|
private readonly world: World;
|
||||||
|
private readonly desktopManager: DesktopManager;
|
||||||
|
private readonly clientMap: Map<AbstractClient, ClientWrapper>;
|
||||||
|
private lastFocusedClient: AbstractClient|null;
|
||||||
|
private readonly windowRuleEnforcer: WindowRuleEnforcer;
|
||||||
|
|
||||||
|
constructor(config: Config, world: World, desktopManager: DesktopManager) {
|
||||||
|
this.world = world;
|
||||||
|
this.desktopManager = desktopManager;
|
||||||
|
this.clientMap = new Map();
|
||||||
|
this.lastFocusedClient = null;
|
||||||
|
|
||||||
|
let parsedWindowRules: WindowRule[] = [];
|
||||||
|
try {
|
||||||
|
parsedWindowRules = JSON.parse(config.windowRules);
|
||||||
|
} catch (error: any) {
|
||||||
|
notificationInvalidWindowRules.sendEvent();
|
||||||
|
log("failed to parse windowRules:", error);
|
||||||
|
}
|
||||||
|
this.windowRuleEnforcer = new WindowRuleEnforcer(parsedWindowRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addClient(kwinClient: TopLevel) {
|
||||||
|
console.assert(!this.hasClient(kwinClient));
|
||||||
|
const client = new ClientWrapper(
|
||||||
|
kwinClient,
|
||||||
|
new ClientState.Floating(null),
|
||||||
|
this.findTransientFor(kwinClient),
|
||||||
|
this.windowRuleEnforcer.initClientSignalManager(this.world, kwinClient),
|
||||||
|
);
|
||||||
|
this.clientMap.set(kwinClient, client);
|
||||||
|
|
||||||
|
if (kwinClient.dock) {
|
||||||
|
client.stateManager.setState(() => new ClientState.Docked(this.world, kwinClient), false);
|
||||||
|
} else if (this.windowRuleEnforcer.shouldTile(kwinClient)) {
|
||||||
|
const grid = this.desktopManager.getDesktopForClient(client.kwinClient).grid;
|
||||||
|
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeClient(kwinClient: AbstractClient, passFocus: boolean) {
|
||||||
|
console.assert(this.hasClient(kwinClient));
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.destroy(passFocus && kwinClient === this.lastFocusedClient);
|
||||||
|
this.clientMap.delete(kwinClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findTransientFor(kwinClient: AbstractClient) {
|
||||||
|
if (!kwinClient.transient) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transientFor = this.clientMap.get(kwinClient.transientFor);
|
||||||
|
if (transientFor === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transientFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public minimizeClient(kwinClient: AbstractClient) {
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||||
|
client.stateManager.setState(() => new ClientState.TiledMinimized(), kwinClient === this.lastFocusedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unminimizeClient(kwinClient: AbstractClient) {
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client.stateManager.getState() instanceof ClientState.TiledMinimized) {
|
||||||
|
const grid = this.desktopManager.getDesktopForClient(client.kwinClient).grid;
|
||||||
|
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public tileClient(kwinClient: AbstractClient) {
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const grid = this.desktopManager.getDesktopForClient(client.kwinClient).grid;
|
||||||
|
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public untileClient(kwinClient: AbstractClient) {
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||||
|
client.stateManager.setState(() => new ClientState.Floating(client), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleFloatingClient(kwinClient: TopLevel) {
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientState = client.stateManager.getState();
|
||||||
|
if (clientState instanceof ClientState.Floating && Clients.canTileEver(kwinClient)) {
|
||||||
|
Clients.makeTileable(kwinClient);
|
||||||
|
const grid = this.desktopManager.getDesktopForClient(client.kwinClient).grid;
|
||||||
|
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
|
||||||
|
} else if (clientState instanceof ClientState.Tiled) {
|
||||||
|
client.stateManager.setState(() => new ClientState.Floating(client), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasClient(kwinClient: AbstractClient) {
|
||||||
|
return this.clientMap.has(kwinClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onClientFocused(kwinClient: AbstractClient) {
|
||||||
|
this.lastFocusedClient = kwinClient;
|
||||||
|
const window = this.findTiledWindow(kwinClient, true);
|
||||||
|
if (window !== null) {
|
||||||
|
window.onFocused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public findTiledWindow(kwinClient: AbstractClient, followTransient: boolean) {
|
||||||
|
const client = this.clientMap.get(kwinClient);
|
||||||
|
if (client === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findTiledWindowOfClient(client, followTransient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findTiledWindowOfClient(client: ClientWrapper, followTransient: boolean): Window|null {
|
||||||
|
const clientState = client.stateManager.getState();
|
||||||
|
if (clientState instanceof ClientState.Tiled) {
|
||||||
|
return clientState.window;
|
||||||
|
} else if (followTransient && client.transientFor !== null) {
|
||||||
|
return this.findTiledWindowOfClient(client.transientFor, true);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeAllClients() {
|
||||||
|
for (const kwinClient of Array.from(this.clientMap.keys())) {
|
||||||
|
this.removeClient(kwinClient, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.removeAllClients();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
class ClientStateDocked {
|
|
||||||
private readonly world: World;
|
|
||||||
private readonly signalManager: SignalManager;
|
|
||||||
|
|
||||||
constructor(world: World, kwinClient: AbstractClient) {
|
|
||||||
this.world = world;
|
|
||||||
this.signalManager = ClientStateDocked.initSignalManager(world, kwinClient);
|
|
||||||
world.onScreenResized();
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(passFocus: boolean) {
|
|
||||||
this.signalManager.destroy();
|
|
||||||
this.world.onScreenResized();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static initSignalManager(world: World, kwinClient: AbstractClient) {
|
|
||||||
const manager = new SignalManager();
|
|
||||||
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
|
||||||
world.onScreenResized();
|
|
||||||
});
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
class ClientStateManager {
|
|
||||||
private state: ClientState;
|
|
||||||
|
|
||||||
constructor(initialState: ClientState) {
|
|
||||||
this.state = initialState;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(newState: ClientState, passFocus: boolean) {
|
|
||||||
this.state.destroy(passFocus);
|
|
||||||
this.state = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
getState() {
|
|
||||||
return this.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(passFocus: boolean) {
|
|
||||||
this.state.destroy(passFocus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientState = ClientStateTiled | ClientStateTiledMinimized | ClientStateFloating | ClientStateDocked;
|
|
||||||
|
|
||||||
class ClientStateTiledMinimized {
|
|
||||||
destroy(passFocus: boolean) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClientStateFloating {
|
|
||||||
destroy(passFocus: boolean) {}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
class ClientStateTiled {
|
|
||||||
readonly window: Window;
|
|
||||||
private readonly signalManager: SignalManager;
|
|
||||||
|
|
||||||
constructor(world: World, client: ClientWrapper) {
|
|
||||||
client.prepareForTiling();
|
|
||||||
|
|
||||||
const grid = world.getClientGrid(client.kwinClient);
|
|
||||||
const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
|
||||||
const window = new Window(client, column);
|
|
||||||
grid.container.arrange();
|
|
||||||
|
|
||||||
this.window = window;
|
|
||||||
this.signalManager = ClientStateTiled.initSignalManager(world, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(passFocus: boolean) {
|
|
||||||
this.signalManager.destroy();
|
|
||||||
|
|
||||||
const window = this.window;
|
|
||||||
const grid = window.column.grid;
|
|
||||||
const clientWrapper = window.client;
|
|
||||||
window.destroy(passFocus);
|
|
||||||
grid.container.arrange();
|
|
||||||
|
|
||||||
clientWrapper.prepareForFloating(grid.container.clientArea);
|
|
||||||
}
|
|
||||||
|
|
||||||
static initSignalManager(world: World, window: Window) {
|
|
||||||
const client = window.client;
|
|
||||||
const kwinClient = client.kwinClient;
|
|
||||||
const manager = new SignalManager();
|
|
||||||
|
|
||||||
manager.connect(kwinClient.desktopChanged, () => {
|
|
||||||
if (kwinClient.desktop === -1) {
|
|
||||||
// windows on all desktops are not supported
|
|
||||||
world.untileClient(kwinClient);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ClientStateTiled.moveWindowToCorrectGrid(world, window);
|
|
||||||
});
|
|
||||||
|
|
||||||
manager.connect(kwinClient.activitiesChanged, (kwinClient: AbstractClient) => {
|
|
||||||
if (kwinClient.activities.length !== 1) {
|
|
||||||
// windows on multiple activities are not supported
|
|
||||||
world.untileClient(kwinClient);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ClientStateTiled.moveWindowToCorrectGrid(world, window);
|
|
||||||
})
|
|
||||||
|
|
||||||
let lastResize = false;
|
|
||||||
manager.connect(kwinClient.moveResizedChanged, () => {
|
|
||||||
if (world.untileOnDrag && kwinClient.move) {
|
|
||||||
world.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.moveResizeCursorChanged, () => {
|
|
||||||
cursorChangedAfterResizeStart = true;
|
|
||||||
});
|
|
||||||
manager.connect(kwinClient.clientStartUserMovedResized, () => {
|
|
||||||
cursorChangedAfterResizeStart = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
|
||||||
const scrollView = window.column.grid.container;
|
|
||||||
if (kwinClient.resize) {
|
|
||||||
window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart);
|
|
||||||
scrollView.arrange();
|
|
||||||
} else {
|
|
||||||
const maximized = rectEqual(kwinClient.frameGeometry, scrollView.clientArea);
|
|
||||||
if (!client.isManipulatingGeometry() && !kwinClient.fullScreen && !maximized) {
|
|
||||||
window.onProgrammaticResize(oldGeometry);
|
|
||||||
scrollView.arrange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
static moveWindowToCorrectGrid(world: World, window: Window) {
|
|
||||||
const kwinClient = window.client.kwinClient;
|
|
||||||
|
|
||||||
const oldGrid = window.column.grid;
|
|
||||||
const newGrid = world.getClientGrid(kwinClient);
|
|
||||||
if (oldGrid === newGrid) {
|
|
||||||
// window already on the correct grid
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newColumn = new Column(newGrid, newGrid.getLastFocusedColumn() ?? newGrid.getLastColumn());
|
|
||||||
window.moveToColumn(newColumn);
|
|
||||||
oldGrid.container.arrange();
|
|
||||||
newGrid.container.arrange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
class ClientWrapper {
|
class ClientWrapper {
|
||||||
public readonly kwinClient: AbstractClient;
|
public readonly kwinClient: TopLevel;
|
||||||
public readonly stateManager: ClientStateManager;
|
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 signalManager: SignalManager;
|
||||||
@@ -9,13 +9,13 @@ class ClientWrapper {
|
|||||||
private readonly manipulatingGeometry: Doer;
|
private readonly manipulatingGeometry: Doer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kwinClient: AbstractClient,
|
kwinClient: TopLevel,
|
||||||
initialState: ClientState,
|
initialState: ClientState.State,
|
||||||
transientFor: ClientWrapper | null,
|
transientFor: ClientWrapper | null,
|
||||||
rulesSignalManager: SignalManager | null,
|
rulesSignalManager: SignalManager | null,
|
||||||
) {
|
) {
|
||||||
this.kwinClient = kwinClient;
|
this.kwinClient = kwinClient;
|
||||||
this.stateManager = new ClientStateManager(initialState);
|
this.stateManager = new ClientState.Manager(initialState);
|
||||||
this.transientFor = transientFor;
|
this.transientFor = transientFor;
|
||||||
this.transients = [];
|
this.transients = [];
|
||||||
if (transientFor !== null) {
|
if (transientFor !== null) {
|
||||||
@@ -27,7 +27,7 @@ class ClientWrapper {
|
|||||||
this.manipulatingGeometry = new Doer();
|
this.manipulatingGeometry = new Doer();
|
||||||
}
|
}
|
||||||
|
|
||||||
place(x: number, y: number, width: number, height: number) {
|
public place(x: number, y: number, width: number, height: number) {
|
||||||
this.manipulatingGeometry.do(() => {
|
this.manipulatingGeometry.do(() => {
|
||||||
if (this.kwinClient.resize) {
|
if (this.kwinClient.resize) {
|
||||||
// window is being manually resized, prevent fighting with the user
|
// window is being manually resized, prevent fighting with the user
|
||||||
@@ -37,76 +37,87 @@ class ClientWrapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private moveTransient(dx: number, dy: number) {
|
private moveTransient(dx: number, dy: number, desktopNumber: number) {
|
||||||
// TODO: prevent moving off the grid
|
// TODO: prevent moving off the grid
|
||||||
if (this.stateManager.getState() instanceof ClientStateFloating) {
|
if (this.stateManager.getState() instanceof ClientState.Floating) {
|
||||||
const frame = this.kwinClient.frameGeometry;
|
if (this.kwinClient.desktop === desktopNumber) {
|
||||||
this.kwinClient.frameGeometry = Qt.rect(
|
const frame = this.kwinClient.frameGeometry;
|
||||||
frame.x + dx,
|
this.kwinClient.frameGeometry = Qt.rect(
|
||||||
frame.y + dy,
|
frame.x + dx,
|
||||||
frame.width,
|
frame.y + dy,
|
||||||
frame.height,
|
frame.width,
|
||||||
);
|
frame.height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const transient of this.transients) {
|
for (const transient of this.transients) {
|
||||||
transient.moveTransient(dx, dy);
|
transient.moveTransient(dx, dy, desktopNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
public focus() {
|
||||||
workspace.activeClient = this.kwinClient;
|
workspace.activeClient = this.kwinClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
isFocused() {
|
public isFocused() {
|
||||||
return workspace.activeClient === this.kwinClient;
|
return workspace.activeClient === this.kwinClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMaximize(horizontally: boolean, vertically: boolean) {
|
public setMaximize(horizontally: boolean, vertically: boolean) {
|
||||||
this.manipulatingGeometry.do(() => {
|
this.manipulatingGeometry.do(() => {
|
||||||
this.kwinClient.setMaximize(vertically, horizontally);
|
this.kwinClient.setMaximize(vertically, horizontally);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullScreen(fullScreen: boolean) {
|
public setFullScreen(fullScreen: boolean) {
|
||||||
this.manipulatingGeometry.do(() => {
|
this.manipulatingGeometry.do(() => {
|
||||||
this.kwinClient.fullScreen = fullScreen;
|
this.kwinClient.fullScreen = fullScreen;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setShade(shade: boolean) {
|
public setShade(shade: boolean) {
|
||||||
this.manipulatingGeometry.do(() => {
|
this.manipulatingGeometry.do(() => {
|
||||||
this.kwinClient.shade = shade;
|
this.kwinClient.shade = shade;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isShaded() {
|
public isShaded() {
|
||||||
return this.kwinClient.shade;
|
return this.kwinClient.shade;
|
||||||
}
|
}
|
||||||
|
|
||||||
isManipulatingGeometry() {
|
public isManipulatingGeometry() {
|
||||||
return this.manipulatingGeometry.isDoing();
|
return this.manipulatingGeometry.isDoing();
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareForTiling() {
|
public prepareForTiling() {
|
||||||
this.kwinClient.keepBelow = true;
|
this.kwinClient.keepBelow = true;
|
||||||
this.setFullScreen(false);
|
this.setFullScreen(false);
|
||||||
|
if (this.kwinClient.tile !== null) {
|
||||||
|
this.setMaximize(false, true); // disable quick tile mode
|
||||||
|
}
|
||||||
this.setMaximize(false, false);
|
this.setMaximize(false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareForFloating(screenSize: QRect) {
|
public restoreAfterTiling(screenSize: QRect) {
|
||||||
this.kwinClient.keepBelow = false;
|
this.kwinClient.keepBelow = false;
|
||||||
this.setShade(false);
|
this.setShade(false);
|
||||||
this.setFullScreen(false);
|
this.setFullScreen(false);
|
||||||
this.setMaximize(false, false);
|
if (this.kwinClient.tile === null) {
|
||||||
|
this.setMaximize(false, false);
|
||||||
|
}
|
||||||
|
this.ensureVisible(screenSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepareForFloating() {
|
||||||
|
const placementArea = workspace.clientArea(ClientAreaOption.PlacementArea, this.kwinClient.screen, this.kwinClient.desktop);
|
||||||
const clientRect = this.kwinClient.frameGeometry;
|
const clientRect = this.kwinClient.frameGeometry;
|
||||||
const width = this.preferredWidth;
|
const width = this.preferredWidth;
|
||||||
this.place(
|
this.place(
|
||||||
clamp(clientRect.x, screenSize.left, screenSize.right - width),
|
clientRect.x,
|
||||||
clientRect.y,
|
clientRect.y,
|
||||||
width,
|
width,
|
||||||
Math.min(clientRect.height, Math.round(screenSize.height / 2)),
|
Math.min(clientRect.height, Math.round(placementArea.height / 2)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +130,28 @@ class ClientWrapper {
|
|||||||
this.transients.splice(i, 1);
|
this.transients.splice(i, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(passFocus: boolean) {
|
public ensureTransientsVisible(screenSize: QRect) {
|
||||||
|
for (const transient of this.transients) {
|
||||||
|
if (transient.stateManager.getState() instanceof ClientState.Floating) {
|
||||||
|
transient.ensureVisible(screenSize);
|
||||||
|
transient.ensureTransientsVisible(screenSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ensureVisible(screenSize: QRect) {
|
||||||
|
if (this.kwinClient.desktop !== workspace.currentDesktop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const frame = this.kwinClient.frameGeometry;
|
||||||
|
if (frame.left < 0) {
|
||||||
|
frame.x = 0;
|
||||||
|
} else if (frame.right > screenSize.width) {
|
||||||
|
frame.x = screenSize.width - frame.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(passFocus: boolean) {
|
||||||
this.stateManager.destroy(passFocus);
|
this.stateManager.destroy(passFocus);
|
||||||
this.signalManager.destroy();
|
this.signalManager.destroy();
|
||||||
if (this.rulesSignalManager !== null) {
|
if (this.rulesSignalManager !== null) {
|
||||||
@@ -133,10 +165,10 @@ class ClientWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static initSignalManager(client: ClientWrapper) {
|
private static initSignalManager(client: ClientWrapper) {
|
||||||
const manager = new SignalManager();
|
const manager = new SignalManager();
|
||||||
manager.connect(client.kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
manager.connect(client.kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
||||||
if (client.stateManager.getState() instanceof ClientStateTiled) {
|
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||||
const newGeometry = client.kwinClient.frameGeometry;
|
const newGeometry = client.kwinClient.frameGeometry;
|
||||||
const oldCenterX = oldGeometry.x + oldGeometry.width/2;
|
const oldCenterX = oldGeometry.x + oldGeometry.width/2;
|
||||||
const oldCenterY = oldGeometry.y + oldGeometry.height/2;
|
const oldCenterY = oldGeometry.y + oldGeometry.height/2;
|
||||||
@@ -145,7 +177,7 @@ class ClientWrapper {
|
|||||||
const dx = Math.round(newCenterX - oldCenterX);
|
const dx = Math.round(newCenterX - oldCenterX);
|
||||||
const dy = Math.round(newCenterY - oldCenterY);
|
const dy = Math.round(newCenterY - oldCenterY);
|
||||||
for (const transient of client.transients) {
|
for (const transient of client.transients) {
|
||||||
transient.moveTransient(dx, dy);
|
transient.moveTransient(dx, dy, client.kwinClient.desktop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
31
src/world/Clients.ts
Normal file
31
src/world/Clients.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
namespace Clients {
|
||||||
|
export function canTileEver(kwinClient: AbstractClient) {
|
||||||
|
return kwinClient.resizeable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canTileNow(kwinClient: TopLevel) {
|
||||||
|
return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktop > 0 && kwinClient.activities.length === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeTileable(kwinClient: TopLevel) {
|
||||||
|
if (kwinClient.minimized) {
|
||||||
|
kwinClient.minimized = false;
|
||||||
|
}
|
||||||
|
if (kwinClient.desktop <= 0) {
|
||||||
|
kwinClient.desktop = workspace.currentDesktop;
|
||||||
|
}
|
||||||
|
if (kwinClient.activities.length !== 1) {
|
||||||
|
kwinClient.activities = [workspace.currentActivity];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMaximizedGeometry(kwinClient: TopLevel) {
|
||||||
|
const maximizeArea = workspace.clientArea(ClientAreaOption.MaximizeArea, kwinClient.screen, kwinClient.desktop);
|
||||||
|
return kwinClient.frameGeometry === maximizeArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFullScreenGeometry(kwinClient: TopLevel) {
|
||||||
|
const fullScreenArea = workspace.clientArea(ClientAreaOption.FullScreenArea, kwinClient.screen, kwinClient.desktop);
|
||||||
|
return kwinClient.frameGeometry === fullScreenArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/world/DesktopManager.ts
Normal file
106
src/world/DesktopManager.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
class DesktopManager {
|
||||||
|
private readonly config: Desktop.Config;
|
||||||
|
public readonly layoutConfig: LayoutConfig;
|
||||||
|
private readonly desktopsPerActivity: Map<string, Desktop[]>;
|
||||||
|
private nVirtualDesktops: number;
|
||||||
|
|
||||||
|
constructor(config: Desktop.Config, layoutConfig: LayoutConfig, currentActivity: string) {
|
||||||
|
this.config = config;
|
||||||
|
this.layoutConfig = layoutConfig;
|
||||||
|
this.desktopsPerActivity = new Map();
|
||||||
|
this.nVirtualDesktops = 0;
|
||||||
|
this.update()
|
||||||
|
this.addActivity(currentActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update() {
|
||||||
|
this.setNVirtualDesktops(workspace.desktops);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDesktop(activity: string, desktopNumber: number) {
|
||||||
|
const desktopIndex = desktopNumber - 1;
|
||||||
|
if (desktopIndex >= this.nVirtualDesktops || desktopIndex < 0) {
|
||||||
|
throw new Error("invalid desktop number: " + String(desktopNumber));
|
||||||
|
}
|
||||||
|
if (!this.desktopsPerActivity.has(activity)) {
|
||||||
|
this.addActivity(activity);
|
||||||
|
}
|
||||||
|
return this.desktopsPerActivity.get(activity)![desktopIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentDesktop() {
|
||||||
|
return this.getDesktop(workspace.currentActivity, workspace.currentDesktop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDesktopInCurrentActivity(desktopNumber: number) {
|
||||||
|
return this.getDesktop(workspace.currentActivity, desktopNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDesktopForClient(kwinClient: TopLevel) {
|
||||||
|
console.assert(kwinClient.activities.length === 1);
|
||||||
|
return this.getDesktop(kwinClient.activities[0], kwinClient.desktop);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setNVirtualDesktops(nVirtualDesktops: number) {
|
||||||
|
if (nVirtualDesktops > this.nVirtualDesktops) {
|
||||||
|
this.addDesktopsToActivities(nVirtualDesktops - this.nVirtualDesktops);
|
||||||
|
} else if (nVirtualDesktops < this.nVirtualDesktops) {
|
||||||
|
this.removeDesktopsFromActivities(this.nVirtualDesktops - nVirtualDesktops);
|
||||||
|
}
|
||||||
|
this.nVirtualDesktops = nVirtualDesktops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addDesktopsToActivities(n: number) {
|
||||||
|
for (const desktops of this.desktopsPerActivity.values()) {
|
||||||
|
this.addDesktops(desktops, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addDesktops(desktops: Desktop[], n: number) {
|
||||||
|
const nStart = desktops.length;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const desktopNumber = nStart + i + 1;
|
||||||
|
desktops.push(new Desktop(desktopNumber, this.config, this.layoutConfig));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addActivity(activity: string) {
|
||||||
|
const desktops: Desktop[] = [];
|
||||||
|
this.addDesktops(desktops, this.nVirtualDesktops);
|
||||||
|
this.desktopsPerActivity.set(activity, desktops);
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeActivity(activity: string) {
|
||||||
|
const removedDesktops = this.desktopsPerActivity.get(activity)!;
|
||||||
|
this.desktopsPerActivity.delete(activity);
|
||||||
|
const targetActivityDesktops = this.desktopsPerActivity.values().next().value;
|
||||||
|
for (let i = 0; i < removedDesktops.length; i++) {
|
||||||
|
removedDesktops[i].grid.evacuate(targetActivityDesktops[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
for (const desktop of this.desktops()) {
|
||||||
|
desktop.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public *desktops() {
|
||||||
|
for (const desktops of this.desktopsPerActivity.values()) {
|
||||||
|
for (const desktop of desktops) {
|
||||||
|
yield desktop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
class ScrollViewManager {
|
|
||||||
private readonly world: World;
|
|
||||||
private readonly config: ScrollView.Config;
|
|
||||||
public readonly layoutConfig: LayoutConfig;
|
|
||||||
private readonly scrollViewsPerActivity: Map<string, ScrollView[]>;
|
|
||||||
private nDesktops: number;
|
|
||||||
|
|
||||||
constructor(world: World, config: ScrollView.Config, layoutConfig: LayoutConfig, currentActivity: string, nDesktops: number) {
|
|
||||||
this.config = config;
|
|
||||||
this.layoutConfig = layoutConfig;
|
|
||||||
this.world = world;
|
|
||||||
this.scrollViewsPerActivity = new Map();
|
|
||||||
this.nDesktops = 0;
|
|
||||||
this.setNDesktops(nDesktops);
|
|
||||||
this.addActivity(currentActivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(activity: string, desktopNumber: number) {
|
|
||||||
const desktopIndex = desktopNumber - 1;
|
|
||||||
if (desktopIndex >= this.nDesktops || this.nDesktops < 0) {
|
|
||||||
throw new Error("invalid desktop number: " + String(desktopNumber));
|
|
||||||
}
|
|
||||||
if (!this.scrollViewsPerActivity.has(activity)) {
|
|
||||||
this.addActivity(activity);
|
|
||||||
}
|
|
||||||
return this.scrollViewsPerActivity.get(activity)![desktopIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
setNDesktops(nDesktops: number) {
|
|
||||||
if (nDesktops > this.nDesktops) {
|
|
||||||
this.addDesktopsToActivities(nDesktops - this.nDesktops);
|
|
||||||
} else if (nDesktops < this.nDesktops) {
|
|
||||||
this.removeDesktopsFromActivities(this.nDesktops - nDesktops);
|
|
||||||
}
|
|
||||||
this.nDesktops = nDesktops;
|
|
||||||
}
|
|
||||||
|
|
||||||
private addDesktopsToActivities(n: number) {
|
|
||||||
for (const scrollViews of this.scrollViewsPerActivity.values()) {
|
|
||||||
this.addDesktops(scrollViews, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private addDesktops(scrollViews: ScrollView[], n: number) {
|
|
||||||
const nStart = scrollViews.length;
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
const desktopNumber = nStart + i + 1;
|
|
||||||
scrollViews.push(new ScrollView(this.world, desktopNumber, this.config, this.layoutConfig));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeDesktopsFromActivities(n: number) {
|
|
||||||
const lastRemainingDesktopIndex = this.nDesktops - n - 1;
|
|
||||||
for (const scrollViews of this.scrollViewsPerActivity.values()) {
|
|
||||||
const targetScrollView = scrollViews[lastRemainingDesktopIndex];
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
const removedScrollView = scrollViews.pop()!;
|
|
||||||
removedScrollView.grid.evacuate(targetScrollView.grid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addActivity(activity: string) {
|
|
||||||
const scrollViews: ScrollView[] = [];
|
|
||||||
this.addDesktops(scrollViews, this.nDesktops);
|
|
||||||
this.scrollViewsPerActivity.set(activity, scrollViews);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeActivity(activity: string) {
|
|
||||||
const removedScrollViews = this.scrollViewsPerActivity.get(activity)!;
|
|
||||||
this.scrollViewsPerActivity.delete(activity);
|
|
||||||
const targetActivityScrollViews = this.scrollViewsPerActivity.values().next().value;
|
|
||||||
for (let i = 0; i < removedScrollViews.length; i++) {
|
|
||||||
removedScrollViews[i].grid.evacuate(targetActivityScrollViews[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*scrollViews() {
|
|
||||||
for (const scrollViews of this.scrollViewsPerActivity.values()) {
|
|
||||||
for (const scrollView of scrollViews) {
|
|
||||||
yield scrollView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +1,24 @@
|
|||||||
class World {
|
class World {
|
||||||
public readonly untileOnDrag: boolean;
|
public readonly untileOnDrag: boolean;
|
||||||
private readonly scrollViewManager: ScrollViewManager;
|
private readonly desktopManager: DesktopManager;
|
||||||
private readonly clientMap: Map<AbstractClient, ClientWrapper>;
|
public readonly clientManager: ClientManager;
|
||||||
private lastFocusedClient: AbstractClient|null;
|
|
||||||
private readonly workspaceSignalManager: SignalManager;
|
private readonly workspaceSignalManager: SignalManager;
|
||||||
private readonly windowRuleEnforcer: WindowRuleEnforcer;
|
|
||||||
private readonly screenResizedDelayer: Delayer;
|
private readonly screenResizedDelayer: Delayer;
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
this.untileOnDrag = config.untileOnDrag;
|
this.untileOnDrag = config.untileOnDrag;
|
||||||
this.clientMap = new Map();
|
|
||||||
this.lastFocusedClient = null;
|
|
||||||
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
||||||
|
|
||||||
let parsedWindowRules: WindowRule[] = [];
|
|
||||||
try {
|
|
||||||
parsedWindowRules = JSON.parse(config.windowRules);
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log("failed to parse windowRules:", error);
|
|
||||||
}
|
|
||||||
this.windowRuleEnforcer = new WindowRuleEnforcer(this, parsedWindowRules);
|
|
||||||
|
|
||||||
this.screenResizedDelayer = new Delayer(1000, () => {
|
this.screenResizedDelayer = new Delayer(1000, () => {
|
||||||
// this delay ensures that docks get taken into account by `workspace.clientArea`
|
// this delay ensures that docks get taken into account by `workspace.clientArea`
|
||||||
const gridManager = this.scrollViewManager; // workaround for bug in Qt5's JS engine
|
const desktopManager = this.desktopManager; // workaround for bug in Qt5's JS engine
|
||||||
for (const scrollView of gridManager.scrollViews()) {
|
for (const desktop of desktopManager.desktops()) {
|
||||||
scrollView.arrange();
|
desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
this.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scrollViewManager = new ScrollViewManager(
|
this.desktopManager = new DesktopManager(
|
||||||
this,
|
|
||||||
{
|
{
|
||||||
marginTop: config.gapsOuterTop,
|
marginTop: config.gapsOuterTop,
|
||||||
marginBottom: config.gapsOuterBottom,
|
marginBottom: config.gapsOuterBottom,
|
||||||
@@ -38,206 +26,64 @@ class World {
|
|||||||
marginRight: config.gapsOuterRight,
|
marginRight: config.gapsOuterRight,
|
||||||
overscroll: config.overscroll,
|
overscroll: config.overscroll,
|
||||||
},
|
},
|
||||||
{
|
config,
|
||||||
gapsInnerHorizontal: config.gapsInnerHorizontal,
|
|
||||||
gapsInnerVertical: config.gapsInnerVertical,
|
|
||||||
stackColumnsByDefault: config.stackColumnsByDefault,
|
|
||||||
resizeNeighborColumn: config.resizeNeighborColumn,
|
|
||||||
},
|
|
||||||
workspace.currentActivity,
|
workspace.currentActivity,
|
||||||
workspace.desktops,
|
|
||||||
);
|
);
|
||||||
|
this.clientManager = new ClientManager(config, this, this.desktopManager);
|
||||||
this.addExistingClients();
|
this.addExistingClients();
|
||||||
}
|
this.update();
|
||||||
|
|
||||||
updateDesktops() {
|
|
||||||
this.scrollViewManager.setNDesktops(workspace.desktops);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private addExistingClients() {
|
private addExistingClients() {
|
||||||
const kwinClients = workspace.clientList();
|
const kwinClients = workspace.clientList();
|
||||||
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.addClient(kwinClient);
|
this.clientManager.addClient(kwinClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getGrid(activity: string, desktopNumber: number) {
|
public updateDesktops() {
|
||||||
console.assert(desktopNumber > 0 && desktopNumber <= workspace.desktops);
|
this.desktopManager.update();
|
||||||
return this.scrollViewManager.get(activity, desktopNumber).grid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getGridInCurrentActivity(desktopNumber: number) {
|
private update() {
|
||||||
return this.getGrid(workspace.currentActivity, desktopNumber);
|
this.desktopManager.getCurrentDesktop().arrange();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentGrid() {
|
public do(f: (clientManager: ClientManager, desktopManager: DesktopManager) => void) {
|
||||||
return this.getGrid(workspace.currentActivity, workspace.currentDesktop);
|
f(this.clientManager, this.desktopManager);
|
||||||
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
getClientGrid(kwinClient: AbstractClient) {
|
public doIfTiled(
|
||||||
console.assert(kwinClient.activities.length === 1);
|
kwinClient: AbstractClient,
|
||||||
return this.getGrid(kwinClient.activities[0], kwinClient.desktop);
|
followTransient: boolean,
|
||||||
}
|
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
|
||||||
|
) {
|
||||||
addClient(kwinClient: AbstractClient) {
|
const window = this.clientManager.findTiledWindow(kwinClient, followTransient);
|
||||||
const client = new ClientWrapper(
|
if (window === null) {
|
||||||
kwinClient,
|
|
||||||
new ClientStateFloating(),
|
|
||||||
this.findTransientFor(kwinClient),
|
|
||||||
this.windowRuleEnforcer.initClientSignalManager(this, kwinClient),
|
|
||||||
);
|
|
||||||
this.clientMap.set(kwinClient, client);
|
|
||||||
|
|
||||||
if (kwinClient.dock) {
|
|
||||||
client.stateManager.setState(new ClientStateDocked(this, kwinClient), false);
|
|
||||||
} else if (this.windowRuleEnforcer.shouldTile(kwinClient)) {
|
|
||||||
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeClient(kwinClient: AbstractClient, passFocus: boolean) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.destroy(passFocus && kwinClient === this.lastFocusedClient);
|
const column = window.column;
|
||||||
this.clientMap.delete(kwinClient);
|
const grid = column.grid;
|
||||||
|
f(this.clientManager, this.desktopManager, window, column, grid);
|
||||||
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
findTransientFor(kwinClient: AbstractClient) {
|
public doIfTiledFocused(
|
||||||
if (!kwinClient.transient) {
|
followTransient: boolean,
|
||||||
return null;
|
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
|
||||||
}
|
) {
|
||||||
|
|
||||||
const transientFor = this.clientMap.get(kwinClient.transientFor);
|
|
||||||
if (transientFor === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return transientFor;
|
|
||||||
}
|
|
||||||
|
|
||||||
minimizeClient(kwinClient: AbstractClient) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (client.stateManager.getState() instanceof ClientStateTiled) {
|
|
||||||
client.stateManager.setState(new ClientStateTiledMinimized(), kwinClient === this.lastFocusedClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unminimizeClient(kwinClient: AbstractClient) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (client.stateManager.getState() instanceof ClientStateTiledMinimized) {
|
|
||||||
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tileClient(kwinClient: AbstractClient) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (client.stateManager.getState() instanceof ClientStateTiled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
untileClient(kwinClient: AbstractClient) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (client.stateManager.getState() instanceof ClientStateTiled) {
|
|
||||||
client.stateManager.setState(new ClientStateFloating(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFloatingClient(kwinClient: AbstractClient) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientState = client.stateManager.getState();
|
|
||||||
if (clientState instanceof ClientStateFloating && canTileEver(kwinClient)) {
|
|
||||||
makeTileable(kwinClient);
|
|
||||||
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
|
||||||
} else if (clientState instanceof ClientStateTiled) {
|
|
||||||
client.stateManager.setState(new ClientStateFloating(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasClient(kwinClient: AbstractClient) {
|
|
||||||
return this.clientMap.has(kwinClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClientFocused(kwinClient: AbstractClient) {
|
|
||||||
this.lastFocusedClient = kwinClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
private doIfTiledInner(client: ClientWrapper, followTransient: boolean, f: (window: Window, column: Column, grid: Grid) => void) {
|
|
||||||
const clientState = client.stateManager.getState();
|
|
||||||
if (clientState instanceof ClientStateTiled) {
|
|
||||||
const window = clientState.window;
|
|
||||||
const column = window.column;
|
|
||||||
const grid = column.grid;
|
|
||||||
f(window, column, grid);
|
|
||||||
} else if (followTransient && client.transientFor !== null) {
|
|
||||||
this.doIfTiledInner(client.transientFor, true, f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doIfTiled(kwinClient: AbstractClient, followTransient: boolean, f: (window: Window, column: Column, grid: Grid) => void) {
|
|
||||||
const client = this.clientMap.get(kwinClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.doIfTiledInner(client, followTransient, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
doIfTiledFocused(followTransient: boolean, f: (window: Window, column: Column, grid: Grid) => void) {
|
|
||||||
this.doIfTiled(workspace.activeClient, followTransient, f);
|
this.doIfTiled(workspace.activeClient, followTransient, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFocusedWindow() {
|
public destroy() {
|
||||||
const activeClient = workspace.activeClient;
|
|
||||||
if (activeClient === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const client = this.clientMap.get(activeClient);
|
|
||||||
if (client === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const clientState = client.stateManager.getState();
|
|
||||||
if (clientState instanceof ClientStateTiled) {
|
|
||||||
return clientState.window;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAllClients() {
|
|
||||||
for (const kwinClient of Array.from(this.clientMap.keys())) {
|
|
||||||
this.removeClient(kwinClient, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.workspaceSignalManager.destroy();
|
this.workspaceSignalManager.destroy();
|
||||||
this.removeAllClients();
|
this.clientManager.destroy();
|
||||||
for (const scrollView of this.scrollViewManager.scrollViews()) {
|
this.desktopManager.destroy();
|
||||||
scrollView.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenResized() {
|
public onScreenResized() {
|
||||||
this.screenResizedDelayer.run();
|
this.screenResizedDelayer.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/world/clientState/Docked.ts
Normal file
25
src/world/clientState/Docked.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace ClientState {
|
||||||
|
export class Docked implements State {
|
||||||
|
private readonly world: World;
|
||||||
|
private readonly signalManager: SignalManager;
|
||||||
|
|
||||||
|
constructor(world: World, kwinClient: TopLevel) {
|
||||||
|
this.world = world;
|
||||||
|
this.signalManager = Docked.initSignalManager(world, kwinClient);
|
||||||
|
world.onScreenResized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(passFocus: boolean) {
|
||||||
|
this.signalManager.destroy();
|
||||||
|
this.world.onScreenResized();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static initSignalManager(world: World, kwinClient: TopLevel) {
|
||||||
|
const manager = new SignalManager();
|
||||||
|
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
||||||
|
world.onScreenResized();
|
||||||
|
});
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/world/clientState/Floating.ts
Normal file
11
src/world/clientState/Floating.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace ClientState {
|
||||||
|
export class Floating implements State {
|
||||||
|
constructor(client: ClientWrapper | null) {
|
||||||
|
if (client !== null && client.kwinClient.tile === null) {
|
||||||
|
client.prepareForFloating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(passFocus: boolean) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/world/clientState/Manager.ts
Normal file
26
src/world/clientState/Manager.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace ClientState {
|
||||||
|
export class Manager {
|
||||||
|
private state: State;
|
||||||
|
|
||||||
|
constructor(initialState: State) {
|
||||||
|
this.state = initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setState(constructNewState: () => State, passFocus: boolean) {
|
||||||
|
this.state.destroy(passFocus);
|
||||||
|
this.state = constructNewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getState() {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(passFocus: boolean) {
|
||||||
|
this.state.destroy(passFocus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
destroy(passFocus: boolean): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
121
src/world/clientState/Tiled.ts
Normal file
121
src/world/clientState/Tiled.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
namespace ClientState {
|
||||||
|
export class Tiled implements State {
|
||||||
|
public readonly window: Window;
|
||||||
|
private readonly signalManager: SignalManager;
|
||||||
|
|
||||||
|
constructor(world: World, client: ClientWrapper, grid: Grid) {
|
||||||
|
client.prepareForTiling();
|
||||||
|
|
||||||
|
const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
||||||
|
const window = new Window(client, column);
|
||||||
|
|
||||||
|
this.window = window;
|
||||||
|
this.signalManager = Tiled.initSignalManager(world, window);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(passFocus: boolean) {
|
||||||
|
this.signalManager.destroy();
|
||||||
|
|
||||||
|
const window = this.window;
|
||||||
|
const grid = window.column.grid;
|
||||||
|
const client = window.client;
|
||||||
|
window.destroy(passFocus);
|
||||||
|
|
||||||
|
client.restoreAfterTiling(grid.desktop.clientArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static initSignalManager(world: World, window: Window) {
|
||||||
|
const client = window.client;
|
||||||
|
const kwinClient = client.kwinClient;
|
||||||
|
const manager = new SignalManager();
|
||||||
|
|
||||||
|
manager.connect(kwinClient.desktopChanged, () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
if (kwinClient.desktop === -1) {
|
||||||
|
// windows on all desktops are not supported
|
||||||
|
clientManager.untileClient(kwinClient);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Tiled.moveWindowToCorrectGrid(desktopManager, window);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.connect(kwinClient.activitiesChanged, (kwinClient: AbstractClient) => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
if (kwinClient.activities.length !== 1) {
|
||||||
|
// windows on multiple activities are not supported
|
||||||
|
clientManager.untileClient(kwinClient);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Tiled.moveWindowToCorrectGrid(desktopManager, window);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
let lastResize = false;
|
||||||
|
manager.connect(kwinClient.moveResizedChanged, () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
if (world.untileOnDrag && kwinClient.move) {
|
||||||
|
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.moveResizeCursorChanged, () => {
|
||||||
|
cursorChangedAfterResizeStart = true;
|
||||||
|
});
|
||||||
|
manager.connect(kwinClient.clientStartUserMovedResized, () => {
|
||||||
|
cursorChangedAfterResizeStart = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
if (kwinClient.resize) {
|
||||||
|
window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart);
|
||||||
|
} else if (!client.isManipulatingGeometry() && !Clients.isMaximizedGeometry(kwinClient) && !Clients.isFullScreenGeometry(kwinClient)) {
|
||||||
|
window.onFrameGeometryChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.connect(kwinClient.fullScreenChanged, () => {
|
||||||
|
window.onFullScreenChanged(kwinClient.fullScreen);
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.connect(kwinClient.tileChanged, (tile: Tile) => {
|
||||||
|
if (tile !== null) {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.untileClient(kwinClient);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static moveWindowToCorrectGrid(desktopManager: DesktopManager, window: Window) {
|
||||||
|
const kwinClient = window.client.kwinClient;
|
||||||
|
|
||||||
|
const oldGrid = window.column.grid;
|
||||||
|
const newGrid = desktopManager.getDesktopForClient(kwinClient).grid;
|
||||||
|
if (oldGrid === newGrid) {
|
||||||
|
// window already on the correct grid
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newColumn = new Column(newGrid, newGrid.getLastFocusedColumn() ?? newGrid.getLastColumn());
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/world/clientState/TiledMinimized.ts
Normal file
5
src/world/clientState/TiledMinimized.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace ClientState {
|
||||||
|
export class TiledMinimized implements State {
|
||||||
|
public destroy(passFocus: boolean) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user