59 Commits
v0.6 ... v0.7.2

Author SHA1 Message Date
Peter Fajdiga
5004417285 bump package to 0.7.2 2024-10-27 13:51:52 +01:00
Peter Fajdiga
a110aee7ce ClientWrapper: implement workaround for Qt5 JS bug 2024-09-10 23:33:56 +02:00
Peter Fajdiga
817ea64171 DesktopManager: fix getDesktopForClient 2024-08-30 12:14:40 +02:00
Peter Fajdiga
af930a9b2f Tiled: check if user is resizing any window in the grid 2024-03-18 19:28:58 +01:00
Peter Fajdiga
489a1447e7 ClientWrapper: create a workaround for the problem with stuck off-screen windows on Wayland 2024-03-18 19:28:58 +01:00
Peter Fajdiga
b984f025ec Column: avoid setting preferredWidth of a window when another window's height in the same column is being resized by the user 2024-03-10 21:29:47 +01:00
Peter Fajdiga
4e1204f1bd DesktopManager: refactor getDesktopForClient 2024-03-10 21:02:24 +01:00
Peter Fajdiga
bbcf51783d World: fix grammar in comment 2024-03-10 15:44:17 +01:00
Peter Fajdiga
019da3766e kwin.d.ts: use unknown for Tile 2024-03-10 15:44:17 +01:00
Peter Fajdiga
1535c994b8 use === everywhere 2024-03-10 15:44:16 +01:00
Peter Fajdiga
296d0deca9 remove void keyword in QSignal params 2024-03-09 21:55:15 +01:00
Peter Fajdiga
17e7d5b46e kwin.d.ts: remove unneeded QSignal params 2024-03-09 21:50:27 +01:00
Peter Fajdiga
840a50d14d move ClientAreaOption to kwin.d.ts 2024-03-09 21:44:23 +01:00
Peter Fajdiga
4f99c4dd45 Actions: remove superfluous return values 2024-03-09 19:53:10 +01:00
Peter Fajdiga
030eddaf34 Floating: add missing ; 2024-03-09 19:34:54 +01:00
Peter Fajdiga
7246a7660e kwin.d.ts: remove superfluous comments 2024-03-09 19:19:39 +01:00
Peter Fajdiga
687256d1dd src/config: unjsonify 2024-03-09 19:19:38 +01:00
Peter Fajdiga
12bb7506cc src/keyBindings: unjsonify 2024-03-09 19:19:13 +01:00
Peter Fajdiga
1808ee0025 mark all signal properties as readonly 2024-03-09 19:07:12 +01:00
Peter Fajdiga
3021f61933 metadata.json: change description 2024-03-01 15:54:06 +01:00
Peter Fajdiga
e908138478 fix bug where resizing used manualScrollStep setting instead of manualResizeStep setting 2024-02-22 20:24:12 +01:00
Peter Fajdiga
99ad115370 rename scrollers 2024-02-18 22:11:34 +01:00
Peter Fajdiga
c5a4238f5f move scroller src files to src/behavior/scroller 2024-02-18 22:07:28 +01:00
Peter Fajdiga
0670d9c265 separate clampScrollX logic into different Clamper implementations 2024-02-18 22:06:14 +01:00
Peter Fajdiga
845874b0d0 separate increaseColumnWidth and columnWidthDecrease logic into different ColumnResizer implementations 2024-02-18 22:06:13 +01:00
Peter Fajdiga
a422a077f6 Makefile: add prerequisites to package target 2024-02-18 21:28:17 +01:00
Peter Fajdiga
2fe1be99cb Desktop.scrollCenterVisible: stop prioritizing visible columns (this is now done by ColumnRange.addNeighbors) 2024-02-18 20:21:34 +01:00
Peter Fajdiga
1a449c238d ColumnRange: prioritize nearer columns 2024-02-18 20:15:58 +01:00
Peter Fajdiga
9bda7d1a09 bump version to 0.7.1 2024-02-12 21:27:34 +01:00
Peter Fajdiga
2ce72bcee8 qt.d.ts: add console.trace 2024-02-12 21:17:37 +01:00
Peter Fajdiga
ff3f6c5d6b Desktop: fix ColumnRange when a column's width is still 0 2024-02-12 21:10:12 +01:00
Peter Fajdiga
3ab230b498 Makefile: append version number to package file name 2024-02-11 20:37:28 +01:00
Peter Fajdiga
ba9f362a1c .gitignore: ignore suffixed packages 2024-02-11 20:37:27 +01:00
Peter Fajdiga
ad6c3f1cae bump version to 0.7 2024-02-11 20:37:26 +01:00
Peter Fajdiga
ba4dd2a9c1 config.ui: relabel scrollingGrouped button 2024-02-11 20:37:25 +01:00
Peter Fajdiga
bb61853009 config.ui: reorder tabs 2024-02-11 20:37:22 +01:00
Peter Fajdiga
0cfd9b9e36 Desktop: scrollCenterRange: add parameter prioritiseVisible 2024-01-22 08:55:05 +01:00
Peter Fajdiga
43c4f7ef9a Actions: columnWidthIncrease: add steps for fully visible screen-edge columns 2024-01-22 08:55:05 +01:00
Peter Fajdiga
9cb3f33ecb Actions: extract function findNextStep 2024-01-22 08:55:05 +01:00
Peter Fajdiga
31b9e61ae3 config.ui: add manualResizeStep 2024-01-22 08:55:05 +01:00
Peter Fajdiga
668e6696ab Actions: column width increase/decrease: replace screen-relative steps with column-relative steps 2024-01-22 08:55:05 +01:00
Peter Fajdiga
e63959cfbf Actions: getWidthSteps: ignore screen-relative steps too close to existing steps 2024-01-22 08:55:05 +01:00
Peter Fajdiga
ef2650beb8 Actions: improve snapping in columnWidthDecrease 2024-01-22 08:55:05 +01:00
Peter Fajdiga
750c47c040 implement resize steps (resolves #25) 2024-01-22 08:55:05 +01:00
Peter Fajdiga
88ca0d02e1 generators/config/kcfg.ts: escape xml characters in default config values 2024-01-21 19:40:12 +01:00
Peter Fajdiga
aba786b754 Desktop: rename scrollIntoView 2024-01-21 18:39:53 +01:00
Peter Fajdiga
47aa625c99 Actions: column width increase/decrease: use getCurrentVisibleRange 2024-01-21 18:39:25 +01:00
Peter Fajdiga
03c7cc6503 Desktop.scrollToRange: simplify 2024-01-21 18:37:34 +01:00
Peter Fajdiga
9e9ff2b74f remove overscroll feature (resolves #23) 2024-01-21 18:26:18 +01:00
Peter Fajdiga
5674624e6f Desktop.equalizeVisibleColumnsWidths: simplify scroll at the end 2024-01-21 18:17:29 +01:00
Peter Fajdiga
44dd88ef7c Desktop.equalizeVisibleColumnsWidths: handle columns with limited min width 2024-01-21 18:17:29 +01:00
Peter Fajdiga
f800d6ecf0 Desktop: rewrite ColumnRange.addNeighbors 2024-01-21 18:17:29 +01:00
Peter Fajdiga
3477e17bb3 Desktop: scrollCenterRange: replace parameter requireVisible with condition 2024-01-21 18:17:29 +01:00
Peter Fajdiga
755c781646 rename parameters of doIfTiled passed functions 2024-01-21 18:17:29 +01:00
Peter Fajdiga
926345ba31 move column width increase/decrease code to Actions.ts 2024-01-21 18:17:29 +01:00
Peter Fajdiga
a2295ede43 Desktop: add method scrollCenterVisible (moved from ScrollerGrouped) 2024-01-21 18:17:29 +01:00
Peter Fajdiga
ca80a7ca28 Grid.onColumnWidthChanged: fix autoAdjustScroll call 2024-01-14 15:40:24 +01:00
Peter Fajdiga
64474b1677 readme: mention Niri 2024-01-14 09:55:57 +01:00
Peter Fajdiga
eca63cbc16 readme: update key bindings 2023-12-30 17:26:59 +01:00
35 changed files with 735 additions and 683 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
/package/contents/code/main.js /package/contents/code/main.js
/package/contents/config/main.xml /package/contents/config/main.xml
/karousel.tar.gz /karousel*.tar.gz
/.idea /.idea

View File

@@ -1,6 +1,7 @@
.PHONY: * .PHONY: *
TSC_SCRIPT_FLAGS = --lib es2020 ./src/extern/qt.d.ts TSC_SCRIPT_FLAGS = --lib es2020 ./src/extern/qt.d.ts
VERSION = $(shell grep '"Version":' ./package/metadata.json | grep -o '[0-9\.]*')
config: config:
mkdir -p ./package/contents/config mkdir -p ./package/contents/config
@@ -15,8 +16,8 @@ install: build config
uninstall: uninstall:
kpackagetool5 --type=KWin/Script -r ./package kpackagetool5 --type=KWin/Script -r ./package
package: package: build config
tar -czf ./karousel.tar.gz ./package tar -czf ./karousel_${subst .,_,${VERSION}}.tar.gz ./package
logs: logs:
journalctl -t kwin_x11 -g '^qml:|^file://.*karousel' -f journalctl -t kwin_x11 -g '^qml:|^file://.*karousel' -f

View File

@@ -13,7 +13,8 @@ unprompted reflow of window content.
Windows are automatically centered when possible. And when running out of width, windows can be Windows are automatically centered when possible. And when running out of width, windows can be
scrolled through horizontally. scrolled through horizontally.
Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) and Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM),
[Niri](https://github.com/YaLTeR/niri), and
[Cardboard](https://gitlab.com/cardboardwm/cardboard). [Cardboard](https://gitlab.com/cardboardwm/cardboard).
## Dependencies ## Dependencies
@@ -52,6 +53,7 @@ Here's the default ones:
| Meta+Ctrl+Shift+End | Move column to end | | Meta+Ctrl+Shift+End | Move column to end |
| Meta+Ctrl++ | Increase column width | | Meta+Ctrl++ | Increase column width |
| Meta+Ctrl+- | Decrease column width | | Meta+Ctrl+- | Decrease column width |
| Meta+Ctrl+X | Equalize widths of visible columns |
| 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 |

View File

@@ -5,9 +5,20 @@ console.log(`<?xml version="1.0" encoding="UTF-8"?>
for (const entry of configDef) { for (const entry of configDef) {
console.log(` <entry name="${entry.name}" type="${entry.type}"> console.log(` <entry name="${entry.name}" type="${entry.type}">
<default>${entry.default}</default> <default>${escapeXml(entry.default)}</default>
</entry>`); </entry>`);
} }
console.log(` </group> console.log(` </group>
</kcfg>`); </kcfg>`);
function escapeXml(input: any) {
if (typeof input === "string") {
return input
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
} else {
return input;
}
}

View File

@@ -21,7 +21,7 @@ function formatComment(comment: string | undefined) {
function printCols(...columns: (string[] | string)[]) { function printCols(...columns: (string[] | string)[]) {
const nCols = columns.length; const nCols = columns.length;
if (nCols == 0) { if (nCols === 0) {
return; return;
} }
@@ -30,7 +30,7 @@ function printCols(...columns: (string[] | string)[]) {
).map( ).map(
(column: string[] | string) => column.length (column: string[] | string) => column.length
)); ));
if (nRows == Infinity) { if (nRows === Infinity) {
// we only have single string columns // we only have single string columns
nRows = 1; nRows = 1;
} }

View File

@@ -11,6 +11,136 @@
<layout class="QVBoxLayout" name="layout_main"> <layout class="QVBoxLayout" name="layout_main">
<item> <item>
<widget class="QTabWidget" name="tabContainer"> <widget class="QTabWidget" name="tabContainer">
<widget class="QWidget" name="tab_behavior">
<attribute name="title">
<string>Behavior</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="kcfg_untileOnDrag">
<property name="text">
<string>Un-tile windows by dragging them</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
<property name="text">
<string>Stack columns by default</string>
</property>
<property name="toolTip">
<string>New columns start in stacked mode (one window in the column visible, others shaded). Not supported on Wayland.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
<property name="text">
<string>Resize neighbor column on edge resize</string>
</property>
<property name="toolTip">
<string>When resizing a column by dragging its edge, also inversely resize the column on the other side of the edge</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_reMaximize">
<property name="text">
<string>Re-maximize tiled windows</string>
</property>
<property name="toolTip">
<string>Restore maximized and full-screen states of tiled windows on focus</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_skipSwitcher">
<property name="text">
<string>Tiled windows skip switcher</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Scrolling mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_scrollingLazy">
<property name="text">
<string>Only scroll as necessary</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingCentered">
<property name="text">
<string>Center focused column</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingGrouped">
<property name="text">
<string>Center visible columns</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Layering mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_tiledKeepBelow">
<property name="text">
<string>Keep tiled windows below</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_floatingKeepAbove">
<property name="text">
<string>Keep floating windows above</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="spacer_footer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_parameters"> <widget class="QWidget" name="tab_parameters">
<attribute name="title"> <attribute name="title">
<string>Parameters</string> <string>Parameters</string>
@@ -143,14 +273,14 @@
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_overscroll"> <widget class="QLabel" name="label_manualScrollStep">
<property name="text"> <property name="text">
<string>Overscroll amount:</string> <string>Manual scroll step size:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="6" column="1">
<widget class="QSpinBox" name="kcfg_overscroll"> <widget class="QSpinBox" name="kcfg_manualScrollStep">
<property name="suffix"> <property name="suffix">
<string> px</string> <string> px</string>
</property> </property>
@@ -164,14 +294,14 @@
</item> </item>
<item row="7" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_manualScrollStep"> <widget class="QLabel" name="label_manualResizeStep">
<property name="text"> <property name="text">
<string>Manual scroll step size:</string> <string>Manual resize step size:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="7" column="1">
<widget class="QSpinBox" name="kcfg_manualScrollStep"> <widget class="QSpinBox" name="kcfg_manualResizeStep">
<property name="suffix"> <property name="suffix">
<string> px</string> <string> px</string>
</property> </property>
@@ -207,136 +337,6 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_behavior">
<attribute name="title">
<string>Behavior</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="kcfg_untileOnDrag">
<property name="text">
<string>Un-tile windows by dragging them</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
<property name="text">
<string>Stack columns by default</string>
</property>
<property name="toolTip">
<string>New columns start in stacked mode (one window in the column visible, others shaded). Not supported on Wayland.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
<property name="text">
<string>Resize neighbor column on edge resize</string>
</property>
<property name="toolTip">
<string>When resizing a column by dragging its edge, also inversely resize the column on the other side of the edge</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_reMaximize">
<property name="text">
<string>Re-maximize tiled windows</string>
</property>
<property name="toolTip">
<string>Restore maximized and full-screen states of tiled windows on focus</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_skipSwitcher">
<property name="text">
<string>Tiled windows skip switcher</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Scrolling mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_scrollingLazy">
<property name="text">
<string>Only scroll as necessary</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingCentered">
<property name="text">
<string>Center focused column</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingGrouped">
<property name="text">
<string>Prevent needlessly obscuring columns</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Layering mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_tiledKeepBelow">
<property name="text">
<string>Keep tiled windows below</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_floatingKeepAbove">
<property name="text">
<string>Keep floating windows above</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="spacer_footer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</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>

View File

@@ -1,7 +1,7 @@
{ {
"KPlugin": { "KPlugin": {
"Name": "Karousel", "Name": "Karousel",
"Description": "Manual columnar tiling extension for KWin", "Description": "Scrollable tiling extension for KWin",
"Icon": "preferences-system-windows", "Icon": "preferences-system-windows",
"Authors": [{ "Authors": [{
"Email": "peter.fajdiga@gmail.com", "Email": "peter.fajdiga@gmail.com",
@@ -9,7 +9,7 @@
}], }],
"Id": "karousel", "Id": "karousel",
"ServiceTypes": ["KWin/Script"], "ServiceTypes": ["KWin/Script"],
"Version": "0.6", "Version": "0.7.2",
"License": "GPLv3", "License": "GPLv3",
"Website": "https://github.com/peterfajdiga/karousel", "Website": "https://github.com/peterfajdiga/karousel",
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues" "BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"

View File

@@ -2,7 +2,7 @@ namespace Actions {
export function init(world: World, config: Config) { export function init(world: World, config: Config) {
return { return {
focusLeft: () => { focusLeft: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const prevColumn = grid.getPrevColumn(column); const prevColumn = grid.getPrevColumn(column);
if (prevColumn === null) { if (prevColumn === null) {
return; return;
@@ -12,7 +12,7 @@ namespace Actions {
}, },
focusRight: () => { focusRight: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const nextColumn = grid.getNextColumn(column); const nextColumn = grid.getNextColumn(column);
if (nextColumn === null) { if (nextColumn === null) {
return; return;
@@ -22,7 +22,7 @@ namespace Actions {
}, },
focusUp: () => { focusUp: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const prevWindow = column.getPrevWindow(window); const prevWindow = column.getPrevWindow(window);
if (prevWindow === null) { if (prevWindow === null) {
return; return;
@@ -32,7 +32,7 @@ namespace Actions {
}, },
focusDown: () => { focusDown: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const nextWindow = column.getNextWindow(window); const nextWindow = column.getNextWindow(window);
if (nextWindow === null) { if (nextWindow === null) {
return; return;
@@ -64,7 +64,7 @@ namespace Actions {
}, },
windowMoveLeft: () => { windowMoveLeft: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
if (column.getWindowCount() === 1) { if (column.getWindowCount() === 1) {
// move from own column into existing column // move from own column into existing column
const prevColumn = grid.getPrevColumn(column); const prevColumn = grid.getPrevColumn(column);
@@ -82,7 +82,7 @@ namespace Actions {
}, },
windowMoveRight: () => { windowMoveRight: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
if (column.getWindowCount() === 1) { if (column.getWindowCount() === 1) {
// move from own column into existing column // move from own column into existing column
const nextColumn = grid.getNextColumn(column); const nextColumn = grid.getNextColumn(column);
@@ -101,27 +101,27 @@ namespace Actions {
windowMoveUp: () => { windowMoveUp: () => {
// TODO (optimization): only arrange moved windows // TODO (optimization): only arrange moved windows
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveWindowUp(window); column.moveWindowUp(window);
}); });
}, },
windowMoveDown: () => { windowMoveDown: () => {
// TODO (optimization): only arrange moved windows // TODO (optimization): only arrange moved windows
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveWindowDown(window); column.moveWindowDown(window);
}); });
}, },
windowMoveStart: () => { windowMoveStart: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const newColumn = new Column(grid, null); const newColumn = new Column(grid, null);
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
}); });
}, },
windowMoveEnd: () => { windowMoveEnd: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const newColumn = new Column(grid, grid.getLastColumn()); const newColumn = new Column(grid, grid.getLastColumn());
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
}); });
@@ -135,44 +135,44 @@ namespace Actions {
}, },
columnMoveLeft: () => { columnMoveLeft: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.moveColumnLeft(column); grid.moveColumnLeft(column);
}); });
}, },
columnMoveRight: () => { columnMoveRight: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.moveColumnRight(column); grid.moveColumnRight(column);
}); });
}, },
columnMoveStart: () => { columnMoveStart: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveAfter(null); column.moveAfter(null);
}); });
}, },
columnMoveEnd: () => { columnMoveEnd: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveAfter(grid.getLastColumn()); column.moveAfter(grid.getLastColumn());
}); });
}, },
columnToggleStacked: () => { columnToggleStacked: () => {
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
column.toggleStacked(); column.toggleStacked();
}); });
}, },
columnWidthIncrease: () => { columnWidthIncrease: () => {
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
grid.increaseColumnWidth(column); config.columnResizer.increaseWidth(column, config.manualResizeStep);
}); });
}, },
columnWidthDecrease: () => { columnWidthDecrease: () => {
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
grid.decreaseColumnWidth(column); config.columnResizer.decreaseWidth(column, config.manualResizeStep);
}); });
}, },
@@ -213,7 +213,7 @@ namespace Actions {
}, },
gridScrollFocused: () => { gridScrollFocused: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.desktop.scrollCenterRange(column); grid.desktop.scrollCenterRange(column);
}) })
}, },
@@ -261,17 +261,17 @@ namespace Actions {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null) { if (targetColumn === null) {
return null; return;
} }
targetColumn.focus(); targetColumn.focus();
}); });
}, },
windowMoveToColumn: (columnIndex: number) => { windowMoveToColumn: (columnIndex: number) => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null) { if (targetColumn === null) {
return null; return;
} }
window.moveToColumn(targetColumn); window.moveToColumn(targetColumn);
grid.desktop.autoAdjustScroll(); grid.desktop.autoAdjustScroll();
@@ -279,10 +279,10 @@ namespace Actions {
}, },
columnMoveToColumn: (columnIndex: number) => { columnMoveToColumn: (columnIndex: number) => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null || targetColumn === column) { if (targetColumn === null || targetColumn === column) {
return null; return;
} }
if (targetColumn.isAfter(column)) { if (targetColumn.isAfter(column)) {
column.moveAfter(targetColumn); column.moveAfter(targetColumn);
@@ -325,5 +325,12 @@ namespace Actions {
export type Config = { export type Config = {
manualScrollStep: number, manualScrollStep: number,
manualResizeStep: number,
columnResizer: ColumnResizer,
}; };
export type ColumnResizer = {
increaseWidth(column: Column, step: number): void,
decreaseWidth(column: Column, step: number): void,
}
} }

View File

@@ -1,10 +0,0 @@
enum ClientAreaOption {
PlacementArea,
MovementArea,
MaximizeArea,
MaximizeFullArea,
FullScreenArea,
WorkArea,
FullArea,
ScreenArea,
}

View File

@@ -0,0 +1,96 @@
class ContextualResizer {
public increaseWidth(column: Column, step: number) {
const grid = column.grid;
const desktop = grid.desktop;
const visibleRange = desktop.getCurrentVisibleRange();
if(!column.isVisible(visibleRange, true) || column.getWidth() >= column.getMaxWidth()) {
return;
}
let leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
let rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
if (leftVisibleColumn === null || rightVisibleColumn === null) {
console.assert(false); // should at least see self
return;
}
const leftSpace = leftVisibleColumn.getLeft() - visibleRange.getLeft();
const rightSpace = visibleRange.getRight() - rightVisibleColumn.getRight();
const newWidth = ContextualResizer.findNextStep(
[
visibleRange.getWidth(),
column.getWidth() + step,
column.getWidth() + leftSpace + rightSpace,
column.getWidth() + leftSpace + rightSpace + leftVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
column.getWidth() + leftSpace + rightSpace + rightVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
],
width => width - column.getWidth(),
)
if (newWidth === undefined) {
return;
}
column.setWidth(newWidth, true);
desktop.scrollCenterVisible(column);
}
public decreaseWidth(column: Column, step: number) {
const grid = column.grid;
const desktop = grid.desktop;
const visibleRange = desktop.getCurrentVisibleRange();
if(!column.isVisible(visibleRange, true) || column.getWidth() <= column.getMinWidth()) {
return;
}
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
if (leftVisibleColumn === null || rightVisibleColumn === null) {
console.assert(false); // should at least see self
return;
}
let leftOffScreenColumn = grid.getPrevColumn(leftVisibleColumn);
if (leftOffScreenColumn === column) {
leftOffScreenColumn = null;
}
let rightOffScreenColumn = grid.getNextColumn(rightVisibleColumn);
if (rightOffScreenColumn === column) {
rightOffScreenColumn = null;
}
const visibleColumnsWidth = rightVisibleColumn.getRight() - leftVisibleColumn.getLeft();
const unusedWidth = visibleRange.getWidth() - visibleColumnsWidth;
const leftOffScreen = leftOffScreenColumn === null ? 0 : leftOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
const rightOffScreen = rightOffScreenColumn === null ? 0 : rightOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
const newWidth = ContextualResizer.findNextStep(
[
visibleRange.getWidth(),
column.getWidth() - step,
column.getWidth() - leftOffScreen,
column.getWidth() - rightOffScreen,
],
width => column.getWidth() - width,
)
if (newWidth === undefined) {
return;
}
column.setWidth(newWidth, true);
desktop.scrollCenterVisible(column);
}
private static findNextStep(steps: number[], evaluate: (step: number) => number) {
let bestScore = Infinity;
let bestStep = undefined;
for (const step of steps) {
const score = evaluate(step);
if (score > 0 && score < bestScore) {
bestScore = score;
bestStep = step;
}
}
return bestStep;
}
}

View File

@@ -0,0 +1,9 @@
class RawResizer {
public increaseWidth(column: Column, step: number) {
column.adjustWidth(step, true);
}
public decreaseWidth(column: Column, step: number) {
column.adjustWidth(-step, true);
}
}

View File

@@ -1,13 +1,5 @@
class ScrollerCentered { class CenterClamper {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollCenterRange(column);
}
public clampScrollX(desktop: Desktop, x: number) { public clampScrollX(desktop: Desktop, x: number) {
return ScrollerCentered.clampScrollX(desktop, x);
}
public static clampScrollX(desktop: Desktop, x: number) {
const firstColumn = desktop.grid.getFirstColumn(); const firstColumn = desktop.grid.getFirstColumn();
if (firstColumn === null) { if (firstColumn === null) {
return 0; return 0;

View File

@@ -1,8 +1,4 @@
class ScrollerLazy { class EdgeClamper {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollToRange(column);
}
public clampScrollX(desktop: Desktop, x: number) { public clampScrollX(desktop: Desktop, x: number) {
let minScroll = 0; let minScroll = 0;
let maxScroll = desktop.grid.getWidth() - desktop.tilingArea.width; let maxScroll = desktop.grid.getWidth() - desktop.tilingArea.width;

View File

@@ -0,0 +1,5 @@
class CenteredScroller {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollCenterRange(column);
}
}

View File

@@ -0,0 +1,5 @@
class GroupedScroller {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollCenterVisible(column);
}
}

View File

@@ -0,0 +1,5 @@
class LazyScroller {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollIntoView(column);
}
}

View File

@@ -5,8 +5,8 @@ type Config = {
gapsOuterRight: number, gapsOuterRight: number,
gapsInnerHorizontal: number, gapsInnerHorizontal: number,
gapsInnerVertical: number, gapsInnerVertical: number,
overscroll: number,
manualScrollStep: number, manualScrollStep: number,
manualResizeStep: number,
offScreenOpacity: number, offScreenOpacity: number,
untileOnDrag: boolean, untileOnDrag: boolean,
stackColumnsByDefault: boolean, stackColumnsByDefault: boolean,

View File

@@ -72,103 +72,103 @@ const defaultWindowRules = `[
const configDef = [ const configDef = [
{ {
"name": "gapsOuterTop", name: "gapsOuterTop",
"type": "UInt", type: "UInt",
"default": 18 default: 18,
}, },
{ {
"name": "gapsOuterBottom", name: "gapsOuterBottom",
"type": "UInt", type: "UInt",
"default": 18 default: 18,
}, },
{ {
"name": "gapsOuterLeft", name: "gapsOuterLeft",
"type": "UInt", type: "UInt",
"default": 18 default: 18,
}, },
{ {
"name": "gapsOuterRight", name: "gapsOuterRight",
"type": "UInt", type: "UInt",
"default": 18 default: 18,
}, },
{ {
"name": "gapsInnerHorizontal", name: "gapsInnerHorizontal",
"type": "UInt", type: "UInt",
"default": 18 default: 18,
}, },
{ {
"name": "gapsInnerVertical", name: "gapsInnerVertical",
"type": "UInt", type: "UInt",
"default": 18 default: 18,
}, },
{ {
"name": "overscroll", name: "manualScrollStep",
"type": "UInt", type: "UInt",
"default": 0 default: 200,
}, },
{ {
"name": "manualScrollStep", name: "manualResizeStep",
"type": "UInt", type: "UInt",
"default": 200 default: 600,
}, },
{ {
"name": "offScreenOpacity", name: "offScreenOpacity",
"type": "UInt", type: "UInt",
"default": 100 default: 100,
}, },
{ {
"name": "untileOnDrag", name: "untileOnDrag",
"type": "Bool", type: "Bool",
"default": true default: true,
}, },
{ {
"name": "stackColumnsByDefault", name: "stackColumnsByDefault",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "resizeNeighborColumn", name: "resizeNeighborColumn",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "reMaximize", name: "reMaximize",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "skipSwitcher", name: "skipSwitcher",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "scrollingLazy", name: "scrollingLazy",
"type": "Bool", type: "Bool",
"default": true default: true,
}, },
{ {
"name": "scrollingCentered", name: "scrollingCentered",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "scrollingGrouped", name: "scrollingGrouped",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "tiledKeepBelow", name: "tiledKeepBelow",
"type": "Bool", type: "Bool",
"default": true default: true,
}, },
{ {
"name": "floatingKeepAbove", name: "floatingKeepAbove",
"type": "Bool", type: "Bool",
"default": false default: false,
}, },
{ {
"name": "windowRules", name: "windowRules",
"type": "String", type: "String",
"default": defaultWindowRules default: defaultWindowRules,
} }
]; ];

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

@@ -1,39 +1,44 @@
declare const KWin: { declare const KWin: {
// Functions
readConfig(key: string, defaultValue: any): any; readConfig(key: string, defaultValue: any): any;
registerShortcut(name: string, description: string, keySequence: string, callback: () => void): void; registerShortcut(name: string, description: string, keySequence: string, callback: () => void): void;
}; };
declare const workspace: { declare const workspace: {
// Read-write Properties
readonly desktops: number; readonly desktops: number;
readonly currentDesktop: number; readonly currentDesktop: number;
readonly currentActivity: string; readonly currentActivity: string;
// Read-write Properties
activeClient: KwinClient; activeClient: KwinClient;
// Signals readonly currentDesktopChanged: QSignal<[]>
currentDesktopChanged: QSignal<[oldDesktopNumber: number]> readonly clientAdded: QSignal<[KwinClient]>;
clientAdded: QSignal<[KwinClient]>; readonly clientRemoved: QSignal<[KwinClient]>;
clientRemoved: QSignal<[KwinClient]>; readonly clientMinimized: QSignal<[KwinClient]>;
clientMinimized: QSignal<[KwinClient]>; readonly clientUnminimized: QSignal<[KwinClient]>;
clientUnminimized: QSignal<[KwinClient]>; readonly clientMaximizeSet: QSignal<[KwinClient, horizontally: boolean, vertically: boolean]>;
clientMaximizeSet: QSignal<[KwinClient, horizontally: boolean, vertically: boolean]>; readonly clientActivated: QSignal<[KwinClient]>;
clientActivated: QSignal<[KwinClient]>; readonly numberDesktopsChanged: QSignal<[]>;
numberDesktopsChanged: QSignal<[oldNumberOfVirtualDesktops: number]>; readonly currentActivityChanged: QSignal<[]>;
currentActivityChanged: QSignal<[newActivity: string]>; readonly virtualScreenSizeChanged: QSignal<[]>;
virtualScreenSizeChanged: QSignal<[void]>;
// Functions
clientArea(option: ClientAreaOption, screenNumber: number, desktopNumber: number); clientArea(option: ClientAreaOption, screenNumber: number, desktopNumber: number);
clientList(): KwinClient[]; clientList(): KwinClient[];
}; };
type Tile = any; const enum ClientAreaOption {
PlacementArea,
MovementArea,
MaximizeArea,
MaximizeFullArea,
FullScreenArea,
WorkArea,
FullArea,
ScreenArea,
}
type Tile = unknown;
interface KwinClient { interface KwinClient {
// Read-only Properties
readonly shadeable: boolean; readonly shadeable: boolean;
readonly caption: string; readonly caption: string;
readonly minSize: QmlSize; readonly minSize: QmlSize;
@@ -47,9 +52,8 @@ interface KwinClient {
readonly dock: boolean; readonly dock: boolean;
readonly normalWindow: boolean; readonly normalWindow: boolean;
readonly managed: boolean; readonly managed: boolean;
opacity: number;
// Read-write Properties opacity: number;
fullScreen: boolean; fullScreen: boolean;
activities: string[]; // empty array means all activities activities: string[]; // empty array means all activities
skipSwitcher: boolean; skipSwitcher: boolean;
@@ -61,17 +65,15 @@ interface KwinClient {
desktop: number; // -1 means all desktops desktop: number; // -1 means all desktops
tile: Tile; tile: Tile;
// Signals readonly fullScreenChanged: QSignal<[]>;
fullScreenChanged: QSignal<[void]>; readonly desktopChanged: QSignal<[]>;
desktopChanged: QSignal<[void]>; readonly activitiesChanged: QSignal<[]>;
activitiesChanged: QSignal<[KwinClient]>; readonly captionChanged: QSignal<[]>;
captionChanged: QSignal<[void]>; readonly tileChanged: QSignal<[]>;
tileChanged: QSignal<[Tile]>; readonly moveResizedChanged: QSignal<[]>;
moveResizedChanged: QSignal<[void]>; readonly moveResizeCursorChanged: QSignal<[]>;
moveResizeCursorChanged: QSignal<[void]>; readonly clientStartUserMovedResized: QSignal<[]>;
clientStartUserMovedResized: QSignal<[void]>; readonly frameGeometryChanged: QSignal<[KwinClient, oldGeometry: QmlRect]>;
frameGeometryChanged: QSignal<[KwinClient, oldGeometry: QmlRect]>;
// Functions
setMaximize(vertically: boolean, horizontally: boolean): void; setMaximize(vertically: boolean, horizontally: boolean): void;
} }

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

@@ -1,5 +1,6 @@
declare const console: { declare const console: {
log(...args: any[]); log(...args: any[]);
trace();
assert(boolean); assert(boolean);
}; };
@@ -35,7 +36,7 @@ type QSignal<T extends unknown[]> = {
type QmlTimer = { type QmlTimer = {
interval: number; interval: number;
triggered: QSignal<[void]>; readonly triggered: QSignal<[]>;
restart(): void; restart(): void;
destroy(): void; destroy(): void;
}; };

View File

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

View File

@@ -53,7 +53,12 @@ function registerNumKeyBindings(name: string, description: string, modifiers: st
} }
function registerKeyBindings(world: World, config: Config) { function registerKeyBindings(world: World, config: Config) {
const actions = Actions.init(world, config); const actions = Actions.init(world, {
manualScrollStep: config.manualScrollStep,
manualResizeStep: config.manualResizeStep,
columnResizer: config.scrollingCentered ? new RawResizer() : new ContextualResizer(),
});
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]);
} }

View File

@@ -92,16 +92,17 @@ class Column {
public 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; if (width === this.width) {
return;
}
this.width = width; this.width = width;
if (setPreferred) { if (setPreferred) {
for (const window of this.windows.iterator()) { for (const window of this.windows.iterator()) {
window.client.preferredWidth = width; window.client.preferredWidth = width;
} }
} }
if (width !== oldWidth) { this.grid.onColumnWidthChanged(this);
this.grid.onColumnWidthChanged(this, oldWidth, width);
}
} }
public adjustWidth(widthDelta: number, setPreferred: boolean) { public adjustWidth(widthDelta: number, setPreferred: boolean) {

View File

@@ -55,10 +55,9 @@ class Desktop {
) )
} }
// calculates a Range that scrolls the contained Range into view public scrollIntoView(range: Desktop.Range) {
public calculateVisibleRange(containedRange: Desktop.Range) { const left = range.getLeft();
const left = containedRange.getLeft(); const right = range.getRight();
const right = containedRange.getRight();
const initialVisibleRange = this.getCurrentVisibleRange(); const initialVisibleRange = this.getCurrentVisibleRange();
let targetScrollX: number; let targetScrollX: number;
@@ -67,26 +66,10 @@ class Desktop {
} else if (right > initialVisibleRange.getRight()) { } else if (right > initialVisibleRange.getRight()) {
targetScrollX = right - this.tilingArea.width; targetScrollX = right - this.tilingArea.width;
} else { } else {
return this.getVisibleRange(this.clampScrollX(this.scrollX)); targetScrollX = initialVisibleRange.getLeft();
} }
const overscroll = this.getTargetOverscroll(targetScrollX, left < initialVisibleRange.getLeft()); this.setScroll(targetScrollX, false);
return this.getVisibleRange(this.clampScrollX(targetScrollX + overscroll));
}
private getTargetOverscroll(targetScrollX: number, scrollLeft: boolean) {
if (this.config.overscroll === 0) {
return 0;
}
const visibleColumnsWidth = this.grid.getVisibleColumnsWidth(this.getVisibleRange(targetScrollX), true);
const remainingSpace = this.tilingArea.width - visibleColumnsWidth;
const overscrollX = Math.min(this.config.overscroll, Math.round(remainingSpace / 2));
const direction = scrollLeft ? -1 : 1;
return overscrollX * direction;
}
public scrollToRange(range: Desktop.Range) {
this.setScroll(this.calculateVisibleRange(range).getLeft(), true);
} }
public scrollCenterRange(range: Desktop.Range) { public scrollCenterRange(range: Desktop.Range) {
@@ -95,6 +78,13 @@ class Desktop {
this.adjustScroll(Math.round(windowCenter - screenCenter), false); this.adjustScroll(Math.round(windowCenter - screenCenter), false);
} }
public scrollCenterVisible(focusedColumn: Column) {
const columnRange = new Desktop.ColumnRange(focusedColumn);
const visibleRange = this.getCurrentVisibleRange();
columnRange.addNeighbors(visibleRange, this.grid.config.gapsInnerHorizontal);
this.scrollCenterRange(columnRange);
}
public autoAdjustScroll() { public autoAdjustScroll() {
const focusedColumn = this.grid.getLastFocusedColumn(); const focusedColumn = this.grid.getLastFocusedColumn();
if (focusedColumn === null || focusedColumn.grid !== this.grid) { if (focusedColumn === null || focusedColumn.grid !== this.grid) {
@@ -119,7 +109,7 @@ class Desktop {
} }
private clampScrollX(x: number) { private clampScrollX(x: number) {
return this.config.scroller.clampScrollX(this, x); return this.config.clamper.clampScrollX(this, x);
} }
public setScroll(x: number, force: boolean) { public setScroll(x: number, force: boolean) {
@@ -141,18 +131,32 @@ class Desktop {
let remainingWidth = this.tilingArea.width - (visibleColumns.length-1) * this.grid.config.gapsInnerHorizontal; let remainingWidth = this.tilingArea.width - (visibleColumns.length-1) * this.grid.config.gapsInnerHorizontal;
let remainingColumns = visibleColumns.length; let remainingColumns = visibleColumns.length;
for (const column of visibleColumns) {
const columnWidth = Math.round(remainingWidth / remainingColumns); const minWidths = visibleColumns.map(column => column.getMinWidth()).sort((a, b) => b - a);
column.setWidth(columnWidth, true); for (const minWidth of minWidths) {
remainingWidth -= columnWidth; if (minWidth > remainingWidth / remainingColumns) {
remainingColumns--; remainingWidth -= minWidth;
remainingColumns--;
}
} }
const targetVisibleRange = Desktop.RangeImpl.fromRanges( const avgWidth = remainingWidth / remainingColumns;
for (const column of visibleColumns) {
const minWidth = column.getMinWidth();
if (minWidth > avgWidth) {
column.setWidth(minWidth, true);
} else {
const columnWidth = Math.round(remainingWidth / remainingColumns);
column.setWidth(columnWidth, true);
remainingWidth -= column.getWidth();
remainingColumns--;
}
}
this.scrollCenterRange(Desktop.RangeImpl.fromRanges(
visibleColumns[0], visibleColumns[0],
visibleColumns[visibleColumns.length - 1], visibleColumns[visibleColumns.length - 1],
); ));
this.setScroll(this.calculateVisibleRange(targetVisibleRange).getLeft(), false);
} }
public arrange() { public arrange() {
@@ -187,8 +191,8 @@ namespace Desktop {
marginBottom: number, marginBottom: number,
marginLeft: number, marginLeft: number,
marginRight: number, marginRight: number,
overscroll: number,
scroller: Desktop.Scroller, scroller: Desktop.Scroller,
clamper: Desktop.Clamper,
}; };
export type Range = { export type Range = {
@@ -225,8 +229,83 @@ namespace Desktop {
} }
} }
export class ColumnRange {
private left: Column;
private right: Column;
private width: number;
constructor(initialColumn: Column) {
this.left = initialColumn;
this.right = initialColumn;
this.width = initialColumn.getWidth();
}
public addNeighbors(visibleRange: Desktop.Range, gap: number) {
const grid = this.left.grid;
const columnRange = this;
function canFit(column: Column) {
return columnRange.width + gap + column.getWidth() <= visibleRange.getWidth();
}
function isUsable(column: Column|null) {
return column !== null && canFit(column);
}
let leftColumn = grid.getPrevColumn(this.left);
let rightColumn = grid.getNextColumn(this.right);
function checkColumns() {
if (!isUsable(leftColumn)) {
leftColumn = null;
}
if (!isUsable(rightColumn)) {
rightColumn = null;
}
}
checkColumns();
const visibleCenter = visibleRange.getLeft() + visibleRange.getWidth() / 2;
while (leftColumn !== null || rightColumn !== null) {
const leftToCenter = leftColumn === null ? Infinity : Math.abs(leftColumn.getLeft() - visibleCenter);
const rightToCenter = rightColumn === null ? Infinity : Math.abs(rightColumn.getRight() - visibleCenter);
if (leftToCenter < rightToCenter) {
this.addLeft(leftColumn!, gap);
leftColumn = grid.getPrevColumn(leftColumn!);
} else {
this.addRight(rightColumn!, gap);
rightColumn = grid.getNextColumn(rightColumn!);
}
checkColumns();
}
}
public addLeft(column: Column, gap: number) {
this.left = column;
this.width += column.getWidth() + gap;
}
public addRight(column: Column, gap: number) {
this.right = column;
this.width += column.getWidth() + gap;
}
public getLeft() {
return this.left.getLeft();
}
public getRight() {
return this.right.getRight();
}
public getWidth() {
return this.width;
}
}
export type Scroller = { export type Scroller = {
scrollToColumn(desktop: Desktop, column: Column): void; scrollToColumn(desktop: Desktop, column: Column): void;
}
export type Clamper = {
clampScrollX(desktop: Desktop, x: number): number; clampScrollX(desktop: Desktop, x: number): number;
} }
} }

View File

@@ -43,6 +43,10 @@ class Grid {
return this.width; return this.width;
} }
public isUserResizing() {
return this.userResize;
}
public getPrevColumn(column: Column) { public getPrevColumn(column: Column) {
return this.columns.getPrev(column); return this.columns.getPrev(column);
} }
@@ -134,93 +138,6 @@ class Grid {
return width; return width;
} }
public increaseColumnWidth(column: Column) {
const visibleRange = this.desktop.calculateVisibleRange(column);
if(!column.isVisible(visibleRange, true) || column.getWidth() >= column.getMaxWidth()) {
return;
}
let leftVisibleColumn = this.getLeftmostVisibleColumn(visibleRange, true);
let rightVisibleColumn = this.getRightmostVisibleColumn(visibleRange, true);
if (leftVisibleColumn === null || rightVisibleColumn === null) {
console.assert(false);
return;
}
const leftSpace = leftVisibleColumn.getLeft() - visibleRange.getLeft();
const rightSpace = visibleRange.getRight() - rightVisibleColumn.getRight();
if (leftSpace + rightSpace > 0) {
column.adjustWidth(leftSpace + rightSpace, true);
} else {
// left and right columns are touching the screen's edges
const leftSpace = leftVisibleColumn === column ? Infinity : leftVisibleColumn.getWidth() + this.config.gapsInnerHorizontal;
const rightSpace = rightVisibleColumn === column ? Infinity : rightVisibleColumn.getWidth() + this.config.gapsInnerHorizontal;
if (leftSpace < rightSpace) {
column.adjustWidth(leftSpace, true);
leftVisibleColumn = this.getNextColumn(leftVisibleColumn)!;
} else {
column.adjustWidth(rightSpace, true);
rightVisibleColumn = this.getPrevColumn(rightVisibleColumn)!;
}
}
this.desktop.scrollCenterRange(Desktop.RangeImpl.fromRanges(leftVisibleColumn, rightVisibleColumn));
}
public decreaseColumnWidth(column: Column) {
const visibleRange = this.desktop.calculateVisibleRange(column);
if (!column.isVisible(visibleRange, true)) {
return;
}
if (this.width <= visibleRange.getWidth()) {
column.setWidth(Math.round(column.getWidth() / 2), true);
return;
}
const leftVisibleColumn = this.getLeftmostVisibleColumn(visibleRange, true);
const rightVisibleColumn = this.getRightmostVisibleColumn(visibleRange, true);
if (leftVisibleColumn === null || rightVisibleColumn === null) {
console.assert(false);
return;
}
let leftOffScreenColumn = this.getPrevColumn(leftVisibleColumn);
if (leftOffScreenColumn === column) {
leftOffScreenColumn = null;
}
let rightOffScreenColumn = this.getNextColumn(rightVisibleColumn);
if (rightOffScreenColumn === column) {
rightOffScreenColumn = null;
}
if (leftOffScreenColumn === null && rightOffScreenColumn === null) {
console.assert(false);
return;
}
const leftInvisibleWidth = leftOffScreenColumn === null ? Infinity : visibleRange.getLeft() - leftOffScreenColumn.getLeft();
const rightInvisibleWidth = rightOffScreenColumn === null ? Infinity : rightOffScreenColumn.getRight() - visibleRange.getRight();
const leftSpace = leftVisibleColumn.getLeft() - visibleRange.getLeft();
const rightSpace = visibleRange.getRight() - rightVisibleColumn.getRight();
if (leftInvisibleWidth < rightInvisibleWidth) {
const deltaWidth = rightSpace - leftInvisibleWidth;
column.adjustWidth(deltaWidth, true);
console.assert(leftOffScreenColumn !== null);
const newVisibleWidth = rightVisibleColumn.getRight() - leftOffScreenColumn!.getLeft();
const leftVisibleColumn = newVisibleWidth <= visibleRange.getWidth() ? leftOffScreenColumn! : this.getNextColumn(leftOffScreenColumn!)!;
this.desktop.scrollCenterRange(Desktop.RangeImpl.fromRanges(leftVisibleColumn, rightVisibleColumn));
} else {
const deltaWidth = leftSpace - rightInvisibleWidth;
column.adjustWidth(deltaWidth, true);
console.assert(rightOffScreenColumn !== null);
const newVisibleWidth = rightOffScreenColumn!.getRight() - leftVisibleColumn.getLeft();
const rightVisibleColumn = newVisibleWidth <= visibleRange.getWidth() ? rightOffScreenColumn! : this.getPrevColumn(rightOffScreenColumn!)!;
this.desktop.scrollCenterRange(Desktop.RangeImpl.fromRanges(leftVisibleColumn, rightVisibleColumn));
}
}
public arrange(x: number, visibleRange: Range) { public arrange(x: number, visibleRange: Range) {
for (const column of this.columns.iterator()) { for (const column of this.columns.iterator()) {
column.arrange(x, visibleRange, this.userResize); column.arrange(x, visibleRange, this.userResize);
@@ -272,13 +189,13 @@ class Grid {
this.desktop.autoAdjustScroll(); this.desktop.autoAdjustScroll();
} }
public onColumnWidthChanged(column: Column, oldWidth: number, width: number) { public onColumnWidthChanged(column: Column) {
const nextColumn = this.columns.getNext(column); const nextColumn = this.columns.getNext(column);
this.columnsSetX(nextColumn); this.columnsSetX(nextColumn);
this.desktop.onLayoutChanged();
if (!this.userResize) { if (!this.userResize) {
this.desktop.autoAdjustScroll(); this.desktop.autoAdjustScroll();
} }
this.desktop.onLayoutChanged();
} }
public onColumnFocused(column: Column) { public onColumnFocused(column: Column) {

View File

@@ -1,87 +0,0 @@
class ScrollerGrouped {
private readonly layoutConfig: LayoutConfig;
constructor(layoutConfig: LayoutConfig) {
this.layoutConfig = layoutConfig;
}
public scrollToColumn(desktop: Desktop, column: Column) {
const columnRange = new ScrollerGrouped.ColumnRange(column);
const visibleRange = desktop.getCurrentVisibleRange();
columnRange.addNeighbors(visibleRange, this.layoutConfig.gapsInnerHorizontal, true);
columnRange.addNeighbors(visibleRange, this.layoutConfig.gapsInnerHorizontal, false);
desktop.scrollCenterRange(columnRange);
}
public clampScrollX(desktop: Desktop, x: number) {
return ScrollerCentered.clampScrollX(desktop, x);
}
}
namespace ScrollerGrouped {
import Range = Desktop.Range;
export class ColumnRange {
private left: Column;
private right: Column;
private width: number;
constructor(initialColumn: Column) {
this.left = initialColumn;
this.right = initialColumn;
this.width = initialColumn.getWidth();
}
public addNeighbors(visibleRange: Range, gap: number, requireVisible: boolean) {
const grid = this.left.grid;
let leftColumn: Column|null = this.left;
while (true) {
leftColumn = grid.getPrevColumn(leftColumn);
if (
leftColumn === null ||
requireVisible && !leftColumn.isVisible(visibleRange, true) ||
this.width + gap + leftColumn.getWidth() > visibleRange.getWidth()
) {
break;
}
this.addLeft(leftColumn, gap);
}
let rightColumn: Column|null = this.right;
while (true) {
rightColumn = grid.getNextColumn(rightColumn);
if (
rightColumn === null ||
requireVisible && !rightColumn.isVisible(visibleRange, true) ||
this.width + gap + rightColumn.getWidth() > visibleRange.getWidth()
) {
break;
}
this.addRight(rightColumn, gap);
}
}
public addLeft(column: Column, gap: number) {
this.left = column;
this.width += column.getWidth() + gap;
}
public addRight(column: Column, gap: number) {
this.right = column;
this.width += column.getWidth() + gap;
}
public getLeft() {
return this.left.getLeft();
}
public getRight() {
return this.right.getRight();
}
public getWidth() {
return this.width;
}
}
}

View File

@@ -31,8 +31,9 @@ class WindowRuleEnforcer {
manager.connect(kwinClient.captionChanged, () => { manager.connect(kwinClient.captionChanged, () => {
const shouldTile = enforcer.shouldTile(kwinClient); const shouldTile = enforcer.shouldTile(kwinClient);
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
if (shouldTile) { const desktop = desktopManager.getDesktopForClient(kwinClient);
clientManager.tileClient(kwinClient); if (shouldTile && desktop !== undefined) {
clientManager.tileClient(kwinClient, desktop.grid);
} else { } else {
clientManager.untileClient(kwinClient); clientManager.untileClient(kwinClient);
} }
@@ -71,11 +72,11 @@ class WindowRuleEnforcer {
} }
private static joinRegexes(regexes: string[]) { private static joinRegexes(regexes: string[]) {
if (regexes.length == 0) { if (regexes.length === 0) {
return new RegExp(""); return new RegExp("");
} }
if (regexes.length == 1) { if (regexes.length === 1) {
return new RegExp("^" + regexes[0] + "$"); return new RegExp("^" + regexes[0] + "$");
} }

View File

@@ -38,7 +38,7 @@ function initWorkspaceSignalHandlers(world: World) {
if ((horizontally || vertically) && kwinClient.tile !== null) { if ((horizontally || vertically) && kwinClient.tile !== null) {
kwinClient.tile = null; kwinClient.tile = null;
} }
world.doIfTiled(kwinClient, false, (world, desktopManager, window, column, grid) => { world.doIfTiled(kwinClient, false, (clientManager, desktopManager, window, column, grid) => {
window.onMaximizedChanged(horizontally, vertically); window.onMaximizedChanged(horizontally, vertically);
}); });
}); });
@@ -60,7 +60,7 @@ function initWorkspaceSignalHandlers(world: World) {
world.do(() => {}); // re-arrange desktop world.do(() => {}); // re-arrange desktop
}); });
manager.connect(workspace.numberDesktopsChanged, (oldNumberOfVirtualDesktops: number) => { manager.connect(workspace.numberDesktopsChanged, () => {
world.updateDesktops(); world.updateDesktops();
}); });

View File

@@ -27,13 +27,13 @@ class ClientManager {
public addClient(kwinClient: KwinClient) { public addClient(kwinClient: KwinClient) {
console.assert(!this.hasClient(kwinClient)); console.assert(!this.hasClient(kwinClient));
const desktop = this.desktopManager.getDesktopForClient(kwinClient);
let constructState: (client: ClientWrapper) => ClientState.State; let constructState: (client: ClientWrapper) => ClientState.State;
if (kwinClient.dock) { if (kwinClient.dock) {
constructState = () => new ClientState.Docked(this.world, kwinClient); constructState = () => new ClientState.Docked(this.world, kwinClient);
} else if (this.windowRuleEnforcer.shouldTile(kwinClient)) { } else if (this.windowRuleEnforcer.shouldTile(kwinClient) && desktop !== undefined) {
const grid = this.desktopManager.getDesktopForClient(kwinClient).grid; constructState = (client: ClientWrapper) => new ClientState.Tiled(this.world, client, desktop.grid);
constructState = (client: ClientWrapper) => new ClientState.Tiled(this.world, client, grid);
} else { } else {
constructState = (client: ClientWrapper) => new ClientState.Floating(this.world, client, this.config, false); constructState = (client: ClientWrapper) => new ClientState.Floating(this.world, client, this.config, false);
} }
@@ -86,12 +86,16 @@ class ClientManager {
return; return;
} }
if (client.stateManager.getState() instanceof ClientState.TiledMinimized) { if (client.stateManager.getState() instanceof ClientState.TiledMinimized) {
const grid = this.desktopManager.getDesktopForClient(kwinClient).grid; const desktop = this.desktopManager.getDesktopForClient(kwinClient);
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false); if (desktop !== undefined) {
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, desktop.grid), false);
} else {
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, false), false);
}
} }
} }
public tileClient(kwinClient: KwinClient) { public tileClient(kwinClient: KwinClient, grid: Grid) {
const client = this.clientMap.get(kwinClient); const client = this.clientMap.get(kwinClient);
if (client === undefined) { if (client === undefined) {
return; return;
@@ -99,7 +103,6 @@ class ClientManager {
if (client.stateManager.getState() instanceof ClientState.Tiled) { if (client.stateManager.getState() instanceof ClientState.Tiled) {
return; return;
} }
const grid = this.desktopManager.getDesktopForClient(kwinClient).grid;
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false); client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
} }
@@ -147,8 +150,11 @@ class ClientManager {
const clientState = client.stateManager.getState(); const clientState = client.stateManager.getState();
if ((clientState instanceof ClientState.Floating || clientState instanceof ClientState.Pinned) && Clients.canTileEver(kwinClient)) { if ((clientState instanceof ClientState.Floating || clientState instanceof ClientState.Pinned) && Clients.canTileEver(kwinClient)) {
Clients.makeTileable(kwinClient); Clients.makeTileable(kwinClient);
const grid = this.desktopManager.getDesktopForClient(kwinClient).grid; const desktop = this.desktopManager.getDesktopForClient(kwinClient);
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false); if (desktop === undefined) {
return;
}
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, desktop.grid), false);
} else if (clientState instanceof ClientState.Tiled) { } else if (clientState instanceof ClientState.Tiled) {
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false); client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false);
} }

