Compare commits
4 Commits
v0.14
...
per-screen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aec5750dc0 | ||
|
|
6784259c12 | ||
|
|
23186bbe91 | ||
|
|
d7df3901e2 |
19
.github/ISSUE_TEMPLATE/1-compatibility-issue.md
vendored
19
.github/ISSUE_TEMPLATE/1-compatibility-issue.md
vendored
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Compatibility issue
|
|
||||||
about: Report an issue with a specific application or window
|
|
||||||
title: "[Compatibility]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Karousel version:
|
|
||||||
Plasma version:
|
|
||||||
X11 / Wayland:
|
|
||||||
|
|
||||||
Window class:
|
|
||||||
Window caption (title):
|
|
||||||
Window type:
|
|
||||||
(Get this info [here](https://github.com/peterfajdiga/karousel/wiki/Getting-window-info))
|
|
||||||
|
|
||||||
Description:
|
|
||||||
14
.github/ISSUE_TEMPLATE/2-bug-report.md
vendored
14
.github/ISSUE_TEMPLATE/2-bug-report.md
vendored
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
name: Generic bug report
|
|
||||||
about: Report a bug
|
|
||||||
title: "[Bug]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Karousel version:
|
|
||||||
Plasma version:
|
|
||||||
X11 / Wayland:
|
|
||||||
|
|
||||||
Description:
|
|
||||||
22
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
22
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Request a feature
|
|
||||||
title: "[Feature]"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
|
|
||||||
These sections are just guidelines, feel free to remove them.
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,6 +2,4 @@
|
|||||||
/package/contents/config/main.xml
|
/package/contents/config/main.xml
|
||||||
/karousel*.tar.gz
|
/karousel*.tar.gz
|
||||||
run-ts-tmp.js
|
run-ts-tmp.js
|
||||||
|
|
||||||
/node_modules
|
|
||||||
/.idea
|
/.idea
|
||||||
|
|||||||
30
Makefile
30
Makefile
@@ -1,43 +1,29 @@
|
|||||||
VERSION = $(shell grep '"Version":' ./package/metadata.json | grep -o '[0-9\.]*')
|
|
||||||
CHECKS := true
|
|
||||||
|
|
||||||
.PHONY: *
|
.PHONY: *
|
||||||
|
|
||||||
build: lint tests
|
VERSION = $(shell grep '"Version":' ./package/metadata.json | grep -o '[0-9\.]*')
|
||||||
|
|
||||||
|
build: tests
|
||||||
tsc -p ./src/main --outFile ./package/contents/code/main.js
|
tsc -p ./src/main --outFile ./package/contents/code/main.js
|
||||||
mkdir -p ./package/contents/config
|
mkdir -p ./package/contents/config
|
||||||
./run-ts.sh ./src/generators/config > ./package/contents/config/main.xml
|
./run-ts.sh ./src/generators/config > ./package/contents/config/main.xml
|
||||||
|
|
||||||
npm-install:
|
|
||||||
npm install
|
|
||||||
|
|
||||||
lint: npm-install
|
|
||||||
ifeq (${CHECKS}, true)
|
|
||||||
npx eslint ./src
|
|
||||||
endif
|
|
||||||
|
|
||||||
lint-fix: npm-install
|
|
||||||
npx eslint ./src --fix
|
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
ifeq (${CHECKS}, true)
|
|
||||||
./run-ts.sh ./src/tests
|
./run-ts.sh ./src/tests
|
||||||
endif
|
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
kpackagetool6 --type=KWin/Script --install=./package || kpackagetool6 --type=KWin/Script --upgrade=./package
|
kpackagetool6 --type=KWin/Script -i ./package || kpackagetool6 --type=KWin/Script -u ./package
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
kpackagetool6 --type=KWin/Script --remove=karousel
|
kpackagetool6 --type=KWin/Script -r karousel
|
||||||
|
|
||||||
package: build
|
package: build
|
||||||
tar -czf ./karousel_${subst .,_,${VERSION}}.tar.gz ./package --transform s/package/karousel/
|
tar -czf ./karousel_${subst .,_,${VERSION}}.tar.gz ./package
|
||||||
|
|
||||||
docs-key-bindings-bbcode:
|
docs-key-bindings-bbcode:
|
||||||
@./run-ts.sh ./src/generators/docs/keyBindingsBbcode
|
@./run-ts.sh ./src/generators/docs/keyBindingsBbcode
|
||||||
|
|
||||||
docs-key-bindings-markdown:
|
docs-key-bindings-table:
|
||||||
@./run-ts.sh ./src/generators/docs/keyBindingsMarkdown
|
@./run-ts.sh ./src/generators/docs/keyBindingsTable
|
||||||
|
|
||||||
docs-key-bindings-fmt:
|
docs-key-bindings-fmt:
|
||||||
@./run-ts.sh ./src/generators/docs/keyBindingsFmt
|
@./run-ts.sh ./src/generators/docs/keyBindingsFmt
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -1,11 +1,17 @@
|
|||||||
# Karousel
|
# Karousel
|
||||||
Scrollable tiling Kwin script. Works especially well with ultrawide screens.
|
KWin tiling script with scrolling. Works especially well with ultrawide screens.
|
||||||
Use with [this](https://github.com/peterfajdiga/kwin4_effect_geometry_change) for animations.
|
Use with [this](https://github.com/peterfajdiga/kwin4_effect_geometry_change) for animations.
|
||||||
|
|
||||||
https://github.com/peterfajdiga/karousel/assets/22796326/2ab62d18-09c7-45f9-8fda-e5e36b8d7a02
|
https://github.com/peterfajdiga/karousel/assets/22796326/2ab62d18-09c7-45f9-8fda-e5e36b8d7a02
|
||||||
|
|
||||||
A scrollable tiling window manager tiles windows, but it does not maximize their widths. Instead, it leaves the width of windows to the user's control.
|
Karousel works differently from most tiling window managers in that it does not maximize the width
|
||||||
Windows are automatically centered when possible. And when running out of width, windows can be scrolled through horizontally.
|
of windows, as this can be undesirable with wider screens, where it results in excessively wide
|
||||||
|
windows that require large return sweeps when reading their content.
|
||||||
|
Instead, it leaves the width of windows to the user's control. This additionally prevents
|
||||||
|
unprompted reflow of window content.
|
||||||
|
|
||||||
|
Windows are automatically centered when possible. And when running out of width, windows can be
|
||||||
|
scrolled through horizontally.
|
||||||
|
|
||||||
Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM),
|
Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM),
|
||||||
[Niri](https://github.com/YaLTeR/niri), and
|
[Niri](https://github.com/YaLTeR/niri), and
|
||||||
@@ -22,13 +28,6 @@ Karousel requires the following QML modules:
|
|||||||
- Doesn't support windows on all desktops
|
- Doesn't support windows on all desktops
|
||||||
- Doesn't support windows on multiple activities
|
- Doesn't support windows on multiple activities
|
||||||
|
|
||||||
## Installation
|
|
||||||
First install the _org.kde.notification_ QML module (_qml-module-org-kde-notifications_ package on Ubuntu).
|
|
||||||
|
|
||||||
Then download the [latest release](https://github.com/peterfajdiga/karousel/releases/latest) and extract it into _~/.local/share/kwin/scripts/_.
|
|
||||||
|
|
||||||
Or clone the repo and run `make install` (requires npm, node, and tsc).
|
|
||||||
|
|
||||||
## Key bindings
|
## Key bindings
|
||||||
The key bindings can be configured in KDE System Settings among KWin's own keyboard shortcuts.
|
The key bindings can be configured in KDE System Settings among KWin's own keyboard shortcuts.
|
||||||
Here's the default ones:
|
Here's the default ones:
|
||||||
@@ -39,30 +38,22 @@ Here's the default ones:
|
|||||||
| Meta+D | Move focus right (Clashes with default KDE shortcuts, may require manual remapping) |
|
| Meta+D | Move focus right (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||||
| Meta+W | Move focus up (Clashes with default KDE shortcuts, may require manual remapping) |
|
| Meta+W | Move focus up (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||||
| Meta+S | Move focus down (Clashes with default KDE shortcuts, may require manual remapping) |
|
| Meta+S | Move focus down (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||||
| (unassigned) | Move focus to the next window in grid |
|
|
||||||
| (unassigned) | Move focus to the previous window in grid |
|
|
||||||
| Meta+Home | Move focus to start |
|
| Meta+Home | Move focus to start |
|
||||||
| Meta+End | Move focus to end |
|
| Meta+End | Move focus to end |
|
||||||
| Meta+Shift+A | Move window left (Moves window out of and into columns) |
|
| Meta+Shift+A | Move window left (Moves window out of and into columns) |
|
||||||
| Meta+Shift+D | Move window right (Moves window out of and into columns) |
|
| Meta+Shift+D | Move window right (Moves window out of and into columns) |
|
||||||
| Meta+Shift+W | Move window up |
|
| Meta+Shift+W | Move window up |
|
||||||
| Meta+Shift+S | Move window down |
|
| Meta+Shift+S | Move window down |
|
||||||
| (unassigned) | Move window to the next position in grid |
|
|
||||||
| (unassigned) | Move window to the previous position in grid |
|
|
||||||
| 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 | Toggle stacked layout for focused column (Only the active window visible) |
|
| Meta+X | Toggle stacked layout for focused column (One window in the column visible, others shaded; not supported on Wayland) |
|
||||||
| 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++ | Increase column width |
|
| Meta+Ctrl++ | Increase column width |
|
||||||
| Meta+Ctrl+- | Decrease column width |
|
| Meta+Ctrl+- | Decrease column width |
|
||||||
| Meta+R | Cycle through preset column widths |
|
|
||||||
| Meta+Shift+R | Cycle through preset column widths in reverse |
|
|
||||||
| Meta+Ctrl+X | Equalize widths of visible columns |
|
| Meta+Ctrl+X | Equalize widths of visible columns |
|
||||||
| Meta+Ctrl+A | Squeeze left column onto the screen (Clashes with default KDE shortcuts, may require manual remapping) |
|
|
||||||
| Meta+Ctrl+D | Squeeze right column onto 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 |
|
||||||
@@ -70,7 +61,6 @@ Here's the default ones:
|
|||||||
| Meta+Alt+PgDown | Scroll right |
|
| Meta+Alt+PgDown | Scroll right |
|
||||||
| Meta+Alt+Home | Scroll to start |
|
| Meta+Alt+Home | Scroll to start |
|
||||||
| Meta+Alt+End | Scroll to end |
|
| Meta+Alt+End | Scroll to end |
|
||||||
| Meta+Ctrl+Return | Move Karousel grid to the current screen |
|
|
||||||
| Meta+[N] | Move focus to column N (Clashes with default KDE shortcuts, may require manual remapping) |
|
| Meta+[N] | Move focus to column N (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||||
| Meta+Shift+[N] | Move window to column N (Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!) |
|
| Meta+Shift+[N] | Move window to column N (Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!) |
|
||||||
| Meta+Ctrl+Shift+[N] | Move column to position N (Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!) |
|
| Meta+Ctrl+Shift+[N] | Move column to position N (Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!) |
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{
|
|
||||||
extends: [tseslint.configs.stylistic],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
|
||||||
"semi": "error",
|
|
||||||
"comma-dangle": ["error", "always-multiline"],
|
|
||||||
"indent": ["error", 4],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
1464
package-lock.json
generated
1464
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "^9.24.0",
|
|
||||||
"typescript-eslint": "^8.30.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,23 +29,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="kcfg_cursorFollowsFocus">
|
|
||||||
<property name="text">
|
|
||||||
<string>Cursor follows focus</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>When a window gains focus, move the cursor to it</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<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>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>New columns start in stacked mode (only the active window visible)</string>
|
<string>New columns start in stacked mode (one window in the column visible, others shaded). Not supported on Wayland.</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -111,35 +101,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Touchpad scrolling (Wayland only)</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="kcfg_gestureScroll">
|
|
||||||
<property name="text">
|
|
||||||
<string>Enable scrolling with touchpad gestures
|
|
||||||
(please don't forget to disable KDE's workspace switching gestures)</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Scroll with a three-finger horizontal swipe gesture</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="kcfg_gestureScrollInvert">
|
|
||||||
<property name="text">
|
|
||||||
<string>Invert scroll direction (Natural scrolling)</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox">
|
<widget class="QGroupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@@ -319,55 +280,13 @@
|
|||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QLabel" name="label_stackOffsetX">
|
|
||||||
<property name="text">
|
|
||||||
<string>Horizontal offset for stacked columns:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="1">
|
|
||||||
<widget class="QSpinBox" name="kcfg_stackOffsetX">
|
|
||||||
<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="7" column="0">
|
|
||||||
<widget class="QLabel" name="label_stackOffsetY">
|
|
||||||
<property name="text">
|
|
||||||
<string>Vertical offset for stacked columns:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="1">
|
|
||||||
<widget class="QSpinBox" name="kcfg_stackOffsetY">
|
|
||||||
<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="0">
|
|
||||||
<widget class="QLabel" name="label_manualScrollStep">
|
<widget class="QLabel" name="label_manualScrollStep">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Manual scroll step size:</string>
|
<string>Manual scroll step size:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="QSpinBox" name="kcfg_manualScrollStep">
|
<widget class="QSpinBox" name="kcfg_manualScrollStep">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string> px</string>
|
<string> px</string>
|
||||||
@@ -381,62 +300,35 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="9" column="0">
|
<item row="7" column="0">
|
||||||
<widget class="QLabel" name="label_gestureScrollStep">
|
<widget class="QLabel" name="label_manualResizeStep">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Touchpad gesture scrolling speed:</string>
|
<string>Manual resize step size:</string>
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>The amount to scroll per edge-to-edge gesture</string>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="QSpinBox" name="kcfg_gestureScrollStep">
|
<widget class="QSpinBox" name="kcfg_manualResizeStep">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string> px</string>
|
<string> px</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>10000</number>
|
<number>999</number>
|
||||||
</property>
|
|
||||||
<property name="minimum">
|
|
||||||
<number>100</number>
|
|
||||||
</property>
|
|
||||||
<property name="singleStep">
|
|
||||||
<number>100</number>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>1920</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="10" column="0">
|
<item row="8" column="0">
|
||||||
<widget class="QLabel" name="label_presetWidths">
|
|
||||||
<property name="text">
|
|
||||||
<string>Preset widths:</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Widths used for cycling through widths</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="10" column="1">
|
|
||||||
<widget class="QLineEdit" name="kcfg_presetWidths">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Comma-separated list of widths. Supported units: "px" and "%".</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item row="11" column="0">
|
|
||||||
<widget class="QLabel" name="label_offScreenOpacity">
|
<widget class="QLabel" name="label_offScreenOpacity">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Obscured window opacity:</string>
|
<string>Obscured window opacity:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="11" column="1">
|
<item row="8" column="1">
|
||||||
<widget class="QSpinBox" name="kcfg_offScreenOpacity">
|
<widget class="QSpinBox" name="kcfg_offScreenOpacity">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string> %</string>
|
<string> %</string>
|
||||||
@@ -449,7 +341,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
|
||||||
|
|||||||
@@ -25,39 +25,4 @@ Item {
|
|||||||
flags: Notification.Persistent
|
flags: Notification.Persistent
|
||||||
urgency: Notification.HighUrgency
|
urgency: Notification.HighUrgency
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification {
|
|
||||||
id: notificationInvalidPresetWidths
|
|
||||||
componentName: "plasma_workspace"
|
|
||||||
eventId: "notification"
|
|
||||||
title: "Karousel"
|
|
||||||
text: "Your preset widths are malformed, please review your Karousel configuration"
|
|
||||||
flags: Notification.Persistent
|
|
||||||
urgency: Notification.HighUrgency
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeGestureHandler {
|
|
||||||
direction: SwipeGestureHandler.Direction.Left
|
|
||||||
fingerCount: 3
|
|
||||||
onActivated: qmlBase.karouselInstance.gestureScrollFinish()
|
|
||||||
onCancelled: qmlBase.karouselInstance.gestureScrollFinish()
|
|
||||||
onProgressChanged: qmlBase.karouselInstance.gestureScroll(-progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeGestureHandler {
|
|
||||||
direction: SwipeGestureHandler.Direction.Right
|
|
||||||
fingerCount: 3
|
|
||||||
onActivated: qmlBase.karouselInstance.gestureScrollFinish()
|
|
||||||
onCancelled: qmlBase.karouselInstance.gestureScrollFinish()
|
|
||||||
onProgressChanged: qmlBase.karouselInstance.gestureScroll(progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
DBusCall {
|
|
||||||
id: moveCursorToFocus
|
|
||||||
|
|
||||||
service: "org.kde.kglobalaccel"
|
|
||||||
path: "/component/kwin"
|
|
||||||
method: "invokeShortcut"
|
|
||||||
arguments: ["MoveMouseToFocus"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"Name": "Peter Fajdiga"
|
"Name": "Peter Fajdiga"
|
||||||
}],
|
}],
|
||||||
"Id": "karousel",
|
"Id": "karousel",
|
||||||
"Version": "0.14",
|
"Version": "0.9.3",
|
||||||
"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"
|
||||||
|
|||||||
@@ -1,8 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
tsc -p "$1" --outFile ./run-ts-tmp.js && node ./run-ts-tmp.js && rm ./run-ts-tmp.js
|
||||||
set -o pipefail
|
|
||||||
|
|
||||||
JS_FILE='./run-ts-tmp.js'
|
|
||||||
|
|
||||||
tsc -p "$1" --outFile "$JS_FILE"
|
|
||||||
node "$JS_FILE"
|
|
||||||
|
|||||||
7
src/extern/global.d.ts
vendored
7
src/extern/global.d.ts
vendored
@@ -1,7 +0,0 @@
|
|||||||
declare const Qt: Qt;
|
|
||||||
declare const KWin: KWin;
|
|
||||||
declare const Workspace: Workspace;
|
|
||||||
declare const qmlBase: QmlObject;
|
|
||||||
declare const notificationInvalidWindowRules: Notification;
|
|
||||||
declare const notificationInvalidPresetWidths: Notification;
|
|
||||||
declare const moveCursorToFocus: DBusCall;
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"include": [
|
"include": ["../../lib/**/*", "./**/*"]
|
||||||
"../../extern/**/*",
|
|
||||||
"../../lib/**/*",
|
|
||||||
"./**/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
interface DocsKeyBinding {
|
function formatComment(comment: string | undefined) {
|
||||||
description: string;
|
return comment === undefined ? "" : ` (${comment})`;
|
||||||
keySequence: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDescription(item: {description: string, comment?: string}) {
|
|
||||||
const suffix = item.comment === undefined ? "" : ` (${item.comment})`;
|
|
||||||
return `${applyMacro(item.description, "N")}${suffix}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function printCols(...columns: (string[] | string)[]) {
|
function printCols(...columns: (string[] | string)[]) {
|
||||||
@@ -15,9 +9,9 @@ function printCols(...columns: (string[] | string)[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nRows = Math.min(...columns.filter(
|
let nRows = Math.min(...columns.filter(
|
||||||
(column: string[] | string) => column instanceof Array,
|
(column: string[] | string) => column instanceof Array
|
||||||
).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
|
||||||
@@ -28,12 +22,12 @@ function printCols(...columns: (string[] | string)[]) {
|
|||||||
(column: string[] | string) => {
|
(column: string[] | string) => {
|
||||||
if (column instanceof Array) {
|
if (column instanceof Array) {
|
||||||
return Math.max(...column.map(
|
return Math.max(...column.map(
|
||||||
(cell: string) => cell.length,
|
(cell: string) => cell.length
|
||||||
));
|
))
|
||||||
} else {
|
} else {
|
||||||
return column.length;
|
return column.length;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function getCell(col: number, row: number) {
|
function getCell(col: number, row: number) {
|
||||||
@@ -54,15 +48,3 @@ function printCols(...columns: (string[] | string)[]) {
|
|||||||
console.log(line);
|
console.log(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const empty: any = {};
|
|
||||||
const keyBindings: DocsKeyBinding[] = Array.prototype.concat(
|
|
||||||
getKeyBindings(empty, empty).map(binding => ({
|
|
||||||
description: formatDescription(binding),
|
|
||||||
keySequence: binding.defaultKeySequence || "(unassigned)",
|
|
||||||
})),
|
|
||||||
getNumKeyBindings(empty, empty).map(binding => ({
|
|
||||||
description: formatDescription(binding),
|
|
||||||
keySequence: `${binding.defaultModifiers}+${binding.fKeys ? "F" : ""}[N]`,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
console.log(`[list]`);
|
console.log(`[list]`);
|
||||||
|
|
||||||
for (const binding of keyBindings) {
|
for (const binding of keyBindings) {
|
||||||
console.log(` [*] ${binding.keySequence} — ${binding.description}`);
|
console.log(` [*] ${binding.defaultKeySequence} — ${binding.description}${formatComment(binding.comment)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const binding of numKeyBindings) {
|
||||||
|
const numPrefix = binding.fKeys ? "F" : "";
|
||||||
|
console.log(` [*] ${binding.defaultModifiers}+${numPrefix}[N] — ${binding.description}N${formatComment(binding.comment)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[/list]`);
|
console.log(`[/list]`);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "../../../tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"../../../extern/**/*",
|
|
||||||
"../../../lib/**/*",
|
"../../../lib/**/*",
|
||||||
"../keyBindings.ts",
|
"../keyBindings.ts",
|
||||||
"./**/*"
|
"./**/*"
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
const colLeft = [
|
const colLeft = [
|
||||||
...keyBindings.map(binding => binding.keySequence),
|
...keyBindings.map((binding: KeyBinding) => binding.defaultKeySequence),
|
||||||
|
...numKeyBindings.map((binding: NumKeyBinding) => {
|
||||||
|
const numPrefix = binding.fKeys ? "F" : "";
|
||||||
|
return `${binding.defaultModifiers}+${numPrefix}[N]`;
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const colRight = [
|
const colRight = [
|
||||||
...keyBindings.map(binding => binding.description),
|
...keyBindings.map((binding: KeyBinding) => `${binding.description}${formatComment(binding.comment)}`),
|
||||||
|
...numKeyBindings.map((binding: NumKeyBinding) => `${binding.description}N${formatComment(binding.comment)}`),
|
||||||
];
|
];
|
||||||
|
|
||||||
printCols(colLeft, " ", colRight);
|
printCols(colLeft, " ", colRight);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "../../../tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"../../../extern/**/*",
|
|
||||||
"../../../lib/**/*",
|
"../../../lib/**/*",
|
||||||
"../keyBindings.ts",
|
"../keyBindings.ts",
|
||||||
"./**/*"
|
"./**/*"
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
const colLeft = [
|
|
||||||
"Shortcut",
|
|
||||||
"---",
|
|
||||||
...keyBindings.map(binding => binding.keySequence),
|
|
||||||
];
|
|
||||||
|
|
||||||
const colRight = [
|
|
||||||
"Action",
|
|
||||||
"---",
|
|
||||||
...keyBindings.map(binding => binding.description),
|
|
||||||
];
|
|
||||||
|
|
||||||
printCols("| ", colLeft, " | ", colRight, " |");
|
|
||||||
18
src/generators/docs/keyBindingsTable/main.ts
Normal file
18
src/generators/docs/keyBindingsTable/main.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const colLeft = [
|
||||||
|
"Shortcut",
|
||||||
|
"---",
|
||||||
|
...keyBindings.map((binding: KeyBinding) => binding.defaultKeySequence),
|
||||||
|
...numKeyBindings.map((binding: NumKeyBinding) => {
|
||||||
|
const numPrefix = binding.fKeys ? "F" : "";
|
||||||
|
return `${binding.defaultModifiers}+${numPrefix}[N]`;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const colRight = [
|
||||||
|
"Action",
|
||||||
|
"---",
|
||||||
|
...keyBindings.map((binding: KeyBinding) => `${binding.description}${formatComment(binding.comment)}`),
|
||||||
|
...numKeyBindings.map((binding: NumKeyBinding) => `${binding.description}N${formatComment(binding.comment)}`),
|
||||||
|
];
|
||||||
|
|
||||||
|
printCols("| ", colLeft, " | ", colRight, " |");
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "../../../tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"../../../extern/**/*",
|
|
||||||
"../../../lib/**/*",
|
"../../../lib/**/*",
|
||||||
"../keyBindings.ts",
|
"../keyBindings.ts",
|
||||||
"./**/*"
|
"./**/*"
|
||||||
346
src/lib/Actions.ts
Normal file
346
src/lib/Actions.ts
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
namespace Actions {
|
||||||
|
export function getAction(world: World, config: Config, name: string) {
|
||||||
|
switch (name) {
|
||||||
|
case "focus-left": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const prevColumn = grid.getPrevColumn(column);
|
||||||
|
if (prevColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevColumn.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "focus-right": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const nextColumn = grid.getNextColumn(column);
|
||||||
|
if (nextColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextColumn.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "focus-up": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const prevWindow = column.getPrevWindow(window);
|
||||||
|
if (prevWindow === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevWindow.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "focus-down": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const nextWindow = column.getNextWindow(window);
|
||||||
|
if (nextWindow === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextWindow.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "focus-start": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const firstColumn = grid.getFirstColumn();
|
||||||
|
if (firstColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
firstColumn.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "focus-end": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const lastColumn = grid.getLastColumn();
|
||||||
|
if (lastColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastColumn.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-left": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-right": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-up": return () => {
|
||||||
|
// TODO (optimization): only arrange moved windows
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
column.moveWindowUp(window);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-down": return () => {
|
||||||
|
// TODO (optimization): only arrange moved windows
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
column.moveWindowDown(window);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-start": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const newColumn = new Column(grid, null);
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-end": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const newColumn = new Column(grid, grid.getLastColumn());
|
||||||
|
window.moveToColumn(newColumn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-toggle-floating": return () => {
|
||||||
|
const kwinClient = Workspace.activeWindow;
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.toggleFloatingClient(kwinClient);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-move-left": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
grid.moveColumnLeft(column);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-move-right": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
grid.moveColumnRight(column);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-move-start": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
grid.moveColumn(column, null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-move-end": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
grid.moveColumn(column, grid.getLastColumn());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-toggle-stacked": return () => {
|
||||||
|
world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
column.toggleStacked();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-width-increase": return () => {
|
||||||
|
world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
config.columnResizer.increaseWidth(column, config.manualResizeStep);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-width-decrease": return () => {
|
||||||
|
world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
config.columnResizer.decreaseWidth(column, config.manualResizeStep);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "columns-width-equalize": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
desktopManager.getCurrentDesktop().equalizeVisibleColumnsWidths();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-left": return () => {
|
||||||
|
gridScroll(world, -config.manualScrollStep);
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-right": return () => {
|
||||||
|
gridScroll(world, config.manualScrollStep);
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-start": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const firstColumn = grid.getFirstColumn();
|
||||||
|
if (firstColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid.desktop.scrollToColumn(firstColumn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-end": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const lastColumn = grid.getLastColumn();
|
||||||
|
if (lastColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid.desktop.scrollToColumn(lastColumn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-focused": return () => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
grid.desktop.scrollCenterRange(column);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-left-column": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
|
||||||
|
if (column === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevColumn = grid.getPrevColumn(column);
|
||||||
|
if (prevColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.desktop.scrollToColumn(prevColumn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "grid-scroll-right-column": return () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
|
||||||
|
if (column === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextColumn = grid.getNextColumn(column);
|
||||||
|
if (nextColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.desktop.scrollToColumn(nextColumn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
default: throw new Error("unknown action: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNumAction(world: World, name: string) {
|
||||||
|
switch (name) {
|
||||||
|
case "focus-": return (columnIndex: number) => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
const grid = desktopManager.getCurrentDesktop().grid;
|
||||||
|
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||||
|
if (targetColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetColumn.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "window-move-to-column-": return (columnIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||||
|
if (targetColumn === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.moveToColumn(targetColumn);
|
||||||
|
grid.desktop.autoAdjustScroll();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-move-to-column-": return (columnIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
|
||||||
|
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||||
|
if (targetColumn === null || targetColumn === column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetColumn.isToTheRightOf(column)) {
|
||||||
|
grid.moveColumn(column, targetColumn);
|
||||||
|
} else {
|
||||||
|
grid.moveColumn(column, grid.getPrevColumn(targetColumn));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "column-move-to-desktop-": return (desktopIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => {
|
||||||
|
const kwinDesktop = Workspace.desktops[desktopIndex];
|
||||||
|
if (kwinDesktop === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newGrid = desktopManager.getDesktopInCurrentActivity(kwinDesktop).grid;
|
||||||
|
if (newGrid === null || newGrid === oldGrid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
column.moveToGrid(newGrid, newGrid.getLastColumn());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
case "tail-move-to-desktop-": return (desktopIndex: number) => {
|
||||||
|
world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, oldGrid) => {
|
||||||
|
const kwinDesktop = Workspace.desktops[desktopIndex];
|
||||||
|
if (kwinDesktop === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newGrid = desktopManager.getDesktopInCurrentActivity(kwinDesktop).grid;
|
||||||
|
if (newGrid === null || newGrid === oldGrid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
oldGrid.evacuateTail(newGrid, column);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
default: throw new Error("unknown num action: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
manualResizeStep: number,
|
||||||
|
columnResizer: ColumnResizer,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ColumnResizer = {
|
||||||
|
increaseWidth(column: Column, step: number): void,
|
||||||
|
decreaseWidth(column: Column, step: number): void,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
class PresetWidths {
|
|
||||||
private readonly presets: ((maxWidth: number) => number)[];
|
|
||||||
|
|
||||||
constructor(presetWidths: string, spacing: number) {
|
|
||||||
this.presets = PresetWidths.parsePresetWidths(presetWidths, spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public next(currentWidth: number, minWidth: number, maxWidth: number) {
|
|
||||||
const widths = this.getWidths(minWidth, maxWidth);
|
|
||||||
const nextIndex = widths.findIndex(width => width > currentWidth);
|
|
||||||
return nextIndex >= 0 ? widths[nextIndex] : widths[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public prev(currentWidth: number, minWidth: number, maxWidth: number) {
|
|
||||||
const widths = this.getWidths(minWidth, maxWidth).reverse();
|
|
||||||
const nextIndex = widths.findIndex(width => width < currentWidth);
|
|
||||||
return nextIndex >= 0 ? widths[nextIndex] : widths[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWidths(minWidth: number, maxWidth: number) {
|
|
||||||
const widths = this.presets.map(f => clamp(f(maxWidth), minWidth, maxWidth));
|
|
||||||
widths.sort((a, b) => a - b);
|
|
||||||
return uniq(widths);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parsePresetWidths(presetWidths: string, spacing: number): ((maxWidth: number) => number)[] {
|
|
||||||
function getRatioFunction(ratio: number) {
|
|
||||||
return (maxWidth: number) => Math.floor((maxWidth + spacing) * ratio - spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return presetWidths.split(",").map((widthStr: string) => {
|
|
||||||
widthStr = widthStr.trim();
|
|
||||||
|
|
||||||
const widthPx = PresetWidths.parseNumberWithSuffix(widthStr, "px");
|
|
||||||
if (widthPx !== undefined) {
|
|
||||||
return () => widthPx;
|
|
||||||
}
|
|
||||||
|
|
||||||
const widthPct = PresetWidths.parseNumberWithSuffix(widthStr, "%");
|
|
||||||
if (widthPct !== undefined) {
|
|
||||||
return getRatioFunction(widthPct / 100.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getRatioFunction(PresetWidths.parseNumberSafe(widthStr));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parseNumberSafe(str: string) {
|
|
||||||
const num = Number(str);
|
|
||||||
if (isNaN(num) || num <= 0) {
|
|
||||||
throw new Error("Invalid number: " + str);
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parseNumberWithSuffix(str: string, suffix: string) {
|
|
||||||
if (!str.endsWith(suffix)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return PresetWidths.parseNumberSafe(str.substring(0, str.length-suffix.length).trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,14 @@
|
|||||||
class ContextualResizer {
|
class ContextualResizer {
|
||||||
constructor(
|
public increaseWidth(column: Column, step: number) {
|
||||||
private readonly presetWidths: { getWidths: (minWidth: number, maxWidth: number) => number[] },
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public increaseWidth(column: Column) {
|
|
||||||
const grid = column.grid;
|
const grid = column.grid;
|
||||||
const desktop = grid.desktop;
|
const desktop = grid.desktop;
|
||||||
const visibleRange = desktop.getCurrentVisibleRange();
|
const visibleRange = desktop.getCurrentVisibleRange();
|
||||||
const minWidth = column.getMinWidth();
|
if(!column.isVisible(visibleRange, true) || column.getWidth() >= column.getMaxWidth()) {
|
||||||
const maxWidth = column.getMaxWidth();
|
|
||||||
if(!Range.contains(visibleRange, column) || column.getWidth() >= maxWidth) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
|
let leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
|
||||||
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
|
let rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
|
||||||
if (leftVisibleColumn === null || rightVisibleColumn === null) {
|
if (leftVisibleColumn === null || rightVisibleColumn === null) {
|
||||||
console.assert(false); // should at least see self
|
console.assert(false); // should at least see self
|
||||||
return;
|
return;
|
||||||
@@ -23,15 +17,16 @@ class ContextualResizer {
|
|||||||
const leftSpace = leftVisibleColumn.getLeft() - visibleRange.getLeft();
|
const leftSpace = leftVisibleColumn.getLeft() - visibleRange.getLeft();
|
||||||
const rightSpace = visibleRange.getRight() - rightVisibleColumn.getRight();
|
const rightSpace = visibleRange.getRight() - rightVisibleColumn.getRight();
|
||||||
|
|
||||||
const newWidth = findMinPositive(
|
const newWidth = ContextualResizer.findNextStep(
|
||||||
[
|
[
|
||||||
|
visibleRange.getWidth(),
|
||||||
|
column.getWidth() + step,
|
||||||
column.getWidth() + leftSpace + rightSpace,
|
column.getWidth() + leftSpace + rightSpace,
|
||||||
column.getWidth() + leftSpace + rightSpace + leftVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
|
column.getWidth() + leftSpace + rightSpace + leftVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
|
||||||
column.getWidth() + leftSpace + rightSpace + rightVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
|
column.getWidth() + leftSpace + rightSpace + rightVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
|
||||||
...this.presetWidths.getWidths(minWidth, maxWidth),
|
|
||||||
],
|
],
|
||||||
width => width - column.getWidth(),
|
width => width - column.getWidth(),
|
||||||
);
|
)
|
||||||
if (newWidth === undefined) {
|
if (newWidth === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -40,13 +35,11 @@ class ContextualResizer {
|
|||||||
desktop.scrollCenterVisible(column);
|
desktop.scrollCenterVisible(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
public decreaseWidth(column: Column) {
|
public decreaseWidth(column: Column, step: number) {
|
||||||
const grid = column.grid;
|
const grid = column.grid;
|
||||||
const desktop = grid.desktop;
|
const desktop = grid.desktop;
|
||||||
const visibleRange = desktop.getCurrentVisibleRange();
|
const visibleRange = desktop.getCurrentVisibleRange();
|
||||||
const minWidth = column.getMinWidth();
|
if(!column.isVisible(visibleRange, true) || column.getWidth() <= column.getMinWidth()) {
|
||||||
const maxWidth = column.getMaxWidth();
|
|
||||||
if(!Range.contains(visibleRange, column) || column.getWidth() <= minWidth) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,11 +50,11 @@ class ContextualResizer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftOffScreenColumn = grid.getLeftColumn(leftVisibleColumn);
|
let leftOffScreenColumn = grid.getPrevColumn(leftVisibleColumn);
|
||||||
if (leftOffScreenColumn === column) {
|
if (leftOffScreenColumn === column) {
|
||||||
leftOffScreenColumn = null;
|
leftOffScreenColumn = null;
|
||||||
}
|
}
|
||||||
let rightOffScreenColumn = grid.getRightColumn(rightVisibleColumn);
|
let rightOffScreenColumn = grid.getNextColumn(rightVisibleColumn);
|
||||||
if (rightOffScreenColumn === column) {
|
if (rightOffScreenColumn === column) {
|
||||||
rightOffScreenColumn = null;
|
rightOffScreenColumn = null;
|
||||||
}
|
}
|
||||||
@@ -71,14 +64,15 @@ class ContextualResizer {
|
|||||||
const leftOffScreen = leftOffScreenColumn === null ? 0 : leftOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
|
const leftOffScreen = leftOffScreenColumn === null ? 0 : leftOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
|
||||||
const rightOffScreen = rightOffScreenColumn === null ? 0 : rightOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
|
const rightOffScreen = rightOffScreenColumn === null ? 0 : rightOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
|
||||||
|
|
||||||
const newWidth = findMinPositive(
|
const newWidth = ContextualResizer.findNextStep(
|
||||||
[
|
[
|
||||||
|
visibleRange.getWidth(),
|
||||||
|
column.getWidth() - step,
|
||||||
column.getWidth() - leftOffScreen,
|
column.getWidth() - leftOffScreen,
|
||||||
column.getWidth() - rightOffScreen,
|
column.getWidth() - rightOffScreen,
|
||||||
...this.presetWidths.getWidths(minWidth, maxWidth),
|
|
||||||
],
|
],
|
||||||
width => column.getWidth() - width,
|
width => column.getWidth() - width,
|
||||||
);
|
)
|
||||||
if (newWidth === undefined) {
|
if (newWidth === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,4 +80,17 @@ class ContextualResizer {
|
|||||||
column.setWidth(newWidth, true);
|
column.setWidth(newWidth, true);
|
||||||
desktop.scrollCenterVisible(column);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,9 @@
|
|||||||
class RawResizer {
|
class RawResizer {
|
||||||
constructor(
|
public increaseWidth(column: Column, step: number) {
|
||||||
private readonly presetWidths: { getWidths: (minWidth: number, maxWidth: number) => number[] },
|
column.adjustWidth(step, true);
|
||||||
) {}
|
|
||||||
|
|
||||||
public increaseWidth(column: Column) {
|
|
||||||
const newWidth = findMinPositive(
|
|
||||||
[
|
|
||||||
...this.presetWidths.getWidths(column.getMinWidth(), column.getMaxWidth()),
|
|
||||||
],
|
|
||||||
width => width - column.getWidth(),
|
|
||||||
);
|
|
||||||
if (newWidth === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
column.setWidth(newWidth, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public decreaseWidth(column: Column) {
|
public decreaseWidth(column: Column, step: number) {
|
||||||
const newWidth = findMinPositive(
|
column.adjustWidth(-step, true);
|
||||||
[
|
|
||||||
...this.presetWidths.getWidths(column.getMinWidth(), column.getMaxWidth()),
|
|
||||||
],
|
|
||||||
width => column.getWidth() - width,
|
|
||||||
);
|
|
||||||
if (newWidth === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
column.setWidth(newWidth, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ class CenterClamper {
|
|||||||
}
|
}
|
||||||
const lastColumn = desktop.grid.getLastColumn()!;
|
const lastColumn = desktop.grid.getLastColumn()!;
|
||||||
|
|
||||||
const minScroll = Math.round((firstColumn.getWidth() - desktop.tilingArea.width) / 2);
|
let minScroll = Math.round((firstColumn.getWidth() - desktop.tilingArea.width) / 2);
|
||||||
const maxScroll = Math.round(desktop.grid.getWidth() - (desktop.tilingArea.width + lastColumn.getWidth()) / 2);
|
let maxScroll = Math.round(desktop.grid.getWidth() - (desktop.tilingArea.width + lastColumn.getWidth()) / 2);
|
||||||
return clamp(x, minScroll, maxScroll);
|
return clamp(x, minScroll, maxScroll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class EdgeClamper {
|
class EdgeClamper {
|
||||||
public clampScrollX(desktop: Desktop, x: number) {
|
public clampScrollX(desktop: Desktop, x: number) {
|
||||||
const minScroll = 0;
|
let minScroll = 0;
|
||||||
const maxScroll = desktop.grid.getWidth() - desktop.tilingArea.width;
|
let maxScroll = desktop.grid.getWidth() - desktop.tilingArea.width;
|
||||||
if (maxScroll < 0) {
|
if (maxScroll < 0) {
|
||||||
return Math.round(maxScroll / 2);
|
return Math.round(maxScroll / 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
interface Config {
|
type Config = {
|
||||||
gapsOuterTop: number;
|
gapsOuterTop: number,
|
||||||
gapsOuterBottom: number;
|
gapsOuterBottom: number,
|
||||||
gapsOuterLeft: number;
|
gapsOuterLeft: number,
|
||||||
gapsOuterRight: number;
|
gapsOuterRight: number,
|
||||||
gapsInnerHorizontal: number;
|
gapsInnerHorizontal: number,
|
||||||
gapsInnerVertical: number;
|
gapsInnerVertical: number,
|
||||||
stackOffsetX: number;
|
manualScrollStep: number,
|
||||||
stackOffsetY: number;
|
manualResizeStep: number,
|
||||||
manualScrollStep: number;
|
offScreenOpacity: number,
|
||||||
presetWidths: string;
|
untileOnDrag: boolean,
|
||||||
offScreenOpacity: number;
|
stackColumnsByDefault: boolean,
|
||||||
untileOnDrag: boolean;
|
resizeNeighborColumn: boolean,
|
||||||
cursorFollowsFocus: boolean;
|
reMaximize: boolean,
|
||||||
stackColumnsByDefault: boolean;
|
skipSwitcher: boolean,
|
||||||
resizeNeighborColumn: boolean;
|
scrollingLazy: boolean,
|
||||||
reMaximize: boolean;
|
scrollingCentered: boolean,
|
||||||
skipSwitcher: boolean;
|
scrollingGrouped: boolean,
|
||||||
scrollingLazy: boolean;
|
tiledKeepBelow: boolean,
|
||||||
scrollingCentered: boolean;
|
floatingKeepAbove: boolean,
|
||||||
scrollingGrouped: boolean;
|
windowRules: string,
|
||||||
gestureScroll: boolean;
|
};
|
||||||
gestureScrollInvert: boolean;
|
|
||||||
gestureScrollStep: number;
|
|
||||||
tiledKeepBelow: boolean;
|
|
||||||
floatingKeepAbove: boolean;
|
|
||||||
windowRules: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
const defaultWindowRules = `[
|
const defaultWindowRules = `[
|
||||||
{
|
{
|
||||||
"class": "(org\\\\.kde\\\\.)?plasmashell",
|
"class": "ksmserver-logout-greeter",
|
||||||
"tile": false
|
"tile": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"class": "(org\\\\.kde\\\\.)?polkit-kde-authentication-agent-1",
|
"class": "xwaylandvideobridge",
|
||||||
|
"tile": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "(org\\\\.kde\\\\.)?plasmashell",
|
||||||
"tile": false
|
"tile": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,23 +35,28 @@ const defaultWindowRules = `[
|
|||||||
"class": "(org\\\\.kde\\\\.)?yakuake",
|
"class": "(org\\\\.kde\\\\.)?yakuake",
|
||||||
"tile": false
|
"tile": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"class": "steam",
|
|
||||||
"caption": "Steam Big Picture Mode",
|
|
||||||
"tile": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"class": "zoom",
|
"class": "zoom",
|
||||||
"caption": "Zoom Cloud Meetings|zoom|zoom <2>",
|
"caption": "Zoom Cloud Meetings|zoom|zoom <2>",
|
||||||
"tile": false
|
"tile": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"class": "jetbrains-.*",
|
"class": "jetbrains-idea",
|
||||||
"caption": "splash",
|
"caption": "splash",
|
||||||
"tile": false
|
"tile": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"class": "jetbrains-.*",
|
"class": "jetbrains-studio",
|
||||||
|
"caption": "splash",
|
||||||
|
"tile": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "jetbrains-idea",
|
||||||
|
"caption": "Unstash Changes|Paths Affected by stash@.*",
|
||||||
|
"tile": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "jetbrains-studio",
|
||||||
"caption": "Unstash Changes|Paths Affected by stash@.*",
|
"caption": "Unstash Changes|Paths Affected by stash@.*",
|
||||||
"tile": true
|
"tile": true
|
||||||
}
|
}
|
||||||
@@ -84,25 +93,15 @@ const configDef = [
|
|||||||
type: "UInt",
|
type: "UInt",
|
||||||
default: 8,
|
default: 8,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "stackOffsetX",
|
|
||||||
type: "UInt",
|
|
||||||
default: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "stackOffsetY",
|
|
||||||
type: "UInt",
|
|
||||||
default: 32,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "manualScrollStep",
|
name: "manualScrollStep",
|
||||||
type: "UInt",
|
type: "UInt",
|
||||||
default: 200,
|
default: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "presetWidths",
|
name: "manualResizeStep",
|
||||||
type: "String",
|
type: "UInt",
|
||||||
default: "50%, 100%",
|
default: 600,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "offScreenOpacity",
|
name: "offScreenOpacity",
|
||||||
@@ -114,11 +113,6 @@ const configDef = [
|
|||||||
type: "Bool",
|
type: "Bool",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "cursorFollowsFocus",
|
|
||||||
type: "Bool",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "stackColumnsByDefault",
|
name: "stackColumnsByDefault",
|
||||||
type: "Bool",
|
type: "Bool",
|
||||||
@@ -154,21 +148,6 @@ const configDef = [
|
|||||||
type: "Bool",
|
type: "Bool",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "gestureScroll",
|
|
||||||
type: "Bool",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "gestureScrollInvert",
|
|
||||||
type: "Bool",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "gestureScrollStep",
|
|
||||||
type: "UInt",
|
|
||||||
default: 1920,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "tiledKeepBelow",
|
name: "tiledKeepBelow",
|
||||||
type: "Bool",
|
type: "Bool",
|
||||||
@@ -188,5 +167,5 @@ const configDef = [
|
|||||||
name: "windowRules",
|
name: "windowRules",
|
||||||
type: "String",
|
type: "String",
|
||||||
default: defaultWindowRules,
|
default: defaultWindowRules,
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
7
src/lib/config/loader.ts
Normal file
7
src/lib/config/loader.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
function loadConfig(): Config {
|
||||||
|
const config: any = {};
|
||||||
|
for (const entry of configDef) {
|
||||||
|
config[entry.name] = KWin.readConfig(entry.name, entry.default);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
3
src/lib/extern/dbuscall.ts
vendored
3
src/lib/extern/dbuscall.ts
vendored
@@ -1,3 +0,0 @@
|
|||||||
interface DBusCall extends QmlObject {
|
|
||||||
call(): void;
|
|
||||||
}
|
|
||||||
5
src/lib/extern/global.d.ts
vendored
5
src/lib/extern/global.d.ts
vendored
@@ -1 +1,6 @@
|
|||||||
declare const console: Console;
|
declare const console: Console;
|
||||||
|
declare const Qt: Qt;
|
||||||
|
declare const KWin: KWin;
|
||||||
|
declare const Workspace: Workspace;
|
||||||
|
declare const qmlBase: QmlObject;
|
||||||
|
declare const notificationInvalidWindowRules: Notification;
|
||||||
|
|||||||
43
src/lib/extern/kwin.ts
vendored
43
src/lib/extern/kwin.ts
vendored
@@ -1,26 +1,23 @@
|
|||||||
interface KWin {
|
type KWin = {
|
||||||
__brand: "KWin";
|
|
||||||
|
|
||||||
readConfig(key: string, defaultValue: any): any;
|
readConfig(key: string, defaultValue: any): any;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface Workspace {
|
|
||||||
__brand: "Workspace";
|
|
||||||
|
|
||||||
|
type Workspace = {
|
||||||
readonly activities: string[];
|
readonly activities: string[];
|
||||||
readonly desktops: KwinDesktop[];
|
readonly desktops: KwinDesktop[];
|
||||||
readonly currentDesktop: KwinDesktop;
|
readonly currentDesktop: KwinDesktop;
|
||||||
readonly currentActivity: string;
|
readonly currentActivity: string;
|
||||||
readonly activeScreen: Output;
|
readonly activeScreen: Output;
|
||||||
|
readonly screens: Output[];
|
||||||
readonly windows: KwinClient[];
|
readonly windows: KwinClient[];
|
||||||
readonly cursorPos: Readonly<QmlPoint>;
|
readonly cursorPos: Readonly<QmlPoint>;
|
||||||
|
|
||||||
activeWindow: KwinClient|null;
|
activeWindow: KwinClient;
|
||||||
|
|
||||||
readonly currentDesktopChanged: QSignal<[]>;
|
readonly currentDesktopChanged: QSignal<[]>
|
||||||
readonly windowAdded: QSignal<[KwinClient]>;
|
readonly windowAdded: QSignal<[KwinClient]>;
|
||||||
readonly windowRemoved: QSignal<[KwinClient]>;
|
readonly windowRemoved: QSignal<[KwinClient]>;
|
||||||
readonly windowActivated: QSignal<[KwinClient|null]>;
|
readonly windowActivated: QSignal<[KwinClient]>;
|
||||||
readonly screensChanged: QSignal<[]>;
|
readonly screensChanged: QSignal<[]>;
|
||||||
readonly activitiesChanged: QSignal<[]>;
|
readonly activitiesChanged: QSignal<[]>;
|
||||||
readonly desktopsChanged: QSignal<[]>;
|
readonly desktopsChanged: QSignal<[]>;
|
||||||
@@ -28,7 +25,7 @@ interface Workspace {
|
|||||||
readonly virtualScreenSizeChanged: QSignal<[]>;
|
readonly virtualScreenSizeChanged: QSignal<[]>;
|
||||||
|
|
||||||
clientArea(option: ClientAreaOption, output: Output, kwinDesktop: KwinDesktop): QmlRect;
|
clientArea(option: ClientAreaOption, output: Output, kwinDesktop: KwinDesktop): QmlRect;
|
||||||
}
|
};
|
||||||
|
|
||||||
const enum ClientAreaOption {
|
const enum ClientAreaOption {
|
||||||
PlacementArea,
|
PlacementArea,
|
||||||
@@ -48,16 +45,18 @@ const enum MaximizedMode {
|
|||||||
Maximized,
|
Maximized,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Tile { __brand: "Tile" }
|
type Tile = unknown;
|
||||||
interface Output { __brand: "Output" }
|
|
||||||
|
type Output = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface KwinClient {
|
interface KwinClient {
|
||||||
__brand: "KwinClient";
|
readonly shadeable: boolean;
|
||||||
|
|
||||||
readonly caption: string;
|
readonly caption: string;
|
||||||
readonly minSize: Readonly<QmlSize>;
|
readonly minSize: Readonly<QmlSize>;
|
||||||
readonly transient: boolean;
|
readonly transient: boolean;
|
||||||
readonly transientFor: KwinClient | null;
|
readonly transientFor: KwinClient;
|
||||||
readonly clientGeometry: Readonly<QmlRect>;
|
readonly clientGeometry: Readonly<QmlRect>;
|
||||||
readonly move: boolean;
|
readonly move: boolean;
|
||||||
readonly resize: boolean;
|
readonly resize: boolean;
|
||||||
@@ -78,17 +77,19 @@ interface KwinClient {
|
|||||||
skipSwitcher: boolean;
|
skipSwitcher: boolean;
|
||||||
keepAbove: boolean;
|
keepAbove: boolean;
|
||||||
keepBelow: boolean;
|
keepBelow: boolean;
|
||||||
|
shade: boolean;
|
||||||
minimized: boolean;
|
minimized: boolean;
|
||||||
frameGeometry: QmlRect;
|
frameGeometry: QmlRect;
|
||||||
desktops: KwinDesktop[]; // empty array means all desktops
|
desktops: KwinDesktop[]; // empty array means all desktops
|
||||||
tile: Tile|null;
|
tile: Tile;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
|
|
||||||
readonly fullScreenChanged: QSignal<[]>;
|
readonly fullScreenChanged: QSignal<[]>;
|
||||||
readonly desktopsChanged: QSignal<[]>;
|
readonly desktopsChanged: QSignal<[]>;
|
||||||
|
readonly outputChanged: QSignal<[]>;
|
||||||
readonly activitiesChanged: QSignal<[]>;
|
readonly activitiesChanged: QSignal<[]>;
|
||||||
readonly minimizedChanged: QSignal<[]>;
|
readonly minimizedChanged: QSignal<[]>;
|
||||||
readonly maximizedAboutToChange: QSignal<[MaximizedMode]>;
|
readonly maximizedAboutToChange: QSignal<[MaximizedMode]>
|
||||||
readonly captionChanged: QSignal<[]>;
|
readonly captionChanged: QSignal<[]>;
|
||||||
readonly tileChanged: QSignal<[]>;
|
readonly tileChanged: QSignal<[]>;
|
||||||
readonly interactiveMoveResizeStarted: QSignal<[]>;
|
readonly interactiveMoveResizeStarted: QSignal<[]>;
|
||||||
@@ -99,12 +100,10 @@ interface KwinClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface KwinDesktop {
|
interface KwinDesktop {
|
||||||
__brand: "KwinDesktop";
|
|
||||||
|
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShortcutHandler extends QmlObject {
|
type ShortcutHandler = {
|
||||||
readonly activated: QSignal<[]>;
|
readonly activated: QSignal<[]>;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
};
|
||||||
|
|||||||
4
src/lib/extern/notification.ts
vendored
4
src/lib/extern/notification.ts
vendored
@@ -1,3 +1,3 @@
|
|||||||
interface Notification extends QmlObject {
|
type Notification = {
|
||||||
sendEvent(): void;
|
sendEvent(): void;
|
||||||
}
|
};
|
||||||
|
|||||||
45
src/lib/extern/qt.ts
vendored
45
src/lib/extern/qt.ts
vendored
@@ -1,56 +1,45 @@
|
|||||||
interface Console {
|
type Console = {
|
||||||
__brand: "Console";
|
|
||||||
|
|
||||||
log(...args: any[]): void;
|
log(...args: any[]): void;
|
||||||
|
trace(): void;
|
||||||
assert(assertion: boolean, message?: string): void;
|
assert(assertion: boolean, message?: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Qt {
|
type Qt = {
|
||||||
__brand: "Qt";
|
|
||||||
|
|
||||||
rect(x: number, y: number, width: number, height: number): QmlRect;
|
rect(x: number, y: number, width: number, height: number): QmlRect;
|
||||||
createQmlObject(qml: string, parent: QmlObject): QmlObject;
|
createQmlObject(qml: string, parent: QmlObject): QmlObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QmlObject { __brand: "QmlObject" }
|
type QmlObject = unknown;
|
||||||
|
|
||||||
interface QmlPoint {
|
|
||||||
__brand: "QmlPoint";
|
|
||||||
|
|
||||||
|
type QmlPoint = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QmlRect {
|
type QmlRect = {
|
||||||
__brand: "QmlRect";
|
|
||||||
|
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
readonly top: number;
|
top: number;
|
||||||
readonly bottom: number; // top + height
|
bottom: number; // top + height
|
||||||
readonly left: number;
|
left: number;
|
||||||
readonly right: number; // left + width
|
right: number; // left + width
|
||||||
}
|
};
|
||||||
|
|
||||||
interface QmlSize {
|
|
||||||
__brand: "QmlSize";
|
|
||||||
|
|
||||||
|
type QmlSize = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface QSignal<T extends unknown[]> {
|
|
||||||
__brand: "QSignal";
|
|
||||||
|
|
||||||
|
type QSignal<T extends unknown[]> = {
|
||||||
connect(handler: (...args: [...T]) => void): void;
|
connect(handler: (...args: [...T]) => void): void;
|
||||||
disconnect(handler: (...args: [...T]) => void): void;
|
disconnect(handler: (...args: [...T]) => void): void;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface QmlTimer extends QmlObject {
|
type QmlTimer = {
|
||||||
interval: number;
|
interval: number;
|
||||||
readonly triggered: QSignal<[]>;
|
readonly triggered: QSignal<[]>;
|
||||||
restart(): void;
|
restart(): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,433 +0,0 @@
|
|||||||
class Actions {
|
|
||||||
constructor(
|
|
||||||
private readonly config: Actions.Config,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public readonly focusLeft = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const leftColumn = grid.getLeftColumn(column);
|
|
||||||
if (leftColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
leftColumn.getWindowToFocus().focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusRight = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const rightColumn = grid.getRightColumn(column);
|
|
||||||
if (rightColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rightColumn.getWindowToFocus().focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusUp = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const aboveWindow = column.getAboveWindow(window);
|
|
||||||
if (aboveWindow === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
aboveWindow.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusDown = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const belowWindow = column.getBelowWindow(window);
|
|
||||||
if (belowWindow === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
belowWindow.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusNext = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const belowWindow = column.getBelowWindow(window);
|
|
||||||
if (belowWindow !== null) {
|
|
||||||
belowWindow.focus();
|
|
||||||
} else {
|
|
||||||
const rightColumn = grid.getRightColumn(column);
|
|
||||||
if (rightColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rightColumn.getFirstWindow().focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusPrevious = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const aboveWindow = column.getAboveWindow(window);
|
|
||||||
if (aboveWindow !== null) {
|
|
||||||
aboveWindow.focus();
|
|
||||||
} else {
|
|
||||||
const leftColumn = grid.getLeftColumn(column);
|
|
||||||
if (leftColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
leftColumn.getLastWindow().focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusStart = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const firstColumn = grid.getFirstColumn();
|
|
||||||
if (firstColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
firstColumn.getWindowToFocus().focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focusEnd = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const lastColumn = grid.getLastColumn();
|
|
||||||
if (lastColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastColumn.getWindowToFocus().focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMoveLeft = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
if (column.getWindowCount() === 1) {
|
|
||||||
// move from own column into existing column
|
|
||||||
const leftColumn = grid.getLeftColumn(column);
|
|
||||||
if (leftColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.moveToColumn(leftColumn, true, FocusPassing.Type.None);
|
|
||||||
grid.desktop.autoAdjustScroll();
|
|
||||||
} else {
|
|
||||||
// move from shared column into own column
|
|
||||||
const newColumn = new Column(grid, grid.getLeftColumn(column));
|
|
||||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMoveRight = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid, bottom = true) => {
|
|
||||||
if (column.getWindowCount() === 1) {
|
|
||||||
// move from own column into existing column
|
|
||||||
const rightColumn = grid.getRightColumn(column);
|
|
||||||
if (rightColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.moveToColumn(rightColumn, bottom, FocusPassing.Type.None);
|
|
||||||
grid.desktop.autoAdjustScroll();
|
|
||||||
} else {
|
|
||||||
// move from shared column into own column
|
|
||||||
const newColumn = new Column(grid, column);
|
|
||||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO (optimization): only arrange moved windows
|
|
||||||
public readonly windowMoveUp = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
column.moveWindowUp(window);
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO (optimization): only arrange moved windows
|
|
||||||
public readonly windowMoveDown = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
column.moveWindowDown(window);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMoveNext = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const canMoveDown = window !== column.getLastWindow();
|
|
||||||
if (canMoveDown) {
|
|
||||||
column.moveWindowDown(window);
|
|
||||||
} else {
|
|
||||||
this.windowMoveRight(cm, dm, window, column, grid, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMovePrevious = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const canMoveUp = window !== column.getFirstWindow();
|
|
||||||
if (canMoveUp) {
|
|
||||||
column.moveWindowUp(window);
|
|
||||||
} else {
|
|
||||||
this.windowMoveLeft(cm, dm, window, column, grid);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMoveStart = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const newColumn = new Column(grid, null);
|
|
||||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMoveEnd = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const newColumn = new Column(grid, grid.getLastColumn());
|
|
||||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowToggleFloating = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
if (Workspace.activeWindow === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cm.toggleFloatingClient(Workspace.activeWindow);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnMoveLeft = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
grid.moveColumnLeft(column);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnMoveRight = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
grid.moveColumnRight(column);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnMoveStart = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
grid.moveColumn(column, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnMoveEnd = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
grid.moveColumn(column, grid.getLastColumn());
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnToggleStacked = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
column.toggleStacked();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnWidthIncrease = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
this.config.columnResizer.increaseWidth(column);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnWidthDecrease = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
this.config.columnResizer.decreaseWidth(column);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly cyclePresetWidths = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const nextWidth = this.config.presetWidths.next(column.getWidth(), column.getMinWidth(), column.getMaxWidth());
|
|
||||||
column.setWidth(nextWidth, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly cyclePresetWidthsReverse = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const nextWidth = this.config.presetWidths.prev(column.getWidth(), column.getMinWidth(), column.getMaxWidth());
|
|
||||||
column.setWidth(nextWidth, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnsWidthEqualize = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const desktop = dm.getCurrentDesktop();
|
|
||||||
const visibleRange = desktop.getCurrentVisibleRange();
|
|
||||||
const visibleColumns = Array.from(desktop.grid.getVisibleColumns(visibleRange, true));
|
|
||||||
|
|
||||||
const availableSpace = desktop.tilingArea.width;
|
|
||||||
const gapsWidth = desktop.grid.config.gapsInnerHorizontal * (visibleColumns.length-1);
|
|
||||||
const widths = fillSpace(
|
|
||||||
availableSpace - gapsWidth,
|
|
||||||
visibleColumns.map(column => ({ min: column.getMinWidth(), max: column.getMaxWidth() })),
|
|
||||||
);
|
|
||||||
visibleColumns.forEach((column, index) => column.setWidth(widths[index], true));
|
|
||||||
|
|
||||||
desktop.scrollCenterRange(Range.fromRanges(
|
|
||||||
visibleColumns[0],
|
|
||||||
visibleColumns[visibleColumns.length - 1],
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnsSqueezeLeft = (cm: ClientManager, dm: DesktopManager, window: Window, focusedColumn: Column, grid: Grid) => {
|
|
||||||
const visibleRange = grid.desktop.getCurrentVisibleRange();
|
|
||||||
if (!Range.contains(visibleRange, focusedColumn)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentVisibleColumns = Array.from(grid.getVisibleColumns(visibleRange, true));
|
|
||||||
console.assert(currentVisibleColumns.includes(focusedColumn), "should at least contain the focused column");
|
|
||||||
|
|
||||||
const targetColumn = grid.getLeftColumn(currentVisibleColumns[0]);
|
|
||||||
if (targetColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wantedVisibleColumns = [targetColumn, ...currentVisibleColumns];
|
|
||||||
while (true) {
|
|
||||||
const success = this.squeezeColumns(wantedVisibleColumns);
|
|
||||||
if (success) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const removedColumn = wantedVisibleColumns.pop();
|
|
||||||
if (removedColumn === focusedColumn) {
|
|
||||||
break; // don't scroll past the currently focused column
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnsSqueezeRight = (cm: ClientManager, dm: DesktopManager, window: Window, focusedColumn: Column, grid: Grid) => {
|
|
||||||
const visibleRange = grid.desktop.getCurrentVisibleRange();
|
|
||||||
if (!Range.contains(visibleRange, focusedColumn)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentVisibleColumns = Array.from(grid.getVisibleColumns(visibleRange, true));
|
|
||||||
console.assert(currentVisibleColumns.includes(focusedColumn), "should at least contain the focused column");
|
|
||||||
|
|
||||||
const targetColumn = grid.getRightColumn(currentVisibleColumns[currentVisibleColumns.length-1]);
|
|
||||||
if (targetColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wantedVisibleColumns = [...currentVisibleColumns, targetColumn];
|
|
||||||
while (true) {
|
|
||||||
const success = this.squeezeColumns(wantedVisibleColumns);
|
|
||||||
if (success) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const removedColumn = wantedVisibleColumns.shift();
|
|
||||||
if (removedColumn === focusedColumn) {
|
|
||||||
break; // don't scroll past the currently focused column
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly squeezeColumns = (columns: Column[]) => {
|
|
||||||
const firstColumn = columns[0];
|
|
||||||
const lastColumn = columns[columns.length-1];
|
|
||||||
const grid = firstColumn.grid;
|
|
||||||
const desktop = grid.desktop;
|
|
||||||
|
|
||||||
const availableSpace = desktop.tilingArea.width;
|
|
||||||
const gapsWidth = grid.config.gapsInnerHorizontal * (columns.length-1);
|
|
||||||
const columnConstraints = columns.map(column => ({ min: column.getMinWidth(), max: column.getWidth() }));
|
|
||||||
const minTotalWidth = gapsWidth + columnConstraints.reduce((acc, constraint) => acc + constraint.min, 0);
|
|
||||||
if (minTotalWidth > availableSpace) {
|
|
||||||
// there's nothing we can do
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const widths = fillSpace(availableSpace - gapsWidth, columnConstraints);
|
|
||||||
columns.forEach((column, index) => column.setWidth(widths[index], true));
|
|
||||||
desktop.scrollCenterRange(Range.fromRanges(firstColumn, lastColumn));
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollLeft = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
this.gridScroll(dm, -this.config.manualScrollStep);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollRight = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
this.gridScroll(dm, this.config.manualScrollStep);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly gridScroll = (desktopManager: DesktopManager, amount: number) => {
|
|
||||||
desktopManager.getCurrentDesktop().adjustScroll(amount, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollStart = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const firstColumn = grid.getFirstColumn();
|
|
||||||
if (firstColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
grid.desktop.scrollToColumn(firstColumn, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollEnd = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const lastColumn = grid.getLastColumn();
|
|
||||||
if (lastColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
grid.desktop.scrollToColumn(lastColumn, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollFocused = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const scrollAmount = Range.minus(column, grid.desktop.getCurrentVisibleRange());
|
|
||||||
if (scrollAmount !== 0) {
|
|
||||||
grid.desktop.adjustScroll(scrollAmount, true);
|
|
||||||
} else {
|
|
||||||
grid.desktop.scrollToColumn(column, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollLeftColumn = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
|
|
||||||
if (column === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const leftColumn = grid.getLeftColumn(column);
|
|
||||||
if (leftColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.desktop.scrollToColumn(leftColumn, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly gridScrollRightColumn = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
|
|
||||||
if (column === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rightColumn = grid.getRightColumn(column);
|
|
||||||
if (rightColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.desktop.scrollToColumn(rightColumn, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly screenSwitch = (cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
dm.selectScreen(Workspace.activeScreen);
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly focus = (columnIndex: number, cm: ClientManager, dm: DesktopManager) => {
|
|
||||||
const grid = dm.getCurrentDesktop().grid;
|
|
||||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
|
||||||
if (targetColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
targetColumn.getWindowToFocus().focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly windowMoveToColumn = (columnIndex: number, cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
|
||||||
if (targetColumn === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.moveToColumn(targetColumn, true, FocusPassing.Type.None);
|
|
||||||
grid.desktop.autoAdjustScroll();
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnMoveToColumn = (columnIndex: number, cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
|
||||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
|
||||||
if (targetColumn === null || targetColumn === column) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (targetColumn.isToTheRightOf(column)) {
|
|
||||||
grid.moveColumn(column, targetColumn);
|
|
||||||
} else {
|
|
||||||
grid.moveColumn(column, grid.getLeftColumn(targetColumn));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly columnMoveToDesktop = (desktopIndex: number, cm: ClientManager, dm: DesktopManager, window: Window, column: Column, oldGrid: Grid) => {
|
|
||||||
const kwinDesktop = Workspace.desktops[desktopIndex];
|
|
||||||
if (kwinDesktop === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newGrid = dm.getDesktopInCurrentActivity(kwinDesktop).grid;
|
|
||||||
if (newGrid === null || newGrid === oldGrid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
column.moveToGrid(newGrid, newGrid.getLastColumn());
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly tailMoveToDesktop = (desktopIndex: number, cm: ClientManager, dm: DesktopManager, window: Window, column: Column, oldGrid: Grid) => {
|
|
||||||
const kwinDesktop = Workspace.desktops[desktopIndex];
|
|
||||||
if (kwinDesktop === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newGrid = dm.getDesktopInCurrentActivity(kwinDesktop).grid;
|
|
||||||
if (newGrid === null || newGrid === oldGrid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
oldGrid.evacuateTail(newGrid, column);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Actions {
|
|
||||||
export interface Config {
|
|
||||||
manualScrollStep: number;
|
|
||||||
presetWidths: {
|
|
||||||
next: (currentWidth: number, minWidth: number, maxWidth: number) => number;
|
|
||||||
prev: (currentWidth: number, minWidth: number, maxWidth: number) => number
|
|
||||||
};
|
|
||||||
columnResizer: ColumnResizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ColumnResizer {
|
|
||||||
increaseWidth(column: Column): void;
|
|
||||||
decreaseWidth(column: Column): void;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,273 +1,185 @@
|
|||||||
function getKeyBindings(world: World, actions: Actions): KeyBinding[] {
|
const keyBindings: KeyBinding[] = [
|
||||||
return [
|
{
|
||||||
{
|
name: "window-toggle-floating",
|
||||||
name: "window-toggle-floating",
|
description: "Toggle floating",
|
||||||
description: "Toggle floating",
|
defaultKeySequence: "Meta+Space",
|
||||||
defaultKeySequence: "Meta+Space",
|
},
|
||||||
action: () => world.do(actions.windowToggleFloating),
|
{
|
||||||
},
|
name: "focus-left",
|
||||||
{
|
description: "Move focus left",
|
||||||
name: "focus-left",
|
defaultKeySequence: "Meta+A",
|
||||||
description: "Move focus left",
|
},
|
||||||
defaultKeySequence: "Meta+A",
|
{
|
||||||
action: () => world.doIfTiledFocused(actions.focusLeft),
|
name: "focus-right",
|
||||||
},
|
description: "Move focus right",
|
||||||
{
|
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
||||||
name: "focus-right",
|
defaultKeySequence: "Meta+D",
|
||||||
description: "Move focus right",
|
},
|
||||||
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
{
|
||||||
defaultKeySequence: "Meta+D",
|
name: "focus-up",
|
||||||
action: () => world.doIfTiledFocused(actions.focusRight),
|
description: "Move focus up",
|
||||||
},
|
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
||||||
{
|
defaultKeySequence: "Meta+W",
|
||||||
name: "focus-up",
|
},
|
||||||
description: "Move focus up",
|
{
|
||||||
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
name: "focus-down",
|
||||||
defaultKeySequence: "Meta+W",
|
description: "Move focus down",
|
||||||
action: () => world.doIfTiledFocused(actions.focusUp),
|
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
||||||
},
|
defaultKeySequence: "Meta+S",
|
||||||
{
|
},
|
||||||
name: "focus-down",
|
{
|
||||||
description: "Move focus down",
|
name: "focus-start",
|
||||||
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
description: "Move focus to start",
|
||||||
defaultKeySequence: "Meta+S",
|
defaultKeySequence: "Meta+Home",
|
||||||
action: () => world.doIfTiledFocused(actions.focusDown),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "focus-end",
|
||||||
name: "focus-next",
|
description: "Move focus to end",
|
||||||
description: "Move focus to the next window in grid",
|
defaultKeySequence: "Meta+End",
|
||||||
action: () => world.doIfTiledFocused(actions.focusNext),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "window-move-left",
|
||||||
name: "focus-previous",
|
description: "Move window left",
|
||||||
description: "Move focus to the previous window in grid",
|
comment: "Moves window out of and into columns",
|
||||||
action: () => world.doIfTiledFocused(actions.focusPrevious),
|
defaultKeySequence: "Meta+Shift+A",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "focus-start",
|
name: "window-move-right",
|
||||||
description: "Move focus to start",
|
description: "Move window right",
|
||||||
defaultKeySequence: "Meta+Home",
|
comment: "Moves window out of and into columns",
|
||||||
action: () => world.do(actions.focusStart),
|
defaultKeySequence: "Meta+Shift+D",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "focus-end",
|
name: "window-move-up",
|
||||||
description: "Move focus to end",
|
description: "Move window up",
|
||||||
defaultKeySequence: "Meta+End",
|
defaultKeySequence: "Meta+Shift+W",
|
||||||
action: () => world.do(actions.focusEnd),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "window-move-down",
|
||||||
name: "window-move-left",
|
description: "Move window down",
|
||||||
description: "Move window left",
|
defaultKeySequence: "Meta+Shift+S",
|
||||||
comment: "Moves window out of and into columns",
|
},
|
||||||
defaultKeySequence: "Meta+Shift+A",
|
{
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveLeft),
|
name: "window-move-start",
|
||||||
},
|
description: "Move window to start",
|
||||||
{
|
defaultKeySequence: "Meta+Shift+Home",
|
||||||
name: "window-move-right",
|
},
|
||||||
description: "Move window right",
|
{
|
||||||
comment: "Moves window out of and into columns",
|
name: "window-move-end",
|
||||||
defaultKeySequence: "Meta+Shift+D",
|
description: "Move window to end",
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveRight),
|
defaultKeySequence: "Meta+Shift+End",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "window-move-up",
|
name: "column-toggle-stacked",
|
||||||
description: "Move window up",
|
description: "Toggle stacked layout for focused column",
|
||||||
defaultKeySequence: "Meta+Shift+W",
|
comment: "One window in the column visible, others shaded; not supported on Wayland",
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveUp),
|
defaultKeySequence: "Meta+X",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "window-move-down",
|
name: "column-move-left",
|
||||||
description: "Move window down",
|
description: "Move column left",
|
||||||
defaultKeySequence: "Meta+Shift+S",
|
defaultKeySequence: "Meta+Ctrl+Shift+A",
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveDown),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "column-move-right",
|
||||||
name: "window-move-next",
|
description: "Move column right",
|
||||||
description: "Move window to the next position in grid",
|
defaultKeySequence: "Meta+Ctrl+Shift+D",
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveNext),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "column-move-start",
|
||||||
name: "window-move-previous",
|
description: "Move column to start",
|
||||||
description: "Move window to the previous position in grid",
|
defaultKeySequence: "Meta+Ctrl+Shift+Home",
|
||||||
action: () => world.doIfTiledFocused(actions.windowMovePrevious),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "column-move-end",
|
||||||
name: "window-move-start",
|
description: "Move column to end",
|
||||||
description: "Move window to start",
|
defaultKeySequence: "Meta+Ctrl+Shift+End",
|
||||||
defaultKeySequence: "Meta+Shift+Home",
|
},
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveStart),
|
{
|
||||||
},
|
name: "column-width-increase",
|
||||||
{
|
description: "Increase column width",
|
||||||
name: "window-move-end",
|
defaultKeySequence: "Meta+Ctrl++",
|
||||||
description: "Move window to end",
|
},
|
||||||
defaultKeySequence: "Meta+Shift+End",
|
{
|
||||||
action: () => world.doIfTiledFocused(actions.windowMoveEnd),
|
name: "column-width-decrease",
|
||||||
},
|
description: "Decrease column width",
|
||||||
{
|
defaultKeySequence: "Meta+Ctrl+-",
|
||||||
name: "column-toggle-stacked",
|
},
|
||||||
description: "Toggle stacked layout for focused column",
|
{
|
||||||
comment: "Only the active window visible",
|
name: "columns-width-equalize",
|
||||||
defaultKeySequence: "Meta+X",
|
description: "Equalize widths of visible columns",
|
||||||
action: () => world.doIfTiledFocused(actions.columnToggleStacked),
|
defaultKeySequence: "Meta+Ctrl+X",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "column-move-left",
|
name: "grid-scroll-focused",
|
||||||
description: "Move column left",
|
description: "Center focused window",
|
||||||
defaultKeySequence: "Meta+Ctrl+Shift+A",
|
comment: "Scrolls so that the focused window is centered in the screen",
|
||||||
action: () => world.doIfTiledFocused(actions.columnMoveLeft),
|
defaultKeySequence: "Meta+Alt+Return",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "column-move-right",
|
name: "grid-scroll-left-column",
|
||||||
description: "Move column right",
|
description: "Scroll one column to the left",
|
||||||
defaultKeySequence: "Meta+Ctrl+Shift+D",
|
defaultKeySequence: "Meta+Alt+A",
|
||||||
action: () => world.doIfTiledFocused(actions.columnMoveRight),
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "grid-scroll-right-column",
|
||||||
name: "column-move-start",
|
description: "Scroll one column to the right",
|
||||||
description: "Move column to start",
|
defaultKeySequence: "Meta+Alt+D",
|
||||||
defaultKeySequence: "Meta+Ctrl+Shift+Home",
|
},
|
||||||
action: () => world.doIfTiledFocused(actions.columnMoveStart),
|
{
|
||||||
},
|
name: "grid-scroll-left",
|
||||||
{
|
description: "Scroll left",
|
||||||
name: "column-move-end",
|
defaultKeySequence: "Meta+Alt+PgUp",
|
||||||
description: "Move column to end",
|
},
|
||||||
defaultKeySequence: "Meta+Ctrl+Shift+End",
|
{
|
||||||
action: () => world.doIfTiledFocused(actions.columnMoveEnd),
|
name: "grid-scroll-right",
|
||||||
},
|
description: "Scroll right",
|
||||||
{
|
defaultKeySequence: "Meta+Alt+PgDown",
|
||||||
name: "column-width-increase",
|
},
|
||||||
description: "Increase column width",
|
{
|
||||||
defaultKeySequence: "Meta+Ctrl++",
|
name: "grid-scroll-start",
|
||||||
action: () => world.doIfTiledFocused(actions.columnWidthIncrease),
|
description: "Scroll to start",
|
||||||
},
|
defaultKeySequence: "Meta+Alt+Home",
|
||||||
{
|
},
|
||||||
name: "column-width-decrease",
|
{
|
||||||
description: "Decrease column width",
|
name: "grid-scroll-end",
|
||||||
defaultKeySequence: "Meta+Ctrl+-",
|
description: "Scroll to end",
|
||||||
action: () => world.doIfTiledFocused(actions.columnWidthDecrease),
|
defaultKeySequence: "Meta+Alt+End",
|
||||||
},
|
},
|
||||||
{
|
];
|
||||||
name: "cycle-preset-widths",
|
|
||||||
description: "Cycle through preset column widths",
|
|
||||||
defaultKeySequence: "Meta+R",
|
|
||||||
action: () => world.doIfTiledFocused(actions.cyclePresetWidths),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "cycle-preset-widths-reverse",
|
|
||||||
description: "Cycle through preset column widths in reverse",
|
|
||||||
defaultKeySequence: "Meta+Shift+R",
|
|
||||||
action: () => world.doIfTiledFocused(actions.cyclePresetWidthsReverse),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "columns-width-equalize",
|
|
||||||
description: "Equalize widths of visible columns",
|
|
||||||
defaultKeySequence: "Meta+Ctrl+X",
|
|
||||||
action: () => world.do(actions.columnsWidthEqualize),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "columns-squeeze-left",
|
|
||||||
description: "Squeeze left column onto the screen",
|
|
||||||
comment: "Clashes with default KDE shortcuts, may require manual remapping",
|
|
||||||
defaultKeySequence: "Meta+Ctrl+A",
|
|
||||||
action: () => world.doIfTiledFocused(actions.columnsSqueezeLeft),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "columns-squeeze-right",
|
|
||||||
description: "Squeeze right column onto the screen",
|
|
||||||
defaultKeySequence: "Meta+Ctrl+D",
|
|
||||||
action: () => world.doIfTiledFocused(actions.columnsSqueezeRight),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-focused",
|
|
||||||
description: "Center focused window",
|
|
||||||
comment: "Scrolls so that the focused window is centered in the screen",
|
|
||||||
defaultKeySequence: "Meta+Alt+Return",
|
|
||||||
action: () => world.doIfTiledFocused(actions.gridScrollFocused),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-left-column",
|
|
||||||
description: "Scroll one column to the left",
|
|
||||||
defaultKeySequence: "Meta+Alt+A",
|
|
||||||
action: () => world.do(actions.gridScrollLeftColumn),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-right-column",
|
|
||||||
description: "Scroll one column to the right",
|
|
||||||
defaultKeySequence: "Meta+Alt+D",
|
|
||||||
action: () => world.do(actions.gridScrollRightColumn),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-left",
|
|
||||||
description: "Scroll left",
|
|
||||||
defaultKeySequence: "Meta+Alt+PgUp",
|
|
||||||
action: () => world.do(actions.gridScrollLeft),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-right",
|
|
||||||
description: "Scroll right",
|
|
||||||
defaultKeySequence: "Meta+Alt+PgDown",
|
|
||||||
action: () => world.do(actions.gridScrollRight),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-start",
|
|
||||||
description: "Scroll to start",
|
|
||||||
defaultKeySequence: "Meta+Alt+Home",
|
|
||||||
action: () => world.do(actions.gridScrollStart),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid-scroll-end",
|
|
||||||
description: "Scroll to end",
|
|
||||||
defaultKeySequence: "Meta+Alt+End",
|
|
||||||
action: () => world.do(actions.gridScrollEnd),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "screen-switch",
|
|
||||||
description: "Move Karousel grid to the current screen",
|
|
||||||
defaultKeySequence: "Meta+Ctrl+Return",
|
|
||||||
action: () => world.do(actions.screenSwitch),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNumKeyBindings(world: World, actions: Actions): NumKeyBinding[] {
|
const numKeyBindings: NumKeyBinding[] = [
|
||||||
return [
|
{
|
||||||
{
|
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: (i: number) => world.do(actions.focus.partial(i)),
|
{
|
||||||
},
|
name: "window-move-to-column-",
|
||||||
{
|
description: "Move window to column ",
|
||||||
name: "window-move-to-column-{}",
|
comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!",
|
||||||
description: "Move window to column {}",
|
defaultModifiers: "Meta+Shift",
|
||||||
comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!",
|
fKeys: false,
|
||||||
defaultModifiers: "Meta+Shift",
|
},
|
||||||
fKeys: false,
|
{
|
||||||
action: (i: number) => world.doIfTiledFocused(actions.windowMoveToColumn.partial(i)),
|
name: "column-move-to-column-",
|
||||||
},
|
description: "Move column to position ",
|
||||||
{
|
comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!",
|
||||||
name: "column-move-to-column-{}",
|
defaultModifiers: "Meta+Ctrl+Shift",
|
||||||
description: "Move column to position {}",
|
fKeys: false,
|
||||||
comment: "Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!",
|
},
|
||||||
defaultModifiers: "Meta+Ctrl+Shift",
|
{
|
||||||
fKeys: false,
|
name: "column-move-to-desktop-",
|
||||||
action: (i: number) => world.doIfTiledFocused(actions.columnMoveToColumn.partial(i)),
|
description: "Move column to desktop ",
|
||||||
},
|
defaultModifiers: "Meta+Ctrl+Shift",
|
||||||
{
|
fKeys: true,
|
||||||
name: "column-move-to-desktop-{}",
|
},
|
||||||
description: "Move column to desktop {}",
|
{
|
||||||
defaultModifiers: "Meta+Ctrl+Shift",
|
name: "tail-move-to-desktop-",
|
||||||
fKeys: true,
|
description: "Move this and all following columns to desktop ",
|
||||||
action: (i: number) => world.doIfTiledFocused(actions.columnMoveToDesktop.partial(i)),
|
defaultModifiers: "Meta+Ctrl+Shift+Alt",
|
||||||
},
|
fKeys: true,
|
||||||
{
|
},
|
||||||
name: "tail-move-to-desktop-{}",
|
];
|
||||||
description: "Move this and all following columns to desktop {}",
|
|
||||||
defaultModifiers: "Meta+Ctrl+Shift+Alt",
|
|
||||||
fKeys: true,
|
|
||||||
action: (i: number) => world.doIfTiledFocused(actions.tailMoveToDesktop.partial(i)),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
interface KeyBinding {
|
type KeyBinding = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
defaultKeySequence?: string;
|
defaultKeySequence: string;
|
||||||
action: () => void;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
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: (i: number) => void;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
function catchWrap(f: () => void) {
|
function catchWrap(f: () => void) {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -26,14 +24,14 @@ function catchWrap(f: () => void) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerKeyBinding(shortcutActions: ShortcutAction[], keyBinding: KeyBinding) {
|
function registerKeyBinding(world: World, config: Actions.Config, shortcutActions: ShortcutAction[], keyBinding: KeyBinding) {
|
||||||
shortcutActions.push(new ShortcutAction(
|
shortcutActions.push(new ShortcutAction(
|
||||||
keyBinding,
|
keyBinding,
|
||||||
catchWrap(keyBinding.action),
|
catchWrap(Actions.getAction(world, config, keyBinding.name)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerNumKeyBindings(shortcutActions: ShortcutAction[], numKeyBinding: NumKeyBinding) {
|
function registerNumKeyBindings(world: World, shortcutActions: ShortcutAction[], numKeyBinding: NumKeyBinding) {
|
||||||
const numPrefix = numKeyBinding.fKeys ? "F" : "";
|
const numPrefix = numKeyBinding.fKeys ? "F" : "";
|
||||||
const n = numKeyBinding.fKeys ? 12 : 9;
|
const n = numKeyBinding.fKeys ? 12 : 9;
|
||||||
for (let i = 0; i < 12; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
@@ -41,27 +39,28 @@ function registerNumKeyBindings(shortcutActions: ShortcutAction[], numKeyBinding
|
|||||||
const keySequence = i < n ?
|
const keySequence = i < n ?
|
||||||
numKeyBinding.defaultModifiers + "+" + numPrefix + numKey :
|
numKeyBinding.defaultModifiers + "+" + numPrefix + numKey :
|
||||||
"";
|
"";
|
||||||
|
const action = Actions.getNumAction(world, numKeyBinding.name);
|
||||||
shortcutActions.push(new ShortcutAction(
|
shortcutActions.push(new ShortcutAction(
|
||||||
{
|
{
|
||||||
name: applyMacro(numKeyBinding.name, numKey),
|
name: numKeyBinding.name + numKey,
|
||||||
description: applyMacro(numKeyBinding.description, numKey),
|
description: numKeyBinding.description + numKey,
|
||||||
defaultKeySequence: keySequence,
|
defaultKeySequence: keySequence,
|
||||||
},
|
},
|
||||||
catchWrap(() => numKeyBinding.action(i)),
|
catchWrap(() => action(i)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: refactor
|
||||||
function registerKeyBindings(world: World, config: Actions.Config) {
|
function registerKeyBindings(world: World, config: Actions.Config) {
|
||||||
const actions = new Actions(config);
|
|
||||||
const shortcutActions: ShortcutAction[] = [];
|
const shortcutActions: ShortcutAction[] = [];
|
||||||
|
|
||||||
for (const keyBinding of getKeyBindings(world, actions)) {
|
for (const keyBinding of keyBindings) {
|
||||||
registerKeyBinding(shortcutActions, keyBinding);
|
registerKeyBinding(world, config, shortcutActions, keyBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const numKeyBinding of getNumKeyBindings(world, actions)) {
|
for (const numKeyBinding of numKeyBindings) {
|
||||||
registerNumKeyBindings(shortcutActions, numKeyBinding);
|
registerNumKeyBindings(world, shortcutActions, numKeyBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
return shortcutActions;
|
return shortcutActions;
|
||||||
|
|||||||
@@ -5,25 +5,25 @@ class Column {
|
|||||||
private readonly windows: LinkedList<Window>;
|
private readonly windows: LinkedList<Window>;
|
||||||
private stacked: boolean;
|
private stacked: boolean;
|
||||||
private focusTaker: Window|null;
|
private focusTaker: Window|null;
|
||||||
private static readonly minWidth = 40;
|
private static readonly minWidth = 10;
|
||||||
|
|
||||||
constructor(grid: Grid, leftColumn: Column|null) {
|
constructor(grid: Grid, prevColumn: Column|null) {
|
||||||
this.gridX = 0;
|
this.gridX = 0;
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.windows = new LinkedList();
|
this.windows = new LinkedList();
|
||||||
this.stacked = grid.config.stackColumnsByDefault;
|
this.stacked = grid.config.stackColumnsByDefault;
|
||||||
this.focusTaker = null;
|
this.focusTaker = null;
|
||||||
this.grid = grid;
|
this.grid = grid;
|
||||||
this.grid.onColumnAdded(this, leftColumn);
|
this.grid.onColumnAdded(this, prevColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveToGrid(targetGrid: Grid, leftColumn: Column|null) {
|
public moveToGrid(targetGrid: Grid, prevColumn: Column|null) {
|
||||||
if (targetGrid === this.grid) {
|
if (targetGrid === this.grid) {
|
||||||
this.grid.moveColumn(this, leftColumn);
|
this.grid.moveColumn(this, prevColumn);
|
||||||
} else {
|
} else {
|
||||||
this.grid.onColumnRemoved(this, this.isFocused() ? FocusPassing.Type.Immediate : FocusPassing.Type.None);
|
this.grid.onColumnRemoved(this, false);
|
||||||
this.grid = targetGrid;
|
this.grid = targetGrid;
|
||||||
targetGrid.onColumnAdded(this, leftColumn);
|
targetGrid.onColumnAdded(this, prevColumn);
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
window.client.kwinClient.desktops = [targetGrid.desktop.kwinDesktop];
|
window.client.kwinClient.desktops = [targetGrid.desktop.kwinDesktop];
|
||||||
}
|
}
|
||||||
@@ -56,19 +56,11 @@ class Column {
|
|||||||
return this.getWindowCount() === 0;
|
return this.getWindowCount() === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFirstWindow(): Window {
|
public getPrevWindow(window: Window) {
|
||||||
return this.windows.getFirst()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getLastWindow(): Window {
|
|
||||||
return this.windows.getLast()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAboveWindow(window: Window) {
|
|
||||||
return this.windows.getPrev(window);
|
return this.windows.getPrev(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBelowWindow(window: Window) {
|
public getNextWindow(window: Window) {
|
||||||
return this.windows.getNext(window);
|
return this.windows.getNext(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,27 +125,6 @@ class Column {
|
|||||||
return this.gridX + this.width;
|
return this.gridX + this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUserResizeWidth(
|
|
||||||
startWidth: number,
|
|
||||||
currentDelta: number,
|
|
||||||
resizingLeftSide: boolean,
|
|
||||||
neighbor?: { column: Column, startWidth: number },
|
|
||||||
) {
|
|
||||||
const oldColumnWidth = this.getWidth();
|
|
||||||
this.setWidth(startWidth + currentDelta, true);
|
|
||||||
const actualDelta = this.getWidth() - startWidth;
|
|
||||||
|
|
||||||
let leftEdgeDeltaStep = resizingLeftSide ? oldColumnWidth - this.getWidth() : 0;
|
|
||||||
if (neighbor !== undefined) {
|
|
||||||
const oldNeighborWidth = neighbor.column.getWidth();
|
|
||||||
neighbor.column.setWidth(neighbor.startWidth - actualDelta, true);
|
|
||||||
if (resizingLeftSide) {
|
|
||||||
leftEdgeDeltaStep -= neighbor.column.getWidth() - oldNeighborWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.grid.desktop.adjustScroll(-leftEdgeDeltaStep, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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) {
|
||||||
@@ -195,48 +166,58 @@ class Column {
|
|||||||
return this.focusTaker;
|
return this.focusTaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWindowToFocus() {
|
public focus() {
|
||||||
return this.getFocusTaker() ?? this.windows.getFirst()!;
|
const window = this.getFocusTaker() ?? this.windows.getFirst();
|
||||||
}
|
if (window === null) {
|
||||||
|
return;
|
||||||
public isFocused() {
|
|
||||||
const lastFocusedWindow = this.grid.getLastFocusedWindow();
|
|
||||||
if (lastFocusedWindow === null) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return lastFocusedWindow.column === this && lastFocusedWindow.isFocused();
|
window.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public arrange(x: number, visibleRange: Range, forceOpaque: boolean) {
|
public arrange(x: number, visibleRange: Range, forceOpaque: boolean) {
|
||||||
if (this.grid.config.offScreenOpacity < 1.0 && !forceOpaque) {
|
if (this.grid.config.offScreenOpacity < 1.0 && !forceOpaque) {
|
||||||
const opacity = Range.contains(visibleRange, this) ? 100 : this.grid.config.offScreenOpacity;
|
const opacity = this.isVisible(visibleRange, true) ? 100 : this.grid.config.offScreenOpacity;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
window.client.kwinClient.opacity = opacity;
|
window.client.kwinClient.opacity = opacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stacked && this.windows.length() >= 2) {
|
if (this.stacked && this.windows.length() >= 2 && this.canStack()) {
|
||||||
this.arrangeStacked(x);
|
this.arrangeStacked(x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let y = this.grid.desktop.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.arrange(x, y, this.width, window.height);
|
window.arrange(x, y, this.width, window.height);
|
||||||
y += window.height + this.grid.config.gapsInnerVertical;
|
y += window.height + this.grid.config.gapsInnerVertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public arrangeStacked(x: number) {
|
public arrangeStacked(x: number) {
|
||||||
const nWindows = this.windows.length();
|
const expandedWindow = this.getFocusTaker();
|
||||||
const windowWidth = this.width - (nWindows - 1) * this.grid.config.stackOffsetX;
|
let collapsedHeight;
|
||||||
const windowHeight = this.grid.desktop.tilingArea.height - (nWindows - 1) * this.grid.config.stackOffsetY;
|
|
||||||
|
|
||||||
let windowX = x;
|
|
||||||
let windowY = this.grid.desktop.tilingArea.y;
|
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
window.arrange(windowX, windowY, windowWidth, windowHeight);
|
if (window === expandedWindow) {
|
||||||
windowX += this.grid.config.stackOffsetX;
|
window.client.setShade(false);
|
||||||
windowY += this.grid.config.stackOffsetY;
|
} else {
|
||||||
|
window.client.setShade(true);
|
||||||
|
collapsedHeight = window.client.kwinClient.frameGeometry.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nCollapsed = this.getWindowCount() - 1;
|
||||||
|
const expandedHeight = this.grid.desktop.tilingArea.height - nCollapsed * (collapsedHeight! + this.grid.config.gapsInnerVertical);
|
||||||
|
let y = this.grid.desktop.tilingArea.y;
|
||||||
|
for (const window of this.windows.iterator()) {
|
||||||
|
if (window === expandedWindow) {
|
||||||
|
window.arrange(x, y, this.width, expandedHeight);
|
||||||
|
y += expandedHeight;
|
||||||
|
} else {
|
||||||
|
window.arrange(x, y, this.width, window.height);
|
||||||
|
y += collapsedHeight!;
|
||||||
|
}
|
||||||
|
y += this.grid.config.gapsInnerVertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,13 +229,27 @@ class Column {
|
|||||||
this.grid.desktop.onLayoutChanged();
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onWindowAdded(window: Window, bottom: boolean) {
|
private canStack() {
|
||||||
if (bottom) {
|
for (const window of this.windows.iterator()) {
|
||||||
this.windows.insertEnd(window);
|
if (!window.client.kwinClient.shadeable) {
|
||||||
} else {
|
return false;
|
||||||
this.windows.insertStart(window);
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isVisible(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||||
|
if (fullyVisible) {
|
||||||
|
return this.getLeft() >= visibleRange.getLeft() &&
|
||||||
|
this.getRight() <= visibleRange.getRight();
|
||||||
|
} else {
|
||||||
|
return this.getRight() + this.grid.config.gapsInnerHorizontal > visibleRange.getLeft() &&
|
||||||
|
this.getLeft() - this.grid.config.gapsInnerHorizontal < visibleRange.getRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onWindowAdded(window: 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);
|
||||||
}
|
}
|
||||||
@@ -269,9 +264,9 @@ class Column {
|
|||||||
this.grid.desktop.onLayoutChanged();
|
this.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onWindowRemoved(window: Window, passFocus: FocusPassing.Type) {
|
public onWindowRemoved(window: Window, passFocus: boolean) {
|
||||||
const lastWindow = this.windows.length() === 1;
|
const lastWindow = this.windows.length() === 1;
|
||||||
const windowToFocus = this.getAboveWindow(window) ?? this.getBelowWindow(window);
|
const windowToFocus = this.getPrevWindow(window) ?? this.getNextWindow(window);
|
||||||
|
|
||||||
this.windows.remove(window);
|
this.windows.remove(window);
|
||||||
|
|
||||||
@@ -284,15 +279,8 @@ class Column {
|
|||||||
this.destroy(passFocus);
|
this.destroy(passFocus);
|
||||||
} else {
|
} else {
|
||||||
this.resizeWindows();
|
this.resizeWindows();
|
||||||
if (windowToFocus !== null) {
|
if (passFocus && windowToFocus !== null) {
|
||||||
switch (passFocus) {
|
windowToFocus.focus();
|
||||||
case FocusPassing.Type.Immediate:
|
|
||||||
windowToFocus.focus();
|
|
||||||
break;
|
|
||||||
case FocusPassing.Type.OnUnfocus:
|
|
||||||
this.grid.focusPasser.request(windowToFocus.client.kwinClient);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,18 +288,18 @@ class Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onWindowFocused(window: Window) {
|
public onWindowFocused(window: Window) {
|
||||||
this.grid.onColumnFocused(this, window);
|
this.grid.onColumnFocused(this);
|
||||||
this.focusTaker = window;
|
this.focusTaker = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public restoreToTiled(focusedWindow: Window) {
|
public restoreToTiled() {
|
||||||
const lastFocusedWindow = this.getFocusTaker();
|
const lastFocusedWindow = this.getFocusTaker();
|
||||||
if (lastFocusedWindow !== null && lastFocusedWindow !== focusedWindow) {
|
if (lastFocusedWindow !== null) {
|
||||||
lastFocusedWindow.restoreToTiled();
|
lastFocusedWindow.restoreToTiled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private destroy(passFocus: FocusPassing.Type) {
|
private destroy(passFocus: boolean) {
|
||||||
this.grid.onColumnRemoved(this, passFocus);
|
this.grid.onColumnRemoved(this, passFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,33 @@
|
|||||||
class Desktop {
|
class Desktop {
|
||||||
public readonly grid: Grid;
|
public readonly grid: Grid;
|
||||||
|
public readonly screen: Output;
|
||||||
|
public readonly kwinDesktop: KwinDesktop;
|
||||||
|
private readonly pinManager: PinManager;
|
||||||
|
private readonly config: Desktop.Config;
|
||||||
private scrollX: number;
|
private scrollX: number;
|
||||||
private gestureScrollXInitial: number | null;
|
|
||||||
private dirty: boolean;
|
private dirty: boolean;
|
||||||
private dirtyScroll: boolean;
|
private dirtyScroll: boolean;
|
||||||
private dirtyPins: boolean;
|
private dirtyPins: boolean;
|
||||||
public clientArea: QmlRect;
|
public clientArea: QmlRect;
|
||||||
public tilingArea: QmlRect;
|
public tilingArea: QmlRect;
|
||||||
|
|
||||||
constructor(
|
constructor(screen: Output, kwinDesktop: KwinDesktop, pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig) {
|
||||||
public readonly kwinDesktop: KwinDesktop,
|
this.pinManager = pinManager;
|
||||||
private readonly pinManager: PinManager,
|
this.config = config;
|
||||||
private readonly config: Desktop.Config,
|
|
||||||
private readonly getScreen: () => Output,
|
|
||||||
layoutConfig: LayoutConfig,
|
|
||||||
focusPasser: FocusPassing.Passer,
|
|
||||||
) {
|
|
||||||
this.scrollX = 0;
|
this.scrollX = 0;
|
||||||
this.gestureScrollXInitial = null;
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
this.dirtyScroll = true;
|
this.dirtyScroll = true;
|
||||||
this.dirtyPins = true;
|
this.dirtyPins = true;
|
||||||
this.grid = new Grid(this, layoutConfig, focusPasser);
|
this.screen = screen;
|
||||||
this.clientArea = Desktop.getClientArea(this.getScreen(), kwinDesktop);
|
this.kwinDesktop = kwinDesktop;
|
||||||
|
this.grid = new Grid(this, layoutConfig);
|
||||||
|
this.clientArea = Desktop.getClientArea(screen, kwinDesktop);
|
||||||
this.tilingArea = Desktop.getTilingArea(this.clientArea, kwinDesktop, pinManager, config);
|
this.tilingArea = Desktop.getTilingArea(this.clientArea, kwinDesktop, pinManager, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateArea() {
|
private updateArea() {
|
||||||
const newClientArea = Desktop.getClientArea(this.getScreen(), this.kwinDesktop);
|
const newClientArea = Desktop.getClientArea(this.screen, this.kwinDesktop);
|
||||||
if (rectEquals(newClientArea, this.clientArea) && !this.dirtyPins) {
|
if (newClientArea === this.clientArea && !this.dirtyPins) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.clientArea = newClientArea;
|
this.clientArea = newClientArea;
|
||||||
@@ -55,10 +54,10 @@ class Desktop {
|
|||||||
top,
|
top,
|
||||||
right - left,
|
right - left,
|
||||||
bottom - top,
|
bottom - top,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollIntoView(range: Range) {
|
public scrollIntoView(range: Desktop.Range) {
|
||||||
const left = range.getLeft();
|
const left = range.getLeft();
|
||||||
const right = range.getRight();
|
const right = range.getRight();
|
||||||
const initialVisibleRange = this.getCurrentVisibleRange();
|
const initialVisibleRange = this.getCurrentVisibleRange();
|
||||||
@@ -75,9 +74,10 @@ class Desktop {
|
|||||||
this.setScroll(targetScrollX, false);
|
this.setScroll(targetScrollX, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollCenterRange(range: Range) {
|
public scrollCenterRange(range: Desktop.Range) {
|
||||||
const scrollAmount = Range.minus(range, this.getCurrentVisibleRange());
|
const windowCenter = range.getLeft() + range.getWidth() / 2;
|
||||||
this.adjustScroll(scrollAmount, true);
|
const screenCenter = this.scrollX + this.tilingArea.width / 2;
|
||||||
|
this.adjustScroll(Math.round(windowCenter - screenCenter), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollCenterVisible(focusedColumn: Column) {
|
public scrollCenterVisible(focusedColumn: Column) {
|
||||||
@@ -93,17 +93,17 @@ class Desktop {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollToColumn(focusedColumn, false);
|
this.scrollToColumn(focusedColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollToColumn(column: Column, force: boolean) {
|
public scrollToColumn(column: Column) {
|
||||||
if (force || this.dirtyScroll || !Range.contains(this.getCurrentVisibleRange(), column)) {
|
if (this.dirtyScroll || !column.isVisible(this.getCurrentVisibleRange(), true)) {
|
||||||
this.config.scroller.scrollToColumn(this, column);
|
this.config.scroller.scrollToColumn(this, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getVisibleRange(scrollX: number) {
|
private getVisibleRange(scrollX: number) {
|
||||||
return Range.create(scrollX, this.tilingArea.width);
|
return new Desktop.RangeImpl(scrollX, this.tilingArea.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentVisibleRange() {
|
public getCurrentVisibleRange() {
|
||||||
@@ -127,22 +127,38 @@ class Desktop {
|
|||||||
this.setScroll(this.scrollX + dx, force);
|
this.setScroll(this.scrollX + dx, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
public gestureScroll(amount: number) {
|
public equalizeVisibleColumnsWidths() {
|
||||||
if (!this.config.gestureScroll) {
|
const visibleRange = this.getCurrentVisibleRange();
|
||||||
return;
|
const visibleColumns = Array.from(this.grid.getVisibleColumns(visibleRange, true));
|
||||||
}
|
|
||||||
if (this.gestureScrollXInitial === null) {
|
let remainingWidth = this.tilingArea.width - (visibleColumns.length-1) * this.grid.config.gapsInnerHorizontal;
|
||||||
this.gestureScrollXInitial = this.scrollX;
|
let remainingColumns = visibleColumns.length;
|
||||||
|
|
||||||
|
const minWidths = visibleColumns.map(column => column.getMinWidth()).sort((a, b) => b - a);
|
||||||
|
for (const minWidth of minWidths) {
|
||||||
|
if (minWidth > remainingWidth / remainingColumns) {
|
||||||
|
remainingWidth -= minWidth;
|
||||||
|
remainingColumns--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.gestureScrollInvert) {
|
const avgWidth = remainingWidth / remainingColumns;
|
||||||
amount = -amount;
|
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.setScroll(this.gestureScrollXInitial + this.config.gestureScrollStep * amount, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public gestureScrollFinish() {
|
this.scrollCenterRange(Desktop.RangeImpl.fromRanges(
|
||||||
this.gestureScrollXInitial = null;
|
visibleColumns[0],
|
||||||
|
visibleColumns[visibleColumns.length - 1],
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public arrange() {
|
public arrange() {
|
||||||
@@ -155,10 +171,6 @@ class Desktop {
|
|||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public forceArrange() {
|
|
||||||
this.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onLayoutChanged() {
|
public onLayoutChanged() {
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
this.dirtyScroll = true;
|
this.dirtyScroll = true;
|
||||||
@@ -176,16 +188,47 @@ class Desktop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace Desktop {
|
namespace Desktop {
|
||||||
export interface Config {
|
export type Config = {
|
||||||
marginTop: number;
|
marginTop: number,
|
||||||
marginBottom: number;
|
marginBottom: number,
|
||||||
marginLeft: number;
|
marginLeft: number,
|
||||||
marginRight: number;
|
marginRight: number,
|
||||||
gestureScroll: boolean;
|
scroller: Desktop.Scroller,
|
||||||
gestureScrollInvert: boolean;
|
clamper: Desktop.Clamper,
|
||||||
gestureScrollStep: number;
|
};
|
||||||
scroller: Desktop.Scroller;
|
|
||||||
clamper: Desktop.Clamper;
|
export type Range = {
|
||||||
|
getLeft(): number;
|
||||||
|
getRight(): number;
|
||||||
|
getWidth(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RangeImpl {
|
||||||
|
private readonly x: number;
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromRanges(leftRange: Range, rightRange: Range) {
|
||||||
|
const left = leftRange.getLeft();
|
||||||
|
const right = rightRange.getRight();
|
||||||
|
return new RangeImpl(left, right - left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ColumnRange {
|
export class ColumnRange {
|
||||||
@@ -199,7 +242,7 @@ namespace Desktop {
|
|||||||
this.width = initialColumn.getWidth();
|
this.width = initialColumn.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public addNeighbors(visibleRange: Range, gap: number) {
|
public addNeighbors(visibleRange: Desktop.Range, gap: number) {
|
||||||
const grid = this.left.grid;
|
const grid = this.left.grid;
|
||||||
|
|
||||||
const columnRange = this;
|
const columnRange = this;
|
||||||
@@ -210,8 +253,8 @@ namespace Desktop {
|
|||||||
return column !== null && canFit(column);
|
return column !== null && canFit(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftColumn = grid.getLeftColumn(this.left);
|
let leftColumn = grid.getPrevColumn(this.left);
|
||||||
let rightColumn = grid.getRightColumn(this.right);
|
let rightColumn = grid.getNextColumn(this.right);
|
||||||
function checkColumns() {
|
function checkColumns() {
|
||||||
if (!isUsable(leftColumn)) {
|
if (!isUsable(leftColumn)) {
|
||||||
leftColumn = null;
|
leftColumn = null;
|
||||||
@@ -228,10 +271,10 @@ namespace Desktop {
|
|||||||
const rightToCenter = rightColumn === null ? Infinity : Math.abs(rightColumn.getRight() - visibleCenter);
|
const rightToCenter = rightColumn === null ? Infinity : Math.abs(rightColumn.getRight() - visibleCenter);
|
||||||
if (leftToCenter < rightToCenter) {
|
if (leftToCenter < rightToCenter) {
|
||||||
this.addLeft(leftColumn!, gap);
|
this.addLeft(leftColumn!, gap);
|
||||||
leftColumn = grid.getLeftColumn(leftColumn!);
|
leftColumn = grid.getPrevColumn(leftColumn!);
|
||||||
} else {
|
} else {
|
||||||
this.addRight(rightColumn!, gap);
|
this.addRight(rightColumn!, gap);
|
||||||
rightColumn = grid.getRightColumn(rightColumn!);
|
rightColumn = grid.getNextColumn(rightColumn!);
|
||||||
}
|
}
|
||||||
checkColumns();
|
checkColumns();
|
||||||
}
|
}
|
||||||
@@ -260,11 +303,11 @@ namespace Desktop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Scroller {
|
export type Scroller = {
|
||||||
scrollToColumn(desktop: Desktop, column: Column): void;
|
scrollToColumn(desktop: Desktop, column: Column): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Clamper {
|
export type Clamper = {
|
||||||
clampScrollX(desktop: Desktop, x: number): number;
|
clampScrollX(desktop: Desktop, x: number): number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import Range = Desktop.Range;
|
||||||
|
|
||||||
class Grid {
|
class Grid {
|
||||||
public readonly desktop: Desktop;
|
public readonly desktop: Desktop;
|
||||||
public readonly config: LayoutConfig;
|
public readonly config: LayoutConfig;
|
||||||
public readonly focusPasser: FocusPassing.Passer;
|
|
||||||
private readonly columns: LinkedList<Column>;
|
private readonly columns: LinkedList<Column>;
|
||||||
private lastFocusedColumn: Column|null;
|
private lastFocusedColumn: Column|null;
|
||||||
private width: number;
|
private width: number;
|
||||||
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(desktop: Desktop, config: LayoutConfig, focusPasser: FocusPassing.Passer) {
|
constructor(desktop: Desktop, config: LayoutConfig) {
|
||||||
this.desktop = desktop;
|
this.desktop = desktop;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.focusPasser = focusPasser;
|
|
||||||
this.columns = new LinkedList();
|
this.columns = new LinkedList();
|
||||||
this.lastFocusedColumn = null;
|
this.lastFocusedColumn = null;
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
@@ -24,13 +24,13 @@ class Grid {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveColumn(column: Column, leftColumn: Column|null) {
|
public moveColumn(column: Column, prevColumn: Column|null) {
|
||||||
if (column === leftColumn) {
|
if (column === prevColumn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const movedLeft = leftColumn === null ? true : column.isToTheRightOf(leftColumn);
|
const movedLeft = prevColumn === null ? true : column.isToTheRightOf(prevColumn);
|
||||||
const firstMovedColumn = movedLeft ? column : this.getRightColumn(column);
|
const firstMovedColumn = movedLeft ? column : this.getNextColumn(column);
|
||||||
this.columns.move(column, leftColumn);
|
this.columns.move(column, prevColumn);
|
||||||
this.columnsSetX(firstMovedColumn);
|
this.columnsSetX(firstMovedColumn);
|
||||||
this.desktop.onLayoutChanged();
|
this.desktop.onLayoutChanged();
|
||||||
this.desktop.autoAdjustScroll();
|
this.desktop.autoAdjustScroll();
|
||||||
@@ -44,11 +44,11 @@ class Grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public moveColumnRight(column: Column) {
|
public moveColumnRight(column: Column) {
|
||||||
const rightColumn = this.columns.getNext(column);
|
const nextColumn = this.columns.getNext(column);
|
||||||
if (rightColumn === null) {
|
if (nextColumn === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.moveColumnLeft(rightColumn);
|
this.moveColumnLeft(nextColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWidth() {
|
public getWidth() {
|
||||||
@@ -59,11 +59,11 @@ class Grid {
|
|||||||
return this.userResize;
|
return this.userResize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLeftColumn(column: Column) {
|
public getPrevColumn(column: Column) {
|
||||||
return this.columns.getPrev(column);
|
return this.columns.getPrev(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRightColumn(column: Column) {
|
public getNextColumn(column: Column) {
|
||||||
return this.columns.getNext(column);
|
return this.columns.getNext(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,19 +106,19 @@ class Grid {
|
|||||||
this.width = x - this.config.gapsInnerHorizontal;
|
this.width = x - this.config.gapsInnerHorizontal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLeftmostVisibleColumn(visibleRange: Range, fullyVisible: boolean) {
|
public getLeftmostVisibleColumn(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
if (Range.contains(visibleRange, column)) {
|
if (column.isVisible(visibleRange, fullyVisible)) {
|
||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRightmostVisibleColumn(visibleRange: Range, fullyVisible: boolean) {
|
public getRightmostVisibleColumn(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||||
let last = null;
|
let last = null;
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
if (Range.contains(visibleRange, column)) {
|
if (column.isVisible(visibleRange, fullyVisible)) {
|
||||||
last = column;
|
last = column;
|
||||||
} else if (last !== null) {
|
} else if (last !== null) {
|
||||||
break;
|
break;
|
||||||
@@ -127,14 +127,29 @@ class Grid {
|
|||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
public *getVisibleColumns(visibleRange: Range, fullyVisible: boolean) {
|
public *getVisibleColumns(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
if (Range.contains(visibleRange, column)) {
|
if (column.isVisible(visibleRange, fullyVisible)) {
|
||||||
yield column;
|
yield column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getVisibleColumnsWidth(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||||
|
let width = 0;
|
||||||
|
let nVisible = 0;
|
||||||
|
for (const column of this.getVisibleColumns(visibleRange, fullyVisible)) {
|
||||||
|
width += column.getWidth();
|
||||||
|
nVisible++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nVisible > 0) {
|
||||||
|
width += (nVisible-1) * this.config.gapsInnerHorizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -147,58 +162,52 @@ class Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onColumnAdded(column: Column, leftColumn: Column|null) {
|
public onColumnAdded(column: Column, prevColumn: Column|null) {
|
||||||
if (leftColumn === null) {
|
if (prevColumn === null) {
|
||||||
this.columns.insertStart(column);
|
this.columns.insertStart(column);
|
||||||
} else {
|
} else {
|
||||||
this.columns.insertAfter(column, leftColumn);
|
this.columns.insertAfter(column, prevColumn);
|
||||||
}
|
}
|
||||||
this.columnsSetX(column);
|
this.columnsSetX(column);
|
||||||
this.desktop.onLayoutChanged();
|
this.desktop.onLayoutChanged();
|
||||||
this.desktop.autoAdjustScroll();
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onColumnRemoved(column: Column, passFocus: FocusPassing.Type) {
|
public onColumnRemoved(column: Column, passFocus: boolean) {
|
||||||
const isLastColumn = this.columns.length() === 1;
|
const isLastColumn = this.columns.length() === 1;
|
||||||
const rightColumn = this.getRightColumn(column);
|
const nextColumn = this.getNextColumn(column);
|
||||||
const columnToFocus = isLastColumn ? null : this.getLeftColumn(column) ?? rightColumn;
|
const columnToFocus = isLastColumn ? null : this.getPrevColumn(column) ?? nextColumn;
|
||||||
if (column === this.lastFocusedColumn) {
|
if (column === this.lastFocusedColumn) {
|
||||||
this.lastFocusedColumn = columnToFocus;
|
this.lastFocusedColumn = columnToFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.columns.remove(column);
|
this.columns.remove(column);
|
||||||
this.columnsSetX(rightColumn);
|
this.columnsSetX(nextColumn);
|
||||||
|
|
||||||
this.desktop.onLayoutChanged();
|
this.desktop.onLayoutChanged();
|
||||||
if (columnToFocus !== null) {
|
if (passFocus && columnToFocus !== null) {
|
||||||
switch (passFocus) {
|
columnToFocus.focus();
|
||||||
case FocusPassing.Type.Immediate:
|
} else {
|
||||||
columnToFocus.getWindowToFocus().focus();
|
this.desktop.autoAdjustScroll();
|
||||||
return;
|
|
||||||
case FocusPassing.Type.OnUnfocus:
|
|
||||||
this.focusPasser.request(columnToFocus.getWindowToFocus().client.kwinClient);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.desktop.autoAdjustScroll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onColumnWidthChanged(column: Column) {
|
public onColumnWidthChanged(column: Column) {
|
||||||
const rightColumn = this.columns.getNext(column);
|
const nextColumn = this.columns.getNext(column);
|
||||||
this.columnsSetX(rightColumn);
|
this.columnsSetX(nextColumn);
|
||||||
this.desktop.onLayoutChanged();
|
this.desktop.onLayoutChanged();
|
||||||
if (!this.userResize) {
|
if (!this.userResize) {
|
||||||
this.desktop.autoAdjustScroll();
|
this.desktop.autoAdjustScroll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onColumnFocused(column: Column, window: Window) {
|
public onColumnFocused(column: Column) {
|
||||||
const lastFocusedColumn = this.getLastFocusedColumn();
|
const lastFocusedColumn = this.getLastFocusedColumn();
|
||||||
if (lastFocusedColumn !== null) {
|
if (lastFocusedColumn !== null) {
|
||||||
lastFocusedColumn.restoreToTiled(window);
|
lastFocusedColumn.restoreToTiled();
|
||||||
}
|
}
|
||||||
this.lastFocusedColumn = column;
|
this.lastFocusedColumn = column;
|
||||||
this.desktop.scrollToColumn(column, false);
|
this.desktop.scrollToColumn(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onScreenSizeChanged() {
|
public onScreenSizeChanged() {
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
interface LayoutConfig {
|
type LayoutConfig = {
|
||||||
gapsInnerHorizontal: number;
|
gapsInnerHorizontal: number,
|
||||||
gapsInnerVertical: number;
|
gapsInnerVertical: number,
|
||||||
stackOffsetX: number;
|
offScreenOpacity: number,
|
||||||
stackOffsetY: number;
|
stackColumnsByDefault: boolean,
|
||||||
offScreenOpacity: number;
|
resizeNeighborColumn: boolean,
|
||||||
stackColumnsByDefault: boolean;
|
reMaximize: boolean,
|
||||||
resizeNeighborColumn: boolean;
|
skipSwitcher: boolean,
|
||||||
reMaximize: boolean;
|
tiledKeepBelow: boolean,
|
||||||
skipSwitcher: boolean;
|
maximizedKeepAbove: boolean,
|
||||||
tiledKeepBelow: boolean;
|
};
|
||||||
maximizedKeepAbove: boolean;
|
|
||||||
untileOnDrag: boolean;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
interface Range {
|
|
||||||
getLeft(): number;
|
|
||||||
getRight(): number;
|
|
||||||
getWidth(): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Range {
|
|
||||||
export function create(x: number, width: number) {
|
|
||||||
return new Basic(x, width);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fromRanges(leftRange: Range, rightRange: Range) {
|
|
||||||
const left = leftRange.getLeft();
|
|
||||||
const right = rightRange.getRight();
|
|
||||||
return new Basic(left, right - left);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function contains(parent: Range, child: Range) {
|
|
||||||
return child.getLeft() >= parent.getLeft() &&
|
|
||||||
child.getRight() <= parent.getRight();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function minus(a: Range, b: Range) {
|
|
||||||
const aCenter = a.getLeft() + a.getWidth() / 2;
|
|
||||||
const bCenter = b.getLeft() + b.getWidth() / 2;
|
|
||||||
return Math.round(aCenter - bCenter);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Basic {
|
|
||||||
constructor(
|
|
||||||
private readonly x: number,
|
|
||||||
private readonly width: number,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public getLeft() {
|
|
||||||
return this.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRight() {
|
|
||||||
return this.x + this.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWidth() {
|
|
||||||
return this.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,28 +8,22 @@ class Window {
|
|||||||
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;
|
||||||
|
|
||||||
let maximizedMode = this.client.getMaximizedMode();
|
|
||||||
if (maximizedMode === undefined) {
|
|
||||||
maximizedMode = MaximizedMode.Unmaximized; // defaulting to unmaximized, as this is set in Tiled.prepareClientForTiling
|
|
||||||
}
|
|
||||||
this.focusedState = {
|
this.focusedState = {
|
||||||
fullScreen: this.client.kwinClient.fullScreen,
|
fullScreen: false,
|
||||||
maximizedMode: maximizedMode,
|
maximizedMode: MaximizedMode.Unmaximized,
|
||||||
};
|
};
|
||||||
|
this.skipArrange = false;
|
||||||
this.skipArrange = this.client.kwinClient.fullScreen || maximizedMode !== MaximizedMode.Unmaximized;
|
|
||||||
this.column = column;
|
this.column = column;
|
||||||
column.onWindowAdded(this, true);
|
column.onWindowAdded(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveToColumn(targetColumn: Column, bottom: boolean, passFocus: FocusPassing.Type) {
|
public moveToColumn(targetColumn: Column) {
|
||||||
if (targetColumn === this.column) {
|
if (targetColumn === this.column) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.column.onWindowRemoved(this, passFocus);
|
this.column.onWindowRemoved(this, false);
|
||||||
this.column = targetColumn;
|
this.column = targetColumn;
|
||||||
targetColumn.onWindowAdded(this, bottom);
|
targetColumn.onWindowAdded(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public arrange(x: number, y: number, width: number, height: number) {
|
public arrange(x: number, y: number, width: number, height: number) {
|
||||||
@@ -60,12 +54,11 @@ class Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.client.focus();
|
if (this.client.isShaded()) {
|
||||||
const kwinClient = this.client.kwinClient;
|
// workaround for KWin deactivating clients when unshading immediately after activation
|
||||||
if (!this.isFocused()) {
|
this.client.setShade(false);
|
||||||
// in some situations focus assignment just doesn't work, let's do it later
|
|
||||||
this.column.grid.focusPasser.request(kwinClient);
|
|
||||||
}
|
}
|
||||||
|
this.client.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFocused() {
|
public isFocused() {
|
||||||
@@ -73,14 +66,6 @@ class Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onFocused() {
|
public onFocused() {
|
||||||
if (this.column.grid.config.reMaximize && (
|
|
||||||
this.focusedState.maximizedMode !== MaximizedMode.Unmaximized ||
|
|
||||||
this.focusedState.fullScreen
|
|
||||||
)) {
|
|
||||||
// We need to maximize/fullscreen this window, but we can't do it here.
|
|
||||||
// We need to do it in `arrange` to ensure it happens after placement.
|
|
||||||
this.column.grid.desktop.forceArrange();
|
|
||||||
}
|
|
||||||
this.column.onWindowFocused(this);
|
this.column.onWindowFocused(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +75,7 @@ class Window {
|
|||||||
}
|
}
|
||||||
this.client.setFullScreen(false);
|
this.client.setFullScreen(false);
|
||||||
this.client.setMaximize(false, false);
|
this.client.setMaximize(false, false);
|
||||||
|
this.column.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onMaximizedChanged(maximizedMode: MaximizedMode) {
|
public onMaximizedChanged(maximizedMode: MaximizedMode) {
|
||||||
@@ -121,20 +107,45 @@ class Window {
|
|||||||
this.column.grid.desktop.onLayoutChanged();
|
this.column.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onUserResize(oldGeometry: QmlRect, resizeNeighborColumn: boolean) {
|
||||||
|
const newGeometry = this.client.kwinClient.frameGeometry;
|
||||||
|
const widthDelta = newGeometry.width - oldGeometry.width;
|
||||||
|
const heightDelta = newGeometry.height - oldGeometry.height;
|
||||||
|
if (widthDelta !== 0) {
|
||||||
|
this.column.adjustWidth(widthDelta, true);
|
||||||
|
let leftEdgeDelta = newGeometry.left - oldGeometry.left;
|
||||||
|
const resizingLeftSide = leftEdgeDelta !== 0;
|
||||||
|
if (resizeNeighborColumn && this.column.grid.config.resizeNeighborColumn) {
|
||||||
|
const neighborColumn = resizingLeftSide ? this.column.grid.getPrevColumn(this.column) : this.column.grid.getNextColumn(this.column);
|
||||||
|
if (neighborColumn !== null) {
|
||||||
|
const oldNeighborWidth = neighborColumn.getWidth();
|
||||||
|
neighborColumn.adjustWidth(-widthDelta, true);
|
||||||
|
if (resizingLeftSide) {
|
||||||
|
leftEdgeDelta -= neighborColumn.getWidth() - oldNeighborWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.column.grid.desktop.adjustScroll(-leftEdgeDelta, true);
|
||||||
|
}
|
||||||
|
if (heightDelta !== 0) {
|
||||||
|
this.column.adjustWindowHeight(this, heightDelta, newGeometry.y !== oldGeometry.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public onFrameGeometryChanged() {
|
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();
|
this.column.grid.desktop.onLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.column.onWindowRemoved(this, passFocus);
|
this.column.onWindowRemoved(this, passFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
export interface State {
|
export type State = {
|
||||||
fullScreen: boolean;
|
fullScreen: boolean,
|
||||||
maximizedMode: MaximizedMode;
|
maximizedMode: MaximizedMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
interface WindowRule {
|
type WindowRule = {
|
||||||
class: string | undefined;
|
class: string | undefined,
|
||||||
caption: string | undefined;
|
caption: string | undefined,
|
||||||
tile: boolean;
|
tile: boolean,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ class WindowRuleEnforcer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public shouldTile(kwinClient: KwinClient) {
|
public shouldTile(kwinClient: KwinClient) {
|
||||||
return this.preferTiling.matches(kwinClient) || (
|
return Clients.canTileNow(kwinClient) && (
|
||||||
kwinClient.normalWindow &&
|
this.preferTiling.matches(kwinClient) || (
|
||||||
!kwinClient.transient &&
|
kwinClient.normalWindow &&
|
||||||
kwinClient.managed &&
|
!kwinClient.transient &&
|
||||||
kwinClient.pid > -1 &&
|
kwinClient.managed &&
|
||||||
!kwinClient.fullScreen &&
|
kwinClient.pid > -1 &&
|
||||||
!Clients.isFullScreenGeometry(kwinClient) &&
|
!this.preferFloating.matches(kwinClient)
|
||||||
!this.preferFloating.matches(kwinClient)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class WindowRuleEnforcer {
|
|||||||
const enforcer = this;
|
const enforcer = this;
|
||||||
const manager = new SignalManager();
|
const manager = new SignalManager();
|
||||||
manager.connect(kwinClient.captionChanged, () => {
|
manager.connect(kwinClient.captionChanged, () => {
|
||||||
const shouldTile = Clients.canTileNow(kwinClient) && enforcer.shouldTile(kwinClient);
|
const shouldTile = enforcer.shouldTile(kwinClient);
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
const desktop = desktopManager.getDesktopForClient(kwinClient);
|
const desktop = desktopManager.getDesktopForClient(kwinClient);
|
||||||
if (shouldTile && desktop !== undefined) {
|
if (shouldTile && desktop !== undefined) {
|
||||||
@@ -52,7 +52,7 @@ class WindowRuleEnforcer {
|
|||||||
const ruleCaption = WindowRuleEnforcer.parseRegex(windowRule.caption);
|
const ruleCaption = WindowRuleEnforcer.parseRegex(windowRule.caption);
|
||||||
const ruleString = ClientMatcher.getRuleString(
|
const ruleString = ClientMatcher.getRuleString(
|
||||||
WindowRuleEnforcer.wrapParens(ruleClass),
|
WindowRuleEnforcer.wrapParens(ruleClass),
|
||||||
WindowRuleEnforcer.wrapParens(ruleCaption),
|
WindowRuleEnforcer.wrapParens(ruleCaption)
|
||||||
);
|
);
|
||||||
|
|
||||||
(windowRule.tile ? tileRegexes : floatRegexes).push(ruleString);
|
(windowRule.tile ? tileRegexes : floatRegexes).push(ruleString);
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ class Delayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initQmlTimer() {
|
function initQmlTimer() {
|
||||||
return Qt.createQmlObject(
|
return <QmlTimer>Qt.createQmlObject(
|
||||||
`import QtQuick 6.0
|
`import QtQuick 6.0
|
||||||
Timer {}`,
|
Timer {}`,
|
||||||
qmlBase,
|
qmlBase
|
||||||
) as QmlTimer;
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
class RateLimiter {
|
|
||||||
private i = 0;
|
|
||||||
private intervalStart = 0;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly n: number,
|
|
||||||
private readonly intervalMs: number,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public acquire() {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.intervalStart >= this.intervalMs) {
|
|
||||||
this.i = 0;
|
|
||||||
this.intervalStart = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.i < this.n) {
|
|
||||||
this.i++;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class ShortcutAction {
|
class ShortcutAction {
|
||||||
private readonly shortcutHandler: ShortcutHandler;
|
private readonly shortcutHandler: ShortcutHandler;
|
||||||
|
|
||||||
constructor(keyBinding: ShortcutAction.KeyBinding, f: () => void) {
|
constructor(keyBinding: KeyBinding, f: () => void) {
|
||||||
this.shortcutHandler = ShortcutAction.initShortcutHandler(keyBinding);
|
this.shortcutHandler = ShortcutAction.initShortcutHandler(keyBinding);
|
||||||
this.shortcutHandler.activated.connect(f);
|
this.shortcutHandler.activated.connect(f);
|
||||||
}
|
}
|
||||||
@@ -10,28 +10,16 @@ class ShortcutAction {
|
|||||||
this.shortcutHandler.destroy();
|
this.shortcutHandler.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static initShortcutHandler(keyBinding: ShortcutAction.KeyBinding) {
|
private static initShortcutHandler(keyBinding: KeyBinding) {
|
||||||
const sequenceLine = keyBinding.defaultKeySequence !== undefined ?
|
return <ShortcutHandler>Qt.createQmlObject(
|
||||||
` sequence: "${keyBinding.defaultKeySequence}";
|
|
||||||
` :
|
|
||||||
"";
|
|
||||||
|
|
||||||
return Qt.createQmlObject(
|
|
||||||
`import QtQuick 6.0
|
`import QtQuick 6.0
|
||||||
import org.kde.kwin 3.0
|
import org.kde.kwin 3.0
|
||||||
ShortcutHandler {
|
ShortcutHandler {
|
||||||
name: "karousel-${keyBinding.name}";
|
name: "karousel-${keyBinding.name}";
|
||||||
text: "Karousel: ${keyBinding.description}";
|
text: "Karousel: ${keyBinding.description}";
|
||||||
${sequenceLine}}`,
|
sequence: "${keyBinding.defaultKeySequence}";
|
||||||
|
}`,
|
||||||
qmlBase,
|
qmlBase,
|
||||||
) as ShortcutHandler;
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ShortcutAction {
|
|
||||||
export interface KeyBinding {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
defaultKeySequence?: string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
function union<T>(array0: T[], array1: T[]) {
|
|
||||||
const set = new Set([...array0, ...array1]);
|
|
||||||
return [...set];
|
|
||||||
}
|
|
||||||
|
|
||||||
function uniq(sortedArray: any[]) {
|
|
||||||
const filtered = [];
|
|
||||||
let lastItem;
|
|
||||||
for (const item of sortedArray) {
|
|
||||||
if (item !== lastItem) {
|
|
||||||
filtered.push(item);
|
|
||||||
lastItem = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapGetOrInit<K, V>(map: Map<K, V>, key: K, defaultItem: V) {
|
|
||||||
const item = map.get(key);
|
|
||||||
if (item !== undefined) {
|
|
||||||
return item;
|
|
||||||
} else {
|
|
||||||
map.set(key, defaultItem);
|
|
||||||
return defaultItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findMinPositive<T>(items: T[], evaluate: (item: T) => number) {
|
|
||||||
let bestScore = Infinity;
|
|
||||||
let bestItem = undefined;
|
|
||||||
for (const item of items) {
|
|
||||||
const score = evaluate(item);
|
|
||||||
if (score > 0 && score < bestScore) {
|
|
||||||
bestScore = score;
|
|
||||||
bestItem = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bestItem;
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
function fillSpace(availableSpace: number, items: { min: number, max: number }[]) {
|
|
||||||
if (items.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const middleSize = findMiddleSize(availableSpace, items);
|
|
||||||
const sizes = items.map(item => clamp(middleSize, item.min, item.max));
|
|
||||||
if (middleSize !== Math.floor(availableSpace / items.length)) {
|
|
||||||
distributeRemainder(availableSpace, middleSize, sizes, items);
|
|
||||||
}
|
|
||||||
return sizes;
|
|
||||||
|
|
||||||
function findMiddleSize(availableSpace: number, items: { min: number, max: number }[]) {
|
|
||||||
const ranges = buildRanges(items);
|
|
||||||
let requiredSpace = items.reduce((acc, item) => acc + item.min, 0);
|
|
||||||
for (const range of ranges) {
|
|
||||||
const rangeSize = range.end - range.start;
|
|
||||||
const maxRequiredSpaceDelta = rangeSize * range.n;
|
|
||||||
if (requiredSpace + maxRequiredSpaceDelta >= availableSpace) {
|
|
||||||
const positionInRange = (availableSpace - requiredSpace) / maxRequiredSpaceDelta;
|
|
||||||
return Math.floor(range.start + rangeSize * positionInRange);
|
|
||||||
}
|
|
||||||
requiredSpace += maxRequiredSpaceDelta;
|
|
||||||
}
|
|
||||||
return ranges[ranges.length-1].end;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildRanges(items: { min: number, max: number }[]) {
|
|
||||||
const fenceposts = extractFenceposts(items);
|
|
||||||
if (fenceposts.length === 1) {
|
|
||||||
return [{
|
|
||||||
start: fenceposts[0].value,
|
|
||||||
end: fenceposts[0].value,
|
|
||||||
n: items.length,
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ranges: Range[] = [];
|
|
||||||
let n = 0;
|
|
||||||
for (let i = 1; i < fenceposts.length; i++) {
|
|
||||||
const startFencepost = fenceposts[i-1];
|
|
||||||
const endFencepost = fenceposts[i];
|
|
||||||
n = n - startFencepost.nMax + startFencepost.nMin;
|
|
||||||
ranges.push({
|
|
||||||
start: startFencepost.value,
|
|
||||||
end: endFencepost.value,
|
|
||||||
n: n,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractFenceposts(items: { min: number, max: number }[]) {
|
|
||||||
const fenceposts = new Map<number, Fencepost>();
|
|
||||||
for (const item of items) {
|
|
||||||
mapGetOrInit(fenceposts, item.min, { value: item.min, nMin: 0, nMax: 0 }).nMin++;
|
|
||||||
mapGetOrInit(fenceposts, item.max, { value: item.max, nMin: 0, nMax: 0 }).nMax++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const array = Array.from(fenceposts.values());
|
|
||||||
array.sort((a, b) => a.value - b.value);
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function distributeRemainder(availableSpace: number, middleSize: number, sizes: number[], constraints: { max: number }[]) {
|
|
||||||
const indexes = Array.from(sizes.keys())
|
|
||||||
.filter(i => sizes[i] === middleSize);
|
|
||||||
indexes.sort((a, b) => constraints[a].max - constraints[b].max);
|
|
||||||
|
|
||||||
const requiredSpace = sum(...sizes);
|
|
||||||
let remainder = availableSpace - requiredSpace;
|
|
||||||
let n = indexes.length;
|
|
||||||
for (const i of indexes) {
|
|
||||||
if (remainder <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const enlargable = constraints[i].max - sizes[i];
|
|
||||||
if (enlargable > 0) {
|
|
||||||
const enlarge = Math.min(enlargable, Math.ceil(remainder / n));
|
|
||||||
sizes[i] += enlarge;
|
|
||||||
remainder -= enlarge;
|
|
||||||
}
|
|
||||||
n--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Range {
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
n: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Fencepost {
|
|
||||||
value: number,
|
|
||||||
nMin: number,
|
|
||||||
nMax: number,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
interface Function {
|
|
||||||
partial<H extends any[], T extends any[], R>(
|
|
||||||
this: (...args: [...H, ...T]) => R,
|
|
||||||
...head: H
|
|
||||||
) : (...tail: T) => R;
|
|
||||||
}
|
|
||||||
|
|
||||||
Function.prototype.partial = function<H extends any[], T extends any[]>(...head: H) {
|
|
||||||
return (...tail: T) => this(...head, ...tail);
|
|
||||||
};
|
|
||||||
@@ -8,25 +8,7 @@ function clamp(value: number, min: number, max: number) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sum(...list: number[]) {
|
function union<T>(array0: T[], array1: T[]) {
|
||||||
return list.reduce((acc, val) => acc + val);
|
const set = new Set([...array0, ...array1]);
|
||||||
}
|
return [...set];
|
||||||
|
|
||||||
function rectEquals(a: QmlRect, b: QmlRect) {
|
|
||||||
return a.x === b.x &&
|
|
||||||
a.y === b.y &&
|
|
||||||
a.width === b.width &&
|
|
||||||
a.height === b.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pointEquals(a: QmlPoint, b: QmlPoint) {
|
|
||||||
return a.x === b.x &&
|
|
||||||
a.y === b.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rectContainsPoint(rect: QmlRect, point: QmlPoint) {
|
|
||||||
return rect.left <= point.x &&
|
|
||||||
rect.right >= point.x &&
|
|
||||||
rect.top <= point.y &&
|
|
||||||
rect.bottom >= point.y;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
function applyMacro(base: string, value: string) {
|
|
||||||
return base.replace("{}", String(value));
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,29 @@
|
|||||||
function initWorkspaceSignalHandlers(world: World, focusPasser: FocusPassing.Passer) {
|
function initWorkspaceSignalHandlers(world: World) {
|
||||||
const manager = new SignalManager();
|
const manager = new SignalManager();
|
||||||
|
|
||||||
manager.connect(Workspace.windowAdded, (kwinClient: KwinClient) => {
|
manager.connect(Workspace.windowAdded, (kwinClient: KwinClient) => {
|
||||||
|
if (Clients.canTileEver(kwinClient)) {
|
||||||
|
// never open new tileable clients on all desktops or activities
|
||||||
|
Clients.makeTileable(kwinClient);
|
||||||
|
}
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
clientManager.addClient(kwinClient);
|
clientManager.addClient(kwinClient)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.windowRemoved, (kwinClient: KwinClient) => {
|
manager.connect(Workspace.windowRemoved, (kwinClient: KwinClient) => {
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
clientManager.removeClient(kwinClient, FocusPassing.Type.Immediate);
|
clientManager.removeClient(kwinClient, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.windowActivated, (kwinClient: KwinClient|null) => {
|
manager.connect(Workspace.windowActivated, (kwinClient: KwinClient) => {
|
||||||
if (kwinClient === null) {
|
if (kwinClient === null) {
|
||||||
focusPasser.activate();
|
return;
|
||||||
} else {
|
|
||||||
focusPasser.clearIfDifferent(kwinClient);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
clientManager.onClientFocused(kwinClient);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
clientManager.onClientFocused(kwinClient);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.currentDesktopChanged, () => {
|
manager.connect(Workspace.currentDesktopChanged, () => {
|
||||||
@@ -32,25 +34,25 @@ function initWorkspaceSignalHandlers(world: World, focusPasser: FocusPassing.Pas
|
|||||||
world.do(() => {}); // re-arrange desktop
|
world.do(() => {}); // re-arrange desktop
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.screensChanged, () => {
|
manager.connect(Workspace.screensChanged, () => {
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
desktopManager.selectScreen(Workspace.activeScreen);
|
desktopManager.updateScreens();
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.activitiesChanged, () => {
|
manager.connect(Workspace.activitiesChanged, () => {
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
desktopManager.updateActivities();
|
desktopManager.updateActivities();
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.desktopsChanged, () => {
|
manager.connect(Workspace.desktopsChanged, () => {
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
desktopManager.updateDesktops();
|
desktopManager.updateDesktops();
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(Workspace.virtualScreenSizeChanged, () => {
|
manager.connect(Workspace.virtualScreenSizeChanged, () => {
|
||||||
world.onScreenResized();
|
world.onScreenResized();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
class ClientManager {
|
class ClientManager {
|
||||||
|
private readonly world: World;
|
||||||
private readonly config: ClientManager.Config;
|
private readonly config: ClientManager.Config;
|
||||||
|
private readonly desktopManager: DesktopManager;
|
||||||
|
private readonly pinManager: PinManager;
|
||||||
private readonly clientMap: Map<KwinClient, ClientWrapper>;
|
private readonly clientMap: Map<KwinClient, ClientWrapper>;
|
||||||
private lastFocusedClient: KwinClient|null;
|
private lastFocusedClient: KwinClient|null;
|
||||||
private readonly windowRuleEnforcer: WindowRuleEnforcer;
|
private readonly windowRuleEnforcer: WindowRuleEnforcer;
|
||||||
|
|
||||||
constructor(
|
constructor(config: Config, world: World, desktopManager: DesktopManager, pinManager: PinManager) {
|
||||||
config: Config,
|
|
||||||
private readonly world: World,
|
|
||||||
private readonly desktopManager: DesktopManager,
|
|
||||||
private readonly pinManager: PinManager,
|
|
||||||
) {
|
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.config = config;
|
this.config = { keepAbove: config.floatingKeepAbove };
|
||||||
this.desktopManager = desktopManager;
|
this.desktopManager = desktopManager;
|
||||||
this.pinManager = pinManager;
|
this.pinManager = pinManager;
|
||||||
this.clientMap = new Map();
|
this.clientMap = new Map();
|
||||||
@@ -29,19 +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 (
|
} else if (this.windowRuleEnforcer.shouldTile(kwinClient) && desktop !== undefined) {
|
||||||
Clients.canTileEver(kwinClient) &&
|
constructState = (client: ClientWrapper) => new ClientState.Tiled(this.world, client, desktop.grid);
|
||||||
this.windowRuleEnforcer.shouldTile(kwinClient)
|
|
||||||
) {
|
|
||||||
Clients.makeTileable(kwinClient);
|
|
||||||
console.assert(Clients.canTileNow(kwinClient));
|
|
||||||
const desktop = this.desktopManager.getDesktopForClient(kwinClient);
|
|
||||||
console.assert(desktop !== undefined);
|
|
||||||
constructState = (client: ClientWrapper) => new ClientState.Tiled(this.world, client, desktop!.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);
|
||||||
}
|
}
|
||||||
@@ -55,21 +47,18 @@ class ClientManager {
|
|||||||
this.clientMap.set(kwinClient, client);
|
this.clientMap.set(kwinClient, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeClient(kwinClient: KwinClient, passFocus: FocusPassing.Type) {
|
public removeClient(kwinClient: KwinClient, passFocus: boolean) {
|
||||||
console.assert(this.hasClient(kwinClient));
|
console.assert(this.hasClient(kwinClient));
|
||||||
const client = this.clientMap.get(kwinClient);
|
const client = this.clientMap.get(kwinClient);
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (kwinClient !== this.lastFocusedClient) {
|
client.destroy(passFocus && kwinClient === this.lastFocusedClient);
|
||||||
passFocus = FocusPassing.Type.None;
|
|
||||||
}
|
|
||||||
client.destroy(passFocus);
|
|
||||||
this.clientMap.delete(kwinClient);
|
this.clientMap.delete(kwinClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findTransientFor(kwinClient: KwinClient) {
|
private findTransientFor(kwinClient: KwinClient) {
|
||||||
if (!kwinClient.transient || kwinClient.transientFor === null) {
|
if (!kwinClient.transient) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,10 +76,9 @@ class ClientManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||||
const passFocus = kwinClient === this.lastFocusedClient ? FocusPassing.Type.Immediate : FocusPassing.Type.None;
|
|
||||||
client.stateManager.setState(
|
client.stateManager.setState(
|
||||||
() => new ClientState.TiledMinimized(this.world, client),
|
() => new ClientState.TiledMinimized(this.world, client),
|
||||||
passFocus,
|
kwinClient === this.lastFocusedClient,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,14 +87,14 @@ class ClientManager {
|
|||||||
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), FocusPassing.Type.None);
|
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public floatClient(client: ClientWrapper) {
|
public floatClient(client: ClientWrapper) {
|
||||||
if (client.stateManager.getState() instanceof ClientState.Floating) {
|
if (client.stateManager.getState() instanceof ClientState.Floating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), FocusPassing.Type.None);
|
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public tileKwinClient(kwinClient: KwinClient, grid: Grid) {
|
public tileKwinClient(kwinClient: KwinClient, grid: Grid) {
|
||||||
@@ -135,7 +123,7 @@ class ClientManager {
|
|||||||
kwinClient.tile = null;
|
kwinClient.tile = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.stateManager.setState(() => new ClientState.Pinned(this.world, this.pinManager, this.desktopManager, kwinClient, this.config), FocusPassing.Type.None);
|
client.stateManager.setState(() => new ClientState.Pinned(this.world, this.pinManager, this.desktopManager, kwinClient, this.config), false);
|
||||||
this.pinManager.addClient(kwinClient);
|
this.pinManager.addClient(kwinClient);
|
||||||
for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) {
|
for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) {
|
||||||
desktop.onPinsChanged();
|
desktop.onPinsChanged();
|
||||||
@@ -148,7 +136,7 @@ class ClientManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.assert(client.stateManager.getState() instanceof ClientState.Pinned);
|
console.assert(client.stateManager.getState() instanceof ClientState.Pinned);
|
||||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, false), FocusPassing.Type.None);
|
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, false), false);
|
||||||
this.pinManager.removeClient(kwinClient);
|
this.pinManager.removeClient(kwinClient);
|
||||||
for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) {
|
for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) {
|
||||||
desktop.onPinsChanged();
|
desktop.onPinsChanged();
|
||||||
@@ -168,9 +156,9 @@ class ClientManager {
|
|||||||
if (desktop === undefined) {
|
if (desktop === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, desktop.grid), FocusPassing.Type.None);
|
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), FocusPassing.Type.None);
|
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,27 +168,27 @@ class ClientManager {
|
|||||||
|
|
||||||
public onClientFocused(kwinClient: KwinClient) {
|
public onClientFocused(kwinClient: KwinClient) {
|
||||||
this.lastFocusedClient = kwinClient;
|
this.lastFocusedClient = kwinClient;
|
||||||
const window = this.findTiledWindow(kwinClient);
|
const window = this.findTiledWindow(kwinClient, true);
|
||||||
if (window !== null) {
|
if (window !== null) {
|
||||||
window.onFocused();
|
window.onFocused();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findTiledWindow(kwinClient: KwinClient) {
|
public findTiledWindow(kwinClient: KwinClient, followTransient: boolean) {
|
||||||
const client = this.clientMap.get(kwinClient);
|
const client = this.clientMap.get(kwinClient);
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.findTiledWindowOfClient(client);
|
return this.findTiledWindowOfClient(client, followTransient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findTiledWindowOfClient(client: ClientWrapper): Window|null {
|
private findTiledWindowOfClient(client: ClientWrapper, followTransient: boolean): Window|null {
|
||||||
const clientState = client.stateManager.getState();
|
const clientState = client.stateManager.getState();
|
||||||
if (clientState instanceof ClientState.Tiled) {
|
if (clientState instanceof ClientState.Tiled) {
|
||||||
return clientState.window;
|
return clientState.window;
|
||||||
} else if (client.transientFor !== null) {
|
} else if (followTransient && client.transientFor !== null) {
|
||||||
return this.findTiledWindowOfClient(client.transientFor);
|
return this.findTiledWindowOfClient(client.transientFor, true);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -208,7 +196,7 @@ class ClientManager {
|
|||||||
|
|
||||||
private removeAllClients() {
|
private removeAllClients() {
|
||||||
for (const kwinClient of Array.from(this.clientMap.keys())) {
|
for (const kwinClient of Array.from(this.clientMap.keys())) {
|
||||||
this.removeClient(kwinClient, FocusPassing.Type.None);
|
this.removeClient(kwinClient, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +206,7 @@ class ClientManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace ClientManager {
|
namespace ClientManager {
|
||||||
export interface Config {
|
export type Config = {
|
||||||
floatingKeepAbove: boolean;
|
keepAbove: boolean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
class ClientWrapper {
|
class ClientWrapper {
|
||||||
|
public readonly kwinClient: KwinClient;
|
||||||
public readonly stateManager: ClientState.Manager;
|
public readonly stateManager: ClientState.Manager;
|
||||||
|
public transientFor: ClientWrapper | null;
|
||||||
private readonly transients: ClientWrapper[];
|
private readonly transients: ClientWrapper[];
|
||||||
private readonly signalManager: SignalManager;
|
private readonly signalManager: SignalManager;
|
||||||
|
private readonly rulesSignalManager: SignalManager | null;
|
||||||
public preferredWidth: number;
|
public preferredWidth: number;
|
||||||
private maximizedMode: MaximizedMode | undefined;
|
private maximizedMode: MaximizedMode | undefined;
|
||||||
private readonly manipulatingGeometry: Doer;
|
private readonly manipulatingGeometry: Doer;
|
||||||
private lastPlacement: QmlRect | null; // workaround for issue #19
|
private lastPlacement: QmlRect | null; // workaround for issue #19
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly kwinClient: KwinClient,
|
kwinClient: KwinClient,
|
||||||
constructInitialState: (client: ClientWrapper) => ClientState.State,
|
constructInitialState: (client: ClientWrapper) => ClientState.State,
|
||||||
public transientFor: ClientWrapper | null,
|
transientFor: ClientWrapper | null,
|
||||||
private readonly rulesSignalManager: SignalManager | null,
|
rulesSignalManager: SignalManager | null,
|
||||||
) {
|
) {
|
||||||
this.kwinClient = kwinClient;
|
this.kwinClient = kwinClient;
|
||||||
this.transientFor = transientFor;
|
this.transientFor = transientFor;
|
||||||
@@ -78,7 +81,6 @@ class ClientWrapper {
|
|||||||
|
|
||||||
public setMaximize(horizontally: boolean, vertically: boolean) {
|
public setMaximize(horizontally: boolean, vertically: boolean) {
|
||||||
if (!this.kwinClient.maximizable) {
|
if (!this.kwinClient.maximizable) {
|
||||||
this.maximizedMode = MaximizedMode.Unmaximized;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +111,16 @@ class ClientWrapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setShade(shade: boolean) {
|
||||||
|
this.manipulatingGeometry.do(() => {
|
||||||
|
this.kwinClient.shade = shade;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public isShaded() {
|
||||||
|
return this.kwinClient.shade;
|
||||||
|
}
|
||||||
|
|
||||||
public getMaximizedMode() {
|
public getMaximizedMode() {
|
||||||
return this.maximizedMode;
|
return this.maximizedMode;
|
||||||
}
|
}
|
||||||
@@ -150,7 +162,7 @@ class ClientWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
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) {
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
namespace Clients {
|
namespace Clients {
|
||||||
const prohibitedClasses = [
|
|
||||||
"ksmserver-logout-greeter",
|
|
||||||
"xwaylandvideobridge",
|
|
||||||
];
|
|
||||||
|
|
||||||
export function canTileEver(kwinClient: KwinClient) {
|
export function canTileEver(kwinClient: KwinClient) {
|
||||||
const shapeable = (kwinClient.moveable && kwinClient.resizeable) || kwinClient.fullScreen; // full-screen windows may become shapeable after exiting full-screen mode
|
return kwinClient.moveable && kwinClient.resizeable && !kwinClient.popupWindow;
|
||||||
return shapeable &&
|
|
||||||
!kwinClient.popupWindow &&
|
|
||||||
!prohibitedClasses.includes(kwinClient.resourceClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canTileNow(kwinClient: KwinClient) {
|
export function canTileNow(kwinClient: KwinClient) {
|
||||||
return canTileEver(kwinClient) &&
|
return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktops.length === 1 && kwinClient.activities.length === 1;
|
||||||
!kwinClient.minimized &&
|
|
||||||
kwinClient.desktops.length === 1 &&
|
|
||||||
kwinClient.activities.length === 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeTileable(kwinClient: KwinClient) {
|
export function makeTileable(kwinClient: KwinClient) {
|
||||||
@@ -32,23 +21,22 @@ namespace Clients {
|
|||||||
|
|
||||||
export function getKwinDesktopApprox(kwinClient: KwinClient) {
|
export function getKwinDesktopApprox(kwinClient: KwinClient) {
|
||||||
switch (kwinClient.desktops.length) {
|
switch (kwinClient.desktops.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return Workspace.currentDesktop;
|
|
||||||
case 1:
|
|
||||||
return kwinClient.desktops[0];
|
|
||||||
default:
|
|
||||||
if (kwinClient.desktops.includes(Workspace.currentDesktop)) {
|
|
||||||
return Workspace.currentDesktop;
|
return Workspace.currentDesktop;
|
||||||
} else {
|
case 1:
|
||||||
return kwinClient.desktops[0];
|
return kwinClient.desktops[0];
|
||||||
}
|
default:
|
||||||
|
if (kwinClient.desktops.includes(Workspace.currentDesktop)) {
|
||||||
|
return Workspace.currentDesktop;
|
||||||
|
} else {
|
||||||
|
return kwinClient.desktops[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFullScreenGeometry(kwinClient: KwinClient) {
|
export function isFullScreenGeometry(kwinClient: KwinClient) {
|
||||||
const fullScreenArea = Workspace.clientArea(ClientAreaOption.FullScreenArea, kwinClient.output, getKwinDesktopApprox(kwinClient));
|
const fullScreenArea = Workspace.clientArea(ClientAreaOption.FullScreenArea, kwinClient.output, getKwinDesktopApprox(kwinClient));
|
||||||
return kwinClient.clientGeometry.width >= fullScreenArea.width &&
|
return kwinClient.frameGeometry === fullScreenArea;
|
||||||
kwinClient.clientGeometry.height >= fullScreenArea.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOnVirtualDesktop(kwinClient: KwinClient, kwinDesktop: KwinDesktop) {
|
export function isOnVirtualDesktop(kwinClient: KwinClient, kwinDesktop: KwinDesktop) {
|
||||||
|
|||||||
@@ -1,68 +1,76 @@
|
|||||||
class DesktopManager {
|
class DesktopManager {
|
||||||
|
// TODO: fix issue with removed and re-added screens
|
||||||
|
|
||||||
|
private readonly pinManager: PinManager;
|
||||||
|
private readonly config: Desktop.Config;
|
||||||
|
public readonly layoutConfig: LayoutConfig;
|
||||||
private readonly desktops: Map<string, Desktop>; // key is activityId|desktopId
|
private readonly desktops: Map<string, Desktop>; // key is activityId|desktopId
|
||||||
private selectedScreen: Output;
|
private kwinScreens: Set<Output>;
|
||||||
private kwinActivities: Set<string>;
|
private kwinActivities: Set<string>;
|
||||||
private kwinDesktops: Set<KwinDesktop>;
|
private kwinDesktops: Set<KwinDesktop>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly pinManager: PinManager,
|
pinManager: PinManager,
|
||||||
private readonly config: Desktop.Config,
|
config: Desktop.Config,
|
||||||
private readonly layoutConfig: LayoutConfig,
|
layoutConfig: LayoutConfig,
|
||||||
private readonly focusPasser: FocusPassing.Passer,
|
currentScreen: Output,
|
||||||
currentActivity: string,
|
currentActivity: string,
|
||||||
currentDesktop: KwinDesktop,
|
currentDesktop: KwinDesktop
|
||||||
) {
|
) {
|
||||||
this.pinManager = pinManager;
|
this.pinManager = pinManager;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.layoutConfig = layoutConfig;
|
this.layoutConfig = layoutConfig;
|
||||||
this.desktops = new Map();
|
this.desktops = new Map();
|
||||||
this.selectedScreen = Workspace.activeScreen;
|
this.kwinScreens = new Set(Workspace.screens);
|
||||||
this.kwinActivities = new Set(Workspace.activities);
|
this.kwinActivities = new Set(Workspace.activities);
|
||||||
this.kwinDesktops = new Set(Workspace.desktops);
|
this.kwinDesktops = new Set(Workspace.desktops);
|
||||||
this.addDesktop(currentActivity, currentDesktop);
|
this.addDesktop(currentScreen, currentActivity, currentDesktop);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDesktop(activity: string, kwinDesktop: KwinDesktop) {
|
public getDesktop(screen: Output, activity: string, kwinDesktop: KwinDesktop) {
|
||||||
const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
|
const desktopKey = DesktopManager.getDesktopKey(screen, activity, kwinDesktop);
|
||||||
const desktop = this.desktops.get(desktopKey);
|
const desktop = this.desktops.get(desktopKey);
|
||||||
if (desktop !== undefined) {
|
if (desktop !== undefined) {
|
||||||
return desktop;
|
return desktop;
|
||||||
} else {
|
} else {
|
||||||
return this.addDesktop(activity, kwinDesktop);
|
return this.addDesktop(screen, activity, kwinDesktop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentDesktop() {
|
public getCurrentDesktop() {
|
||||||
return this.getDesktop(Workspace.currentActivity, Workspace.currentDesktop);
|
return this.getDesktop(Workspace.activeScreen, Workspace.currentActivity, Workspace.currentDesktop);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDesktopInCurrentActivity(kwinDesktop: KwinDesktop) {
|
public getDesktopInCurrentActivity(kwinDesktop: KwinDesktop) {
|
||||||
return this.getDesktop(Workspace.currentActivity, kwinDesktop);
|
return this.getDesktop(Workspace.activeScreen, Workspace.currentActivity, kwinDesktop);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDesktopForClient(kwinClient: KwinClient) {
|
public getDesktopForClient(kwinClient: KwinClient) {
|
||||||
if (kwinClient.activities.length !== 1 || kwinClient.desktops.length !== 1) {
|
if (kwinClient.activities.length !== 1 || kwinClient.desktops.length !== 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.getDesktop(kwinClient.activities[0], kwinClient.desktops[0]);
|
return this.getDesktop(kwinClient.output, kwinClient.activities[0], kwinClient.desktops[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private addDesktop(activity: string, kwinDesktop: KwinDesktop) {
|
private addDesktop(screen: Output, activity: string, kwinDesktop: KwinDesktop) {
|
||||||
const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
|
const desktopKey = DesktopManager.getDesktopKey(screen, activity, kwinDesktop);
|
||||||
const desktop = new Desktop(
|
const desktop = new Desktop(screen, kwinDesktop, this.pinManager, this.config, this.layoutConfig);
|
||||||
kwinDesktop,
|
|
||||||
this.pinManager,
|
|
||||||
this.config,
|
|
||||||
() => this.selectedScreen,
|
|
||||||
this.layoutConfig,
|
|
||||||
this.focusPasser,
|
|
||||||
);
|
|
||||||
this.desktops.set(desktopKey, desktop);
|
this.desktops.set(desktopKey, desktop);
|
||||||
return desktop;
|
return desktop;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getDesktopKey(activity: string, kwinDesktop: KwinDesktop) {
|
private static getDesktopKey(screen: Output, activity: string, kwinDesktop: KwinDesktop) {
|
||||||
return activity + "|" + kwinDesktop.id;
|
return screen.name + "|" + activity + "|" + kwinDesktop.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateScreens() {
|
||||||
|
const newScreens = new Set(Workspace.screens);
|
||||||
|
for (const screen of this.kwinScreens) {
|
||||||
|
if (!newScreens.has(screen)) {
|
||||||
|
this.removeScreen(screen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.kwinScreens = newScreens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateActivities() {
|
public updateActivities() {
|
||||||
@@ -85,24 +93,32 @@ class DesktopManager {
|
|||||||
this.kwinDesktops = newDesktops;
|
this.kwinDesktops = newDesktops;
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectScreen(screen: Output) {
|
private removeScreen(kwinScreen: Output) {
|
||||||
this.selectedScreen = screen;
|
for (const activity of this.kwinActivities) {
|
||||||
|
for (const kwinDesktop of this.kwinDesktops) {
|
||||||
|
this.destroyDesktop(kwinScreen, activity, kwinDesktop);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeActivity(activity: string) {
|
private removeActivity(activity: string) {
|
||||||
for (const kwinDesktop of this.kwinDesktops) {
|
for (const kwinScreen of this.kwinScreens) {
|
||||||
this.destroyDesktop(activity, kwinDesktop);
|
for (const kwinDesktop of this.kwinDesktops) {
|
||||||
|
this.destroyDesktop(kwinScreen, activity, kwinDesktop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeKwinDesktop(kwinDesktop: KwinDesktop) {
|
private removeKwinDesktop(kwinDesktop: KwinDesktop) {
|
||||||
for (const activity of this.kwinActivities) {
|
for (const kwinScreen of this.kwinScreens) {
|
||||||
this.destroyDesktop(activity, kwinDesktop);
|
for (const activity of this.kwinActivities) {
|
||||||
|
this.destroyDesktop(kwinScreen, activity, kwinDesktop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private destroyDesktop(activity: string, kwinDesktop: KwinDesktop) {
|
private destroyDesktop(screen: Output, activity: string, kwinDesktop: KwinDesktop) {
|
||||||
const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
|
const desktopKey = DesktopManager.getDesktopKey(screen, activity, kwinDesktop);
|
||||||
const desktop = this.desktops.get(desktopKey);
|
const desktop = this.desktops.get(desktopKey);
|
||||||
if (desktop !== undefined) {
|
if (desktop !== undefined) {
|
||||||
desktop.destroy();
|
desktop.destroy();
|
||||||
@@ -123,20 +139,23 @@ class DesktopManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getDesktopsForClient(kwinClient: KwinClient) {
|
public getDesktopsForClient(kwinClient: KwinClient) {
|
||||||
const desktops = this.getDesktops(kwinClient.activities, kwinClient.desktops); // workaround for QTBUG-109880
|
const desktops = this.getDesktops([kwinClient.output], kwinClient.activities, kwinClient.desktops); // workaround for QTBUG-109880
|
||||||
return desktops;
|
return desktops;
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty array means all
|
// empty array means all
|
||||||
public *getDesktops(activities: string[], kwinDesktops: KwinDesktop[]) {
|
public *getDesktops(screens: Output[], activities: string[], kwinDesktops: KwinDesktop[]) {
|
||||||
|
const matchedScreens = screens.length > 0 ? screens : this.kwinScreens.keys();
|
||||||
const matchedActivities = activities.length > 0 ? activities : this.kwinActivities.keys();
|
const matchedActivities = activities.length > 0 ? activities : this.kwinActivities.keys();
|
||||||
const matchedDesktops = kwinDesktops.length > 0 ? kwinDesktops : this.kwinDesktops.keys();
|
const matchedDesktops = kwinDesktops.length > 0 ? kwinDesktops : this.kwinDesktops.keys();
|
||||||
for (const matchedActivity of matchedActivities) {
|
for (const matchedScreen of matchedScreens) {
|
||||||
for (const matchedDesktop of matchedDesktops) {
|
for (const matchedActivity of matchedActivities) {
|
||||||
const desktopKey = DesktopManager.getDesktopKey(matchedActivity, matchedDesktop);
|
for (const matchedDesktop of matchedDesktops) {
|
||||||
const desktop = this.desktops.get(desktopKey);
|
const desktopKey = DesktopManager.getDesktopKey(matchedScreen, matchedActivity, matchedDesktop);
|
||||||
if (desktop !== undefined) {
|
const desktop = this.desktops.get(desktopKey);
|
||||||
yield desktop;
|
if (desktop !== undefined) {
|
||||||
|
yield desktop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
namespace FocusPassing {
|
|
||||||
export const enum Type {
|
|
||||||
None,
|
|
||||||
Immediate,
|
|
||||||
OnUnfocus,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Passer {
|
|
||||||
private currentRequest: Request | null = null;
|
|
||||||
|
|
||||||
public request(target: KwinClient) {
|
|
||||||
this.currentRequest = new Request(target, Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public clear() {
|
|
||||||
this.currentRequest = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public clearIfDifferent(kwinClient: KwinClient) {
|
|
||||||
if (this.currentRequest !== null && this.currentRequest.target !== kwinClient) {
|
|
||||||
this.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public activate() {
|
|
||||||
if (this.currentRequest === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.currentRequest.isExpired()) {
|
|
||||||
this.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Workspace.activeWindow = this.currentRequest.target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Request {
|
|
||||||
private static readonly validMs = 200;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public readonly target: KwinClient,
|
|
||||||
private readonly time: number,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public isExpired() {
|
|
||||||
return Date.now() - this.time > Request.validMs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
class PinManager {
|
class PinManager {
|
||||||
|
// TODO: per-screen
|
||||||
|
|
||||||
private readonly pinnedClients: Set<KwinClient>;
|
private readonly pinnedClients: Set<KwinClient>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -17,7 +19,7 @@ class PinManager {
|
|||||||
const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right);
|
const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right);
|
||||||
let lots = [baseLot];
|
let lots = [baseLot];
|
||||||
for (const client of this.pinnedClients) {
|
for (const client of this.pinnedClients) {
|
||||||
if (!Clients.isOnVirtualDesktop(client, kwinDesktop) || client.minimized) {
|
if (!Clients.isOnVirtualDesktop(client, kwinDesktop)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,19 @@
|
|||||||
class World {
|
class World {
|
||||||
|
public readonly untileOnDrag: boolean;
|
||||||
private readonly desktopManager: DesktopManager;
|
private readonly desktopManager: DesktopManager;
|
||||||
private readonly clientManager: ClientManager;
|
public readonly clientManager: ClientManager;
|
||||||
private readonly pinManager: PinManager;
|
private readonly pinManager: PinManager;
|
||||||
private readonly workspaceSignalManager: SignalManager;
|
private readonly workspaceSignalManager: SignalManager;
|
||||||
private readonly shortcutActions: ShortcutAction[];
|
private readonly shortcutActions: ShortcutAction[];
|
||||||
private readonly screenResizedDelayer: Delayer;
|
private readonly screenResizedDelayer: Delayer;
|
||||||
private readonly cursorFollowsFocus: boolean;
|
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
const focusPasser = new FocusPassing.Passer();
|
this.untileOnDrag = config.untileOnDrag;
|
||||||
this.workspaceSignalManager = initWorkspaceSignalHandlers(this, focusPasser);
|
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
||||||
this.cursorFollowsFocus = config.cursorFollowsFocus;
|
|
||||||
|
|
||||||
let presetWidths = {
|
|
||||||
next: (currentWidth: number, minWidth: number, maxWidth: number) => currentWidth,
|
|
||||||
prev: (currentWidth: number, minWidth: number, maxWidth: number) => currentWidth,
|
|
||||||
getWidths: (minWidth: number, maxWidth: number): number[] => [],
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
presetWidths = new PresetWidths(config.presetWidths, config.gapsInnerHorizontal);
|
|
||||||
} catch (error: any) {
|
|
||||||
notificationInvalidPresetWidths.sendEvent();
|
|
||||||
log("failed to parse presetWidths:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.shortcutActions = registerKeyBindings(this, {
|
this.shortcutActions = registerKeyBindings(this, {
|
||||||
manualScrollStep: config.manualScrollStep,
|
manualScrollStep: config.manualScrollStep,
|
||||||
presetWidths: presetWidths,
|
manualResizeStep: config.manualResizeStep,
|
||||||
columnResizer: config.scrollingCentered ? new RawResizer(presetWidths) : new ContextualResizer(presetWidths),
|
columnResizer: config.scrollingCentered ? new RawResizer() : new ContextualResizer(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.screenResizedDelayer = new Delayer(1000, () => {
|
this.screenResizedDelayer = new Delayer(1000, () => {
|
||||||
@@ -43,8 +29,6 @@ class World {
|
|||||||
const layoutConfig = {
|
const layoutConfig = {
|
||||||
gapsInnerHorizontal: config.gapsInnerHorizontal,
|
gapsInnerHorizontal: config.gapsInnerHorizontal,
|
||||||
gapsInnerVertical: config.gapsInnerVertical,
|
gapsInnerVertical: config.gapsInnerVertical,
|
||||||
stackOffsetX: config.stackOffsetX,
|
|
||||||
stackOffsetY: config.stackOffsetY,
|
|
||||||
offScreenOpacity: config.offScreenOpacity / 100.0,
|
offScreenOpacity: config.offScreenOpacity / 100.0,
|
||||||
stackColumnsByDefault: config.stackColumnsByDefault,
|
stackColumnsByDefault: config.stackColumnsByDefault,
|
||||||
resizeNeighborColumn: config.resizeNeighborColumn,
|
resizeNeighborColumn: config.resizeNeighborColumn,
|
||||||
@@ -52,7 +36,6 @@ class World {
|
|||||||
skipSwitcher: config.skipSwitcher,
|
skipSwitcher: config.skipSwitcher,
|
||||||
tiledKeepBelow: config.tiledKeepBelow,
|
tiledKeepBelow: config.tiledKeepBelow,
|
||||||
maximizedKeepAbove: config.floatingKeepAbove,
|
maximizedKeepAbove: config.floatingKeepAbove,
|
||||||
untileOnDrag: config.untileOnDrag,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.desktopManager = new DesktopManager(
|
this.desktopManager = new DesktopManager(
|
||||||
@@ -64,12 +47,9 @@ class World {
|
|||||||
marginRight: config.gapsOuterRight,
|
marginRight: config.gapsOuterRight,
|
||||||
scroller: World.createScroller(config),
|
scroller: World.createScroller(config),
|
||||||
clamper: config.scrollingLazy ? new EdgeClamper() : new CenterClamper(),
|
clamper: config.scrollingLazy ? new EdgeClamper() : new CenterClamper(),
|
||||||
gestureScroll: config.gestureScroll,
|
|
||||||
gestureScrollInvert: config.gestureScrollInvert,
|
|
||||||
gestureScrollStep: config.gestureScrollStep,
|
|
||||||
},
|
},
|
||||||
layoutConfig,
|
layoutConfig,
|
||||||
focusPasser,
|
Workspace.activeScreen,
|
||||||
Workspace.currentActivity,
|
Workspace.currentActivity,
|
||||||
Workspace.currentDesktop,
|
Workspace.currentDesktop,
|
||||||
);
|
);
|
||||||
@@ -92,24 +72,15 @@ class World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private addExistingClients() {
|
private addExistingClients() {
|
||||||
for (const kwinClient of Workspace.windows) {
|
const kwinClients = Workspace.windows;
|
||||||
|
for (let i = 0; i < kwinClients.length; i++) {
|
||||||
|
const kwinClient = kwinClients[i];
|
||||||
this.clientManager.addClient(kwinClient);
|
this.clientManager.addClient(kwinClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
private update() {
|
||||||
this.desktopManager.getCurrentDesktop().arrange();
|
this.desktopManager.getCurrentDesktop().arrange();
|
||||||
this.moveCursorToFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private moveCursorToFocus() {
|
|
||||||
if (this.cursorFollowsFocus && Workspace.activeWindow !== null) {
|
|
||||||
const cursorAlreadyInFocus = rectContainsPoint(Workspace.activeWindow.frameGeometry, Workspace.cursorPos);
|
|
||||||
if (cursorAlreadyInFocus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveCursorToFocus.call();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public do(f: (clientManager: ClientManager, desktopManager: DesktopManager) => void) {
|
public do(f: (clientManager: ClientManager, desktopManager: DesktopManager) => void) {
|
||||||
@@ -119,9 +90,10 @@ class World {
|
|||||||
|
|
||||||
public doIfTiled(
|
public doIfTiled(
|
||||||
kwinClient: KwinClient,
|
kwinClient: KwinClient,
|
||||||
|
followTransient: boolean,
|
||||||
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
|
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
|
||||||
) {
|
) {
|
||||||
const window = this.clientManager.findTiledWindow(kwinClient);
|
const window = this.clientManager.findTiledWindow(kwinClient, followTransient);
|
||||||
if (window === null) {
|
if (window === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -132,20 +104,10 @@ class World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public doIfTiledFocused(
|
public doIfTiledFocused(
|
||||||
|
followTransient: boolean,
|
||||||
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
|
f: (clientManager: ClientManager, desktopManager: DesktopManager, window: Window, column: Column, grid: Grid) => void,
|
||||||
) {
|
) {
|
||||||
if (Workspace.activeWindow === null) {
|
this.doIfTiled(Workspace.activeWindow, followTransient, f);
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.doIfTiled(Workspace.activeWindow, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public gestureScroll(amount: number) {
|
|
||||||
this.do((clientManager, desktopManager) => desktopManager.getCurrentDesktop().gestureScroll(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
public gestureScrollFinish() {
|
|
||||||
this.do((clientManager, desktopManager) => desktopManager.getCurrentDesktop().gestureScrollFinish());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace ClientState {
|
|||||||
world.onScreenResized();
|
world.onScreenResized();
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.signalManager.destroy();
|
this.signalManager.destroy();
|
||||||
this.world.onScreenResized();
|
this.world.onScreenResized();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace ClientState {
|
|||||||
constructor(world: World, client: ClientWrapper, config: ClientManager.Config, limitHeight: boolean) {
|
constructor(world: World, client: ClientWrapper, config: ClientManager.Config, limitHeight: boolean) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
if (config.floatingKeepAbove) {
|
if (config.keepAbove) {
|
||||||
client.kwinClient.keepAbove = true;
|
client.kwinClient.keepAbove = true;
|
||||||
}
|
}
|
||||||
if (limitHeight && client.kwinClient.tile === null) {
|
if (limitHeight && client.kwinClient.tile === null) {
|
||||||
@@ -16,11 +16,13 @@ namespace ClientState {
|
|||||||
this.signalManager = Floating.initSignalManager(world, client.kwinClient);
|
this.signalManager = Floating.initSignalManager(world, client.kwinClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.signalManager.destroy();
|
this.signalManager.destroy();
|
||||||
|
if (this.config.keepAbove) {
|
||||||
|
this.client.kwinClient.keepAbove = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to `Tiled.restoreClientAfterTiling`
|
|
||||||
private static limitHeight(client: ClientWrapper) {
|
private static limitHeight(client: ClientWrapper) {
|
||||||
const placementArea = Workspace.clientArea(
|
const placementArea = Workspace.clientArea(
|
||||||
ClientAreaOption.PlacementArea,
|
ClientAreaOption.PlacementArea,
|
||||||
@@ -48,7 +50,7 @@ namespace ClientState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(kwinClient.frameGeometryChanged, () => {
|
manager.connect(kwinClient.frameGeometryChanged, () => {
|
||||||
// on Wayland, this fires after `tileChanged`
|
// on Wayland, this fires after `tileChanged`
|
||||||
if (kwinClient.tile !== null) {
|
if (kwinClient.tile !== null) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace ClientState {
|
|||||||
this.state = initialState;
|
this.state = initialState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setState(constructNewState: () => State, passFocus: FocusPassing.Type) {
|
public setState(constructNewState: () => State, passFocus: boolean) {
|
||||||
this.state.destroy(passFocus);
|
this.state.destroy(passFocus);
|
||||||
this.state = constructNewState();
|
this.state = constructNewState();
|
||||||
}
|
}
|
||||||
@@ -15,12 +15,12 @@ namespace ClientState {
|
|||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.state.destroy(passFocus);
|
this.state.destroy(passFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export type State = {
|
||||||
destroy(passFocus: FocusPassing.Type): void;
|
destroy(passFocus: boolean): void,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,17 @@ namespace ClientState {
|
|||||||
this.pinManager = pinManager;
|
this.pinManager = pinManager;
|
||||||
this.desktopManager = desktopManager;
|
this.desktopManager = desktopManager;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
if (config.floatingKeepAbove) {
|
if (config.keepAbove) {
|
||||||
kwinClient.keepAbove = true;
|
kwinClient.keepAbove = true;
|
||||||
}
|
}
|
||||||
this.signalManager = Pinned.initSignalManager(world, pinManager, kwinClient);
|
this.signalManager = Pinned.initSignalManager(world, pinManager, kwinClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.signalManager.destroy();
|
this.signalManager.destroy();
|
||||||
|
if (this.config.keepAbove) {
|
||||||
|
this.kwinClient.keepAbove = true;
|
||||||
|
}
|
||||||
this.pinManager.removeClient(this.kwinClient);
|
this.pinManager.removeClient(this.kwinClient);
|
||||||
for (const desktop of this.desktopManager.getDesktopsForClient(this.kwinClient)) {
|
for (const desktop of this.desktopManager.getDesktopsForClient(this.kwinClient)) {
|
||||||
desktop.onPinsChanged();
|
desktop.onPinsChanged();
|
||||||
@@ -50,15 +53,7 @@ namespace ClientState {
|
|||||||
for (const desktop of desktopManager.getDesktopsForClient(kwinClient)) {
|
for (const desktop of desktopManager.getDesktopsForClient(kwinClient)) {
|
||||||
desktop.onPinsChanged();
|
desktop.onPinsChanged();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
manager.connect(kwinClient.minimizedChanged, () => {
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
for (const desktop of desktopManager.getDesktopsForClient(kwinClient)) {
|
|
||||||
desktop.onPinsChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(kwinClient.desktopsChanged, () => {
|
manager.connect(kwinClient.desktopsChanged, () => {
|
||||||
@@ -66,7 +61,7 @@ namespace ClientState {
|
|||||||
[] :
|
[] :
|
||||||
union(oldDesktops, kwinClient.desktops);
|
union(oldDesktops, kwinClient.desktops);
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
for (const desktop of desktopManager.getDesktops(kwinClient.activities, changedDesktops)) {
|
for (const desktop of desktopManager.getDesktops([kwinClient.output], kwinClient.activities, changedDesktops)) {
|
||||||
desktop.onPinsChanged();
|
desktop.onPinsChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -78,13 +73,21 @@ namespace ClientState {
|
|||||||
[] :
|
[] :
|
||||||
union(oldActivities, kwinClient.activities);
|
union(oldActivities, kwinClient.activities);
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
for (const desktop of desktopManager.getDesktops(changedActivities, kwinClient.desktops)) {
|
for (const desktop of desktopManager.getDesktops([kwinClient.output], changedActivities, kwinClient.desktops)) {
|
||||||
desktop.onPinsChanged();
|
desktop.onPinsChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
oldActivities = kwinClient.activities;
|
oldActivities = kwinClient.activities;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
manager.connect(kwinClient.outputChanged, () => {
|
||||||
|
world.do((clientManager, desktopManager) => {
|
||||||
|
for (const desktop of desktopManager.getDesktops([kwinClient.output], kwinClient.activities, kwinClient.desktops)) {
|
||||||
|
desktop.onPinsChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ namespace ClientState {
|
|||||||
public readonly window: Window;
|
public readonly window: Window;
|
||||||
private readonly defaultState: Tiled.WindowState;
|
private readonly defaultState: Tiled.WindowState;
|
||||||
private readonly signalManager: SignalManager;
|
private readonly signalManager: SignalManager;
|
||||||
private static readonly maxExternalFrameGeometryChangedIntervalMs = 1000;
|
|
||||||
|
|
||||||
constructor(world: World, client: ClientWrapper, grid: Grid) {
|
constructor(world: World, client: ClientWrapper, grid: Grid) {
|
||||||
this.defaultState = { skipSwitcher: client.kwinClient.skipSwitcher };
|
this.defaultState = { skipSwitcher: client.kwinClient.skipSwitcher };
|
||||||
@@ -13,10 +12,10 @@ namespace ClientState {
|
|||||||
const window = new Window(client, column);
|
const window = new Window(client, column);
|
||||||
|
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.signalManager = Tiled.initSignalManager(world, window, grid.config);
|
this.signalManager = Tiled.initSignalManager(world, window);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.signalManager.destroy();
|
this.signalManager.destroy();
|
||||||
|
|
||||||
const window = this.window;
|
const window = this.window;
|
||||||
@@ -27,7 +26,7 @@ namespace ClientState {
|
|||||||
Tiled.restoreClientAfterTiling(client, grid.config, this.defaultState, grid.desktop.clientArea);
|
Tiled.restoreClientAfterTiling(client, grid.config, this.defaultState, grid.desktop.clientArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static initSignalManager(world: World, window: Window, config: LayoutConfig) {
|
private static initSignalManager(world: World, window: Window) {
|
||||||
const client = window.client;
|
const client = window.client;
|
||||||
const kwinClient = client.kwinClient;
|
const kwinClient = client.kwinClient;
|
||||||
const manager = new SignalManager();
|
const manager = new SignalManager();
|
||||||
@@ -37,7 +36,7 @@ namespace ClientState {
|
|||||||
const desktop = desktopManager.getDesktopForClient(kwinClient);
|
const desktop = desktopManager.getDesktopForClient(kwinClient);
|
||||||
if (desktop === undefined) {
|
if (desktop === undefined) {
|
||||||
// windows on multiple desktops are not supported
|
// windows on multiple desktops are not supported
|
||||||
clientManager.floatClient(client);
|
clientManager.floatKwinClient(kwinClient);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Tiled.moveWindowToGrid(window, desktop.grid);
|
Tiled.moveWindowToGrid(window, desktop.grid);
|
||||||
@@ -49,12 +48,12 @@ namespace ClientState {
|
|||||||
const desktop = desktopManager.getDesktopForClient(kwinClient);
|
const desktop = desktopManager.getDesktopForClient(kwinClient);
|
||||||
if (desktop === undefined) {
|
if (desktop === undefined) {
|
||||||
// windows on multiple activities are not supported
|
// windows on multiple activities are not supported
|
||||||
clientManager.floatClient(client);
|
clientManager.floatKwinClient(kwinClient);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Tiled.moveWindowToGrid(window, desktop.grid);
|
Tiled.moveWindowToGrid(window, desktop.grid);
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
|
||||||
manager.connect(kwinClient.minimizedChanged, () => {
|
manager.connect(kwinClient.minimizedChanged, () => {
|
||||||
console.assert(kwinClient.minimized);
|
console.assert(kwinClient.minimized);
|
||||||
@@ -69,51 +68,33 @@ namespace ClientState {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let moving = false;
|
|
||||||
let resizing = false;
|
let resizing = false;
|
||||||
let resizeStartWidth = 0;
|
let resizingBorder = false;
|
||||||
let resizeNeighbor: { column: Column, startWidth: number } | undefined;
|
|
||||||
manager.connect(kwinClient.interactiveMoveResizeStarted, () => {
|
manager.connect(kwinClient.interactiveMoveResizeStarted, () => {
|
||||||
if (kwinClient.move) {
|
if (kwinClient.move) {
|
||||||
if (config.untileOnDrag) {
|
if (world.untileOnDrag) {
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do((clientManager, desktopManager) => {
|
||||||
clientManager.floatClient(client);
|
clientManager.floatKwinClient(kwinClient);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
moving = true;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kwinClient.resize) {
|
if (kwinClient.resize) {
|
||||||
resizing = true;
|
resizing = true;
|
||||||
resizeStartWidth = window.column.getWidth();
|
resizingBorder = Workspace.cursorPos.x > kwinClient.clientGeometry.right ||
|
||||||
if (config.resizeNeighborColumn) {
|
Workspace.cursorPos.x < kwinClient.clientGeometry.left;
|
||||||
const resizeNeighborColumn = Tiled.getResizeNeighborColumn(window);
|
|
||||||
if (resizeNeighborColumn !== null) {
|
|
||||||
resizeNeighbor = {
|
|
||||||
column: resizeNeighborColumn,
|
|
||||||
startWidth: resizeNeighborColumn.getWidth(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.column.grid.onUserResizeStarted();
|
window.column.grid.onUserResizeStarted();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(kwinClient.interactiveMoveResizeFinished, () => {
|
manager.connect(kwinClient.interactiveMoveResizeFinished, () => {
|
||||||
if (moving) {
|
|
||||||
moving = false;
|
|
||||||
world.do(() => window.column.grid.desktop.onLayoutChanged()); // move the dragged window back to its position
|
|
||||||
}
|
|
||||||
if (resizing) {
|
if (resizing) {
|
||||||
resizing = false;
|
resizing = false;
|
||||||
resizeNeighbor = undefined;
|
|
||||||
window.column.grid.onUserResizeFinished();
|
window.column.grid.onUserResizeFinished();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const externalFrameGeometryChangedRateLimiter = new RateLimiter(4, Tiled.maxExternalFrameGeometryChangedIntervalMs);
|
|
||||||
manager.connect(kwinClient.frameGeometryChanged, (oldGeometry: QmlRect) => {
|
manager.connect(kwinClient.frameGeometryChanged, (oldGeometry: QmlRect) => {
|
||||||
// on Wayland, this fires after `tileChanged`
|
// on Wayland, this fires after `tileChanged`
|
||||||
if (kwinClient.tile !== null) {
|
if (kwinClient.tile !== null) {
|
||||||
@@ -131,51 +112,23 @@ namespace ClientState {
|
|||||||
const dx = Math.round(newCenterX - oldCenterX);
|
const dx = Math.round(newCenterX - oldCenterX);
|
||||||
const dy = Math.round(newCenterY - oldCenterY);
|
const dy = Math.round(newCenterY - oldCenterY);
|
||||||
if (dx !== 0 || dy !== 0) {
|
if (dx !== 0 || dy !== 0) {
|
||||||
// TODO: instead of passing dx and dy, remember relative (to the parent) x and y for each
|
|
||||||
// transient window and use them for `moveTransients` and `ensureTransientsVisible`
|
|
||||||
client.moveTransients(dx, dy);
|
client.moveTransients(dx, dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kwinClient.resize) {
|
if (kwinClient.resize) {
|
||||||
world.do(() => {
|
world.do(() => window.onUserResize(oldGeometry, resizingBorder));
|
||||||
if (newGeometry.width !== oldGeometry.width) {
|
|
||||||
window.column.onUserResizeWidth(
|
|
||||||
resizeStartWidth,
|
|
||||||
newGeometry.width - resizeStartWidth,
|
|
||||||
newGeometry.left !== oldGeometry.left,
|
|
||||||
resizeNeighbor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (newGeometry.height !== oldGeometry.height) {
|
|
||||||
window.column.adjustWindowHeight(
|
|
||||||
window,
|
|
||||||
newGeometry.height - oldGeometry.height,
|
|
||||||
newGeometry.y !== oldGeometry.y,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (
|
} else if (
|
||||||
!window.column.grid.isUserResizing() &&
|
!window.column.grid.isUserResizing() &&
|
||||||
!client.isManipulatingGeometry(newGeometry) &&
|
!client.isManipulatingGeometry(newGeometry) &&
|
||||||
client.getMaximizedMode() === MaximizedMode.Unmaximized &&
|
client.getMaximizedMode() === MaximizedMode.Unmaximized &&
|
||||||
!Clients.isFullScreenGeometry(kwinClient) // not using `kwinClient.fullScreen` because it may not be set yet at this point
|
!Clients.isFullScreenGeometry(kwinClient) // not using `kwinClient.fullScreen` because it may not be set yet at this point
|
||||||
) {
|
) {
|
||||||
if (externalFrameGeometryChangedRateLimiter.acquire()) {
|
world.do(() => window.onFrameGeometryChanged());
|
||||||
world.do(() => window.onFrameGeometryChanged());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(kwinClient.fullScreenChanged, () => {
|
manager.connect(kwinClient.fullScreenChanged, () => {
|
||||||
world.do((clientManager, desktopManager) => {
|
world.do(() => window.onFullScreenChanged(kwinClient.fullScreen));
|
||||||
// some clients only turn out to be untileable after exiting full-screen mode
|
|
||||||
if (!Clients.canTileEver(kwinClient)) {
|
|
||||||
clientManager.floatClient(client);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onFullScreenChanged(kwinClient.fullScreen);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(kwinClient.tileChanged, () => {
|
manager.connect(kwinClient.tileChanged, () => {
|
||||||
@@ -190,18 +143,6 @@ namespace ClientState {
|
|||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getResizeNeighborColumn(window: Window) {
|
|
||||||
const kwinClient = window.client.kwinClient;
|
|
||||||
const column = window.column;
|
|
||||||
if (Workspace.cursorPos.x > kwinClient.clientGeometry.right) {
|
|
||||||
return column.grid.getRightColumn(column);
|
|
||||||
} else if (Workspace.cursorPos.x < kwinClient.clientGeometry.left) {
|
|
||||||
return column.grid.getLeftColumn(column);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static moveWindowToGrid(window: Window, grid: Grid) {
|
private static moveWindowToGrid(window: Window, grid: Grid) {
|
||||||
if (grid === window.column.grid) {
|
if (grid === window.column.grid) {
|
||||||
// window already on the given grid
|
// window already on the given grid
|
||||||
@@ -209,26 +150,17 @@ namespace ClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newColumn = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
const newColumn = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
||||||
const passFocus = window.isFocused() ? FocusPassing.Type.OnUnfocus : FocusPassing.Type.None;
|
window.moveToColumn(newColumn);
|
||||||
window.moveToColumn(newColumn, true, passFocus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static prepareClientForTiling(client: ClientWrapper, config: LayoutConfig) {
|
private static prepareClientForTiling(client: ClientWrapper, config: LayoutConfig) {
|
||||||
if (config.skipSwitcher) {
|
if (config.skipSwitcher) {
|
||||||
client.kwinClient.skipSwitcher = true;
|
client.kwinClient.skipSwitcher = true;
|
||||||
}
|
}
|
||||||
|
if (config.tiledKeepBelow) {
|
||||||
if (client.kwinClient.fullScreen) {
|
client.kwinClient.keepBelow = true;
|
||||||
if (config.maximizedKeepAbove) {
|
|
||||||
client.kwinClient.keepAbove = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (config.tiledKeepBelow) {
|
|
||||||
client.kwinClient.keepBelow = true;
|
|
||||||
}
|
|
||||||
client.kwinClient.keepAbove = false;
|
|
||||||
}
|
}
|
||||||
|
client.setFullScreen(false);
|
||||||
if (client.kwinClient.tile !== null) {
|
if (client.kwinClient.tile !== null) {
|
||||||
client.setMaximize(false, true); // disable quick tile mode
|
client.setMaximize(false, true); // disable quick tile mode
|
||||||
}
|
}
|
||||||
@@ -245,6 +177,7 @@ namespace ClientState {
|
|||||||
if (config.offScreenOpacity < 1.0) {
|
if (config.offScreenOpacity < 1.0) {
|
||||||
client.kwinClient.opacity = 1.0;
|
client.kwinClient.opacity = 1.0;
|
||||||
}
|
}
|
||||||
|
client.setShade(false);
|
||||||
client.setFullScreen(false);
|
client.setFullScreen(false);
|
||||||
if (client.kwinClient.tile === null) {
|
if (client.kwinClient.tile === null) {
|
||||||
client.setMaximize(false, false);
|
client.setMaximize(false, false);
|
||||||
@@ -254,8 +187,8 @@ namespace ClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace Tiled {
|
namespace Tiled {
|
||||||
export interface WindowState {
|
export type WindowState = {
|
||||||
skipSwitcher: boolean;
|
skipSwitcher: boolean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace ClientState {
|
|||||||
this.signalManager = TiledMinimized.initSignalManager(world, client);
|
this.signalManager = TiledMinimized.initSignalManager(world, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(passFocus: FocusPassing.Type) {
|
public destroy(passFocus: boolean) {
|
||||||
this.signalManager.destroy();
|
this.signalManager.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
function init() {
|
function init() {
|
||||||
return new World(loadConfig());
|
return new World(loadConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadConfig(): Config {
|
|
||||||
const config: any = {};
|
|
||||||
for (const entry of configDef) {
|
|
||||||
config[entry.name] = KWin.readConfig(entry.name, entry.default);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": [
|
"include": ["../lib/**/*", "./**/*"]
|
||||||
"../extern/**/*",
|
|
||||||
"../lib/**/*",
|
|
||||||
"./**/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
tests.register("Center focused", 1, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client0, client1, client2] = workspaceMock.createClientsWithWidths(300, 152, 300);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(client0));
|
|
||||||
Assert.assert(clientManager.hasClient(client1));
|
|
||||||
Assert.assert(clientManager.hasClient(client2));
|
|
||||||
});
|
|
||||||
Assert.assert(workspaceMock.activeWindow === client2);
|
|
||||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
|
||||||
|
|
||||||
// center client2
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
|
||||||
Assert.centered(config, tilingArea, client2);
|
|
||||||
Assert.fullyVisible(client1.frameGeometry);
|
|
||||||
Assert.fullyVisible(client2.frameGeometry);
|
|
||||||
|
|
||||||
// undo center client2
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
|
||||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
|
||||||
|
|
||||||
// center client2
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
|
||||||
Assert.centered(config, tilingArea, client2);
|
|
||||||
Assert.fullyVisible(client1.frameGeometry);
|
|
||||||
Assert.fullyVisible(client2.frameGeometry);
|
|
||||||
|
|
||||||
// focus client1 (no scrolling should occur)
|
|
||||||
qtMock.fireShortcut("karousel-focus-left");
|
|
||||||
Assert.centered(config, tilingArea, client2, { message: "No scrolling should have occured" });
|
|
||||||
Assert.fullyVisible(client1.frameGeometry);
|
|
||||||
Assert.fullyVisible(client2.frameGeometry);
|
|
||||||
|
|
||||||
// center client1
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
|
||||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
|
||||||
|
|
||||||
// undo center client1 (no scrolling should occur, because all clients are already visible and centered)
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
|
||||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
|
||||||
});
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
tests.register("columns squeeze side", 1, () => {
|
|
||||||
const baseTestCases = [
|
|
||||||
{ widths: [500, 500], blocked: [false, false], possible: true },
|
|
||||||
{ widths: [500, 768], blocked: [false, false], possible: true },
|
|
||||||
{ widths: [500, 500], blocked: [false, true], possible: true },
|
|
||||||
{ widths: [500, 200, 200], blocked: [false, false, false], possible: true },
|
|
||||||
{ widths: [500, 200, 200], blocked: [false, false, true], possible: true },
|
|
||||||
{ widths: [500, 200, 200], blocked: [true, false, true], possible: true },
|
|
||||||
{ widths: [500, 500, 500], blocked: [false, true, true], possible: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
const testCasesLeft = baseTestCases.map((baseTestCase, i) => ({
|
|
||||||
...baseTestCase,
|
|
||||||
name: "left " + i,
|
|
||||||
action: "karousel-columns-squeeze-left",
|
|
||||||
focus: baseTestCase.widths.length-1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testCasesRight = baseTestCases.map((baseTestCase, i) => ({
|
|
||||||
...baseTestCase,
|
|
||||||
widths: baseTestCase.widths.slice().reverse(),
|
|
||||||
blocked: baseTestCase.blocked.slice().reverse(),
|
|
||||||
name: "right " + i,
|
|
||||||
action: "karousel-columns-squeeze-right",
|
|
||||||
focus: 0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testCases = [...testCasesLeft, ...testCasesRight];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const assertOpt = { message: `Case: ${testCase.name}` };
|
|
||||||
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const clients = workspaceMock.createClientsWithWidths(...testCase.widths);
|
|
||||||
workspaceMock.activeWindow = clients[testCase.focus];
|
|
||||||
for (let i = 0; i < clients.length; i++) {
|
|
||||||
if (testCase.blocked[i]) {
|
|
||||||
clients[i].minSize = new MockQmlSize(testCase.widths[i], 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testCase.possible) {
|
|
||||||
qtMock.fireShortcut(testCase.action);
|
|
||||||
Assert.columnsFillTilingArea(clients, assertOpt);
|
|
||||||
for (let i = 0; i < clients.length; i++) {
|
|
||||||
if (testCase.blocked[i]) {
|
|
||||||
Assert.equal(clients[i].frameGeometry.width, testCase.widths[i], assertOpt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const frames = clients.map(client => client.frameGeometry);
|
|
||||||
qtMock.fireShortcut(testCase.action);
|
|
||||||
const newFrames = clients.map(client => client.frameGeometry);
|
|
||||||
for (let i = 0; i < clients.length; i++) {
|
|
||||||
Assert.equalRects(frames[i], newFrames[i], assertOpt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("columns squeeze side (just scroll)", 1, () => {
|
|
||||||
const baseTestCases = [
|
|
||||||
{ focus: 0, startVisible: [true, true, false], endVisible: [true, true, false] },
|
|
||||||
{ focus: 1, startVisible: [false, true, true], endVisible: [true, true, false] },
|
|
||||||
{ focus: 2, startVisible: [false, true, true], endVisible: [false, true, true] },
|
|
||||||
];
|
|
||||||
|
|
||||||
const testCasesLeft = baseTestCases.map((baseTestCase, i) => ({
|
|
||||||
...baseTestCase,
|
|
||||||
name: "left " + i,
|
|
||||||
action: "karousel-columns-squeeze-left",
|
|
||||||
scrollStart: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testCasesRight = baseTestCases.map((baseTestCase, i) => ({
|
|
||||||
focus: 2 - baseTestCase.focus,
|
|
||||||
startVisible: baseTestCase.startVisible.slice().reverse(),
|
|
||||||
endVisible: baseTestCase.endVisible.slice().reverse(),
|
|
||||||
name: "right " + i,
|
|
||||||
action: "karousel-columns-squeeze-right",
|
|
||||||
scrollStart: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testCases = [...testCasesLeft, ...testCasesRight];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const assertMsg = `Case: ${testCase.name}`;
|
|
||||||
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
function assertVisible(clients: KwinClient[], visible: boolean[]) {
|
|
||||||
for (let i = 0; i < clients.length; i++) {
|
|
||||||
if (visible[i]) {
|
|
||||||
Assert.fullyVisible(clients[i].frameGeometry, { message: assertMsg, skip: 1 });
|
|
||||||
} else {
|
|
||||||
Assert.notFullyVisible(clients[i].frameGeometry, { message: assertMsg, skip: 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const clients = workspaceMock.createClientsWithWidths(300, 300, 300);
|
|
||||||
for (const client of clients) {
|
|
||||||
client.minSize = new MockQmlSize(300, 100);
|
|
||||||
}
|
|
||||||
if (testCase.scrollStart) {
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-start");
|
|
||||||
}
|
|
||||||
workspaceMock.activeWindow = clients[testCase.focus];
|
|
||||||
assertVisible(clients, testCase.startVisible);
|
|
||||||
|
|
||||||
qtMock.fireShortcut(testCase.action);
|
|
||||||
assertVisible(clients, testCase.endVisible);
|
|
||||||
|
|
||||||
const frames = clients.map(client => client.frameGeometry);
|
|
||||||
qtMock.fireShortcut(testCase.action);
|
|
||||||
const newFrames = clients.map(client => client.frameGeometry);
|
|
||||||
for (let i = 0; i < clients.length; i++) {
|
|
||||||
Assert.equalRects(frames[i], newFrames[i], { message: assertMsg });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
tests.register("Drag tiled window, untile", 10, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.cursorFollowsFocus = true;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client1, client2] = workspaceMock.createClients(2);
|
|
||||||
const initialCursorPos = new MockQmlPoint(380, 20);
|
|
||||||
Assert.assert(rectContainsPoint(client1.frameGeometry, initialCursorPos), { message: "invalid test setup" });
|
|
||||||
workspaceMock.cursorPos = initialCursorPos.clone();
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { Workspace.activeWindow = client1; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
|
||||||
);
|
|
||||||
Assert.assert(rectContainsPoint(client1.frameGeometry, Workspace.cursorPos));
|
|
||||||
Assert.assert(!rectContainsPoint(client2.frameGeometry, Workspace.cursorPos));
|
|
||||||
Assert.assert(pointEquals(Workspace.cursorPos, initialCursorPos), { message: "Cursor should not have been moved because it was already within the focused client" });
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { Workspace.activeWindow = client2; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-2"); },
|
|
||||||
);
|
|
||||||
Assert.assert(!rectContainsPoint(client1.frameGeometry, Workspace.cursorPos));
|
|
||||||
Assert.assert(rectContainsPoint(client2.frameGeometry, Workspace.cursorPos));
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { Workspace.activeWindow = client1; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
|
||||||
);
|
|
||||||
Assert.assert(rectContainsPoint(client1.frameGeometry, Workspace.cursorPos));
|
|
||||||
Assert.assert(!rectContainsPoint(client2.frameGeometry, Workspace.cursorPos));
|
|
||||||
const lastCursorPos = workspaceMock.cursorPos.clone();
|
|
||||||
|
|
||||||
Workspace.activeWindow = null;
|
|
||||||
Assert.assert(pointEquals(Workspace.cursorPos, lastCursorPos), { message: "Cursor should not have been moved" });
|
|
||||||
});
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
tests.register("Drag tiled window, untile", 20, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.untileOnDrag = true;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
const clientManager = getClientManager(world);
|
|
||||||
|
|
||||||
const [client0, client1] = workspaceMock.createClients(2);
|
|
||||||
Assert.tiledClient(clientManager, client0);
|
|
||||||
Assert.tiledClient(clientManager, client1);
|
|
||||||
Assert.grid(config, tilingArea, 100, [[client0], [client1]], true);
|
|
||||||
|
|
||||||
workspaceMock.moveWindow(client0, new MockQmlPoint(10, 10));
|
|
||||||
Assert.notTiledClient(clientManager, client0);
|
|
||||||
Assert.tiledClient(clientManager, client1);
|
|
||||||
Assert.grid(config, tilingArea, 100, [[client1]], true);
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Drag tiled window, keep tiled", 20, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.untileOnDrag = false;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
const clientManager = getClientManager(world);
|
|
||||||
|
|
||||||
const [client0, client1] = workspaceMock.createClients(2);
|
|
||||||
Assert.tiledClient(clientManager, client0);
|
|
||||||
Assert.tiledClient(clientManager, client1);
|
|
||||||
Assert.grid(config, tilingArea, 100, [[client0], [client1]], true);
|
|
||||||
|
|
||||||
const move = new MockQmlPoint(10, 10);
|
|
||||||
workspaceMock.moveWindow(client0, move, move, move, move, move, move, move, move, move); // many moves in order to trigger externalFrameGeometryChangedRateLimiter
|
|
||||||
Assert.tiledClient(clientManager, client0);
|
|
||||||
Assert.tiledClient(clientManager, client1);
|
|
||||||
Assert.grid(config, tilingArea, 100, [[client0], [client1]], true);
|
|
||||||
});
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
tests.register("External resize", 1, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
function getClientDesiredFrame(width: number) {
|
|
||||||
return new MockQmlRect(10, 10, width, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTiledFrame(width: number) {
|
|
||||||
return new MockQmlRect(
|
|
||||||
tilingArea.left + Math.round((tilingArea.width - width) / 2),
|
|
||||||
tilingArea.top,
|
|
||||||
width,
|
|
||||||
tilingArea.height,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [client] = workspaceMock.createClientsWithFrames(getClientDesiredFrame(100));
|
|
||||||
Assert.equalRects(client.frameGeometry, getTiledFrame(100), { message: "We should tile the window, respecting its desired width" });
|
|
||||||
|
|
||||||
function testExternalResizing() {
|
|
||||||
client.frameGeometry = getClientDesiredFrame(110);
|
|
||||||
Assert.equalRects(client.frameGeometry, getTiledFrame(110), { message: "We should re-arrange the window, respecting its new desired width" });
|
|
||||||
|
|
||||||
client.frameGeometry = getClientDesiredFrame(120);
|
|
||||||
Assert.equalRects(client.frameGeometry, getTiledFrame(120), { message: "We should re-arrange the window, respecting its new desired width" });
|
|
||||||
|
|
||||||
client.frameGeometry = getClientDesiredFrame(130);
|
|
||||||
Assert.equalRects(client.frameGeometry, getTiledFrame(130), { message: "We should re-arrange the window, respecting its new desired width" });
|
|
||||||
|
|
||||||
client.frameGeometry = getClientDesiredFrame(140);
|
|
||||||
Assert.equalRects(client.frameGeometry, getTiledFrame(140), { message: "We should re-arrange the window, respecting its new desired width" });
|
|
||||||
|
|
||||||
client.frameGeometry = getClientDesiredFrame(200);
|
|
||||||
Assert.equalRects(client.frameGeometry, getClientDesiredFrame(200), { message: "We should give up and let the client have its desired frame" });
|
|
||||||
}
|
|
||||||
|
|
||||||
timeControl(addTime => {
|
|
||||||
testExternalResizing();
|
|
||||||
addTime(1000);
|
|
||||||
// the concession has expired, let's test again
|
|
||||||
testExternalResizing();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
tests.register("Move and follow window to desktop", 20, () => {
|
|
||||||
// This tests the Kwin shortcuts for moving windows to adjacent desktops.
|
|
||||||
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client0, client1] = workspaceMock.createClients(2);
|
|
||||||
client1.moveAndFollowToDesktop(workspaceMock.desktops[1], workspaceMock);
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client1);
|
|
||||||
});
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
tests.register("tiledKeepBelow", 10, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.tiledKeepBelow = true;
|
|
||||||
config.floatingKeepAbove = false;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const pinGeometry = new MockQmlRect(0, 0, 200, screen.height);
|
|
||||||
|
|
||||||
const [client] = workspaceMock.createClients(1);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
|
||||||
});
|
|
||||||
Assert.assert(client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
client.pin(pinGeometry);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
client.unpin();
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
|
||||||
});
|
|
||||||
Assert.assert(client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
client.pin(pinGeometry);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
|
||||||
});
|
|
||||||
Assert.assert(client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("floatingKeepAbove", 10, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.tiledKeepBelow = false;
|
|
||||||
config.floatingKeepAbove = true;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const pinGeometry = new MockQmlRect(0, 0, 200, screen.height);
|
|
||||||
|
|
||||||
const [client] = workspaceMock.createClients(1);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(client.keepAbove);
|
|
||||||
|
|
||||||
client.pin(pinGeometry);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(client.keepAbove);
|
|
||||||
|
|
||||||
client.unpin();
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(client.keepAbove);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
|
|
||||||
client.pin(pinGeometry);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(client.keepAbove);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
|
||||||
});
|
|
||||||
Assert.assert(!client.keepBelow);
|
|
||||||
Assert.assert(!client.keepAbove);
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("No layering", 10, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.tiledKeepBelow = false;
|
|
||||||
config.floatingKeepAbove = false;
|
|
||||||
// In this mode, Karousel shouldn't change keepBelow or keepAbove.
|
|
||||||
// Except when tiling a window, keepAbove should still be cleared.
|
|
||||||
|
|
||||||
const pinGeometry = new MockQmlRect(0, 0, 200, screen.height);
|
|
||||||
|
|
||||||
const testCases = [
|
|
||||||
{ keepBelow: false, keepAbove: false },
|
|
||||||
{ keepBelow: false, keepAbove: true },
|
|
||||||
{ keepBelow: true, keepAbove: false },
|
|
||||||
{ keepBelow: true, keepAbove: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const assertOptions = { message: JSON.stringify(testCase) };
|
|
||||||
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client] = workspaceMock.createClients(1);
|
|
||||||
client.keepBelow = testCase.keepBelow;
|
|
||||||
client.keepAbove = testCase.keepAbove;
|
|
||||||
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
|
||||||
|
|
||||||
client.pin(pinGeometry);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
|
||||||
|
|
||||||
client.unpin();
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.assert(!client.keepAbove, assertOptions);
|
|
||||||
client.keepAbove = testCase.keepAbove;
|
|
||||||
|
|
||||||
client.pin(pinGeometry);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.findTiledWindow(client) !== null, assertOptions);
|
|
||||||
});
|
|
||||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
|
||||||
Assert.assert(!client.keepAbove, assertOptions);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
tests.register("Focus and move windows", 1, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client1, client2, client3] = workspaceMock.createClients(3);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(client1));
|
|
||||||
Assert.assert(clientManager.hasClient(client2));
|
|
||||||
Assert.assert(clientManager.hasClient(client3));
|
|
||||||
});
|
|
||||||
Assert.assert(workspaceMock.activeWindow === client3);
|
|
||||||
|
|
||||||
function testLayout(shortcutName: string, grid: KwinClient[][]) {
|
|
||||||
qtMock.fireShortcut(shortcutName);
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true, [], { skip: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
function testFocus(shortcutName: string, expectedFocus: KwinClient) {
|
|
||||||
qtMock.fireShortcut(shortcutName);
|
|
||||||
Assert.assert(workspaceMock.activeWindow === expectedFocus, {
|
|
||||||
message: `wrong activeWindow: ${workspaceMock.activeWindow?.pid}`,
|
|
||||||
skip: 1,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
testLayout("karousel-column-move-right", [ [client1], [client2], [client3] ]);
|
|
||||||
|
|
||||||
testLayout("karousel-window-move-left", [ [client1], [client2,client3] ]);
|
|
||||||
testLayout("karousel-window-move-left", [ [client1], [client3], [client2] ]);
|
|
||||||
testLayout("karousel-window-move-left", [ [client1,client3], [client2] ]);
|
|
||||||
testFocus("karousel-focus-right", client2);
|
|
||||||
testLayout("karousel-window-move-left", [ [client1,client3,client2] ]);
|
|
||||||
testLayout("karousel-window-move-left", [ [client2], [client1,client3] ]);
|
|
||||||
testLayout("karousel-window-move-left", [ [client2], [client1,client3] ]);
|
|
||||||
testFocus("karousel-focus-2", client3);
|
|
||||||
testFocus("karousel-focus-up", client1);
|
|
||||||
testLayout("karousel-column-move-left", [ [client1,client3], [client2] ]);
|
|
||||||
testLayout("karousel-window-move-right", [ [client3], [client1], [client2] ]);
|
|
||||||
|
|
||||||
testFocus("karousel-focus-3", client2);
|
|
||||||
testLayout("karousel-window-move-start", [ [client2], [client3], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-to-column-3", [ [client3], [client1,client2] ]);
|
|
||||||
testLayout("karousel-column-move-left", [ [client1,client2], [client3] ]);
|
|
||||||
testLayout("karousel-column-move-end", [ [client3], [client1,client2] ]);
|
|
||||||
testLayout("karousel-column-move-to-column-1", [ [client1,client2], [client3] ]);
|
|
||||||
testLayout("karousel-column-move-right", [ [client3], [client1,client2] ]);
|
|
||||||
|
|
||||||
testLayout("karousel-window-move-previous", [ [client3], [client2,client1] ]);
|
|
||||||
testLayout("karousel-window-move-previous", [ [client3], [client2], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-previous", [ [client3,client2], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-previous", [ [client2,client3], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-previous", [ [client2], [client3], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-previous", [ [client2], [client3], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client2,client3], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client3,client2], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client3], [client2], [client1] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client3], [client2,client1] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client3], [client1,client2] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client3], [client1], [client2] ]);
|
|
||||||
testLayout("karousel-window-move-next", [ [client3], [client1], [client2] ]);
|
|
||||||
testLayout("karousel-window-move-left", [ [client3], [client1,client2] ]);
|
|
||||||
|
|
||||||
const col1Win1 = client3;
|
|
||||||
const col2Win1 = client1;
|
|
||||||
const col2Win2 = client2;
|
|
||||||
|
|
||||||
testFocus("karousel-focus-up", col2Win1);
|
|
||||||
testFocus("karousel-focus-up", col2Win1);
|
|
||||||
testFocus("karousel-focus-down", col2Win2);
|
|
||||||
testFocus("karousel-focus-left", col1Win1);
|
|
||||||
testFocus("karousel-focus-left", col1Win1);
|
|
||||||
testFocus("karousel-focus-right", col2Win2);
|
|
||||||
testFocus("karousel-focus-right", col2Win2);
|
|
||||||
|
|
||||||
testFocus("karousel-focus-2", col2Win2);
|
|
||||||
testFocus("karousel-focus-1", col1Win1);
|
|
||||||
testFocus("karousel-focus-2", col2Win2);
|
|
||||||
testFocus("karousel-focus-start", col1Win1);
|
|
||||||
testFocus("karousel-focus-end", col2Win2);
|
|
||||||
|
|
||||||
testFocus("karousel-focus-up", col2Win1);
|
|
||||||
testFocus("karousel-focus-left", col1Win1);
|
|
||||||
testFocus("karousel-focus-right", col2Win1);
|
|
||||||
testFocus("karousel-focus-2", col2Win1);
|
|
||||||
testFocus("karousel-focus-1", col1Win1);
|
|
||||||
testFocus("karousel-focus-2", col2Win1);
|
|
||||||
testFocus("karousel-focus-start", col1Win1);
|
|
||||||
testFocus("karousel-focus-end", col2Win1);
|
|
||||||
|
|
||||||
testFocus("karousel-focus-down", col2Win2);
|
|
||||||
testFocus("karousel-focus-start", col1Win1);
|
|
||||||
testFocus("karousel-focus-next", col2Win1);
|
|
||||||
testFocus("karousel-focus-next", col2Win2);
|
|
||||||
testFocus("karousel-focus-next", col2Win2);
|
|
||||||
testFocus("karousel-focus-previous", col2Win1);
|
|
||||||
testFocus("karousel-focus-previous", col1Win1);
|
|
||||||
testFocus("karousel-focus-previous", col1Win1);
|
|
||||||
});
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
tests.register("LazyScroller", 20, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.scrollingLazy = true;
|
|
||||||
config.scrollingCentered = false;
|
|
||||||
config.scrollingGrouped = false;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client1] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1]], true);
|
|
||||||
|
|
||||||
const [client2] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1], [client2]], true);
|
|
||||||
|
|
||||||
const [client3] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
|
||||||
Assert.equal(client3.frameGeometry.right, tilingArea.right);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = client2; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-2"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-left"); },
|
|
||||||
);
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
|
||||||
Assert.equal(client3.frameGeometry.right, tilingArea.right);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = client1; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-left"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-start"); },
|
|
||||||
);
|
|
||||||
workspaceMock.activeWindow = client1;
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
|
||||||
Assert.equal(client1.frameGeometry.left, tilingArea.left);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1]], true);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = client2; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-2"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-right"); },
|
|
||||||
);
|
|
||||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
|
||||||
Assert.equal(client1.frameGeometry.left, tilingArea.left);
|
|
||||||
});
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
{
|
|
||||||
function registerTests(
|
|
||||||
suffix: string,
|
|
||||||
getConfig: () => Config,
|
|
||||||
shouldKeepBelow: (tiled: boolean) => boolean,
|
|
||||||
shouldKeepAbove: (tiled: boolean) => boolean,
|
|
||||||
) {
|
|
||||||
tests.register("Maximization " + suffix, 100, () => {
|
|
||||||
const config = getConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [kwinClient] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(kwinClient));
|
|
||||||
});
|
|
||||||
|
|
||||||
const columnLeftX = tilingArea.left + tilingArea.width/2 - 300/2;
|
|
||||||
const columnTopY = tilingArea.top;
|
|
||||||
const columnHeight = tilingArea.height;
|
|
||||||
Assert.assert(!kwinClient.fullScreen);
|
|
||||||
Assert.equal(kwinClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(kwinClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.rect(kwinClient.frameGeometry, columnLeftX, columnTopY, 300, columnHeight);
|
|
||||||
|
|
||||||
kwinClient.fullScreen = true;
|
|
||||||
Assert.assert(kwinClient.fullScreen);
|
|
||||||
Assert.equal(kwinClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(kwinClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, screen);
|
|
||||||
|
|
||||||
kwinClient.fullScreen = false;
|
|
||||||
Assert.assert(!kwinClient.fullScreen);
|
|
||||||
Assert.equal(kwinClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(kwinClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.rect(kwinClient.frameGeometry, columnLeftX, columnTopY, 300, columnHeight);
|
|
||||||
|
|
||||||
kwinClient.setMaximize(true, true);
|
|
||||||
Assert.assert(!kwinClient.fullScreen);
|
|
||||||
Assert.equal(kwinClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(kwinClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, screen);
|
|
||||||
|
|
||||||
kwinClient.setMaximize(true, false);
|
|
||||||
Assert.assert(!kwinClient.fullScreen);
|
|
||||||
Assert.equal(kwinClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(kwinClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.rect(kwinClient.frameGeometry, columnLeftX, 0, 300, screen.height);
|
|
||||||
|
|
||||||
kwinClient.setMaximize(false, false);
|
|
||||||
Assert.assert(!kwinClient.fullScreen);
|
|
||||||
Assert.equal(kwinClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(kwinClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.rect(kwinClient.frameGeometry, columnLeftX, columnTopY, 300, columnHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Maximize with transient " + suffix, 100, () => {
|
|
||||||
const config = getConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const parent = new MockKwinClient(new MockQmlRect(10, 20, 300, 200));
|
|
||||||
const child = new MockKwinClient(new MockQmlRect(14, 24, 50, 50), parent);
|
|
||||||
|
|
||||||
workspaceMock.createWindows(parent);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(parent));
|
|
||||||
});
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { parent.fullScreen = true; },
|
|
||||||
() => { parent.setMaximize(true, true); },
|
|
||||||
);
|
|
||||||
Assert.equal(parent.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(parent.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(parent.frameGeometry, screen);
|
|
||||||
|
|
||||||
workspaceMock.createWindows(child);
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(child));
|
|
||||||
});
|
|
||||||
Assert.assert(!child.fullScreen);
|
|
||||||
Assert.equal(child.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(child.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.rect(child.frameGeometry, 14, 24, 50, 50);
|
|
||||||
Assert.equal(parent.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(parent.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(parent.frameGeometry, screen);
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
function assertWindowed(config: Config, clients: KwinClient[]) {
|
|
||||||
Assert.assert(!clients[0].fullScreen);
|
|
||||||
Assert.equal(clients[0].keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(clients[0].keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.assert(!clients[1].fullScreen);
|
|
||||||
Assert.equal(clients[1].keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(clients[1].keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.assert(!clients[2].fullScreen);
|
|
||||||
Assert.equal(clients[2].keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(clients[2].keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.grid(config, tilingArea, [300, 400], [[clients[0]], [clients[1], clients[2]]], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertFullScreenOrMaximized(clients: KwinClient[]) {
|
|
||||||
Assert.assert(!clients[0].fullScreen);
|
|
||||||
Assert.equal(clients[0].keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(clients[0].keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.assert(!clients[1].fullScreen);
|
|
||||||
Assert.equal(clients[1].keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(clients[1].keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.equal(clients[2].keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(clients[2].keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(clients[2].frameGeometry, screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
tests.register("Re-maximize disabled " + suffix, 100, () => {
|
|
||||||
const config = getConfig();
|
|
||||||
config.reMaximize = false;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const clients = workspaceMock.createClientsWithWidths(300, 400, 400);
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { clients[2].fullScreen = true; },
|
|
||||||
() => { clients[2].setMaximize(true, true); },
|
|
||||||
);
|
|
||||||
assertFullScreenOrMaximized(clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[0]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-left"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-start"); },
|
|
||||||
);
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[2]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-2"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-right"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-end"); },
|
|
||||||
);
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { clients[2].fullScreen = true; },
|
|
||||||
() => { clients[2].setMaximize(true, true); },
|
|
||||||
);
|
|
||||||
assertFullScreenOrMaximized(clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[1]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-up"); },
|
|
||||||
);
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[2]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-down"); },
|
|
||||||
);
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Re-maximize enabled " + suffix, 100, () => {
|
|
||||||
const config = getConfig();
|
|
||||||
config.reMaximize = true;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const clients = workspaceMock.createClientsWithWidths(300, 400, 400);
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { clients[2].fullScreen = true; },
|
|
||||||
() => { clients[2].setMaximize(true, true); },
|
|
||||||
);
|
|
||||||
assertFullScreenOrMaximized(clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[0]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-left"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-start"); },
|
|
||||||
);
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[2]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-2"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-right"); },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-end"); },
|
|
||||||
);
|
|
||||||
assertFullScreenOrMaximized(clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[1]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-up"); },
|
|
||||||
);
|
|
||||||
assertWindowed(config, clients);
|
|
||||||
|
|
||||||
runOneOf(
|
|
||||||
() => { workspaceMock.activeWindow = clients[2]; },
|
|
||||||
() => { qtMock.fireShortcut("karousel-focus-down"); },
|
|
||||||
);
|
|
||||||
assertFullScreenOrMaximized(clients);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tests.register("Start full-screen " + suffix, 100, () => {
|
|
||||||
const config = getConfig();
|
|
||||||
config.reMaximize = true;
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [windowedClient] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
const fullScreenClient = new MockKwinClient(new MockQmlRect(0, 0, 400, 200));
|
|
||||||
fullScreenClient.resourceClass = "full-screen-app";
|
|
||||||
fullScreenClient.fullScreen = true;
|
|
||||||
workspaceMock.createWindows(fullScreenClient);
|
|
||||||
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(windowedClient));
|
|
||||||
Assert.assert(clientManager.hasClient(fullScreenClient));
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.assert(!windowedClient.fullScreen);
|
|
||||||
Assert.equal(windowedClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(windowedClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.centered(config, tilingArea, windowedClient);
|
|
||||||
Assert.assert(fullScreenClient.fullScreen);
|
|
||||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
|
||||||
Assert.equal(Workspace.activeWindow, fullScreenClient);
|
|
||||||
|
|
||||||
{
|
|
||||||
qtMock.fireShortcut("karousel-focus-left");
|
|
||||||
const opts = { message: "fullScreenClient is not in the grid, so we can't move focus directionally" };
|
|
||||||
Assert.assert(!windowedClient.fullScreen);
|
|
||||||
Assert.equal(windowedClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(windowedClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.centered(config, tilingArea, windowedClient);
|
|
||||||
Assert.assert(fullScreenClient.fullScreen);
|
|
||||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
|
||||||
Assert.equal(Workspace.activeWindow, fullScreenClient, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
qtMock.fireShortcut("karousel-focus-1");
|
|
||||||
const opts = { message: "fullScreenClient is not in grid, so it should stay full-screen" };
|
|
||||||
Assert.assert(!windowedClient.fullScreen);
|
|
||||||
Assert.equal(windowedClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(windowedClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.centered(config, tilingArea, windowedClient);
|
|
||||||
Assert.assert(fullScreenClient.fullScreen);
|
|
||||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
|
||||||
Assert.equal(Workspace.activeWindow, windowedClient);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Start full-screen (force tiling) " + suffix, 100, () => {
|
|
||||||
const config = getConfig();
|
|
||||||
config.reMaximize = true;
|
|
||||||
config.windowRules = '[{ "class": "full-screen-app", "tile": true }]';
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const column1Width = 300;
|
|
||||||
const [windowedClient] = workspaceMock.createClientsWithWidths(column1Width);
|
|
||||||
const fullScreenClient = new MockKwinClient(new MockQmlRect(0, 0, 400, 200));
|
|
||||||
fullScreenClient.resourceClass = "full-screen-app";
|
|
||||||
fullScreenClient.fullScreen = true;
|
|
||||||
workspaceMock.createWindows(fullScreenClient);
|
|
||||||
|
|
||||||
world.do((clientManager, desktopManager) => {
|
|
||||||
Assert.assert(clientManager.hasClient(windowedClient));
|
|
||||||
Assert.assert(clientManager.hasClient(fullScreenClient));
|
|
||||||
});
|
|
||||||
Assert.assert(!windowedClient.fullScreen);
|
|
||||||
Assert.equal(windowedClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(windowedClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.grid(config, tilingArea, [column1Width], [[windowedClient]], false);
|
|
||||||
Assert.assert(fullScreenClient.fullScreen);
|
|
||||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
|
||||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
|
||||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
|
||||||
Assert.equal(Workspace.activeWindow, fullScreenClient);
|
|
||||||
|
|
||||||
let expectedColumn2Width = 0;
|
|
||||||
let expectedActiveWindow;
|
|
||||||
runOneOf(
|
|
||||||
() => {
|
|
||||||
fullScreenClient.fullScreen = false;
|
|
||||||
expectedColumn2Width = 400;
|
|
||||||
expectedActiveWindow = fullScreenClient;
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
qtMock.fireShortcut("karousel-focus-left");
|
|
||||||
expectedColumn2Width = tilingArea.width;
|
|
||||||
expectedActiveWindow = windowedClient;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const opts = { message: "fullScreenClient should be restored from full-screen mode to tiled mode" };
|
|
||||||
Assert.assert(!windowedClient.fullScreen);
|
|
||||||
Assert.equal(windowedClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(windowedClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.assert(!fullScreenClient.fullScreen);
|
|
||||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(true));
|
|
||||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(true));
|
|
||||||
Assert.grid(config, tilingArea, [column1Width, expectedColumn2Width], [[windowedClient], [fullScreenClient]], false, [], opts);
|
|
||||||
Assert.equal(Workspace.activeWindow, expectedActiveWindow);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfig(floatingKeepAbove: boolean) {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.tiledKeepBelow = !floatingKeepAbove;
|
|
||||||
config.floatingKeepAbove = floatingKeepAbove;
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerTests(
|
|
||||||
"(tiled below)",
|
|
||||||
getConfig.partial(false),
|
|
||||||
tiled => tiled,
|
|
||||||
tiled => false,
|
|
||||||
);
|
|
||||||
|
|
||||||
registerTests(
|
|
||||||
"(floating above)",
|
|
||||||
getConfig.partial(true),
|
|
||||||
tiled => false,
|
|
||||||
tiled => !tiled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
tests.register("Pass focus", 100, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [client0, client1a, client1b, client1c, client4, client5, client6] = workspaceMock.createClients(7);
|
|
||||||
workspaceMock.activeWindow = client1b;
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
workspaceMock.activeWindow = client1c;
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
workspaceMock.activeWindow = client1b;
|
|
||||||
workspaceMock.activeWindow = client5;
|
|
||||||
|
|
||||||
function removeWindow(client: MockKwinClient) {
|
|
||||||
runOneOf(
|
|
||||||
() => workspaceMock.removeWindow(client),
|
|
||||||
() => client.desktops = [workspaceMock.desktops[1]],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeWindow(client5);
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client4);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-column-move-to-desktop-2");
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client1b);
|
|
||||||
|
|
||||||
removeWindow(client1b);
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client1a);
|
|
||||||
|
|
||||||
removeWindow(client1a);
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client1c);
|
|
||||||
|
|
||||||
removeWindow(client1c);
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client0);
|
|
||||||
|
|
||||||
removeWindow(client0);
|
|
||||||
Assert.equal(workspaceMock.activeWindow, client6);
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
tests.register("Pin", 20, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const screenHalfLeft = new MockQmlRect(0, 0, screen.width/2, screen.height);
|
|
||||||
const screenHalfRight = new MockQmlRect(screen.width/2, 0, screen.width/2, screen.height);
|
|
||||||
|
|
||||||
const tilingAreaHalfLeft = new MockQmlRect(
|
|
||||||
tilingArea.x,
|
|
||||||
tilingArea.y,
|
|
||||||
screen.width/2 - config.gapsOuterLeft - config.gapsOuterRight,
|
|
||||||
tilingArea.height,
|
|
||||||
);
|
|
||||||
const tilingAreaHalfRight = new MockQmlRect(
|
|
||||||
screen.width/2 + config.gapsOuterLeft,
|
|
||||||
tilingArea.y,
|
|
||||||
screen.width/2 - config.gapsOuterLeft - config.gapsOuterRight,
|
|
||||||
tilingArea.height,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [pinned, tiled1, tiled2] = workspaceMock.createClients(3);
|
|
||||||
Assert.grid(config, tilingArea, 100, [ [pinned], [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
pinned.pin(screenHalfLeft);
|
|
||||||
Assert.equalRects(pinned.frameGeometry, screenHalfLeft);
|
|
||||||
Assert.grid(config, tilingAreaHalfRight, 100, [ [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
pinned.pin(screenHalfRight);
|
|
||||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
|
||||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
pinned.unpin();
|
|
||||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
|
||||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
pinned.pin(screenHalfRight);
|
|
||||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
|
||||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
pinned.minimized = true;
|
|
||||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
pinned.minimized = false;
|
|
||||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
|
||||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
|
||||||
|
|
||||||
workspaceMock.activeWindow = pinned;
|
|
||||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
|
||||||
Assert.assert(pinned.tile === null);
|
|
||||||
pinned.frameGeometry = new MockQmlRect(10, 20, 100, 200); // This is needed because the window's preferredWidth can change when pinning, because frameGeometryChanged can fire before tileChanged. TODO: Ensure pinned window keeps its preferredWidth.
|
|
||||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2], [pinned] ], true);
|
|
||||||
});
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
tests.register("Preset Widths default", 1, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const maxWidth = tilingArea.width;
|
|
||||||
const halfWidth = maxWidth/2 - config.gapsInnerHorizontal/2;
|
|
||||||
|
|
||||||
function getRect(columnWidth: number) {
|
|
||||||
return new MockQmlRect(
|
|
||||||
tilingArea.left + (tilingArea.width - columnWidth) / 2,
|
|
||||||
tilingArea.top,
|
|
||||||
columnWidth,
|
|
||||||
tilingArea.height,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [kwinClient] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(300));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(maxWidth));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(maxWidth));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Preset Widths custom", 1, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.presetWidths = "500px, 250px, 100px, 50%";
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const maxWidth = tilingArea.width;
|
|
||||||
const halfWidth = maxWidth/2 - config.gapsInnerHorizontal/2;
|
|
||||||
|
|
||||||
function getRect(columnWidth: number) {
|
|
||||||
return new MockQmlRect(
|
|
||||||
tilingArea.left + (tilingArea.width - columnWidth) / 2,
|
|
||||||
tilingArea.top,
|
|
||||||
columnWidth,
|
|
||||||
tilingArea.height,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [kwinClient] = workspaceMock.createClientsWithWidths(200);
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(200));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(250));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(500));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(100));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(250));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(100));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(500));
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
|
||||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Preset Widths fill screen uniform", 1, () => {
|
|
||||||
for (let nColumns = 1; nColumns < 10; nColumns++) {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.presetWidths = String(1 / nColumns);
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
let firstClient, lastClient;
|
|
||||||
for (let i = 0; i < nColumns; i++) {
|
|
||||||
const [kwinClient] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
if (i === 0) {
|
|
||||||
firstClient = kwinClient;
|
|
||||||
}
|
|
||||||
if (i === nColumns-1) {
|
|
||||||
lastClient = kwinClient;
|
|
||||||
}
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
}
|
|
||||||
|
|
||||||
const left = tilingArea.left;
|
|
||||||
const right = tilingArea.right;
|
|
||||||
const maxLeftoverPx = nColumns - 1;
|
|
||||||
const eps = Math.ceil(maxLeftoverPx / 2);
|
|
||||||
Assert.between(firstClient!.frameGeometry.left, left, left+eps, { message: `nColumns: ${nColumns}` });
|
|
||||||
Assert.between(lastClient!.frameGeometry.right, right-eps, right, { message: `nColumns: ${nColumns}` });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tests.register("Preset Widths fill screen non-uniform", 1, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.presetWidths = String("50%, 25%");
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [clientThin1] = workspaceMock.createClientsWithWidths(100);
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
|
|
||||||
const [clientThin2] = workspaceMock.createClientsWithWidths(100);
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
|
|
||||||
const [clientWide] = workspaceMock.createClientsWithWidths(300);
|
|
||||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
|
||||||
|
|
||||||
const maxWidth = tilingArea.width;
|
|
||||||
const halfWidth = maxWidth/2 - config.gapsInnerHorizontal/2;
|
|
||||||
const quarterWidth = halfWidth/2 - config.gapsInnerHorizontal/2;
|
|
||||||
const height = tilingArea.height;
|
|
||||||
const left1 = tilingArea.left;
|
|
||||||
const left2 = left1 + config.gapsInnerHorizontal + quarterWidth;
|
|
||||||
const left3 = left2 + config.gapsInnerHorizontal + quarterWidth;
|
|
||||||
|
|
||||||
Assert.rect(clientThin1.frameGeometry, left1, tilingArea.top, quarterWidth, height);
|
|
||||||
Assert.rect(clientThin2.frameGeometry, left2, tilingArea.top, quarterWidth, height);
|
|
||||||
Assert.rect(clientWide.frameGeometry, left3, tilingArea.top, halfWidth, height);
|
|
||||||
Assert.equal(clientWide.frameGeometry.right, tilingArea.right);
|
|
||||||
});
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
tests.register("Stacked", 5, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
|
|
||||||
const [leftTop, leftBottom, rightTop, rightBottom] = workspaceMock.createClients(4);
|
|
||||||
const grid = [[leftTop, leftBottom], [rightTop, rightBottom]];
|
|
||||||
workspaceMock.activeWindow = rightBottom;
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
workspaceMock.activeWindow = leftBottom;
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-column-toggle-stacked");
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-focus-up");
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-focus-down");
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-move-up");
|
|
||||||
Assert.grid(config, tilingArea, 100, [[leftBottom, leftTop], [rightTop, rightBottom]], true, [0]);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-window-move-down");
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
|
||||||
|
|
||||||
qtMock.fireShortcut("karousel-column-toggle-stacked");
|
|
||||||
Assert.grid(config, tilingArea, 100, grid, true);
|
|
||||||
});
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
tests.register("User resize", 10, () => {
|
|
||||||
const config = getDefaultConfig();
|
|
||||||
config.resizeNeighborColumn = true;
|
|
||||||
|
|
||||||
const h = getWindowHeight(2);
|
|
||||||
let clientLeft: MockKwinClient, clientRightTop: MockKwinClient, clientRightBottom: MockKwinClient;
|
|
||||||
function assertSizes(leftWidth: number, rightWidth: number, topHeight: number, bottomHeight: number) {
|
|
||||||
const { left, right } = getGridBounds(clientLeft, clientRightTop);
|
|
||||||
Assert.rect(clientLeft.frameGeometry, left, tilingArea.top, leftWidth, tilingArea.height);
|
|
||||||
Assert.rect(clientRightTop.frameGeometry, left+leftWidth+gapH, tilingArea.top, rightWidth, topHeight);
|
|
||||||
Assert.rect(clientRightBottom.frameGeometry, left+leftWidth+gapH, tilingArea.top+topHeight+gapV, rightWidth, bottomHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
[clientLeft, clientRightTop, clientRightBottom] = workspaceMock.createClientsWithWidths(300, 300, 200);
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
assertSizes(300, 300, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeft, false, false, false, new MockQmlSize(10, 20));
|
|
||||||
assertSizes(310, 300, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeft, true, false, false, new MockQmlSize(10, 0), new MockQmlSize(-10, 0));
|
|
||||||
assertSizes(310, 300, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightTop, false, false, false, new MockQmlSize(-5, -10), new MockQmlSize(-5, -10));
|
|
||||||
assertSizes(310, 290, h-20, h+20);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightBottom, false, false, false, new MockQmlSize(-10, 20));
|
|
||||||
assertSizes(310, 280, h-20, h+20);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightBottom, false, false, true, new MockQmlSize(0, 20));
|
|
||||||
assertSizes(310, 280, h-40, h+40);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
[clientLeft, clientRightTop, clientRightBottom] = workspaceMock.createClientsWithWidths(300, 300, 200);
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
assertSizes(300, 300, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeft, true, false, false, new MockQmlSize(10, 20));
|
|
||||||
assertSizes(310, 290, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeft, true, false, false, new MockQmlSize(10, 0), new MockQmlSize(-10, 0));
|
|
||||||
assertSizes(310, 290, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightTop, true, false, false, new MockQmlSize(-5, -10), new MockQmlSize(-5, -10));
|
|
||||||
assertSizes(310, 280, h-20, h+20);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightBottom, true, true, false, new MockQmlSize(-10, 20));
|
|
||||||
assertSizes(320, 270, h-20, h+20);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightBottom, true, false, true, new MockQmlSize(0, 20));
|
|
||||||
assertSizes(320, 270, h-40, h+40);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
[clientLeft, clientRightTop, clientRightBottom] = workspaceMock.createClientsWithWidths(300, 300, 200);
|
|
||||||
clientRightBottom.minSize = new MockQmlSize(295, h-20);
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
assertSizes(300, 300, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeft, true, false, false, new MockQmlSize(10, 20));
|
|
||||||
assertSizes(310, 295, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeft, true, false, false, new MockQmlSize(10, 0), new MockQmlSize(-10, 0));
|
|
||||||
assertSizes(310, 295, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightTop, true, false, false, new MockQmlSize(-5, -10), new MockQmlSize(-5, -10));
|
|
||||||
assertSizes(310, 295, h-20, h+20);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightBottom, true, true, false, new MockQmlSize(-10, 20));
|
|
||||||
assertSizes(310, 295, h-20, h+20);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientRightTop, true, true, false, new MockQmlSize(-10, 0));
|
|
||||||
assertSizes(310, 295, h-20, h+20);
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// workspaceMock.resizeWindow(clientRightBottom, true, false, true, new MockQmlSize(0, -80));
|
|
||||||
// assertSizes(310, 295, h+60, h-20);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const { qtMock, workspaceMock, world } = init(config);
|
|
||||||
const [clientLeftTop, clientLeftBottom, clientRight] = workspaceMock.createClientsWithWidths(300, 200, 300);
|
|
||||||
clientLeftBottom.minSize = new MockQmlSize(295, h-20);
|
|
||||||
|
|
||||||
function assertSizes(leftWidth: number, rightWidth: number, topHeight: number, bottomHeight: number) {
|
|
||||||
const { left, right } = getGridBounds(clientLeftTop, clientRight);
|
|
||||||
Assert.rect(clientLeftTop.frameGeometry, left, tilingArea.top, leftWidth, topHeight);
|
|
||||||
Assert.rect(clientLeftBottom.frameGeometry, left, tilingArea.top+topHeight+gapV, leftWidth, bottomHeight);
|
|
||||||
Assert.rect(clientRight.frameGeometry, left+leftWidth+gapH, tilingArea.top, rightWidth, tilingArea.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
workspaceMock.activeWindow = clientLeftBottom;
|
|
||||||
qtMock.fireShortcut("karousel-window-move-left");
|
|
||||||
assertSizes(300, 300, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeftTop, true, false, false, new MockQmlSize(-10, 0));
|
|
||||||
assertSizes(295, 305, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeftTop, true, false, false, new MockQmlSize(10, 0));
|
|
||||||
assertSizes(305, 295, h, h);
|
|
||||||
|
|
||||||
workspaceMock.resizeWindow(clientLeftTop, true, false, false, new MockQmlSize(-20, 0), new MockQmlSize(20, 0));
|
|
||||||
assertSizes(305, 295, h, h);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
tests.run();
|
|
||||||
36
src/tests/rules/WindowRuleEnforcer.ts
Normal file
36
src/tests/rules/WindowRuleEnforcer.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
const testCases = [
|
||||||
|
{tiledByDefault: true, resourceClass: "unknown", caption: "anything", shouldTile: true},
|
||||||
|
{tiledByDefault: false, resourceClass: "unknown", caption: "anything", shouldTile: false},
|
||||||
|
{tiledByDefault: true, resourceClass: "ksmserver-logout-greeter", caption: "anything", shouldTile: false},
|
||||||
|
{tiledByDefault: true, resourceClass: "org.kde.plasmashell", caption: "something", shouldTile: false},
|
||||||
|
{tiledByDefault: true, resourceClass: "plasmashell", caption: "something", shouldTile: false},
|
||||||
|
{tiledByDefault: false, resourceClass: "org.kde.kfind", caption: "something", shouldTile: true},
|
||||||
|
{tiledByDefault: false, resourceClass: "kfind", caption: "something", shouldTile: true},
|
||||||
|
{tiledByDefault: true, resourceClass: "zoom", caption: "something", shouldTile: true},
|
||||||
|
{tiledByDefault: true, resourceClass: "zoom", caption: "zoom", shouldTile: false},
|
||||||
|
];
|
||||||
|
|
||||||
|
const enforcer = new WindowRuleEnforcer(JSON.parse(defaultWindowRules));
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
const kwinClient: any = createKwinClient(testCase.tiledByDefault, testCase.resourceClass, testCase.caption);
|
||||||
|
assert(enforcer.shouldTile(kwinClient) === testCase.shouldTile, "failed case: " + JSON.stringify(testCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createKwinClient(normalWindow: boolean, resoureClass: string, caption: string) {
|
||||||
|
return {
|
||||||
|
normalWindow: normalWindow,
|
||||||
|
transient: false,
|
||||||
|
managed: true,
|
||||||
|
pid: 100,
|
||||||
|
moveable: true,
|
||||||
|
resizeable: true,
|
||||||
|
popupWindow: false,
|
||||||
|
minimized: false,
|
||||||
|
desktops: [1],
|
||||||
|
activities: [1],
|
||||||
|
resourceClass: resoureClass,
|
||||||
|
caption: caption,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/tests/tests.ts
Normal file
17
src/tests/tests.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
declare const process: {
|
||||||
|
exit(code?: number): void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function assert(assertion: boolean, message?: string) {
|
||||||
|
if (assertion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message != undefined) {
|
||||||
|
console.assert(assertion, message);
|
||||||
|
} else {
|
||||||
|
console.assert(assertion);
|
||||||
|
}
|
||||||
|
console.trace();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": [
|
"include": ["../lib/**/*", "./**/*"]
|
||||||
"../lib/**/*",
|
|
||||||
"./utils/**/*",
|
|
||||||
"./units/**/*",
|
|
||||||
"./flows/**/*",
|
|
||||||
"./main.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
tests.register("PresetWidths", 1, () => {
|
|
||||||
const minWidth = 50;
|
|
||||||
const maxWidth = 800;
|
|
||||||
const spacing = 10;
|
|
||||||
|
|
||||||
const testCases = [
|
|
||||||
{ str: "100%, 50%", result: [395, 800] },
|
|
||||||
{ str: "105%, 50%", result: [395, 800] },
|
|
||||||
{ str: "100px,50 px", result: [50, 100] },
|
|
||||||
{ str: "900px,25 px", result: [50, 800] },
|
|
||||||
{ str: " 100px, 25 % , 0.1 ", result: [71, 100, 192] },
|
|
||||||
{ str: "100px, 25%, 0.1, 100px", result: [71, 100, 192] },
|
|
||||||
{ str: "100px, -25 % , 0.1 ", error: true },
|
|
||||||
{ str: "100px, 25 % , -0.1 ", error: true },
|
|
||||||
{ str: "100px, 25 % , 0.1p", error: true },
|
|
||||||
{ str: "100px, % , 0.1 ", error: true },
|
|
||||||
{ str: "100px, , 0.1 ", error: true },
|
|
||||||
{ str: "100px, 0, 0.1 ", error: true },
|
|
||||||
{ str: "100px,, 0.1 ", error: true },
|
|
||||||
{ str: "100px, 25 % , ", error: true },
|
|
||||||
{ str: "asdf", error: true },
|
|
||||||
{ str: "", error: true },
|
|
||||||
{ str: " ", error: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
function assertWidths(presetWidths: PresetWidths, expectedWidths: number[]) {
|
|
||||||
let currentWidth = 0;
|
|
||||||
for (const expectedWidth of expectedWidths) {
|
|
||||||
currentWidth = presetWidths.next(currentWidth, minWidth, maxWidth);
|
|
||||||
Assert.equal(currentWidth, expectedWidth);
|
|
||||||
}
|
|
||||||
const repeatedWidth = presetWidths.next(currentWidth, minWidth, maxWidth);
|
|
||||||
Assert.equal(repeatedWidth, expectedWidths[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
try {
|
|
||||||
const presetWidths = new PresetWidths(testCase.str, spacing);
|
|
||||||
Assert.assert(!testCase.error);
|
|
||||||
assertWidths(presetWidths, testCase.result!);
|
|
||||||
} catch (error) {
|
|
||||||
Assert.assert(testCase.error === true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
tests.register("WindowRuleEnforcer", 1, () => {
|
|
||||||
screen = new MockQmlRect(0, 0, 800, 600);
|
|
||||||
Workspace = new MockWorkspace();
|
|
||||||
|
|
||||||
const testCases = [
|
|
||||||
{ tiledByDefault: true, resourceClass: "unknown", caption: "anything", shouldTile: true },
|
|
||||||
{ tiledByDefault: false, resourceClass: "unknown", caption: "anything", shouldTile: false },
|
|
||||||
{ tiledByDefault: true, resourceClass: "org.kde.plasmashell", caption: "something", shouldTile: false },
|
|
||||||
{ tiledByDefault: true, resourceClass: "plasmashell", caption: "something", shouldTile: false },
|
|
||||||
{ tiledByDefault: false, resourceClass: "org.kde.kfind", caption: "something", shouldTile: true },
|
|
||||||
{ tiledByDefault: false, resourceClass: "kfind", caption: "something", shouldTile: true },
|
|
||||||
{ tiledByDefault: true, resourceClass: "org.kde.kruler", caption: "anything", shouldTile: false },
|
|
||||||
{ tiledByDefault: true, resourceClass: "kruler", caption: "anything", shouldTile: false },
|
|
||||||
{ tiledByDefault: true, resourceClass: "zoom", caption: "something", shouldTile: true },
|
|
||||||
{ tiledByDefault: true, resourceClass: "zoom", caption: "zoom", shouldTile: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
const enforcer = new WindowRuleEnforcer(JSON.parse(defaultWindowRules));
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const kwinClient: any = createKwinClient(testCase.tiledByDefault, testCase.resourceClass, testCase.caption);
|
|
||||||
Assert.assert(
|
|
||||||
enforcer.shouldTile(kwinClient) === testCase.shouldTile,
|
|
||||||
{ message: "failed case: " + JSON.stringify(testCase) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createKwinClient(normalWindow: boolean, resourceClass: string, caption: string) {
|
|
||||||
return {
|
|
||||||
normalWindow: normalWindow,
|
|
||||||
transient: false,
|
|
||||||
clientGeometry: new MockQmlRect(0, 0, 200, 200),
|
|
||||||
managed: true,
|
|
||||||
pid: 100,
|
|
||||||
moveable: true,
|
|
||||||
resizeable: true,
|
|
||||||
popupWindow: false,
|
|
||||||
minimized: false,
|
|
||||||
desktops: [1],
|
|
||||||
activities: [1],
|
|
||||||
resourceClass: resourceClass,
|
|
||||||
caption: caption,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
tests.register("RateLimiter", 1, () => {
|
|
||||||
const rateLimiter = new RateLimiter(3, 100);
|
|
||||||
|
|
||||||
function testRateLimiter() {
|
|
||||||
Assert.assert(rateLimiter.acquire());
|
|
||||||
Assert.assert(rateLimiter.acquire());
|
|
||||||
Assert.assert(rateLimiter.acquire());
|
|
||||||
Assert.assert(!rateLimiter.acquire());
|
|
||||||
Assert.assert(!rateLimiter.acquire());
|
|
||||||
}
|
|
||||||
|
|
||||||
timeControl(addTime => {
|
|
||||||
testRateLimiter();
|
|
||||||
|
|
||||||
addTime(10);
|
|
||||||
Assert.assert(!rateLimiter.acquire(), { message: "The interval hasn't expired yet" });
|
|
||||||
|
|
||||||
addTime(90);
|
|
||||||
// the rate limiter interval has expired, let's test again
|
|
||||||
testRateLimiter();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
tests.register("fillSpace", 1, () => {
|
|
||||||
const testCases: {
|
|
||||||
availableSpace: number,
|
|
||||||
items: { min: number, max: number }[],
|
|
||||||
expected: number[],
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
availableSpace: 600,
|
|
||||||
items: [],
|
|
||||||
expected: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 600,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
],
|
|
||||||
expected: [300, 300],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 700,
|
|
||||||
items: [
|
|
||||||
{ min: 300, max: 300 },
|
|
||||||
{ min: 300, max: 300 },
|
|
||||||
],
|
|
||||||
expected: [300, 300],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 700,
|
|
||||||
items: [
|
|
||||||
{ min: 300, max: 300 },
|
|
||||||
{ min: 300, max: 300 },
|
|
||||||
{ min: 10, max: 900 },
|
|
||||||
],
|
|
||||||
expected: [300, 300, 100],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 600,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 10, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [250, 350],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 600,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 400, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [200, 400],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 765,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 10, max: 254 },
|
|
||||||
{ min: 10, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [250, 254, 261],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 600,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 150 },
|
|
||||||
{ min: 400, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [150, 450],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 750,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 400, max: 500 },
|
|
||||||
{ min: 10, max: 300 },
|
|
||||||
],
|
|
||||||
expected: [117, 117, 400, 116],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 750,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 120, max: 250 },
|
|
||||||
{ min: 400, max: 500 },
|
|
||||||
{ min: 10, max: 300 },
|
|
||||||
],
|
|
||||||
expected: [115, 120, 400, 115],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 1200,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 10, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [250, 500],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 5,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 10, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [10, 10],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 800,
|
|
||||||
items: [
|
|
||||||
{ min: 114, max: 800 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 109, max: 800 },
|
|
||||||
{ min: 10, max: 800 },
|
|
||||||
],
|
|
||||||
expected: [114, 93, 93, 93, 93, 93, 111, 110],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 801,
|
|
||||||
items: [
|
|
||||||
{ min: 114, max: 800 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 109, max: 800 },
|
|
||||||
{ min: 10, max: 800 },
|
|
||||||
],
|
|
||||||
expected: [114, 93, 93, 93, 93, 93, 111, 111],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 801,
|
|
||||||
items: [
|
|
||||||
{ min: 114, max: 800 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 109, max: 800 },
|
|
||||||
{ min: 10, max: 95 },
|
|
||||||
],
|
|
||||||
expected: [121, 93, 93, 93, 93, 93, 120, 95],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 799,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 86 },
|
|
||||||
{ min: 107, max: 800 },
|
|
||||||
{ min: 107, max: 800 },
|
|
||||||
{ min: 107, max: 800 },
|
|
||||||
{ min: 107, max: 800 },
|
|
||||||
{ min: 107, max: 800 },
|
|
||||||
{ min: 10, max: 91},
|
|
||||||
{ min: 105, max: 800 },
|
|
||||||
],
|
|
||||||
expected: [80, 107, 107, 107, 107, 107, 79, 105],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 1029,
|
|
||||||
items: [
|
|
||||||
{ min: 114, max: 800 },
|
|
||||||
{ min: 114, max: 800 },
|
|
||||||
{ min: 114, max: 800 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 10, max: 93 },
|
|
||||||
{ min: 109, max: 800 },
|
|
||||||
{ min: 10, max: 800 },
|
|
||||||
],
|
|
||||||
expected: [114, 114, 114, 93, 93, 93, 93, 93, 111, 111],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 602,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
],
|
|
||||||
expected: [200, 200, 200],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 602,
|
|
||||||
items: [
|
|
||||||
{ min: 204, max: 600 },
|
|
||||||
{ min: 202, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
],
|
|
||||||
expected: [204, 202, 196],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 803,
|
|
||||||
items: [
|
|
||||||
{ min: 204, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
{ min: 10, max: 600 },
|
|
||||||
],
|
|
||||||
expected: [204, 200, 200, 199],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 900,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 120 },
|
|
||||||
{ min: 10, max: 250 },
|
|
||||||
{ min: 500, max: 500 },
|
|
||||||
{ min: 300, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [50, 50, 500, 300],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 845,
|
|
||||||
items: [
|
|
||||||
{ min: 5, max: 5 },
|
|
||||||
{ min: 10, max: 40 },
|
|
||||||
{ min: 500, max: 500 },
|
|
||||||
{ min: 300, max: 500 },
|
|
||||||
],
|
|
||||||
expected: [5, 40, 500, 300],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
availableSpace: 800,
|
|
||||||
items: [
|
|
||||||
{ min: 10, max: 20 },
|
|
||||||
{ min: 220, max: 221 },
|
|
||||||
{ min: 250, max: 260 },
|
|
||||||
{ min: 300, max: 305 },
|
|
||||||
],
|
|
||||||
expected: [20, 221, 259, 300],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const result = fillSpace(testCase.availableSpace, testCase.items);
|
|
||||||
Assert.equalArrays(
|
|
||||||
result,
|
|
||||||
testCase.expected,
|
|
||||||
{ message: JSON.stringify(testCase) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
tests.register("math", 1, () => {
|
|
||||||
const rect = new MockQmlRect(100, 200, 10, 20);
|
|
||||||
const testCases: {
|
|
||||||
rect: QmlRect,
|
|
||||||
point: QmlPoint,
|
|
||||||
contained: boolean,
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(100, 200),
|
|
||||||
contained: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(110, 220),
|
|
||||||
contained: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(105, 205),
|
|
||||||
contained: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(110.01, 205),
|
|
||||||
contained: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(105, 220.01),
|
|
||||||
contained: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(16, 205),
|
|
||||||
contained: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rect: rect,
|
|
||||||
point: new MockQmlPoint(105, 16),
|
|
||||||
contained: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const result = rectContainsPoint(testCase.rect, testCase.point);
|
|
||||||
Assert.equal(
|
|
||||||
result,
|
|
||||||
testCase.contained,
|
|
||||||
{ message: JSON.stringify(testCase) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
tests.register("Clients.canTileEver", 1, () => {
|
|
||||||
const testCases = [
|
|
||||||
{ clientProperties: { resourceClass: "app", caption: "Title" }, tileable: true },
|
|
||||||
{ clientProperties: { resourceClass: "app", caption: "Title", moveable: false }, tileable: false },
|
|
||||||
{ clientProperties: { resourceClass: "app", caption: "Caption", resizeable: false }, tileable: false },
|
|
||||||
{ clientProperties: { resourceClass: "app", caption: "Caption", normalWindow: false, popupWindow: true }, tileable: false },
|
|
||||||
{ clientProperties: { resourceClass: "app", caption: "Caption", moveable: false, resizeable: false, fullScreen: true }, tileable: true },
|
|
||||||
{ clientProperties: { resourceClass: "ksmserver-logout-greeter", caption: "Caption" }, tileable: false },
|
|
||||||
{ clientProperties: { resourceClass: "xwaylandvideobridge", caption: "" }, tileable: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const kwinClient: any = createKwinClient(testCase.clientProperties);
|
|
||||||
Assert.assert(
|
|
||||||
Clients.canTileEver(kwinClient) === testCase.tileable,
|
|
||||||
{ message: "failed case: " + JSON.stringify(testCase) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createKwinClient(properties: { resourceClass: string, caption: string }) {
|
|
||||||
const defaultProperties = {
|
|
||||||
normalWindow: true,
|
|
||||||
transient: false,
|
|
||||||
managed: true,
|
|
||||||
pid: 100,
|
|
||||||
moveable: true,
|
|
||||||
resizeable: true,
|
|
||||||
fullScreen: false,
|
|
||||||
popupWindow: false,
|
|
||||||
minimized: false,
|
|
||||||
desktops: [1],
|
|
||||||
activities: [1],
|
|
||||||
};
|
|
||||||
return { ...defaultProperties, ...properties };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
namespace Assert {
|
|
||||||
interface Options {
|
|
||||||
message?: string,
|
|
||||||
skip?: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function assert(
|
|
||||||
assertion: boolean,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
if (assertion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message != undefined) {
|
|
||||||
console.assert(assertion, message);
|
|
||||||
} else {
|
|
||||||
console.assert(assertion);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(getStackTrace(skip+1));
|
|
||||||
|
|
||||||
console.log("Random branches:");
|
|
||||||
if (runLog !== undefined) {
|
|
||||||
for (const message of runLog) {
|
|
||||||
console.log(" " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStackTrace(skip: number) {
|
|
||||||
return new Error().stack!.split("\n").slice(skip+2).join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendMessage(base: string, message?: string) {
|
|
||||||
if (message === undefined) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
return `${base}
|
|
||||||
Message: ${message}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMessage(actual: any, expected: any, header: string, message?: string) {
|
|
||||||
return appendMessage(
|
|
||||||
`${header}
|
|
||||||
Expected: ${expected}
|
|
||||||
Actual: ${actual}`,
|
|
||||||
message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function equal(
|
|
||||||
actual: any,
|
|
||||||
expected: any,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
expected === actual,
|
|
||||||
{
|
|
||||||
message: buildMessage(actual, expected, "Values not equal", message),
|
|
||||||
skip: skip + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function equalArrays(
|
|
||||||
actual: any[],
|
|
||||||
expected: any[],
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
actual.length === expected.length && actual.every((item, index) => item === expected[index]),
|
|
||||||
{
|
|
||||||
message: buildMessage(actual, expected, "Arrays not equal", message),
|
|
||||||
skip: skip + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function between(
|
|
||||||
actual: any,
|
|
||||||
min: any,
|
|
||||||
max: any,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
actual >= min && actual <= max,
|
|
||||||
{
|
|
||||||
message: buildMessage(actual, `[${min}, ${max}]`, "Value not in range", message),
|
|
||||||
skip: skip + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function equalRects(
|
|
||||||
actual: QmlRect,
|
|
||||||
expected: QmlRect,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
rectEquals(expected, actual),
|
|
||||||
{
|
|
||||||
message: buildMessage(actual, expected, "QmlRect not equal", message),
|
|
||||||
skip: skip + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rect(
|
|
||||||
actual: QmlRect,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
equalRects(
|
|
||||||
actual,
|
|
||||||
new MockQmlRect(x, y, width, height),
|
|
||||||
{ message: message, skip: skip+1 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function grid(
|
|
||||||
config: Config,
|
|
||||||
tilingArea: QmlRect,
|
|
||||||
columnWidths: number[] | number,
|
|
||||||
grid: KwinClient[][],
|
|
||||||
centered: boolean,
|
|
||||||
stackedColumns: number[] = [],
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
const nColumns = grid.length;
|
|
||||||
function getGridWidth() {
|
|
||||||
function getColumnsWidth() {
|
|
||||||
if (columnWidths instanceof Array) {
|
|
||||||
let columnsWidth = 0;
|
|
||||||
for (const columnWidth of columnWidths) {
|
|
||||||
columnsWidth += columnWidth;
|
|
||||||
}
|
|
||||||
return columnsWidth;
|
|
||||||
} else {
|
|
||||||
return nColumns * columnWidths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gapsWidth = (nColumns-1) * config.gapsInnerHorizontal;
|
|
||||||
return getColumnsWidth() + gapsWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getColumnWidth(column: number) {
|
|
||||||
if (columnWidths instanceof Array) {
|
|
||||||
return columnWidths[column];
|
|
||||||
} else {
|
|
||||||
return columnWidths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gridWidth = getGridWidth();
|
|
||||||
const startX = centered ?
|
|
||||||
tilingArea.x + (tilingArea.width - gridWidth) / 2 :
|
|
||||||
grid[0][0].frameGeometry.x;
|
|
||||||
|
|
||||||
function getColumnX(column: number) {
|
|
||||||
if (columnWidths instanceof Array) {
|
|
||||||
let x = startX;
|
|
||||||
for (let i = 0; i < column; i++) {
|
|
||||||
x += columnWidths[i] + config.gapsInnerHorizontal;
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
} else {
|
|
||||||
return startX + column * (columnWidths + config.gapsInnerHorizontal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes uniformly sized windows within columns of uniform width
|
|
||||||
function getRectInGrid(column: number, window: number, nColumns: number, nWindows: number) {
|
|
||||||
const columnWidth = getColumnWidth(column);
|
|
||||||
const windowHeight = (tilingArea.height - config.gapsInnerVertical * (nWindows-1)) / nWindows;
|
|
||||||
return new MockQmlRect(
|
|
||||||
getColumnX(column),
|
|
||||||
tilingArea.y + (windowHeight + config.gapsInnerVertical) * window,
|
|
||||||
columnWidth,
|
|
||||||
(tilingArea.height - config.gapsInnerVertical * (nWindows-1)) / nWindows,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRectInGridStacked(column: number, window: number, nColumns: number, nWindows: number) {
|
|
||||||
const columnWidth = getColumnWidth(column);
|
|
||||||
return new MockQmlRect(
|
|
||||||
getColumnX(column) + window * config.stackOffsetX,
|
|
||||||
tilingArea.y + window * config.stackOffsetY,
|
|
||||||
columnWidth - (nWindows-1) * config.stackOffsetX,
|
|
||||||
tilingArea.height - (nWindows-1) * config.stackOffsetY,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let iColumn = 0; iColumn < nColumns; iColumn++) {
|
|
||||||
const column = grid[iColumn];
|
|
||||||
const stacked = stackedColumns.includes(iColumn);
|
|
||||||
const getRect = stacked ? getRectInGridStacked : getRectInGrid;
|
|
||||||
const nWindows = column.length;
|
|
||||||
for (let iWindow = 0; iWindow < nWindows; iWindow++) {
|
|
||||||
const window = column[iWindow];
|
|
||||||
equalRects(
|
|
||||||
window.frameGeometry,
|
|
||||||
getRect(iColumn, iWindow, nColumns, nWindows),
|
|
||||||
{ message: appendMessage(`column ${iColumn}, window ${iWindow}`, message), skip: skip+1 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function centered(
|
|
||||||
config: Config,
|
|
||||||
tilingArea: QmlRect,
|
|
||||||
client:KwinClient,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
grid(
|
|
||||||
config,
|
|
||||||
tilingArea,
|
|
||||||
client.frameGeometry.width,
|
|
||||||
[[client]],
|
|
||||||
true,
|
|
||||||
[],
|
|
||||||
{ message: appendMessage("Window not centered", message), skip: skip+1 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fullyVisible(
|
|
||||||
rect: QmlRect,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
rect.left >= tilingArea.left && rect.right <= tilingArea.right,
|
|
||||||
{
|
|
||||||
message: appendMessage(`Rect ${rect} not fully visible`, message),
|
|
||||||
skip: skip + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function notFullyVisible(
|
|
||||||
rect: QmlRect,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
rect.left < tilingArea.left || rect.right > tilingArea.right,
|
|
||||||
{
|
|
||||||
message: appendMessage(`Rect ${rect} is fully visible, but shouldn't be`, message),
|
|
||||||
skip: skip + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function columnsFillTilingArea(
|
|
||||||
columns: KwinClient[],
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
const options = { message: message, skip: skip+1 };
|
|
||||||
let x = tilingArea.left;
|
|
||||||
for (const column of columns) {
|
|
||||||
const width = column.frameGeometry.width;
|
|
||||||
fullyVisible(column.frameGeometry, options);
|
|
||||||
rect(column.frameGeometry, x, tilingArea.top, width, tilingArea.height, options);
|
|
||||||
x += width + gapH;
|
|
||||||
}
|
|
||||||
equal(columns[columns.length-1].frameGeometry.right, tilingArea.right, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tiledClient(
|
|
||||||
clientManager: ClientManager,
|
|
||||||
client: KwinClient,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
clientManager.findTiledWindow(client) !== null,
|
|
||||||
{ message: message, skip: skip+1 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function notTiledClient(
|
|
||||||
clientManager: ClientManager,
|
|
||||||
client: KwinClient,
|
|
||||||
{ message, skip=0 }: Options = {},
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
clientManager.findTiledWindow(client) === null,
|
|
||||||
{ message: message, skip: skip+1 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
class TestRunner {
|
|
||||||
private readonly tests: TestRunner.Test[] = [];
|
|
||||||
|
|
||||||
public register(name: string, count: number, f: () => void) {
|
|
||||||
this.tests.push({ name: name, count: count, f: f });
|
|
||||||
}
|
|
||||||
|
|
||||||
public run() {
|
|
||||||
for (const test of this.tests) {
|
|
||||||
console.log("Running test " + test.name);
|
|
||||||
for (let i = 0; i < test.count; i++) {
|
|
||||||
test.f();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace TestRunner {
|
|
||||||
export interface Test {
|
|
||||||
name: string,
|
|
||||||
count: number,
|
|
||||||
f: () => void,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = new TestRunner();
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
function getDefaultConfig(): Config {
|
|
||||||
const config: any = {};
|
|
||||||
for (const prop of configDef) {
|
|
||||||
config[prop.name] = prop.default;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user