Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d0e83ca2f | ||
|
|
b8650a2e1f | ||
|
|
19f275325d | ||
|
|
9a5a28db82 | ||
|
|
6c698c9c82 | ||
|
|
973956ed5e | ||
|
|
f05ae6686d | ||
|
|
3a47fef028 | ||
|
|
ccf0626795 | ||
|
|
9ba3bc6b0b | ||
|
|
99c6a39ac5 | ||
|
|
85d7bbe777 | ||
|
|
6f69252001 | ||
|
|
60bee26e29 | ||
|
|
7d4eab03b9 | ||
|
|
7070e59044 | ||
|
|
7f5745b2cf | ||
|
|
c7752bf20a | ||
|
|
8149100aac | ||
|
|
99bf71f0b9 | ||
|
|
2b882768d9 | ||
|
|
bb42e4d3ad | ||
|
|
c7effc8913 | ||
|
|
2c433867f3 | ||
|
|
e995555074 | ||
|
|
6a1e018df1 | ||
|
|
872a67e6e1 | ||
|
|
5a57ba76d8 | ||
|
|
55c6932338 | ||
|
|
b1d6193e48 | ||
|
|
456bbf30b4 | ||
|
|
24c1fa0a38 | ||
|
|
ac7566d2cf | ||
|
|
195f4e6d30 | ||
|
|
e8f2a50420 | ||
|
|
9910bc7041 | ||
|
|
1b592c5b4b | ||
|
|
75384d9fb4 | ||
|
|
dba92d3826 | ||
|
|
dbb95e0470 | ||
|
|
056149440d |
4
Makefile
4
Makefile
@@ -3,7 +3,7 @@ CHECKS := true
|
||||
|
||||
.PHONY: *
|
||||
|
||||
build: lint tests
|
||||
build: lint test
|
||||
tsc -p ./src/main --outFile ./package/contents/code/main.js
|
||||
mkdir -p ./package/contents/config
|
||||
./run-ts.sh ./src/generators/config > ./package/contents/config/main.xml
|
||||
@@ -19,7 +19,7 @@ endif
|
||||
lint-fix: npm-install
|
||||
npx eslint ./src --fix
|
||||
|
||||
tests:
|
||||
test:
|
||||
ifeq (${CHECKS}, true)
|
||||
./run-ts.sh ./src/tests
|
||||
endif
|
||||
|
||||
@@ -27,7 +27,7 @@ First install the _org.kde.notification_ QML module (_qml-module-org-kde-notific
|
||||
|
||||
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 node and tsc).
|
||||
Or clone the repo and run `make install` (requires npm, node, and tsc).
|
||||
|
||||
## Key bindings
|
||||
The key bindings can be configured in KDE System Settings among KWin's own keyboard shortcuts.
|
||||
|
||||
@@ -8,6 +8,7 @@ export default tseslint.config(
|
||||
rules: {
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"semi": "error",
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"indent": ["error", 4],
|
||||
},
|
||||
}
|
||||
|
||||
125
package-lock.json
generated
125
package-lock.json
generated
@@ -4,15 +4,16 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "karousel",
|
||||
"devDependencies": {
|
||||
"eslint": "^9.24.0",
|
||||
"typescript-eslint": "^8.30.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz",
|
||||
"integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==",
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
|
||||
"integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
@@ -49,9 +50,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
|
||||
"integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
|
||||
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^2.1.6",
|
||||
@@ -63,18 +64,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
|
||||
"integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
|
||||
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
|
||||
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
|
||||
"version": "0.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
|
||||
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
@@ -107,12 +108,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz",
|
||||
"integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==",
|
||||
"version": "9.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz",
|
||||
"integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/object-schema": {
|
||||
@@ -125,30 +129,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
|
||||
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
|
||||
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.13.0",
|
||||
"@eslint/core": "^0.15.2",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
|
||||
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -390,9 +382,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@@ -454,9 +446,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -518,9 +510,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
@@ -638,19 +630,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.24.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
|
||||
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
|
||||
"version": "9.35.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz",
|
||||
"integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.20.0",
|
||||
"@eslint/config-helpers": "^0.2.0",
|
||||
"@eslint/core": "^0.12.0",
|
||||
"@eslint/config-array": "^0.21.0",
|
||||
"@eslint/config-helpers": "^0.3.1",
|
||||
"@eslint/core": "^0.15.2",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.24.0",
|
||||
"@eslint/plugin-kit": "^0.2.7",
|
||||
"@eslint/js": "9.35.0",
|
||||
"@eslint/plugin-kit": "^0.3.5",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
@@ -661,9 +653,9 @@
|
||||
"cross-spawn": "^7.0.6",
|
||||
"debug": "^4.3.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^8.3.0",
|
||||
"eslint-visitor-keys": "^4.2.0",
|
||||
"espree": "^10.3.0",
|
||||
"eslint-scope": "^8.4.0",
|
||||
"eslint-visitor-keys": "^4.2.1",
|
||||
"espree": "^10.4.0",
|
||||
"esquery": "^1.5.0",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -698,9 +690,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
|
||||
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
@@ -714,9 +706,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -726,14 +718,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1008,10 +1000,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="kcfg_cursorFollowsFocus">
|
||||
<property name="text">
|
||||
<string>Cursor follows focus</string>
|
||||
<string>Cursor follows focus (experimental)</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>When a window gains focus, move the cursor to it</string>
|
||||
@@ -149,14 +149,14 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="kcfg_tiledKeepBelow">
|
||||
<property name="text">
|
||||
<string>Keep tiled windows below</string>
|
||||
<string>Set "Keep Below" for tiled windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="kcfg_floatingKeepAbove">
|
||||
<property name="text">
|
||||
<string>Keep floating windows above</string>
|
||||
<string>Set "Keep Above" for floating windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -458,6 +458,27 @@
|
||||
<string>Window Rules</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_desktops">
|
||||
<property name="text">
|
||||
<string>Tiled desktops:</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="kcfg_tiledDesktops">
|
||||
<property name="toolTip">
|
||||
<string>Regex string to match desktops by desktop name"</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="kcfg_windowRules">
|
||||
<property name="tabChangesFocus">
|
||||
|
||||
@@ -16,6 +16,16 @@ Item {
|
||||
qmlBase.karouselInstance.destroy();
|
||||
}
|
||||
|
||||
Notification {
|
||||
id: notificationInvalidTiledDesktops
|
||||
componentName: "plasma_workspace"
|
||||
eventId: "notification"
|
||||
title: "Karousel"
|
||||
text: "Your Tiled Desktops regex is malformed, please review your Karousel configuration"
|
||||
flags: Notification.Persistent
|
||||
urgency: Notification.HighUrgency
|
||||
}
|
||||
|
||||
Notification {
|
||||
id: notificationInvalidWindowRules
|
||||
componentName: "plasma_workspace"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"Name": "Peter Fajdiga"
|
||||
}],
|
||||
"Id": "karousel",
|
||||
"Version": "0.13",
|
||||
"Version": "0.16",
|
||||
"License": "GPLv3",
|
||||
"Website": "https://github.com/peterfajdiga/karousel",
|
||||
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"
|
||||
|
||||
1
src/extern/global.d.ts
vendored
1
src/extern/global.d.ts
vendored
@@ -2,6 +2,7 @@ declare const Qt: Qt;
|
||||
declare const KWin: KWin;
|
||||
declare const Workspace: Workspace;
|
||||
declare const qmlBase: QmlObject;
|
||||
declare const notificationInvalidTiledDesktops: Notification;
|
||||
declare const notificationInvalidWindowRules: Notification;
|
||||
declare const notificationInvalidPresetWidths: Notification;
|
||||
declare const moveCursorToFocus: DBusCall;
|
||||
|
||||
@@ -15,9 +15,9 @@ function printCols(...columns: (string[] | string)[]) {
|
||||
}
|
||||
|
||||
let nRows = Math.min(...columns.filter(
|
||||
(column: string[] | string) => column instanceof Array
|
||||
(column: string[] | string) => column instanceof Array,
|
||||
).map(
|
||||
(column: string[] | string) => column.length
|
||||
(column: string[] | string) => column.length,
|
||||
));
|
||||
if (nRows === Infinity) {
|
||||
// we only have single string columns
|
||||
@@ -28,12 +28,12 @@ function printCols(...columns: (string[] | string)[]) {
|
||||
(column: string[] | string) => {
|
||||
if (column instanceof Array) {
|
||||
return Math.max(...column.map(
|
||||
(cell: string) => cell.length
|
||||
(cell: string) => cell.length,
|
||||
));
|
||||
} else {
|
||||
return column.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function getCell(col: number, row: number) {
|
||||
|
||||
@@ -13,8 +13,8 @@ class ContextualResizer {
|
||||
return;
|
||||
}
|
||||
|
||||
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
|
||||
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
|
||||
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange);
|
||||
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange);
|
||||
if (leftVisibleColumn === null || rightVisibleColumn === null) {
|
||||
console.assert(false); // should at least see self
|
||||
return;
|
||||
@@ -50,8 +50,8 @@ class ContextualResizer {
|
||||
return;
|
||||
}
|
||||
|
||||
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
|
||||
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
|
||||
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange);
|
||||
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange);
|
||||
if (leftVisibleColumn === null || rightVisibleColumn === null) {
|
||||
console.assert(false); // should at least see self
|
||||
return;
|
||||
@@ -86,4 +86,22 @@ class ContextualResizer {
|
||||
column.setWidth(newWidth, true);
|
||||
desktop.scrollCenterVisible(column);
|
||||
}
|
||||
|
||||
public maximizeWidth(column: Column) {
|
||||
const grid = column.grid;
|
||||
const desktop = grid.desktop;
|
||||
const presetWidths = this.presetWidths.getWidths(column.getMinWidth(), column.getMaxWidth());
|
||||
const maxWidth = presetWidths[presetWidths.length-1];
|
||||
column.setWidth(maxWidth, true);
|
||||
desktop.scrollCenterVisible(column);
|
||||
}
|
||||
|
||||
public minimizeWidth(column: Column) {
|
||||
const grid = column.grid;
|
||||
const desktop = grid.desktop;
|
||||
const presetWidths = this.presetWidths.getWidths(column.getMinWidth(), column.getMaxWidth());
|
||||
const minWidth = presetWidths[0];
|
||||
column.setWidth(minWidth, true);
|
||||
desktop.scrollCenterVisible(column);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,16 @@ class RawResizer {
|
||||
}
|
||||
column.setWidth(newWidth, true);
|
||||
}
|
||||
|
||||
public maximizeWidth(column: Column) {
|
||||
const presetWidths = this.presetWidths.getWidths(column.getMinWidth(), column.getMaxWidth());
|
||||
const maxWidth = presetWidths[presetWidths.length-1];
|
||||
column.setWidth(maxWidth, true);
|
||||
}
|
||||
|
||||
public minimizeWidth(column: Column) {
|
||||
const presetWidths = this.presetWidths.getWidths(column.getMinWidth(), column.getMaxWidth());
|
||||
const minWidth = presetWidths[0];
|
||||
column.setWidth(minWidth, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,5 @@ interface Config {
|
||||
tiledKeepBelow: boolean;
|
||||
floatingKeepAbove: boolean;
|
||||
windowRules: string;
|
||||
tiledDesktops: string;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ const defaultWindowRules = `[
|
||||
"class": "(org\\\\.kde\\\\.)?yakuake",
|
||||
"tile": false
|
||||
},
|
||||
{
|
||||
"class": "wl-copy|wl-paste",
|
||||
"caption": "wl-clipboard",
|
||||
"tile": false
|
||||
},
|
||||
{
|
||||
"class": "steam",
|
||||
"caption": "Steam Big Picture Mode",
|
||||
@@ -188,5 +193,10 @@ const configDef = [
|
||||
name: "windowRules",
|
||||
type: "String",
|
||||
default: defaultWindowRules,
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "tiledDesktops",
|
||||
type: "String",
|
||||
default: ".*",
|
||||
},
|
||||
];
|
||||
|
||||
1
src/lib/extern/kwin.ts
vendored
1
src/lib/extern/kwin.ts
vendored
@@ -102,6 +102,7 @@ interface KwinDesktop {
|
||||
__brand: "KwinDesktop";
|
||||
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
interface ShortcutHandler extends QmlObject {
|
||||
|
||||
4
src/lib/extern/qt.ts
vendored
4
src/lib/extern/qt.ts
vendored
@@ -28,10 +28,6 @@ interface QmlRect {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
readonly top: number;
|
||||
readonly bottom: number; // top + height
|
||||
readonly left: number;
|
||||
readonly right: number; // left + width
|
||||
}
|
||||
|
||||
interface QmlSize {
|
||||
|
||||
@@ -8,7 +8,7 @@ class Actions {
|
||||
if (leftColumn === null) {
|
||||
return;
|
||||
}
|
||||
leftColumn.focus();
|
||||
leftColumn.getWindowToFocus().focus();
|
||||
};
|
||||
|
||||
public readonly focusRight = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
@@ -16,7 +16,7 @@ class Actions {
|
||||
if (rightColumn === null) {
|
||||
return;
|
||||
}
|
||||
rightColumn.focus();
|
||||
rightColumn.getWindowToFocus().focus();
|
||||
};
|
||||
|
||||
public readonly focusUp = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
@@ -62,21 +62,29 @@ class Actions {
|
||||
};
|
||||
|
||||
public readonly focusStart = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const firstColumn = grid.getFirstColumn();
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
}
|
||||
firstColumn.focus();
|
||||
firstColumn.getWindowToFocus().focus();
|
||||
};
|
||||
|
||||
public readonly focusEnd = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const lastColumn = grid.getLastColumn();
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
}
|
||||
lastColumn.focus();
|
||||
lastColumn.getWindowToFocus().focus();
|
||||
};
|
||||
|
||||
public readonly windowMoveLeft = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
@@ -86,12 +94,12 @@ class Actions {
|
||||
if (leftColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(leftColumn, true);
|
||||
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);
|
||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -102,12 +110,12 @@ class Actions {
|
||||
if (rightColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(rightColumn, bottom);
|
||||
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);
|
||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -141,12 +149,12 @@ class Actions {
|
||||
|
||||
public readonly windowMoveStart = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
const newColumn = new Column(grid, null);
|
||||
window.moveToColumn(newColumn, true);
|
||||
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);
|
||||
window.moveToColumn(newColumn, true, FocusPassing.Type.None);
|
||||
};
|
||||
|
||||
public readonly windowToggleFloating = (cm: ClientManager, dm: DesktopManager) => {
|
||||
@@ -184,6 +192,15 @@ class Actions {
|
||||
this.config.columnResizer.decreaseWidth(column);
|
||||
};
|
||||
|
||||
public readonly columnWidthMaximize = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
this.config.columnResizer.maximizeWidth(column);
|
||||
};
|
||||
|
||||
|
||||
public readonly columnWidthMinimize = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
this.config.columnResizer.minimizeWidth(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);
|
||||
@@ -196,8 +213,11 @@ class Actions {
|
||||
|
||||
public readonly columnsWidthEqualize = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const visibleRange = desktop.getCurrentVisibleRange();
|
||||
const visibleColumns = Array.from(desktop.grid.getVisibleColumns(visibleRange, true));
|
||||
const visibleColumns = Array.from(desktop.grid.getVisibleColumns(visibleRange));
|
||||
|
||||
const availableSpace = desktop.tilingArea.width;
|
||||
const gapsWidth = desktop.grid.config.gapsInnerHorizontal * (visibleColumns.length-1);
|
||||
@@ -219,7 +239,7 @@ class Actions {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentVisibleColumns = Array.from(grid.getVisibleColumns(visibleRange, true));
|
||||
const currentVisibleColumns = Array.from(grid.getVisibleColumns(visibleRange));
|
||||
console.assert(currentVisibleColumns.includes(focusedColumn), "should at least contain the focused column");
|
||||
|
||||
const targetColumn = grid.getLeftColumn(currentVisibleColumns[0]);
|
||||
@@ -246,7 +266,7 @@ class Actions {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentVisibleColumns = Array.from(grid.getVisibleColumns(visibleRange, true));
|
||||
const currentVisibleColumns = Array.from(grid.getVisibleColumns(visibleRange));
|
||||
console.assert(currentVisibleColumns.includes(focusedColumn), "should at least contain the focused column");
|
||||
|
||||
const targetColumn = grid.getRightColumn(currentVisibleColumns[currentVisibleColumns.length-1]);
|
||||
@@ -297,11 +317,18 @@ class Actions {
|
||||
};
|
||||
|
||||
private readonly gridScroll = (desktopManager: DesktopManager, amount: number) => {
|
||||
desktopManager.getCurrentDesktop().adjustScroll(amount, false);
|
||||
const desktop = desktopManager.getCurrentDesktop();
|
||||
if (desktop !== undefined) {
|
||||
desktop.adjustScroll(amount, false);
|
||||
}
|
||||
};
|
||||
|
||||
public readonly gridScrollStart = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const firstColumn = grid.getFirstColumn();
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
@@ -310,7 +337,11 @@ class Actions {
|
||||
};
|
||||
|
||||
public readonly gridScrollEnd = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const lastColumn = grid.getLastColumn();
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
@@ -328,8 +359,12 @@ class Actions {
|
||||
};
|
||||
|
||||
public readonly gridScrollLeftColumn = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange());
|
||||
if (column === null) {
|
||||
return;
|
||||
}
|
||||
@@ -343,8 +378,12 @@ class Actions {
|
||||
};
|
||||
|
||||
public readonly gridScrollRightColumn = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange());
|
||||
if (column === null) {
|
||||
return;
|
||||
}
|
||||
@@ -362,12 +401,16 @@ class Actions {
|
||||
};
|
||||
|
||||
public readonly focus = (columnIndex: number, cm: ClientManager, dm: DesktopManager) => {
|
||||
const grid = dm.getCurrentDesktop().grid;
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const grid = desktop.grid;
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null) {
|
||||
return;
|
||||
}
|
||||
targetColumn.focus();
|
||||
targetColumn.getWindowToFocus().focus();
|
||||
};
|
||||
|
||||
public readonly windowMoveToColumn = (columnIndex: number, cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
@@ -375,7 +418,7 @@ class Actions {
|
||||
if (targetColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(targetColumn, true);
|
||||
window.moveToColumn(targetColumn, true, FocusPassing.Type.None);
|
||||
grid.desktop.autoAdjustScroll();
|
||||
};
|
||||
|
||||
@@ -396,7 +439,11 @@ class Actions {
|
||||
if (kwinDesktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const newGrid = dm.getDesktopInCurrentActivity(kwinDesktop).grid;
|
||||
const newDesktop = dm.getDesktopInCurrentActivity(kwinDesktop);
|
||||
if (newDesktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const newGrid = newDesktop.grid;
|
||||
if (newGrid === null || newGrid === oldGrid) {
|
||||
return;
|
||||
}
|
||||
@@ -408,7 +455,11 @@ class Actions {
|
||||
if (kwinDesktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const newGrid = dm.getDesktopInCurrentActivity(kwinDesktop).grid;
|
||||
const newDesktop = dm.getDesktopInCurrentActivity(kwinDesktop);
|
||||
if (newDesktop === undefined) {
|
||||
return;
|
||||
}
|
||||
const newGrid = newDesktop.grid;
|
||||
if (newGrid === null || newGrid === oldGrid) {
|
||||
return;
|
||||
}
|
||||
@@ -422,12 +473,14 @@ namespace Actions {
|
||||
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;
|
||||
maximizeWidth(column: Column): void;
|
||||
minimizeWidth(column: Column): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,16 @@ function getKeyBindings(world: World, actions: Actions): KeyBinding[] {
|
||||
defaultKeySequence: "Meta+Ctrl+-",
|
||||
action: () => world.doIfTiledFocused(actions.columnWidthDecrease),
|
||||
},
|
||||
{
|
||||
name: "column-width-maximize",
|
||||
description: "Increase column width to maximum",
|
||||
action: () => world.doIfTiledFocused(actions.columnWidthMaximize),
|
||||
},
|
||||
{
|
||||
name: "column-width-minimize",
|
||||
description: "Decrease column width to minimum",
|
||||
action: () => world.doIfTiledFocused(actions.columnWidthMinimize),
|
||||
},
|
||||
{
|
||||
name: "cycle-preset-widths",
|
||||
description: "Cycle through preset column widths",
|
||||
|
||||
@@ -21,7 +21,7 @@ class Column {
|
||||
if (targetGrid === this.grid) {
|
||||
this.grid.moveColumn(this, leftColumn);
|
||||
} else {
|
||||
this.grid.onColumnRemoved(this, this.isFocused());
|
||||
this.grid.onColumnRemoved(this, this.isFocused() ? FocusPassing.Type.Immediate : FocusPassing.Type.None);
|
||||
this.grid = targetGrid;
|
||||
targetGrid.onColumnAdded(this, leftColumn);
|
||||
for (const window of this.windows.iterator()) {
|
||||
@@ -79,7 +79,7 @@ class Column {
|
||||
public getMinWidth() {
|
||||
let maxMinWidth = Column.minWidth;
|
||||
for (const window of this.windows.iterator()) {
|
||||
const minWidth = window.client.kwinClient.minSize.width;
|
||||
const minWidth = window.client.kwinClient.minSize.width.ceil();
|
||||
if (minWidth > maxMinWidth) {
|
||||
maxMinWidth = minWidth;
|
||||
}
|
||||
@@ -195,12 +195,8 @@ class Column {
|
||||
return this.focusTaker;
|
||||
}
|
||||
|
||||
public focus() {
|
||||
const window = this.getFocusTaker() ?? this.windows.getFirst();
|
||||
if (window === null) {
|
||||
return;
|
||||
}
|
||||
window.focus();
|
||||
public getWindowToFocus() {
|
||||
return this.getFocusTaker() ?? this.windows.getFirst()!;
|
||||
}
|
||||
|
||||
public isFocused() {
|
||||
@@ -273,7 +269,7 @@ class Column {
|
||||
this.grid.desktop.onLayoutChanged();
|
||||
}
|
||||
|
||||
public onWindowRemoved(window: Window, passFocus: boolean) {
|
||||
public onWindowRemoved(window: Window, passFocus: FocusPassing.Type) {
|
||||
const lastWindow = this.windows.length() === 1;
|
||||
const windowToFocus = this.getAboveWindow(window) ?? this.getBelowWindow(window);
|
||||
|
||||
@@ -288,8 +284,15 @@ class Column {
|
||||
this.destroy(passFocus);
|
||||
} else {
|
||||
this.resizeWindows();
|
||||
if (passFocus && windowToFocus !== null) {
|
||||
windowToFocus.focus();
|
||||
if (windowToFocus !== null) {
|
||||
switch (passFocus) {
|
||||
case FocusPassing.Type.Immediate:
|
||||
windowToFocus.focus();
|
||||
break;
|
||||
case FocusPassing.Type.OnUnfocus:
|
||||
this.grid.focusPasser.request(windowToFocus.client.kwinClient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +311,7 @@ class Column {
|
||||
}
|
||||
}
|
||||
|
||||
private destroy(passFocus: boolean) {
|
||||
private destroy(passFocus: FocusPassing.Type) {
|
||||
this.grid.onColumnRemoved(this, passFocus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ class Desktop {
|
||||
private readonly config: Desktop.Config,
|
||||
private readonly getScreen: () => Output,
|
||||
layoutConfig: LayoutConfig,
|
||||
focusPasser: FocusPassing.Passer,
|
||||
) {
|
||||
this.scrollX = 0;
|
||||
this.gestureScrollXInitial = null;
|
||||
this.dirty = true;
|
||||
this.dirtyScroll = true;
|
||||
this.dirtyPins = true;
|
||||
this.grid = new Grid(this, layoutConfig);
|
||||
this.grid = new Grid(this, layoutConfig, focusPasser);
|
||||
this.clientArea = Desktop.getClientArea(this.getScreen(), kwinDesktop);
|
||||
this.tilingArea = Desktop.getTilingArea(this.clientArea, kwinDesktop, pinManager, config);
|
||||
}
|
||||
@@ -140,8 +141,20 @@ class Desktop {
|
||||
this.setScroll(this.gestureScrollXInitial + this.config.gestureScrollStep * amount, false);
|
||||
}
|
||||
|
||||
public gestureScrollFinish() {
|
||||
public gestureScrollFinish(focusedWindow: Window|null) {
|
||||
const scrolledRight = this.scrollX > this.gestureScrollXInitial!;
|
||||
this.gestureScrollXInitial = null;
|
||||
|
||||
const visibleRange = this.getCurrentVisibleRange();
|
||||
if (focusedWindow !== null && !Range.contains(visibleRange, focusedWindow.column)) {
|
||||
// the focused window is no longer visible, find a new window to focus
|
||||
const focusTargetColumn = scrolledRight ?
|
||||
this.grid.getLeftmostVisibleColumn(visibleRange) :
|
||||
this.grid.getRightmostVisibleColumn(visibleRange);
|
||||
if (focusTargetColumn !== null) {
|
||||
focusTargetColumn.getWindowToFocus().focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public arrange() {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
class Grid {
|
||||
public readonly desktop: Desktop;
|
||||
public readonly config: LayoutConfig;
|
||||
public readonly focusPasser: FocusPassing.Passer;
|
||||
private readonly columns: LinkedList<Column>;
|
||||
private lastFocusedColumn: Column|null;
|
||||
private width: number;
|
||||
private userResize: boolean; // is any part of the grid being resized by the user
|
||||
private readonly userResizeFinishedDelayer: Delayer;
|
||||
|
||||
constructor(desktop: Desktop, config: LayoutConfig) {
|
||||
constructor(desktop: Desktop, config: LayoutConfig, focusPasser: FocusPassing.Passer) {
|
||||
this.desktop = desktop;
|
||||
this.config = config;
|
||||
this.focusPasser = focusPasser;
|
||||
this.columns = new LinkedList();
|
||||
this.lastFocusedColumn = null;
|
||||
this.width = 0;
|
||||
@@ -104,7 +106,7 @@ class Grid {
|
||||
this.width = x - this.config.gapsInnerHorizontal;
|
||||
}
|
||||
|
||||
public getLeftmostVisibleColumn(visibleRange: Range, fullyVisible: boolean) {
|
||||
public getLeftmostVisibleColumn(visibleRange: Range) {
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (Range.contains(visibleRange, column)) {
|
||||
return column;
|
||||
@@ -113,7 +115,7 @@ class Grid {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getRightmostVisibleColumn(visibleRange: Range, fullyVisible: boolean) {
|
||||
public getRightmostVisibleColumn(visibleRange: Range) {
|
||||
let last = null;
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (Range.contains(visibleRange, column)) {
|
||||
@@ -125,7 +127,7 @@ class Grid {
|
||||
return last;
|
||||
}
|
||||
|
||||
public *getVisibleColumns(visibleRange: Range, fullyVisible: boolean) {
|
||||
public *getVisibleColumns(visibleRange: Range) {
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (Range.contains(visibleRange, column)) {
|
||||
yield column;
|
||||
@@ -156,7 +158,7 @@ class Grid {
|
||||
this.desktop.autoAdjustScroll();
|
||||
}
|
||||
|
||||
public onColumnRemoved(column: Column, passFocus: boolean) {
|
||||
public onColumnRemoved(column: Column, passFocus: FocusPassing.Type) {
|
||||
const isLastColumn = this.columns.length() === 1;
|
||||
const rightColumn = this.getRightColumn(column);
|
||||
const columnToFocus = isLastColumn ? null : this.getLeftColumn(column) ?? rightColumn;
|
||||
@@ -168,11 +170,17 @@ class Grid {
|
||||
this.columnsSetX(rightColumn);
|
||||
|
||||
this.desktop.onLayoutChanged();
|
||||
if (passFocus && columnToFocus !== null) {
|
||||
columnToFocus.focus();
|
||||
} else {
|
||||
this.desktop.autoAdjustScroll();
|
||||
if (columnToFocus !== null) {
|
||||
switch (passFocus) {
|
||||
case FocusPassing.Type.Immediate:
|
||||
columnToFocus.getWindowToFocus().focus();
|
||||
return;
|
||||
case FocusPassing.Type.OnUnfocus:
|
||||
this.focusPasser.request(columnToFocus.getWindowToFocus().client.kwinClient);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.desktop.autoAdjustScroll();
|
||||
}
|
||||
|
||||
public onColumnWidthChanged(column: Column) {
|
||||
|
||||
@@ -7,7 +7,7 @@ class Window {
|
||||
|
||||
constructor(client: ClientWrapper, column: Column) {
|
||||
this.client = client;
|
||||
this.height = client.kwinClient.frameGeometry.height;
|
||||
this.height = client.kwinClient.frameGeometry.height.round();
|
||||
|
||||
let maximizedMode = this.client.getMaximizedMode();
|
||||
if (maximizedMode === undefined) {
|
||||
@@ -23,11 +23,11 @@ class Window {
|
||||
column.onWindowAdded(this, true);
|
||||
}
|
||||
|
||||
public moveToColumn(targetColumn: Column, bottom: boolean) {
|
||||
public moveToColumn(targetColumn: Column, bottom: boolean, passFocus: FocusPassing.Type) {
|
||||
if (targetColumn === this.column) {
|
||||
return;
|
||||
}
|
||||
this.column.onWindowRemoved(this, this.isFocused() && targetColumn.grid !== this.column.grid);
|
||||
this.column.onWindowRemoved(this, passFocus);
|
||||
this.column = targetColumn;
|
||||
targetColumn.onWindowAdded(this, bottom);
|
||||
}
|
||||
@@ -61,6 +61,11 @@ class Window {
|
||||
|
||||
public focus() {
|
||||
this.client.focus();
|
||||
const kwinClient = this.client.kwinClient;
|
||||
if (!this.isFocused()) {
|
||||
// in some situations focus assignment just doesn't work, let's do it later
|
||||
this.column.grid.focusPasser.request(kwinClient);
|
||||
}
|
||||
}
|
||||
|
||||
public isFocused() {
|
||||
@@ -118,11 +123,11 @@ class Window {
|
||||
|
||||
public onFrameGeometryChanged() {
|
||||
const newGeometry = this.client.kwinClient.frameGeometry;
|
||||
this.column.setWidth(newGeometry.width, true);
|
||||
this.column.setWidth(newGeometry.width.round(), true);
|
||||
this.column.grid.desktop.onLayoutChanged();
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.column.onWindowRemoved(this, passFocus);
|
||||
}
|
||||
}
|
||||
|
||||
30
src/lib/rules/DesktopFilter.ts
Normal file
30
src/lib/rules/DesktopFilter.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
class DesktopFilter {
|
||||
private readonly desktopRegex: RegExp | null; // null means all desktops
|
||||
|
||||
constructor(desktopsConfig: string) {
|
||||
this.desktopRegex = DesktopFilter.parseDesktopConfig(desktopsConfig);
|
||||
}
|
||||
|
||||
public shouldWorkOnDesktop(kwinDesktop: KwinDesktop): boolean {
|
||||
if (this.desktopRegex === null) {
|
||||
return true; // Work on all desktops
|
||||
}
|
||||
return this.desktopRegex.test(kwinDesktop.name);
|
||||
}
|
||||
|
||||
private static parseDesktopConfig(config: string): RegExp | null {
|
||||
const trimmed = config.trim();
|
||||
|
||||
if (trimmed.length === 0) {
|
||||
return null; // Empty config means work on all desktops
|
||||
}
|
||||
|
||||
try {
|
||||
return new RegExp(`^${trimmed}$`);
|
||||
} catch (e) {
|
||||
notificationInvalidTiledDesktops.sendEvent();
|
||||
log(`Invalid regex pattern in tiledDesktops config: ${trimmed}. Working on all desktops.`);
|
||||
return null; // Invalid regex means work on all desktops as fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class WindowRuleEnforcer {
|
||||
const ruleCaption = WindowRuleEnforcer.parseRegex(windowRule.caption);
|
||||
const ruleString = ClientMatcher.getRuleString(
|
||||
WindowRuleEnforcer.wrapParens(ruleClass),
|
||||
WindowRuleEnforcer.wrapParens(ruleCaption)
|
||||
WindowRuleEnforcer.wrapParens(ruleCaption),
|
||||
);
|
||||
|
||||
(windowRule.tile ? tileRegexes : floatRegexes).push(ruleString);
|
||||
|
||||
@@ -20,6 +20,6 @@ function initQmlTimer() {
|
||||
return Qt.createQmlObject(
|
||||
`import QtQuick 6.0
|
||||
Timer {}`,
|
||||
qmlBase
|
||||
qmlBase,
|
||||
) as QmlTimer;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
interface Number {
|
||||
round(this: number): number;
|
||||
floor(this: number): number;
|
||||
ceil(this: number): number;
|
||||
}
|
||||
|
||||
Number.prototype.round = function() {
|
||||
return Math.round(this);
|
||||
};
|
||||
|
||||
Number.prototype.floor = function() {
|
||||
return Math.floor(this);
|
||||
};
|
||||
|
||||
Number.prototype.ceil = function() {
|
||||
return Math.ceil(this);
|
||||
};
|
||||
|
||||
interface Function {
|
||||
partial<H extends any[], T extends any[], R>(
|
||||
this: (...args: [...H, ...T]) => R,
|
||||
|
||||
@@ -24,9 +24,34 @@ function pointEquals(a: QmlPoint, b: QmlPoint) {
|
||||
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;
|
||||
function rectRight(rect: QmlRect) {
|
||||
return rect.x + rect.width;
|
||||
}
|
||||
|
||||
function rectBottom(rect: QmlRect) {
|
||||
return rect.y + rect.height;
|
||||
}
|
||||
|
||||
function rectContainsPoint(rect: QmlRect, point: QmlPoint) {
|
||||
return rect.x <= point.x &&
|
||||
rectRight(rect) >= point.x &&
|
||||
rect.y <= point.y &&
|
||||
rectBottom(rect) >= point.y;
|
||||
}
|
||||
|
||||
function roundQtRect(rect: QmlRect) {
|
||||
return Qt.rect(
|
||||
rect.x.round(),
|
||||
rect.y.round(),
|
||||
rect.width.round(),
|
||||
rect.height.round(),
|
||||
);
|
||||
}
|
||||
|
||||
function rectRightRound(rect: QmlRect) {
|
||||
return rect.x.round() + rect.width.round();
|
||||
}
|
||||
|
||||
function rectBottomRound(rect: QmlRect) {
|
||||
return rect.y.round() + rect.height.round();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
function initWorkspaceSignalHandlers(world: World) {
|
||||
function initWorkspaceSignalHandlers(world: World, focusPasser: FocusPassing.Passer) {
|
||||
const manager = new SignalManager();
|
||||
|
||||
manager.connect(Workspace.windowAdded, (kwinClient: KwinClient) => {
|
||||
@@ -9,17 +9,19 @@ function initWorkspaceSignalHandlers(world: World) {
|
||||
|
||||
manager.connect(Workspace.windowRemoved, (kwinClient: KwinClient) => {
|
||||
world.do((clientManager, desktopManager) => {
|
||||
clientManager.removeClient(kwinClient, true);
|
||||
clientManager.removeClient(kwinClient, FocusPassing.Type.Immediate);
|
||||
});
|
||||
});
|
||||
|
||||
manager.connect(Workspace.windowActivated, (kwinClient: KwinClient|null) => {
|
||||
if (kwinClient === null) {
|
||||
return;
|
||||
focusPasser.activate();
|
||||
} else {
|
||||
focusPasser.clearIfDifferent(kwinClient);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
clientManager.onClientFocused(kwinClient);
|
||||
});
|
||||
}
|
||||
world.do((clientManager, desktopManager) => {
|
||||
clientManager.onClientFocused(kwinClient);
|
||||
});
|
||||
});
|
||||
|
||||
manager.connect(Workspace.currentDesktopChanged, () => {
|
||||
|
||||
@@ -31,16 +31,16 @@ class ClientManager {
|
||||
console.assert(!this.hasClient(kwinClient));
|
||||
|
||||
let constructState: (client: ClientWrapper) => ClientState.State;
|
||||
let desktop: Desktop | undefined;
|
||||
if (kwinClient.dock) {
|
||||
constructState = () => new ClientState.Docked(this.world, kwinClient);
|
||||
} else if (
|
||||
Clients.canTileEver(kwinClient) &&
|
||||
this.windowRuleEnforcer.shouldTile(kwinClient)
|
||||
this.windowRuleEnforcer.shouldTile(kwinClient) &&
|
||||
(desktop = this.desktopManager.getDesktopForClient(kwinClient)) !== undefined
|
||||
) {
|
||||
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 {
|
||||
constructState = (client: ClientWrapper) => new ClientState.Floating(this.world, client, this.config, false);
|
||||
@@ -55,13 +55,16 @@ class ClientManager {
|
||||
this.clientMap.set(kwinClient, client);
|
||||
}
|
||||
|
||||
public removeClient(kwinClient: KwinClient, passFocus: boolean) {
|
||||
public removeClient(kwinClient: KwinClient, passFocus: FocusPassing.Type) {
|
||||
console.assert(this.hasClient(kwinClient));
|
||||
const client = this.clientMap.get(kwinClient);
|
||||
if (client === undefined) {
|
||||
return;
|
||||
}
|
||||
client.destroy(passFocus && kwinClient === this.lastFocusedClient);
|
||||
if (kwinClient !== this.lastFocusedClient) {
|
||||
passFocus = FocusPassing.Type.None;
|
||||
}
|
||||
client.destroy(passFocus);
|
||||
this.clientMap.delete(kwinClient);
|
||||
}
|
||||
|
||||
@@ -84,9 +87,10 @@ class ClientManager {
|
||||
return;
|
||||
}
|
||||
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||
const passFocus = kwinClient === this.lastFocusedClient ? FocusPassing.Type.Immediate : FocusPassing.Type.None;
|
||||
client.stateManager.setState(
|
||||
() => new ClientState.TiledMinimized(this.world, client),
|
||||
kwinClient === this.lastFocusedClient,
|
||||
passFocus,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -95,14 +99,14 @@ class ClientManager {
|
||||
if (client.stateManager.getState() instanceof ClientState.Tiled) {
|
||||
return;
|
||||
}
|
||||
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), false);
|
||||
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, grid), FocusPassing.Type.None);
|
||||
}
|
||||
|
||||
public floatClient(client: ClientWrapper) {
|
||||
if (client.stateManager.getState() instanceof ClientState.Floating) {
|
||||
return;
|
||||
}
|
||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false);
|
||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), FocusPassing.Type.None);
|
||||
}
|
||||
|
||||
public tileKwinClient(kwinClient: KwinClient, grid: Grid) {
|
||||
@@ -131,7 +135,7 @@ class ClientManager {
|
||||
kwinClient.tile = null;
|
||||
return;
|
||||
}
|
||||
client.stateManager.setState(() => new ClientState.Pinned(this.world, this.pinManager, this.desktopManager, kwinClient, this.config), false);
|
||||
client.stateManager.setState(() => new ClientState.Pinned(this.world, this.pinManager, this.desktopManager, kwinClient, this.config), FocusPassing.Type.None);
|
||||
this.pinManager.addClient(kwinClient);
|
||||
for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) {
|
||||
desktop.onPinsChanged();
|
||||
@@ -144,7 +148,7 @@ class ClientManager {
|
||||
return;
|
||||
}
|
||||
console.assert(client.stateManager.getState() instanceof ClientState.Pinned);
|
||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, false), false);
|
||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, false), FocusPassing.Type.None);
|
||||
this.pinManager.removeClient(kwinClient);
|
||||
for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) {
|
||||
desktop.onPinsChanged();
|
||||
@@ -164,9 +168,9 @@ class ClientManager {
|
||||
if (desktop === undefined) {
|
||||
return;
|
||||
}
|
||||
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, desktop.grid), false);
|
||||
client.stateManager.setState(() => new ClientState.Tiled(this.world, client, desktop.grid), FocusPassing.Type.None);
|
||||
} else if (clientState instanceof ClientState.Tiled) {
|
||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), false);
|
||||
client.stateManager.setState(() => new ClientState.Floating(this.world, client, this.config, true), FocusPassing.Type.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +208,7 @@ class ClientManager {
|
||||
|
||||
private removeAllClients() {
|
||||
for (const kwinClient of Array.from(this.clientMap.keys())) {
|
||||
this.removeClient(kwinClient, false);
|
||||
this.removeClient(kwinClient, FocusPassing.Type.None);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class ClientWrapper {
|
||||
}
|
||||
this.signalManager = ClientWrapper.initSignalManager(this);
|
||||
this.rulesSignalManager = rulesSignalManager;
|
||||
this.preferredWidth = kwinClient.frameGeometry.width;
|
||||
this.preferredWidth = kwinClient.frameGeometry.width.round();
|
||||
this.manipulatingGeometry = new Doer();
|
||||
this.lastPlacement = null;
|
||||
this.stateManager = new ClientState.Manager(constructInitialState(this));
|
||||
@@ -49,10 +49,10 @@ class ClientWrapper {
|
||||
if (Clients.isOnOneOfVirtualDesktops(this.kwinClient, kwinDesktops)) {
|
||||
const frame = this.kwinClient.frameGeometry;
|
||||
this.kwinClient.frameGeometry = Qt.rect(
|
||||
frame.x + dx,
|
||||
frame.y + dy,
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame.x.round() + dx,
|
||||
frame.y.round() + dy,
|
||||
frame.width.round(),
|
||||
frame.height.round(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,15 +142,15 @@ class ClientWrapper {
|
||||
if (!Clients.isOnVirtualDesktop(this.kwinClient, Workspace.currentDesktop)) {
|
||||
return;
|
||||
}
|
||||
const frame = this.kwinClient.frameGeometry;
|
||||
if (frame.left < screenSize.left) {
|
||||
frame.x = screenSize.left;
|
||||
} else if (frame.right > screenSize.right) {
|
||||
frame.x = screenSize.right - frame.width;
|
||||
const frame = roundQtRect(this.kwinClient.frameGeometry);
|
||||
if (frame.x < screenSize.x) {
|
||||
frame.x = screenSize.x;
|
||||
} else if (rectRight(frame) > rectRight(screenSize)) {
|
||||
frame.x = rectRight(screenSize) - frame.width;
|
||||
}
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.stateManager.destroy(passFocus);
|
||||
this.signalManager.destroy();
|
||||
if (this.rulesSignalManager !== null) {
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace Clients {
|
||||
|
||||
export function isFullScreenGeometry(kwinClient: KwinClient) {
|
||||
const fullScreenArea = Workspace.clientArea(ClientAreaOption.FullScreenArea, kwinClient.output, getKwinDesktopApprox(kwinClient));
|
||||
return kwinClient.clientGeometry.width === fullScreenArea.width &&
|
||||
kwinClient.clientGeometry.height === fullScreenArea.height;
|
||||
return kwinClient.clientGeometry.width.round() >= fullScreenArea.width &&
|
||||
kwinClient.clientGeometry.height.round() >= fullScreenArea.height;
|
||||
}
|
||||
|
||||
export function isOnVirtualDesktop(kwinClient: KwinClient, kwinDesktop: KwinDesktop) {
|
||||
|
||||
@@ -7,9 +7,9 @@ class DesktopManager {
|
||||
constructor(
|
||||
private readonly pinManager: PinManager,
|
||||
private readonly config: Desktop.Config,
|
||||
public readonly layoutConfig: LayoutConfig,
|
||||
currentActivity: string,
|
||||
currentDesktop: KwinDesktop,
|
||||
private readonly layoutConfig: LayoutConfig,
|
||||
private readonly focusPasser: FocusPassing.Passer,
|
||||
private readonly desktopFilter: DesktopFilter,
|
||||
) {
|
||||
this.pinManager = pinManager;
|
||||
this.config = config;
|
||||
@@ -18,10 +18,12 @@ class DesktopManager {
|
||||
this.selectedScreen = Workspace.activeScreen;
|
||||
this.kwinActivities = new Set(Workspace.activities);
|
||||
this.kwinDesktops = new Set(Workspace.desktops);
|
||||
this.addDesktop(currentActivity, currentDesktop);
|
||||
}
|
||||
|
||||
public getDesktop(activity: string, kwinDesktop: KwinDesktop) {
|
||||
if (!this.desktopFilter.shouldWorkOnDesktop(kwinDesktop)) {
|
||||
return undefined;
|
||||
}
|
||||
const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
|
||||
const desktop = this.desktops.get(desktopKey);
|
||||
if (desktop !== undefined) {
|
||||
@@ -54,6 +56,7 @@ class DesktopManager {
|
||||
this.config,
|
||||
() => this.selectedScreen,
|
||||
this.layoutConfig,
|
||||
this.focusPasser,
|
||||
);
|
||||
this.desktops.set(desktopKey, desktop);
|
||||
return desktop;
|
||||
|
||||
51
src/lib/world/FocusPassing.ts
Normal file
51
src/lib/world/FocusPassing.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class PinManager {
|
||||
}
|
||||
|
||||
public getAvailableSpace(kwinDesktop: KwinDesktop, screen: QmlRect) {
|
||||
const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right);
|
||||
const baseLot = new PinManager.Lot(screen.y, rectBottom(screen), screen.x, rectRight(screen));
|
||||
let lots = [baseLot];
|
||||
for (const client of this.pinnedClients) {
|
||||
if (!Clients.isOnVirtualDesktop(client, kwinDesktop) || client.minimized) {
|
||||
@@ -23,7 +23,7 @@ class PinManager {
|
||||
|
||||
const newLots: PinManager.Lot[] = [];
|
||||
for (const lot of lots) {
|
||||
lot.split(newLots, client.frameGeometry);
|
||||
lot.split(newLots, roundQtRect(client.frameGeometry));
|
||||
}
|
||||
lots = newLots;
|
||||
}
|
||||
@@ -60,23 +60,23 @@ namespace PinManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obstacle.top - this.top >= Lot.minHeight) {
|
||||
destLots.push(new Lot(this.top, obstacle.top, this.left, this.right));
|
||||
if (obstacle.y - this.top >= Lot.minHeight) {
|
||||
destLots.push(new Lot(this.top, obstacle.y, this.left, this.right));
|
||||
}
|
||||
if (this.bottom - obstacle.bottom >= Lot.minHeight) {
|
||||
destLots.push(new Lot(obstacle.bottom, this.bottom, this.left, this.right));
|
||||
if (this.bottom - rectBottom(obstacle) >= Lot.minHeight) {
|
||||
destLots.push(new Lot(rectBottom(obstacle), this.bottom, this.left, this.right));
|
||||
}
|
||||
if (obstacle.left - this.left >= Lot.minWidth) {
|
||||
destLots.push(new Lot(this.top, this.bottom, this.left, obstacle.left));
|
||||
if (obstacle.x - this.left >= Lot.minWidth) {
|
||||
destLots.push(new Lot(this.top, this.bottom, this.left, obstacle.x));
|
||||
}
|
||||
if (this.right - obstacle.right >= Lot.minWidth) {
|
||||
destLots.push(new Lot(this.top, this.bottom, obstacle.right, this.right));
|
||||
if (this.right - rectRight(obstacle) >= Lot.minWidth) {
|
||||
destLots.push(new Lot(this.top, this.bottom, rectRight(obstacle), this.right));
|
||||
}
|
||||
}
|
||||
|
||||
private contains(obstacle: QmlRect) {
|
||||
return obstacle.right > this.left && obstacle.left < this.right &&
|
||||
obstacle.bottom > this.top && obstacle.top < this.bottom;
|
||||
return rectRight(obstacle) > this.left && obstacle.x < this.right &&
|
||||
rectBottom(obstacle) > this.top && obstacle.y < this.bottom;
|
||||
}
|
||||
|
||||
public area() {
|
||||
|
||||
@@ -8,7 +8,8 @@ class World {
|
||||
private readonly cursorFollowsFocus: boolean;
|
||||
|
||||
constructor(config: Config) {
|
||||
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
||||
const focusPasser = new FocusPassing.Passer();
|
||||
this.workspaceSignalManager = initWorkspaceSignalHandlers(this, focusPasser);
|
||||
this.cursorFollowsFocus = config.cursorFollowsFocus;
|
||||
|
||||
let presetWidths = {
|
||||
@@ -68,8 +69,8 @@ class World {
|
||||
gestureScrollStep: config.gestureScrollStep,
|
||||
},
|
||||
layoutConfig,
|
||||
Workspace.currentActivity,
|
||||
Workspace.currentDesktop,
|
||||
focusPasser,
|
||||
new DesktopFilter(config.tiledDesktops),
|
||||
);
|
||||
this.clientManager = new ClientManager(config, this, this.desktopManager, this.pinManager);
|
||||
this.addExistingClients();
|
||||
@@ -96,13 +97,21 @@ class World {
|
||||
}
|
||||
|
||||
private update() {
|
||||
this.desktopManager.getCurrentDesktop().arrange();
|
||||
this.moveCursorToFocus();
|
||||
const currentDesktop = this.desktopManager.getCurrentDesktop();
|
||||
if (currentDesktop !== undefined) {
|
||||
currentDesktop.arrange();
|
||||
this.moveCursorToFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private moveCursorToFocus() {
|
||||
if (this.cursorFollowsFocus && Workspace.activeWindow !== null) {
|
||||
const cursorAlreadyInFocus = rectContainsPoint(Workspace.activeWindow.frameGeometry, Workspace.cursorPos);
|
||||
// Only move cursor for tiled windows
|
||||
const tiledWindow = this.clientManager.findTiledWindow(Workspace.activeWindow);
|
||||
if (tiledWindow === null) {
|
||||
return;
|
||||
}
|
||||
const cursorAlreadyInFocus = rectContainsPoint(roundQtRect(Workspace.activeWindow.frameGeometry), Workspace.cursorPos);
|
||||
if (cursorAlreadyInFocus) {
|
||||
return;
|
||||
}
|
||||
@@ -139,11 +148,23 @@ class World {
|
||||
}
|
||||
|
||||
public gestureScroll(amount: number) {
|
||||
this.do((clientManager, desktopManager) => desktopManager.getCurrentDesktop().gestureScroll(amount));
|
||||
this.do((clientManager, desktopManager) => {
|
||||
const currentDesktop = desktopManager.getCurrentDesktop();
|
||||
if (currentDesktop !== undefined) {
|
||||
currentDesktop.gestureScroll(amount);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public gestureScrollFinish() {
|
||||
this.do((clientManager, desktopManager) => desktopManager.getCurrentDesktop().gestureScrollFinish());
|
||||
this.do((clientManager, desktopManager) => {
|
||||
const focusedWindow = Workspace.activeWindow === null ? null : clientManager.findTiledWindow(Workspace.activeWindow);
|
||||
const currentDesktop = desktopManager.getCurrentDesktop();
|
||||
if (currentDesktop !== undefined) {
|
||||
console.assert(focusedWindow === null || focusedWindow.column.grid.desktop === currentDesktop);
|
||||
currentDesktop.gestureScrollFinish(focusedWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ClientState {
|
||||
world.onScreenResized();
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.signalManager.destroy();
|
||||
this.world.onScreenResized();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace ClientState {
|
||||
this.signalManager = Floating.initSignalManager(world, client.kwinClient);
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.signalManager.destroy();
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ namespace ClientState {
|
||||
const clientRect = client.kwinClient.frameGeometry;
|
||||
const width = client.preferredWidth;
|
||||
client.place(
|
||||
clientRect.x,
|
||||
clientRect.y,
|
||||
clientRect.x.round(),
|
||||
clientRect.y.round(),
|
||||
width,
|
||||
Math.min(clientRect.height, Math.round(placementArea.height / 2)),
|
||||
Math.min(clientRect.height.round(), Math.round(placementArea.height / 2)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ClientState {
|
||||
this.state = initialState;
|
||||
}
|
||||
|
||||
public setState(constructNewState: () => State, passFocus: boolean) {
|
||||
public setState(constructNewState: () => State, passFocus: FocusPassing.Type) {
|
||||
this.state.destroy(passFocus);
|
||||
this.state = constructNewState();
|
||||
}
|
||||
@@ -15,12 +15,12 @@ namespace ClientState {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.state.destroy(passFocus);
|
||||
}
|
||||
}
|
||||
|
||||
export interface State {
|
||||
destroy(passFocus: boolean): void;
|
||||
destroy(passFocus: FocusPassing.Type): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace ClientState {
|
||||
this.signalManager = Pinned.initSignalManager(world, pinManager, kwinClient);
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.signalManager.destroy();
|
||||
this.pinManager.removeClient(this.kwinClient);
|
||||
for (const desktop of this.desktopManager.getDesktopsForClient(this.kwinClient)) {
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace ClientState {
|
||||
this.signalManager = Tiled.initSignalManager(world, window, grid.config);
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.signalManager.destroy();
|
||||
|
||||
const window = this.window;
|
||||
@@ -123,7 +123,12 @@ namespace ClientState {
|
||||
return;
|
||||
}
|
||||
|
||||
const newGeometry = client.kwinClient.frameGeometry;
|
||||
const newGeometry = roundQtRect(client.kwinClient.frameGeometry);
|
||||
if (rectEquals(oldGeometry, newGeometry)) {
|
||||
// no real changes, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const oldCenterX = oldGeometry.x + oldGeometry.width/2;
|
||||
const oldCenterY = oldGeometry.y + oldGeometry.height/2;
|
||||
const newCenterX = newGeometry.x + newGeometry.width/2;
|
||||
@@ -142,7 +147,7 @@ namespace ClientState {
|
||||
window.column.onUserResizeWidth(
|
||||
resizeStartWidth,
|
||||
newGeometry.width - resizeStartWidth,
|
||||
newGeometry.left !== oldGeometry.left,
|
||||
newGeometry.x !== oldGeometry.x,
|
||||
resizeNeighbor,
|
||||
);
|
||||
}
|
||||
@@ -193,9 +198,9 @@ namespace ClientState {
|
||||
private static getResizeNeighborColumn(window: Window) {
|
||||
const kwinClient = window.client.kwinClient;
|
||||
const column = window.column;
|
||||
if (Workspace.cursorPos.x > kwinClient.clientGeometry.right) {
|
||||
if (Workspace.cursorPos.x > rectRightRound(kwinClient.clientGeometry)) {
|
||||
return column.grid.getRightColumn(column);
|
||||
} else if (Workspace.cursorPos.x < kwinClient.clientGeometry.left) {
|
||||
} else if (Workspace.cursorPos.x < kwinClient.clientGeometry.x.round()) {
|
||||
return column.grid.getLeftColumn(column);
|
||||
} else {
|
||||
return null;
|
||||
@@ -209,7 +214,8 @@ namespace ClientState {
|
||||
}
|
||||
|
||||
const newColumn = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
||||
window.moveToColumn(newColumn, true);
|
||||
const passFocus = window.isFocused() ? FocusPassing.Type.OnUnfocus : FocusPassing.Type.None;
|
||||
window.moveToColumn(newColumn, true, passFocus);
|
||||
}
|
||||
|
||||
private static prepareClientForTiling(client: ClientWrapper, config: LayoutConfig) {
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ClientState {
|
||||
this.signalManager = TiledMinimized.initSignalManager(world, client);
|
||||
}
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
public destroy(passFocus: FocusPassing.Type) {
|
||||
this.signalManager.destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tests.register("Center focused", 1, () => {
|
||||
tests.register("Center focused", 5, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
@@ -14,8 +14,8 @@ tests.register("Center focused", 1, () => {
|
||||
// center client2
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.centered(config, tilingArea, client2);
|
||||
Assert.fullyVisible(client1.frameGeometry);
|
||||
Assert.fullyVisible(client2.frameGeometry);
|
||||
Assert.fullyVisible(client1.getActualFrameGeometry());
|
||||
Assert.fullyVisible(client2.getActualFrameGeometry());
|
||||
|
||||
// undo center client2
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
@@ -24,14 +24,14 @@ tests.register("Center focused", 1, () => {
|
||||
// center client2
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.centered(config, tilingArea, client2);
|
||||
Assert.fullyVisible(client1.frameGeometry);
|
||||
Assert.fullyVisible(client2.frameGeometry);
|
||||
Assert.fullyVisible(client1.getActualFrameGeometry());
|
||||
Assert.fullyVisible(client2.getActualFrameGeometry());
|
||||
|
||||
// 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);
|
||||
Assert.fullyVisible(client1.getActualFrameGeometry());
|
||||
Assert.fullyVisible(client2.getActualFrameGeometry());
|
||||
|
||||
// center client1
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tests.register("columns squeeze side", 1, () => {
|
||||
tests.register("columns squeeze side", 5, () => {
|
||||
const baseTestCases = [
|
||||
{ widths: [500, 500], blocked: [false, false], possible: true },
|
||||
{ widths: [500, 768], blocked: [false, false], possible: true },
|
||||
@@ -46,21 +46,21 @@ tests.register("columns squeeze side", 1, () => {
|
||||
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);
|
||||
Assert.equal(clients[i].getActualFrameGeometry().width, testCase.widths[i], assertOpt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const frames = clients.map(client => client.frameGeometry);
|
||||
const frames = clients.map(client => client.getActualFrameGeometry());
|
||||
qtMock.fireShortcut(testCase.action);
|
||||
const newFrames = clients.map(client => client.frameGeometry);
|
||||
const newFrames = clients.map(client => client.getActualFrameGeometry());
|
||||
for (let i = 0; i < clients.length; i++) {
|
||||
Assert.equalRects(frames[i], newFrames[i], assertOpt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tests.register("columns squeeze side (just scroll)", 1, () => {
|
||||
tests.register("columns squeeze side (just scroll)", 5, () => {
|
||||
const baseTestCases = [
|
||||
{ focus: 0, startVisible: [true, true, false], endVisible: [true, true, false] },
|
||||
{ focus: 1, startVisible: [false, true, true], endVisible: [true, true, false] },
|
||||
@@ -91,12 +91,12 @@ tests.register("columns squeeze side (just scroll)", 1, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
function assertVisible(clients: KwinClient[], visible: boolean[]) {
|
||||
function assertVisible(clients: MockKwinClient[], visible: boolean[]) {
|
||||
for (let i = 0; i < clients.length; i++) {
|
||||
if (visible[i]) {
|
||||
Assert.fullyVisible(clients[i].frameGeometry, { message: assertMsg, skip: 1 });
|
||||
Assert.fullyVisible(clients[i].getActualFrameGeometry(), { message: assertMsg, skip: 1 });
|
||||
} else {
|
||||
Assert.notFullyVisible(clients[i].frameGeometry, { message: assertMsg, skip: 1 });
|
||||
Assert.notFullyVisible(clients[i].getActualFrameGeometry(), { message: assertMsg, skip: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,9 +114,9 @@ tests.register("columns squeeze side (just scroll)", 1, () => {
|
||||
qtMock.fireShortcut(testCase.action);
|
||||
assertVisible(clients, testCase.endVisible);
|
||||
|
||||
const frames = clients.map(client => client.frameGeometry);
|
||||
const frames = clients.map(client => client.getActualFrameGeometry());
|
||||
qtMock.fireShortcut(testCase.action);
|
||||
const newFrames = clients.map(client => client.frameGeometry);
|
||||
const newFrames = clients.map(client => client.getActualFrameGeometry());
|
||||
for (let i = 0; i < clients.length; i++) {
|
||||
Assert.equalRects(frames[i], newFrames[i], { message: assertMsg });
|
||||
}
|
||||
|
||||
@@ -5,32 +5,78 @@ tests.register("Drag tiled window, untile", 10, () => {
|
||||
|
||||
const [client1, client2] = workspaceMock.createClients(2);
|
||||
const initialCursorPos = new MockQmlPoint(380, 20);
|
||||
Assert.assert(rectContainsPoint(client1.frameGeometry, initialCursorPos), { message: "invalid test setup" });
|
||||
Assert.assert(rectContainsPoint(client1.getActualFrameGeometry(), initialCursorPos), { message: "invalid test setup" });
|
||||
workspaceMock.cursorPos = initialCursorPos.clone();
|
||||
|
||||
runOneOf(
|
||||
() => Workspace.activeWindow = client1,
|
||||
() => qtMock.fireShortcut("karousel-focus-1"),
|
||||
() => { Workspace.activeWindow = client1; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
||||
);
|
||||
Assert.assert(rectContainsPoint(client1.frameGeometry, Workspace.cursorPos));
|
||||
Assert.assert(!rectContainsPoint(client2.frameGeometry, Workspace.cursorPos));
|
||||
Assert.assert(rectContainsPoint(client1.getActualFrameGeometry(), Workspace.cursorPos));
|
||||
Assert.assert(!rectContainsPoint(client2.getActualFrameGeometry(), 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"),
|
||||
() => { Workspace.activeWindow = client2; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-2"); },
|
||||
);
|
||||
Assert.assert(!rectContainsPoint(client1.frameGeometry, Workspace.cursorPos));
|
||||
Assert.assert(rectContainsPoint(client2.frameGeometry, Workspace.cursorPos));
|
||||
Assert.assert(!rectContainsPoint(client1.getActualFrameGeometry(), Workspace.cursorPos));
|
||||
Assert.assert(rectContainsPoint(client2.getActualFrameGeometry(), Workspace.cursorPos));
|
||||
|
||||
runOneOf(
|
||||
() => Workspace.activeWindow = client1,
|
||||
() => qtMock.fireShortcut("karousel-focus-1"),
|
||||
() => { Workspace.activeWindow = client1; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-1"); },
|
||||
);
|
||||
Assert.assert(rectContainsPoint(client1.frameGeometry, Workspace.cursorPos));
|
||||
Assert.assert(!rectContainsPoint(client2.frameGeometry, Workspace.cursorPos));
|
||||
Assert.assert(rectContainsPoint(client1.getActualFrameGeometry(), Workspace.cursorPos));
|
||||
Assert.assert(!rectContainsPoint(client2.getActualFrameGeometry(), Workspace.cursorPos));
|
||||
const lastCursorPos = workspaceMock.cursorPos.clone();
|
||||
|
||||
Workspace.activeWindow = null;
|
||||
Assert.assert(pointEquals(Workspace.cursorPos, lastCursorPos), { message: "Cursor should not have been moved" });
|
||||
});
|
||||
|
||||
tests.register("Cursor follows focus only on matched desktops", 1, () => {
|
||||
// Test that cursor follow focus only works for windows on matched desktops (tiled windows)
|
||||
const config = getDefaultConfig();
|
||||
config.cursorFollowsFocus = true;
|
||||
config.tiledDesktops = "^Desktop 1$"; // Only work on Desktop 1
|
||||
const { workspaceMock, world } = init(config);
|
||||
|
||||
// Create a client on Desktop 1 (matched desktop) - should be tiled
|
||||
const client1 = new MockKwinClient();
|
||||
client1.desktops = [workspaceMock.desktops[0]]; // Desktop 1
|
||||
workspaceMock.createWindows(client1);
|
||||
|
||||
// Create a client on Desktop 2 (non-matched desktop) - should be floating
|
||||
const client2 = new MockKwinClient();
|
||||
client2.desktops = [workspaceMock.desktops[1]]; // Desktop 2
|
||||
workspaceMock.createWindows(client2);
|
||||
|
||||
// Set initial cursor position outside both windows
|
||||
const initialCursorPos = new MockQmlPoint(10, 10);
|
||||
workspaceMock.cursorPos = initialCursorPos.clone();
|
||||
|
||||
// Test 1: Focus client1 on matched desktop (Desktop 1) - cursor should move
|
||||
workspaceMock.currentDesktop = workspaceMock.desktops[0]; // Switch to Desktop 1
|
||||
Workspace.activeWindow = client1;
|
||||
world.do(() => {});
|
||||
Assert.assert(rectContainsPoint(client1.getActualFrameGeometry(), Workspace.cursorPos),
|
||||
{ message: "Cursor should have moved to tiled window on matched desktop" });
|
||||
|
||||
// Test 2: Switch to non-matched desktop (Desktop 2) and focus client2 - cursor should NOT move
|
||||
workspaceMock.cursorPos = initialCursorPos.clone();
|
||||
workspaceMock.currentDesktop = workspaceMock.desktops[1]; // Switch to Desktop 2
|
||||
Workspace.activeWindow = client2;
|
||||
world.do(() => {});
|
||||
Assert.assert(pointEquals(Workspace.cursorPos, initialCursorPos),
|
||||
{ message: "Cursor should NOT move on non-matched desktop" });
|
||||
|
||||
// Test 3: Even if we focus client1 (tiled) while on Desktop 2, cursor should NOT move
|
||||
// because the current desktop is not matched
|
||||
workspaceMock.cursorPos = initialCursorPos.clone();
|
||||
workspaceMock.currentDesktop = workspaceMock.desktops[1]; // Stay on Desktop 2
|
||||
Workspace.activeWindow = client1;
|
||||
world.do(() => {});
|
||||
Assert.assert(pointEquals(Workspace.cursorPos, initialCursorPos),
|
||||
{ message: "Cursor should NOT move even for tiled window when current desktop is not matched" });
|
||||
});
|
||||
|
||||
73
src/tests/flows/desktopFiltering.ts
Normal file
73
src/tests/flows/desktopFiltering.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
tests.register("Desktop filtering", 1, () => {
|
||||
// Test 1: Default config should work on all desktops
|
||||
const config1 = getDefaultConfig();
|
||||
const { workspaceMock: wm1, world: world1 } = init(config1);
|
||||
|
||||
const client1 = new MockKwinClient();
|
||||
client1.desktops = [wm1.desktops[0]];
|
||||
wm1.createWindows(client1);
|
||||
|
||||
world1.do((clientManager) => {
|
||||
Assert.tiledClient(clientManager, client1, { message: "Client should be tiled on desktop1 with default config (*)" });
|
||||
});
|
||||
});
|
||||
|
||||
tests.register("Desktop filtering - specific desktop", 1, () => {
|
||||
// Test 2: Specific desktop name - should work only on matching desktop
|
||||
const config2 = getDefaultConfig();
|
||||
config2.tiledDesktops = "^Desktop 1$";
|
||||
const { workspaceMock: wm2, world: world2 } = init(config2);
|
||||
|
||||
const client1 = new MockKwinClient();
|
||||
client1.desktops = [wm2.desktops[0]]; // Desktop 1
|
||||
wm2.createWindows(client1);
|
||||
world2.do((clientManager) => {
|
||||
Assert.tiledClient(clientManager, client1, { message: "Client should be tiled on Desktop 1" });
|
||||
});
|
||||
|
||||
wm2.removeWindow(client1);
|
||||
|
||||
const client2 = new MockKwinClient();
|
||||
client2.desktops = [wm2.desktops[1]]; // Desktop 2
|
||||
wm2.createWindows(client2);
|
||||
world2.do((clientManager) => {
|
||||
Assert.notTiledClient(clientManager, client2, { message: "Client should NOT be tiled on Desktop 2" });
|
||||
});
|
||||
});
|
||||
|
||||
tests.register("Desktop filtering - multiple desktops", 1, () => {
|
||||
// Test 3: Multiple desktop names using regex alternation
|
||||
const config3 = getDefaultConfig();
|
||||
config3.tiledDesktops = "^Desktop [12]$";
|
||||
const { workspaceMock: wm3, world: world3 } = init(config3);
|
||||
|
||||
const client1 = new MockKwinClient();
|
||||
client1.desktops = [wm3.desktops[0]]; // Desktop 1
|
||||
wm3.createWindows(client1);
|
||||
world3.do((clientManager) => {
|
||||
Assert.tiledClient(clientManager, client1, { message: "Client should be tiled on Desktop 1" });
|
||||
});
|
||||
|
||||
wm3.removeWindow(client1);
|
||||
|
||||
const client2 = new MockKwinClient();
|
||||
client2.desktops = [wm3.desktops[1]]; // Desktop 2
|
||||
wm3.createWindows(client2);
|
||||
world3.do((clientManager) => {
|
||||
Assert.tiledClient(clientManager, client2, { message: "Client should be tiled on Desktop 2" });
|
||||
});
|
||||
});
|
||||
|
||||
tests.register("Desktop filtering - windows on multiple desktops", 1, () => {
|
||||
// Test 4: Windows on multiple desktops should not be tiled (fallback to floating)
|
||||
const config4 = getDefaultConfig();
|
||||
config4.tiledDesktops = ".*";
|
||||
const { workspaceMock: wm4, world: world4 } = init(config4);
|
||||
|
||||
const client1 = new MockKwinClient();
|
||||
client1.desktops = [wm4.desktops[0], wm4.desktops[1]]; // Multiple desktops
|
||||
wm4.createWindows(client1);
|
||||
world4.do((clientManager) => {
|
||||
Assert.notTiledClient(clientManager, client1, { message: "Client on multiple desktops should not be tiled" });
|
||||
});
|
||||
});
|
||||
@@ -8,31 +8,31 @@ tests.register("External resize", 1, () => {
|
||||
|
||||
function getTiledFrame(width: number) {
|
||||
return new MockQmlRect(
|
||||
tilingArea.left + Math.round((tilingArea.width - width) / 2),
|
||||
tilingArea.top,
|
||||
tilingArea.x + Math.round((tilingArea.width - width) / 2),
|
||||
tilingArea.y,
|
||||
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" });
|
||||
Assert.equalRects(client.getActualFrameGeometry(), 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" });
|
||||
Assert.equalRects(client.getActualFrameGeometry(), 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" });
|
||||
Assert.equalRects(client.getActualFrameGeometry(), 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" });
|
||||
Assert.equalRects(client.getActualFrameGeometry(), 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" });
|
||||
Assert.equalRects(client.getActualFrameGeometry(), 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" });
|
||||
Assert.equalRects(client.getActualFrameGeometry(), getClientDesiredFrame(200), { message: "We should give up and let the client have its desired frame" });
|
||||
}
|
||||
|
||||
timeControl(addTime => {
|
||||
|
||||
10
src/tests/flows/followWindowToDesktop.ts
Normal file
10
src/tests/flows/followWindowToDesktop.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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);
|
||||
});
|
||||
@@ -10,7 +10,7 @@ tests.register("Focus and move windows", 1, () => {
|
||||
});
|
||||
Assert.assert(workspaceMock.activeWindow === client3);
|
||||
|
||||
function testLayout(shortcutName: string, grid: KwinClient[][]) {
|
||||
function testLayout(shortcutName: string, grid: MockKwinClient[][]) {
|
||||
qtMock.fireShortcut(shortcutName);
|
||||
Assert.grid(config, tilingArea, 100, grid, true, [], { skip: 1 });
|
||||
}
|
||||
|
||||
@@ -13,35 +13,35 @@ tests.register("LazyScroller", 20, () => {
|
||||
|
||||
const [client3] = workspaceMock.createClientsWithWidths(300);
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
||||
Assert.equal(client3.frameGeometry.right, tilingArea.right);
|
||||
Assert.equal(rectRight(client3.getActualFrameGeometry()), rectRight(tilingArea));
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = client2,
|
||||
() => qtMock.fireShortcut("karousel-focus-2"),
|
||||
() => qtMock.fireShortcut("karousel-focus-left"),
|
||||
() => { 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);
|
||||
Assert.equal(rectRight(client3.getActualFrameGeometry()), rectRight(tilingArea));
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = client1,
|
||||
() => qtMock.fireShortcut("karousel-focus-1"),
|
||||
() => qtMock.fireShortcut("karousel-focus-left"),
|
||||
() => qtMock.fireShortcut("karousel-focus-start"),
|
||||
() => { 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);
|
||||
Assert.equal(client1.getActualFrameGeometry().x, tilingArea.x);
|
||||
|
||||
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"),
|
||||
() => { 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);
|
||||
Assert.equal(client1.getActualFrameGeometry().x, tilingArea.x);
|
||||
});
|
||||
|
||||
@@ -14,43 +14,43 @@
|
||||
Assert.assert(clientManager.hasClient(kwinClient));
|
||||
});
|
||||
|
||||
const columnLeftX = tilingArea.left + tilingArea.width/2 - 300/2;
|
||||
const columnTopY = tilingArea.top;
|
||||
const columnLeftX = tilingArea.x + tilingArea.width/2 - 300/2;
|
||||
const columnTopY = tilingArea.y;
|
||||
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);
|
||||
Assert.rect(kwinClient.getActualFrameGeometry(), 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);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), 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);
|
||||
Assert.rect(kwinClient.getActualFrameGeometry(), 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);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), 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);
|
||||
Assert.rect(kwinClient.getActualFrameGeometry(), 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);
|
||||
Assert.rect(kwinClient.getActualFrameGeometry(), columnLeftX, columnTopY, 300, columnHeight);
|
||||
});
|
||||
|
||||
tests.register("Maximize with transient " + suffix, 100, () => {
|
||||
@@ -66,12 +66,12 @@
|
||||
});
|
||||
|
||||
runOneOf(
|
||||
() => parent.fullScreen = true,
|
||||
() => parent.setMaximize(true, true),
|
||||
() => { parent.fullScreen = true; },
|
||||
() => { parent.setMaximize(true, true); },
|
||||
);
|
||||
Assert.equal(parent.keepBelow, shouldKeepBelow(false));
|
||||
Assert.equal(parent.keepAbove, shouldKeepAbove(false));
|
||||
Assert.equalRects(parent.frameGeometry, screen);
|
||||
Assert.equalRects(parent.getActualFrameGeometry(), screen);
|
||||
|
||||
workspaceMock.createWindows(child);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
@@ -80,14 +80,14 @@
|
||||
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.rect(child.getActualFrameGeometry(), 14, 24, 50, 50);
|
||||
Assert.equal(parent.keepBelow, shouldKeepBelow(false));
|
||||
Assert.equal(parent.keepAbove, shouldKeepAbove(false));
|
||||
Assert.equalRects(parent.frameGeometry, screen);
|
||||
Assert.equalRects(parent.getActualFrameGeometry(), screen);
|
||||
});
|
||||
|
||||
{
|
||||
function assertWindowed(config: Config, clients: KwinClient[]) {
|
||||
function assertWindowed(config: Config, clients: MockKwinClient[]) {
|
||||
Assert.assert(!clients[0].fullScreen);
|
||||
Assert.equal(clients[0].keepBelow, shouldKeepBelow(true));
|
||||
Assert.equal(clients[0].keepAbove, shouldKeepAbove(true));
|
||||
@@ -100,7 +100,7 @@
|
||||
Assert.grid(config, tilingArea, [300, 400], [[clients[0]], [clients[1], clients[2]]], true);
|
||||
}
|
||||
|
||||
function assertFullScreenOrMaximized(clients: KwinClient[]) {
|
||||
function assertFullScreenOrMaximized(clients: MockKwinClient[]) {
|
||||
Assert.assert(!clients[0].fullScreen);
|
||||
Assert.equal(clients[0].keepBelow, shouldKeepBelow(true));
|
||||
Assert.equal(clients[0].keepAbove, shouldKeepAbove(true));
|
||||
@@ -109,7 +109,7 @@
|
||||
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);
|
||||
Assert.equalRects(clients[2].getActualFrameGeometry(), screen);
|
||||
}
|
||||
|
||||
tests.register("Re-maximize disabled " + suffix, 100, () => {
|
||||
@@ -123,42 +123,42 @@
|
||||
assertWindowed(config, clients);
|
||||
|
||||
runOneOf(
|
||||
() => clients[2].fullScreen = true,
|
||||
() => clients[2].setMaximize(true, true),
|
||||
() => { 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"),
|
||||
() => { 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"),
|
||||
() => { 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),
|
||||
() => { clients[2].fullScreen = true; },
|
||||
() => { clients[2].setMaximize(true, true); },
|
||||
);
|
||||
assertFullScreenOrMaximized(clients);
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = clients[1],
|
||||
() => qtMock.fireShortcut("karousel-focus-up"),
|
||||
() => { workspaceMock.activeWindow = clients[1]; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-up"); },
|
||||
);
|
||||
assertWindowed(config, clients);
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = clients[2],
|
||||
() => qtMock.fireShortcut("karousel-focus-down"),
|
||||
() => { workspaceMock.activeWindow = clients[2]; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-down"); },
|
||||
);
|
||||
assertWindowed(config, clients);
|
||||
});
|
||||
@@ -174,36 +174,36 @@
|
||||
assertWindowed(config, clients);
|
||||
|
||||
runOneOf(
|
||||
() => clients[2].fullScreen = true,
|
||||
() => clients[2].setMaximize(true, true),
|
||||
() => { 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"),
|
||||
() => { 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"),
|
||||
() => { 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"),
|
||||
() => { workspaceMock.activeWindow = clients[1]; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-up"); },
|
||||
);
|
||||
assertWindowed(config, clients);
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = clients[2],
|
||||
() => qtMock.fireShortcut("karousel-focus-down"),
|
||||
() => { workspaceMock.activeWindow = clients[2]; },
|
||||
() => { qtMock.fireShortcut("karousel-focus-down"); },
|
||||
);
|
||||
assertFullScreenOrMaximized(clients);
|
||||
});
|
||||
@@ -232,7 +232,7 @@
|
||||
Assert.assert(fullScreenClient.fullScreen);
|
||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
||||
Assert.equalRects(fullScreenClient.getActualFrameGeometry(), screen);
|
||||
Assert.equal(Workspace.activeWindow, fullScreenClient);
|
||||
|
||||
{
|
||||
@@ -245,7 +245,7 @@
|
||||
Assert.assert(fullScreenClient.fullScreen);
|
||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
||||
Assert.equalRects(fullScreenClient.getActualFrameGeometry(), screen);
|
||||
Assert.equal(Workspace.activeWindow, fullScreenClient, opts);
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
Assert.assert(fullScreenClient.fullScreen);
|
||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
||||
Assert.equalRects(fullScreenClient.getActualFrameGeometry(), screen);
|
||||
Assert.equal(Workspace.activeWindow, windowedClient);
|
||||
}
|
||||
});
|
||||
@@ -288,7 +288,7 @@
|
||||
Assert.assert(fullScreenClient.fullScreen);
|
||||
Assert.equal(fullScreenClient.keepBelow, shouldKeepBelow(false));
|
||||
Assert.equal(fullScreenClient.keepAbove, shouldKeepAbove(false));
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
||||
Assert.equalRects(fullScreenClient.getActualFrameGeometry(), screen);
|
||||
Assert.equal(Workspace.activeWindow, fullScreenClient);
|
||||
|
||||
let expectedColumn2Width = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tests.register("Pass focus", 20, () => {
|
||||
tests.register("Pass focus", 100, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
@@ -34,7 +34,4 @@ tests.register("Pass focus", 20, () => {
|
||||
|
||||
removeWindow(client0);
|
||||
Assert.equal(workspaceMock.activeWindow, client6);
|
||||
|
||||
removeWindow(client6);
|
||||
Assert.equal(workspaceMock.activeWindow, null);
|
||||
});
|
||||
|
||||
@@ -22,26 +22,26 @@ tests.register("Pin", 20, () => {
|
||||
Assert.grid(config, tilingArea, 100, [ [pinned], [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.pin(screenHalfLeft);
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfLeft);
|
||||
Assert.equalRects(pinned.getActualFrameGeometry(), screenHalfLeft);
|
||||
Assert.grid(config, tilingAreaHalfRight, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.pin(screenHalfRight);
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.equalRects(pinned.getActualFrameGeometry(), screenHalfRight);
|
||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.unpin();
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.equalRects(pinned.getActualFrameGeometry(), screenHalfRight);
|
||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.pin(screenHalfRight);
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.equalRects(pinned.getActualFrameGeometry(), 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.equalRects(pinned.getActualFrameGeometry(), screenHalfRight);
|
||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
workspaceMock.activeWindow = pinned;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tests.register("Preset Widths default", 1, () => {
|
||||
tests.register("Preset Widths default", 5, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
@@ -7,33 +7,48 @@ tests.register("Preset Widths default", 1, () => {
|
||||
|
||||
function getRect(columnWidth: number) {
|
||||
return new MockQmlRect(
|
||||
tilingArea.left + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.top,
|
||||
tilingArea.x + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.y,
|
||||
columnWidth,
|
||||
tilingArea.height,
|
||||
);
|
||||
}
|
||||
|
||||
const [kwinClient] = workspaceMock.createClientsWithWidths(300);
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(300));
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(300));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(halfWidth));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(maxWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(maxWidth));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(halfWidth));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(maxWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(maxWidth));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(halfWidth));
|
||||
});
|
||||
|
||||
tests.register("Preset Widths custom", 1, () => {
|
||||
tests.register("Preset Widths custom", 5, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.presetWidths = "500px, 250px, 100px, 50%";
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
@@ -43,39 +58,122 @@ tests.register("Preset Widths custom", 1, () => {
|
||||
|
||||
function getRect(columnWidth: number) {
|
||||
return new MockQmlRect(
|
||||
tilingArea.left + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.top,
|
||||
tilingArea.x + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.y,
|
||||
columnWidth,
|
||||
tilingArea.height,
|
||||
);
|
||||
}
|
||||
|
||||
const [kwinClient] = workspaceMock.createClientsWithWidths(200);
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(200));
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(200));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(250));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(halfWidth));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(500));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(250));
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(100));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(250));
|
||||
|
||||
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));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths-reverse"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(100));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(100));
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(500));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths-reverse"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(halfWidth));
|
||||
});
|
||||
|
||||
tests.register("Preset Widths custom percentages", 5, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.presetWidths = "25%, 50%, 75%, 100%";
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const width100 = tilingArea.width;
|
||||
const width75 = width100*0.75 - config.gapsInnerHorizontal*0.25;
|
||||
const width50 = width100*0.50 - config.gapsInnerHorizontal*0.50;
|
||||
const width25 = width100*0.25 - config.gapsInnerHorizontal*0.75;
|
||||
|
||||
function getRect(columnWidth: number) {
|
||||
return new MockQmlRect(
|
||||
tilingArea.x + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.y,
|
||||
columnWidth,
|
||||
tilingArea.height,
|
||||
);
|
||||
}
|
||||
|
||||
const [kwinClient] = workspaceMock.createClientsWithWidths(200);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(200));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width50));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width75));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-increase"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width100));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width25));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(500));
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width100));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths-reverse"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width75));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths-reverse"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width50));
|
||||
|
||||
runOneOf(
|
||||
() => qtMock.fireShortcut("karousel-cycle-preset-widths-reverse"),
|
||||
() => qtMock.fireShortcut("karousel-column-width-decrease"),
|
||||
);
|
||||
Assert.equalRects(kwinClient.getActualFrameGeometry(), getRect(width25));
|
||||
});
|
||||
|
||||
tests.register("Preset Widths fill screen uniform", 1, () => {
|
||||
@@ -96,12 +194,12 @@ tests.register("Preset Widths fill screen uniform", 1, () => {
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
}
|
||||
|
||||
const left = tilingArea.left;
|
||||
const right = tilingArea.right;
|
||||
const left = tilingArea.x;
|
||||
const right = rectRight(tilingArea);
|
||||
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}` });
|
||||
Assert.between(firstClient!.getActualFrameGeometry().x, left, left+eps, { message: `nColumns: ${nColumns}` });
|
||||
Assert.between(rectRight(lastClient!.getActualFrameGeometry()), right-eps, right, { message: `nColumns: ${nColumns}` });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -123,12 +221,12 @@ tests.register("Preset Widths fill screen non-uniform", 1, () => {
|
||||
const halfWidth = maxWidth/2 - config.gapsInnerHorizontal/2;
|
||||
const quarterWidth = halfWidth/2 - config.gapsInnerHorizontal/2;
|
||||
const height = tilingArea.height;
|
||||
const left1 = tilingArea.left;
|
||||
const left1 = tilingArea.x;
|
||||
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);
|
||||
Assert.rect(clientThin1.getActualFrameGeometry(), left1, tilingArea.y, quarterWidth, height);
|
||||
Assert.rect(clientThin2.getActualFrameGeometry(), left2, tilingArea.y, quarterWidth, height);
|
||||
Assert.rect(clientWide.getActualFrameGeometry(), left3, tilingArea.y, halfWidth, height);
|
||||
Assert.equal(rectRight(clientWide.getActualFrameGeometry()), rectRight(tilingArea));
|
||||
});
|
||||
|
||||
@@ -6,9 +6,9 @@ tests.register("User resize", 10, () => {
|
||||
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);
|
||||
Assert.rect(clientLeft.getActualFrameGeometry(), left, tilingArea.y, leftWidth, tilingArea.height);
|
||||
Assert.rect(clientRightTop.getActualFrameGeometry(), left+leftWidth+gapH, tilingArea.y, rightWidth, topHeight);
|
||||
Assert.rect(clientRightBottom.getActualFrameGeometry(), left+leftWidth+gapH, tilingArea.y+topHeight+gapV, rightWidth, bottomHeight);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -89,9 +89,9 @@ tests.register("User resize", 10, () => {
|
||||
|
||||
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);
|
||||
Assert.rect(clientLeftTop.getActualFrameGeometry(), left, tilingArea.y, leftWidth, topHeight);
|
||||
Assert.rect(clientLeftBottom.getActualFrameGeometry(), left, tilingArea.y+topHeight+gapV, leftWidth, bottomHeight);
|
||||
Assert.rect(clientRight.getActualFrameGeometry(), left+leftWidth+gapH, tilingArea.y, rightWidth, tilingArea.height);
|
||||
}
|
||||
|
||||
workspaceMock.activeWindow = clientLeftBottom;
|
||||
|
||||
41
src/tests/units/rules/DesktopFilter.ts
Normal file
41
src/tests/units/rules/DesktopFilter.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
tests.register("DesktopFilter", 1, () => {
|
||||
const desktop1 = { __brand: "KwinDesktop" as const, id: "1", name: "Desktop 1" };
|
||||
const desktop2 = { __brand: "KwinDesktop" as const, id: "2", name: "Work" };
|
||||
const desktop3 = { __brand: "KwinDesktop" as const, id: "3", name: "Desktop 2" };
|
||||
|
||||
// Test 1: Empty config means all desktops
|
||||
let filter = new DesktopFilter("");
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Empty config should work on desktop1" });
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Empty config should work on desktop2" });
|
||||
|
||||
// Test 2: Whitespace only means all desktops
|
||||
filter = new DesktopFilter(" \n \n ");
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Whitespace only should work on desktop1" });
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Whitespace only should work on desktop2" });
|
||||
|
||||
// Test 3: Match all regex pattern
|
||||
filter = new DesktopFilter(".*");
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Regex '.*' should work on desktop1" });
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Regex '.*' should work on desktop2" });
|
||||
|
||||
// Test 4: Partial match without anchors
|
||||
filter = new DesktopFilter("Work");
|
||||
Assert.assert(!filter.shouldWorkOnDesktop(desktop1), { message: "Should not work on desktop1" });
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Should work on desktop2 containing 'Work'" });
|
||||
|
||||
// Test 5: Regex alternation for multiple desktops
|
||||
filter = new DesktopFilter("Desktop 1|Work");
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Should work on desktop1" });
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Should work on desktop2" });
|
||||
Assert.assert(!filter.shouldWorkOnDesktop(desktop3), { message: "Should not work on desktop3" });
|
||||
|
||||
// Test 6: Regex pattern with character class
|
||||
filter = new DesktopFilter("Desktop [12]");
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Should work on desktop1" });
|
||||
Assert.assert(!filter.shouldWorkOnDesktop(desktop2), { message: "Should not work on desktop2" });
|
||||
Assert.assert(filter.shouldWorkOnDesktop(desktop3), { message: "Should work on desktop3" });
|
||||
|
||||
// Test 7: Case-sensitive matching
|
||||
filter = new DesktopFilter("work");
|
||||
Assert.assert(!filter.shouldWorkOnDesktop(desktop2), { message: "Should not work on desktop2 (case mismatch)" });
|
||||
});
|
||||
@@ -127,7 +127,7 @@ namespace Assert {
|
||||
config: Config,
|
||||
tilingArea: QmlRect,
|
||||
columnWidths: number[] | number,
|
||||
grid: KwinClient[][],
|
||||
grid: MockKwinClient[][],
|
||||
centered: boolean,
|
||||
stackedColumns: number[] = [],
|
||||
{ message, skip=0 }: Options = {},
|
||||
@@ -161,7 +161,7 @@ namespace Assert {
|
||||
const gridWidth = getGridWidth();
|
||||
const startX = centered ?
|
||||
tilingArea.x + (tilingArea.width - gridWidth) / 2 :
|
||||
grid[0][0].frameGeometry.x;
|
||||
grid[0][0].getActualFrameGeometry().x;
|
||||
|
||||
function getColumnX(column: number) {
|
||||
if (columnWidths instanceof Array) {
|
||||
@@ -205,7 +205,7 @@ namespace Assert {
|
||||
for (let iWindow = 0; iWindow < nWindows; iWindow++) {
|
||||
const window = column[iWindow];
|
||||
equalRects(
|
||||
window.frameGeometry,
|
||||
window.getActualFrameGeometry(),
|
||||
getRect(iColumn, iWindow, nColumns, nWindows),
|
||||
{ message: appendMessage(`column ${iColumn}, window ${iWindow}`, message), skip: skip+1 },
|
||||
);
|
||||
@@ -216,13 +216,13 @@ namespace Assert {
|
||||
export function centered(
|
||||
config: Config,
|
||||
tilingArea: QmlRect,
|
||||
client:KwinClient,
|
||||
client:MockKwinClient,
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
grid(
|
||||
config,
|
||||
tilingArea,
|
||||
client.frameGeometry.width,
|
||||
client.getActualFrameGeometry().width,
|
||||
[[client]],
|
||||
true,
|
||||
[],
|
||||
@@ -235,7 +235,7 @@ namespace Assert {
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
assert(
|
||||
rect.left >= tilingArea.left && rect.right <= tilingArea.right,
|
||||
rect.x >= tilingArea.x && rectRight(rect) <= rectRight(tilingArea),
|
||||
{
|
||||
message: appendMessage(`Rect ${rect} not fully visible`, message),
|
||||
skip: skip + 1,
|
||||
@@ -248,7 +248,7 @@ namespace Assert {
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
assert(
|
||||
rect.left < tilingArea.left || rect.right > tilingArea.right,
|
||||
rect.x < tilingArea.x || rectRight(rect) > rectRight(tilingArea),
|
||||
{
|
||||
message: appendMessage(`Rect ${rect} is fully visible, but shouldn't be`, message),
|
||||
skip: skip + 1,
|
||||
@@ -257,18 +257,18 @@ namespace Assert {
|
||||
}
|
||||
|
||||
export function columnsFillTilingArea(
|
||||
columns: KwinClient[],
|
||||
columns: MockKwinClient[],
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
const options = { message: message, skip: skip+1 };
|
||||
let x = tilingArea.left;
|
||||
let x = tilingArea.x;
|
||||
for (const column of columns) {
|
||||
const width = column.frameGeometry.width;
|
||||
fullyVisible(column.frameGeometry, options);
|
||||
rect(column.frameGeometry, x, tilingArea.top, width, tilingArea.height, options);
|
||||
const width = column.getActualFrameGeometry().width;
|
||||
fullyVisible(column.getActualFrameGeometry(), options);
|
||||
rect(column.getActualFrameGeometry(), x, tilingArea.y, width, tilingArea.height, options);
|
||||
x += width + gapH;
|
||||
}
|
||||
equal(columns[columns.length-1].frameGeometry.right, tilingArea.right, options);
|
||||
equal(rectRight(columns[columns.length-1].getActualFrameGeometry()), rectRight(tilingArea), options);
|
||||
}
|
||||
|
||||
export function tiledClient(
|
||||
|
||||
@@ -2,6 +2,7 @@ let Qt: Qt;
|
||||
let KWin: KWin;
|
||||
let Workspace: Workspace;
|
||||
let qmlBase: QmlObject;
|
||||
let notificationInvalidTiledDesktops: Notification;
|
||||
let notificationInvalidWindowRules: Notification;
|
||||
let notificationInvalidPresetWidths: Notification;
|
||||
let moveCursorToFocus: DBusCall;
|
||||
@@ -33,7 +34,7 @@ function init(config: Config) {
|
||||
__brand: "QmlObject",
|
||||
call: () => {
|
||||
Assert.assert(Workspace.activeWindow !== null, { message: "moveCursorToFocus should never be called if there's no focused window" });
|
||||
const frame = Workspace.activeWindow!.frameGeometry;
|
||||
const frame = (Workspace.activeWindow! as MockKwinClient).getActualFrameGeometry();
|
||||
workspaceMock.cursorPos.x = Math.floor(frame.x + frame.width/2);
|
||||
workspaceMock.cursorPos.y = Math.floor(frame.y + frame.height/2);
|
||||
},
|
||||
@@ -43,9 +44,9 @@ function init(config: Config) {
|
||||
return { qtMock, workspaceMock, world };
|
||||
}
|
||||
|
||||
function getGridBounds(clientLeft: KwinClient, clientRight: KwinClient) {
|
||||
const columnsWidth = clientRight.frameGeometry.right - clientLeft.frameGeometry.left;
|
||||
const left = tilingArea.left + Math.floor((tilingArea.width - columnsWidth) / 2);
|
||||
function getGridBounds(clientLeft: MockKwinClient, clientRight: MockKwinClient) {
|
||||
const columnsWidth = rectRight(clientRight.getActualFrameGeometry()) - clientLeft.getActualFrameGeometry().x;
|
||||
const left = tilingArea.x + Math.floor((tilingArea.width - columnsWidth) / 2);
|
||||
const right = left + columnsWidth;
|
||||
return { left, right };
|
||||
}
|
||||
@@ -61,3 +62,10 @@ function getClientManager(world: World): ClientManager {
|
||||
world.do((cm, dm) => clientManager = cm);
|
||||
return clientManager!;
|
||||
}
|
||||
|
||||
function activateRandomWindowOnDesktop(desktop: KwinDesktop) {
|
||||
const windows = Workspace.windows.filter(w => w.desktops.includes(desktop));
|
||||
if (windows.length > 0) {
|
||||
Workspace.activeWindow = randomItem(windows);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ class MockKwinClient {
|
||||
private static readonly borderThickness = 10;
|
||||
|
||||
public caption = "App";
|
||||
public minSize: Readonly<QmlSize> = new MockQmlSize(0, 0);
|
||||
public minSize: Readonly<QmlSize> = new MockQmlSize(randomJitter(), randomJitter());
|
||||
public readonly transient: boolean;
|
||||
public move = false;
|
||||
public resize = false;
|
||||
@@ -52,6 +52,7 @@ class MockKwinClient {
|
||||
this.windowedFrameGeometry = _frameGeometry.clone();
|
||||
this.transient = transientFor !== null;
|
||||
this._desktops = [Workspace.currentDesktop];
|
||||
this.activities = [Workspace.currentActivity];
|
||||
}
|
||||
|
||||
setMaximize(vertically: boolean, horizontally: boolean) {
|
||||
@@ -68,7 +69,7 @@ class MockKwinClient {
|
||||
horizontally ? MaximizedMode.Maximized : MaximizedMode.Vertically
|
||||
) : (
|
||||
horizontally ? MaximizedMode.Horizontally : MaximizedMode.Unmaximized
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
this.frameGeometry = new MockQmlRect(
|
||||
@@ -88,7 +89,15 @@ class MockKwinClient {
|
||||
this.frameGeometry.height - 2 * MockKwinClient.borderThickness,
|
||||
);
|
||||
} else {
|
||||
return this.frameGeometry;
|
||||
return runOneOf(
|
||||
() => this.frameGeometry,
|
||||
() => new MockQmlRect(
|
||||
this.frameGeometry.x - 20,
|
||||
this.frameGeometry.y - 20,
|
||||
this.frameGeometry.width + 40,
|
||||
this.frameGeometry.height + 40,
|
||||
), // some full-screen windows that manage their own window decorations can temporarily have a client geometry bigger than the screen
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,18 +133,22 @@ class MockKwinClient {
|
||||
return;
|
||||
}
|
||||
runOneOf(
|
||||
() => this.frameGeometry = new MockQmlRect(
|
||||
0,
|
||||
0,
|
||||
screen.width + 2 * MockKwinClient.borderThickness,
|
||||
screen.height + 2 * MockKwinClient.borderThickness,
|
||||
),
|
||||
() => this.frameGeometry = new MockQmlRect(
|
||||
-MockKwinClient.borderThickness,
|
||||
-MockKwinClient.borderThickness,
|
||||
screen.width + 2 * MockKwinClient.borderThickness,
|
||||
screen.height + 2 * MockKwinClient.borderThickness,
|
||||
),
|
||||
() => {
|
||||
this.frameGeometry = new MockQmlRect(
|
||||
0,
|
||||
0,
|
||||
screen.width + 2 * MockKwinClient.borderThickness,
|
||||
screen.height + 2 * MockKwinClient.borderThickness,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this.frameGeometry = new MockQmlRect(
|
||||
-MockKwinClient.borderThickness,
|
||||
-MockKwinClient.borderThickness,
|
||||
screen.width + 2 * MockKwinClient.borderThickness,
|
||||
screen.height + 2 * MockKwinClient.borderThickness,
|
||||
);
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
@@ -147,10 +160,22 @@ class MockKwinClient {
|
||||
);
|
||||
}
|
||||
|
||||
public get frameGeometry() {
|
||||
// for assertions
|
||||
public getActualFrameGeometry() {
|
||||
return this._frameGeometry;
|
||||
}
|
||||
|
||||
// for Karousel
|
||||
public get frameGeometry() {
|
||||
return new MockQmlRect(
|
||||
this._frameGeometry.x + randomJitter(),
|
||||
this._frameGeometry.y + randomJitter(),
|
||||
this._frameGeometry.width + randomJitter(),
|
||||
this._frameGeometry.height + randomJitter(),
|
||||
this.frameGeometryChanged.fire.bind(this.frameGeometryChanged),
|
||||
);
|
||||
}
|
||||
|
||||
public set frameGeometry(frameGeometry: MockQmlRect) {
|
||||
const oldFrameGeometry = this._frameGeometry;
|
||||
this._frameGeometry = new MockQmlRect(
|
||||
@@ -186,9 +211,20 @@ class MockKwinClient {
|
||||
this.desktopsChanged.fire();
|
||||
if (Workspace.activeWindow === this && !desktops.includes(Workspace.currentDesktop)) {
|
||||
Workspace.activeWindow = null;
|
||||
runMaybe(() => Workspace.activeWindow = null); // fired again for some reason
|
||||
if (Workspace.activeWindow === null) {
|
||||
activateRandomWindowOnDesktop(Workspace.currentDesktop);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public moveAndFollowToDesktop(desktop: KwinDesktop, workspaceMock: MockWorkspace) {
|
||||
Assert.assert(workspaceMock.activeWindow === this);
|
||||
this._desktops = [desktop];
|
||||
this.desktopsChanged.fire();
|
||||
workspaceMock.currentDesktop = desktop;
|
||||
}
|
||||
|
||||
public get tile() {
|
||||
return this._tile;
|
||||
}
|
||||
|
||||
@@ -49,22 +49,6 @@ class MockQmlRect {
|
||||
this.onChanged(oldRect);
|
||||
}
|
||||
|
||||
public get top() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public get bottom() {
|
||||
return this.y + this.height;
|
||||
}
|
||||
|
||||
public get left() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public get right() {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
public set(target: QmlRect) {
|
||||
const oldRect = this.clone();
|
||||
this._x = target.x;
|
||||
|
||||
@@ -3,15 +3,15 @@ class MockWorkspace {
|
||||
|
||||
public activities = ["test-activity"];
|
||||
public desktops: KwinDesktop[] = [
|
||||
{ __brand: "KwinDesktop", id: "desktop1" },
|
||||
{ __brand: "KwinDesktop", id: "desktop2" }
|
||||
{ __brand: "KwinDesktop", id: "desktop1", name: "Desktop 1" },
|
||||
{ __brand: "KwinDesktop", id: "desktop2", name: "Desktop 2" },
|
||||
];
|
||||
public currentDesktop = this.desktops[0];
|
||||
public currentActivity = this.activities[0];
|
||||
public activeScreen: Output = { __brand: "Output" };
|
||||
public readonly windows: MockKwinClient[] = [];
|
||||
public cursorPos = new MockQmlPoint(0, 0);
|
||||
|
||||
private _currentDesktop = this.desktops[0];
|
||||
private _activeWindow: KwinClient|null = null;
|
||||
|
||||
public readonly currentDesktopChanged = new MockQSignal<[]>();
|
||||
@@ -52,13 +52,13 @@ class MockWorkspace {
|
||||
}
|
||||
|
||||
public removeWindow(window: MockKwinClient) {
|
||||
this.activeWindow = null;
|
||||
runReorder(
|
||||
() => this.windows.splice(this.windows.indexOf(window), 1),
|
||||
() => this.windowRemoved.fire(window),
|
||||
);
|
||||
if (window === this.activeWindow) {
|
||||
const windows = this.windows.filter(w => w.desktops.includes(this.currentDesktop));
|
||||
Workspace.activeWindow = windows.length > 0 ? randomItem(windows) : null;
|
||||
if (this.activeWindow === null) {
|
||||
activateRandomWindowOnDesktop(this.currentDesktop);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ class MockWorkspace {
|
||||
frame.y += delta.y;
|
||||
}
|
||||
runOneOf(
|
||||
() => window.frameGeometry.set(frame),
|
||||
() => window.getActualFrameGeometry().set(frame),
|
||||
() => window.frameGeometry = frame,
|
||||
);
|
||||
}
|
||||
@@ -88,8 +88,8 @@ class MockWorkspace {
|
||||
const frame = window.getFrameGeometryCopy();
|
||||
if (edgeResize) {
|
||||
this.cursorPos = new MockQmlPoint(
|
||||
leftEdge ? frame.left : frame.right,
|
||||
topEdge ? frame.top : frame.bottom,
|
||||
leftEdge ? frame.x : rectRight(frame),
|
||||
topEdge ? frame.y : rectBottom(frame),
|
||||
);
|
||||
} else {
|
||||
this.cursorPos = new MockQmlPoint(
|
||||
@@ -114,7 +114,7 @@ class MockWorkspace {
|
||||
}
|
||||
}
|
||||
runOneOf(
|
||||
() => window.frameGeometry.set(frame),
|
||||
() => window.getActualFrameGeometry().set(frame),
|
||||
() => window.frameGeometry = frame,
|
||||
);
|
||||
}
|
||||
@@ -123,6 +123,15 @@ class MockWorkspace {
|
||||
window.interactiveMoveResizeFinished.fire();
|
||||
}
|
||||
|
||||
public get currentDesktop() {
|
||||
return this._currentDesktop;
|
||||
}
|
||||
|
||||
public set currentDesktop(currentDesktop: KwinDesktop) {
|
||||
this._currentDesktop = currentDesktop;
|
||||
this.currentDesktopChanged.fire();
|
||||
}
|
||||
|
||||
public get activeWindow() {
|
||||
return this._activeWindow;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ function runMaybe(f: () => void) {
|
||||
}
|
||||
}
|
||||
|
||||
function runOneOf(...fs: (() => void)[]) {
|
||||
function runOneOf<T>(...fs: (() => T)[]) {
|
||||
const index = randomInt(fs.length);
|
||||
runLog.push(`${getStackFrame(1)} - Chose ${index}`);
|
||||
fs[index]();
|
||||
return fs[index]();
|
||||
}
|
||||
|
||||
function runReorder(...fs: (() => void)[]) {
|
||||
@@ -28,6 +28,14 @@ function runReorderDebug(order: number[], ...fs: (() => void)[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function randomJitter() {
|
||||
if (Math.random() < 0.25) {
|
||||
return (Math.random() - 0.5) * 0.5;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function randomInt(n: number) {
|
||||
return Math.floor(Math.random() * n);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user