View File

@@ -33,8 +33,15 @@ class ClientWrapper {
// window is being manually resized, prevent fighting with the user // window is being manually resized, prevent fighting with the user
return; return;
} }
this.lastPlacement = Qt.rect(x, y, width, height); const clientWrapper = this; // workaround for bug in Qt5's JS engine
this.kwinClient.frameGeometry = this.lastPlacement; clientWrapper.lastPlacement = Qt.rect(x, y, width, height);
clientWrapper.kwinClient.frameGeometry = clientWrapper.lastPlacement;
if (clientWrapper.kwinClient.frameGeometry !== clientWrapper.lastPlacement) {
// frameGeometry assignment failed. This sometimes happens on Wayland
// when a window is off-screen, effectively making it stuck there.
clientWrapper.kwinClient.frameGeometry.x = x; // This makes it unstuck.
clientWrapper.kwinClient.frameGeometry = clientWrapper.lastPlacement;
}
}); });
} }

View File

@@ -39,7 +39,9 @@ class DesktopManager {
} }
public getDesktopForClient(kwinClient: KwinClient) { public getDesktopForClient(kwinClient: KwinClient) {
console.assert(kwinClient.activities.length === 1 && kwinClient.desktop > 0); if (kwinClient.activities.length !== 1 || kwinClient.desktop <= 0) {
return undefined;
}
return this.getDesktop(kwinClient.activities[0], kwinClient.desktop); return this.getDesktop(kwinClient.activities[0], kwinClient.desktop);
} }

View File

@@ -11,7 +11,7 @@ class World {
this.workspaceSignalManager = initWorkspaceSignalHandlers(this); this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
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 are taken into account by `workspace.clientArea`
const desktopManager = this.desktopManager; // workaround for bug in Qt5's JS engine const desktopManager = this.desktopManager; // workaround for bug in Qt5's JS engine
for (const desktop of desktopManager.desktops()) { for (const desktop of desktopManager.desktops()) {
desktop.onLayoutChanged(); desktop.onLayoutChanged();
@@ -40,11 +40,11 @@ class World {
marginBottom: config.gapsOuterBottom, marginBottom: config.gapsOuterBottom,
marginLeft: config.gapsOuterLeft, marginLeft: config.gapsOuterLeft,
marginRight: config.gapsOuterRight, marginRight: config.gapsOuterRight,
overscroll: config.overscroll, scroller: config.scrollingLazy ? new LazyScroller() :
scroller: config.scrollingLazy ? new ScrollerLazy() : config.scrollingCentered ? new CenteredScroller() :
config.scrollingCentered ? new ScrollerCentered() : config.scrollingGrouped ? new GroupedScroller() :
config.scrollingGrouped ? new ScrollerGrouped(layoutConfig) :
console.assert(false), console.assert(false),
clamper: config.scrollingLazy ? new EdgeClamper() : new CenterClamper(),
}, },
layoutConfig, layoutConfig,
workspace.currentActivity, workspace.currentActivity,

View File

@@ -54,7 +54,7 @@ namespace ClientState {
clientManager.pinClient(kwinClient); clientManager.pinClient(kwinClient);
}); });
} }
}) });
return manager; return manager;
} }

View File

@@ -68,7 +68,7 @@ namespace ClientState {
oldDesktopNumber = kwinClient.desktop; oldDesktopNumber = kwinClient.desktop;
}); });
manager.connect(kwinClient.activitiesChanged, (kwinClient: KwinClient) => { manager.connect(kwinClient.activitiesChanged, () => {
const desktops = kwinClient.desktop === -1 ? [] : [kwinClient.desktop]; const desktops = kwinClient.desktop === -1 ? [] : [kwinClient.desktop];
const changedActivities = oldActivities.length === 0 || kwinClient.activities.length === 0 ? const changedActivities = oldActivities.length === 0 || kwinClient.activities.length === 0 ?
[] : [] :

View File

@@ -33,23 +33,25 @@ namespace ClientState {
manager.connect(kwinClient.desktopChanged, () => { manager.connect(kwinClient.desktopChanged, () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
if (kwinClient.desktop === -1) { const desktop = desktopManager.getDesktopForClient(kwinClient);
if (desktop === undefined) {
// windows on all desktops are not supported // windows on all desktops are not supported
clientManager.untileClient(kwinClient); clientManager.untileClient(kwinClient);
return; return;
} }
Tiled.moveWindowToCorrectGrid(desktopManager, window); Tiled.moveWindowToGrid(window, desktop.grid);
}); });
}); });
manager.connect(kwinClient.activitiesChanged, () => { manager.connect(kwinClient.activitiesChanged, () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
if (kwinClient.activities.length !== 1) { const desktop = desktopManager.getDesktopForClient(kwinClient);
if (desktop === undefined) {
// windows on multiple activities are not supported // windows on multiple activities are not supported
clientManager.untileClient(kwinClient); clientManager.untileClient(kwinClient);
return; return;
} }
Tiled.moveWindowToCorrectGrid(desktopManager, window); Tiled.moveWindowToGrid(window, desktop.grid);
}); });
}) })
@@ -106,6 +108,7 @@ namespace ClientState {
if (kwinClient.resize) { if (kwinClient.resize) {
world.do(() => window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart)); world.do(() => window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart));
} else if ( } else if (
!window.column.grid.isUserResizing() &&
!client.isManipulatingGeometry(newGeometry) && !client.isManipulatingGeometry(newGeometry) &&
!Clients.isMaximizedGeometry(kwinClient) && !Clients.isMaximizedGeometry(kwinClient) &&
!Clients.isFullScreenGeometry(kwinClient) // not using `kwinClient.fullScreen` because it may not be set yet at this point !Clients.isFullScreenGeometry(kwinClient) // not using `kwinClient.fullScreen` because it may not be set yet at this point
@@ -130,17 +133,13 @@ namespace ClientState {
return manager; return manager;
} }
private static moveWindowToCorrectGrid(desktopManager: DesktopManager, window: Window) { private static moveWindowToGrid(window: Window, grid: Grid) {
const kwinClient = window.client.kwinClient; if (grid === window.column.grid) {
// window already on the given grid
const oldGrid = window.column.grid;
const newGrid = desktopManager.getDesktopForClient(kwinClient).grid;
if (oldGrid === newGrid) {
// window already on the correct grid
return; return;
} }
const newColumn = new Column(newGrid, newGrid.getLastFocusedColumn() ?? newGrid.getLastColumn()); const newColumn = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
} }