mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-14 14:23:36 +01:00
Compare commits
433 Commits
v0.1.4
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41e7b4a9a0 | ||
|
|
b5035fbb5d | ||
|
|
1a703690c9 | ||
|
|
039f69b70b | ||
|
|
f7dccfe0f5 | ||
|
|
fc7198823b | ||
|
|
c3637665e9 | ||
|
|
45046a0e1e | ||
|
|
63dee21257 | ||
|
|
2675f747bb | ||
|
|
5b9730823e | ||
|
|
84da706d64 | ||
|
|
cf7a01695e | ||
|
|
510650cb94 | ||
|
|
6fe2290d74 | ||
|
|
647f5b7ded | ||
|
|
933122b073 | ||
|
|
9b62d5408d | ||
|
|
087b08612d | ||
|
|
c4be0636f7 | ||
|
|
6df91d7d40 | ||
|
|
b0737ff603 | ||
|
|
8ff0963203 | ||
|
|
ce326f31bc | ||
|
|
2050d9a3fa | ||
|
|
8e47bfdba6 | ||
|
|
9103ce2b2b | ||
|
|
7ba7067c96 | ||
|
|
a51f2387aa | ||
|
|
e4189c19ce | ||
|
|
1a2be3bc02 | ||
|
|
c37ba42825 | ||
|
|
d0c081feae | ||
|
|
0027c7d1de | ||
|
|
096729c2bd | ||
|
|
de5efd9b35 | ||
|
|
294c14e6a6 | ||
|
|
4d26cdee32 | ||
|
|
6748d7e4a9 | ||
|
|
7f350341bb | ||
|
|
33dcadfce3 | ||
|
|
52236679a5 | ||
|
|
9431bac4ad | ||
|
|
e4a9719f4f | ||
|
|
e044a5a16f | ||
|
|
60d3ecd8aa | ||
|
|
27e0758089 | ||
|
|
7d1aef8203 | ||
|
|
b273617f44 | ||
|
|
4306a7bafe | ||
|
|
b647fdf01a | ||
|
|
9f16cb91a9 | ||
|
|
2520c4abe1 | ||
|
|
d32661ec2d | ||
|
|
9df99f28cf | ||
|
|
7327bb9a70 | ||
|
|
b9a9d20c66 | ||
|
|
f89224c5d4 | ||
|
|
e68cf6fa91 | ||
|
|
b50326ed27 | ||
|
|
66f2395840 | ||
|
|
7828c403ba | ||
|
|
366cd4ff91 | ||
|
|
a3ee513003 | ||
|
|
0de3fa62f0 | ||
|
|
aadf80f3be | ||
|
|
0a3f27d5ad | ||
|
|
9c5b380412 | ||
|
|
587c5a2636 | ||
|
|
b5ca0bfd45 | ||
|
|
30fc1ef538 | ||
|
|
76d0a38165 | ||
|
|
34d65dddba | ||
|
|
a8e7f02b0a | ||
|
|
1a59b3a2e3 | ||
|
|
cd7606540a | ||
|
|
f41e8c151e | ||
|
|
8ce49f5868 | ||
|
|
72d20d5745 | ||
|
|
38686a1167 | ||
|
|
d9648ddd0c | ||
|
|
6d01e53ef3 | ||
|
|
f87d4d520b | ||
|
|
0c8eceb0c4 | ||
|
|
afde7a3fb5 | ||
|
|
b89e5eafd2 | ||
|
|
8adff69b81 | ||
|
|
a23019eccf | ||
|
|
7bd2ff4087 | ||
|
|
3cc0e5d4c4 | ||
|
|
4c35f47bc4 | ||
|
|
6b918dae7f | ||
|
|
bda6054044 | ||
|
|
80c98596dd | ||
|
|
89d1924736 | ||
|
|
5d6bc49ca9 | ||
|
|
c299326a27 | ||
|
|
ec63b3e8bc | ||
|
|
d592889dbf | ||
|
|
04a5b3e669 | ||
|
|
49dcdf806a | ||
|
|
0463a28d3d | ||
|
|
dc31ce227a | ||
|
|
51f1aa7ede | ||
|
|
86b4d239e7 | ||
|
|
925f3bd87b | ||
|
|
f8120f6b11 | ||
|
|
67e0914e1e | ||
|
|
a6e0fa2ca9 | ||
|
|
03fb786183 | ||
|
|
0aab892269 | ||
|
|
9561c0fba0 | ||
|
|
acc119a529 | ||
|
|
fd351b6a01 | ||
|
|
499a960f4c | ||
|
|
6957af3196 | ||
|
|
3f1348e5b9 | ||
|
|
54b4b37836 | ||
|
|
93223d2d92 | ||
|
|
99af0f8f7b | ||
|
|
4ee4d199a0 | ||
|
|
00477e2696 | ||
|
|
fbb7f70b15 | ||
|
|
71b6a0eeea | ||
|
|
5e5323e696 | ||
|
|
13bf9e64da | ||
|
|
613d69a737 | ||
|
|
ca09b9b300 | ||
|
|
fa87a8ca88 | ||
|
|
7c25f2c2e4 | ||
|
|
e764dad7a4 | ||
|
|
a2bd277620 | ||
|
|
97423fc8e9 | ||
|
|
3b0830e511 | ||
|
|
f5c9008287 | ||
|
|
0f7d164550 | ||
|
|
22b8029fb4 | ||
|
|
46f2aad674 | ||
|
|
6d9f51e645 | ||
|
|
ec0dea588d | ||
|
|
24f838e83e | ||
|
|
13114724b7 | ||
|
|
267c24bc75 | ||
|
|
57b1bc1414 | ||
|
|
22dcf15129 | ||
|
|
e1634d2a32 | ||
|
|
37edbcfebc | ||
|
|
b010215318 | ||
|
|
a48715f15a | ||
|
|
6308414129 | ||
|
|
035e77bd25 | ||
|
|
7b98b563eb | ||
|
|
5fda4a39b8 | ||
|
|
3ad0ae6aca | ||
|
|
2575ca2da4 | ||
|
|
37f1a163cc | ||
|
|
91c532d9b1 | ||
|
|
75d72522a2 | ||
|
|
438bfc86ff | ||
|
|
ca86418e3c | ||
|
|
d8c76b97a9 | ||
|
|
a4be1b95e0 | ||
|
|
6e666bc4d8 | ||
|
|
857b3c9ccf | ||
|
|
9b916c0c21 | ||
|
|
b642eddb96 | ||
|
|
1229c65580 | ||
|
|
b25cc1c8bf | ||
|
|
e02ddd47cc | ||
|
|
e20b4aabc3 | ||
|
|
acaee5595d | ||
|
|
083a142597 | ||
|
|
1633a5ebc2 | ||
|
|
352c010021 | ||
|
|
33965f92ad | ||
|
|
635272fc10 | ||
|
|
5354e9c7a4 | ||
|
|
e35164f106 | ||
|
|
a637eefd82 | ||
|
|
db361f36db | ||
|
|
577fa0a97f | ||
|
|
f8ada73739 | ||
|
|
ffcc2f71d1 | ||
|
|
ed79793002 | ||
|
|
afb819a383 | ||
|
|
3438818999 | ||
|
|
611e4cc4a8 | ||
|
|
27490de0d1 | ||
|
|
5112a8f39d | ||
|
|
c3f77ef4f8 | ||
|
|
33520a46b5 | ||
|
|
0903be7931 | ||
|
|
e78e6b1382 | ||
|
|
adafa32488 | ||
|
|
329b3052a4 | ||
|
|
b25662fea2 | ||
|
|
edc9b0cd3d | ||
|
|
a43eb8fbf5 | ||
|
|
8b0f1d007f | ||
|
|
83a502f199 | ||
|
|
5006aa9009 | ||
|
|
e4a8117a94 | ||
|
|
748c389b34 | ||
|
|
26a18adeb4 | ||
|
|
5d094f601f | ||
|
|
5a0ba4cdbb | ||
|
|
7c41460b14 | ||
|
|
d34a561753 | ||
|
|
04146a3ce9 | ||
|
|
09a544b45b | ||
|
|
0e1ad164d4 | ||
|
|
cec8b04ffd | ||
|
|
f4b3d568ee | ||
|
|
d3dc193d29 | ||
|
|
441bfce053 | ||
|
|
458d1ef80a | ||
|
|
be5945c64b | ||
|
|
38ce38d65c | ||
|
|
6ed52c9387 | ||
|
|
e466a17877 | ||
|
|
f5def84010 | ||
|
|
12473aa41c | ||
|
|
33d1c0edbc | ||
|
|
8d346627d5 | ||
|
|
be83b4b5f2 | ||
|
|
f7ac1d0ece | ||
|
|
2ba3ca4f31 | ||
|
|
52b7b8d03d | ||
|
|
f669231517 | ||
|
|
4d8afc96c9 | ||
|
|
c154c32b3d | ||
|
|
2618d8f529 | ||
|
|
4dfab7d65f | ||
|
|
075c0602a7 | ||
|
|
83d9232d0b | ||
|
|
84f74fc5a6 | ||
|
|
01b2c52460 | ||
|
|
dae77d87b9 | ||
|
|
07bd53876f | ||
|
|
8c051d9f5e | ||
|
|
7e12f6f4a9 | ||
|
|
67b00fd06d | ||
|
|
ce2c55bbd8 | ||
|
|
ec47526de1 | ||
|
|
81ea4569e1 | ||
|
|
7b3f03bd6a | ||
|
|
a6d46dbf45 | ||
|
|
5b91e22114 | ||
|
|
091e9c3e56 | ||
|
|
a7d29a7344 | ||
|
|
763c710770 | ||
|
|
f8cf70ee1d | ||
|
|
89bd7d2465 | ||
|
|
eec628f7f1 | ||
|
|
77ae5bc2f4 | ||
|
|
87a0aaee0c | ||
|
|
fc5bb892f9 | ||
|
|
26ec574452 | ||
|
|
1c7a5ccb42 | ||
|
|
876439b96b | ||
|
|
d1b1a9e006 | ||
|
|
04791f427b | ||
|
|
e9bccd0316 | ||
|
|
29201b6b94 | ||
|
|
8efce49f2c | ||
|
|
6c022f8d69 | ||
|
|
748659db35 | ||
|
|
91c7f0588c | ||
|
|
60ec439d06 | ||
|
|
ead175ddbc | ||
|
|
5dd3e76602 | ||
|
|
ebcd7ce224 | ||
|
|
b982021573 | ||
|
|
3c84bfd27e | ||
|
|
c874bfc7bf | ||
|
|
85f9c381e5 | ||
|
|
336a4e358f | ||
|
|
4576078b96 | ||
|
|
39971774ea | ||
|
|
7da431081e | ||
|
|
7cc69a4a40 | ||
|
|
5f325a7458 | ||
|
|
34a7b2eb0c | ||
|
|
b08eb0d50c | ||
|
|
005a95b1e6 | ||
|
|
092e36b8b3 | ||
|
|
70be6f4ea4 | ||
|
|
e09d55e71a | ||
|
|
7cef7b53b5 | ||
|
|
8594e72d31 | ||
|
|
bc22ab699f | ||
|
|
b3844af1f3 | ||
|
|
b43f03ce83 | ||
|
|
5c1cfe7b2e | ||
|
|
6269e5972c | ||
|
|
ffa0b0b55e | ||
|
|
66199c5b15 | ||
|
|
aa42a64a48 | ||
|
|
17f1923423 | ||
|
|
5cfc3e831b | ||
|
|
aaf0434053 | ||
|
|
2224479c30 | ||
|
|
957588f60d | ||
|
|
d111d68c0b | ||
|
|
a10b13c799 | ||
|
|
1e69c65c25 | ||
|
|
711ab8d59b | ||
|
|
e1c36c9190 | ||
|
|
686d013734 | ||
|
|
fad4cbf019 | ||
|
|
839f8c9bf7 | ||
|
|
5d468ae70a | ||
|
|
93edcfaa2f | ||
|
|
02a3220cbd | ||
|
|
4b6a7c05e0 | ||
|
|
304158cb1f | ||
|
|
c426c06c01 | ||
|
|
c2cc21d09d | ||
|
|
09a24b89e5 | ||
|
|
4686d5e346 | ||
|
|
532adc9c6c | ||
|
|
a4e8286327 | ||
|
|
75234caa98 | ||
|
|
31b8be1481 | ||
|
|
634bc04d76 | ||
|
|
3b30c10ebb | ||
|
|
3eade94032 | ||
|
|
e46f1f4f6d | ||
|
|
45ea630e6a | ||
|
|
ed01bb674f | ||
|
|
a9534fa49c | ||
|
|
d4c0c35f3a | ||
|
|
f6e0f5ab81 | ||
|
|
51139b9e0c | ||
|
|
d7f1190152 | ||
|
|
b62d77501a | ||
|
|
7cb60ca7c5 | ||
|
|
43edf13bb2 | ||
|
|
cd894655db | ||
|
|
02c54734fb | ||
|
|
4a3f7ee34e | ||
|
|
2db0d888c1 | ||
|
|
cf5a41b5eb | ||
|
|
e4ee298606 | ||
|
|
38c0b25a1c | ||
|
|
d1b6a63af5 | ||
|
|
c246b209c4 | ||
|
|
a2e1b8c967 | ||
|
|
cb387025d2 | ||
|
|
6655d290f2 | ||
|
|
999f2ae2d4 | ||
|
|
cddc69d2bf | ||
|
|
43b2366378 | ||
|
|
e67425f841 | ||
|
|
b2a34204c6 | ||
|
|
d18283969a | ||
|
|
0138a313c0 | ||
|
|
c62ddb3c42 | ||
|
|
87e8eb48a6 | ||
|
|
5f1356b3e2 | ||
|
|
00df672352 | ||
|
|
2b83ff8148 | ||
|
|
749e247d85 | ||
|
|
e70086b681 | ||
|
|
39685dd615 | ||
|
|
228ef78d7f | ||
|
|
e2ae9b1207 | ||
|
|
5e3f1cbb44 | ||
|
|
9fd4dbf044 | ||
|
|
9be248bc03 | ||
|
|
85fe20ebba | ||
|
|
409d374b72 | ||
|
|
1fb0a7cd6e | ||
|
|
d0e46515c5 | ||
|
|
e01bbd9f74 | ||
|
|
be53ea2c24 | ||
|
|
d49279e888 | ||
|
|
daa2912945 | ||
|
|
2c515d54f7 | ||
|
|
f9785bef55 | ||
|
|
0519ebddbf | ||
|
|
b1ca0a3e3c | ||
|
|
84ccfedad4 | ||
|
|
adcb38fed9 | ||
|
|
4a19edaab2 | ||
|
|
676b643faf | ||
|
|
7f74640dbd | ||
|
|
c247426b8e | ||
|
|
4d7ccc5519 | ||
|
|
71e28b33e3 | ||
|
|
40226a2bbd | ||
|
|
2814349228 | ||
|
|
d627a1a771 | ||
|
|
78683ce7b3 | ||
|
|
a1ca4f03c3 | ||
|
|
147a56c274 | ||
|
|
127254b7ac | ||
|
|
4e6e2b3aa8 | ||
|
|
a55069df48 | ||
|
|
7fd545ca35 | ||
|
|
14e63292e1 | ||
|
|
18f34babfa | ||
|
|
2f7ae6f15f | ||
|
|
29a6c39084 | ||
|
|
5d0806a8c9 | ||
|
|
6c53fd7830 | ||
|
|
6ae59671a2 | ||
|
|
f17bfe267e | ||
|
|
840af215a0 | ||
|
|
6981d778a9 | ||
|
|
5d6351f48d | ||
|
|
ac0f33f7ed | ||
|
|
f19bd3032b | ||
|
|
3f3c2815da | ||
|
|
7070878f4a | ||
|
|
d3cb9e07f7 | ||
|
|
6f6181625f | ||
|
|
80dd07fcde | ||
|
|
09d1d69668 | ||
|
|
786f5e846a | ||
|
|
65bc1a966e | ||
|
|
ddafe599a2 | ||
|
|
7ed6df511f | ||
|
|
f9c4dbd447 | ||
|
|
b344888b72 | ||
|
|
a62ed682de | ||
|
|
94e9bb8e9e | ||
|
|
644f7ee604 | ||
|
|
b9a40924a8 | ||
|
|
80bcb51f75 | ||
|
|
e10e11d1de | ||
|
|
2807cafdd0 | ||
|
|
63cf48daa5 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: LGUG2Z
|
||||
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]: Short descriptive title"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See bug
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots and Videos**
|
||||
Add screenshots and videos to help explain your problem.
|
||||
|
||||
**Operating System**
|
||||
Provide the output of `systeminfo | grep "^OS Name\|^OS Version"`
|
||||
|
||||
For example:
|
||||
```
|
||||
OS Name: Microsoft Windows 11 Pro
|
||||
OS Version: 10.0.22000 N/A Build 22000
|
||||
```
|
||||
|
||||
**`komorebic check` Output**
|
||||
Provide the output of `komorebic check`
|
||||
|
||||
For example:
|
||||
```
|
||||
No KOMOREBI_CONFIG_HOME detected, defaulting to C:\Users\LGUG2Z
|
||||
|
||||
Looking for configuration files in C:\Users\LGUG2Z
|
||||
|
||||
No komorebi configuration found in C:\Users\LGUG2Z
|
||||
|
||||
If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
In particular, if you have any other AHK scripts or software running that handle any aspect of window management or manipulation
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEAT]: Short descriptive title"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -13,7 +13,7 @@ updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "weekly"
|
||||
assignees:
|
||||
- "LGUG2Z"
|
||||
commit-message:
|
||||
|
||||
40
.github/workflows/windows.yaml
vendored
40
.github/workflows/windows.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
target:
|
||||
- x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Prep cargo dirs
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
echo "TARGET=${{ matrix.target }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||
echo "SKIP_TESTS=" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||
- name: Cache cargo registry, git trees and binaries
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
echo "::set-output name=rust_hash::$(rustc -Vv | grep commit-hash | awk '{print $2}')"
|
||||
shell: bash
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target
|
||||
key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
@@ -76,26 +76,52 @@ jobs:
|
||||
- name: Run a full build
|
||||
run: |
|
||||
cargo build --locked --release --target ${{ matrix.target }}
|
||||
- name: Create MSI installer
|
||||
run: |
|
||||
cargo install cargo-wix
|
||||
cargo wix -p komorebi --nocapture -I .\wix\main.wxs --target x86_64-pc-windows-msvc
|
||||
- name: Upload the built artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: komorebi-${{ matrix.target }}
|
||||
path: |
|
||||
target/${{ matrix.target }}/release/komorebi.exe
|
||||
target/${{ matrix.target }}/release/komorebic.exe
|
||||
target/${{ matrix.target }}/release/komorebi.pdb
|
||||
target/${{ matrix.target }}/release/komorebic.pdb
|
||||
target/wix/komorebi-*.msi
|
||||
retention-days: 7
|
||||
|
||||
# Release
|
||||
- name: Generate changelog
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
shell: bash
|
||||
run: |
|
||||
if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi
|
||||
kokai release --no-emoji --add-links github:commits,issues --ref "$(git tag --points-at HEAD)" >"CHANGELOG.md"
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
version: latest
|
||||
args: release --skip-validate --rm-dist --release-notes=CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
|
||||
- name: Add MSI to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: "target/wix/komorebi-*.msi"
|
||||
|
||||
winget:
|
||||
name: Publish to WinGet
|
||||
runs-on: windows-latest
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: LGUG2Z.komorebi
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
@@ -16,7 +16,7 @@ builds:
|
||||
hooks:
|
||||
post:
|
||||
- mkdir -p dist/windows_amd64
|
||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64\komorebi.exe"
|
||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64_v1\komorebi.exe"
|
||||
- id: komorebic
|
||||
main: dummy.go
|
||||
goos: ["windows"]
|
||||
@@ -25,7 +25,7 @@ builds:
|
||||
hooks:
|
||||
post:
|
||||
- mkdir -p dist/windows_amd64
|
||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64\komorebic.exe"
|
||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64_v1\komorebic.exe"
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
@@ -35,7 +35,6 @@ archives:
|
||||
name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Arch }}-{{ .Os }}"
|
||||
files:
|
||||
- LICENSE
|
||||
- komorebi.sample.ahk
|
||||
- CHANGELOG.md
|
||||
|
||||
checksum:
|
||||
@@ -43,18 +42,3 @@ checksum:
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
|
||||
scoop:
|
||||
bucket:
|
||||
owner: LGUG2Z
|
||||
name: komorebi-bucket
|
||||
token: "{{ .Env.SCOOP_TOKEN }}"
|
||||
homepage: https://github.com/LGUG2Z/komorebi
|
||||
description: A tiling window manager for Windows
|
||||
license: MIT
|
||||
pre_install:
|
||||
- if (Get-Process -Name komorebi -ErrorAction SilentlyContinue) { komorebic stop }
|
||||
post_install:
|
||||
- Write-Host "`nRun 'cp $original_dir\komorebi.sample.ahk $Env:UserProfile\komorebi.ahk' to get started with the sample configuration"
|
||||
- Write-Host "`nRun 'komorebic ahk-library' if you would like to generate an AHK helper library to use in your configuration"
|
||||
- Write-Host "`nOnce you have a configuration file in place, you can run 'komorebic start' to start the window manager"
|
||||
|
||||
1839
Cargo.lock
generated
1839
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
@@ -1,9 +1,33 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"bindings",
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"komorebic"
|
||||
"komorebic",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
windows-interface = { version = "0.48" }
|
||||
windows-implement = { version = "0.48" }
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.48"
|
||||
features = [
|
||||
"implement",
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_Shell_Common", # for IObjectArray
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_System_SystemServices"
|
||||
]
|
||||
|
||||
739
README.md
739
README.md
@@ -2,7 +2,53 @@
|
||||
|
||||
Tiling Window Management for Windows.
|
||||
|
||||

|
||||
<p>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LGUG2Z/komorebi/.github/workflows/windows.yaml">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LGUG2Z/komorebi">
|
||||
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/LGUG2Z/komorebi/total">
|
||||
<img alt="GitHub commits since latest release (by date) for a branch" src="https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest">
|
||||
<a href="https://discord.gg/mGkn66PHkx">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/898554690126630914">
|
||||
</a>
|
||||
<a href="https://github.com/sponsors/LGUG2Z">
|
||||
<img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/LGUG2Z">
|
||||
</a>
|
||||
<a href="https://notado.app/feeds/jado/software-development">
|
||||
<img alt="Notado Feed" src="https://img.shields.io/badge/Notado-Subscribe-informational">
|
||||
</a>
|
||||
<a href="https://jeezy.substack.com">
|
||||
<img alt="Substack Read" src="https://img.shields.io/badge/Substack-Read-orange">
|
||||
</a>
|
||||
<a href="https://twitter.com/LGUG2Z">
|
||||
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/LGUG2Z">
|
||||
</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
- [About](#about)
|
||||
- [Charitable Donations](#charitable-donations)
|
||||
- [GitHub Sponsors](#github-sponsors)
|
||||
- [Demonstrations](#demonstrations)
|
||||
- [Description](#description)
|
||||
- [Design](#design)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Quickstart](#quickstart)
|
||||
- [GitHub Releases](#github-releases)
|
||||
- [Building from Source](#building-from-source)
|
||||
- [Running](#running)
|
||||
- [Configuring](#configuring)
|
||||
- [Common First-Time Tips](#common-first-time-tips)
|
||||
- [Development](#development)
|
||||
- [Logs and Debugging](#logs-and-debugging)
|
||||
- [Restoring Windows](#restoring-windows)
|
||||
- [Panics and Deadlocks](#panics-and-deadlocks)
|
||||
- [Window Manager State and Integrations](#window-manager-state-and-integrations)
|
||||
- [Window Manager Event Subscriptions](#window-manager-event-subscriptions)
|
||||
- [Subscription Event Notification Schema](#subscription-event-notification-schema)
|
||||
- [Communication over TCP](#communication-over-tcp)
|
||||
- [Socket Message Schema](#socket-message-schema)
|
||||
- [Appreciations](#appreciations)
|
||||
|
||||
## About
|
||||
|
||||
@@ -14,10 +60,67 @@ _komorebi_ allows you to control application windows, virtual workspaces and dis
|
||||
used with third-party software such as [AutoHotKey](https://github.com/Lexikos/AutoHotkey_L) to set user-defined
|
||||
keyboard shortcuts.
|
||||
|
||||
_komorebi_ aims to make _as few modifications as possible_ to the operating system and desktop environment by default.
|
||||
Users are free to make such modifications in their own configuration files for _komorebi_, but these will remain
|
||||
opt-in and off-by-default for the foreseeable future.
|
||||
|
||||
Translations of this document can be found in the project wiki:
|
||||
|
||||
- [komorebi 中文用户指南](https://github.com/LGUG2Z/komorebi/wiki/README-zh) (by [@crosstyan](https://github.com/crosstyan))
|
||||
|
||||
There is a [Discord server](https://discord.gg/mGkn66PHkx) available for _komorebi_-related discussion, help,
|
||||
troubleshooting etc. If you have any specific feature requests or bugs to report, please create an issue in this
|
||||
repository.
|
||||
|
||||
There is a [YouTube channel](https://www.youtube.com/channel/UCeai3-do-9O4MNy9_xjO6mg) where I livestream development
|
||||
on _komorebi_. If you would like to be notified of upcoming livestreams please subscribe and turn on
|
||||
notifications. Videos of previous livestreams are also made available in
|
||||
a [dedicated playlist](https://www.youtube.com/playlist?list=PLllZnrEJu89Cpu4tMO8LAg1m6gWYWLSGJ).
|
||||
|
||||
Articles, blog posts, demos, and videos about _komorebi_ can be added to this list by PR:
|
||||
|
||||
- [Moving to Windows from Linux Pt 1](https://kvwu.io/posts/moving-to-windows/)
|
||||
- [Windows 下的现代化平铺窗口管理器 komorebi](https://zhuanlan.zhihu.com/p/455064481)
|
||||
- [komorebi を導入してみる](https://zenn.dev/omochice/articles/50f42a3df8f426)
|
||||
|
||||
## Charitable Donations
|
||||
|
||||
_komorebi_ is a free and open-source project, and one that encourages you to make charitable donations if
|
||||
you find the software to be useful and have the financial means.
|
||||
|
||||
I encourage you to make a charitable donation
|
||||
to [Fresh Start Refugee](https://www.freshstartrefugee.org/donate) before
|
||||
you consider sponsoring me on GitHub.
|
||||
|
||||
## GitHub Sponsors
|
||||
|
||||
[GitHub Sponsors is enabled for this project](https://github.com/sponsors/LGUG2Z). Users who sponsor my work
|
||||
on `komorebi` at any of the predefined monthly tiers will be given access to a private fork of this repository where I
|
||||
push features-in-progress that are not yet quite ready to be pushed on the main repository.
|
||||
|
||||
There will never be any feature of `komorebi` that is gated behind sponsorship; every new feature will always be
|
||||
available for free in the public repository once it meets the requisite level of code quality and completion.
|
||||
|
||||
Features-in-progress that are available in early access will be tagged in the issues with
|
||||
an ["early access" label](https://github.com/LGUG2Z/komorebi/issues?q=is%3Aopen+is%3Aissue+label%3A%22early+access%22).
|
||||
|
||||
## Demonstrations
|
||||
|
||||
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
|
||||
11 with a terminal emulator, a web browser and a code editor. The original
|
||||
video can be viewed
|
||||
[here](https://twitter.com/haxibami/status/1501560766578659332).
|
||||
|
||||
https://user-images.githubusercontent.com/13164844/163496447-20c3ff0a-c5d8-40d1-9cc8-156c4cebf12e.mp4
|
||||
|
||||
[@aik2mlj](https://github.com/aik2mlj) showing _komorebi_ running on Windows 11
|
||||
with multiple workspaces, terminal emulators, a web browser, and the
|
||||
[yasb](https://github.com/DenBot/yasb) status bar with the _komorebi_ workspace
|
||||
widget enabled. The original video can be viewed
|
||||
[here](https://zhuanlan.zhihu.com/p/455064481).
|
||||
|
||||
https://user-images.githubusercontent.com/13164844/163496414-a9cde3d1-b8a7-4a7a-96fb-a8985380bc70.mp4
|
||||
|
||||
## Description
|
||||
|
||||
_komorebi_ only responds to [WinEvents](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) and the
|
||||
@@ -25,27 +128,19 @@ messages it receives on a dedicated socket.
|
||||
|
||||
_komorebic_ is a CLI that writes messages on _komorebi_'s socket.
|
||||
|
||||
_komorebi_ doesn't handle any keyboard or mouse inputs; a third party program (e.g. AutoHotKey) is needed in order to
|
||||
translate keyboard and mouse events to _komorebic_ commands.
|
||||
_komorebi_ doesn't handle any keyboard or mouse inputs; a third party program (e.g.
|
||||
[whkd](https://github.com/LGUG2Z/whkd)) is needed in order to translate keyboard and mouse events to _komorebic_ commands.
|
||||
|
||||
This architecture, popularised by [_bspwm_](https://github.com/baskerville/bspwm) on Linux and
|
||||
[_yabai_](https://github.com/koekeishiya/yabai) on macOS, is outlined as follows:
|
||||
|
||||
```
|
||||
PROCESS SOCKET
|
||||
ahk --------> komorebic <------> komorebi
|
||||
PROCESS SOCKET
|
||||
whkd/ahk --------> komorebic <------> komorebi
|
||||
```
|
||||
|
||||
## Design
|
||||
|
||||
_komorebi_ is the successor to [_yatta_](https://github.com/LGUG2Z/yatta) and as such aims to build on the learnings
|
||||
from that project.
|
||||
|
||||
While _yatta_ was primary an attempt to learn how to work with and call Windows APIs from Rust, while secondarily
|
||||
implementing a minimal viable tiling window manager for my own needs (largely single monitor, single workspace),
|
||||
_komorebi_ has been redesigned from the ground-up to support more complex features that have become standard in tiling
|
||||
window managers on other platforms.
|
||||
|
||||
_komorebi_ holds a list of physical monitors.
|
||||
|
||||
A monitor is just a rectangle of the available work area which contains one or more virtual workspaces.
|
||||
@@ -63,34 +158,83 @@ This means that:
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Quickstart
|
||||
|
||||
Make sure that you have either the [Scoop Package Manager](https://scoop.sh) or WinGet installed, then run the following
|
||||
commands at a PowerShell prompt. If you are using WinGet, make sure that you open a new terminal window or reload your
|
||||
profile after running the installation steps. Since this is not required when using `scoop`, I personally recommend that
|
||||
you use `scoop` for this process.
|
||||
|
||||
As of v0.1.17, the quickstart recommends the use of a static configuration file. If you would like to see older versions
|
||||
of this quickstart which recommend the use of dynamic configuration scripts, please refer to
|
||||
the [README file of v0.1.16](https://github.com/LGUG2Z/komorebi/tree/v0.1.16).
|
||||
|
||||
```powershell
|
||||
# if using scoop
|
||||
scoop bucket add extras
|
||||
scoop install whkd
|
||||
scoop install komorebi
|
||||
|
||||
# if using winget
|
||||
winget install LGUG2Z.whkd
|
||||
winget install LGUG2Z.komorebi
|
||||
|
||||
# save the example configuration to ~/komorebi.json
|
||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.example.json -OutFile $Env:USERPROFILE\komorebi.example.json
|
||||
|
||||
# save the latest generated app-specific config tweaks and fixes
|
||||
komorebic fetch-app-specific-configuration
|
||||
|
||||
# ensure the ~/.config folder exists
|
||||
mkdir $Env:USERPROFILE\.config -ea 0
|
||||
|
||||
# save the sample whkdrc file with key bindings to ~/.config/whkdrc
|
||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/whkdrc.sample -OutFile $Env:USERPROFILE\.config\whkdrc
|
||||
|
||||
# start komorebi and whkd
|
||||
komorebic start -c $Env:USERPROFILE\komorebi.json --whkd
|
||||
```
|
||||
|
||||
Thanks to [@sitiom](https://github.com/sitiom) for getting _komorebi_ added to both the popular Scoop Extras bucket and
|
||||
to WinGet.
|
||||
|
||||
<!-- You can watch a walkthrough video of this quickstart below on YouTube. -->
|
||||
|
||||
<!-- [](https://www.youtube.com/watch?v=cBnLIwMtv8g) -->
|
||||
|
||||
#### Using Autohotkey
|
||||
|
||||
If you would like to use Autohotkey, please make sure you have AutoHotKey v2 installed.
|
||||
|
||||
Generally, users who opt for AHK will have specific needs that can only be addressed by the advanced functionality of AHK,
|
||||
and so they are assumed to be able to craft their own configuration files.
|
||||
|
||||
If you would like to try out AHK, a simple sample configuration powered by `komorebic.lib.ahk` is provided as a starting
|
||||
point. This sample configuration does not take into account the use of a static configuration file; if you choose to use
|
||||
a static configuration file alongside AHK, you can remove all the configuration options from your `komorebi.ahk` and use
|
||||
it solely to handle hotkey bindings.
|
||||
|
||||
```powershell
|
||||
# save the latest generated komorebic library to ~/komorebic.lib.ahk
|
||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebic.lib.ahk -OutFile $Env:USERPROFILE\komorebic.lib.ahk
|
||||
|
||||
# save the latest generated app-specific config tweaks and fixes to ~/komorebi.generated.ahk
|
||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.generated.ahk -OutFile $Env:USERPROFILE\komorebi.generated.ahk
|
||||
|
||||
# save the sample komorebi configuration file to ~/komorebi.ahk
|
||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.sample.ahk -OutFile $Env:USERPROFILE\komorebi.ahk
|
||||
```
|
||||
|
||||
### GitHub Releases
|
||||
|
||||
Prebuilt binaries are available on the [releases page](https://github.com/LGUG2Z/komorebi/releases) in a `zip` archive.
|
||||
Once downloaded, you will need to move the `komorebi.exe` and `komorebic.exe` binaries to a directory in your `Path` (
|
||||
you can see these directories by running `$Env:Path.split(";")` at a PowerShell prompt).
|
||||
|
||||
Alternatively, you may add a new directory to your `Path`
|
||||
using [`setx`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/setx) or the Environment
|
||||
Variables pop up in System Properties Advanced (which can be launched with `SystemPropertiesAdvanced.exe` at a
|
||||
PowerShell prompt), and then move the binaries to that directory.
|
||||
|
||||
### Scoop
|
||||
|
||||
If you use the [Scoop](https://scoop.sh/) command line installer, you can run the following commands to install the
|
||||
binaries from the latest GitHub Release:
|
||||
|
||||
```
|
||||
scoop bucket add komorebi https://github.com/LGUG2Z/komorebi-bucket
|
||||
scoop install komorebi
|
||||
```
|
||||
|
||||
If you install _komorebi_ using Scoop, the binaries will automatically be added to your `Path` and a command will be
|
||||
shown for you to run in order to get started using the sample configuration file.
|
||||
|
||||
### Building from Source
|
||||
|
||||
If you prefer to compile _komorebi_ from source, you will need
|
||||
a [working Rust development environment on Windows 10](https://rustup.rs/). The `x86_64-pc-windows-msvc` toolchain is
|
||||
a [working Rust development environment on Windows 10/11](https://rustup.rs/). The `x86_64-pc-windows-msvc` toolchain is
|
||||
required, so make sure you have also installed
|
||||
the [Build Tools for Visual Studio 2019](https://stackoverflow.com/a/55603112).
|
||||
|
||||
@@ -103,35 +247,222 @@ cargo install --path komorebic --locked
|
||||
|
||||
### Running
|
||||
|
||||
Once you have either the prebuilt binaries in your `Path`, or have compiled the binaries from source (these will already
|
||||
be in your `Path` if you installed Rust with [rustup](https://rustup.rs), which you absolutely should), you can
|
||||
run `komorebic start` at a Powershell prompt, and you will see the following output:
|
||||
`komorebi` can be run in two ways, using either a static configuration file or a dynamic configuration script.
|
||||
|
||||
The quickstart covers running with a static configuration file.
|
||||
|
||||
If you would like to use a dynamic configuration script, ensure that you have a `komorebi.ps1` or `komorebi.ahk` file
|
||||
present, run `komorebic start --await-configuration` at a Powershell prompt, and you will see the following output:
|
||||
|
||||
```
|
||||
Start-Process komorebi -WindowStyle hidden
|
||||
Start-Process komorebi.exe -ArgumentList '--await-configuration' -WindowStyle hidden
|
||||
Waiting for komorebi.exe to start...Started!
|
||||
```
|
||||
|
||||
This means that `komorebi` is now running in the background, tiling all your windows, and listening for commands sent to
|
||||
it by `komorebic`. You can similarly stop the process by running `komorebic stop`.
|
||||
|
||||
For further information on running with a dynamic configuration script, please refer to
|
||||
the quickstart section in the [README file of v0.1.16](https://github.com/LGUG2Z/komorebi/tree/v0.1.16)
|
||||
|
||||
### Configuring
|
||||
|
||||
Once `komorebi` is running, you can execute the `komorebi.sample.ahk` script to set up the default keybindings via AHK
|
||||
(the file includes comments to help you start building your own configuration).
|
||||
If you followed the quickstart, `komorebi.json` will be the single place where you declaratively configure the behaviour
|
||||
of the window manager. There is a [complete JSON Schema for this configuration file](schema.json) available to provide
|
||||
users with auto-completions in their editors.
|
||||
|
||||
If you have AutoHotKey installed and a `komorebi.ahk` file in your home directory (run `$Env:UserProfile` at a
|
||||
PowerShell prompt to find your home directory), `komorebi` will automatically try to load it when starting.
|
||||
If you are running with a dynamic configuration script as recommended in v0.1.16 and earlier, `komorebi` will find the
|
||||
sample `komorebi.ps1` file in your `$Env:USERPROFILE` directory and automatically load it. This file also starts `whkd` using the sample `whkrc` file
|
||||
in your `$Env:USERPROFILE\.config` directory.
|
||||
|
||||
There is also tentative support for loading a AutoHotKey v2 files, if the file is named `komorebi.ahk2` and
|
||||
the `AutoHotKey64.exe` executable for AutoHotKey v2 is in your `Path`. If both `komorebi.ahk` and `komorebi.ahk2` files
|
||||
exist in your home directory, only `komorebi.ahk` will be loaded. An example of an AutoHotKey v2 configuration file
|
||||
for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778dabf693ce9236c57b201cd).
|
||||
Alternatively, if you have AutoHotKey installed and a `komorebi.ahk` file in `$Env:UserProfile` directory, `komorebi`
|
||||
will automatically try to load it when starting.
|
||||
|
||||
#### Migrating to a Static Configuration File
|
||||
|
||||
If you have been using `komorebi` with a dynamic configuration script and wish to migrate to using a static
|
||||
configuration file, once you have `komorebi` running in the desired configuration state, you can
|
||||
run `komorebic generate-static-config`.
|
||||
|
||||
This will print a static configuration that mostly represents your current configuration to the terminal.
|
||||
|
||||
There are four configuration options that you may need to set yourself, if you make use of them:
|
||||
|
||||
- Custom layouts paths for workspaces
|
||||
- Custom layout rules for workspaces
|
||||
- The applications.yaml path
|
||||
- Any individual application rules you have that are not in applications.yaml
|
||||
|
||||
#### Configuration with `komorebic`
|
||||
|
||||
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
|
||||
personally use [`whkd`](https://github.com/LGUG2Z/whkd) to manage my window management shortcuts, and have provided a
|
||||
sample [whkdrc](whkdrc.sample) configuration that you can use as a starting point for your own.
|
||||
|
||||
You can run `komorebic.exe` to get a full list of the commands that you can use to customise `komorebi` and create
|
||||
keybindings with. You can run `komorebic.exe <COMMAND> --help` to get a full explanation of the arguments required for
|
||||
each command.
|
||||
|
||||
You can run any configuration command in the `komorebi.ps1` file, and you can bind any action command to your desired
|
||||
key combinations in the `whkdrc` file.
|
||||
|
||||
#### AutoHotKey Helper Library for `komorebic`
|
||||
|
||||
❗️**NOTE**: This section is only relevant for people who wish to use AutoHotKey instead of [`whkd`](https://github.com/LGUG2Z/whkd).
|
||||
|
||||
❗️**NOTE**: This helper library is only compatible with AutoHotKey v1.1, not with AutoHotKey v2.
|
||||
|
||||
Additionally, you may run `komorebic.exe ahk-library` to generate a helper library for AutoHotKey which wraps
|
||||
every `komorebic` command in a native AHK function.
|
||||
|
||||
The output of this command is in AHKv1 syntax. It must be manually converted to AHKv2 syntax
|
||||
using [this tool](https://github.com/mmikeww/AHK-v2-script-converter) or something similar.
|
||||
|
||||
If you include the generated library at the top of your `~/komorebi.ahk` configuration file, you will be able to call
|
||||
any of the functions that it contains.
|
||||
|
||||
#### Using Different AHK Executables
|
||||
|
||||
❗️**NOTE**: This section is only relevant for people who wish to use AutoHotKey instead of [`whkd`](https://github.com/LGUG2Z/whkd).
|
||||
|
||||
The preferred way to install AutoHotKey for use with `komorebi` is to install it via `scoop`:
|
||||
|
||||
```powershell
|
||||
scoop bucket add versions
|
||||
scoop install autohotkey
|
||||
```
|
||||
|
||||
If you install AutoHotKey using a different method, the name of the executable file may differ from the name given by
|
||||
`scoop`, and thus what is expected by default in `komorebi`.
|
||||
|
||||
You may override the executable that `komorebi` looks for to launch and reload `komorebi.ahk` configuration files by
|
||||
setting the `$Env:KOMOREBI_AHK_EXE` environment variable.
|
||||
|
||||
Please keep in mind that even when setting a custom executable name using these environment variables, the executables
|
||||
are still required to be in your `Path`.
|
||||
|
||||
### Common First-Time Tips
|
||||
|
||||
#### Setting a Custom KOMOREBI_CONFIG_HOME Directory
|
||||
|
||||
If you do not want to keep _komorebi_-related files in your `$Env:USERPROFILE` directory, you can specify a custom directory
|
||||
by setting the `$Env:KOMOREBI_CONFIG_HOME` environment variable.
|
||||
|
||||
For example, to use the `~/.config/komorebi` directory:
|
||||
|
||||
```powershell
|
||||
# Run this command to make sure that the directory has been created
|
||||
mkdir -p ~/.config/komorebi
|
||||
|
||||
# Run this command to open up your PowerShell profile configuration in Notepad
|
||||
notepad $PROFILE
|
||||
|
||||
# Add this line (with your login user!) to the bottom of your PowerShell profile configuration
|
||||
$Env:KOMOREBI_CONFIG_HOME = 'C:\Users\LGUG2Z\.config\komorebi'
|
||||
|
||||
# Save the changes and then reload the PowerShell profile
|
||||
. $PROFILE
|
||||
```
|
||||
|
||||
If you already have configuration files that you wish to keep, move them to the `~/.config/komorebi` directory.
|
||||
|
||||
The next time you run `komorebic start`, any files created by or loaded by _komorebi_ will be placed or expected to
|
||||
exist in this folder.
|
||||
|
||||
#### Generating Common Application-Specific Configurations
|
||||
|
||||
❗️**NOTE**: This section is only relevant for people who use dynamic configuration scripts.
|
||||
|
||||
A curated selection of application-specific configurations can be generated to
|
||||
help ease the setup for first-time users.
|
||||
[`komorebi-application-specific-configuration`](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
|
||||
contains YAML definitions of settings that are known to make tricky
|
||||
applications behave as expected. These YAML definitions can be used to generate
|
||||
a `ps1` or an `ahk` file which you can import at the start of your own `komorebi.ps1` or `komorebi.ahk` files,
|
||||
leaving you to focus primarily on your desired keybindings and workspace
|
||||
configurations.
|
||||
|
||||
If you have settings for an application that you think should be part of this
|
||||
curated selection, please open a PR on the configuration repository.
|
||||
|
||||
In the event that your PR is not accepted, or if you find there are any
|
||||
settings that you wish to override, this can easily be done using an override
|
||||
file.
|
||||
|
||||
```powershell
|
||||
# Clone and enter the repository
|
||||
git clone https://github.com/LGUG2Z/komorebi-application-specific-configuration.git
|
||||
cd komorebi-application-specific-configuration
|
||||
|
||||
# Use komorebic to generate a ps1 file
|
||||
komorebic.exe pwsh-app-specific-configuration applications.yaml
|
||||
|
||||
# Application-specific generated configuration written to C:\Users\LGUG2Z\.config\komorebi\komorebi.generated.ps1
|
||||
|
||||
# Or use komorebic to generate an ahk file
|
||||
komorebic.exe ahk-app-specific-configuration applications.yaml
|
||||
|
||||
# Application-specific generated configuration written to C:\Users\LGUG2Z\.config\komorebi\komorebi.generated.ahk
|
||||
#
|
||||
# You can include the generated configuration at the top of your komorebi.ahk config with this line:
|
||||
#
|
||||
# #Include %A_ScriptDir%\komorebi.generated.ahk
|
||||
|
||||
# Optionally, provide an override file that follows the same schema as the second argument
|
||||
komorebic.exe pwsh-app-specific-configuration applications.yaml overrides.yaml
|
||||
```
|
||||
|
||||
#### Adding an Active Window Border
|
||||
|
||||
If you would like to add a visual border around the currently focused window, two commands are available:
|
||||
|
||||
```powershell
|
||||
komorebic.exe active-window-border [enable|disable]
|
||||
komorebic.exe active-window-border-colour [R G B] --window-kind single
|
||||
|
||||
# optionally, if you want a different colour for stacks of windows
|
||||
komorebic.exe active-window-border-colour [R G B] --window-kind stack
|
||||
|
||||
# optionally, if you want a different colour for windows in monocle mode
|
||||
komorebic.exe active-window-border-colour [R G B] --window-kind monocle
|
||||
```
|
||||
|
||||
It is important to note that the active window border will only apply to windows managed by `komorebi`.
|
||||
|
||||
[](https://www.youtube.com/watch?v=ywiAvoMV_gE)
|
||||
|
||||
#### Removing Gaps
|
||||
|
||||
If you would like to remove all gaps from a given workspace, both between windows themselves, and between the monitor edges and the windows, you can set the following two configuration options to `0` for the desired monitors and workspaces:
|
||||
|
||||
```powershell
|
||||
komorebic.exe container-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
|
||||
komorebic.exe workspace-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
|
||||
```
|
||||
|
||||
[](https://www.youtube.com/watch?v=eGr07mymgWE)
|
||||
|
||||
#### Multiple Layout Changes on Startup
|
||||
|
||||
❗️**NOTE**: This section is only relevant for people who use dynamic configuration scripts.
|
||||
|
||||
Depending on what is in your configuration, when `komorebi` is started, you may experience the layout rapidly being adjusted
|
||||
with many retile events.
|
||||
|
||||
If you would like to avoid this, you can start `komorebi` with a flag which tells `komorebi` to wait until all configuration
|
||||
has been loaded before listening to and responding to window manager events: `komorebic start --await-configuration`.
|
||||
|
||||
If you start `komorebi` with the `--await-configuration` flag, you _must_ send the `komorebic complete-configuration`
|
||||
command at the end of the configuration section of your `komorebi.ps1` (or `komorebi.ahk` config, before you start
|
||||
defining the key bindings). The layout will not be updated and `komorebi` will not respond to `komorebic` commands until
|
||||
this command has been received.
|
||||
|
||||
#### Floating Windows
|
||||
|
||||
Sometimes you will want a specific application to never be tiled, and instead float all the time. You add add rules to
|
||||
❗️**NOTE**: A significant number of floating window rules for the most common applications are
|
||||
[already generated for you](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)
|
||||
|
||||
Sometimes you will want a specific application to never be tiled, and instead float all the time. You can add rules to
|
||||
enforce this behaviour:
|
||||
|
||||
```powershell
|
||||
@@ -142,6 +473,9 @@ komorebic.exe float-rule title "Control Panel"
|
||||
|
||||
#### Windows Not Getting Managed
|
||||
|
||||
❗️**NOTE**: A significant number of force-manage window rules for the most common applications are
|
||||
[already generated for you](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)
|
||||
|
||||
In some rare cases, a window may not automatically be registered to be managed by `komorebi`. When this happens, you can
|
||||
manually add a rule to force `komorebi` to manage it:
|
||||
|
||||
@@ -153,6 +487,9 @@ komorebic.exe manage-rule exe TIM.exe
|
||||
|
||||
#### Tray Applications
|
||||
|
||||
❗️**NOTE**: A significant number of tray application rules for the most common applications are
|
||||
[already generated for you](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)
|
||||
|
||||
If you are experiencing behaviour where
|
||||
[closing a window leaves a blank tile, but minimizing the same window does not](https://github.com/LGUG2Z/komorebi/issues/6)
|
||||
, you have probably enabled a 'close/minimize to tray' option for that application. You can tell _komorebi_ to handle
|
||||
@@ -164,6 +501,27 @@ komorebic.exe identify-tray-application exe Discord.exe
|
||||
# komorebic.exe identify-tray-application title [TITLE]
|
||||
```
|
||||
|
||||
#### Microsoft Office Applications
|
||||
|
||||
❗️**NOTE**: Microsoft Office-specific application rules are
|
||||
[already generated for you](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)
|
||||
|
||||
Microsoft Office applications such as Word and Excel require certain configuration options to be set in order to be
|
||||
managed correctly. Below is an example of configuring Microsoft Word to be managed correctly by _komorebi_.
|
||||
|
||||
```powershell
|
||||
# This only needs to be added once
|
||||
komorebic.exe float-rule class _WwB
|
||||
|
||||
# Repeat these for other office applications such as EXCEL.EXE etc
|
||||
# Note that the capitalised EXE is important here- double check the
|
||||
# exact case for the name and the file extension in Task Manager or
|
||||
# the AHK Window Spy
|
||||
|
||||
komorebic.exe identify-layered-application exe WINWORD.EXE
|
||||
komorebic.exe identify-border-overflow-application exe WINWORD.EXE
|
||||
```
|
||||
|
||||
#### Focus Follows Mouse
|
||||
|
||||
`komorebi` supports two focus-follows-mouse implementations; the native Windows Xmouse implementation, which treats the
|
||||
@@ -184,119 +542,115 @@ passing it as an argument to the `--implementation` flag:
|
||||
komorebic.exe toggle-focus-follows-mouse --implementation komorebi
|
||||
```
|
||||
|
||||
## Configuration with `komorebic`
|
||||
#### Mouse Follows Focus
|
||||
|
||||
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
|
||||
personally use AutoHotKey to manage my window management shortcuts, and have provided a
|
||||
sample [komorebi.ahk](komorebi.sample.ahk) AHK script that you can use as a starting point for your own.
|
||||
By default, the mouse will move to the center of the window when the focus is changed in a given direction. This
|
||||
behaviour is know is 'mouse follows focus'. To disable this behaviour across all workspaces, add the following command
|
||||
to your configuration file:
|
||||
|
||||
You can run `komorebic.exe` to get a full list of the commands that you can use to customise `komorebi` and create
|
||||
keybindings with. You can run `komorebic.exe <COMMAND> --help` to get a full explanation of the arguments required for
|
||||
each command.
|
||||
|
||||
```
|
||||
start Start komorebi.exe as a background process
|
||||
stop Stop the komorebi.exe process and restore all hidden windows
|
||||
state Show a JSON representation of the current window manager state
|
||||
query Query the current window manager state
|
||||
log Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||
focus Change focus to the window in the specified direction
|
||||
move Move the focused window in the specified direction
|
||||
stack Stack the focused window in the specified direction
|
||||
resize Resize the focused window in the specified direction
|
||||
unstack Unstack the focused window
|
||||
cycle-stack Cycle the focused stack in the specified cycle direction
|
||||
move-to-monitor Move the focused window to the specified monitor
|
||||
move-to-workspace Move the focused window to the specified workspace
|
||||
send-to-monitor Send the focused window to the specified monitor
|
||||
send-to-workspace Send the focused window to the specified workspace
|
||||
focus-monitor Focus the specified monitor
|
||||
focus-workspace Focus the specified workspace on the focused monitor
|
||||
new-workspace Create and append a new workspace on the focused monitor
|
||||
invisible-borders Set the invisible border dimensions around each window
|
||||
adjust-container-padding Adjust container padding on the focused workspace
|
||||
adjust-workspace-padding Adjust workspace padding on the focused workspace
|
||||
change-layout Set the layout on the focused workspace
|
||||
flip-layout Flip the layout on the focused workspace (BSP only)
|
||||
promote Promote the focused window to the top of the tree
|
||||
retile Force the retiling of all managed windows
|
||||
ensure-workspaces Create at least this many workspaces for the specified monitor
|
||||
container-padding Set the container padding for the specified workspace
|
||||
workspace-padding Set the workspace padding for the specified workspace
|
||||
workspace-layout Set the layout for the specified workspace
|
||||
workspace-tiling Enable or disable window tiling for the specified workspace
|
||||
workspace-name Set the workspace name for the specified workspace
|
||||
toggle-pause Toggle the window manager on and off across all monitors
|
||||
toggle-tiling Toggle window tiling on the focused workspace
|
||||
toggle-float Toggle floating mode for the focused window
|
||||
toggle-monocle Toggle monocle mode for the focused container
|
||||
toggle-maximize Toggle native maximization for the focused window
|
||||
restore-windows Restore all hidden windows (debugging command)
|
||||
manage Force komorebi to manage the focused window
|
||||
unmanage Unmanage a window that was forcibly managed
|
||||
reload-configuration Reload ~/komorebi.ahk (if it exists)
|
||||
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
|
||||
float-rule Add a rule to always float the specified application
|
||||
manage-rule Add a rule to always manage the specified application
|
||||
workspace-rule Add a rule to associate an application with a workspace
|
||||
identify-tray-application Identify an application that closes to the system tray
|
||||
identify-border-overflow Identify an application that has overflowing borders
|
||||
focus-follows-mouse Enable or disable focus follows mouse for the operating system
|
||||
toggle-focus-follows-mouse Toggle focus follows mouse for the operating system
|
||||
ahk-library Generate a library of AutoHotKey helper functions
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```powershell
|
||||
komorebic.exe mouse-follows-focus disable
|
||||
```
|
||||
|
||||
### AutoHotKey Helper Library for `komorebic`
|
||||
[](https://www.youtube.com/watch?v=LBoyXQiNINc)
|
||||
|
||||
Additionally, you may run `komorebic.exe ahk-library` to
|
||||
generate [a helper library for AutoHotKey](komorebic.lib.sample.ahk) which wraps every `komorebic` command in a native
|
||||
AHK function.
|
||||
#### Saving and Loading Resized Layouts
|
||||
|
||||
If you include the generated library at the top of your `~/komorebi.ahk` configuration file, you will be able to call
|
||||
any of the functions that it contains. A sample AHK script that shows how this library can be
|
||||
used [is available here](komorebi.sample.with.lib.ahk).
|
||||
If you create a BSP layout through various resize adjustments that you want to be able to restore easily in the future,
|
||||
it is possible to "quicksave" that layout to the system's temporary folder and load it later in the same session, or
|
||||
alternatively, you may save it to a specific file to be loaded again at any point in the future.
|
||||
|
||||
## Features
|
||||
```powershell
|
||||
komorebic.exe quick-save # saves the focused workspace to $Env:TEMP\komorebi.quicksave.json
|
||||
komorebic.exe quick-load # loads $Env:TEMP\komorebi.quicksave.json on the focused workspace
|
||||
|
||||
- [x] Multi-monitor
|
||||
- [x] Virtual workspaces
|
||||
- [x] Window stacks
|
||||
- [x] Cycle through stacked windows
|
||||
- [x] Change focused window by direction
|
||||
- [x] Move focused window container in direction
|
||||
- [x] Move focused window container to monitor and follow
|
||||
- [x] Move focused window container to workspace follow
|
||||
- [x] Send focused window container to monitor
|
||||
- [x] Send focused window container to workspace
|
||||
- [x] Mouse follows focused container
|
||||
- [x] Resize window container in direction
|
||||
- [ ] Resize child window containers by split ratio
|
||||
- [x] Mouse drag to swap window container position
|
||||
- [x] Mouse drag to resize window container
|
||||
- [x] Configurable workspace and container gaps
|
||||
- [x] BSP tree layout
|
||||
- [x] Flip BSP tree layout horizontally or vertically
|
||||
- [x] Equal-width, max-height column layout
|
||||
- [x] Floating rules based on exe name, window title and class
|
||||
- [x] Workspace rules based on exe name and window class
|
||||
- [x] Additional manage rules based on exe name and window class
|
||||
- [x] Identify applications which overflow their borders by exe name and class
|
||||
- [x] Identify 'close/minimize to tray' applications by exe name and class
|
||||
- [x] Configure and compensate for the size of Windows 10's invisible borders
|
||||
- [x] Toggle floating windows
|
||||
- [x] Toggle monocle window
|
||||
- [x] Toggle native maximization
|
||||
- [x] Toggle Xmouse/Windows focus follows mouse implementation
|
||||
- [x] Toggle Komorebi focus follows mouse implementation (desktop and system tray-aware)
|
||||
- [x] Toggle automatic tiling
|
||||
- [x] Pause all window management
|
||||
- [x] Load configuration on startup
|
||||
- [x] Manually reload configuration
|
||||
- [x] Watch configuration for changes
|
||||
- [x] Helper library for AutoHotKey
|
||||
- [x] View window manager state
|
||||
- [x] Query window manager state
|
||||
komorebic.exe save ~/layouts/primary.json # saves the focused workspace to $Env:USERPROFILE\layouts\primary.json
|
||||
komorebic.exe load ~/layouts/secondary.json # loads $Env:USERPROFILE\layouts\secondary.json on the focused workspace
|
||||
```
|
||||
|
||||
These layouts can be applied to arbitrary collections of windows on any workspace, as they only track the layout
|
||||
dimensions and are not coupled to the applications that were running at the time of saving.
|
||||
|
||||
When layouts that expect more or less windows than the number currently on the focused workspace are loaded, `komorebi`
|
||||
will automatically reconcile the difference.
|
||||
|
||||
#### Creating and Loading Custom Layouts
|
||||
|
||||
Particularly for users of ultrawide monitors, traditional tiling layouts may not seem like the most efficient use of
|
||||
screen space. If you feel this is the case with any of the default layouts, you are also welcome to create your own
|
||||
custom layouts and save them as JSON or YAML.
|
||||
|
||||
If you're not comfortable writing the layouts directly in JSON or YAML, you can use
|
||||
the [komorebi Custom Layout Generator](https://lgug2z.github.io/komorebi-custom-layout-generator/) to interactively
|
||||
define a custom layout, and then copy the generated JSON content.
|
||||
|
||||
Custom layouts can be loaded on the current workspace or configured for a specific workspace with the following
|
||||
commands:
|
||||
|
||||
```powershell
|
||||
komorebic.exe load-custom-layout ~/custom.yaml
|
||||
komorebic.exe workspace-custom-layout 0 0 ~/custom.yaml
|
||||
```
|
||||
|
||||
The fundamental building block of a custom _komorebi_ layout is the Column.
|
||||
|
||||
Columns come in three variants:
|
||||
|
||||
- **Primary**: This is where your primary focus will be on the screen most of the time. There must be exactly one Primary
|
||||
Column in any custom layout. Optionally, you can specify the percentage of the screen width that you want the Primary
|
||||
Column to occupy.
|
||||
- **Secondary**: This is an optional column that can either be full height of split horizontally into a fixed number of
|
||||
maximum rows. There can be any number of Secondary Columns in a custom layout.
|
||||
- **Tertiary**: This is the final column where any remaining windows will be split horizontally into rows as they get added.
|
||||
|
||||
If there is only one window on the screen when a custom layout is selected, that window will take up the full work area
|
||||
of the screen.
|
||||
|
||||
If the number of windows is equal to or less than the total number of columns defined in a custom layout, the windows
|
||||
will be arranged in an equal-width columns.
|
||||
|
||||
When the number of windows is greater than the number of columns defined in the custom layout, the windows will begin to
|
||||
be arranged according to the constraints set on the Primary and Secondary columns of the layout.
|
||||
|
||||
Here is an example custom layout that can be used as a starting point for your own:
|
||||
|
||||
YAML
|
||||
|
||||
```yaml
|
||||
- column: Secondary
|
||||
configuration: !Horizontal 2 # max number of rows
|
||||
- column: Primary
|
||||
configuration: !WidthPercentage 50 # percentage of screen
|
||||
- column: Tertiary
|
||||
configuration: Horizontal
|
||||
```
|
||||
|
||||
[](https://www.youtube.com/watch?v=SgmBHKEOcQ4)
|
||||
|
||||
#### Dynamically Changing Layouts Based on Number of Visible Window Containers
|
||||
|
||||
With `komorebi` it is possible to define rules to automatically change the layout on a specified workspace when a
|
||||
threshold of window containers is met.
|
||||
|
||||
```powershell
|
||||
# On the first workspace of the first monitor (0 0)
|
||||
# When there are one or more window containers visible on the screen (1)
|
||||
# Use the bsp layout (bsp)
|
||||
komorebic workspace-layout-rule 0 0 1 bsp
|
||||
|
||||
# On the first workspace of the first monitor (0 0)
|
||||
# When there are five or more window containers visible on the screen (five)
|
||||
# Use the custom layout stored in the home directory (~/custom.yaml)
|
||||
komorebic workspace-custom-layout-rule 0 0 5 ~/custom.yaml
|
||||
```
|
||||
|
||||
However, if you add workspace layout rules, you will not be able to manually change the layout of a workspace until all
|
||||
layout rules for that workspace have been cleared.
|
||||
|
||||
```powershell
|
||||
# If you decide that workspace layout rules are not for you, you can remove them from that same workspace like this
|
||||
komorebic clear-workspace-layout-rules 0 0
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
@@ -323,14 +677,14 @@ the IDE for completions and navigation:
|
||||
|
||||
## Logs and Debugging
|
||||
|
||||
Logs from `komorebi` will be appended to `~/komorebi.log`; this file is never rotated or overwritten, so it will keep
|
||||
Logs from `komorebi` will be appended to `%LOCALAPPDATA%/komorebi/komorebi.log`; this file is never rotated or overwritten, so it will keep
|
||||
growing until it is deleted by the user.
|
||||
|
||||
Whenever running the `komorebic stop` command or sending a Ctrl-C signal to `komorebi` directly, the `komorebi` process
|
||||
ensures that all hidden windows are restored before termination.
|
||||
|
||||
If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known
|
||||
to `komorebi` are stored and continuously updated in `~/komorebi.hwnd.json`.
|
||||
to `komorebi` are stored and continuously updated in `%LOCALAPPDATA%/komorebi//komorebi.hwnd.json`.
|
||||
|
||||
### Restoring Windows
|
||||
|
||||
@@ -354,3 +708,84 @@ representation of the `State` struct, which includes the current state of `Windo
|
||||
|
||||
This may also be polled to build further integrations and widgets on top of (if you ever wanted to build something
|
||||
like [Stackline](https://github.com/AdamWagner/stackline) for Windows, you could do it by polling this command).
|
||||
|
||||
## Window Manager Event Subscriptions
|
||||
|
||||
It is also possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
||||
by `komorebi` using [Named Pipes](https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes).
|
||||
|
||||
First, your application must create a named pipe. Once the named pipe has been created, run the following command:
|
||||
|
||||
```powershell
|
||||
komorebic.exe subscribe <your pipe name>
|
||||
```
|
||||
|
||||
Note that you do not have to include the full path of the named pipe, just the name.
|
||||
|
||||
If the named pipe exists, `komorebi` will start pushing JSON data of successfully handled events and messages:
|
||||
|
||||
```json lines
|
||||
{"event":{"type":"AddSubscriber","content":"yasb"},"state":{}}
|
||||
{"event":{"type":"FocusWindow","content":"Left"},"state":{}}
|
||||
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi – README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{}}
|
||||
{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{}}
|
||||
{"event":{"type":"FocusWindow","content":"Right"},"state":{}}
|
||||
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}}
|
||||
{"event":{"type":"FocusWindow","content":"Down"},"state":{}}
|
||||
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{}}
|
||||
{"event":{"type":"FocusWindow","content":"Up"},"state":{}}
|
||||
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}}
|
||||
```
|
||||
|
||||
You may then filter on the `type` key to listen to the events that you are interested in. For a full list of possible
|
||||
notification types, refer to the enum variants of `WindowManagerEvent` in `komorebi` and `SocketMessage`
|
||||
in `komorebi-core`.
|
||||
|
||||
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Python
|
||||
by [@denBot](https://github.com/denBot) can be
|
||||
found [here](https://gist.github.com/denBot/4136279812f87819f86d99eba77c1ee0).
|
||||
|
||||
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Rust can also be found
|
||||
in the [`komokana`](https://github.com/LGUG2Z/komokana) repository.
|
||||
|
||||
### Subscription Event Notification Schema
|
||||
|
||||
A [JSON Schema](https://json-schema.org/) of the event notifications emitted to subscribers can be generated with
|
||||
the `komorebic notification-schema` command. The output of this command can be redirected to the clipboard or a file,
|
||||
which can be used with services such as [Quicktype](https://app.quicktype.io/) to generate type definitions in different
|
||||
programming languages.
|
||||
|
||||
### Communication over TCP
|
||||
|
||||
A TCP listener can optionally be exposed on a port of your choosing with the `--tcp-port=N` flag. If this flag is not
|
||||
provided to `komorebi` or `komorebic start`, no TCP listener will be created.
|
||||
|
||||
Once created, your client may send
|
||||
any [SocketMessage](https://github.com/LGUG2Z/komorebi/blob/master/komorebi-core/src/lib.rs#L37) to `komorebi` in the
|
||||
same way that `komorebic` would.
|
||||
|
||||
This can be used if you would like to create your own alternative to `komorebic` which incorporates scripting and
|
||||
various middleware layers, and similarly it can be used if you would like to integrate `komorebi` with
|
||||
a [custom input handler](https://github.com/LGUG2Z/komorebi/issues/176#issue-1302643961).
|
||||
|
||||
If a client sends an unrecognized message, it will be disconnected and have to reconnect before trying to communicate
|
||||
again.
|
||||
|
||||
### Socket Message Schema
|
||||
|
||||
A [JSON Schema](https://json-schema.org/) of socket messages used to send instructions to `komorebi` can be generated
|
||||
with the `komorebic socket-schema` command. The output of this command can be redirected to the clipboard or a file,
|
||||
which can be used with services such as [Quicktype](https://app.quicktype.io/) to generate type definitions in different
|
||||
programming languages.
|
||||
|
||||
## Appreciations
|
||||
|
||||
- First and foremost, thank you to my wife, both for naming this project and for her patience throughout its never-ending development
|
||||
|
||||
- Thank you to [@sitiom](https://github.com/sitiom) for being [an exemplary open source community leader](https://jeezy.substack.com/p/the-open-source-contributions-i-appreciate)
|
||||
|
||||
- Thank you to the developers of [nog](https://github.com/TimUntersberger/nog) who came before me and whose work taught me more than I can ever hope to repay
|
||||
|
||||
- Thank you to the developers of [GlazeWM](https://github.com/lars-berger/GlazeWM) for pushing the boundaries of tiling window management on Windows with me and having an excellent spirit of collaboration
|
||||
|
||||
- Thank you to [@Ciantic](https://github.com/Ciantic) for helping me bring the [hidden Virtual Desktops cloaking function](https://github.com/Ciantic/AltTabAccessor/issues/1) to `komorebi`
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "bindings"
|
||||
version = "0.1.0"
|
||||
authors = ["Jade Iqbal"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
windows = "0.19"
|
||||
|
||||
[build-dependencies]
|
||||
windows = "0.19"
|
||||
@@ -1,26 +0,0 @@
|
||||
fn main() {
|
||||
windows::build!(
|
||||
Windows::Win32::Foundation::RECT,
|
||||
Windows::Win32::Foundation::POINT,
|
||||
Windows::Win32::Foundation::BOOL,
|
||||
Windows::Win32::Foundation::PWSTR,
|
||||
Windows::Win32::Foundation::HWND,
|
||||
Windows::Win32::Foundation::LPARAM,
|
||||
// error: `Windows.Win32.Graphics.Dwm.DWMWA_CLOAKED` not found in metadata
|
||||
Windows::Win32::Graphics::Dwm::*,
|
||||
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
|
||||
Windows::Win32::Graphics::Gdi::*,
|
||||
Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS,
|
||||
Windows::Win32::System::Threading::PROCESS_NAME_FORMAT,
|
||||
Windows::Win32::System::Threading::OpenProcess,
|
||||
Windows::Win32::System::Threading::QueryFullProcessImageNameW,
|
||||
Windows::Win32::System::Threading::GetCurrentThreadId,
|
||||
Windows::Win32::System::Threading::AttachThreadInput,
|
||||
Windows::Win32::System::Threading::GetCurrentProcessId,
|
||||
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
|
||||
Windows::Win32::UI::Accessibility::SetWinEventHook,
|
||||
Windows::Win32::UI::Accessibility::HWINEVENTHOOK,
|
||||
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
|
||||
Windows::Win32::UI::WindowsAndMessaging::*,
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
::windows::include_bindings!();
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "derive-ahk"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
use ::std::clone::Clone;
|
||||
use ::std::convert::From;
|
||||
use ::std::convert::Into;
|
||||
use ::std::format;
|
||||
use ::std::iter::Extend;
|
||||
use ::std::iter::Iterator;
|
||||
use ::std::matches;
|
||||
use ::std::option::Option::Some;
|
||||
use ::std::string::String;
|
||||
use ::std::string::ToString;
|
||||
use ::std::unreachable;
|
||||
use ::std::vec::Vec;
|
||||
|
||||
use ::quote::quote;
|
||||
use ::syn::parse_macro_input;
|
||||
@@ -102,7 +105,8 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
|
||||
let flag_idents_clone = flag_idents.clone();
|
||||
let flags = quote! {#(--#flag_idents_clone) *}
|
||||
.to_string()
|
||||
.replace("- - ", "--");
|
||||
.replace("- - ", "--")
|
||||
.replace('_', "-");
|
||||
|
||||
let called_flag_arguments = quote! {#(%#flag_idents%) *}
|
||||
.to_string()
|
||||
@@ -110,19 +114,28 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
|
||||
.replace("% ", "%")
|
||||
.replace("%%", "% %");
|
||||
|
||||
let flags_split: Vec<_> = flags.split(' ').collect();
|
||||
let flag_args_split: Vec<_> = called_flag_arguments.split(' ').collect();
|
||||
let mut consolidated_flags: Vec<String> = Vec::new();
|
||||
|
||||
for (idx, flag) in flags_split.iter().enumerate() {
|
||||
consolidated_flags.push(format!("{} {}", flag, flag_args_split[idx]));
|
||||
}
|
||||
|
||||
let all_flags = consolidated_flags.join(" ");
|
||||
|
||||
quote! {
|
||||
impl AhkFunction for #name {
|
||||
fn generate_ahk_function() -> String {
|
||||
::std::format!(r#"
|
||||
{}({}) {{
|
||||
Run, komorebic.exe {} {} {} {}, , Hide
|
||||
RunWait, komorebic.exe {} {} {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
#all_arguments,
|
||||
::std::stringify!(#name).to_kebab_case(),
|
||||
#called_arguments,
|
||||
#flags,
|
||||
#called_flag_arguments
|
||||
#all_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -135,7 +148,7 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
|
||||
fn generate_ahk_function() -> String {
|
||||
::std::format!(r#"
|
||||
{}({}) {{
|
||||
Run, komorebic.exe {} {}, , Hide
|
||||
RunWait, komorebic.exe {} {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
#arguments,
|
||||
@@ -181,7 +194,7 @@ pub fn ahk_library(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStrea
|
||||
stream.extend(quote! {
|
||||
v.push(::std::format!(r#"
|
||||
{}() {{
|
||||
Run, komorebic.exe {}, , Hide
|
||||
RunWait, komorebic.exe {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
::std::stringify!(#name).to_kebab_case()
|
||||
|
||||
49
justfile
Normal file
49
justfile
Normal file
@@ -0,0 +1,49 @@
|
||||
set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"]
|
||||
export RUST_BACKTRACE := "full"
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
fmt:
|
||||
cargo +nightly fmt
|
||||
cargo +stable clippy
|
||||
prettier --write README.md
|
||||
prettier --write .goreleaser.yml
|
||||
prettier --write .github/dependabot.yml
|
||||
prettier --write .github/FUNDING.yml
|
||||
prettier --write .github/workflows/windows.yaml
|
||||
|
||||
install-komorebic:
|
||||
cargo +stable install --path komorebic --locked
|
||||
|
||||
install-komorebi:
|
||||
cargo +stable install --path komorebi --locked
|
||||
|
||||
install:
|
||||
just install-komorebic
|
||||
just install-komorebi
|
||||
komorebic ahk-asc '~/komorebi-application-specific-configuration/applications.yaml'
|
||||
komorebic pwsh-asc '~/komorebi-application-specific-configuration/applications.yaml'
|
||||
cat '~/.config/komorebi/komorebi.generated.ps1' >komorebi.generated.ps1
|
||||
cat '~/.config/komorebi/komorebi.generated.ahk' >komorebi.generated.ahk
|
||||
cat '~/.config/komorebi/komorebic.lib_newV2.ahk' >komorebic.lib.ahk
|
||||
|
||||
run:
|
||||
just install-komorebic
|
||||
cargo +stable run --bin komorebi --locked -- -a
|
||||
|
||||
warn $RUST_LOG="warn":
|
||||
just run
|
||||
|
||||
info $RUST_LOG="info":
|
||||
just run
|
||||
|
||||
debug $RUST_LOG="debug":
|
||||
just run
|
||||
|
||||
trace $RUST_LOG="trace":
|
||||
just run
|
||||
|
||||
deadlock $RUST_LOG="trace":
|
||||
just install-komorebic
|
||||
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
||||
@@ -1,15 +1,16 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.4"
|
||||
edition = "2018"
|
||||
version = "0.1.16"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bindings = { package = "bindings", path = "../bindings" }
|
||||
|
||||
clap = "3.0.0-beta.4"
|
||||
color-eyre = "0.5"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
color-eyre = "0.6"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
strum = { version = "0.21", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
strum = { version = "0.25", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
windows = { workspace = true }
|
||||
588
komorebi-core/src/arrangement.rs
Normal file
588
komorebi-core/src/arrangement.rs
Normal file
@@ -0,0 +1,588 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::custom_layout::Column;
|
||||
use crate::custom_layout::ColumnSplit;
|
||||
use crate::custom_layout::ColumnSplitWithCapacity;
|
||||
use crate::CustomLayout;
|
||||
use crate::DefaultLayout;
|
||||
use crate::Rect;
|
||||
|
||||
pub trait Arrangement {
|
||||
fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
len: NonZeroUsize,
|
||||
container_padding: Option<i32>,
|
||||
layout_flip: Option<Axis>,
|
||||
resize_dimensions: &[Option<Rect>],
|
||||
) -> Vec<Rect>;
|
||||
}
|
||||
|
||||
impl Arrangement for DefaultLayout {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
len: NonZeroUsize,
|
||||
container_padding: Option<i32>,
|
||||
layout_flip: Option<Axis>,
|
||||
resize_dimensions: &[Option<Rect>],
|
||||
) -> Vec<Rect> {
|
||||
let len = usize::from(len);
|
||||
let mut dimensions = match self {
|
||||
Self::BSP => recursive_fibonacci(
|
||||
0,
|
||||
len,
|
||||
area,
|
||||
layout_flip,
|
||||
calculate_resize_adjustments(resize_dimensions),
|
||||
),
|
||||
Self::Columns => columns(area, len),
|
||||
Self::Rows => rows(area, len),
|
||||
Self::VerticalStack => {
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
|
||||
let primary_right = match len {
|
||||
1 => area.right,
|
||||
_ => area.right / 2,
|
||||
};
|
||||
|
||||
let mut main_left = area.left;
|
||||
let mut stack_left = area.left + primary_right;
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
|
||||
main_left = main_left + area.right - primary_right;
|
||||
stack_left = area.left;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if len >= 1 {
|
||||
layouts.push(Rect {
|
||||
left: main_left,
|
||||
top: area.top,
|
||||
right: primary_right,
|
||||
bottom: area.bottom,
|
||||
});
|
||||
|
||||
if len > 1 {
|
||||
layouts.append(&mut rows(
|
||||
&Rect {
|
||||
left: stack_left,
|
||||
top: area.top,
|
||||
right: area.right - primary_right,
|
||||
bottom: area.bottom,
|
||||
},
|
||||
len - 1,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
Self::HorizontalStack => {
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
|
||||
let bottom = match len {
|
||||
1 => area.bottom,
|
||||
_ => area.bottom / 2,
|
||||
};
|
||||
|
||||
let mut main_top = area.top;
|
||||
let mut stack_top = area.top + bottom;
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical) if len > 1 => {
|
||||
main_top = main_top + area.bottom - bottom;
|
||||
stack_top = area.top;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if len >= 1 {
|
||||
layouts.push(Rect {
|
||||
left: area.left,
|
||||
top: main_top,
|
||||
right: area.right,
|
||||
bottom,
|
||||
});
|
||||
|
||||
if len > 1 {
|
||||
layouts.append(&mut columns(
|
||||
&Rect {
|
||||
left: area.left,
|
||||
top: stack_top,
|
||||
right: area.right,
|
||||
bottom: area.bottom - bottom,
|
||||
},
|
||||
len - 1,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
Self::UltrawideVerticalStack => {
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
|
||||
let primary_right = match len {
|
||||
1 => area.right,
|
||||
_ => area.right / 2,
|
||||
};
|
||||
|
||||
let secondary_right = match len {
|
||||
1 => 0,
|
||||
2 => area.right - primary_right,
|
||||
_ => (area.right - primary_right) / 2,
|
||||
};
|
||||
|
||||
let (primary_left, secondary_left, stack_left) = match len {
|
||||
1 => (area.left, 0, 0),
|
||||
2 => {
|
||||
let mut primary = area.left + secondary_right;
|
||||
let mut secondary = area.left;
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
|
||||
primary = area.left;
|
||||
secondary = area.left + primary_right;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
(primary, secondary, 0)
|
||||
}
|
||||
_ => {
|
||||
let primary = area.left + secondary_right;
|
||||
let mut secondary = area.left;
|
||||
let mut stack = area.left + primary_right + secondary_right;
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
|
||||
secondary = area.left + primary_right + secondary_right;
|
||||
stack = area.left;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
(primary, secondary, stack)
|
||||
}
|
||||
};
|
||||
|
||||
if len >= 1 {
|
||||
layouts.push(Rect {
|
||||
left: primary_left,
|
||||
top: area.top,
|
||||
right: primary_right,
|
||||
bottom: area.bottom,
|
||||
});
|
||||
|
||||
if len >= 2 {
|
||||
layouts.push(Rect {
|
||||
left: secondary_left,
|
||||
top: area.top,
|
||||
right: secondary_right,
|
||||
bottom: area.bottom,
|
||||
});
|
||||
|
||||
if len > 2 {
|
||||
layouts.append(&mut rows(
|
||||
&Rect {
|
||||
left: stack_left,
|
||||
top: area.top,
|
||||
right: secondary_right,
|
||||
bottom: area.bottom,
|
||||
},
|
||||
len - 2,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
};
|
||||
|
||||
dimensions
|
||||
.iter_mut()
|
||||
.for_each(|l| l.add_padding(container_padding));
|
||||
|
||||
dimensions
|
||||
}
|
||||
}
|
||||
|
||||
impl Arrangement for CustomLayout {
|
||||
fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
len: NonZeroUsize,
|
||||
container_padding: Option<i32>,
|
||||
_layout_flip: Option<Axis>,
|
||||
_resize_dimensions: &[Option<Rect>],
|
||||
) -> Vec<Rect> {
|
||||
let mut dimensions = vec![];
|
||||
let container_count = len.get();
|
||||
|
||||
if container_count < self.len() {
|
||||
let mut layouts = columns(area, container_count);
|
||||
dimensions.append(&mut layouts);
|
||||
} else {
|
||||
let count_map = self.column_container_counts();
|
||||
|
||||
// If there are not enough windows to trigger the final tertiary
|
||||
// column in the custom layout, use an offset to reduce the number of
|
||||
// columns to calculate each column's area by, so that we don't have
|
||||
// an empty ghost tertiary column and the screen space can be maximised
|
||||
// until there are enough windows to create it
|
||||
let mut tertiary_trigger_threshold = 0;
|
||||
|
||||
// always -1 because we don't insert the tertiary column in the count_map
|
||||
for i in 0..self.len() - 1 {
|
||||
tertiary_trigger_threshold += count_map.get(&i).unwrap();
|
||||
}
|
||||
|
||||
let enable_tertiary_column = len.get() > tertiary_trigger_threshold;
|
||||
|
||||
let offset = if enable_tertiary_column {
|
||||
None
|
||||
} else {
|
||||
Option::from(1)
|
||||
};
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
let primary_right = self.primary_width_percentage().map_or_else(
|
||||
|| area.right / self.len() as i32,
|
||||
|percentage| (area.right / 100) * percentage as i32,
|
||||
);
|
||||
|
||||
for (idx, column) in self.iter().enumerate() {
|
||||
// If we are offsetting a tertiary column for which the threshold
|
||||
// has not yet been met, this loop should not run for that final
|
||||
// tertiary column
|
||||
if idx < self.len() - offset.unwrap_or(0) {
|
||||
let column_area = if idx == 0 {
|
||||
Self::column_area_with_last(self.len(), area, primary_right, None, offset)
|
||||
} else {
|
||||
Self::column_area_with_last(
|
||||
self.len(),
|
||||
area,
|
||||
primary_right,
|
||||
Option::from(dimensions[self.first_container_idx(idx - 1)]),
|
||||
offset,
|
||||
)
|
||||
};
|
||||
|
||||
match column {
|
||||
Column::Primary(Option::Some(_)) => {
|
||||
let main_column_area = if idx == 0 {
|
||||
Self::main_column_area(area, primary_right, None)
|
||||
} else {
|
||||
Self::main_column_area(
|
||||
area,
|
||||
primary_right,
|
||||
Option::from(dimensions[self.first_container_idx(idx - 1)]),
|
||||
)
|
||||
};
|
||||
|
||||
dimensions.push(main_column_area);
|
||||
}
|
||||
Column::Primary(None) | Column::Secondary(None) => {
|
||||
dimensions.push(column_area);
|
||||
}
|
||||
Column::Secondary(Some(split)) => match split {
|
||||
ColumnSplitWithCapacity::Horizontal(capacity) => {
|
||||
let mut rows = rows(&column_area, *capacity);
|
||||
dimensions.append(&mut rows);
|
||||
}
|
||||
ColumnSplitWithCapacity::Vertical(capacity) => {
|
||||
let mut columns = columns(&column_area, *capacity);
|
||||
dimensions.append(&mut columns);
|
||||
}
|
||||
},
|
||||
Column::Tertiary(split) => {
|
||||
let column_area = Self::column_area_with_last(
|
||||
self.len(),
|
||||
area,
|
||||
primary_right,
|
||||
Option::from(dimensions[self.first_container_idx(idx - 1)]),
|
||||
offset,
|
||||
);
|
||||
|
||||
let remaining = container_count - tertiary_trigger_threshold;
|
||||
|
||||
match split {
|
||||
ColumnSplit::Horizontal => {
|
||||
let mut rows = rows(&column_area, remaining);
|
||||
dimensions.append(&mut rows);
|
||||
}
|
||||
ColumnSplit::Vertical => {
|
||||
let mut columns = columns(&column_area, remaining);
|
||||
dimensions.append(&mut columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dimensions
|
||||
.iter_mut()
|
||||
.for_each(|l| l.add_padding(container_padding));
|
||||
|
||||
dimensions
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Axis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
HorizontalAndVertical,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn columns(area: &Rect, len: usize) -> Vec<Rect> {
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
let right = area.right / len as i32;
|
||||
let mut left = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..len {
|
||||
layouts.push(Rect {
|
||||
left: area.left + left,
|
||||
top: area.top,
|
||||
right,
|
||||
bottom: area.bottom,
|
||||
});
|
||||
|
||||
left += right;
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn rows(area: &Rect, len: usize) -> Vec<Rect> {
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
let bottom = area.bottom / len as i32;
|
||||
let mut top = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..len {
|
||||
layouts.push(Rect {
|
||||
left: area.left,
|
||||
top: area.top + top,
|
||||
right: area.right,
|
||||
bottom,
|
||||
});
|
||||
|
||||
top += bottom;
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
|
||||
fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {
|
||||
let mut resize_adjustments = resize_dimensions.to_vec();
|
||||
|
||||
// This needs to be aware of layout flips
|
||||
for (i, opt) in resize_dimensions.iter().enumerate() {
|
||||
if let Some(resize_ref) = opt {
|
||||
if i > 0 {
|
||||
if resize_ref.left != 0 {
|
||||
#[allow(clippy::if_not_else)]
|
||||
let range = if i == 1 {
|
||||
0..1
|
||||
} else if i & 1 != 0 {
|
||||
i - 1..i
|
||||
} else {
|
||||
i - 2..i
|
||||
};
|
||||
|
||||
for n in range {
|
||||
let should_adjust = n % 2 == 0;
|
||||
if should_adjust {
|
||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||
adjacent_resize.right += resize_ref.left;
|
||||
} else {
|
||||
resize_adjustments[n] = Option::from(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: resize_ref.left,
|
||||
bottom: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rr) = resize_adjustments[i].as_mut() {
|
||||
rr.left = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if resize_ref.top != 0 {
|
||||
let range = if i == 1 {
|
||||
0..1
|
||||
} else if i & 1 == 0 {
|
||||
i - 1..i
|
||||
} else {
|
||||
i - 2..i
|
||||
};
|
||||
|
||||
for n in range {
|
||||
let should_adjust = n % 2 != 0;
|
||||
if should_adjust {
|
||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||
adjacent_resize.bottom += resize_ref.top;
|
||||
} else {
|
||||
resize_adjustments[n] = Option::from(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: resize_ref.top,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(resize)) = resize_adjustments.get_mut(i) {
|
||||
resize.top = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cleaned_resize_adjustments: Vec<_> = resize_adjustments
|
||||
.iter()
|
||||
.map(|adjustment| match adjustment {
|
||||
None => None,
|
||||
Some(rect) if rect.eq(&Rect::default()) => None,
|
||||
Some(_) => *adjustment,
|
||||
})
|
||||
.collect();
|
||||
|
||||
cleaned_resize_adjustments
|
||||
}
|
||||
|
||||
#[allow(clippy::only_used_in_recursion)]
|
||||
fn recursive_fibonacci(
|
||||
idx: usize,
|
||||
count: usize,
|
||||
area: &Rect,
|
||||
layout_flip: Option<Axis>,
|
||||
resize_adjustments: Vec<Option<Rect>>,
|
||||
) -> Vec<Rect> {
|
||||
let mut a = *area;
|
||||
|
||||
let resized = if let Some(Some(r)) = resize_adjustments.get(idx) {
|
||||
a.left += r.left;
|
||||
a.top += r.top;
|
||||
a.right += r.right;
|
||||
a.bottom += r.bottom;
|
||||
a
|
||||
} else {
|
||||
*area
|
||||
};
|
||||
|
||||
let half_width = area.right / 2;
|
||||
let half_height = area.bottom / 2;
|
||||
let half_resized_width = resized.right / 2;
|
||||
let half_resized_height = resized.bottom / 2;
|
||||
|
||||
let (main_x, alt_x, alt_y, main_y);
|
||||
|
||||
if let Some(flip) = layout_flip {
|
||||
match flip {
|
||||
Axis::Horizontal => {
|
||||
main_x = resized.left + half_width + (half_width - half_resized_width);
|
||||
alt_x = resized.left;
|
||||
|
||||
alt_y = resized.top + half_resized_height;
|
||||
main_y = resized.top;
|
||||
}
|
||||
Axis::Vertical => {
|
||||
main_y = resized.top + half_height + (half_height - half_resized_height);
|
||||
alt_y = resized.top;
|
||||
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
}
|
||||
Axis::HorizontalAndVertical => {
|
||||
main_x = resized.left + half_width + (half_width - half_resized_width);
|
||||
alt_x = resized.left;
|
||||
main_y = resized.top + half_height + (half_height - half_resized_height);
|
||||
alt_y = resized.top;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
main_y = resized.top;
|
||||
alt_y = resized.top + half_resized_height;
|
||||
}
|
||||
|
||||
#[allow(clippy::if_not_else)]
|
||||
if count == 0 {
|
||||
vec![]
|
||||
} else if count == 1 {
|
||||
vec![Rect {
|
||||
left: resized.left,
|
||||
top: resized.top,
|
||||
right: resized.right,
|
||||
bottom: resized.bottom,
|
||||
}]
|
||||
} else if idx % 2 != 0 {
|
||||
let mut res = vec![Rect {
|
||||
left: resized.left,
|
||||
top: main_y,
|
||||
right: resized.right,
|
||||
bottom: half_resized_height,
|
||||
}];
|
||||
res.append(&mut recursive_fibonacci(
|
||||
idx + 1,
|
||||
count - 1,
|
||||
&Rect {
|
||||
left: area.left,
|
||||
top: alt_y,
|
||||
right: area.right,
|
||||
bottom: area.bottom - half_resized_height,
|
||||
},
|
||||
layout_flip,
|
||||
resize_adjustments,
|
||||
));
|
||||
res
|
||||
} else {
|
||||
let mut res = vec![Rect {
|
||||
left: main_x,
|
||||
top: resized.top,
|
||||
right: half_resized_width,
|
||||
bottom: resized.bottom,
|
||||
}];
|
||||
res.append(&mut recursive_fibonacci(
|
||||
idx + 1,
|
||||
count - 1,
|
||||
&Rect {
|
||||
left: alt_x,
|
||||
top: area.top,
|
||||
right: area.right - half_resized_width,
|
||||
bottom: area.bottom,
|
||||
},
|
||||
layout_flip,
|
||||
resize_adjustments,
|
||||
));
|
||||
res
|
||||
}
|
||||
}
|
||||
216
komorebi-core/src/config_generation.rs
Normal file
216
komorebi-core/src/config_generation.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::ApplicationIdentifier;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApplicationOptions {
|
||||
ObjectNameChange,
|
||||
Layered,
|
||||
BorderOverflow,
|
||||
TrayAndMultiWindow,
|
||||
Force,
|
||||
}
|
||||
|
||||
impl ApplicationOptions {
|
||||
#[must_use]
|
||||
pub fn raw_cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
|
||||
match self {
|
||||
ApplicationOptions::ObjectNameChange => {
|
||||
format!("komorebic.exe identify-object-name-change-application {kind} \"{id}\"",)
|
||||
}
|
||||
ApplicationOptions::Layered => {
|
||||
format!("komorebic.exe identify-layered-application {kind} \"{id}\"",)
|
||||
}
|
||||
ApplicationOptions::BorderOverflow => {
|
||||
format!("komorebic.exe identify-border-overflow-application {kind} \"{id}\"",)
|
||||
}
|
||||
ApplicationOptions::TrayAndMultiWindow => {
|
||||
format!("komorebic.exe identify-tray-application {kind} \"{id}\"",)
|
||||
}
|
||||
ApplicationOptions::Force => {
|
||||
format!("komorebic.exe manage-rule {kind} \"{id}\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
|
||||
format!(
|
||||
"RunWait('{}', , \"Hide\")",
|
||||
ApplicationOptions::raw_cfgen(self, kind, id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct IdWithIdentifier {
|
||||
pub kind: ApplicationIdentifier,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct IdWithIdentifierAndComment {
|
||||
pub kind: ApplicationIdentifier,
|
||||
pub id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApplicationConfiguration {
|
||||
pub name: String,
|
||||
pub identifier: IdWithIdentifier,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub options: Option<Vec<ApplicationOptions>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub float_identifiers: Option<Vec<IdWithIdentifierAndComment>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApplicationConfigurationGenerator;
|
||||
|
||||
impl ApplicationConfigurationGenerator {
|
||||
pub fn load(content: &str) -> Result<Vec<ApplicationConfiguration>> {
|
||||
Ok(serde_yaml::from_str(content)?)
|
||||
}
|
||||
|
||||
pub fn format(content: &str) -> Result<String> {
|
||||
let mut cfgen = Self::load(content)?;
|
||||
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Ok(serde_yaml::to_string(&cfgen)?)
|
||||
}
|
||||
|
||||
fn merge(base_content: &str, override_content: &str) -> Result<Vec<ApplicationConfiguration>> {
|
||||
let base_cfgen = Self::load(base_content)?;
|
||||
let override_cfgen = Self::load(override_content)?;
|
||||
|
||||
let mut final_cfgen = base_cfgen.clone();
|
||||
|
||||
for entry in override_cfgen {
|
||||
let mut replace_idx = None;
|
||||
for (idx, base_entry) in base_cfgen.iter().enumerate() {
|
||||
if base_entry.name == entry.name {
|
||||
replace_idx = Option::from(idx);
|
||||
}
|
||||
}
|
||||
|
||||
match replace_idx {
|
||||
None => final_cfgen.push(entry),
|
||||
Some(idx) => final_cfgen[idx] = entry,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(final_cfgen)
|
||||
}
|
||||
|
||||
pub fn generate_pwsh(
|
||||
base_content: &str,
|
||||
override_content: Option<&str>,
|
||||
) -> Result<Vec<String>> {
|
||||
let mut cfgen = if let Some(override_content) = override_content {
|
||||
Self::merge(base_content, override_content)?
|
||||
} else {
|
||||
Self::load(base_content)?
|
||||
};
|
||||
|
||||
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
let mut lines = vec![String::from("# Generated by komorebic.exe"), String::new()];
|
||||
|
||||
let mut float_rules = vec![];
|
||||
|
||||
for app in cfgen {
|
||||
lines.push(format!("# {}", app.name));
|
||||
if let Some(options) = app.options {
|
||||
for opt in options {
|
||||
if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {
|
||||
lines.push(String::from("# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
|
||||
}
|
||||
|
||||
lines.push(opt.raw_cfgen(&app.identifier.kind, &app.identifier.id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(float_identifiers) = app.float_identifiers {
|
||||
for float in float_identifiers {
|
||||
let float_rule =
|
||||
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
|
||||
|
||||
// Don't want to send duped signals especially as configs get larger
|
||||
if !float_rules.contains(&float_rule) {
|
||||
float_rules.push(float_rule.clone());
|
||||
|
||||
if let Some(comment) = float.comment {
|
||||
lines.push(format!("# {comment}"));
|
||||
};
|
||||
|
||||
lines.push(float_rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(String::new());
|
||||
}
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
pub fn generate_ahk(base_content: &str, override_content: Option<&str>) -> Result<Vec<String>> {
|
||||
let mut cfgen = if let Some(override_content) = override_content {
|
||||
Self::merge(base_content, override_content)?
|
||||
} else {
|
||||
Self::load(base_content)?
|
||||
};
|
||||
|
||||
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
let mut lines = vec![String::from("; Generated by komorebic.exe"), String::new()];
|
||||
|
||||
let mut float_rules = vec![];
|
||||
|
||||
for app in cfgen {
|
||||
lines.push(format!("; {}", app.name));
|
||||
if let Some(options) = app.options {
|
||||
for opt in options {
|
||||
if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {
|
||||
lines.push(String::from("; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
|
||||
}
|
||||
|
||||
lines.push(opt.cfgen(&app.identifier.kind, &app.identifier.id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(float_identifiers) = app.float_identifiers {
|
||||
for float in float_identifiers {
|
||||
let float_rule = format!(
|
||||
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
|
||||
float.kind, float.id
|
||||
);
|
||||
|
||||
// Don't want to send duped signals especially as configs get larger
|
||||
if !float_rules.contains(&float_rule) {
|
||||
float_rules.push(float_rule.clone());
|
||||
|
||||
if let Some(comment) = float.comment {
|
||||
lines.push(format!("; {comment}"));
|
||||
};
|
||||
|
||||
lines.push(float_rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(String::new());
|
||||
}
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
}
|
||||
278
komorebi-core/src/custom_layout.rs
Normal file
278
komorebi-core/src/custom_layout.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Rect;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CustomLayout(Vec<Column>);
|
||||
|
||||
impl Deref for CustomLayout {
|
||||
type Target = Vec<Column>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CustomLayout {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomLayout {
|
||||
pub fn from_path_buf(path: PathBuf) -> Result<Self> {
|
||||
let invalid_filetype = anyhow!("custom layouts must be json or yaml files");
|
||||
let layout: Self = match path.extension() {
|
||||
Some(extension) => {
|
||||
if extension == "yaml" || extension == "yml" {
|
||||
serde_yaml::from_reader(BufReader::new(File::open(path)?))?
|
||||
} else if extension == "json" {
|
||||
serde_json::from_reader(BufReader::new(File::open(path)?))?
|
||||
} else {
|
||||
return Err(invalid_filetype);
|
||||
}
|
||||
}
|
||||
None => return Err(invalid_filetype),
|
||||
};
|
||||
|
||||
if !layout.is_valid() {
|
||||
return Err(anyhow!("the layout file provided was invalid"));
|
||||
}
|
||||
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn column_with_idx(&self, idx: usize) -> (usize, Option<&Column>) {
|
||||
let column_idx = self.column_for_container_idx(idx);
|
||||
let column = self.get(column_idx);
|
||||
(column_idx, column)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn primary_idx(&self) -> Option<usize> {
|
||||
for (i, column) in self.iter().enumerate() {
|
||||
if let Column::Primary(_) = column {
|
||||
return Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn primary_width_percentage(&self) -> Option<f32> {
|
||||
for column in self.iter() {
|
||||
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(percentage))) = column
|
||||
{
|
||||
return Option::from(*percentage);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_primary_width_percentage(&mut self, percentage: f32) {
|
||||
for column in self.iter_mut() {
|
||||
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(current))) = column {
|
||||
*current = percentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
// A valid layout must have at least one column
|
||||
if self.is_empty() {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Vertical column splits aren't supported at the moment
|
||||
for column in self.iter() {
|
||||
match column {
|
||||
Column::Tertiary(ColumnSplit::Vertical)
|
||||
| Column::Secondary(Some(ColumnSplitWithCapacity::Vertical(_))) => return false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// The final column must not have a fixed capacity
|
||||
match self.last() {
|
||||
Some(Column::Tertiary(_)) => {}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
let mut primaries = 0;
|
||||
let mut tertiaries = 0;
|
||||
|
||||
for column in self.iter() {
|
||||
match column {
|
||||
Column::Primary(_) => primaries += 1,
|
||||
Column::Tertiary(_) => tertiaries += 1,
|
||||
Column::Secondary(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// There must only be one primary and one tertiary column
|
||||
matches!(primaries, 1) && matches!(tertiaries, 1)
|
||||
}
|
||||
|
||||
pub(crate) fn column_container_counts(&self) -> HashMap<usize, usize> {
|
||||
let mut count_map = HashMap::new();
|
||||
|
||||
for (idx, column) in self.iter().enumerate() {
|
||||
match column {
|
||||
Column::Primary(_) | Column::Secondary(None) => {
|
||||
count_map.insert(idx, 1);
|
||||
}
|
||||
Column::Secondary(Some(split)) => {
|
||||
count_map.insert(
|
||||
idx,
|
||||
match split {
|
||||
ColumnSplitWithCapacity::Vertical(n)
|
||||
| ColumnSplitWithCapacity::Horizontal(n) => *n,
|
||||
},
|
||||
);
|
||||
}
|
||||
Column::Tertiary(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
count_map
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn first_container_idx(&self, col_idx: usize) -> usize {
|
||||
let count_map = self.column_container_counts();
|
||||
let mut container_idx_accumulator = 0;
|
||||
|
||||
for i in 0..col_idx {
|
||||
if let Some(n) = count_map.get(&i) {
|
||||
container_idx_accumulator += n;
|
||||
}
|
||||
}
|
||||
|
||||
container_idx_accumulator
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn column_for_container_idx(&self, idx: usize) -> usize {
|
||||
let count_map = self.column_container_counts();
|
||||
let mut container_idx_accumulator = 0;
|
||||
|
||||
// always -1 because we don't insert the tertiary column in the count_map
|
||||
for i in 0..self.len() - 1 {
|
||||
if let Some(n) = count_map.get(&i) {
|
||||
container_idx_accumulator += n;
|
||||
|
||||
// The accumulator becomes greater than the window container index
|
||||
// for the first time when we reach a column that contains that
|
||||
// window container index
|
||||
if container_idx_accumulator > idx {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the accumulator never reaches a point where it is greater than the
|
||||
// window container index, then the only remaining possibility is the
|
||||
// final tertiary column
|
||||
self.len() - 1
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn column_area(&self, work_area: &Rect, idx: usize, offset: Option<usize>) -> Rect {
|
||||
let divisor = offset.map_or_else(|| self.len(), |offset| self.len() - offset);
|
||||
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
let equal_width = work_area.right / divisor as i32;
|
||||
let mut left = work_area.left;
|
||||
let right = equal_width;
|
||||
|
||||
for _ in 0..idx {
|
||||
left += right;
|
||||
}
|
||||
|
||||
Rect {
|
||||
left,
|
||||
top: work_area.top,
|
||||
right,
|
||||
bottom: work_area.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn column_area_with_last(
|
||||
len: usize,
|
||||
work_area: &Rect,
|
||||
primary_right: i32,
|
||||
last_column: Option<Rect>,
|
||||
offset: Option<usize>,
|
||||
) -> Rect {
|
||||
let divisor = offset.map_or_else(|| len - 1, |offset| len - offset - 1);
|
||||
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
let equal_width = (work_area.right - primary_right) / divisor as i32;
|
||||
let left = last_column.map_or(work_area.left, |last| last.left + last.right);
|
||||
let right = equal_width;
|
||||
|
||||
Rect {
|
||||
left,
|
||||
top: work_area.top,
|
||||
right,
|
||||
bottom: work_area.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn main_column_area(
|
||||
work_area: &Rect,
|
||||
primary_right: i32,
|
||||
last_column: Option<Rect>,
|
||||
) -> Rect {
|
||||
let left = last_column.map_or(work_area.left, |last| last.left + last.right);
|
||||
|
||||
Rect {
|
||||
left,
|
||||
top: work_area.top,
|
||||
right: primary_right,
|
||||
bottom: work_area.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "column", content = "configuration")]
|
||||
pub enum Column {
|
||||
Primary(Option<ColumnWidth>),
|
||||
Secondary(Option<ColumnSplitWithCapacity>),
|
||||
Tertiary(ColumnSplit),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum ColumnWidth {
|
||||
WidthPercentage(f32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum ColumnSplit {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum ColumnSplitWithCapacity {
|
||||
Horizontal(usize),
|
||||
Vertical(usize),
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
use clap::ArgEnum;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum CycleDirection {
|
||||
Previous,
|
||||
@@ -13,17 +18,17 @@ pub enum CycleDirection {
|
||||
|
||||
impl CycleDirection {
|
||||
#[must_use]
|
||||
pub const fn next_idx(&self, idx: usize, len: usize) -> usize {
|
||||
pub const fn next_idx(&self, idx: usize, len: NonZeroUsize) -> usize {
|
||||
match self {
|
||||
CycleDirection::Previous => {
|
||||
Self::Previous => {
|
||||
if idx == 0 {
|
||||
len - 1
|
||||
len.get() - 1
|
||||
} else {
|
||||
idx - 1
|
||||
}
|
||||
}
|
||||
CycleDirection::Next => {
|
||||
if idx == len - 1 {
|
||||
Self::Next => {
|
||||
if idx == len.get() - 1 {
|
||||
0
|
||||
} else {
|
||||
idx + 1
|
||||
|
||||
128
komorebi-core/src/default_layout.rs
Normal file
128
komorebi-core/src/default_layout.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::OperationDirection;
|
||||
use crate::Rect;
|
||||
use crate::Sizing;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum DefaultLayout {
|
||||
BSP,
|
||||
Columns,
|
||||
Rows,
|
||||
VerticalStack,
|
||||
HorizontalStack,
|
||||
UltrawideVerticalStack,
|
||||
}
|
||||
|
||||
impl DefaultLayout {
|
||||
#[must_use]
|
||||
#[allow(clippy::cast_precision_loss, clippy::only_used_in_recursion)]
|
||||
pub fn resize(
|
||||
&self,
|
||||
unaltered: &Rect,
|
||||
resize: &Option<Rect>,
|
||||
edge: OperationDirection,
|
||||
sizing: Sizing,
|
||||
delta: i32,
|
||||
) -> Option<Rect> {
|
||||
if !matches!(self, Self::BSP) {
|
||||
return None;
|
||||
};
|
||||
|
||||
let max_divisor = 1.005;
|
||||
let mut r = resize.unwrap_or_default();
|
||||
|
||||
let resize_delta = delta;
|
||||
|
||||
match edge {
|
||||
OperationDirection::Left => match sizing {
|
||||
Sizing::Increase => {
|
||||
// Some final checks to make sure the user can't infinitely resize to
|
||||
// the point of pushing other windows out of bounds
|
||||
|
||||
// Note: These checks cannot take into account the changes made to the
|
||||
// edges of adjacent windows at operation time, so it is still possible
|
||||
// to push windows out of bounds by maxing out an Increase Left on a
|
||||
// Window with index 1, and then maxing out a Decrease Right on a Window
|
||||
// with index 0. I don't think it's worth trying to defensively program
|
||||
// against this; if people end up in this situation they are better off
|
||||
// just hitting the retile command
|
||||
let diff = ((r.left + -resize_delta) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.left += -resize_delta;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.left - -resize_delta) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.left -= -resize_delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Up => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.top + resize_delta) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.top += -resize_delta;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.top - resize_delta) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.top -= -resize_delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Right => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.right + resize_delta) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.right += resize_delta;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.right - resize_delta) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.right -= resize_delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Down => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.bottom + resize_delta) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.bottom += resize_delta;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.bottom - resize_delta) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.bottom -= resize_delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if r.eq(&Rect::default()) {
|
||||
None
|
||||
} else {
|
||||
Option::from(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
278
komorebi-core/src/direction.rs
Normal file
278
komorebi-core/src/direction.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use crate::custom_layout::Column;
|
||||
use crate::custom_layout::ColumnSplit;
|
||||
use crate::custom_layout::ColumnSplitWithCapacity;
|
||||
use crate::custom_layout::CustomLayout;
|
||||
use crate::DefaultLayout;
|
||||
use crate::OperationDirection;
|
||||
|
||||
pub trait Direction {
|
||||
fn index_in_direction(
|
||||
&self,
|
||||
op_direction: OperationDirection,
|
||||
idx: usize,
|
||||
count: usize,
|
||||
) -> Option<usize>;
|
||||
|
||||
fn is_valid_direction(
|
||||
&self,
|
||||
op_direction: OperationDirection,
|
||||
idx: usize,
|
||||
count: usize,
|
||||
) -> bool;
|
||||
fn up_index(&self, idx: usize) -> usize;
|
||||
fn down_index(&self, idx: usize) -> usize;
|
||||
fn left_index(&self, idx: usize) -> usize;
|
||||
fn right_index(&self, idx: usize) -> usize;
|
||||
}
|
||||
|
||||
impl Direction for DefaultLayout {
|
||||
fn index_in_direction(
|
||||
&self,
|
||||
op_direction: OperationDirection,
|
||||
idx: usize,
|
||||
count: usize,
|
||||
) -> Option<usize> {
|
||||
match op_direction {
|
||||
OperationDirection::Left => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.left_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OperationDirection::Right => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.right_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OperationDirection::Up => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.up_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OperationDirection::Down => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.down_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_direction(
|
||||
&self,
|
||||
op_direction: OperationDirection,
|
||||
idx: usize,
|
||||
count: usize,
|
||||
) -> bool {
|
||||
match op_direction {
|
||||
OperationDirection::Up => match self {
|
||||
Self::BSP => count > 2 && idx != 0 && idx != 1,
|
||||
Self::Columns => false,
|
||||
Self::Rows | Self::HorizontalStack => idx != 0,
|
||||
Self::VerticalStack => idx != 0 && idx != 1,
|
||||
Self::UltrawideVerticalStack => idx > 2,
|
||||
},
|
||||
OperationDirection::Down => match self {
|
||||
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
|
||||
Self::Columns => false,
|
||||
Self::Rows => idx != count - 1,
|
||||
Self::VerticalStack => idx != 0 && idx != count - 1,
|
||||
Self::HorizontalStack => idx == 0,
|
||||
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
|
||||
},
|
||||
OperationDirection::Left => match self {
|
||||
Self::BSP => count > 1 && idx != 0,
|
||||
Self::Columns | Self::VerticalStack => idx != 0,
|
||||
Self::Rows => false,
|
||||
Self::HorizontalStack => idx != 0 && idx != 1,
|
||||
Self::UltrawideVerticalStack => count > 1 && idx != 1,
|
||||
},
|
||||
OperationDirection::Right => match self {
|
||||
Self::BSP => count > 1 && idx % 2 == 0 && idx != count - 1,
|
||||
Self::Columns => idx != count - 1,
|
||||
Self::Rows => false,
|
||||
Self::VerticalStack => idx == 0,
|
||||
Self::HorizontalStack => idx != 0 && idx != count - 1,
|
||||
Self::UltrawideVerticalStack => match count {
|
||||
0 | 1 => false,
|
||||
2 => idx != 0,
|
||||
_ => idx < 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn up_index(&self, idx: usize) -> usize {
|
||||
match self {
|
||||
Self::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 1
|
||||
} else {
|
||||
idx - 2
|
||||
}
|
||||
}
|
||||
Self::Columns => unreachable!(),
|
||||
Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx - 1,
|
||||
Self::HorizontalStack => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn down_index(&self, idx: usize) -> usize {
|
||||
match self {
|
||||
Self::BSP | Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx + 1,
|
||||
Self::Columns => unreachable!(),
|
||||
Self::HorizontalStack => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn left_index(&self, idx: usize) -> usize {
|
||||
match self {
|
||||
Self::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 2
|
||||
} else {
|
||||
idx - 1
|
||||
}
|
||||
}
|
||||
Self::Columns | Self::HorizontalStack => idx - 1,
|
||||
Self::Rows => unreachable!(),
|
||||
Self::VerticalStack => 0,
|
||||
Self::UltrawideVerticalStack => match idx {
|
||||
0 => 1,
|
||||
1 => unreachable!(),
|
||||
_ => 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn right_index(&self, idx: usize) -> usize {
|
||||
match self {
|
||||
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
||||
Self::Rows => unreachable!(),
|
||||
Self::VerticalStack => 1,
|
||||
Self::UltrawideVerticalStack => match idx {
|
||||
1 => 0,
|
||||
0 => 2,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Direction for CustomLayout {
|
||||
fn index_in_direction(
|
||||
&self,
|
||||
op_direction: OperationDirection,
|
||||
idx: usize,
|
||||
count: usize,
|
||||
) -> Option<usize> {
|
||||
if count <= self.len() {
|
||||
return DefaultLayout::Columns.index_in_direction(op_direction, idx, count);
|
||||
}
|
||||
|
||||
match op_direction {
|
||||
OperationDirection::Left => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.left_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OperationDirection::Right => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.right_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OperationDirection::Up => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.up_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OperationDirection::Down => {
|
||||
if self.is_valid_direction(op_direction, idx, count) {
|
||||
Option::from(self.down_index(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_direction(
|
||||
&self,
|
||||
op_direction: OperationDirection,
|
||||
idx: usize,
|
||||
count: usize,
|
||||
) -> bool {
|
||||
if count <= self.len() {
|
||||
return DefaultLayout::Columns.is_valid_direction(op_direction, idx, count);
|
||||
}
|
||||
|
||||
match op_direction {
|
||||
OperationDirection::Left => idx != 0 && self.column_for_container_idx(idx) != 0,
|
||||
OperationDirection::Right => {
|
||||
idx != count - 1 && self.column_for_container_idx(idx) != self.len() - 1
|
||||
}
|
||||
OperationDirection::Up => {
|
||||
if idx == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let (column_idx, column) = self.column_with_idx(idx);
|
||||
column.map_or(false, |column| match column {
|
||||
Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))
|
||||
| Column::Tertiary(ColumnSplit::Horizontal) => {
|
||||
self.column_for_container_idx(idx - 1) == column_idx
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
OperationDirection::Down => {
|
||||
if idx == count - 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let (column_idx, column) = self.column_with_idx(idx);
|
||||
column.map_or(false, |column| match column {
|
||||
Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))
|
||||
| Column::Tertiary(ColumnSplit::Horizontal) => {
|
||||
self.column_for_container_idx(idx + 1) == column_idx
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn up_index(&self, idx: usize) -> usize {
|
||||
idx - 1
|
||||
}
|
||||
|
||||
fn down_index(&self, idx: usize) -> usize {
|
||||
idx + 1
|
||||
}
|
||||
|
||||
fn left_index(&self, idx: usize) -> usize {
|
||||
let column_idx = self.column_for_container_idx(idx);
|
||||
if column_idx - 1 == 0 {
|
||||
0
|
||||
} else {
|
||||
self.first_container_idx(column_idx - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn right_index(&self, idx: usize) -> usize {
|
||||
let column_idx = self.column_for_container_idx(idx);
|
||||
self.first_container_idx(column_idx + 1)
|
||||
}
|
||||
}
|
||||
@@ -1,388 +1,32 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ArgEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::OperationDirection;
|
||||
use crate::Rect;
|
||||
use crate::Sizing;
|
||||
use crate::Arrangement;
|
||||
use crate::CustomLayout;
|
||||
use crate::DefaultLayout;
|
||||
use crate::Direction;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum Layout {
|
||||
BSP,
|
||||
Columns,
|
||||
Rows,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Flip {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
HorizontalAndVertical,
|
||||
Default(DefaultLayout),
|
||||
Custom(CustomLayout),
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
#[must_use]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn resize(
|
||||
&self,
|
||||
unaltered: &Rect,
|
||||
resize: &Option<Rect>,
|
||||
edge: OperationDirection,
|
||||
sizing: Sizing,
|
||||
step: Option<i32>,
|
||||
) -> Option<Rect> {
|
||||
if !matches!(self, Self::BSP) {
|
||||
return None;
|
||||
};
|
||||
|
||||
let max_divisor = 1.005;
|
||||
let mut r = resize.unwrap_or_default();
|
||||
|
||||
let resize_step = step.unwrap_or(50);
|
||||
|
||||
match edge {
|
||||
OperationDirection::Left => match sizing {
|
||||
Sizing::Increase => {
|
||||
// Some final checks to make sure the user can't infinitely resize to
|
||||
// the point of pushing other windows out of bounds
|
||||
|
||||
// Note: These checks cannot take into account the changes made to the
|
||||
// edges of adjacent windows at operation time, so it is still possible
|
||||
// to push windows out of bounds by maxing out an Increase Left on a
|
||||
// Window with index 1, and then maxing out a Decrease Right on a Window
|
||||
// with index 0. I don't think it's worth trying to defensively program
|
||||
// against this; if people end up in this situation they are better off
|
||||
// just hitting the retile command
|
||||
let diff = ((r.left + -resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.left += -resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.left - -resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.left -= -resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Up => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.top + resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.top += -resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.top - resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.top -= -resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Right => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.right + resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.right += resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.right - resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.right -= resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Down => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.bottom + resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.bottom += resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.bottom - resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.bottom -= resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if r.eq(&Rect::default()) {
|
||||
None
|
||||
} else {
|
||||
Option::from(r)
|
||||
pub fn as_boxed_direction(&self) -> Box<dyn Direction> {
|
||||
match self {
|
||||
Layout::Default(layout) => Box::new(*layout),
|
||||
Layout::Custom(layout) => Box::new(layout.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
pub fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
len: NonZeroUsize,
|
||||
container_padding: Option<i32>,
|
||||
layout_flip: Option<Flip>,
|
||||
resize_dimensions: &[Option<Rect>],
|
||||
) -> Vec<Rect> {
|
||||
let len = usize::from(len);
|
||||
let mut dimensions = match self {
|
||||
Layout::BSP => recursive_fibonacci(
|
||||
0,
|
||||
len,
|
||||
area,
|
||||
layout_flip,
|
||||
calculate_resize_adjustments(resize_dimensions),
|
||||
),
|
||||
Layout::Columns => {
|
||||
let right = area.right / len as i32;
|
||||
let mut left = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..len {
|
||||
layouts.push(Rect {
|
||||
left: area.left + left,
|
||||
top: area.top,
|
||||
right,
|
||||
bottom: area.bottom,
|
||||
});
|
||||
|
||||
left += right;
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
Layout::Rows => {
|
||||
let bottom = area.bottom / len as i32;
|
||||
let mut top = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..len {
|
||||
layouts.push(Rect {
|
||||
left: area.left,
|
||||
top: area.top + top,
|
||||
right: area.right,
|
||||
bottom,
|
||||
});
|
||||
|
||||
top += bottom;
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
};
|
||||
|
||||
dimensions
|
||||
.iter_mut()
|
||||
.for_each(|l| l.add_padding(container_padding));
|
||||
|
||||
dimensions
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {
|
||||
let mut resize_adjustments = resize_dimensions.to_vec();
|
||||
|
||||
// This needs to be aware of layout flips
|
||||
for (i, opt) in resize_dimensions.iter().enumerate() {
|
||||
if let Some(resize_ref) = opt {
|
||||
if i > 0 {
|
||||
if resize_ref.left != 0 {
|
||||
#[allow(clippy::if_not_else)]
|
||||
let range = if i == 1 {
|
||||
0..1
|
||||
} else if i & 1 != 0 {
|
||||
i - 1..i
|
||||
} else {
|
||||
i - 2..i
|
||||
};
|
||||
|
||||
for n in range {
|
||||
let should_adjust = n % 2 == 0;
|
||||
if should_adjust {
|
||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||
adjacent_resize.right += resize_ref.left;
|
||||
} else {
|
||||
resize_adjustments[n] = Option::from(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: resize_ref.left,
|
||||
bottom: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rr) = resize_adjustments[i].as_mut() {
|
||||
rr.left = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if resize_ref.top != 0 {
|
||||
let range = if i == 1 {
|
||||
0..1
|
||||
} else if i & 1 == 0 {
|
||||
i - 1..i
|
||||
} else {
|
||||
i - 2..i
|
||||
};
|
||||
|
||||
for n in range {
|
||||
let should_adjust = n % 2 != 0;
|
||||
if should_adjust {
|
||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||
adjacent_resize.bottom += resize_ref.top;
|
||||
} else {
|
||||
resize_adjustments[n] = Option::from(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: resize_ref.top,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(resize)) = resize_adjustments.get_mut(i) {
|
||||
resize.top = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn as_boxed_arrangement(&self) -> Box<dyn Arrangement> {
|
||||
match self {
|
||||
Layout::Default(layout) => Box::new(*layout),
|
||||
Layout::Custom(layout) => Box::new(layout.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
let cleaned_resize_adjustments: Vec<_> = resize_adjustments
|
||||
.iter()
|
||||
.map(|adjustment| match adjustment {
|
||||
None => None,
|
||||
Some(rect) if rect.eq(&Rect::default()) => None,
|
||||
Some(_) => *adjustment,
|
||||
})
|
||||
.collect();
|
||||
|
||||
cleaned_resize_adjustments
|
||||
}
|
||||
|
||||
fn recursive_fibonacci(
|
||||
idx: usize,
|
||||
count: usize,
|
||||
area: &Rect,
|
||||
layout_flip: Option<Flip>,
|
||||
resize_adjustments: Vec<Option<Rect>>,
|
||||
) -> Vec<Rect> {
|
||||
let mut a = *area;
|
||||
|
||||
let resized = if let Some(Some(r)) = resize_adjustments.get(idx) {
|
||||
a.left += r.left;
|
||||
a.top += r.top;
|
||||
a.right += r.right;
|
||||
a.bottom += r.bottom;
|
||||
a
|
||||
} else {
|
||||
*area
|
||||
};
|
||||
|
||||
let half_width = area.right / 2;
|
||||
let half_height = area.bottom / 2;
|
||||
let half_resized_width = resized.right / 2;
|
||||
let half_resized_height = resized.bottom / 2;
|
||||
|
||||
let (main_x, alt_x, alt_y, main_y);
|
||||
|
||||
if let Some(flip) = layout_flip {
|
||||
match flip {
|
||||
Flip::Horizontal => {
|
||||
main_x = resized.left + half_width + (half_width - half_resized_width);
|
||||
alt_x = resized.left;
|
||||
|
||||
alt_y = resized.top + half_resized_height;
|
||||
main_y = resized.top;
|
||||
}
|
||||
Flip::Vertical => {
|
||||
main_y = resized.top + half_height + (half_height - half_resized_height);
|
||||
alt_y = resized.top;
|
||||
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
}
|
||||
Flip::HorizontalAndVertical => {
|
||||
main_x = resized.left + half_width + (half_width - half_resized_width);
|
||||
alt_x = resized.left;
|
||||
main_y = resized.top + half_height + (half_height - half_resized_height);
|
||||
alt_y = resized.top;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
main_y = resized.top;
|
||||
alt_y = resized.top + half_resized_height;
|
||||
}
|
||||
|
||||
#[allow(clippy::if_not_else)]
|
||||
if count == 0 {
|
||||
vec![]
|
||||
} else if count == 1 {
|
||||
vec![Rect {
|
||||
left: resized.left,
|
||||
top: resized.top,
|
||||
right: resized.right,
|
||||
bottom: resized.bottom,
|
||||
}]
|
||||
} else if idx % 2 != 0 {
|
||||
let mut res = vec![Rect {
|
||||
left: resized.left,
|
||||
top: main_y,
|
||||
right: resized.right,
|
||||
bottom: half_resized_height,
|
||||
}];
|
||||
res.append(&mut recursive_fibonacci(
|
||||
idx + 1,
|
||||
count - 1,
|
||||
&Rect {
|
||||
left: area.left,
|
||||
top: alt_y,
|
||||
right: area.right,
|
||||
bottom: area.bottom - half_resized_height,
|
||||
},
|
||||
layout_flip,
|
||||
resize_adjustments,
|
||||
));
|
||||
res
|
||||
} else {
|
||||
let mut res = vec![Rect {
|
||||
left: main_x,
|
||||
top: resized.top,
|
||||
right: half_resized_width,
|
||||
bottom: resized.bottom,
|
||||
}];
|
||||
res.append(&mut recursive_fibonacci(
|
||||
idx + 1,
|
||||
count - 1,
|
||||
&Rect {
|
||||
left: alt_x,
|
||||
top: area.top,
|
||||
right: area.right - half_resized_width,
|
||||
bottom: area.bottom,
|
||||
},
|
||||
layout_flip,
|
||||
resize_adjustments,
|
||||
));
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,165 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::missing_errors_doc, clippy::use_self)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgEnum;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
pub use arrangement::Arrangement;
|
||||
pub use arrangement::Axis;
|
||||
pub use custom_layout::CustomLayout;
|
||||
pub use cycle_direction::CycleDirection;
|
||||
pub use layout::Flip;
|
||||
pub use default_layout::DefaultLayout;
|
||||
pub use direction::Direction;
|
||||
pub use layout::Layout;
|
||||
pub use operation_direction::OperationDirection;
|
||||
pub use rect::Rect;
|
||||
|
||||
pub mod arrangement;
|
||||
pub mod config_generation;
|
||||
pub mod custom_layout;
|
||||
pub mod cycle_direction;
|
||||
pub mod default_layout;
|
||||
pub mod direction;
|
||||
pub mod layout;
|
||||
pub mod operation_direction;
|
||||
pub mod rect;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum SocketMessage {
|
||||
// Window / Container Commands
|
||||
FocusWindow(OperationDirection),
|
||||
MoveWindow(OperationDirection),
|
||||
CycleFocusWindow(CycleDirection),
|
||||
CycleMoveWindow(CycleDirection),
|
||||
StackWindow(OperationDirection),
|
||||
ResizeWindow(OperationDirection, Sizing),
|
||||
ResizeWindowEdge(OperationDirection, Sizing),
|
||||
ResizeWindowAxis(Axis, Sizing),
|
||||
UnstackWindow,
|
||||
CycleStack(CycleDirection),
|
||||
MoveContainerToMonitorNumber(usize),
|
||||
CycleMoveContainerToMonitor(CycleDirection),
|
||||
MoveContainerToWorkspaceNumber(usize),
|
||||
MoveContainerToNamedWorkspace(String),
|
||||
CycleMoveContainerToWorkspace(CycleDirection),
|
||||
SendContainerToMonitorNumber(usize),
|
||||
CycleSendContainerToMonitor(CycleDirection),
|
||||
SendContainerToWorkspaceNumber(usize),
|
||||
CycleSendContainerToWorkspace(CycleDirection),
|
||||
SendContainerToMonitorWorkspaceNumber(usize, usize),
|
||||
SendContainerToNamedWorkspace(String),
|
||||
MoveWorkspaceToMonitorNumber(usize),
|
||||
SwapWorkspacesToMonitorNumber(usize),
|
||||
ForceFocus,
|
||||
Close,
|
||||
Minimize,
|
||||
Promote,
|
||||
PromoteFocus,
|
||||
ToggleFloat,
|
||||
ToggleMonocle,
|
||||
ToggleMaximize,
|
||||
ToggleWindowContainerBehaviour,
|
||||
WindowHidingBehaviour(HidingBehaviour),
|
||||
ToggleCrossMonitorMoveBehaviour,
|
||||
CrossMonitorMoveBehaviour(MoveBehaviour),
|
||||
UnmanagedWindowOperationBehaviour(OperationBehaviour),
|
||||
// Current Workspace Commands
|
||||
ManageFocusedWindow,
|
||||
UnmanageFocusedWindow,
|
||||
AdjustContainerPadding(Sizing, i32),
|
||||
AdjustWorkspacePadding(Sizing, i32),
|
||||
ChangeLayout(Layout),
|
||||
FlipLayout(Flip),
|
||||
ChangeLayout(DefaultLayout),
|
||||
ChangeLayoutCustom(PathBuf),
|
||||
FlipLayout(Axis),
|
||||
// Monitor and Workspace Commands
|
||||
MonitorIndexPreference(usize, i32, i32, i32, i32),
|
||||
EnsureWorkspaces(usize, usize),
|
||||
EnsureNamedWorkspaces(usize, Vec<String>),
|
||||
NewWorkspace,
|
||||
ToggleTiling,
|
||||
Stop,
|
||||
TogglePause,
|
||||
Retile,
|
||||
QuickSave,
|
||||
QuickLoad,
|
||||
Save(PathBuf),
|
||||
Load(PathBuf),
|
||||
CycleFocusMonitor(CycleDirection),
|
||||
CycleFocusWorkspace(CycleDirection),
|
||||
FocusMonitorNumber(usize),
|
||||
FocusWorkspaceNumber(usize),
|
||||
FocusWorkspaceNumbers(usize),
|
||||
FocusMonitorWorkspaceNumber(usize, usize),
|
||||
FocusNamedWorkspace(String),
|
||||
ContainerPadding(usize, usize, i32),
|
||||
NamedWorkspaceContainerPadding(String, i32),
|
||||
WorkspacePadding(usize, usize, i32),
|
||||
NamedWorkspacePadding(String, i32),
|
||||
WorkspaceTiling(usize, usize, bool),
|
||||
NamedWorkspaceTiling(String, bool),
|
||||
WorkspaceName(usize, usize, String),
|
||||
WorkspaceLayout(usize, usize, Layout),
|
||||
WorkspaceLayout(usize, usize, DefaultLayout),
|
||||
NamedWorkspaceLayout(String, DefaultLayout),
|
||||
WorkspaceLayoutCustom(usize, usize, PathBuf),
|
||||
NamedWorkspaceLayoutCustom(String, PathBuf),
|
||||
WorkspaceLayoutRule(usize, usize, usize, DefaultLayout),
|
||||
NamedWorkspaceLayoutRule(String, usize, DefaultLayout),
|
||||
WorkspaceLayoutCustomRule(usize, usize, usize, PathBuf),
|
||||
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
|
||||
ClearWorkspaceLayoutRules(usize, usize),
|
||||
ClearNamedWorkspaceLayoutRules(String),
|
||||
// Configuration
|
||||
ReloadConfiguration,
|
||||
ReloadStaticConfiguration(PathBuf),
|
||||
WatchConfiguration(bool),
|
||||
CompleteConfiguration,
|
||||
AltFocusHack(bool),
|
||||
ActiveWindowBorder(bool),
|
||||
ActiveWindowBorderColour(WindowKind, u32, u32, u32),
|
||||
ActiveWindowBorderWidth(i32),
|
||||
ActiveWindowBorderOffset(i32),
|
||||
InvisibleBorders(Rect),
|
||||
WorkAreaOffset(Rect),
|
||||
MonitorWorkAreaOffset(usize, Rect),
|
||||
ResizeDelta(i32),
|
||||
InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),
|
||||
InitialNamedWorkspaceRule(ApplicationIdentifier, String, String),
|
||||
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
|
||||
NamedWorkspaceRule(ApplicationIdentifier, String, String),
|
||||
FloatRule(ApplicationIdentifier, String),
|
||||
ManageRule(ApplicationIdentifier, String),
|
||||
IdentifyObjectNameChangeApplication(ApplicationIdentifier, String),
|
||||
IdentifyTrayApplication(ApplicationIdentifier, String),
|
||||
IdentifyBorderOverflow(ApplicationIdentifier, String),
|
||||
IdentifyLayeredApplication(ApplicationIdentifier, String),
|
||||
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
|
||||
State,
|
||||
Query(StateQuery),
|
||||
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
|
||||
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
|
||||
MouseFollowsFocus(bool),
|
||||
ToggleMouseFollowsFocus,
|
||||
RemoveTitleBar(ApplicationIdentifier, String),
|
||||
ToggleTitleBars,
|
||||
AddSubscriber(String),
|
||||
RemoveSubscriber(String),
|
||||
NotificationSchema,
|
||||
SocketSchema,
|
||||
StaticConfigSchema,
|
||||
GenerateStaticConfig,
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
pub fn as_bytes(&self) -> Result<Vec<u8>> {
|
||||
Ok(serde_json::to_string(self)?.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
Ok(serde_json::from_slice(bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SocketMessage {
|
||||
@@ -92,7 +170,19 @@ impl FromStr for SocketMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum WindowKind {
|
||||
Single,
|
||||
Stack,
|
||||
Monocle,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum StateQuery {
|
||||
FocusedMonitorIndex,
|
||||
@@ -101,22 +191,79 @@ pub enum StateQuery {
|
||||
FocusedWindowIndex,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ApplicationIdentifier {
|
||||
#[serde(alias = "exe")]
|
||||
Exe,
|
||||
#[serde(alias = "class")]
|
||||
Class,
|
||||
#[serde(alias = "title")]
|
||||
Title,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum FocusFollowsMouseImplementation {
|
||||
/// A custom FFM implementation (slightly more CPU-intensive)
|
||||
Komorebi,
|
||||
/// The native (legacy) Windows FFM implementation
|
||||
Windows,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum WindowContainerBehaviour {
|
||||
/// Create a new container for each new window
|
||||
Create,
|
||||
/// Append new windows to the focused window container
|
||||
Append,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum MoveBehaviour {
|
||||
/// Swap the window container with the window container at the edge of the adjacent monitor
|
||||
Swap,
|
||||
/// Insert the window container into the focused workspace on the adjacent monitor
|
||||
Insert,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum HidingBehaviour {
|
||||
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
||||
Hide,
|
||||
/// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
|
||||
Minimize,
|
||||
/// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces (has foregrounding issues)
|
||||
Cloak,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum OperationBehaviour {
|
||||
/// Process komorebic commands on temporarily unmanaged/floated windows
|
||||
Op,
|
||||
/// Ignore komorebic commands on temporarily unmanaged/floated windows
|
||||
NoOp,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Sizing {
|
||||
Increase,
|
||||
@@ -127,8 +274,8 @@ impl Sizing {
|
||||
#[must_use]
|
||||
pub const fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {
|
||||
match self {
|
||||
Sizing::Increase => value + adjustment,
|
||||
Sizing::Decrease => {
|
||||
Self::Increase => value + adjustment,
|
||||
Self::Decrease => {
|
||||
if value > 0 && value - adjustment >= 0 {
|
||||
value - adjustment
|
||||
} else {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
use clap::ArgEnum;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::Flip;
|
||||
use crate::Layout;
|
||||
use crate::direction::Direction;
|
||||
use crate::Axis;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum OperationDirection {
|
||||
Left,
|
||||
@@ -27,92 +32,35 @@ impl OperationDirection {
|
||||
}
|
||||
}
|
||||
|
||||
fn flip_direction(direction: Self, layout_flip: Option<Flip>) -> Self {
|
||||
layout_flip.map_or(direction, |flip| match direction {
|
||||
fn flip(self, layout_flip: Option<Axis>) -> Self {
|
||||
layout_flip.map_or(self, |flip| match self {
|
||||
Self::Left => match flip {
|
||||
Flip::Horizontal | Flip::HorizontalAndVertical => Self::Right,
|
||||
Flip::Vertical => direction,
|
||||
Axis::Horizontal | Axis::HorizontalAndVertical => Self::Right,
|
||||
Axis::Vertical => self,
|
||||
},
|
||||
Self::Right => match flip {
|
||||
Flip::Horizontal | Flip::HorizontalAndVertical => Self::Left,
|
||||
Flip::Vertical => direction,
|
||||
Axis::Horizontal | Axis::HorizontalAndVertical => Self::Left,
|
||||
Axis::Vertical => self,
|
||||
},
|
||||
Self::Up => match flip {
|
||||
Flip::Vertical | Flip::HorizontalAndVertical => Self::Down,
|
||||
Flip::Horizontal => direction,
|
||||
Axis::Vertical | Axis::HorizontalAndVertical => Self::Down,
|
||||
Axis::Horizontal => self,
|
||||
},
|
||||
Self::Down => match flip {
|
||||
Flip::Vertical | Flip::HorizontalAndVertical => Self::Up,
|
||||
Flip::Horizontal => direction,
|
||||
Axis::Vertical | Axis::HorizontalAndVertical => Self::Up,
|
||||
Axis::Horizontal => self,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid(
|
||||
pub fn destination(
|
||||
self,
|
||||
layout: Layout,
|
||||
layout_flip: Option<Flip>,
|
||||
layout: &dyn Direction,
|
||||
layout_flip: Option<Axis>,
|
||||
idx: usize,
|
||||
len: usize,
|
||||
) -> bool {
|
||||
match Self::flip_direction(self, layout_flip) {
|
||||
OperationDirection::Up => match layout {
|
||||
Layout::BSP => len > 2 && idx != 0 && idx != 1,
|
||||
Layout::Columns => false,
|
||||
Layout::Rows => idx != 0,
|
||||
},
|
||||
OperationDirection::Down => match layout {
|
||||
Layout::BSP => len > 2 && idx != len - 1 && idx % 2 != 0,
|
||||
Layout::Columns => false,
|
||||
Layout::Rows => idx != len - 1,
|
||||
},
|
||||
OperationDirection::Left => match layout {
|
||||
Layout::BSP => len > 1 && idx != 0,
|
||||
Layout::Columns => idx != 0,
|
||||
Layout::Rows => false,
|
||||
},
|
||||
OperationDirection::Right => match layout {
|
||||
Layout::BSP => len > 1 && idx % 2 == 0 && idx != len - 1,
|
||||
Layout::Columns => idx != len - 1,
|
||||
Layout::Rows => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_idx(self, layout: Layout, layout_flip: Option<Flip>, idx: usize) -> usize {
|
||||
match Self::flip_direction(self, layout_flip) {
|
||||
Self::Up => match layout {
|
||||
Layout::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 1
|
||||
} else {
|
||||
idx - 2
|
||||
}
|
||||
}
|
||||
Layout::Columns => unreachable!(),
|
||||
Layout::Rows => idx - 1,
|
||||
},
|
||||
Self::Down => match layout {
|
||||
Layout::BSP | Layout::Rows => idx + 1,
|
||||
Layout::Columns => unreachable!(),
|
||||
},
|
||||
Self::Left => match layout {
|
||||
Layout::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 2
|
||||
} else {
|
||||
idx - 1
|
||||
}
|
||||
}
|
||||
Layout::Columns => idx - 1,
|
||||
Layout::Rows => unreachable!(),
|
||||
},
|
||||
Self::Right => match layout {
|
||||
Layout::BSP | Layout::Columns => idx + 1,
|
||||
Layout::Rows => unreachable!(),
|
||||
},
|
||||
}
|
||||
len: NonZeroUsize,
|
||||
) -> Option<usize> {
|
||||
layout.index_in_direction(self.flip(layout_flip), idx, len.get())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||
pub struct Rect {
|
||||
/// The left point in a Win32 Rect
|
||||
pub left: i32,
|
||||
/// The top point in a Win32 Rect
|
||||
pub top: i32,
|
||||
/// The right point in a Win32 Rect
|
||||
pub right: i32,
|
||||
/// The bottom point in a Win32 Rect
|
||||
pub bottom: i32,
|
||||
}
|
||||
|
||||
|
||||
25
komorebi.example.json
Normal file
25
komorebi.example.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
|
||||
"window_hiding_behaviour": "Cloak",
|
||||
"cross_monitor_move_behaviour": "Insert",
|
||||
"alt_focus_hack": true,
|
||||
"default_workspace_padding": 20,
|
||||
"default_container_padding": 20,
|
||||
"active_window_border": false,
|
||||
"active_window_border_colours": {
|
||||
"single": { "r": 66, "g": 165, "b": 245 },
|
||||
"stack": { "r": 256, "g": 165, "b": 66 },
|
||||
"monocle": { "r": 255, "g": 51, "b": 153 }
|
||||
},
|
||||
"monitors": [
|
||||
{
|
||||
"workspaces": [
|
||||
{ "name": "I", "layout": "BSP" },
|
||||
{ "name": "II", "layout": "VerticalStack" },
|
||||
{ "name": "III", "layout": "HorizontalStack" },
|
||||
{ "name": "IV", "layout": "UltrawideVerticalStack" },
|
||||
{ "name": "V", "layout": "Rows" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
522
komorebi.generated.ahk
Normal file
522
komorebi.generated.ahk
Normal file
@@ -0,0 +1,522 @@
|
||||
; Generated by komorebic.exe
|
||||
|
||||
; 1Password
|
||||
RunWait('komorebic.exe float-rule exe "1Password.exe"', , "Hide")
|
||||
|
||||
; Ableton Live
|
||||
; Targets VST2 windows
|
||||
RunWait('komorebic.exe float-rule class "AbletonVstPlugClass"', , "Hide")
|
||||
; Targets VST3 windows
|
||||
RunWait('komorebic.exe float-rule class "Vst3PlugWindow"', , "Hide")
|
||||
|
||||
; Adobe Creative Cloud
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application class "CreativeCloudDesktopWindowClass"', , "Hide")
|
||||
|
||||
; Adobe Photoshop
|
||||
RunWait('komorebic.exe identify-border-overflow-application class "Photoshop"', , "Hide")
|
||||
|
||||
; Affinity Photo 2
|
||||
RunWait('komorebic.exe manage-rule title "Affinity Photo 2"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule exe "Photo.exe"', , "Hide")
|
||||
|
||||
; Akiflow
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Akiflow.exe"', , "Hide")
|
||||
|
||||
; Android Studio
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "studio64.exe"', , "Hide")
|
||||
|
||||
; ArmCord
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "ArmCord.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ArmCord.exe"', , "Hide")
|
||||
|
||||
; AutoHotkey
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "AutoHotkeyU64.exe"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule title "Window Spy"', , "Hide")
|
||||
|
||||
; Beeper
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Beeper.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Beeper.exe"', , "Hide")
|
||||
|
||||
; Bitwarden
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Bitwarden.exe"', , "Hide")
|
||||
|
||||
; Bloxstrap
|
||||
RunWait('komorebic.exe float-rule exe "Bloxstrap.exe"', , "Hide")
|
||||
|
||||
; Calculator
|
||||
RunWait('komorebic.exe float-rule title "Calculator"', , "Hide")
|
||||
|
||||
; Credential Manager UI Host
|
||||
; Targets the Windows popup prompting you for a PIN instead of a password on 1Password etc.
|
||||
RunWait('komorebic.exe float-rule exe "CredentialUIBroker.exe"', , "Hide")
|
||||
|
||||
; Cron
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Cron.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Cron.exe"', , "Hide")
|
||||
|
||||
; DS4Windows
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "DS4Windows.exe"', , "Hide")
|
||||
|
||||
; Delphi applications
|
||||
; Target hidden window spawned by Delphi applications
|
||||
RunWait('komorebic.exe float-rule class "TApplication"', , "Hide")
|
||||
; Target Inno Setup installers
|
||||
RunWait('komorebic.exe float-rule class "TWizardForm"', , "Hide")
|
||||
|
||||
; Discord
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Discord.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Discord.exe"', , "Hide")
|
||||
|
||||
; DiscordCanary
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "DiscordCanary.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "DiscordCanary.exe"', , "Hide")
|
||||
|
||||
; DiscordDevelopment
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "DiscordDevelopment.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "DiscordDevelopment.exe"', , "Hide")
|
||||
|
||||
; DiscordPTB
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "DiscordPTB.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "DiscordPTB.exe"', , "Hide")
|
||||
|
||||
; Dropbox
|
||||
RunWait('komorebic.exe float-rule exe "Dropbox.exe"', , "Hide")
|
||||
|
||||
; ElectronMail
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ElectronMail.exe"', , "Hide")
|
||||
|
||||
; Element
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Element.exe"', , "Hide")
|
||||
|
||||
; Elephicon
|
||||
RunWait('komorebic.exe float-rule exe "Elephicon.exe"', , "Hide")
|
||||
|
||||
; ElevenClock
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ElevenClock.exe"', , "Hide")
|
||||
|
||||
; Elgato Camera Hub
|
||||
RunWait('komorebic.exe float-rule exe "Camera Hub.exe"', , "Hide")
|
||||
|
||||
; Elgato Control Center
|
||||
RunWait('komorebic.exe float-rule exe "ControlCenter.exe"', , "Hide")
|
||||
|
||||
; Elgato Wave Link
|
||||
RunWait('komorebic.exe float-rule exe "WaveLink.exe"', , "Hide")
|
||||
|
||||
; Epic Games Launcher
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "EpicGamesLauncher.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "EpicGamesLauncher.exe"', , "Hide")
|
||||
|
||||
; Flow Launcher
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Flow.Launcher.exe"', , "Hide")
|
||||
|
||||
; GOG Galaxy
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "GalaxyClient.exe"', , "Hide")
|
||||
RunWait('komorebic.exe manage-rule exe "GalaxyClient.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "GalaxyClient.exe"', , "Hide")
|
||||
; Targets a hidden window spawned by GOG Galaxy
|
||||
RunWait('komorebic.exe float-rule class "Chrome_RenderWidgetHostHWND"', , "Hide")
|
||||
|
||||
; GoPro Webcam
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application class "GoPro Webcam"', , "Hide")
|
||||
|
||||
; Godot Manager
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "GodotManager.exe"', , "Hide")
|
||||
RunWait('komorebic.exe manage-rule exe "GodotManager.exe"', , "Hide")
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "GodotManager.exe"', , "Hide")
|
||||
|
||||
; Google Chrome
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "chrome.exe"', , "Hide")
|
||||
|
||||
; Google Drive
|
||||
RunWait('komorebic.exe float-rule exe "GoogleDriveFS.exe"', , "Hide")
|
||||
|
||||
; Houdoku
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Houdoku.exe"', , "Hide")
|
||||
|
||||
; IntelliJ IDEA
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "idea64.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "idea64.exe"', , "Hide")
|
||||
; Targets JetBrains IDE popups and floating windows
|
||||
RunWait('komorebic.exe float-rule class "SunAwtDialog"', , "Hide")
|
||||
|
||||
; Itch.io
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "itch.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "itch.exe"', , "Hide")
|
||||
|
||||
; Keyviz
|
||||
RunWait('komorebic.exe float-rule exe "keyviz.exe"', , "Hide")
|
||||
|
||||
; Kleopatra
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "kleopatra.exe"', , "Hide")
|
||||
|
||||
; Kotatogram
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Kotatogram.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Kotatogram.exe"', , "Hide")
|
||||
|
||||
; LocalSend
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "localsend_app.exe"', , "Hide")
|
||||
|
||||
; Logi Bolt
|
||||
RunWait('komorebic.exe float-rule exe "LogiBolt.exe"', , "Hide")
|
||||
|
||||
; LogiTune
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "LogiTune.exe"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule exe "LogiTune.exe"', , "Hide")
|
||||
|
||||
; Logitech G HUB
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "lghub.exe"', , "Hide")
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "lghub.exe"', , "Hide")
|
||||
|
||||
; Logitech Options
|
||||
RunWait('komorebic.exe float-rule exe "LogiOptionsUI.exe"', , "Hide")
|
||||
|
||||
; Mailspring
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "mailspring.exe"', , "Hide")
|
||||
|
||||
; ManyCam
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "ManyCam.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ManyCam.exe"', , "Hide")
|
||||
|
||||
; Mica For Everyone
|
||||
|
||||
; Microsoft Excel
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "EXCEL.EXE"', , "Hide")
|
||||
RunWait('komorebic.exe identify-layered-application exe "EXCEL.EXE"', , "Hide")
|
||||
; Targets a hidden window spawned by Microsoft Office applications
|
||||
RunWait('komorebic.exe float-rule class "_WwB"', , "Hide")
|
||||
|
||||
; Microsoft Outlook
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "OUTLOOK.EXE"', , "Hide")
|
||||
RunWait('komorebic.exe identify-layered-application exe "OUTLOOK.EXE"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "OUTLOOK.EXE"', , "Hide")
|
||||
|
||||
; Microsoft PC Manager
|
||||
RunWait('komorebic.exe float-rule exe "MSPCManager.exe"', , "Hide")
|
||||
|
||||
; Microsoft PowerPoint
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "POWERPNT.EXE"', , "Hide")
|
||||
RunWait('komorebic.exe identify-layered-application exe "POWERPNT.EXE"', , "Hide")
|
||||
|
||||
; Microsoft Teams
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Teams.exe"', , "Hide")
|
||||
; Target Teams pop-up notification windows
|
||||
RunWait('komorebic.exe float-rule title "Microsoft Teams Notification"', , "Hide")
|
||||
; Target Teams call in progress windows
|
||||
RunWait('komorebic.exe float-rule title "Microsoft Teams Call"', , "Hide")
|
||||
|
||||
; Microsoft Word
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "WINWORD.EXE"', , "Hide")
|
||||
RunWait('komorebic.exe identify-layered-application exe "WINWORD.EXE"', , "Hide")
|
||||
|
||||
; Modern Flyouts
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ModernFlyoutsHost.exe"', , "Hide")
|
||||
|
||||
; Mozilla Firefox
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "firefox.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "firefox.exe"', , "Hide")
|
||||
; Targets invisible windows spawned by Firefox to show tab previews in the taskbar
|
||||
RunWait('komorebic.exe float-rule class "MozillaTaskbarPreviewClass"', , "Hide")
|
||||
|
||||
; NVIDIA GeForce Experience
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "NVIDIA GeForce Experience.exe"', , "Hide")
|
||||
|
||||
; NZXT CAM
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "NZXT CAM.exe"', , "Hide")
|
||||
|
||||
; NiceHash Miner
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "nhm_app.exe"', , "Hide")
|
||||
RunWait('komorebic.exe manage-rule exe "nhm_app.exe"', , "Hide")
|
||||
|
||||
; NohBoard
|
||||
RunWait('komorebic.exe float-rule exe "NohBoard.exe"', , "Hide")
|
||||
|
||||
; Notion Enhanced
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Notion Enhanced.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Notion Enhanced.exe"', , "Hide")
|
||||
|
||||
; OBS Studio (32-bit)
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "obs32.exe"', , "Hide")
|
||||
|
||||
; OBS Studio (64-bit)
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "obs64.exe"', , "Hide")
|
||||
|
||||
; ONLYOFFICE Editors
|
||||
RunWait('komorebic.exe identify-border-overflow-application class "DocEditorsWindowClass"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application class "DocEditorsWindowClass"', , "Hide")
|
||||
|
||||
; Obsidian
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Obsidian.exe"', , "Hide")
|
||||
RunWait('komorebic.exe manage-rule exe "Obsidian.exe"', , "Hide")
|
||||
|
||||
; OneDrive
|
||||
RunWait('komorebic.exe float-rule class "OneDriveReactNativeWin32WindowClass"', , "Hide")
|
||||
|
||||
; OpenRGB
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "OpenRGB.exe"', , "Hide")
|
||||
|
||||
; Paradox Launcher
|
||||
RunWait('komorebic.exe float-rule exe "Paradox Launcher.exe"', , "Hide")
|
||||
|
||||
; Plexamp
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Plexamp.exe"', , "Hide")
|
||||
|
||||
; PowerToys
|
||||
; Target color picker dialog
|
||||
RunWait('komorebic.exe float-rule exe "PowerToys.ColorPickerUI.exe"', , "Hide")
|
||||
; Target image resizer dialog
|
||||
RunWait('komorebic.exe float-rule exe "PowerToys.ImageResizer.exe"', , "Hide")
|
||||
; Target Peek popup
|
||||
RunWait('komorebic.exe float-rule exe "PowerToys.Peek.UI.exe"', , "Hide")
|
||||
|
||||
; Process Hacker
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ProcessHacker.exe"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule exe "ProcessHacker.exe"', , "Hide")
|
||||
|
||||
; ProtonVPN
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "ProtonVPN.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ProtonVPN.exe"', , "Hide")
|
||||
|
||||
; PyCharm
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "pycharm64.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "pycharm64.exe"', , "Hide")
|
||||
|
||||
; QtScrcpy
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "QtScrcpy.exe"', , "Hide")
|
||||
|
||||
; QuickLook
|
||||
RunWait('komorebic.exe float-rule exe "QuickLook.exe"', , "Hide")
|
||||
|
||||
; RepoZ
|
||||
RunWait('komorebic.exe float-rule exe "RepoZ.exe"', , "Hide")
|
||||
|
||||
; Rider
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "rider64.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "rider64.exe"', , "Hide")
|
||||
|
||||
; Roblox FPS Unlocker
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "rbxfpsunlocker.exe"', , "Hide")
|
||||
|
||||
; RoundedTB
|
||||
RunWait('komorebic.exe float-rule exe "RoundedTB.exe"', , "Hide")
|
||||
|
||||
; RoundedTB
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "RoundedTB.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "RoundedTB.exe"', , "Hide")
|
||||
|
||||
; ShareX
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "ShareX.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ShareX.exe"', , "Hide")
|
||||
|
||||
; Sideloadly
|
||||
RunWait('komorebic.exe float-rule exe "sideloadly.exe"', , "Hide")
|
||||
|
||||
; Signal
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "signal.exe"', , "Hide")
|
||||
|
||||
; SiriKali
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "sirikali.exe"', , "Hide")
|
||||
|
||||
; Slack
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Slack.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Slack.exe"', , "Hide")
|
||||
|
||||
; Slack
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "slack.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "slack.exe"', , "Hide")
|
||||
|
||||
; Smart Install Maker
|
||||
; Target hidden window spawned by installer
|
||||
RunWait('komorebic.exe float-rule class "obj_App"', , "Hide")
|
||||
; Target installer
|
||||
RunWait('komorebic.exe float-rule class "obj_Form"', , "Hide")
|
||||
|
||||
; SoulseekQt
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "SoulseekQt.exe"', , "Hide")
|
||||
|
||||
; Spotify
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Spotify.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Spotify.exe"', , "Hide")
|
||||
|
||||
; Steam
|
||||
RunWait('komorebic.exe identify-border-overflow-application class "vguiPopupWindow"', , "Hide")
|
||||
|
||||
; Steam Beta
|
||||
RunWait('komorebic.exe identify-border-overflow-application class "SDL_app"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application class "SDL_app"', , "Hide")
|
||||
; Target notification toast popups
|
||||
RunWait('komorebic.exe float-rule title "notificationtoasts_"', , "Hide")
|
||||
|
||||
; Stremio
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "stremio.exe"', , "Hide")
|
||||
|
||||
; System Informer
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "SystemInformer.exe"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule exe "SystemInformer.exe"', , "Hide")
|
||||
|
||||
; SystemSettings
|
||||
RunWait('komorebic.exe float-rule class "Shell_Dialog"', , "Hide")
|
||||
|
||||
; Task Manager
|
||||
RunWait('komorebic.exe float-rule class "TaskManagerWindow"', , "Hide")
|
||||
|
||||
; Telegram
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Telegram.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Telegram.exe"', , "Hide")
|
||||
|
||||
; TickTick
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "TickTick.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "TickTick.exe"', , "Hide")
|
||||
|
||||
; TouchCursor
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "tcconfig.exe"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule exe "tcconfig.exe"', , "Hide")
|
||||
|
||||
; TranslucentTB
|
||||
RunWait('komorebic.exe float-rule exe "TranslucentTB.exe"', , "Hide")
|
||||
|
||||
; TranslucentTB
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "TranslucentTB.exe"', , "Hide")
|
||||
|
||||
; Unity Hub
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "Unity Hub.exe"', , "Hide")
|
||||
|
||||
; Unreal Editor
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "UnrealEditor.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "UnrealEditor.exe"', , "Hide")
|
||||
|
||||
; VRCX
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "VRCX.exe"', , "Hide")
|
||||
|
||||
; Visual Studio
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "devenv.exe"', , "Hide")
|
||||
|
||||
; Visual Studio Code
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Code.exe"', , "Hide")
|
||||
|
||||
; Visual Studio Code - Insiders
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "Code - Insiders.exe"', , "Hide")
|
||||
|
||||
; Voice.ai
|
||||
RunWait('komorebic.exe identify-border-overflow-application exe "VoiceAI.exe"', , "Hide")
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "VoiceAI.exe"', , "Hide")
|
||||
|
||||
; WebTorrent Desktop
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "WebTorrent.exe"', , "Hide")
|
||||
|
||||
; WinZip (32-bit)
|
||||
RunWait('komorebic.exe float-rule exe "winzip32.exe"', , "Hide")
|
||||
|
||||
; WinZip (64-bit)
|
||||
RunWait('komorebic.exe float-rule exe "winzip64.exe"', , "Hide")
|
||||
|
||||
; Windows Console (conhost.exe)
|
||||
RunWait('komorebic.exe manage-rule class "ConsoleWindowClass"', , "Hide")
|
||||
|
||||
; Windows Explorer
|
||||
; Targets copy/move operation windows
|
||||
RunWait('komorebic.exe float-rule class "OperationStatusWindow"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule title "Control Panel"', , "Hide")
|
||||
|
||||
; Windows Installer
|
||||
RunWait('komorebic.exe float-rule exe "msiexec.exe"', , "Hide")
|
||||
|
||||
; WingetUI
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "WingetUI.exe"', , "Hide")
|
||||
|
||||
; WingetUI
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "wingetui.exe"', , "Hide")
|
||||
|
||||
; Wox
|
||||
; Targets a hidden window spawned by Wox
|
||||
RunWait('komorebic.exe float-rule title "Hotkey sink"', , "Hide")
|
||||
|
||||
; XAMPP Control Panel
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "xampp-control.exe"', , "Hide")
|
||||
|
||||
; Zoom
|
||||
RunWait('komorebic.exe float-rule exe "Zoom.exe"', , "Hide")
|
||||
|
||||
; mpv.net
|
||||
RunWait('komorebic.exe identify-object-name-change-application exe "mpvnet.exe"', , "Hide")
|
||||
|
||||
; paint.net
|
||||
RunWait('komorebic.exe float-rule exe "paintdotnet.exe"', , "Hide")
|
||||
|
||||
; pinentry
|
||||
RunWait('komorebic.exe float-rule exe "pinentry.exe"', , "Hide")
|
||||
|
||||
; qBittorrent
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "qbittorrent.exe"', , "Hide")
|
||||
|
||||
; ueli
|
||||
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
RunWait('komorebic.exe identify-tray-application exe "ueli.exe"', , "Hide")
|
||||
RunWait('komorebic.exe float-rule exe "ueli.exe"', , "Hide")
|
||||
522
komorebi.generated.ps1
Normal file
522
komorebi.generated.ps1
Normal file
@@ -0,0 +1,522 @@
|
||||
# Generated by komorebic.exe
|
||||
|
||||
# 1Password
|
||||
komorebic.exe float-rule exe "1Password.exe"
|
||||
|
||||
# Ableton Live
|
||||
# Targets VST2 windows
|
||||
komorebic.exe float-rule class "AbletonVstPlugClass"
|
||||
# Targets VST3 windows
|
||||
komorebic.exe float-rule class "Vst3PlugWindow"
|
||||
|
||||
# Adobe Creative Cloud
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application class "CreativeCloudDesktopWindowClass"
|
||||
|
||||
# Adobe Photoshop
|
||||
komorebic.exe identify-border-overflow-application class "Photoshop"
|
||||
|
||||
# Affinity Photo 2
|
||||
komorebic.exe manage-rule title "Affinity Photo 2"
|
||||
komorebic.exe float-rule exe "Photo.exe"
|
||||
|
||||
# Akiflow
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Akiflow.exe"
|
||||
|
||||
# Android Studio
|
||||
komorebic.exe identify-object-name-change-application exe "studio64.exe"
|
||||
|
||||
# ArmCord
|
||||
komorebic.exe identify-border-overflow-application exe "ArmCord.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ArmCord.exe"
|
||||
|
||||
# AutoHotkey
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "AutoHotkeyU64.exe"
|
||||
komorebic.exe float-rule title "Window Spy"
|
||||
|
||||
# Beeper
|
||||
komorebic.exe identify-border-overflow-application exe "Beeper.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Beeper.exe"
|
||||
|
||||
# Bitwarden
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Bitwarden.exe"
|
||||
|
||||
# Bloxstrap
|
||||
komorebic.exe float-rule exe "Bloxstrap.exe"
|
||||
|
||||
# Calculator
|
||||
komorebic.exe float-rule title "Calculator"
|
||||
|
||||
# Credential Manager UI Host
|
||||
# Targets the Windows popup prompting you for a PIN instead of a password on 1Password etc.
|
||||
komorebic.exe float-rule exe "CredentialUIBroker.exe"
|
||||
|
||||
# Cron
|
||||
komorebic.exe identify-border-overflow-application exe "Cron.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Cron.exe"
|
||||
|
||||
# DS4Windows
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "DS4Windows.exe"
|
||||
|
||||
# Delphi applications
|
||||
# Target hidden window spawned by Delphi applications
|
||||
komorebic.exe float-rule class "TApplication"
|
||||
# Target Inno Setup installers
|
||||
komorebic.exe float-rule class "TWizardForm"
|
||||
|
||||
# Discord
|
||||
komorebic.exe identify-border-overflow-application exe "Discord.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Discord.exe"
|
||||
|
||||
# DiscordCanary
|
||||
komorebic.exe identify-border-overflow-application exe "DiscordCanary.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "DiscordCanary.exe"
|
||||
|
||||
# DiscordDevelopment
|
||||
komorebic.exe identify-border-overflow-application exe "DiscordDevelopment.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "DiscordDevelopment.exe"
|
||||
|
||||
# DiscordPTB
|
||||
komorebic.exe identify-border-overflow-application exe "DiscordPTB.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "DiscordPTB.exe"
|
||||
|
||||
# Dropbox
|
||||
komorebic.exe float-rule exe "Dropbox.exe"
|
||||
|
||||
# ElectronMail
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ElectronMail.exe"
|
||||
|
||||
# Element
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Element.exe"
|
||||
|
||||
# Elephicon
|
||||
komorebic.exe float-rule exe "Elephicon.exe"
|
||||
|
||||
# ElevenClock
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ElevenClock.exe"
|
||||
|
||||
# Elgato Camera Hub
|
||||
komorebic.exe float-rule exe "Camera Hub.exe"
|
||||
|
||||
# Elgato Control Center
|
||||
komorebic.exe float-rule exe "ControlCenter.exe"
|
||||
|
||||
# Elgato Wave Link
|
||||
komorebic.exe float-rule exe "WaveLink.exe"
|
||||
|
||||
# Epic Games Launcher
|
||||
komorebic.exe identify-border-overflow-application exe "EpicGamesLauncher.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "EpicGamesLauncher.exe"
|
||||
|
||||
# Flow Launcher
|
||||
komorebic.exe identify-border-overflow-application exe "Flow.Launcher.exe"
|
||||
|
||||
# GOG Galaxy
|
||||
komorebic.exe identify-border-overflow-application exe "GalaxyClient.exe"
|
||||
komorebic.exe manage-rule exe "GalaxyClient.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "GalaxyClient.exe"
|
||||
# Targets a hidden window spawned by GOG Galaxy
|
||||
komorebic.exe float-rule class "Chrome_RenderWidgetHostHWND"
|
||||
|
||||
# GoPro Webcam
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application class "GoPro Webcam"
|
||||
|
||||
# Godot Manager
|
||||
komorebic.exe identify-border-overflow-application exe "GodotManager.exe"
|
||||
komorebic.exe manage-rule exe "GodotManager.exe"
|
||||
komorebic.exe identify-object-name-change-application exe "GodotManager.exe"
|
||||
|
||||
# Google Chrome
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "chrome.exe"
|
||||
|
||||
# Google Drive
|
||||
komorebic.exe float-rule exe "GoogleDriveFS.exe"
|
||||
|
||||
# Houdoku
|
||||
komorebic.exe identify-border-overflow-application exe "Houdoku.exe"
|
||||
|
||||
# IntelliJ IDEA
|
||||
komorebic.exe identify-object-name-change-application exe "idea64.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "idea64.exe"
|
||||
# Targets JetBrains IDE popups and floating windows
|
||||
komorebic.exe float-rule class "SunAwtDialog"
|
||||
|
||||
# Itch.io
|
||||
komorebic.exe identify-border-overflow-application exe "itch.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "itch.exe"
|
||||
|
||||
# Keyviz
|
||||
komorebic.exe float-rule exe "keyviz.exe"
|
||||
|
||||
# Kleopatra
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "kleopatra.exe"
|
||||
|
||||
# Kotatogram
|
||||
komorebic.exe identify-border-overflow-application exe "Kotatogram.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Kotatogram.exe"
|
||||
|
||||
# LocalSend
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "localsend_app.exe"
|
||||
|
||||
# Logi Bolt
|
||||
komorebic.exe float-rule exe "LogiBolt.exe"
|
||||
|
||||
# LogiTune
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "LogiTune.exe"
|
||||
komorebic.exe float-rule exe "LogiTune.exe"
|
||||
|
||||
# Logitech G HUB
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "lghub.exe"
|
||||
komorebic.exe identify-border-overflow-application exe "lghub.exe"
|
||||
|
||||
# Logitech Options
|
||||
komorebic.exe float-rule exe "LogiOptionsUI.exe"
|
||||
|
||||
# Mailspring
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "mailspring.exe"
|
||||
|
||||
# ManyCam
|
||||
komorebic.exe identify-border-overflow-application exe "ManyCam.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ManyCam.exe"
|
||||
|
||||
# Mica For Everyone
|
||||
|
||||
# Microsoft Excel
|
||||
komorebic.exe identify-border-overflow-application exe "EXCEL.EXE"
|
||||
komorebic.exe identify-layered-application exe "EXCEL.EXE"
|
||||
# Targets a hidden window spawned by Microsoft Office applications
|
||||
komorebic.exe float-rule class "_WwB"
|
||||
|
||||
# Microsoft Outlook
|
||||
komorebic.exe identify-border-overflow-application exe "OUTLOOK.EXE"
|
||||
komorebic.exe identify-layered-application exe "OUTLOOK.EXE"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "OUTLOOK.EXE"
|
||||
|
||||
# Microsoft PC Manager
|
||||
komorebic.exe float-rule exe "MSPCManager.exe"
|
||||
|
||||
# Microsoft PowerPoint
|
||||
komorebic.exe identify-border-overflow-application exe "POWERPNT.EXE"
|
||||
komorebic.exe identify-layered-application exe "POWERPNT.EXE"
|
||||
|
||||
# Microsoft Teams
|
||||
komorebic.exe identify-border-overflow-application exe "Teams.exe"
|
||||
# Target Teams pop-up notification windows
|
||||
komorebic.exe float-rule title "Microsoft Teams Notification"
|
||||
# Target Teams call in progress windows
|
||||
komorebic.exe float-rule title "Microsoft Teams Call"
|
||||
|
||||
# Microsoft Word
|
||||
komorebic.exe identify-border-overflow-application exe "WINWORD.EXE"
|
||||
komorebic.exe identify-layered-application exe "WINWORD.EXE"
|
||||
|
||||
# Modern Flyouts
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ModernFlyoutsHost.exe"
|
||||
|
||||
# Mozilla Firefox
|
||||
komorebic.exe identify-object-name-change-application exe "firefox.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "firefox.exe"
|
||||
# Targets invisible windows spawned by Firefox to show tab previews in the taskbar
|
||||
komorebic.exe float-rule class "MozillaTaskbarPreviewClass"
|
||||
|
||||
# NVIDIA GeForce Experience
|
||||
komorebic.exe identify-border-overflow-application exe "NVIDIA GeForce Experience.exe"
|
||||
|
||||
# NZXT CAM
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "NZXT CAM.exe"
|
||||
|
||||
# NiceHash Miner
|
||||
komorebic.exe identify-border-overflow-application exe "nhm_app.exe"
|
||||
komorebic.exe manage-rule exe "nhm_app.exe"
|
||||
|
||||
# NohBoard
|
||||
komorebic.exe float-rule exe "NohBoard.exe"
|
||||
|
||||
# Notion Enhanced
|
||||
komorebic.exe identify-border-overflow-application exe "Notion Enhanced.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Notion Enhanced.exe"
|
||||
|
||||
# OBS Studio (32-bit)
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "obs32.exe"
|
||||
|
||||
# OBS Studio (64-bit)
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "obs64.exe"
|
||||
|
||||
# ONLYOFFICE Editors
|
||||
komorebic.exe identify-border-overflow-application class "DocEditorsWindowClass"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application class "DocEditorsWindowClass"
|
||||
|
||||
# Obsidian
|
||||
komorebic.exe identify-border-overflow-application exe "Obsidian.exe"
|
||||
komorebic.exe manage-rule exe "Obsidian.exe"
|
||||
|
||||
# OneDrive
|
||||
komorebic.exe float-rule class "OneDriveReactNativeWin32WindowClass"
|
||||
|
||||
# OpenRGB
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "OpenRGB.exe"
|
||||
|
||||
# Paradox Launcher
|
||||
komorebic.exe float-rule exe "Paradox Launcher.exe"
|
||||
|
||||
# Plexamp
|
||||
komorebic.exe identify-border-overflow-application exe "Plexamp.exe"
|
||||
|
||||
# PowerToys
|
||||
# Target color picker dialog
|
||||
komorebic.exe float-rule exe "PowerToys.ColorPickerUI.exe"
|
||||
# Target image resizer dialog
|
||||
komorebic.exe float-rule exe "PowerToys.ImageResizer.exe"
|
||||
# Target Peek popup
|
||||
komorebic.exe float-rule exe "PowerToys.Peek.UI.exe"
|
||||
|
||||
# Process Hacker
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ProcessHacker.exe"
|
||||
komorebic.exe float-rule exe "ProcessHacker.exe"
|
||||
|
||||
# ProtonVPN
|
||||
komorebic.exe identify-border-overflow-application exe "ProtonVPN.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ProtonVPN.exe"
|
||||
|
||||
# PyCharm
|
||||
komorebic.exe identify-object-name-change-application exe "pycharm64.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "pycharm64.exe"
|
||||
|
||||
# QtScrcpy
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "QtScrcpy.exe"
|
||||
|
||||
# QuickLook
|
||||
komorebic.exe float-rule exe "QuickLook.exe"
|
||||
|
||||
# RepoZ
|
||||
komorebic.exe float-rule exe "RepoZ.exe"
|
||||
|
||||
# Rider
|
||||
komorebic.exe identify-object-name-change-application exe "rider64.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "rider64.exe"
|
||||
|
||||
# Roblox FPS Unlocker
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "rbxfpsunlocker.exe"
|
||||
|
||||
# RoundedTB
|
||||
komorebic.exe float-rule exe "RoundedTB.exe"
|
||||
|
||||
# RoundedTB
|
||||
komorebic.exe identify-border-overflow-application exe "RoundedTB.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "RoundedTB.exe"
|
||||
|
||||
# ShareX
|
||||
komorebic.exe identify-border-overflow-application exe "ShareX.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ShareX.exe"
|
||||
|
||||
# Sideloadly
|
||||
komorebic.exe float-rule exe "sideloadly.exe"
|
||||
|
||||
# Signal
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "signal.exe"
|
||||
|
||||
# SiriKali
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "sirikali.exe"
|
||||
|
||||
# Slack
|
||||
komorebic.exe identify-border-overflow-application exe "Slack.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Slack.exe"
|
||||
|
||||
# Slack
|
||||
komorebic.exe identify-border-overflow-application exe "slack.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "slack.exe"
|
||||
|
||||
# Smart Install Maker
|
||||
# Target hidden window spawned by installer
|
||||
komorebic.exe float-rule class "obj_App"
|
||||
# Target installer
|
||||
komorebic.exe float-rule class "obj_Form"
|
||||
|
||||
# SoulseekQt
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "SoulseekQt.exe"
|
||||
|
||||
# Spotify
|
||||
komorebic.exe identify-border-overflow-application exe "Spotify.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Spotify.exe"
|
||||
|
||||
# Steam
|
||||
komorebic.exe identify-border-overflow-application class "vguiPopupWindow"
|
||||
|
||||
# Steam Beta
|
||||
komorebic.exe identify-border-overflow-application class "SDL_app"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application class "SDL_app"
|
||||
# Target notification toast popups
|
||||
komorebic.exe float-rule title "notificationtoasts_"
|
||||
|
||||
# Stremio
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "stremio.exe"
|
||||
|
||||
# System Informer
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "SystemInformer.exe"
|
||||
komorebic.exe float-rule exe "SystemInformer.exe"
|
||||
|
||||
# SystemSettings
|
||||
komorebic.exe float-rule class "Shell_Dialog"
|
||||
|
||||
# Task Manager
|
||||
komorebic.exe float-rule class "TaskManagerWindow"
|
||||
|
||||
# Telegram
|
||||
komorebic.exe identify-border-overflow-application exe "Telegram.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Telegram.exe"
|
||||
|
||||
# TickTick
|
||||
komorebic.exe identify-border-overflow-application exe "TickTick.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "TickTick.exe"
|
||||
|
||||
# TouchCursor
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "tcconfig.exe"
|
||||
komorebic.exe float-rule exe "tcconfig.exe"
|
||||
|
||||
# TranslucentTB
|
||||
komorebic.exe float-rule exe "TranslucentTB.exe"
|
||||
|
||||
# TranslucentTB
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "TranslucentTB.exe"
|
||||
|
||||
# Unity Hub
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "Unity Hub.exe"
|
||||
|
||||
# Unreal Editor
|
||||
komorebic.exe identify-border-overflow-application exe "UnrealEditor.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "UnrealEditor.exe"
|
||||
|
||||
# VRCX
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "VRCX.exe"
|
||||
|
||||
# Visual Studio
|
||||
komorebic.exe identify-object-name-change-application exe "devenv.exe"
|
||||
|
||||
# Visual Studio Code
|
||||
komorebic.exe identify-border-overflow-application exe "Code.exe"
|
||||
|
||||
# Visual Studio Code - Insiders
|
||||
komorebic.exe identify-border-overflow-application exe "Code - Insiders.exe"
|
||||
|
||||
# Voice.ai
|
||||
komorebic.exe identify-border-overflow-application exe "VoiceAI.exe"
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "VoiceAI.exe"
|
||||
|
||||
# WebTorrent Desktop
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "WebTorrent.exe"
|
||||
|
||||
# WinZip (32-bit)
|
||||
komorebic.exe float-rule exe "winzip32.exe"
|
||||
|
||||
# WinZip (64-bit)
|
||||
komorebic.exe float-rule exe "winzip64.exe"
|
||||
|
||||
# Windows Console (conhost.exe)
|
||||
komorebic.exe manage-rule class "ConsoleWindowClass"
|
||||
|
||||
# Windows Explorer
|
||||
# Targets copy/move operation windows
|
||||
komorebic.exe float-rule class "OperationStatusWindow"
|
||||
komorebic.exe float-rule title "Control Panel"
|
||||
|
||||
# Windows Installer
|
||||
komorebic.exe float-rule exe "msiexec.exe"
|
||||
|
||||
# WingetUI
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "WingetUI.exe"
|
||||
|
||||
# WingetUI
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "wingetui.exe"
|
||||
|
||||
# Wox
|
||||
# Targets a hidden window spawned by Wox
|
||||
komorebic.exe float-rule title "Hotkey sink"
|
||||
|
||||
# XAMPP Control Panel
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "xampp-control.exe"
|
||||
|
||||
# Zoom
|
||||
komorebic.exe float-rule exe "Zoom.exe"
|
||||
|
||||
# mpv.net
|
||||
komorebic.exe identify-object-name-change-application exe "mpvnet.exe"
|
||||
|
||||
# paint.net
|
||||
komorebic.exe float-rule exe "paintdotnet.exe"
|
||||
|
||||
# pinentry
|
||||
komorebic.exe float-rule exe "pinentry.exe"
|
||||
|
||||
# qBittorrent
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "qbittorrent.exe"
|
||||
|
||||
# ueli
|
||||
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
|
||||
komorebic.exe identify-tray-application exe "ueli.exe"
|
||||
komorebic.exe float-rule exe "ueli.exe"
|
||||
15
komorebi.iml
15
komorebi.iml
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUST_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/bindings/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/komorebi-core/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/komorebi/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/komorebic/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,225 +1,93 @@
|
||||
#SingleInstance Force
|
||||
|
||||
; Load library
|
||||
#Include komorebic.lib.ahk
|
||||
; Load configuration
|
||||
#Include komorebi.generated.ahk
|
||||
|
||||
; Send the ALT key whenever changing focus to force focus changes
|
||||
AltFocusHack("enable")
|
||||
; Default to cloaking windows when switching workspaces
|
||||
WindowHidingBehaviour("cloak")
|
||||
; Set cross-monitor move behaviour to insert instead of swap
|
||||
CrossMonitorMoveBehaviour("Insert")
|
||||
; Enable hot reloading of changes to this file
|
||||
Run, komorebic.exe watch-configuration enable, , Hide
|
||||
WatchConfiguration("enable")
|
||||
|
||||
; Create named workspaces I-V on monitor 0
|
||||
EnsureNamedWorkspaces(0, "I II III IV V")
|
||||
; You can do the same thing for secondary monitors too
|
||||
; EnsureNamedWorkspaces(1, "A B C D E F")
|
||||
|
||||
; Assign layouts to workspaces, possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack
|
||||
NamedWorkspaceLayout("I", "bsp")
|
||||
|
||||
; Set the gaps around the edge of the screen for a workspace
|
||||
NamedWorkspacePadding("I", 20)
|
||||
; Set the gaps between the containers for a workspace
|
||||
NamedWorkspaceContainerPadding("I", 20)
|
||||
|
||||
; You can assign specific apps to named workspaces
|
||||
; NamedWorkspaceRule("exe", "Firefox.exe", "III")
|
||||
|
||||
; Configure the invisible border dimensions
|
||||
Run, komorebic.exe invisible-borders 7 0 14 7, , Hide
|
||||
InvisibleBorders(7, 0, 14, 7)
|
||||
|
||||
; Enable focus follows mouse
|
||||
Run, komorebic.exe focus-follows-mouse enable, , Hide
|
||||
; Uncomment the next lines if you want a visual border around the active window
|
||||
; ActiveWindowBorderColour(66, 165, 245, "single")
|
||||
; ActiveWindowBorderColour(256, 165, 66, "stack")
|
||||
; ActiveWindowBorderColour(255, 51, 153, "monocle")
|
||||
|
||||
; Ensure there are 3 workspaces created on monitor 0
|
||||
Run, komorebic.exe ensure-workspaces 0 5, , Hide
|
||||
CompleteConfiguration()
|
||||
|
||||
; Give the workspaces some optional names
|
||||
Run, komorebic.exe workspace-name 0 0 bsp, , Hide
|
||||
Run, komorebic.exe workspace-name 0 1 columns, , Hide
|
||||
Run, komorebic.exe workspace-name 0 2 thicc, , Hide
|
||||
Run, komorebic.exe workspace-name 0 3 matrix, , Hide
|
||||
Run, komorebic.exe workspace-name 0 4 floaty, , Hide
|
||||
; Focus windows
|
||||
!h::Focus("left")
|
||||
!j::Focus("down")
|
||||
!k::Focus("up")
|
||||
!l::Focus("right")
|
||||
!+[::CycleFocus("previous")
|
||||
!+]::CycleFocus("next")
|
||||
|
||||
; Set the padding of the different workspaces
|
||||
Run, komorebic.exe workspace-padding 0 1 30, , Hide
|
||||
Run, komorebic.exe container-padding 0 1 30, , Hide
|
||||
Run, komorebic.exe workspace-padding 0 2 200, , Hide
|
||||
Run, komorebic.exe workspace-padding 0 3 0, , Hide
|
||||
Run, komorebic.exe container-padding 0 3 0, , Hide
|
||||
; Move windows
|
||||
!+h::Move("left")
|
||||
!+j::Move("down")
|
||||
!+k::Move("up")
|
||||
!+l::Move("right")
|
||||
!+Enter::Promote()
|
||||
|
||||
; Set the layouts of different workspaces
|
||||
Run, komorebic.exe workspace-layout 0 1 columns, , Hide
|
||||
; Stack windows
|
||||
!Left::Stack("left")
|
||||
!Right::Stack("right")
|
||||
!Up::Stack("up")
|
||||
!Down::Stack("down")
|
||||
!;::Unstack()
|
||||
![::CycleStack("previous")
|
||||
!]::CycleStack("next")
|
||||
|
||||
; Set the floaty layout to not tile any windows
|
||||
Run, komorebic.exe workspace-tiling 0 4 disable, , Hide
|
||||
; Resize
|
||||
!=::ResizeAxis("horizontal", "increase")
|
||||
!-::ResizeAxis("horizontal", "decrease")
|
||||
!+=::ResizeAxis("vertical", "increase")
|
||||
!+-::ResizeAxis("vertical", "decrease")
|
||||
|
||||
; Always show chat apps on the second workspace
|
||||
Run, komorebic.exe workspace-rule exe slack.exe 0 1, , Hide
|
||||
Run, komorebic.exe workspace-rule exe Discord.exe 0 1, , Hide
|
||||
; Manipulate windows
|
||||
!t::ToggleFloat()
|
||||
!+f::ToggleMonocle()
|
||||
|
||||
; Always float IntelliJ popups, matching on class
|
||||
Run, komorebic.exe float-rule class SunAwtDialog, , Hide
|
||||
; Always float Control Panel, matching on title
|
||||
Run, komorebic.exe float-rule title "Control Panel", , Hide
|
||||
; Always float Task Manager, matching on class
|
||||
Run, komorebic.exe float-rule class TaskManagerWindow, , Hide
|
||||
; Always float Wally, matching on executable name
|
||||
Run, komorebic.exe float-rule exe Wally.exe, , Hide
|
||||
Run, komorebic.exe float-rule exe wincompose.exe, , Hide
|
||||
; Always float Calculator app, matching on window title
|
||||
Run, komorebic.exe float-rule title Calculator, , Hide
|
||||
Run, komorebic.exe float-rule exe 1Password.exe, , Hide
|
||||
; Window manager options
|
||||
!+r::Retile()
|
||||
!p::TogglePause()
|
||||
|
||||
; Always manage forcibly these applications that don't automatically get picked up by komorebi
|
||||
Run, komorebic.exe manage-rule exe TIM.exe, , Hide
|
||||
; Layouts
|
||||
!x::FlipLayout("horizontal")
|
||||
!y::FlipLayout("vertical")
|
||||
|
||||
; Identify applications that close to the tray
|
||||
Run, komorebic.exe identify-tray-application exe Discord.exe, , Hide
|
||||
; Workspaces
|
||||
!1::FocusWorkspace(0)
|
||||
!2::FocusWorkspace(1)
|
||||
!3::FocusWorkspace(2)
|
||||
|
||||
; Identify applications that have overflowing borders
|
||||
Run, komorebic.exe identify-border-overflow exe Discord.exe, , Hide
|
||||
|
||||
; Change the focused window, Alt + Vim direction keys
|
||||
!h::
|
||||
Run, komorebic.exe focus left, , Hide
|
||||
return
|
||||
|
||||
!j::
|
||||
Run, komorebic.exe focus down, , Hide
|
||||
return
|
||||
|
||||
!k::
|
||||
Run, komorebic.exe focus up, , Hide
|
||||
return
|
||||
|
||||
!l::
|
||||
Run, komorebic.exe focus right, , Hide
|
||||
return
|
||||
|
||||
; Move the focused window in a given direction, Alt + Shift + Vim direction keys
|
||||
!+h::
|
||||
Run, komorebic.exe move left, , Hide
|
||||
return
|
||||
|
||||
!+j::
|
||||
Run, komorebic.exe move down, , Hide
|
||||
return
|
||||
|
||||
!+k::
|
||||
Run, komorebic.exe move up, , Hide
|
||||
return
|
||||
|
||||
!+l::
|
||||
Run, komorebic.exe move right, , Hide
|
||||
return
|
||||
|
||||
; Stack the focused window in a given direction, Alt + Shift + direction keys
|
||||
!+Left::
|
||||
Run, komorebic.exe stack left, , Hide
|
||||
return
|
||||
|
||||
!+Down::
|
||||
Run, komorebic.exe stack down, , Hide
|
||||
return
|
||||
|
||||
!+Up::
|
||||
Run, komorebic.exe stack up, , Hide
|
||||
return
|
||||
|
||||
!+Right::
|
||||
Run, komorebic.exe stack right, , Hide
|
||||
return
|
||||
|
||||
!]::
|
||||
Run, komorebic.exe cycle-stack next, , Hide
|
||||
return
|
||||
|
||||
![::
|
||||
Run, komorebic.exe cycle-stack previous, , Hide
|
||||
return
|
||||
|
||||
; Unstack the focused window, Alt + Shift + D
|
||||
!+d::
|
||||
Run, komorebic.exe unstack, , Hide
|
||||
return
|
||||
|
||||
; Promote the focused window to the top of the tree, Alt + Shift + Enter
|
||||
!+Enter::
|
||||
Run, komorebic.exe promote, , Hide
|
||||
return
|
||||
|
||||
; Switch to an equal-width, max-height column layout on the main workspace, Alt + Shift + C
|
||||
!+c::
|
||||
Run, komorebic.exe workspace-layout 0 0 columns, , Hide
|
||||
return
|
||||
|
||||
; Switch to the default bsp tiling layout on the main workspace, Alt + Shift + T
|
||||
!+t::
|
||||
Run, komorebic.exe workspace-layout 0 0 bsp, , Hide
|
||||
return
|
||||
|
||||
; Toggle the Monocle layout for the focused window, Alt + Shift + F
|
||||
!+f::
|
||||
Run, komorebic.exe toggle-monocle, , Hide
|
||||
return
|
||||
|
||||
; Toggle native maximize for the focused window, Alt + Shift + =
|
||||
!+=::
|
||||
Run, komorebic.exe toggle-maximize, , Hide
|
||||
return
|
||||
|
||||
; Flip horizontally, Alt + X
|
||||
!x::
|
||||
Run, komorebic.exe flip-layout horizontal, , Hide
|
||||
return
|
||||
|
||||
; Flip vertically, Alt + Y
|
||||
!y::
|
||||
Run, komorebic.exe flip-layout vertical, , Hide
|
||||
return
|
||||
|
||||
; Force a retile if things get janky, Alt + Shift + R
|
||||
!+r::
|
||||
Run, komorebic.exe retile, , Hide
|
||||
return
|
||||
|
||||
; Float the focused window, Alt + T
|
||||
!t::
|
||||
Run, komorebic.exe toggle-float, , Hide
|
||||
return
|
||||
|
||||
; Reload ~/komorebi.ahk, Alt + O
|
||||
!o::
|
||||
Run, komorebic.exe reload-configuration, , Hide
|
||||
return
|
||||
|
||||
; Pause responding to any window events or komorebic commands, Alt + P
|
||||
!p::
|
||||
Run, komorebic.exe toggle-pause, , Hide
|
||||
return
|
||||
|
||||
; Switch to workspace
|
||||
!1::
|
||||
Send !
|
||||
Run, komorebic.exe focus-workspace 0, , Hide
|
||||
return
|
||||
|
||||
!2::
|
||||
Send !
|
||||
Run, komorebic.exe focus-workspace 1, , Hide
|
||||
return
|
||||
|
||||
!3::
|
||||
Send !
|
||||
Run, komorebic.exe focus-workspace 2, , Hide
|
||||
return
|
||||
|
||||
!4::
|
||||
Send !
|
||||
Run, komorebic.exe focus-workspace 3, , Hide
|
||||
return
|
||||
|
||||
!5::
|
||||
Send !
|
||||
Run, komorebic.exe focus-workspace 4, , Hide
|
||||
return
|
||||
|
||||
; Move window to workspace
|
||||
!+1::
|
||||
Run, komorebic.exe move-to-workspace 0, , Hide
|
||||
return
|
||||
|
||||
!+2::
|
||||
Run, komorebic.exe move-to-workspace 1, , Hide
|
||||
return
|
||||
|
||||
!+3::
|
||||
Run, komorebic.exe move-to-workspace 2, , Hide
|
||||
return
|
||||
|
||||
!+4::
|
||||
Run, komorebic.exe move-to-workspace 3, , Hide
|
||||
return
|
||||
|
||||
!+5::
|
||||
Run, komorebic.exe move-to-workspace 4, , Hide
|
||||
return
|
||||
; Move windows across workspaces
|
||||
!+1::MoveToWorkspace(0)
|
||||
!+2::MoveToWorkspace(1)
|
||||
!+3::MoveToWorkspace(2)
|
||||
|
||||
42
komorebi.sample.ps1
Normal file
42
komorebi.sample.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
||||
{
|
||||
Start-Process whkd -WindowStyle hidden
|
||||
}
|
||||
|
||||
. $PSScriptRoot\komorebi.generated.ps1
|
||||
|
||||
# Send the ALT key whenever changing focus to force focus changes
|
||||
komorebic alt-focus-hack enable
|
||||
# Default to cloaking windows when switching workspaces
|
||||
komorebic window-hiding-behaviour cloak
|
||||
# Set cross-monitor move behaviour to insert instead of swap
|
||||
komorebic cross-monitor-move-behaviour insert
|
||||
# Enable hot reloading of changes to this file
|
||||
komorebic watch-configuration enable
|
||||
|
||||
# Create named workspaces I-V on monitor 0
|
||||
komorebic ensure-named-workspaces 0 I II III IV V
|
||||
# You can do the same thing for secondary monitors too
|
||||
# komorebic ensure-named-workspaces 1 A B C D E F
|
||||
|
||||
# Assign layouts to workspaces, possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack
|
||||
komorebic named-workspace-layout I bsp
|
||||
|
||||
# Set the gaps around the edge of the screen for a workspace
|
||||
komorebic named-workspace-padding I 20
|
||||
# Set the gaps between the containers for a workspace
|
||||
komorebic named-workspace-container-padding I 20
|
||||
|
||||
# You can assign specific apps to named workspaces
|
||||
# komorebic named-workspace-rule exe "Firefox.exe" III
|
||||
|
||||
# Configure the invisible border dimensions
|
||||
komorebic invisible-borders 7 0 14 7
|
||||
|
||||
# Uncomment the next lines if you want a visual border around the active window
|
||||
# komorebic active-window-border-colour 66 165 245 --window-kind single
|
||||
# komorebic active-window-border-colour 256 165 66 --window-kind stack
|
||||
# komorebic active-window-border-colour 255 51 153 --window-kind monocle
|
||||
# komorebic active-window-border enable
|
||||
|
||||
komorebic complete-configuration
|
||||
@@ -1,232 +0,0 @@
|
||||
#SingleInstance Force
|
||||
#Include %A_ScriptDir%\komorebic.lib.ahk
|
||||
|
||||
; Enable hot reloading of changes to this file
|
||||
WatchConfiguration("enable")
|
||||
|
||||
; Ensure there are 5 workspaces created on monitor 0
|
||||
EnsureWorkspaces(0, 5)
|
||||
|
||||
; Configure the invisible border dimensions
|
||||
InvisibleBorders(7, 0, 14, 7)
|
||||
|
||||
; Configure the 1st workspace
|
||||
WorkspaceName(0, 0, "bsp")
|
||||
|
||||
; Configure the 2nd workspace
|
||||
WorkspaceName(0, 1, "columns") ; Optionally set the name of the workspace
|
||||
WorkspacePadding(0, 1, 30) ; Set the padding around the edge of the screen
|
||||
ContainerPadding(0, 1, 30) ; Set the padding between the containers on the screen
|
||||
WorkspaceRule("exe", "slack.exe", 0, 1) ; Always show chat apps on this workspace
|
||||
|
||||
; Configure the 3rd workspace
|
||||
WorkspaceName(0, 2, "thicc")
|
||||
WorkspacePadding(0, 2, 200) ; Set some super thicc padding
|
||||
|
||||
; Configure the 4th workspace
|
||||
WorkspaceName(0, 3, "matrix")
|
||||
WorkspacePadding(0, 3, 0) ; No padding at all
|
||||
ContainerPadding(0, 3, 0) ; Matrix-y hacker vibes
|
||||
|
||||
; Configure the 5th workspace
|
||||
WorkspaceName(0, 4, "floaty")
|
||||
WorkspaceTiling(0, 4, "disable") ; Everything floats here
|
||||
|
||||
; Configure floating rules
|
||||
FloatRule("class", "SunAwtDialog") ; All the IntelliJ popups
|
||||
FloatRule("title", "Control Panek")
|
||||
FloatRule("class", "TaskManagerWindow")
|
||||
FloatRule("exe", "Wally.exe")
|
||||
FloatRule("exe", "wincompose.exe")
|
||||
FloatRule("exe", "1Password.exe")
|
||||
FloatRule("exe", "Wox.exe")
|
||||
|
||||
; Identify Minimize-to-Tray Applications
|
||||
IdentifyTrayApplication("exe", "Discord.exe")
|
||||
IdentifyTrayApplication("exe", "Spotify.exe")
|
||||
|
||||
; Identify Electron applications with overflowing borders
|
||||
IdentifyBorderOverflow("exe", "Discord.exe")
|
||||
IdentifyBorderOverflow("exe", "Spotify.exe")
|
||||
IdentifyBorderOverflow("class", "ZPFTEWndClass")
|
||||
|
||||
; Change the focused window, Alt + Vim direction keys
|
||||
!h::
|
||||
Focus("left")
|
||||
return
|
||||
|
||||
!j::
|
||||
Focus("down")
|
||||
return
|
||||
|
||||
!k::
|
||||
Focus("up")
|
||||
return
|
||||
|
||||
!l::
|
||||
Focus("right")
|
||||
return
|
||||
|
||||
; Move the focused window in a given direction, Alt + Shift + Vim direction keys
|
||||
!+h::
|
||||
Move("left")
|
||||
return
|
||||
|
||||
!+j::
|
||||
Move("down")
|
||||
return
|
||||
|
||||
!+k::
|
||||
Move("up")
|
||||
return
|
||||
|
||||
!+l::
|
||||
Move("right")
|
||||
return
|
||||
|
||||
; Stack the focused window in a given direction, Alt + Shift + direction keys
|
||||
!+Left::
|
||||
Stack("left")
|
||||
return
|
||||
|
||||
!+Down::
|
||||
Stack("down")
|
||||
return
|
||||
|
||||
!+Up::
|
||||
Stack("up")
|
||||
return
|
||||
|
||||
!+Right::
|
||||
Stack("right")
|
||||
return
|
||||
|
||||
!]::
|
||||
CycleStack("next")
|
||||
return
|
||||
|
||||
![::
|
||||
CycleStack("previous")
|
||||
return
|
||||
|
||||
; Unstack the focused window, Alt + Shift + D
|
||||
!+d::
|
||||
Unstack()
|
||||
return
|
||||
|
||||
; Promote the focused window to the top of the tree, Alt + Shift + Enter
|
||||
!+Enter::
|
||||
Promote()
|
||||
return
|
||||
|
||||
; Manage the focused window
|
||||
!=::
|
||||
Manage()
|
||||
return
|
||||
|
||||
; Unmanage the focused window
|
||||
!-::
|
||||
Unmanage()
|
||||
return
|
||||
|
||||
; Switch to an equal-width, max-height column layout on the main workspace, Alt + Shift + C
|
||||
!+c::
|
||||
ChangeLayout("columns")
|
||||
return
|
||||
|
||||
; Switch to the default bsp tiling layout on the main workspace, Alt + Shift + T
|
||||
!+t::
|
||||
ChangeLayout("bsp")
|
||||
return
|
||||
|
||||
; Toggle the Monocle layout for the focused window, Alt + Shift + F
|
||||
!+f::
|
||||
ToggleMonocle()
|
||||
return
|
||||
|
||||
; Toggle native maximize for the focused window, Alt + Shift + =
|
||||
!+=::
|
||||
ToggleMaximize()
|
||||
return
|
||||
|
||||
; Flip horizontally, Alt + X
|
||||
!x::
|
||||
FlipLayout("horizontal")
|
||||
return
|
||||
|
||||
; Flip vertically, Alt + Y
|
||||
!y::
|
||||
FlipLayout("vertical")
|
||||
return
|
||||
|
||||
; Force a retile if things get janky, Alt + Shift + R
|
||||
!+r::
|
||||
Retile()
|
||||
return
|
||||
|
||||
; Float the focused window, Alt + T
|
||||
!t::
|
||||
ToggleFloat()
|
||||
return
|
||||
|
||||
; Reload ~/komorebi.ahk, Alt + O
|
||||
!o::
|
||||
ReloadConfiguration()
|
||||
return
|
||||
|
||||
; Pause responding to any window events or komorebic commands, Alt + P
|
||||
!p::
|
||||
TogglePause()
|
||||
return
|
||||
|
||||
; Enable focus follows mouse
|
||||
!0::
|
||||
ToggleFocusFollowsMouse("komorebi")
|
||||
return
|
||||
|
||||
; Switch to workspace
|
||||
!1::
|
||||
Send !
|
||||
FocusWorkspace(0)
|
||||
return
|
||||
|
||||
!2::
|
||||
Send !
|
||||
FocusWorkspace(1)
|
||||
return
|
||||
|
||||
!3::
|
||||
Send !
|
||||
FocusWorkspace(2)
|
||||
return
|
||||
|
||||
!4::
|
||||
Send !
|
||||
FocusWorkspace(3)
|
||||
return
|
||||
|
||||
!5::
|
||||
Send !
|
||||
FocusWorkspace(4)
|
||||
return
|
||||
|
||||
; Move window to workspace
|
||||
!+1::
|
||||
MoveToWorkspace(0)
|
||||
return
|
||||
|
||||
!+2::
|
||||
MoveToWorkspace(1)
|
||||
return
|
||||
|
||||
!+3::
|
||||
MoveToWorkspace(2)
|
||||
return
|
||||
|
||||
!+4::
|
||||
MoveToWorkspace(3)
|
||||
return
|
||||
|
||||
!+5::
|
||||
MoveToWorkspace(4)
|
||||
return
|
||||
@@ -1,43 +1,49 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.4"
|
||||
version = "0.1.16"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bindings = { package = "bindings", path = "../bindings" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
bitflags = "1"
|
||||
clap = "3.0.0-beta.4"
|
||||
color-eyre = "0.5"
|
||||
bitflags = "2"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
color-eyre = "0.6"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
ctrlc = "3"
|
||||
dirs = "3"
|
||||
dirs = "5"
|
||||
getset = "0.1"
|
||||
hotwatch = "0.4"
|
||||
lazy_static = "1"
|
||||
miow = "0.5"
|
||||
nanoid = "0.4"
|
||||
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
|
||||
net2 = "0.2"
|
||||
os_info = "3.7"
|
||||
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
|
||||
paste = "1"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
strum = { version = "0.21", features = ["derive"] }
|
||||
sysinfo = "0.20"
|
||||
strum = { version = "0.25", features = ["derive"] }
|
||||
sysinfo = "0.29"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.1"
|
||||
tracing-subscriber = "0.2"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uds_windows = "1"
|
||||
which = "4"
|
||||
winput = "0.2"
|
||||
winvd = "0.0.20"
|
||||
winreg = "0.50"
|
||||
windows-interface = { workspace = true }
|
||||
windows-implement = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
|
||||
[features]
|
||||
deadlock_detection = []
|
||||
|
||||
145
komorebi/src/border.rs
Normal file
145
komorebi/src/border.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::Result;
|
||||
use windows::core::PCSTR;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::FindWindowA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
|
||||
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::window::Window;
|
||||
use crate::windows_callbacks;
|
||||
use crate::WindowsApi;
|
||||
use crate::BORDER_HWND;
|
||||
use crate::BORDER_OFFSET;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::BORDER_RECT;
|
||||
use crate::TRANSPARENCY_COLOUR;
|
||||
use crate::WINDOWS_11;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Border {
|
||||
pub(crate) hwnd: isize,
|
||||
}
|
||||
|
||||
impl From<isize> for Border {
|
||||
fn from(hwnd: isize) -> Self {
|
||||
Self { hwnd }
|
||||
}
|
||||
}
|
||||
|
||||
impl Border {
|
||||
pub const fn hwnd(self) -> HWND {
|
||||
HWND(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn create(name: &str) -> Result<()> {
|
||||
let name = format!("{name}\0");
|
||||
let instance = WindowsApi::module_handle_w()?;
|
||||
let class_name = PCSTR(name.as_ptr());
|
||||
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
|
||||
let window_class = WNDCLASSA {
|
||||
hInstance: instance,
|
||||
lpszClassName: class_name,
|
||||
style: CS_HREDRAW | CS_VREDRAW,
|
||||
lpfnWndProc: Some(windows_callbacks::border_window),
|
||||
hbrBackground: brush,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let _atom = WindowsApi::register_class_a(&window_class)?;
|
||||
|
||||
let name_cl = name.clone();
|
||||
std::thread::spawn(move || -> Result<()> {
|
||||
let hwnd = WindowsApi::create_border_window(PCSTR(name_cl.as_ptr()), instance)?;
|
||||
let border = Self::from(hwnd);
|
||||
|
||||
let mut message = MSG::default();
|
||||
|
||||
unsafe {
|
||||
while GetMessageA(&mut message, border.hwnd(), 0, 0).into() {
|
||||
DispatchMessageA(&message);
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let mut hwnd = HWND(0);
|
||||
while hwnd == HWND(0) {
|
||||
hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) };
|
||||
}
|
||||
|
||||
BORDER_HWND.store(hwnd.0, Ordering::SeqCst);
|
||||
|
||||
if *WINDOWS_11 {
|
||||
WindowsApi::round_corners(hwnd.0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hide(self) -> Result<()> {
|
||||
if self.hwnd == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
WindowsApi::hide_border_window(self.hwnd())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_position(
|
||||
self,
|
||||
window: Window,
|
||||
invisible_borders: &Rect,
|
||||
activate: bool,
|
||||
) -> Result<()> {
|
||||
if self.hwnd == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
if !WindowsApi::is_window(self.hwnd()) {
|
||||
Self::create("komorebi-border-window")?;
|
||||
}
|
||||
|
||||
let mut should_expand_border = false;
|
||||
|
||||
let mut rect = WindowsApi::window_rect(window.hwnd())?;
|
||||
rect.top -= invisible_borders.bottom;
|
||||
rect.bottom += invisible_borders.bottom;
|
||||
|
||||
let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||
if border_overflows.contains(&window.title()?)
|
||||
|| border_overflows.contains(&window.exe()?)
|
||||
|| border_overflows.contains(&window.class()?)
|
||||
{
|
||||
should_expand_border = true;
|
||||
}
|
||||
|
||||
if should_expand_border {
|
||||
rect.left -= invisible_borders.left;
|
||||
rect.top -= invisible_borders.top;
|
||||
rect.right += invisible_borders.right;
|
||||
rect.bottom += invisible_borders.bottom;
|
||||
}
|
||||
|
||||
let border_offset = BORDER_OFFSET.lock();
|
||||
if let Some(border_offset) = *border_offset {
|
||||
rect.left -= border_offset.left;
|
||||
rect.top -= border_offset.top;
|
||||
rect.right += border_offset.right;
|
||||
rect.bottom += border_offset.bottom;
|
||||
}
|
||||
|
||||
*BORDER_RECT.lock() = rect;
|
||||
|
||||
WindowsApi::position_border_window(self.hwnd(), &rect, activate)
|
||||
}
|
||||
}
|
||||
}
|
||||
246
komorebi/src/com/interfaces.rs
Normal file
246
komorebi/src/com/interfaces.rs
Normal file
@@ -0,0 +1,246 @@
|
||||
// This code is largely taken verbatim from this repository: https://github.com/Ciantic/AltTabAccessor
|
||||
// which the author Jari Pennanen (Ciantic) has kindly made available with the MIT license, available
|
||||
// in full here: https://github.com/Ciantic/AltTabAccessor/blob/main/LICENSE.txt
|
||||
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ops::Deref;
|
||||
use windows::core::IUnknown;
|
||||
use windows::core::IUnknown_Vtbl;
|
||||
use windows::core::GUID;
|
||||
use windows::core::HRESULT;
|
||||
use windows::core::HSTRING;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
use windows::Win32::Foundation::SIZE;
|
||||
use windows::Win32::UI::Shell::Common::IObjectArray;
|
||||
|
||||
type DesktopID = GUID;
|
||||
|
||||
// Idea here is that the cloned ComIn instance lifetime is within the original ComIn instance lifetime
|
||||
#[repr(transparent)]
|
||||
pub struct ComIn<'a, T> {
|
||||
data: T,
|
||||
_phantom: std::marker::PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone> ComIn<'a, T> {
|
||||
pub fn new(t: &'a T) -> Self {
|
||||
Self {
|
||||
data: t.clone(),
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const unsafe fn unsafe_new_no_clone(t: T) -> Self {
|
||||
Self {
|
||||
data: t,
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for ComIn<'a, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const CLSID_ImmersiveShell: GUID = GUID {
|
||||
data1: 0xC2F0_3A33,
|
||||
data2: 0x21F5,
|
||||
data3: 0x47FA,
|
||||
data4: [0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39],
|
||||
};
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type DWORD = u32;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type INT = i32;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type LPVOID = *mut c_void;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type UINT = u32;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type ULONG = u32;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type ULONGLONG = u64;
|
||||
|
||||
type IAsyncCallback = UINT;
|
||||
type IImmersiveMonitor = UINT;
|
||||
type IApplicationViewOperation = UINT;
|
||||
type IApplicationViewPosition = UINT;
|
||||
type IImmersiveApplication = UINT;
|
||||
type IApplicationViewChangeListener = UINT;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
type APPLICATION_VIEW_COMPATIBILITY_POLICY = UINT;
|
||||
#[allow(non_camel_case_types)]
|
||||
type APPLICATION_VIEW_CLOAK_TYPE = UINT;
|
||||
|
||||
#[windows_interface::interface("6D5140C1-7436-11CE-8034-00AA006009FA")]
|
||||
pub unsafe trait IServiceProvider: IUnknown {
|
||||
pub unsafe fn query_service(
|
||||
&self,
|
||||
guid_service: *const GUID,
|
||||
riid: *const GUID,
|
||||
ppv_object: *mut *mut c_void,
|
||||
) -> HRESULT;
|
||||
}
|
||||
|
||||
#[windows_interface::interface("372E1D3B-38D3-42E4-A15B-8AB2B178F513")]
|
||||
pub unsafe trait IApplicationView: IUnknown {
|
||||
/* IInspecateble */
|
||||
pub unsafe fn get_iids(
|
||||
&self,
|
||||
out_iid_count: *mut ULONG,
|
||||
out_opt_iid_array_ptr: *mut *mut GUID,
|
||||
) -> HRESULT;
|
||||
pub unsafe fn get_runtime_class_name(&self, out_opt_class_name: *mut HSTRING) -> HRESULT;
|
||||
pub unsafe fn get_trust_level(&self, ptr_trust_level: LPVOID) -> HRESULT;
|
||||
|
||||
/* IApplicationView methods */
|
||||
pub unsafe fn set_focus(&self) -> HRESULT;
|
||||
pub unsafe fn switch_to(&self) -> HRESULT;
|
||||
|
||||
pub unsafe fn try_invoke_back(&self, ptr_async_callback: IAsyncCallback) -> HRESULT;
|
||||
pub unsafe fn get_thumbnail_window(&self, out_hwnd: *mut HWND) -> HRESULT;
|
||||
pub unsafe fn get_monitor(&self, out_monitors: *mut *mut IImmersiveMonitor) -> HRESULT;
|
||||
pub unsafe fn get_visibility(&self, out_int: LPVOID) -> HRESULT;
|
||||
pub unsafe fn set_cloak(
|
||||
&self,
|
||||
application_view_cloak_type: APPLICATION_VIEW_CLOAK_TYPE,
|
||||
unknown: INT,
|
||||
) -> HRESULT;
|
||||
pub unsafe fn get_position(
|
||||
&self,
|
||||
unknowniid: *const GUID,
|
||||
unknown_array_ptr: LPVOID,
|
||||
) -> HRESULT;
|
||||
pub unsafe fn set_position(&self, view_position: *mut IApplicationViewPosition) -> HRESULT;
|
||||
pub unsafe fn insert_after_window(&self, window: HWND) -> HRESULT;
|
||||
pub unsafe fn get_extended_frame_position(&self, rect: *mut RECT) -> HRESULT;
|
||||
pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17
|
||||
pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;
|
||||
pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)
|
||||
-> HRESULT;
|
||||
|
||||
/*** IApplicationView methods ***/
|
||||
pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20
|
||||
pub unsafe fn set_view_state(&self, state: UINT) -> HRESULT; // Proc21
|
||||
pub unsafe fn get_neediness(&self, out_neediness: *mut INT) -> HRESULT; // Proc22
|
||||
pub unsafe fn get_last_activation_timestamp(&self, out_timestamp: *mut ULONGLONG) -> HRESULT;
|
||||
pub unsafe fn set_last_activation_timestamp(&self, timestamp: ULONGLONG) -> HRESULT;
|
||||
pub unsafe fn get_virtual_desktop_id(&self, out_desktop_guid: *mut DesktopID) -> HRESULT;
|
||||
pub unsafe fn set_virtual_desktop_id(&self, desktop_guid: *const DesktopID) -> HRESULT;
|
||||
pub unsafe fn get_show_in_switchers(&self, out_show: *mut INT) -> HRESULT;
|
||||
pub unsafe fn set_show_in_switchers(&self, show: INT) -> HRESULT;
|
||||
pub unsafe fn get_scale_factor(&self, out_scale_factor: *mut INT) -> HRESULT;
|
||||
pub unsafe fn can_receive_input(&self, out_can: *mut BOOL) -> HRESULT;
|
||||
pub unsafe fn get_compatibility_policy_type(
|
||||
&self,
|
||||
out_policy_type: *mut APPLICATION_VIEW_COMPATIBILITY_POLICY,
|
||||
) -> HRESULT;
|
||||
pub unsafe fn set_compatibility_policy_type(
|
||||
&self,
|
||||
policy_type: APPLICATION_VIEW_COMPATIBILITY_POLICY,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_size_constraints(
|
||||
&self,
|
||||
monitor: *mut IImmersiveMonitor,
|
||||
out_size1: *mut SIZE,
|
||||
out_size2: *mut SIZE,
|
||||
) -> HRESULT;
|
||||
pub unsafe fn get_size_constraints_for_dpi(
|
||||
&self,
|
||||
dpi: UINT,
|
||||
out_size1: *mut SIZE,
|
||||
out_size2: *mut SIZE,
|
||||
) -> HRESULT;
|
||||
pub unsafe fn set_size_constraints_for_dpi(
|
||||
&self,
|
||||
dpi: *const UINT,
|
||||
size1: *const SIZE,
|
||||
size2: *const SIZE,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn on_min_size_preferences_updated(&self, window: HWND) -> HRESULT;
|
||||
pub unsafe fn apply_operation(&self, operation: *mut IApplicationViewOperation) -> HRESULT;
|
||||
pub unsafe fn is_tray(&self, out_is: *mut BOOL) -> HRESULT;
|
||||
pub unsafe fn is_in_high_zorder_band(&self, out_is: *mut BOOL) -> HRESULT;
|
||||
pub unsafe fn is_splash_screen_presented(&self, out_is: *mut BOOL) -> HRESULT;
|
||||
pub unsafe fn flash(&self) -> HRESULT;
|
||||
pub unsafe fn get_root_switchable_owner(&self, app_view: *mut IApplicationView) -> HRESULT; // proc45
|
||||
pub unsafe fn enumerate_ownership_tree(&self, objects: *mut IObjectArray) -> HRESULT; // proc46
|
||||
|
||||
pub unsafe fn get_enterprise_id(&self, out_id: *mut PWSTR) -> HRESULT; // proc47
|
||||
pub unsafe fn is_mirrored(&self, out_is: *mut BOOL) -> HRESULT; //
|
||||
|
||||
pub unsafe fn unknown1(&self, arg: *mut INT) -> HRESULT;
|
||||
pub unsafe fn unknown2(&self, arg: *mut INT) -> HRESULT;
|
||||
pub unsafe fn unknown3(&self, arg: *mut INT) -> HRESULT;
|
||||
pub unsafe fn unknown4(&self, arg: INT) -> HRESULT;
|
||||
pub unsafe fn unknown5(&self, arg: *mut INT) -> HRESULT;
|
||||
pub unsafe fn unknown6(&self, arg: INT) -> HRESULT;
|
||||
pub unsafe fn unknown7(&self) -> HRESULT;
|
||||
pub unsafe fn unknown8(&self, arg: *mut INT) -> HRESULT;
|
||||
pub unsafe fn unknown9(&self, arg: INT) -> HRESULT;
|
||||
pub unsafe fn unknown10(&self, arg: INT, arg2: INT) -> HRESULT;
|
||||
pub unsafe fn unknown11(&self, arg: INT) -> HRESULT;
|
||||
pub unsafe fn unknown12(&self, arg: *mut SIZE) -> HRESULT;
|
||||
}
|
||||
|
||||
#[windows_interface::interface("1841c6d7-4f9d-42c0-af41-8747538f10e5")]
|
||||
pub unsafe trait IApplicationViewCollection: IUnknown {
|
||||
pub unsafe fn get_views(&self, out_views: *mut IObjectArray) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_views_by_zorder(&self, out_views: *mut IObjectArray) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_views_by_app_user_model_id(
|
||||
&self,
|
||||
id: PCWSTR,
|
||||
out_views: *mut IObjectArray,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_view_for_hwnd(
|
||||
&self,
|
||||
window: HWND,
|
||||
out_view: *mut Option<IApplicationView>,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_view_for_application(
|
||||
&self,
|
||||
app: ComIn<IImmersiveApplication>,
|
||||
out_view: *mut IApplicationView,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_view_for_app_user_model_id(
|
||||
&self,
|
||||
id: PCWSTR,
|
||||
out_view: *mut IApplicationView,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn get_view_in_focus(&self, out_view: *mut IApplicationView) -> HRESULT;
|
||||
|
||||
pub unsafe fn try_get_last_active_visible_view(
|
||||
&self,
|
||||
out_view: *mut IApplicationView,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn refresh_collection(&self) -> HRESULT;
|
||||
|
||||
pub unsafe fn register_for_application_view_changes(
|
||||
&self,
|
||||
listener: ComIn<IApplicationViewChangeListener>,
|
||||
out_id: *mut DWORD,
|
||||
) -> HRESULT;
|
||||
|
||||
pub unsafe fn unregister_for_application_view_changes(&self, id: DWORD) -> HRESULT;
|
||||
}
|
||||
104
komorebi/src/com/mod.rs
Normal file
104
komorebi/src/com/mod.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// This code is largely taken verbatim from this repository: https://github.com/Ciantic/AltTabAccessor
|
||||
// which the author Jari Pennanen (Ciantic) has kindly made available with the MIT license, available
|
||||
// in full here: https://github.com/Ciantic/AltTabAccessor/blob/main/LICENSE.txt
|
||||
|
||||
mod interfaces;
|
||||
|
||||
use interfaces::CLSID_ImmersiveShell;
|
||||
use interfaces::IApplicationViewCollection;
|
||||
use interfaces::IServiceProvider;
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use windows::core::ComInterface;
|
||||
use windows::core::Interface;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::System::Com::CoCreateInstance;
|
||||
use windows::Win32::System::Com::CoInitializeEx;
|
||||
use windows::Win32::System::Com::CoUninitialize;
|
||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||
use windows::Win32::System::Com::COINIT_APARTMENTTHREADED;
|
||||
|
||||
struct ComInit();
|
||||
|
||||
impl ComInit {
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
// Notice: Only COINIT_APARTMENTTHREADED works correctly!
|
||||
//
|
||||
// Not COINIT_MULTITHREADED or CoIncrementMTAUsage, they cause a seldom crashes in threading tests.
|
||||
CoInitializeEx(None, COINIT_APARTMENTTHREADED).unwrap();
|
||||
}
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ComInit {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static COM_INIT: ComInit = ComInit::new();
|
||||
}
|
||||
|
||||
fn get_iservice_provider() -> IServiceProvider {
|
||||
COM_INIT.with(|_| unsafe { CoCreateInstance(&CLSID_ImmersiveShell, None, CLSCTX_ALL).unwrap() })
|
||||
}
|
||||
|
||||
fn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplicationViewCollection {
|
||||
COM_INIT.with(|_| {
|
||||
let mut obj = std::ptr::null_mut::<c_void>();
|
||||
unsafe {
|
||||
provider
|
||||
.query_service(
|
||||
&IApplicationViewCollection::IID,
|
||||
&IApplicationViewCollection::IID,
|
||||
&mut obj,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert!(!obj.is_null());
|
||||
|
||||
unsafe { IApplicationViewCollection::from_raw(obj) }
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {
|
||||
COM_INIT.with(|_| {
|
||||
let provider = get_iservice_provider();
|
||||
let view_collection = get_iapplication_view_collection(&provider);
|
||||
let mut view = None;
|
||||
unsafe {
|
||||
if view_collection.get_view_for_hwnd(hwnd, &mut view).is_err() {
|
||||
tracing::error!(
|
||||
"could not get view for hwnd {} due to os error: {}",
|
||||
hwnd.0,
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
view.map_or_else(
|
||||
|| {
|
||||
tracing::error!("no view was found for {}", hwnd.0,);
|
||||
},
|
||||
|view| {
|
||||
unsafe {
|
||||
if view.set_cloak(cloak_type, flags).is_err() {
|
||||
tracing::error!(
|
||||
"could not change the cloaking status for hwnd {} due to os error: {}",
|
||||
hwnd.0,
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -2,12 +2,13 @@ use std::collections::VecDeque;
|
||||
|
||||
use getset::Getters;
|
||||
use nanoid::nanoid;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, JsonSchema)]
|
||||
pub struct Container {
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get = "pub")]
|
||||
@@ -78,18 +79,18 @@ impl Container {
|
||||
}
|
||||
|
||||
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
|
||||
self.windows_mut().remove(idx)
|
||||
let window = self.windows_mut().remove(idx);
|
||||
|
||||
if idx != 0 {
|
||||
self.focus_window(idx - 1);
|
||||
};
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
pub fn remove_focused_window(&mut self) -> Option<Window> {
|
||||
let focused_idx = self.focused_window_idx();
|
||||
let window = self.remove_window_by_idx(focused_idx);
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_window(focused_idx - 1);
|
||||
}
|
||||
|
||||
window
|
||||
self.remove_window_by_idx(focused_idx)
|
||||
}
|
||||
|
||||
pub fn add_window(&mut self, window: Window) {
|
||||
|
||||
78
komorebi/src/hidden.rs
Normal file
78
komorebi/src/hidden.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::Result;
|
||||
use windows::core::PCSTR;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::FindWindowA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
|
||||
|
||||
use crate::windows_callbacks;
|
||||
use crate::WindowsApi;
|
||||
use crate::HIDDEN_HWND;
|
||||
use crate::TRANSPARENCY_COLOUR;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Hidden {
|
||||
pub(crate) hwnd: isize,
|
||||
}
|
||||
|
||||
impl From<isize> for Hidden {
|
||||
fn from(hwnd: isize) -> Self {
|
||||
Self { hwnd }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hidden {
|
||||
pub const fn hwnd(self) -> HWND {
|
||||
HWND(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn create(name: &str) -> Result<()> {
|
||||
let name = format!("{name}\0");
|
||||
let instance = WindowsApi::module_handle_w()?;
|
||||
let class_name = PCSTR(name.as_ptr());
|
||||
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
|
||||
let window_class = WNDCLASSA {
|
||||
hInstance: instance,
|
||||
lpszClassName: class_name,
|
||||
style: CS_HREDRAW | CS_VREDRAW,
|
||||
lpfnWndProc: Some(windows_callbacks::hidden_window),
|
||||
hbrBackground: brush,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let _atom = WindowsApi::register_class_a(&window_class)?;
|
||||
|
||||
let name_cl = name.clone();
|
||||
std::thread::spawn(move || -> Result<()> {
|
||||
let hwnd = WindowsApi::create_hidden_window(PCSTR(name_cl.as_ptr()), instance)?;
|
||||
let hidden = Self::from(hwnd);
|
||||
|
||||
let mut message = MSG::default();
|
||||
|
||||
unsafe {
|
||||
while GetMessageA(&mut message, hidden.hwnd(), 0, 0).into() {
|
||||
DispatchMessageA(&message);
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let mut hwnd = HWND(0);
|
||||
while hwnd == HWND(0) {
|
||||
hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) };
|
||||
}
|
||||
|
||||
HIDDEN_HWND.store(hwnd.0, Ordering::SeqCst);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,55 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::missing_errors_doc, clippy::redundant_pub_crate)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::net::TcpStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::thread;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Clap;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::Backoff;
|
||||
use lazy_static::lazy_static;
|
||||
use os_info::Version;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use parking_lot::deadlock;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use sysinfo::Process;
|
||||
use sysinfo::ProcessExt;
|
||||
use sysinfo::SystemExt;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use which::which;
|
||||
use winreg::enums::HKEY_CURRENT_USER;
|
||||
use winreg::RegKey;
|
||||
|
||||
use crate::hidden::Hidden;
|
||||
use komorebi_core::HidingBehaviour;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::SocketMessage;
|
||||
|
||||
use crate::process_command::listen_for_commands;
|
||||
use crate::process_command::listen_for_commands_tcp;
|
||||
use crate::process_event::listen_for_events;
|
||||
use crate::process_movement::listen_for_movements;
|
||||
use crate::static_config::StaticConfig;
|
||||
use crate::window_manager::State;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
@@ -36,12 +57,16 @@ use crate::windows_api::WindowsApi;
|
||||
#[macro_use]
|
||||
mod ring;
|
||||
|
||||
mod border;
|
||||
mod com;
|
||||
mod container;
|
||||
mod hidden;
|
||||
mod monitor;
|
||||
mod process_command;
|
||||
mod process_event;
|
||||
mod process_movement;
|
||||
mod set_window_position;
|
||||
mod static_config;
|
||||
mod styles;
|
||||
mod window;
|
||||
mod window_manager;
|
||||
@@ -52,9 +77,11 @@ mod winevent;
|
||||
mod winevent_listener;
|
||||
mod workspace;
|
||||
|
||||
type WorkspaceRule = (usize, usize, bool);
|
||||
|
||||
lazy_static! {
|
||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
|
||||
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec![
|
||||
@@ -69,14 +96,94 @@ lazy_static! {
|
||||
"firefox.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
]));
|
||||
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
|
||||
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, WorkspaceRule>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
// mstsc.exe creates these on Windows 11 when a WSL process is launched
|
||||
// https://github.com/LGUG2Z/komorebi/issues/74
|
||||
"OPContainerClass".to_string(),
|
||||
"IHWindowClass".to_string()
|
||||
]));
|
||||
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"Chrome_RenderWidgetHostHWND".to_string(),
|
||||
]));
|
||||
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"X410.exe".to_string(),
|
||||
"vcxsrv.exe".to_string(),
|
||||
]));
|
||||
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
||||
Arc::new(Mutex::new(HidingBehaviour::Minimize));
|
||||
static ref HOME_DIR: PathBuf = {
|
||||
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(|_| dirs::home_dir().expect("there is no home directory"), |home_path| {
|
||||
let home = PathBuf::from(&home_path);
|
||||
|
||||
if home.as_path().is_dir() {
|
||||
home
|
||||
} else {
|
||||
panic!(
|
||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
|
||||
);
|
||||
}
|
||||
})
|
||||
};
|
||||
static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
|
||||
static ref AHK_EXE: String = {
|
||||
let mut ahk: String = String::from("autohotkey.exe");
|
||||
|
||||
if let Ok(komorebi_ahk_exe) = std::env::var("KOMOREBI_AHK_EXE") {
|
||||
if which(&komorebi_ahk_exe).is_ok() {
|
||||
ahk = komorebi_ahk_exe;
|
||||
}
|
||||
}
|
||||
|
||||
ahk
|
||||
};
|
||||
static ref WINDOWS_11: bool = {
|
||||
matches!(
|
||||
os_info::get().version(),
|
||||
Version::Semantic(_, _, x) if x >= &22000
|
||||
)
|
||||
};
|
||||
|
||||
static ref BORDER_RECT: Arc<Mutex<Rect>> =
|
||||
Arc::new(Mutex::new(Rect::default()));
|
||||
|
||||
static ref BORDER_OFFSET: Arc<Mutex<Option<Rect>>> =
|
||||
Arc::new(Mutex::new(None));
|
||||
|
||||
// Use app-specific titlebar removal options where possible
|
||||
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
||||
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
}
|
||||
|
||||
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||
|
||||
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||
pub static ALT_FOCUS_HACK: AtomicBool = AtomicBool::new(false);
|
||||
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0);
|
||||
pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0);
|
||||
pub static BORDER_COLOUR_MONOCLE: AtomicU32 = AtomicU32::new(0);
|
||||
pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0);
|
||||
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(20);
|
||||
// 0 0 0 aka pure black, I doubt anyone will want this as a border colour
|
||||
pub const TRANSPARENCY_COLOUR: u32 = 0;
|
||||
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
@@ -89,8 +196,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let appender = tracing_appender::rolling::never(home, "komorebi.log");
|
||||
let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "komorebi.log");
|
||||
let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
||||
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
|
||||
@@ -120,59 +226,159 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
// occurred to be recorded.
|
||||
std::panic::set_hook(Box::new(|panic| {
|
||||
// If the panic has a source location, record it as structured fields.
|
||||
if let Some(location) = panic.location() {
|
||||
// On nightly Rust, where the `PanicInfo` type also exposes a
|
||||
// `message()` method returning just the message, we could record
|
||||
// just the message instead of the entire `fmt::Display`
|
||||
// implementation, avoiding the duplciated location
|
||||
tracing::error!(
|
||||
message = %panic,
|
||||
panic.file = location.file(),
|
||||
panic.line = location.line(),
|
||||
panic.column = location.column(),
|
||||
);
|
||||
} else {
|
||||
tracing::error!(message = %panic);
|
||||
}
|
||||
panic.location().map_or_else(
|
||||
|| {
|
||||
tracing::error!(message = %panic);
|
||||
},
|
||||
|location| {
|
||||
// On nightly Rust, where the `PanicInfo` type also exposes a
|
||||
// `message()` method returning just the message, we could record
|
||||
// just the message instead of the entire `fmt::Display`
|
||||
// implementation, avoiding the duplciated location
|
||||
tracing::error!(
|
||||
message = %panic,
|
||||
panic.file = location.file(),
|
||||
panic.line = location.line(),
|
||||
panic.column = location.column(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}));
|
||||
|
||||
Ok((guard, color_guard))
|
||||
}
|
||||
|
||||
pub fn load_configuration() -> Result<()> {
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let home = HOME_DIR.clone();
|
||||
|
||||
let mut config_v1 = home.clone();
|
||||
config_v1.push("komorebi.ahk");
|
||||
let mut config_pwsh = home.clone();
|
||||
config_pwsh.push("komorebi.ps1");
|
||||
|
||||
let mut config_v2 = home;
|
||||
config_v2.push("komorebi.ahk2");
|
||||
let mut config_ahk = home;
|
||||
config_ahk.push("komorebi.ahk");
|
||||
|
||||
if config_pwsh.exists() {
|
||||
let powershell_exe = if which("pwsh.exe").is_ok() {
|
||||
"pwsh.exe"
|
||||
} else {
|
||||
"powershell.exe"
|
||||
};
|
||||
|
||||
if config_v1.exists() && which("autohotkey.exe").is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_v1
|
||||
config_pwsh
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("autohotkey.exe")
|
||||
.arg(config_v1.as_os_str())
|
||||
Command::new(powershell_exe)
|
||||
.arg(config_pwsh.as_os_str())
|
||||
.output()?;
|
||||
} else if config_v2.exists() && which("AutoHotkey64.exe").is_ok() {
|
||||
} else if config_ahk.exists() && which(&*AHK_EXE).is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_v2
|
||||
config_ahk
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("AutoHotkey64.exe")
|
||||
.arg(config_v2.as_os_str())
|
||||
Command::new(&*AHK_EXE)
|
||||
.arg(config_ahk.as_os_str())
|
||||
.output()?;
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
|
||||
// This is the path on Windows 10
|
||||
let mut current = hkcu
|
||||
.open_subkey(format!(
|
||||
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
|
||||
SESSION_ID.load(Ordering::SeqCst)
|
||||
))
|
||||
.ok()
|
||||
.and_then(
|
||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||
Ok(current) => Option::from(current.bytes),
|
||||
Err(_) => None,
|
||||
},
|
||||
);
|
||||
|
||||
// This is the path on Windows 11
|
||||
if current.is_none() {
|
||||
current = hkcu
|
||||
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
|
||||
.ok()
|
||||
.and_then(
|
||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||
Ok(current) => Option::from(current.bytes),
|
||||
Err(_) => None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
|
||||
// exist until one has been created in the task view
|
||||
|
||||
// The registry value will also not exist on user login if virtual desktops have been created
|
||||
// but the task view has not been initiated
|
||||
|
||||
// In both of these cases, we return None, and the virtual desktop validation will never run. In
|
||||
// the latter case, if the user desires this validation after initiating the task view, komorebi
|
||||
// should be restarted, and then when this // fn runs again for the first time, it will pick up
|
||||
// the value of CurrentVirtualDesktop and validate against it accordingly
|
||||
current
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum NotificationEvent {
|
||||
WindowManager(WindowManagerEvent),
|
||||
Socket(SocketMessage),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct Notification {
|
||||
pub event: NotificationEvent,
|
||||
pub state: State,
|
||||
}
|
||||
|
||||
pub fn notify_subscribers(notification: &str) -> Result<()> {
|
||||
let mut stale_subscriptions = vec![];
|
||||
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
|
||||
for (subscriber, pipe) in subscriptions.iter_mut() {
|
||||
match writeln!(pipe, "{notification}") {
|
||||
Ok(_) => {
|
||||
tracing::debug!("pushed notification to subscriber: {}", subscriber);
|
||||
}
|
||||
Err(error) => {
|
||||
// ERROR_FILE_NOT_FOUND
|
||||
// 2 (0x2)
|
||||
// The system cannot find the file specified.
|
||||
|
||||
// ERROR_NO_DATA
|
||||
// 232 (0xE8)
|
||||
// The pipe is being closed.
|
||||
|
||||
// Remove the subscription; the process will have to subscribe again
|
||||
if let Some(2 | 232) = error.raw_os_error() {
|
||||
let subscriber_cl = subscriber.clone();
|
||||
stale_subscriptions.push(subscriber_cl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for subscriber in stale_subscriptions {
|
||||
tracing::warn!("removing stale subscription: {}", subscriber);
|
||||
subscriptions.remove(&subscriber);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -181,9 +387,9 @@ pub fn load_configuration() -> Result<()> {
|
||||
#[tracing::instrument]
|
||||
fn detect_deadlocks() {
|
||||
// Create a background thread which checks for deadlocks every 10s
|
||||
thread::spawn(move || loop {
|
||||
std::thread::spawn(move || loop {
|
||||
tracing::info!("running deadlock detector");
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
let deadlocks = deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
@@ -200,76 +406,139 @@ fn detect_deadlocks() {
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Parser)]
|
||||
#[clap(author, about, version)]
|
||||
struct Opts {
|
||||
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
||||
#[clap(long = "ffm")]
|
||||
#[clap(action, short, long = "ffm")]
|
||||
focus_follows_mouse: bool,
|
||||
/// Wait for 'komorebic complete-configuration' to be sent before processing events
|
||||
#[clap(action, short, long)]
|
||||
await_configuration: bool,
|
||||
/// Start a TCP server on the given port to allow the direct sending of SocketMessages
|
||||
#[clap(action, short, long)]
|
||||
tcp_port: Option<usize>,
|
||||
/// Path to a static configuration JSON file
|
||||
#[clap(action, short, long)]
|
||||
config: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn main() -> Result<()> {
|
||||
let opts: Opts = Opts::parse();
|
||||
CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
|
||||
|
||||
let arg_count = std::env::args().count();
|
||||
let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst));
|
||||
let process_id = WindowsApi::current_process_id();
|
||||
WindowsApi::allow_set_foreground_window(process_id)?;
|
||||
WindowsApi::set_process_dpi_awareness_context()?;
|
||||
|
||||
if has_valid_args {
|
||||
let mut system = sysinfo::System::new_all();
|
||||
system.refresh_processes();
|
||||
let session_id = WindowsApi::process_id_to_session_id()?;
|
||||
SESSION_ID.store(session_id, Ordering::SeqCst);
|
||||
|
||||
if system.process_by_name("komorebi.exe").len() > 1 {
|
||||
let mut system = sysinfo::System::new_all();
|
||||
system.refresh_processes();
|
||||
|
||||
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe").collect();
|
||||
|
||||
if matched_procs.len() > 1 {
|
||||
let mut len = matched_procs.len();
|
||||
for proc in matched_procs {
|
||||
if proc.root().ends_with("shims") {
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if len > 1 {
|
||||
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
detect_deadlocks();
|
||||
|
||||
let process_id = WindowsApi::current_process_id();
|
||||
WindowsApi::allow_set_foreground_window(process_id)?;
|
||||
|
||||
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
crossbeam_channel::unbounded();
|
||||
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
incoming,
|
||||
)))?));
|
||||
|
||||
wm.lock().init()?;
|
||||
listen_for_commands(wm.clone());
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
if CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||
listen_for_movements(wm.clone());
|
||||
}
|
||||
|
||||
load_configuration()?;
|
||||
|
||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||
ctrlc::set_handler(move || {
|
||||
ctrlc_sender
|
||||
.send(())
|
||||
.expect("could not send signal on ctrl-c channel");
|
||||
})?;
|
||||
|
||||
ctrlc_receiver
|
||||
.recv()
|
||||
.expect("could not receive signal on ctrl-c channel");
|
||||
|
||||
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
|
||||
|
||||
wm.lock().restore_all_windows();
|
||||
std::process::exit(130);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
detect_deadlocks();
|
||||
|
||||
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
crossbeam_channel::unbounded();
|
||||
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
Hidden::create("komorebi-hidden")?;
|
||||
|
||||
let wm = if let Some(config) = &opts.config {
|
||||
tracing::info!(
|
||||
"creating window manager from static configuration file: {}",
|
||||
config.as_os_str().to_str().unwrap()
|
||||
);
|
||||
|
||||
Arc::new(Mutex::new(StaticConfig::preload(
|
||||
config,
|
||||
Arc::new(Mutex::new(incoming)),
|
||||
)?))
|
||||
} else {
|
||||
Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
incoming,
|
||||
)))?))
|
||||
};
|
||||
|
||||
wm.lock().init()?;
|
||||
if let Some(config) = &opts.config {
|
||||
StaticConfig::postload(config, &wm)?;
|
||||
}
|
||||
|
||||
listen_for_commands(wm.clone());
|
||||
|
||||
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
|
||||
};
|
||||
|
||||
if let Some(port) = opts.tcp_port {
|
||||
listen_for_commands_tcp(wm.clone(), port);
|
||||
}
|
||||
|
||||
if opts.config.is_none() {
|
||||
std::thread::spawn(|| {
|
||||
load_configuration().expect("could not load configuration");
|
||||
});
|
||||
|
||||
if opts.await_configuration {
|
||||
let backoff = Backoff::new();
|
||||
while !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||
backoff.snooze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wm.lock().retile_all(false)?;
|
||||
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
if CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||
listen_for_movements(wm.clone());
|
||||
}
|
||||
|
||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||
ctrlc::set_handler(move || {
|
||||
ctrlc_sender
|
||||
.send(())
|
||||
.expect("could not send signal on ctrl-c channel");
|
||||
})?;
|
||||
|
||||
ctrlc_receiver
|
||||
.recv()
|
||||
.expect("could not receive signal on ctrl-c channel");
|
||||
|
||||
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
|
||||
|
||||
wm.lock().restore_all_windows()?;
|
||||
|
||||
if WindowsApi::focus_follows_mouse()? {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
}
|
||||
|
||||
std::process::exit(130);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Rect;
|
||||
@@ -15,14 +16,18 @@ use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||
pub struct Monitor {
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
id: isize,
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
name: String,
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
size: Rect,
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
work_area_size: Rect,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
work_area_offset: Option<Rect>,
|
||||
workspaces: Ring<Workspace>,
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_mut = "pub")]
|
||||
@@ -31,25 +36,27 @@ pub struct Monitor {
|
||||
|
||||
impl_ring_elements!(Monitor, Workspace);
|
||||
|
||||
pub fn new(id: isize, size: Rect, work_area_size: Rect) -> Monitor {
|
||||
pub fn new(id: isize, size: Rect, work_area_size: Rect, name: String) -> Monitor {
|
||||
let mut workspaces = Ring::default();
|
||||
workspaces.elements_mut().push_back(Workspace::default());
|
||||
|
||||
Monitor {
|
||||
id,
|
||||
name,
|
||||
size,
|
||||
work_area_size,
|
||||
work_area_offset: None,
|
||||
workspaces,
|
||||
workspace_names: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
pub fn load_focused_workspace(&mut self) -> Result<()> {
|
||||
pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||
let focused_idx = self.focused_workspace_idx();
|
||||
for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
|
||||
if i == focused_idx {
|
||||
workspace.restore()?;
|
||||
workspace.restore(mouse_follows_focus)?;
|
||||
} else {
|
||||
workspace.hide();
|
||||
}
|
||||
@@ -58,16 +65,39 @@ impl Monitor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_container(&mut self, container: Container) -> Result<()> {
|
||||
let workspace = self
|
||||
.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
pub fn add_container(
|
||||
&mut self,
|
||||
container: Container,
|
||||
workspace_idx: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let workspace = if let Some(idx) = workspace_idx {
|
||||
self.workspaces_mut()
|
||||
.get_mut(idx)
|
||||
.ok_or_else(|| anyhow!("there is no workspace at index {}", idx))?
|
||||
} else {
|
||||
self.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||
};
|
||||
|
||||
workspace.add_container(container);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_workspace_by_idx(&mut self, idx: usize) -> Option<Workspace> {
|
||||
if idx < self.workspaces().len() {
|
||||
return self.workspaces_mut().remove(idx);
|
||||
}
|
||||
|
||||
if idx == 0 {
|
||||
self.workspaces_mut().push_back(Workspace::default());
|
||||
} else {
|
||||
self.focus_workspace(idx - 1).ok()?;
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn ensure_workspace_count(&mut self, ensure_count: usize) {
|
||||
if self.workspaces().len() < ensure_count {
|
||||
self.workspaces_mut()
|
||||
@@ -75,6 +105,10 @@ impl Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_workspaces(&mut self) -> VecDeque<Workspace> {
|
||||
self.workspaces_mut().drain(..).collect()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_to_workspace(
|
||||
&mut self,
|
||||
@@ -97,6 +131,7 @@ impl Monitor {
|
||||
|
||||
let workspaces = self.workspaces_mut();
|
||||
|
||||
#[allow(clippy::option_if_let_else)]
|
||||
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
||||
None => {
|
||||
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
||||
@@ -146,12 +181,21 @@ impl Monitor {
|
||||
self.workspaces().len()
|
||||
}
|
||||
|
||||
pub fn update_focused_workspace(&mut self, invisible_borders: &Rect) -> Result<()> {
|
||||
pub fn update_focused_workspace(
|
||||
&mut self,
|
||||
offset: Option<Rect>,
|
||||
invisible_borders: &Rect,
|
||||
) -> Result<()> {
|
||||
let work_area = *self.work_area_size();
|
||||
let offset = if self.work_area_offset().is_some() {
|
||||
self.work_area_offset()
|
||||
} else {
|
||||
offset
|
||||
};
|
||||
|
||||
self.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||
.update(&work_area, invisible_borders)?;
|
||||
.update(&work_area, offset, invisible_borders)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
@@ -10,10 +10,24 @@ use parking_lot::Mutex;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
use komorebi_core::WindowContainerBehaviour;
|
||||
|
||||
use crate::border::Border;
|
||||
use crate::current_virtual_desktop;
|
||||
use crate::notify_subscribers;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
use crate::BORDER_COLOUR_MONOCLE;
|
||||
use crate::BORDER_COLOUR_SINGLE;
|
||||
use crate::BORDER_COLOUR_STACK;
|
||||
use crate::BORDER_ENABLED;
|
||||
use crate::BORDER_HIDDEN;
|
||||
use crate::BORDER_HWND;
|
||||
use crate::DATA_DIR;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
|
||||
@@ -21,7 +35,7 @@ use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||
let receiver = wm.lock().incoming_events.lock().clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
std::thread::spawn(move || {
|
||||
tracing::info!("listening");
|
||||
loop {
|
||||
select! {
|
||||
@@ -47,32 +61,67 @@ impl WindowManager {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.validate_virtual_desktop_id();
|
||||
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
|
||||
if let Some(id) = current_virtual_desktop() {
|
||||
if id != *virtual_desktop_id {
|
||||
tracing::info!(
|
||||
"ignoring events and commands while not on virtual desktop {:?}",
|
||||
virtual_desktop_id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we have the most recently focused monitor from any event
|
||||
match event {
|
||||
WindowManagerEvent::MonitorPoll(_, window)
|
||||
| WindowManagerEvent::FocusChange(_, window)
|
||||
WindowManagerEvent::FocusChange(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::DisplayChange(window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||
self.reconcile_monitors()?;
|
||||
|
||||
let monitor_idx = self.monitor_idx_from_window(*window)
|
||||
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
// This is a hidden window apparently associated with COM support mechanisms (based
|
||||
// on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)
|
||||
//
|
||||
// The hidden window, OLEChannelWnd, associated with this class (spawned by
|
||||
// explorer.exe), after some debugging, is observed to always be tied to the primary
|
||||
// display monitor, or (usually) monitor 0 in the WindowManager state.
|
||||
//
|
||||
// Due to this, at least one user in the Discord has witnessed behaviour where, when
|
||||
// a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets
|
||||
// set repeatedly to 0, regardless of where the current foreground window is actually
|
||||
// located.
|
||||
//
|
||||
// This check ensures that we only update the focused monitor when the window
|
||||
// triggering monitor reconciliation is known to not be tied to a specific monitor.
|
||||
if window.class()? != "OleMainThreadWndClass"
|
||||
&& self.focused_monitor_idx() != monitor_idx
|
||||
{
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
let offset = self.work_area_offset;
|
||||
|
||||
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||
let work_area = *monitor.work_area_size();
|
||||
let offset = if monitor.work_area_offset().is_some() {
|
||||
monitor.work_area_offset()
|
||||
} else {
|
||||
offset
|
||||
};
|
||||
|
||||
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||
let reaped_orphans = workspace.reap_orphans()?;
|
||||
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
||||
workspace.update(&work_area, &invisible_borders)?;
|
||||
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||
tracing::info!(
|
||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||
reaped_orphans.0,
|
||||
@@ -95,16 +144,32 @@ impl WindowManager {
|
||||
|
||||
match event {
|
||||
WindowManagerEvent::Raise(window) => {
|
||||
window.raise()?;
|
||||
window.raise();
|
||||
self.has_pending_raise_op = false;
|
||||
}
|
||||
WindowManagerEvent::Minimize(_, window)
|
||||
| WindowManagerEvent::Destroy(_, window)
|
||||
| WindowManagerEvent::Unmanage(window) => {
|
||||
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
|
||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
|
||||
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
|
||||
|
||||
already_moved_window_handles.remove(&window.hwnd);
|
||||
}
|
||||
WindowManagerEvent::Minimize(_, window) => {
|
||||
let mut hide = false;
|
||||
|
||||
{
|
||||
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if !programmatically_hidden_hwnds.contains(&window.hwnd) {
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
|
||||
if hide {
|
||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::Hide(_, window) => {
|
||||
let mut hide = false;
|
||||
// Some major applications unfortunately send the HIDE signal when they are being
|
||||
@@ -119,10 +184,10 @@ impl WindowManager {
|
||||
// they are not on the top of a container stack.
|
||||
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
|
||||
if (!window.is_window()
|
||||
if ((!window.is_window()
|
||||
|| tray_and_multi_window_identifiers.contains(&window.exe()?))
|
||||
|| tray_and_multi_window_identifiers.contains(&window.class()?)
|
||||
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
|
||||
|| tray_and_multi_window_identifiers.contains(&window.class()?))
|
||||
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
|
||||
{
|
||||
hide = true;
|
||||
}
|
||||
@@ -132,25 +197,33 @@ impl WindowManager {
|
||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
|
||||
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
|
||||
|
||||
already_moved_window_handles.remove(&window.hwnd);
|
||||
}
|
||||
WindowManagerEvent::FocusChange(_, window) => {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
if workspace
|
||||
if !workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.any(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(w) = workspace.maximized_window() {
|
||||
if w.hwnd == window.hwnd {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(w) = workspace.maximized_window() {
|
||||
if w.hwnd == window.hwnd {
|
||||
return Ok(());
|
||||
if let Some(monocle) = workspace.monocle_container() {
|
||||
if let Some(window) = monocle.focused_window() {
|
||||
window.focus(false)?;
|
||||
}
|
||||
} else {
|
||||
self.focused_workspace_mut()?
|
||||
.focus_container_by_window(window.hwnd)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.focused_workspace_mut()?
|
||||
.focus_container_by_window(window.hwnd)?;
|
||||
}
|
||||
WindowManagerEvent::Show(_, window) | WindowManagerEvent::Manage(window) => {
|
||||
let mut switch_to = None;
|
||||
@@ -197,59 +270,199 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
let behaviour = self.window_container_behaviour;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if !workspace.contains_window(window.hwnd) {
|
||||
workspace.new_container_for_window(*window);
|
||||
self.update_focused_workspace(false)?;
|
||||
match behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
workspace.new_container_for_window(*window);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
workspace
|
||||
.focused_container_mut()
|
||||
.ok_or_else(|| anyhow!("there is no focused container"))?
|
||||
.add_window(*window);
|
||||
self.update_focused_workspace(true)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::MoveResizeStart(_, window) => {
|
||||
let monitor_idx = self.focused_monitor_idx();
|
||||
let workspace_idx = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
||||
.focused_workspace_idx();
|
||||
let container_idx = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
||||
.focused_workspace()
|
||||
.ok_or_else(|| anyhow!("there is no workspace with this idx"))?
|
||||
.focused_container_idx();
|
||||
|
||||
WindowsApi::bring_window_to_top(window.hwnd())?;
|
||||
|
||||
self.pending_move_op = Option::from((monitor_idx, workspace_idx, container_idx));
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||
// We need this because if the event ends on a different monitor,
|
||||
// that monitor will already have been focused and updated in the state
|
||||
let pending = self.pending_move_op;
|
||||
// Always consume the pending move op whenever this event is handled
|
||||
self.pending_move_op = None;
|
||||
|
||||
let target_monitor_idx = self
|
||||
.monitor_idx_from_current_pos()
|
||||
.ok_or_else(|| anyhow!("cannot get monitor idx from current position"))?;
|
||||
|
||||
let new_window_behaviour = self.window_container_behaviour;
|
||||
let invisible_borders = self.invisible_borders;
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
if workspace
|
||||
if !workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.any(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let focused_container_idx = workspace.focused_container_idx();
|
||||
|
||||
let focused_idx = workspace.focused_container_idx();
|
||||
let old_position = *workspace
|
||||
.latest_layout()
|
||||
.get(focused_idx)
|
||||
.ok_or_else(|| anyhow!("there is no latest layout"))?;
|
||||
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||
|
||||
// Adjust for the invisible borders
|
||||
new_position.left += invisible_borders.left;
|
||||
new_position.top += invisible_borders.top;
|
||||
new_position.right -= invisible_borders.right;
|
||||
new_position.bottom -= invisible_borders.bottom;
|
||||
let old_position = *workspace
|
||||
.latest_layout()
|
||||
.get(focused_container_idx)
|
||||
// If the move was to another monitor with an empty workspace, the
|
||||
// workspace here will refer to that empty workspace, which won't
|
||||
// have any latest layout set. We fall back to a Default for Rect
|
||||
// which allows us to make a reasonable guess that the drag has taken
|
||||
// place across a monitor boundary to an empty workspace
|
||||
.unwrap_or(&Rect::default());
|
||||
|
||||
let resize = Rect {
|
||||
left: new_position.left - old_position.left,
|
||||
top: new_position.top - old_position.top,
|
||||
right: new_position.right - old_position.right,
|
||||
bottom: new_position.bottom - old_position.bottom,
|
||||
};
|
||||
// This will be true if we have moved to an empty workspace on another monitor
|
||||
let mut moved_across_monitors = old_position == Rect::default();
|
||||
|
||||
let is_move = resize.right == 0 && resize.bottom == 0;
|
||||
|
||||
if is_move {
|
||||
tracing::info!("moving with mouse");
|
||||
match workspace.container_idx_from_current_point() {
|
||||
Some(target_idx) => {
|
||||
workspace.swap_containers(focused_idx, target_idx);
|
||||
self.update_focused_workspace(false)?;
|
||||
if let Some((origin_monitor_idx, _, _)) = pending {
|
||||
// If we didn't move to another monitor with an empty workspace, it is
|
||||
// still possible that we moved to another monitor with a populated workspace
|
||||
if !moved_across_monitors {
|
||||
// So we'll check if the origin monitor index and the target monitor index
|
||||
// are different, if they are, we can set the override
|
||||
moved_across_monitors = origin_monitor_idx != target_monitor_idx;
|
||||
}
|
||||
None => self.update_focused_workspace(true)?,
|
||||
}
|
||||
} else {
|
||||
tracing::info!("resizing with mouse");
|
||||
let mut ops = vec![];
|
||||
|
||||
macro_rules! resize_op {
|
||||
// Adjust for the invisible borders
|
||||
new_position.left += invisible_borders.left;
|
||||
new_position.top += invisible_borders.top;
|
||||
new_position.right -= invisible_borders.right;
|
||||
new_position.bottom -= invisible_borders.bottom;
|
||||
|
||||
let resize = Rect {
|
||||
left: new_position.left - old_position.left,
|
||||
top: new_position.top - old_position.top,
|
||||
right: new_position.right - old_position.right,
|
||||
bottom: new_position.bottom - old_position.bottom,
|
||||
};
|
||||
|
||||
// If we have moved across the monitors, use that override, otherwise determine
|
||||
// if a move has taken place by ruling out a resize
|
||||
let is_move = moved_across_monitors
|
||||
|| resize.right == 0 && resize.bottom == 0
|
||||
|| resize.right.abs() == invisible_borders.right
|
||||
&& resize.bottom.abs() == invisible_borders.bottom;
|
||||
|
||||
if is_move {
|
||||
tracing::info!("moving with mouse");
|
||||
|
||||
if moved_across_monitors {
|
||||
if let Some((
|
||||
origin_monitor_idx,
|
||||
origin_workspace_idx,
|
||||
origin_container_idx,
|
||||
)) = pending
|
||||
{
|
||||
let target_workspace_idx = self
|
||||
.monitors()
|
||||
.get(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
|
||||
.focused_workspace_idx();
|
||||
|
||||
let target_container_idx = self
|
||||
.monitors()
|
||||
.get(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
|
||||
.focused_workspace()
|
||||
.ok_or_else(|| {
|
||||
anyhow!("there is no focused workspace for this monitor")
|
||||
})?
|
||||
.container_idx_from_current_point()
|
||||
// Default to 0 in the case of an empty workspace
|
||||
.unwrap_or(0);
|
||||
|
||||
self.transfer_container(
|
||||
(
|
||||
origin_monitor_idx,
|
||||
origin_workspace_idx,
|
||||
origin_container_idx,
|
||||
),
|
||||
(
|
||||
target_monitor_idx,
|
||||
target_workspace_idx,
|
||||
target_container_idx,
|
||||
),
|
||||
)?;
|
||||
|
||||
// We want to make sure both the origin and target monitors are updated,
|
||||
// so that we don't have ghost tiles until we force an interaction on
|
||||
// the origin monitor's focused workspace
|
||||
self.focus_monitor(origin_monitor_idx)?;
|
||||
self.focus_workspace(origin_workspace_idx)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
|
||||
self.focus_monitor(target_monitor_idx)?;
|
||||
self.focus_workspace(target_workspace_idx)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
// Here we handle a simple move on the same monitor which is treated as
|
||||
// a container swap
|
||||
} else {
|
||||
match new_window_behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
match workspace.container_idx_from_current_point() {
|
||||
Some(target_idx) => {
|
||||
workspace
|
||||
.swap_containers(focused_container_idx, target_idx);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
None => {
|
||||
self.update_focused_workspace(
|
||||
self.mouse_follows_focus,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
match workspace.container_idx_from_current_point() {
|
||||
Some(target_idx) => {
|
||||
workspace.move_window_to_container(target_idx)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
None => {
|
||||
self.update_focused_workspace(
|
||||
self.mouse_follows_focus,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::info!("resizing with mouse");
|
||||
let mut ops = vec![];
|
||||
|
||||
macro_rules! resize_op {
|
||||
($coordinate:expr, $comparator:tt, $direction:expr) => {{
|
||||
let adjusted = $coordinate * 2;
|
||||
let sizing = if adjusted $comparator 0 {
|
||||
@@ -262,37 +475,134 @@ impl WindowManager {
|
||||
}};
|
||||
}
|
||||
|
||||
if resize.left != 0 {
|
||||
ops.push(resize_op!(resize.left, >, OperationDirection::Left));
|
||||
}
|
||||
if resize.left != 0 {
|
||||
ops.push(resize_op!(resize.left, >, OperationDirection::Left));
|
||||
}
|
||||
|
||||
if resize.top != 0 {
|
||||
ops.push(resize_op!(resize.top, >, OperationDirection::Up));
|
||||
}
|
||||
if resize.top != 0 {
|
||||
ops.push(resize_op!(resize.top, >, OperationDirection::Up));
|
||||
}
|
||||
|
||||
if resize.right != 0 && resize.left == 0 {
|
||||
ops.push(resize_op!(resize.right, <, OperationDirection::Right));
|
||||
}
|
||||
if resize.right != 0 && resize.left == 0 {
|
||||
ops.push(resize_op!(resize.right, <, OperationDirection::Right));
|
||||
}
|
||||
|
||||
if resize.bottom != 0 && resize.top == 0 {
|
||||
ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));
|
||||
}
|
||||
if resize.bottom != 0 && resize.top == 0 {
|
||||
ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));
|
||||
}
|
||||
|
||||
for (edge, sizing, step) in ops {
|
||||
self.resize_window(edge, sizing, Option::from(step))?;
|
||||
}
|
||||
for (edge, sizing, delta) in ops {
|
||||
self.resize_window(edge, sizing, delta, true)?;
|
||||
}
|
||||
|
||||
self.update_focused_workspace(false)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::MonitorPoll(..) | WindowManagerEvent::MouseCapture(..) => {}
|
||||
WindowManagerEvent::DisplayChange(..)
|
||||
| WindowManagerEvent::MouseCapture(..)
|
||||
| WindowManagerEvent::Cloak(..)
|
||||
| WindowManagerEvent::Uncloak(..) => {}
|
||||
};
|
||||
|
||||
if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||
match event {
|
||||
WindowManagerEvent::MoveResizeStart(_, _) => {
|
||||
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||
border.hide()?;
|
||||
BORDER_HIDDEN.store(true, Ordering::SeqCst);
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::FocusChange(_, window)
|
||||
| WindowManagerEvent::Hide(_, window)
|
||||
| WindowManagerEvent::Minimize(_, window) => {
|
||||
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||
let mut target_window = None;
|
||||
let mut target_window_is_monocle = false;
|
||||
if self
|
||||
.focused_workspace()?
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.any(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
target_window = Option::from(*window);
|
||||
WindowsApi::raise_window(border.hwnd())?;
|
||||
};
|
||||
|
||||
if let Some(monocle_container) = self.focused_workspace()?.monocle_container() {
|
||||
if let Some(window) = monocle_container.focused_window() {
|
||||
target_window = Option::from(*window);
|
||||
target_window_is_monocle = true;
|
||||
}
|
||||
}
|
||||
|
||||
if target_window.is_none() {
|
||||
match self.focused_container() {
|
||||
// if there is no focused container, the desktop is empty
|
||||
Err(..) => {
|
||||
WindowsApi::hide_border_window(border.hwnd())?;
|
||||
}
|
||||
Ok(container) => {
|
||||
if !(matches!(event, WindowManagerEvent::Minimize(_, _))
|
||||
&& container.windows().len() == 1)
|
||||
{
|
||||
let container_size = self.focused_container()?.windows().len();
|
||||
target_window = Option::from(*self.focused_window()?);
|
||||
|
||||
if target_window_is_monocle {
|
||||
BORDER_COLOUR_CURRENT.store(
|
||||
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
} else if container_size > 1 {
|
||||
BORDER_COLOUR_CURRENT.store(
|
||||
BORDER_COLOUR_STACK.load(Ordering::SeqCst),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
} else {
|
||||
BORDER_COLOUR_CURRENT.store(
|
||||
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(target_window) = target_window {
|
||||
let window = target_window;
|
||||
let mut rect = WindowsApi::window_rect(window.hwnd())?;
|
||||
rect.top -= self.invisible_borders.bottom;
|
||||
rect.bottom += self.invisible_borders.bottom;
|
||||
|
||||
let activate = BORDER_HIDDEN.load(Ordering::SeqCst);
|
||||
|
||||
WindowsApi::invalidate_border_rect()?;
|
||||
border.set_position(target_window, &self.invisible_borders, activate)?;
|
||||
|
||||
if activate {
|
||||
BORDER_HIDDEN.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
|
||||
if let WindowManagerEvent::Unmanage(window) = event {
|
||||
window.center(&self.focused_monitor_work_area()?, &invisible_borders)?;
|
||||
}
|
||||
|
||||
// If there are no more windows on the workspace, we shouldn't show the border window
|
||||
if self.focused_workspace()?.containers().is_empty() {
|
||||
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||
border.hide()?;
|
||||
BORDER_HIDDEN.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
tracing::trace!("updating list of known hwnds");
|
||||
let mut known_hwnds = vec![];
|
||||
for monitor in self.monitors() {
|
||||
@@ -305,9 +615,7 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
let mut hwnd_json =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
@@ -315,6 +623,10 @@ impl WindowManager {
|
||||
.open(hwnd_json)?;
|
||||
|
||||
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
||||
notify_subscribers(&serde_json::to_string(&Notification {
|
||||
event: NotificationEvent::WindowManager(*event),
|
||||
state: self.as_ref().into(),
|
||||
})?)?;
|
||||
|
||||
tracing::info!("processed: {}", event.window().to_string());
|
||||
Ok(())
|
||||
|
||||
@@ -17,8 +17,11 @@ pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
|
||||
let receiver = message_loop::start().expect("could not start winput message loop");
|
||||
|
||||
loop {
|
||||
let focus_follows_mouse = wm.lock().focus_follows_mouse.clone();
|
||||
if let Some(FocusFollowsMouseImplementation::Komorebi) = focus_follows_mouse {
|
||||
let focus_follows_mouse = wm.lock().focus_follows_mouse;
|
||||
if matches!(
|
||||
focus_follows_mouse,
|
||||
Some(FocusFollowsMouseImplementation::Komorebi)
|
||||
) {
|
||||
match receiver.next_event() {
|
||||
// Don't want to send any raise events while we are dragging or resizing
|
||||
Event::MouseButton { action, .. } => match action {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
pub struct Ring<T> {
|
||||
elements: VecDeque<T>,
|
||||
focused: usize,
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_DEFERERASE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_DRAWFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_HIDEWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOCOPYBITS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOREDRAW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOREPOSITION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOSENDCHANGING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_DEFERERASE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_DRAWFRAME;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_HIDEWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOCOPYBITS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOREPOSITION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSENDCHANGING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
|
||||
754
komorebi/src/static_config.rs
Normal file
754
komorebi/src/static_config.rs
Normal file
@@ -0,0 +1,754 @@
|
||||
use crate::border::Border;
|
||||
use crate::current_virtual_desktop;
|
||||
use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::ALT_FOCUS_HACK;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
use crate::BORDER_COLOUR_MONOCLE;
|
||||
use crate::BORDER_COLOUR_SINGLE;
|
||||
use crate::BORDER_COLOUR_STACK;
|
||||
use crate::BORDER_ENABLED;
|
||||
use crate::BORDER_HWND;
|
||||
use crate::BORDER_OFFSET;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::BORDER_WIDTH;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WORKSPACE_RULES;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use dirs::home_dir;
|
||||
use hotwatch::notify::DebouncedEvent;
|
||||
use hotwatch::Hotwatch;
|
||||
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
||||
use komorebi_core::config_generation::ApplicationOptions;
|
||||
use komorebi_core::config_generation::IdWithIdentifier;
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::DefaultLayout;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::HidingBehaviour;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::MoveBehaviour;
|
||||
use komorebi_core::OperationBehaviour;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::SocketMessage;
|
||||
use komorebi_core::WindowContainerBehaviour;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use uds_windows::UnixListener;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u32,
|
||||
/// Green
|
||||
pub g: u32,
|
||||
/// Blue
|
||||
pub b: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ActiveWindowBorderColours {
|
||||
/// Border colour when the container contains a single window
|
||||
pub single: Rgb,
|
||||
/// Border colour when the container contains multiple windows
|
||||
pub stack: Rgb,
|
||||
/// Border colour when the container is in monocle mode
|
||||
pub monocle: Rgb,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct WorkspaceConfig {
|
||||
/// Name
|
||||
pub name: String,
|
||||
/// Layout (default: BSP)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub layout: Option<DefaultLayout>,
|
||||
/// Custom Layout (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub custom_layout: Option<PathBuf>,
|
||||
/// Layout rules (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
|
||||
/// Layout rules (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,
|
||||
/// Container padding (default: global)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub container_padding: Option<i32>,
|
||||
/// Container padding (default: global)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workspace_padding: Option<i32>,
|
||||
/// Initial workspace application rules
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub initial_workspace_rules: Option<Vec<IdWithIdentifier>>,
|
||||
/// Permanent workspace application rules
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workspace_rules: Option<Vec<IdWithIdentifier>>,
|
||||
}
|
||||
|
||||
impl From<&Workspace> for WorkspaceConfig {
|
||||
fn from(value: &Workspace) -> Self {
|
||||
let mut layout_rules = HashMap::new();
|
||||
for (threshold, layout) in value.layout_rules() {
|
||||
match layout {
|
||||
Layout::Default(value) => {
|
||||
layout_rules.insert(*threshold, *value);
|
||||
}
|
||||
Layout::Custom(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let workspace_rules = WORKSPACE_RULES.lock();
|
||||
let mut initial_ws_rules = vec![];
|
||||
let mut ws_rules = vec![];
|
||||
|
||||
for (identifier, (_, _, is_initial)) in &*workspace_rules {
|
||||
if identifier.ends_with("exe") {
|
||||
let rule = IdWithIdentifier {
|
||||
kind: ApplicationIdentifier::Exe,
|
||||
id: identifier.clone(),
|
||||
};
|
||||
|
||||
if *is_initial {
|
||||
initial_ws_rules.push(rule);
|
||||
} else {
|
||||
ws_rules.push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let initial_ws_rules = if initial_ws_rules.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Option::from(initial_ws_rules)
|
||||
};
|
||||
let ws_rules = if ws_rules.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Option::from(ws_rules)
|
||||
};
|
||||
|
||||
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
|
||||
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
|
||||
|
||||
let container_padding = value.container_padding().and_then(|container_padding| {
|
||||
if container_padding == default_container_padding {
|
||||
None
|
||||
} else {
|
||||
Option::from(container_padding)
|
||||
}
|
||||
});
|
||||
|
||||
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
|
||||
if workspace_padding == default_workspace_padding {
|
||||
None
|
||||
} else {
|
||||
Option::from(workspace_padding)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
name: value
|
||||
.name()
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("unnamed")),
|
||||
layout: match value.layout() {
|
||||
Layout::Default(layout) => Option::from(*layout),
|
||||
// TODO: figure out how we might resolve file references in the future
|
||||
Layout::Custom(_) => None,
|
||||
},
|
||||
custom_layout: None,
|
||||
layout_rules: Option::from(layout_rules),
|
||||
// TODO: figure out how we might resolve file references in the future
|
||||
custom_layout_rules: None,
|
||||
container_padding,
|
||||
workspace_padding,
|
||||
initial_workspace_rules: initial_ws_rules,
|
||||
workspace_rules: ws_rules,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct MonitorConfig {
|
||||
/// Workspace configurations
|
||||
pub workspaces: Vec<WorkspaceConfig>,
|
||||
/// Monitor-specific work area offset (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub work_area_offset: Option<Rect>,
|
||||
}
|
||||
|
||||
impl From<&Monitor> for MonitorConfig {
|
||||
fn from(value: &Monitor) -> Self {
|
||||
let mut workspaces = vec![];
|
||||
for w in value.workspaces() {
|
||||
workspaces.push(WorkspaceConfig::from(w));
|
||||
}
|
||||
|
||||
Self {
|
||||
workspaces,
|
||||
work_area_offset: value.work_area_offset(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct StaticConfig {
|
||||
/// Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub invisible_borders: Option<Rect>,
|
||||
/// Delta to resize windows by (default 50)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resize_delta: Option<i32>,
|
||||
/// Determine what happens when a new window is opened (default: Create)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub window_container_behaviour: Option<WindowContainerBehaviour>,
|
||||
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
|
||||
/// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,
|
||||
/// Determine focus follows mouse implementation (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
||||
/// Enable or disable mouse follows focus (default: true)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mouse_follows_focus: Option<bool>,
|
||||
/// Path to applications.yaml from komorebi-application-specific-configurations (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub app_specific_configuration_path: Option<PathBuf>,
|
||||
/// Width of the active window border (default: 20)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub border_width: Option<i32>,
|
||||
/// Offset of the active window border (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub border_offset: Option<Rect>,
|
||||
/// Display an active window border (default: false)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_window_border: Option<bool>,
|
||||
/// Active window border colours for different container types
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_window_border_colours: Option<ActiveWindowBorderColours>,
|
||||
/// Global default workspace padding (default: 10)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_workspace_padding: Option<i32>,
|
||||
/// Global default container padding (default: 10)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_container_padding: Option<i32>,
|
||||
/// Monitor and workspace configurations
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub monitors: Option<Vec<MonitorConfig>>,
|
||||
/// Always send the ALT key when using focus commands (default: false)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub alt_focus_hack: Option<bool>,
|
||||
/// Which Windows signal to use when hiding windows (default: minimize)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub window_hiding_behaviour: Option<HidingBehaviour>,
|
||||
/// Global work area (space used for tiling) offset (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub global_work_area_offset: Option<Rect>,
|
||||
/// Individual window floating rules
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub float_rules: Option<Vec<IdWithIdentifier>>,
|
||||
/// Individual window force-manage rules
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub manage_rules: Option<Vec<IdWithIdentifier>>,
|
||||
/// Identify border overflow applications
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub border_overflow_applications: Option<Vec<IdWithIdentifier>>,
|
||||
/// Identify tray and multi-window applications
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tray_and_multi_window_applications: Option<Vec<IdWithIdentifier>>,
|
||||
/// Identify applications that have the WS_EX_LAYERED extended window style
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub layered_applications: Option<Vec<IdWithIdentifier>>,
|
||||
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub object_name_change_applications: Option<Vec<IdWithIdentifier>>,
|
||||
}
|
||||
|
||||
impl From<&WindowManager> for StaticConfig {
|
||||
fn from(value: &WindowManager) -> Self {
|
||||
let default_invisible_borders = Rect {
|
||||
left: 7,
|
||||
top: 0,
|
||||
right: 14,
|
||||
bottom: 7,
|
||||
};
|
||||
|
||||
let mut monitors = vec![];
|
||||
for m in value.monitors() {
|
||||
monitors.push(MonitorConfig::from(m));
|
||||
}
|
||||
|
||||
let mut to_remove = vec![];
|
||||
|
||||
let workspace_rules = WORKSPACE_RULES.lock();
|
||||
for (m_idx, m) in monitors.iter().enumerate() {
|
||||
for (w_idx, w) in m.workspaces.iter().enumerate() {
|
||||
if let Some(rules) = &w.initial_workspace_rules {
|
||||
for iwsr in rules {
|
||||
for (identifier, (monitor_idx, workspace_idx, _)) in &*workspace_rules {
|
||||
if iwsr.id.eq(identifier)
|
||||
&& (*monitor_idx != m_idx || *workspace_idx != w_idx)
|
||||
{
|
||||
to_remove.push((m_idx, w_idx, iwsr.id.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rules) = &w.workspace_rules {
|
||||
for wsr in rules {
|
||||
for (identifier, (monitor_idx, workspace_idx, _)) in &*workspace_rules {
|
||||
if wsr.id.eq(identifier)
|
||||
&& (*monitor_idx != m_idx || *workspace_idx != w_idx)
|
||||
{
|
||||
to_remove.push((m_idx, w_idx, wsr.id.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (m_idx, w_idx, id) in to_remove {
|
||||
if let Some(monitor) = monitors.get_mut(m_idx) {
|
||||
if let Some(workspace) = monitor.workspaces.get_mut(w_idx) {
|
||||
if let Some(rules) = &mut workspace.workspace_rules {
|
||||
rules.retain(|r| r.id != id);
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut workspace.initial_workspace_rules {
|
||||
rules.retain(|r| r.id != id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
invisible_borders: if value.invisible_borders == default_invisible_borders {
|
||||
None
|
||||
} else {
|
||||
Option::from(value.invisible_borders)
|
||||
},
|
||||
resize_delta: Option::from(value.resize_delta),
|
||||
window_container_behaviour: Option::from(value.window_container_behaviour),
|
||||
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
|
||||
unmanaged_window_operation_behaviour: Option::from(
|
||||
value.unmanaged_window_operation_behaviour,
|
||||
),
|
||||
focus_follows_mouse: value.focus_follows_mouse,
|
||||
mouse_follows_focus: Option::from(value.mouse_follows_focus),
|
||||
app_specific_configuration_path: None,
|
||||
border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)),
|
||||
border_offset: *BORDER_OFFSET.lock(),
|
||||
active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)),
|
||||
active_window_border_colours: None,
|
||||
default_workspace_padding: Option::from(
|
||||
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
||||
),
|
||||
default_container_padding: Option::from(
|
||||
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
|
||||
),
|
||||
monitors: Option::from(monitors),
|
||||
alt_focus_hack: Option::from(ALT_FOCUS_HACK.load(Ordering::SeqCst)),
|
||||
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
|
||||
global_work_area_offset: value.work_area_offset,
|
||||
float_rules: None,
|
||||
manage_rules: None,
|
||||
border_overflow_applications: None,
|
||||
tray_and_multi_window_applications: None,
|
||||
layered_applications: None,
|
||||
object_name_change_applications: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticConfig {
|
||||
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
|
||||
fn apply_globals(&self) -> Result<()> {
|
||||
if let Some(behaviour) = self.window_hiding_behaviour {
|
||||
let mut window_hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
||||
*window_hiding_behaviour = behaviour;
|
||||
}
|
||||
|
||||
if let Some(hack) = self.alt_focus_hack {
|
||||
ALT_FOCUS_HACK.store(hack, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if let Some(container) = self.default_container_padding {
|
||||
DEFAULT_CONTAINER_PADDING.store(container, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if let Some(workspace) = self.default_workspace_padding {
|
||||
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if let Some(width) = self.border_width {
|
||||
BORDER_WIDTH.store(width, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if let Some(offset) = self.border_offset {
|
||||
let mut border_offset = BORDER_OFFSET.lock();
|
||||
*border_offset = Some(offset);
|
||||
}
|
||||
|
||||
if let Some(colours) = &self.active_window_border_colours {
|
||||
BORDER_COLOUR_SINGLE.store(
|
||||
colours.single.r | (colours.single.g << 8) | (colours.single.b << 16),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
BORDER_COLOUR_CURRENT.store(
|
||||
colours.single.r | (colours.single.g << 8) | (colours.single.b << 16),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
BORDER_COLOUR_STACK.store(
|
||||
colours.stack.r | (colours.stack.g << 8) | (colours.stack.b << 16),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
BORDER_COLOUR_MONOCLE.store(
|
||||
colours.monocle.r | (colours.monocle.g << 8) | (colours.monocle.b << 16),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
}
|
||||
|
||||
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
||||
let mut border_overflow_identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
||||
let mut layered_identifiers = LAYERED_WHITELIST.lock();
|
||||
|
||||
if let Some(float) = &self.float_rules {
|
||||
for identifier in float {
|
||||
if !float_identifiers.contains(&identifier.id) {
|
||||
float_identifiers.push(identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(float) = &self.manage_rules {
|
||||
for identifier in float {
|
||||
if !manage_identifiers.contains(&identifier.id) {
|
||||
manage_identifiers.push(identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identifiers) = &self.object_name_change_applications {
|
||||
for identifier in identifiers {
|
||||
if !object_name_change_identifiers.contains(&identifier.id) {
|
||||
object_name_change_identifiers.push(identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identifiers) = &self.layered_applications {
|
||||
for identifier in identifiers {
|
||||
if !layered_identifiers.contains(&identifier.id) {
|
||||
layered_identifiers.push(identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identifiers) = &self.border_overflow_applications {
|
||||
for identifier in identifiers {
|
||||
if !border_overflow_identifiers.contains(&identifier.id) {
|
||||
border_overflow_identifiers.push(identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identifiers) = &self.tray_and_multi_window_applications {
|
||||
for identifier in identifiers {
|
||||
if !tray_and_multi_window_identifiers.contains(&identifier.id) {
|
||||
tray_and_multi_window_identifiers.push(identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = &self.app_specific_configuration_path {
|
||||
let stringified = path.to_string_lossy();
|
||||
let stringified = stringified.replace(
|
||||
"$Env:USERPROFILE",
|
||||
&home_dir().expect("no home dir").to_string_lossy(),
|
||||
);
|
||||
|
||||
let content = std::fs::read_to_string(stringified)?;
|
||||
let asc = ApplicationConfigurationGenerator::load(&content)?;
|
||||
|
||||
for entry in asc {
|
||||
if let Some(float) = entry.float_identifiers {
|
||||
for f in float {
|
||||
if !float_identifiers.contains(&f.id) {
|
||||
float_identifiers.push(f.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(options) = entry.options {
|
||||
for o in options {
|
||||
match o {
|
||||
ApplicationOptions::ObjectNameChange => {
|
||||
if !object_name_change_identifiers.contains(&entry.identifier.id) {
|
||||
object_name_change_identifiers
|
||||
.push(entry.identifier.id.clone());
|
||||
}
|
||||
}
|
||||
ApplicationOptions::Layered => {
|
||||
if !layered_identifiers.contains(&entry.identifier.id) {
|
||||
layered_identifiers.push(entry.identifier.id.clone());
|
||||
}
|
||||
}
|
||||
ApplicationOptions::BorderOverflow => {
|
||||
if !border_overflow_identifiers.contains(&entry.identifier.id) {
|
||||
border_overflow_identifiers.push(entry.identifier.id.clone());
|
||||
}
|
||||
}
|
||||
ApplicationOptions::TrayAndMultiWindow => {
|
||||
if !tray_and_multi_window_identifiers.contains(&entry.identifier.id)
|
||||
{
|
||||
tray_and_multi_window_identifiers
|
||||
.push(entry.identifier.id.clone());
|
||||
}
|
||||
}
|
||||
ApplicationOptions::Force => {
|
||||
if !manage_identifiers.contains(&entry.identifier.id) {
|
||||
manage_identifiers.push(entry.identifier.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn preload(
|
||||
path: &PathBuf,
|
||||
incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||
) -> Result<WindowManager> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let value: Self = serde_json::from_str(&content)?;
|
||||
value.apply_globals()?;
|
||||
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
|
||||
let mut wm = WindowManager {
|
||||
monitors: Ring::default(),
|
||||
monitor_cache: HashMap::new(),
|
||||
incoming_events: incoming,
|
||||
command_listener: listener,
|
||||
is_paused: false,
|
||||
invisible_borders: value.invisible_borders.unwrap_or(Rect {
|
||||
left: 7,
|
||||
top: 0,
|
||||
right: 14,
|
||||
bottom: 7,
|
||||
}),
|
||||
virtual_desktop_id: current_virtual_desktop(),
|
||||
work_area_offset: value.global_work_area_offset,
|
||||
window_container_behaviour: value
|
||||
.window_container_behaviour
|
||||
.unwrap_or(WindowContainerBehaviour::Create),
|
||||
cross_monitor_move_behaviour: value
|
||||
.cross_monitor_move_behaviour
|
||||
.unwrap_or(MoveBehaviour::Swap),
|
||||
unmanaged_window_operation_behaviour: value
|
||||
.unmanaged_window_operation_behaviour
|
||||
.unwrap_or(OperationBehaviour::Op),
|
||||
resize_delta: value.resize_delta.unwrap_or(50),
|
||||
focus_follows_mouse: value.focus_follows_mouse,
|
||||
mouse_follows_focus: value.mouse_follows_focus.unwrap_or(true),
|
||||
hotwatch: Hotwatch::new()?,
|
||||
has_pending_raise_op: false,
|
||||
pending_move_op: None,
|
||||
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;
|
||||
|
||||
wm.hotwatch.watch(path, move |event| match event {
|
||||
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
||||
// a NoticeRemove, presumably because of the use of swap files?
|
||||
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
let mut stream =
|
||||
UnixStream::connect(socket).expect("could not connect to komorebi.sock");
|
||||
stream
|
||||
.write_all(&bytes)
|
||||
.expect("could not write to komorebi.sock");
|
||||
}
|
||||
_ => {}
|
||||
})?;
|
||||
|
||||
Ok(wm)
|
||||
}
|
||||
|
||||
pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> Result<()> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let value: Self = serde_json::from_str(&content)?;
|
||||
let mut wm = wm.lock();
|
||||
|
||||
if let Some(monitors) = value.monitors {
|
||||
for (i, monitor) in monitors.iter().enumerate() {
|
||||
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
||||
m.ensure_workspace_count(monitor.workspaces.len());
|
||||
m.set_work_area_offset(monitor.work_area_offset);
|
||||
|
||||
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
||||
ws.load_static_config(
|
||||
monitor
|
||||
.workspaces
|
||||
.get(j)
|
||||
.expect("no static workspace config"),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (j, ws) in monitor.workspaces.iter().enumerate() {
|
||||
if let Some(rules) = &ws.workspace_rules {
|
||||
for r in rules {
|
||||
wm.handle_workspace_rules(&r.id, i, j, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rules) = &ws.initial_workspace_rules {
|
||||
for r in rules {
|
||||
wm.handle_workspace_rules(&r.id, i, j, true)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if value.active_window_border == Some(true) {
|
||||
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
|
||||
Border::create("komorebi-border-window")?;
|
||||
}
|
||||
|
||||
BORDER_ENABLED.store(true, Ordering::SeqCst);
|
||||
wm.show_border()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reload(path: &PathBuf, wm: &mut WindowManager) -> Result<()> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let value: Self = serde_json::from_str(&content)?;
|
||||
|
||||
value.apply_globals()?;
|
||||
|
||||
if let Some(monitors) = value.monitors {
|
||||
for (i, monitor) in monitors.iter().enumerate() {
|
||||
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
||||
m.ensure_workspace_count(monitor.workspaces.len());
|
||||
m.set_work_area_offset(monitor.work_area_offset);
|
||||
|
||||
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
||||
ws.load_static_config(
|
||||
monitor
|
||||
.workspaces
|
||||
.get(j)
|
||||
.expect("no static workspace config"),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (j, ws) in monitor.workspaces.iter().enumerate() {
|
||||
if let Some(rules) = &ws.workspace_rules {
|
||||
for r in rules {
|
||||
wm.handle_workspace_rules(&r.id, i, j, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rules) = &ws.initial_workspace_rules {
|
||||
for r in rules {
|
||||
wm.handle_workspace_rules(&r.id, i, j, true)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if value.active_window_border == Some(true) {
|
||||
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
|
||||
Border::create("komorebi-border-window")?;
|
||||
}
|
||||
|
||||
BORDER_ENABLED.store(true, Ordering::SeqCst);
|
||||
wm.show_border()?;
|
||||
} else {
|
||||
BORDER_ENABLED.store(false, Ordering::SeqCst);
|
||||
wm.hide_border()?;
|
||||
}
|
||||
|
||||
if let Some(val) = value.invisible_borders {
|
||||
wm.invisible_borders = val;
|
||||
}
|
||||
|
||||
if let Some(val) = value.window_container_behaviour {
|
||||
wm.window_container_behaviour = val;
|
||||
}
|
||||
|
||||
if let Some(val) = value.cross_monitor_move_behaviour {
|
||||
wm.cross_monitor_move_behaviour = val;
|
||||
}
|
||||
|
||||
if let Some(val) = value.unmanaged_window_operation_behaviour {
|
||||
wm.unmanaged_window_operation_behaviour = val;
|
||||
}
|
||||
|
||||
if let Some(val) = value.resize_delta {
|
||||
wm.resize_delta = val;
|
||||
}
|
||||
|
||||
if let Some(val) = value.mouse_follows_focus {
|
||||
wm.mouse_follows_focus = val;
|
||||
}
|
||||
|
||||
wm.work_area_offset = value.global_work_area_offset;
|
||||
wm.focus_follows_mouse = value.focus_follows_mouse;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,63 @@
|
||||
use bitflags::bitflags;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_CHILDWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_CLIPCHILDREN;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_CLIPSIBLINGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_DLGFRAME;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_ACCEPTFILES;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_APPWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_CLIENTEDGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_COMPOSITED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTEXTHELP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTROLPARENT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_DLGMODALFRAME;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYOUTRTL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFTSCROLLBAR;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LTRREADING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_MDICHILD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOINHERITLAYOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOPARENTNOTIFY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_PALETTEWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHTSCROLLBAR;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_RTLREADING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_STATICEDGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TRANSPARENT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_WINDOWEDGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_GROUP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_HSCROLL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_ICONIC;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUPWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_SIZEBOX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_TABSTOP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_THICKFRAME;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_TILED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_TILEDWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
|
||||
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CHILDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CLIPCHILDREN;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CLIPSIBLINGS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_DLGFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_ACCEPTFILES;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_APPWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CLIENTEDGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_COMPOSITED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTEXTHELP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTROLPARENT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_DLGMODALFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYOUTRTL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFTSCROLLBAR;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LTRREADING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_MDICHILD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOINHERITLAYOUT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOPARENTNOTIFY;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_PALETTEWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHTSCROLLBAR;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RTLREADING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_STATICEDGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TRANSPARENT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_WINDOWEDGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_GROUP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_HSCROLL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_ICONIC;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_POPUPWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_SIZEBOX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TABSTOP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_THICKFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TILED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TILEDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct GwlStyle: u32 {
|
||||
pub struct WindowStyle: u32 {
|
||||
const BORDER = WS_BORDER.0;
|
||||
const CAPTION = WS_CAPTION.0;
|
||||
const CHILD = WS_CHILD.0;
|
||||
@@ -88,9 +88,10 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct GwlExStyle: u32 {
|
||||
pub struct ExtendedWindowStyle: u32 {
|
||||
const ACCEPTFILES = WS_EX_ACCEPTFILES.0;
|
||||
const APPWINDOW = WS_EX_APPWINDOW.0;
|
||||
const CLIENTEDGE = WS_EX_CLIENTEDGE.0;
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
use crate::com::SetCloak;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::fmt::Write as _;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::ser::Error;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use winput::press;
|
||||
use winput::release;
|
||||
use winput::Vk;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use komorebi_core::HidingBehaviour;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::styles::GwlExStyle;
|
||||
use crate::styles::GwlStyle;
|
||||
use crate::styles::ExtendedWindowStyle;
|
||||
use crate::styles::WindowStyle;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::ALT_FOCUS_HACK;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::PERMAIGNORE_CLASSES;
|
||||
use crate::WSL2_UI_PROCESSES;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, JsonSchema)]
|
||||
pub struct Window {
|
||||
pub(crate) hwnd: isize,
|
||||
}
|
||||
@@ -31,20 +45,20 @@ impl Display for Window {
|
||||
let mut display = format!("(hwnd: {}", self.hwnd);
|
||||
|
||||
if let Ok(title) = self.title() {
|
||||
display.push_str(&format!(", title: {}", title));
|
||||
write!(display, ", title: {title}")?;
|
||||
}
|
||||
|
||||
if let Ok(exe) = self.exe() {
|
||||
display.push_str(&format!(", exe: {}", exe));
|
||||
write!(display, ", exe: {exe}")?;
|
||||
}
|
||||
|
||||
if let Ok(class) = self.class() {
|
||||
display.push_str(&format!(", class: {}", class));
|
||||
write!(display, ", class: {class}")?;
|
||||
}
|
||||
|
||||
display.push(')');
|
||||
write!(display, ")")?;
|
||||
|
||||
write!(f, "{}", display)
|
||||
write!(f, "{display}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +69,28 @@ impl Serialize for Window {
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Window", 5)?;
|
||||
state.serialize_field("hwnd", &self.hwnd)?;
|
||||
state.serialize_field("title", &self.title().expect("could not get window title"))?;
|
||||
state.serialize_field("exe", &self.exe().expect("could not get window exe"))?;
|
||||
state.serialize_field("class", &self.class().expect("could not get window class"))?;
|
||||
state.serialize_field(
|
||||
"title",
|
||||
&self
|
||||
.title()
|
||||
.map_err(|_| S::Error::custom("could not get window title"))?,
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"exe",
|
||||
&self
|
||||
.exe()
|
||||
.map_err(|_| S::Error::custom("could not get window exe"))?,
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"class",
|
||||
&self
|
||||
.class()
|
||||
.map_err(|_| S::Error::custom("could not get window class"))?,
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"rect",
|
||||
&WindowsApi::window_rect(self.hwnd()).expect("could not get window rect"),
|
||||
&WindowsApi::window_rect(self.hwnd())
|
||||
.map_err(|_| S::Error::custom("could not get window rect"))?,
|
||||
)?;
|
||||
state.end()
|
||||
}
|
||||
@@ -121,7 +151,12 @@ impl Window {
|
||||
programmatically_hidden_hwnds.push(self.hwnd);
|
||||
}
|
||||
|
||||
WindowsApi::hide_window(self.hwnd());
|
||||
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
||||
match *hiding_behaviour {
|
||||
HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd()),
|
||||
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd()),
|
||||
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(self) {
|
||||
@@ -133,7 +168,21 @@ impl Window {
|
||||
programmatically_hidden_hwnds.remove(idx);
|
||||
}
|
||||
|
||||
WindowsApi::restore_window(self.hwnd());
|
||||
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
||||
match *hiding_behaviour {
|
||||
HidingBehaviour::Hide | HidingBehaviour::Minimize => {
|
||||
WindowsApi::restore_window(self.hwnd());
|
||||
}
|
||||
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn minimize(self) {
|
||||
WindowsApi::minimize_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn close(self) -> Result<()> {
|
||||
WindowsApi::close_window(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn maximize(self) {
|
||||
@@ -148,64 +197,184 @@ impl Window {
|
||||
WindowsApi::maximize_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn raise(self) -> Result<()> {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
let current_thread_id = WindowsApi::current_thread_id();
|
||||
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
|
||||
pub fn unmaximize(self) {
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if let Some(idx) = programmatically_hidden_hwnds
|
||||
.iter()
|
||||
.position(|&hwnd| hwnd == self.hwnd)
|
||||
{
|
||||
programmatically_hidden_hwnds.remove(idx);
|
||||
}
|
||||
|
||||
// Raise Window to foreground
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set as foreground window, but continuing execution of focus(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
WindowsApi::set_focus(self.hwnd())
|
||||
WindowsApi::unmaximize_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn focus(self) -> Result<()> {
|
||||
pub fn raise(self) {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
let current_thread_id = WindowsApi::current_thread_id();
|
||||
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
|
||||
|
||||
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
|
||||
// hook has been installed
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
|
||||
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not attach to window thread input processing mechanism, but continuing execution of raise(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Raise Window to foreground
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set as foreground window, but continuing execution of focus(): {}",
|
||||
"could not set as foreground window, but continuing execution of raise(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
match WindowsApi::set_focus(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set focus, but continuing execution of raise(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, false) {
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not detach from window thread input processing mechanism, but continuing execution of raise(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
let current_thread_id = WindowsApi::current_thread_id();
|
||||
|
||||
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
|
||||
// hook has been installed
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
|
||||
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not attach to window thread input processing mechanism, but continuing execution of focus(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Raise Window to foreground
|
||||
let mut foregrounded = false;
|
||||
let mut tried_resetting_foreground_access = false;
|
||||
let mut max_attempts = 10;
|
||||
|
||||
let hotkey_uses_alt = WindowsApi::alt_is_pressed();
|
||||
while !foregrounded && max_attempts > 0 {
|
||||
if ALT_FOCUS_HACK.load(Ordering::SeqCst) {
|
||||
press(Vk::Alt);
|
||||
}
|
||||
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
Ok(_) => {
|
||||
foregrounded = true;
|
||||
}
|
||||
Err(error) => {
|
||||
max_attempts -= 1;
|
||||
tracing::error!(
|
||||
"could not set as foreground window, but continuing execution of focus(): {}",
|
||||
error
|
||||
);
|
||||
|
||||
// If this still doesn't work then maybe try https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-locksetforegroundwindow
|
||||
if !tried_resetting_foreground_access {
|
||||
let process_id = WindowsApi::current_process_id();
|
||||
if WindowsApi::allow_set_foreground_window(process_id).is_ok() {
|
||||
tried_resetting_foreground_access = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ALT_FOCUS_HACK.load(Ordering::SeqCst) && !hotkey_uses_alt {
|
||||
release(Vk::Alt);
|
||||
}
|
||||
}
|
||||
|
||||
// Center cursor in Window
|
||||
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
||||
if mouse_follows_focus {
|
||||
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
||||
}
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
WindowsApi::set_focus(self.hwnd())
|
||||
match WindowsApi::set_focus(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set focus, but continuing execution of focus(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, false) {
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not detach from window thread input processing mechanism, but continuing execution of focus(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transparent(self) -> Result<()> {
|
||||
let mut ex_style = self.ex_style()?;
|
||||
ex_style.insert(ExtendedWindowStyle::LAYERED);
|
||||
self.update_ex_style(&ex_style)?;
|
||||
WindowsApi::set_transparent(self.hwnd());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn opaque(self) -> Result<()> {
|
||||
let mut ex_style = self.ex_style()?;
|
||||
ex_style.remove(ExtendedWindowStyle::LAYERED);
|
||||
self.update_ex_style(&ex_style)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_style(self, style: GwlStyle) -> Result<()> {
|
||||
pub fn update_style(self, style: &WindowStyle) -> Result<()> {
|
||||
WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?)
|
||||
}
|
||||
|
||||
pub fn style(self) -> Result<GwlStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
|
||||
GwlStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
|
||||
pub fn update_ex_style(self, style: &ExtendedWindowStyle) -> Result<()> {
|
||||
WindowsApi::update_ex_style(self.hwnd(), isize::try_from(style.bits())?)
|
||||
}
|
||||
|
||||
pub fn ex_style(self) -> Result<GwlExStyle> {
|
||||
pub fn style(self) -> Result<WindowStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
|
||||
WindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
|
||||
}
|
||||
|
||||
pub fn ex_style(self) -> Result<ExtendedWindowStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
|
||||
GwlExStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
|
||||
ExtendedWindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
|
||||
}
|
||||
|
||||
pub fn title(self) -> Result<String> {
|
||||
@@ -214,7 +383,10 @@ impl Window {
|
||||
|
||||
pub fn exe(self) -> Result<String> {
|
||||
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
WindowsApi::exe(WindowsApi::process_handle(process_id)?)
|
||||
let handle = WindowsApi::process_handle(process_id)?;
|
||||
let exe = WindowsApi::exe(handle);
|
||||
WindowsApi::close_process(handle)?;
|
||||
exe
|
||||
}
|
||||
|
||||
pub fn class(self) -> Result<String> {
|
||||
@@ -229,12 +401,27 @@ impl Window {
|
||||
WindowsApi::is_window(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn remove_title_bar(self) -> Result<()> {
|
||||
let mut style = self.style()?;
|
||||
style.remove(WindowStyle::CAPTION);
|
||||
style.remove(WindowStyle::THICKFRAME);
|
||||
self.update_style(&style)
|
||||
}
|
||||
|
||||
pub fn add_title_bar(self) -> Result<()> {
|
||||
let mut style = self.style()?;
|
||||
style.insert(WindowStyle::CAPTION);
|
||||
style.insert(WindowStyle::THICKFRAME);
|
||||
self.update_style(&style)
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(exe, title))]
|
||||
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
|
||||
if let Some(WindowManagerEvent::MonitorPoll(_, _)) = event {
|
||||
if let Some(WindowManagerEvent::DisplayChange(_)) = event {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
#[allow(clippy::question_mark)]
|
||||
if self.title().is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -242,8 +429,14 @@ impl Window {
|
||||
let is_cloaked = self.is_cloaked()?;
|
||||
|
||||
let mut allow_cloaked = false;
|
||||
if let Some(WindowManagerEvent::Hide(_, _)) = event {
|
||||
allow_cloaked = true;
|
||||
|
||||
if let Some(event) = event {
|
||||
if matches!(
|
||||
event,
|
||||
WindowManagerEvent::Hide(_, _) | WindowManagerEvent::Cloak(_, _)
|
||||
) {
|
||||
allow_cloaked = true;
|
||||
}
|
||||
}
|
||||
|
||||
match (allow_cloaked, is_cloaked) {
|
||||
@@ -252,42 +445,7 @@ impl Window {
|
||||
// If not allowing cloaked windows, we need to ensure the window is not cloaked
|
||||
(false, false) => {
|
||||
if let (Ok(title), Ok(exe_name), Ok(class)) = (self.title(), self.exe(), self.class()) {
|
||||
{
|
||||
let float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||
if float_identifiers.contains(&title)
|
||||
|| float_identifiers.contains(&exe_name)
|
||||
|| float_identifiers.contains(&class) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
let managed_override = {
|
||||
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
manage_identifiers.contains(&exe_name) || manage_identifiers.contains(&class)
|
||||
};
|
||||
|
||||
let allow_layered = {
|
||||
let layered_exe_whitelist = LAYERED_EXE_WHITELIST.lock();
|
||||
layered_exe_whitelist.contains(&exe_name)
|
||||
};
|
||||
|
||||
let style = self.style()?;
|
||||
let ex_style = self.ex_style()?;
|
||||
|
||||
if style.contains(GwlStyle::CAPTION)
|
||||
&& ex_style.contains(GwlExStyle::WINDOWEDGE)
|
||||
&& !ex_style.contains(GwlExStyle::DLGMODALFRAME)
|
||||
// Get a lot of dupe events coming through that make the redrawing go crazy
|
||||
// on FocusChange events if I don't filter out this one. But, if we are
|
||||
// allowing a specific layered window on the whitelist (like Steam), it should
|
||||
// pass this check
|
||||
&& (allow_layered || !ex_style.contains(GwlExStyle::LAYERED))
|
||||
|| managed_override
|
||||
{
|
||||
return Ok(true);
|
||||
} else if event.is_some() {
|
||||
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
|
||||
}
|
||||
return Ok(window_is_eligible(&title, &exe_name, &class, &self.style()?, &self.ex_style()?, event));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -296,3 +454,85 @@ impl Window {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn window_is_eligible(
|
||||
title: &String,
|
||||
exe_name: &String,
|
||||
class: &String,
|
||||
style: &WindowStyle,
|
||||
ex_style: &ExtendedWindowStyle,
|
||||
event: Option<WindowManagerEvent>,
|
||||
) -> bool {
|
||||
{
|
||||
let permaignore_classes = PERMAIGNORE_CLASSES.lock();
|
||||
if permaignore_classes.contains(class) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut should_float = false;
|
||||
|
||||
{
|
||||
let float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||
for identifier in float_identifiers.iter() {
|
||||
if title.starts_with(identifier) || title.ends_with(identifier) {
|
||||
should_float = true;
|
||||
}
|
||||
|
||||
if class.starts_with(identifier) || class.ends_with(identifier) {
|
||||
should_float = true;
|
||||
}
|
||||
|
||||
if identifier == exe_name {
|
||||
should_float = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let managed_override = {
|
||||
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
manage_identifiers.contains(exe_name)
|
||||
|| manage_identifiers.contains(class)
|
||||
|| manage_identifiers.contains(title)
|
||||
};
|
||||
|
||||
if should_float && !managed_override {
|
||||
return false;
|
||||
}
|
||||
|
||||
let allow_layered = {
|
||||
let layered_whitelist = LAYERED_WHITELIST.lock();
|
||||
layered_whitelist.contains(exe_name)
|
||||
|| layered_whitelist.contains(class)
|
||||
|| layered_whitelist.contains(title)
|
||||
};
|
||||
|
||||
// TODO: might need this for transparency
|
||||
// let allow_layered = true;
|
||||
|
||||
let allow_wsl2_gui = {
|
||||
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
|
||||
wsl2_ui_processes.contains(exe_name)
|
||||
};
|
||||
|
||||
let allow_titlebar_removed = {
|
||||
let titlebars_removed = NO_TITLEBAR.lock();
|
||||
titlebars_removed.contains(exe_name)
|
||||
};
|
||||
|
||||
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
|
||||
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
|
||||
// Get a lot of dupe events coming through that make the redrawing go crazy
|
||||
// on FocusChange events if I don't filter out this one. But, if we are
|
||||
// allowing a specific layered window on the whitelist (like Steam), it should
|
||||
// pass this check
|
||||
&& (allow_layered || !ex_style.contains(ExtendedWindowStyle::LAYERED))
|
||||
|| managed_override
|
||||
{
|
||||
return true;
|
||||
} else if event.is_some() {
|
||||
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +1,79 @@
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::window::Window;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum WindowManagerEvent {
|
||||
Destroy(WinEvent, Window),
|
||||
FocusChange(WinEvent, Window),
|
||||
Hide(WinEvent, Window),
|
||||
Cloak(WinEvent, Window),
|
||||
Minimize(WinEvent, Window),
|
||||
Show(WinEvent, Window),
|
||||
Uncloak(WinEvent, Window),
|
||||
MoveResizeStart(WinEvent, Window),
|
||||
MoveResizeEnd(WinEvent, Window),
|
||||
MouseCapture(WinEvent, Window),
|
||||
Manage(Window),
|
||||
Unmanage(Window),
|
||||
Raise(Window),
|
||||
MonitorPoll(WinEvent, Window),
|
||||
DisplayChange(Window),
|
||||
}
|
||||
|
||||
impl Display for WindowManagerEvent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WindowManagerEvent::Manage(window) => {
|
||||
write!(f, "Manage (Window: {})", window)
|
||||
Self::Manage(window) => {
|
||||
write!(f, "Manage (Window: {window})")
|
||||
}
|
||||
WindowManagerEvent::Unmanage(window) => {
|
||||
write!(f, "Unmanage (Window: {})", window)
|
||||
Self::Unmanage(window) => {
|
||||
write!(f, "Unmanage (Window: {window})")
|
||||
}
|
||||
WindowManagerEvent::Destroy(winevent, window) => {
|
||||
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
|
||||
Self::Destroy(winevent, window) => {
|
||||
write!(f, "Destroy (WinEvent: {winevent}, Window: {window})")
|
||||
}
|
||||
WindowManagerEvent::FocusChange(winevent, window) => {
|
||||
Self::FocusChange(winevent, window) => {
|
||||
write!(f, "FocusChange (WinEvent: {winevent}, Window: {window})",)
|
||||
}
|
||||
Self::Hide(winevent, window) => {
|
||||
write!(f, "Hide (WinEvent: {winevent}, Window: {window})")
|
||||
}
|
||||
Self::Cloak(winevent, window) => {
|
||||
write!(f, "Cloak (WinEvent: {winevent}, Window: {window})")
|
||||
}
|
||||
Self::Minimize(winevent, window) => {
|
||||
write!(f, "Minimize (WinEvent: {winevent}, Window: {window})")
|
||||
}
|
||||
Self::Show(winevent, window) => {
|
||||
write!(f, "Show (WinEvent: {winevent}, Window: {window})")
|
||||
}
|
||||
Self::Uncloak(winevent, window) => {
|
||||
write!(f, "Uncloak (WinEvent: {winevent}, Window: {window})")
|
||||
}
|
||||
Self::MoveResizeStart(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"FocusChange (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
"MoveResizeStart (WinEvent: {winevent}, Window: {window})",
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::Hide(winevent, window) => {
|
||||
write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window)
|
||||
Self::MoveResizeEnd(winevent, window) => {
|
||||
write!(f, "MoveResizeEnd (WinEvent: {winevent}, Window: {window})",)
|
||||
}
|
||||
WindowManagerEvent::Minimize(winevent, window) => {
|
||||
write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window)
|
||||
Self::MouseCapture(winevent, window) => {
|
||||
write!(f, "MouseCapture (WinEvent: {winevent}, Window: {window})",)
|
||||
}
|
||||
WindowManagerEvent::Show(winevent, window) => {
|
||||
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
|
||||
Self::Raise(window) => {
|
||||
write!(f, "Raise (Window: {window})")
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MoveResizeEnd (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::MouseCapture(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MouseCapture (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::Raise(window) => {
|
||||
write!(f, "Raise (Window: {})", window)
|
||||
}
|
||||
WindowManagerEvent::MonitorPoll(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MonitorPoll (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
Self::DisplayChange(window) => {
|
||||
write!(f, "DisplayChange (Window: {window})")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,17 +82,20 @@ impl Display for WindowManagerEvent {
|
||||
impl WindowManagerEvent {
|
||||
pub const fn window(self) -> Window {
|
||||
match self {
|
||||
WindowManagerEvent::Destroy(_, window)
|
||||
| WindowManagerEvent::FocusChange(_, window)
|
||||
| WindowManagerEvent::Hide(_, window)
|
||||
| WindowManagerEvent::Minimize(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window)
|
||||
| WindowManagerEvent::MouseCapture(_, window)
|
||||
| WindowManagerEvent::MonitorPoll(_, window)
|
||||
| WindowManagerEvent::Raise(window)
|
||||
| WindowManagerEvent::Manage(window)
|
||||
| WindowManagerEvent::Unmanage(window) => window,
|
||||
Self::Destroy(_, window)
|
||||
| Self::FocusChange(_, window)
|
||||
| Self::Hide(_, window)
|
||||
| Self::Cloak(_, window)
|
||||
| Self::Minimize(_, window)
|
||||
| Self::Show(_, window)
|
||||
| Self::Uncloak(_, window)
|
||||
| Self::MoveResizeStart(_, window)
|
||||
| Self::MoveResizeEnd(_, window)
|
||||
| Self::MouseCapture(_, window)
|
||||
| Self::Raise(window)
|
||||
| Self::Manage(window)
|
||||
| Self::DisplayChange(window)
|
||||
| Self::Unmanage(window) => window,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,19 +103,21 @@ impl WindowManagerEvent {
|
||||
match winevent {
|
||||
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),
|
||||
|
||||
WinEvent::ObjectCloaked | WinEvent::ObjectHide => {
|
||||
Option::from(Self::Hide(winevent, window))
|
||||
}
|
||||
WinEvent::ObjectHide => Option::from(Self::Hide(winevent, window)),
|
||||
WinEvent::ObjectCloaked => Option::from(Self::Cloak(winevent, window)),
|
||||
|
||||
WinEvent::SystemMinimizeStart => Option::from(Self::Minimize(winevent, window)),
|
||||
|
||||
WinEvent::ObjectShow | WinEvent::ObjectUncloaked | WinEvent::SystemMinimizeEnd => {
|
||||
WinEvent::ObjectShow | WinEvent::SystemMinimizeEnd => {
|
||||
Option::from(Self::Show(winevent, window))
|
||||
}
|
||||
|
||||
WinEvent::ObjectUncloaked => Option::from(Self::Uncloak(winevent, window)),
|
||||
|
||||
WinEvent::ObjectFocus | WinEvent::SystemForeground => {
|
||||
Option::from(Self::FocusChange(winevent, window))
|
||||
}
|
||||
WinEvent::SystemMoveSizeStart => Option::from(Self::MoveResizeStart(winevent, window)),
|
||||
WinEvent::SystemMoveSizeEnd => Option::from(Self::MoveResizeEnd(winevent, window)),
|
||||
WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => {
|
||||
Option::from(Self::MouseCapture(winevent, window))
|
||||
@@ -124,23 +132,15 @@ impl WindowManagerEvent {
|
||||
|
||||
let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
||||
|
||||
if object_name_change_on_launch.contains(&window.exe().ok()?) {
|
||||
if object_name_change_on_launch.contains(&window.exe().ok()?)
|
||||
|| object_name_change_on_launch.contains(&window.class().ok()?)
|
||||
|| object_name_change_on_launch.contains(&window.title().ok()?)
|
||||
{
|
||||
Option::from(Self::Show(winevent, window))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
WinEvent::ObjectCreate => {
|
||||
if let Ok(title) = window.title() {
|
||||
// Hidden COM support mechanism window that fires this event on both DPI/scaling
|
||||
// changes and resolution changes, a good candidate for polling
|
||||
if title == "OLEChannelWnd" {
|
||||
return Option::from(Self::MonitorPoll(winevent, window));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,131 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use windows::core::Result as WindowsCrateResult;
|
||||
use windows::core::PCSTR;
|
||||
use windows::core::PWSTR;
|
||||
use windows::Win32::Foundation::CloseHandle;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::COLORREF;
|
||||
use windows::Win32::Foundation::HANDLE;
|
||||
use windows::Win32::Foundation::HMODULE;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Foundation::POINT;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
|
||||
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
|
||||
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
||||
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
||||
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
||||
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
||||
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||
use windows::Win32::Graphics::Gdi::HBRUSH;
|
||||
use windows::Win32::Graphics::Gdi::HDC;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
|
||||
use windows::Win32::System::Threading::AttachThreadInput;
|
||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use windows::Win32::System::Threading::OpenProcess;
|
||||
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
||||
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
|
||||
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
|
||||
use windows::Win32::UI::Shell::Common::DEVICE_SCALE_FACTOR;
|
||||
use windows::Win32::UI::Shell::GetScaleFactorForMonitor;
|
||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::RegisterClassA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
|
||||
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
|
||||
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::BOOL;
|
||||
use bindings::Windows::Win32::Foundation::HANDLE;
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::Foundation::LPARAM;
|
||||
use bindings::Windows::Win32::Foundation::POINT;
|
||||
use bindings::Windows::Win32::Foundation::PWSTR;
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HDC;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MONITORINFO;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use bindings::Windows::Win32::System::Threading::AttachThreadInput;
|
||||
use bindings::Windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use bindings::Windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use bindings::Windows::Win32::System::Threading::OpenProcess;
|
||||
use bindings::Windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||
use bindings::Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
||||
use bindings::Windows::Win32::System::Threading::PROCESS_NAME_FORMAT;
|
||||
use bindings::Windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::SetFocus;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsIconic;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
@@ -86,42 +134,14 @@ use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::set_window_position::SetWindowPosition;
|
||||
use crate::windows_callbacks;
|
||||
use crate::BORDER_HWND;
|
||||
use crate::TRANSPARENCY_COLOUR;
|
||||
|
||||
pub enum WindowsResult<T, E> {
|
||||
Err(E),
|
||||
Ok(T),
|
||||
}
|
||||
|
||||
impl From<BOOL> for WindowsResult<(), Error> {
|
||||
fn from(return_value: BOOL) -> Self {
|
||||
if return_value.as_bool() {
|
||||
Self::Ok(())
|
||||
} else {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HWND> for WindowsResult<isize, Error> {
|
||||
fn from(return_value: HWND) -> Self {
|
||||
if return_value.is_null() {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Self::Ok(return_value.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HANDLE> for WindowsResult<HANDLE, Error> {
|
||||
fn from(return_value: HANDLE) -> Self {
|
||||
if return_value.is_null() {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Self::Ok(return_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_integer_for_windows_result {
|
||||
( $( $integer_type:ty ),+ ) => {
|
||||
$(
|
||||
@@ -137,7 +157,7 @@ macro_rules! impl_from_integer_for_windows_result {
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_integer_for_windows_result!(isize, u32, i32);
|
||||
impl_from_integer_for_windows_result!(usize, isize, u16, u32, i32);
|
||||
|
||||
impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
|
||||
fn from(result: WindowsResult<T, E>) -> Self {
|
||||
@@ -148,6 +168,41 @@ impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ProcessWindowsCrateResult<T> {
|
||||
fn process(self) -> Result<T>;
|
||||
}
|
||||
|
||||
macro_rules! impl_process_windows_crate_integer_wrapper_result {
|
||||
( $($input:ty => $deref:ty),+ $(,)? ) => (
|
||||
paste::paste! {
|
||||
$(
|
||||
impl ProcessWindowsCrateResult<$deref> for $input {
|
||||
fn process(self) -> Result<$deref> {
|
||||
if self == $input(0) {
|
||||
Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
impl_process_windows_crate_integer_wrapper_result!(
|
||||
HWND => isize,
|
||||
);
|
||||
|
||||
impl<T> ProcessWindowsCrateResult<T> for WindowsCrateResult<T> {
|
||||
fn process(self) -> Result<T> {
|
||||
match self {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => Err(error.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsApi;
|
||||
|
||||
impl WindowsApi {
|
||||
@@ -155,22 +210,17 @@ impl WindowsApi {
|
||||
callback: MONITORENUMPROC,
|
||||
callback_data_address: isize,
|
||||
) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
EnumDisplayMonitors(
|
||||
HDC(0),
|
||||
std::ptr::null_mut(),
|
||||
Option::from(callback),
|
||||
LPARAM(callback_data_address),
|
||||
)
|
||||
}))
|
||||
unsafe { EnumDisplayMonitors(HDC(0), None, callback, LPARAM(callback_data_address)) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn valid_hmonitors() -> Result<Vec<isize>> {
|
||||
let mut monitors: Vec<isize> = vec![];
|
||||
let monitors_ref: &mut Vec<isize> = monitors.as_mut();
|
||||
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
|
||||
let mut monitors: Vec<(String, isize)> = vec![];
|
||||
let monitors_ref: &mut Vec<(String, isize)> = monitors.as_mut();
|
||||
Self::enum_display_monitors(
|
||||
windows_callbacks::valid_display_monitors,
|
||||
monitors_ref as *mut Vec<isize> as isize,
|
||||
Option::Some(windows_callbacks::valid_display_monitors),
|
||||
monitors_ref as *mut Vec<(String, isize)> as isize,
|
||||
)?;
|
||||
|
||||
Ok(monitors)
|
||||
@@ -178,24 +228,24 @@ impl WindowsApi {
|
||||
|
||||
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||
Self::enum_display_monitors(
|
||||
windows_callbacks::enum_display_monitor,
|
||||
Option::Some(windows_callbacks::enum_display_monitor),
|
||||
monitors as *mut Ring<Monitor> as isize,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
EnumWindows(Option::from(callback), LPARAM(callback_data_address))
|
||||
}))
|
||||
unsafe { EnumWindows(callback, LPARAM(callback_data_address)) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||
for monitor in monitors.elements_mut() {
|
||||
let monitor_id = monitor.id();
|
||||
let monitor_name = monitor.name().clone();
|
||||
if let Some(workspace) = monitor.workspaces_mut().front_mut() {
|
||||
// EnumWindows will enumerate through windows on all monitors
|
||||
Self::enum_windows(
|
||||
windows_callbacks::enum_window,
|
||||
Option::Some(windows_callbacks::enum_window),
|
||||
workspace.containers_mut() as *mut VecDeque<Container> as isize,
|
||||
)?;
|
||||
|
||||
@@ -209,7 +259,7 @@ impl WindowsApi {
|
||||
|
||||
for container in workspace.containers_mut() {
|
||||
for window in container.windows() {
|
||||
if Self::monitor_from_window(window.hwnd()) != monitor_id {
|
||||
if Self::monitor_name_from_window(window.hwnd())? != monitor_name {
|
||||
windows_on_other_monitors.push(window.hwnd().0);
|
||||
}
|
||||
}
|
||||
@@ -225,9 +275,9 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
pub fn allow_set_foreground_window(process_id: u32) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
AllowSetForegroundWindow(process_id)
|
||||
}))
|
||||
unsafe { AllowSetForegroundWindow(process_id) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn monitor_from_window(hwnd: HWND) -> isize {
|
||||
@@ -236,6 +286,16 @@ impl WindowsApi {
|
||||
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
|
||||
}
|
||||
|
||||
pub fn monitor_name_from_window(hwnd: HWND) -> Result<String> {
|
||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
Ok(
|
||||
Self::monitor(unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0)?
|
||||
.name()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn monitor_from_point(point: POINT) -> isize {
|
||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
@@ -243,14 +303,46 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
|
||||
let flags = SetWindowPosition::NO_ACTIVATE;
|
||||
let flags = SetWindowPosition::NO_ACTIVATE
|
||||
| SetWindowPosition::NO_SEND_CHANGING
|
||||
| SetWindowPosition::NO_COPY_BITS
|
||||
| SetWindowPosition::FRAME_CHANGED;
|
||||
|
||||
let position = if top { HWND_TOPMOST } else { HWND_NOTOPMOST };
|
||||
let position = if top { HWND_TOPMOST } else { HWND_BOTTOM };
|
||||
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
||||
}
|
||||
|
||||
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
|
||||
unsafe { BringWindowToTop(hwnd) }.ok().process()
|
||||
}
|
||||
|
||||
pub fn raise_window(hwnd: HWND) -> Result<()> {
|
||||
let flags = SetWindowPosition::NO_MOVE;
|
||||
|
||||
let position = HWND_TOPMOST;
|
||||
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||
}
|
||||
|
||||
pub fn position_border_window(hwnd: HWND, layout: &Rect, activate: bool) -> Result<()> {
|
||||
let flags = if activate {
|
||||
SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE
|
||||
} else {
|
||||
SetWindowPosition::NO_ACTIVATE
|
||||
};
|
||||
|
||||
let position = HWND_NOTOPMOST;
|
||||
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
||||
}
|
||||
|
||||
pub fn hide_border_window(hwnd: HWND) -> Result<()> {
|
||||
let flags = SetWindowPosition::HIDE_WINDOW;
|
||||
|
||||
let position = HWND_BOTTOM;
|
||||
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||
}
|
||||
|
||||
pub fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
position,
|
||||
@@ -260,7 +352,9 @@ impl WindowsApi {
|
||||
layout.bottom,
|
||||
SET_WINDOW_POS_FLAGS(flags),
|
||||
)
|
||||
}))
|
||||
}
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
|
||||
@@ -269,12 +363,33 @@ impl WindowsApi {
|
||||
unsafe { ShowWindow(hwnd, command) };
|
||||
}
|
||||
|
||||
pub fn minimize_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_MINIMIZE);
|
||||
}
|
||||
|
||||
fn post_message(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> Result<()> {
|
||||
unsafe { PostMessageW(hwnd, message, wparam, lparam) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn close_window(hwnd: HWND) -> Result<()> {
|
||||
match Self::post_message(hwnd, WM_CLOSE, WPARAM(0), LPARAM(0)) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(anyhow!("could not close window")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_HIDE);
|
||||
}
|
||||
|
||||
pub fn restore_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_RESTORE);
|
||||
Self::show_window(hwnd, SW_SHOWNOACTIVATE);
|
||||
}
|
||||
|
||||
pub fn unmaximize_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_NORMAL);
|
||||
}
|
||||
|
||||
pub fn maximize_window(hwnd: HWND) {
|
||||
@@ -282,39 +397,25 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
pub fn foreground_window() -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { GetForegroundWindow() }))
|
||||
unsafe { GetForegroundWindow() }.process()
|
||||
}
|
||||
|
||||
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
|
||||
match WindowsResult::from(unsafe { SetForegroundWindow(hwnd) }) {
|
||||
WindowsResult::Ok(_) => Ok(()),
|
||||
WindowsResult::Err(error) => {
|
||||
// TODO: Figure out the odd behaviour here, docs state that a zero value means
|
||||
// TODO: that the window was not brought to the foreground, but this contradicts
|
||||
// TODO: the behaviour that I have observed which resulted in this check
|
||||
if error.to_string() == "The operation completed successfully. (os error 0)" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { SetForegroundWindow(hwnd) }.ok().process()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn top_window() -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { GetTopWindow(HWND::NULL).0 }))
|
||||
unsafe { GetTopWindow(HWND::default()) }.process()
|
||||
}
|
||||
|
||||
pub fn desktop_window() -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { GetDesktopWindow() }))
|
||||
unsafe { GetDesktopWindow() }.process()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn next_window(hwnd: HWND) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetWindow(hwnd, GW_HWNDNEXT).0
|
||||
}))
|
||||
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -335,30 +436,24 @@ impl WindowsApi {
|
||||
|
||||
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
|
||||
let mut rect = unsafe { std::mem::zeroed() };
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetWindowRect(hwnd, &mut rect)
|
||||
}))?;
|
||||
unsafe { GetWindowRect(hwnd, &mut rect) }.ok().process()?;
|
||||
|
||||
Ok(Rect::from(rect))
|
||||
}
|
||||
|
||||
fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe { SetCursorPos(x, y) }))
|
||||
unsafe { SetCursorPos(x, y) }.ok().process()
|
||||
}
|
||||
|
||||
pub fn cursor_pos() -> Result<POINT> {
|
||||
let mut cursor_pos: POINT = unsafe { std::mem::zeroed() };
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetCursorPos(&mut cursor_pos)
|
||||
}))?;
|
||||
let mut cursor_pos = POINT::default();
|
||||
unsafe { GetCursorPos(&mut cursor_pos) }.ok().process()?;
|
||||
|
||||
Ok(cursor_pos)
|
||||
}
|
||||
|
||||
pub fn window_from_point(point: POINT) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { WindowFromPoint(point) }))
|
||||
unsafe { WindowFromPoint(point) }.process()
|
||||
}
|
||||
|
||||
pub fn window_at_cursor_pos() -> Result<isize> {
|
||||
@@ -374,7 +469,9 @@ impl WindowsApi {
|
||||
|
||||
// Behaviour is undefined if an invalid HWND is given
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
|
||||
let thread_id = unsafe { GetWindowThreadProcessId(hwnd, &mut process_id) };
|
||||
let thread_id = unsafe {
|
||||
GetWindowThreadProcessId(hwnd, Option::from(std::ptr::addr_of_mut!(process_id)))
|
||||
};
|
||||
|
||||
(process_id, thread_id)
|
||||
}
|
||||
@@ -387,25 +484,27 @@ impl WindowsApi {
|
||||
unsafe { GetCurrentProcessId() }
|
||||
}
|
||||
|
||||
pub fn process_id_to_session_id() -> Result<u32> {
|
||||
let process_id = Self::current_process_id();
|
||||
let mut session_id = 0;
|
||||
|
||||
unsafe {
|
||||
if ProcessIdToSessionId(process_id, &mut session_id).as_bool() {
|
||||
Ok(session_id)
|
||||
} else {
|
||||
Err(anyhow!("could not determine current session id"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach_thread_input(thread_id: u32, target_thread_id: u32, attach: bool) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
AttachThreadInput(thread_id, target_thread_id, attach)
|
||||
}))
|
||||
unsafe { AttachThreadInput(thread_id, target_thread_id, attach) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn set_focus(hwnd: HWND) -> Result<()> {
|
||||
match WindowsResult::from(unsafe { SetFocus(hwnd) }) {
|
||||
WindowsResult::Ok(_) => Ok(()),
|
||||
WindowsResult::Err(error) => {
|
||||
// If the window is not attached to the calling thread's message queue, the return value is NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setfocus
|
||||
if error.to_string() == "The operation completed successfully. (os error 0)" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { SetFocus(hwnd) }.process().map(|_| ())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -429,6 +528,8 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result<isize> {
|
||||
// Can return 0, which does not always mean that an error has occurred
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetWindowLongPtrW(hwnd, index)
|
||||
}))
|
||||
@@ -439,11 +540,14 @@ impl WindowsApi {
|
||||
Self::set_window_long_ptr_w(hwnd, GWL_STYLE, new_value)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_ex_style(hwnd: HWND, new_value: isize) -> Result<()> {
|
||||
Self::set_window_long_ptr_w(hwnd, GWL_EXSTYLE, new_value)
|
||||
}
|
||||
|
||||
pub fn window_text_w(hwnd: HWND) -> Result<String> {
|
||||
let mut text: [u16; 512] = [0; 512];
|
||||
match WindowsResult::from(unsafe {
|
||||
GetWindowTextW(hwnd, PWSTR(text.as_mut_ptr()), text.len().try_into()?)
|
||||
}) {
|
||||
match WindowsResult::from(unsafe { GetWindowTextW(hwnd, &mut text) }) {
|
||||
WindowsResult::Ok(len) => {
|
||||
let length = usize::try_from(len)?;
|
||||
Ok(String::from_utf16(&text[..length])?)
|
||||
@@ -457,9 +561,11 @@ impl WindowsApi {
|
||||
inherit_handle: bool,
|
||||
process_id: u32,
|
||||
) -> Result<HANDLE> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
OpenProcess(access_rights, inherit_handle, process_id)
|
||||
}))
|
||||
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }.process()
|
||||
}
|
||||
|
||||
pub fn close_process(handle: HANDLE) -> Result<()> {
|
||||
unsafe { CloseHandle(handle) }.ok().process()
|
||||
}
|
||||
|
||||
pub fn process_handle(process_id: u32) -> Result<HANDLE> {
|
||||
@@ -471,14 +577,11 @@ impl WindowsApi {
|
||||
let mut path: Vec<u16> = vec![0; len as usize];
|
||||
let text_ptr = path.as_mut_ptr();
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
QueryFullProcessImageNameW(
|
||||
handle,
|
||||
PROCESS_NAME_FORMAT(0),
|
||||
PWSTR(text_ptr),
|
||||
&mut len as *mut u32,
|
||||
)
|
||||
}))?;
|
||||
unsafe {
|
||||
QueryFullProcessImageNameW(handle, PROCESS_NAME_WIN32, PWSTR(text_ptr), &mut len)
|
||||
}
|
||||
.ok()
|
||||
.process()?;
|
||||
|
||||
Ok(String::from_utf16(&path[..len as usize])?)
|
||||
}
|
||||
@@ -496,7 +599,7 @@ impl WindowsApi {
|
||||
let mut class: [u16; BUF_SIZE] = [0; BUF_SIZE];
|
||||
|
||||
let len = Result::from(WindowsResult::from(unsafe {
|
||||
RealGetWindowClassW(hwnd, PWSTR(class.as_mut_ptr()), u32::try_from(BUF_SIZE)?)
|
||||
RealGetWindowClassW(hwnd, &mut class)
|
||||
}))?;
|
||||
|
||||
Ok(String::from_utf16(&class[0..len as usize])?)
|
||||
@@ -510,7 +613,7 @@ impl WindowsApi {
|
||||
unsafe {
|
||||
DwmGetWindowAttribute(
|
||||
hwnd,
|
||||
std::mem::transmute::<_, u32>(attribute),
|
||||
attribute,
|
||||
(value as *mut T).cast(),
|
||||
u32::try_from(std::mem::size_of::<T>())?,
|
||||
)?;
|
||||
@@ -519,14 +622,6 @@ impl WindowsApi {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn window_rect_with_extended_frame_bounds(hwnd: HWND) -> Result<Rect> {
|
||||
let mut rect = RECT::default();
|
||||
Self::dwm_get_window_attribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &mut rect)?;
|
||||
|
||||
Ok(Rect::from(rect))
|
||||
}
|
||||
|
||||
pub fn is_window_cloaked(hwnd: HWND) -> Result<bool> {
|
||||
let mut cloaked: u32 = 0;
|
||||
Self::dwm_get_window_attribute(hwnd, DWMWA_CLOAKED, &mut cloaked)?;
|
||||
@@ -549,27 +644,39 @@ impl WindowsApi {
|
||||
unsafe { IsIconic(hwnd) }.into()
|
||||
}
|
||||
|
||||
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFO> {
|
||||
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
|
||||
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
|
||||
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFOEXW> {
|
||||
let mut ex_info = MONITORINFOEXW::default();
|
||||
ex_info.monitorInfo.cbSize = u32::try_from(std::mem::size_of::<MONITORINFOEXW>())?;
|
||||
unsafe { GetMonitorInfoW(hmonitor, &mut ex_info.monitorInfo) }
|
||||
.ok()
|
||||
.process()?;
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetMonitorInfoW(hmonitor, (&mut monitor_info as *mut MONITORINFO).cast())
|
||||
}))?;
|
||||
|
||||
Ok(monitor_info)
|
||||
Ok(ex_info)
|
||||
}
|
||||
|
||||
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
|
||||
let monitor_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
|
||||
let ex_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
|
||||
let name = OsString::from_wide(&ex_info.szDevice);
|
||||
let name = name
|
||||
.to_string_lossy()
|
||||
.replace('\u{0000}', "")
|
||||
.trim_start_matches(r"\\.\")
|
||||
.to_string();
|
||||
|
||||
Ok(monitor::new(
|
||||
hmonitor,
|
||||
monitor_info.rcMonitor.into(),
|
||||
monitor_info.rcWork.into(),
|
||||
ex_info.monitorInfo.rcMonitor.into(),
|
||||
ex_info.monitorInfo.rcWork.into(),
|
||||
name,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_process_dpi_awareness_context() -> Result<()> {
|
||||
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn system_parameters_info_w(
|
||||
action: SYSTEM_PARAMETERS_INFO_ACTION,
|
||||
@@ -577,9 +684,9 @@ impl WindowsApi {
|
||||
pv_param: *mut c_void,
|
||||
update_flags: SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
|
||||
) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
SystemParametersInfoW(action, ui_param, pv_param, update_flags)
|
||||
}))
|
||||
unsafe { SystemParametersInfoW(action, ui_param, Option::from(pv_param), update_flags) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -589,7 +696,7 @@ impl WindowsApi {
|
||||
Self::system_parameters_info_w(
|
||||
SPI_GETACTIVEWINDOWTRACKING,
|
||||
0,
|
||||
(&mut is_enabled as *mut BOOL).cast(),
|
||||
std::ptr::addr_of_mut!(is_enabled).cast(),
|
||||
SPIF_SENDCHANGE,
|
||||
)?;
|
||||
|
||||
@@ -615,4 +722,142 @@ impl WindowsApi {
|
||||
SPIF_SENDCHANGE,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn module_handle_w() -> Result<HMODULE> {
|
||||
unsafe { GetModuleHandleW(None) }.process()
|
||||
}
|
||||
|
||||
pub fn create_solid_brush(colour: u32) -> HBRUSH {
|
||||
unsafe { CreateSolidBrush(COLORREF(colour)) }
|
||||
}
|
||||
|
||||
pub fn register_class_a(window_class: &WNDCLASSA) -> Result<u16> {
|
||||
Result::from(WindowsResult::from(unsafe { RegisterClassA(window_class) }))
|
||||
}
|
||||
|
||||
pub fn scale_factor_for_monitor(hmonitor: isize) -> Result<DEVICE_SCALE_FACTOR> {
|
||||
unsafe { GetScaleFactorForMonitor(HMONITOR(hmonitor)) }.process()
|
||||
}
|
||||
|
||||
pub fn monitors_have_same_scale_factor(a: isize, b: isize) -> Result<bool> {
|
||||
let a = Self::scale_factor_for_monitor(a)?;
|
||||
let b = Self::scale_factor_for_monitor(b)?;
|
||||
|
||||
Ok(a == b)
|
||||
}
|
||||
|
||||
pub fn round_corners(hwnd: isize) -> Result<()> {
|
||||
let round = DWMWCP_ROUND;
|
||||
|
||||
unsafe {
|
||||
DwmSetWindowAttribute(
|
||||
HWND(hwnd),
|
||||
DWMWA_WINDOW_CORNER_PREFERENCE,
|
||||
std::ptr::addr_of!(round).cast(),
|
||||
4,
|
||||
)
|
||||
}
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn create_border_window(name: PCSTR, instance: HMODULE) -> Result<isize> {
|
||||
unsafe {
|
||||
let hwnd = CreateWindowExA(
|
||||
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
|
||||
name,
|
||||
name,
|
||||
WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
None,
|
||||
None,
|
||||
instance,
|
||||
None,
|
||||
);
|
||||
|
||||
SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY);
|
||||
|
||||
hwnd
|
||||
}
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn set_transparent(hwnd: HWND) {
|
||||
unsafe {
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// TODO: alpha should be configurable
|
||||
SetLayeredWindowAttributes(hwnd, COLORREF(-1i32 as u32), 150, LWA_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_hidden_window(name: PCSTR, instance: HMODULE) -> Result<isize> {
|
||||
unsafe {
|
||||
CreateWindowExA(
|
||||
WS_EX_NOACTIVATE,
|
||||
name,
|
||||
name,
|
||||
WS_DISABLED,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
None,
|
||||
None,
|
||||
instance,
|
||||
None,
|
||||
)
|
||||
}
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn invalidate_border_rect() -> Result<()> {
|
||||
unsafe { InvalidateRect(HWND(BORDER_HWND.load(Ordering::SeqCst)), None, false) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn alt_is_pressed() -> bool {
|
||||
let state = unsafe { GetKeyState(i32::from(VK_MENU.0)) };
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
let actual = (state as u16) & 0x8000;
|
||||
actual != 0
|
||||
}
|
||||
|
||||
pub fn left_click() -> u32 {
|
||||
let inputs = [
|
||||
INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: 0,
|
||||
dwFlags: MOUSEEVENTF_LEFTDOWN,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: 0,
|
||||
dwFlags: MOUSEEVENTF_LEFTUP,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
unsafe {
|
||||
SendInput(&inputs, std::mem::size_of::<INPUT>() as i32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::BOOL;
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::Foundation::LPARAM;
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HDC;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use bindings::Windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::COLORREF;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Foundation::LRESULT;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::Graphics::Gdi::BeginPaint;
|
||||
use windows::Win32::Graphics::Gdi::CreatePen;
|
||||
use windows::Win32::Graphics::Gdi::EndPaint;
|
||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||
use windows::Win32::Graphics::Gdi::SelectObject;
|
||||
use windows::Win32::Graphics::Gdi::ValidateRect;
|
||||
use windows::Win32::Graphics::Gdi::HDC;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
|
||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_ICONVERTICALSPACING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::monitor::Monitor;
|
||||
@@ -15,6 +37,11 @@ use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
use crate::BORDER_RECT;
|
||||
use crate::BORDER_WIDTH;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
use crate::TRANSPARENCY_COLOUR;
|
||||
|
||||
pub extern "system" fn valid_display_monitors(
|
||||
hmonitor: HMONITOR,
|
||||
@@ -22,8 +49,11 @@ pub extern "system" fn valid_display_monitors(
|
||||
_: *mut RECT,
|
||||
lparam: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
||||
monitors.push(hmonitor.0);
|
||||
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<(String, isize)>) };
|
||||
if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
|
||||
monitors.push((m.name().to_string(), hmonitor.0));
|
||||
}
|
||||
|
||||
true.into()
|
||||
}
|
||||
|
||||
@@ -43,7 +73,24 @@ pub extern "system" fn enum_display_monitor(
|
||||
}
|
||||
|
||||
if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
|
||||
monitors.elements_mut().push_back(m);
|
||||
let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
|
||||
let mut index_preference = None;
|
||||
for (index, monitor_size) in &*monitor_index_preferences {
|
||||
if m.size() == monitor_size {
|
||||
index_preference = Option::from(index);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(preference) = index_preference {
|
||||
let current_len = monitors.elements().len();
|
||||
if *preference > current_len {
|
||||
monitors.elements_mut().reserve(1);
|
||||
}
|
||||
|
||||
monitors.elements_mut().insert(*preference, m);
|
||||
} else {
|
||||
monitors.elements_mut().push_back(m);
|
||||
}
|
||||
}
|
||||
|
||||
true.into()
|
||||
@@ -103,3 +150,90 @@ pub extern "system" fn win_event_hook(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "system" fn border_window(
|
||||
window: HWND,
|
||||
message: u32,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
unsafe {
|
||||
match message {
|
||||
WM_PAINT => {
|
||||
let border_rect = *BORDER_RECT.lock();
|
||||
let mut ps = PAINTSTRUCT::default();
|
||||
let hdc = BeginPaint(window, &mut ps);
|
||||
let hpen = CreatePen(
|
||||
PS_SOLID,
|
||||
BORDER_WIDTH.load(Ordering::SeqCst),
|
||||
COLORREF(BORDER_COLOUR_CURRENT.load(Ordering::SeqCst)),
|
||||
);
|
||||
let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
|
||||
|
||||
SelectObject(hdc, hpen);
|
||||
SelectObject(hdc, hbrush);
|
||||
Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom);
|
||||
EndPaint(window, &ps);
|
||||
ValidateRect(window, None);
|
||||
|
||||
LRESULT(0)
|
||||
}
|
||||
WM_DESTROY => {
|
||||
PostQuitMessage(0);
|
||||
LRESULT(0)
|
||||
}
|
||||
_ => DefWindowProcW(window, message, wparam, lparam),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "system" fn hidden_window(
|
||||
window: HWND,
|
||||
message: u32,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
unsafe {
|
||||
match message {
|
||||
WM_DISPLAYCHANGE => {
|
||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||
WINEVENT_CALLBACK_CHANNEL
|
||||
.lock()
|
||||
.0
|
||||
.send(event_type)
|
||||
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||
|
||||
LRESULT(0)
|
||||
}
|
||||
// Added based on this https://stackoverflow.com/a/33762334
|
||||
WM_SETTINGCHANGE => {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
if wparam.0 as u32 == SPI_SETWORKAREA.0
|
||||
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
|
||||
{
|
||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||
WINEVENT_CALLBACK_CHANNEL
|
||||
.lock()
|
||||
.0
|
||||
.send(event_type)
|
||||
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||
}
|
||||
LRESULT(0)
|
||||
}
|
||||
// Added based on this https://stackoverflow.com/a/33762334
|
||||
WM_DEVICECHANGE => {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||
WINEVENT_CALLBACK_CHANNEL
|
||||
.lock()
|
||||
.0
|
||||
.send(event_type)
|
||||
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||
}
|
||||
LRESULT(0)
|
||||
}
|
||||
_ => DefWindowProcW(window, message, wparam, lparam),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +1,94 @@
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_START;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_CARET;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END_APPLICATION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_LAYOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_START_APPLICATION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_REGION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SCROLL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SIMPLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_ACCELERATORCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CLOAKED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CONTENTSCROLLED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CREATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DEFACTIONCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESCRIPTIONCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCANCEL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCOMPLETE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGDROPPED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGENTER;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGLEAVE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_FOCUS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HELPCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HIDE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_CHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_HIDE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_SHOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_INVOKED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LIVEREGIONCHANGED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_NAMECHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_PARENTCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_REORDER;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONADD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONREMOVE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONWITHIN;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SHOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_STATECHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTSELECTIONCHANGED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_UNCLOAKED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_VALUECHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_START;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ALERT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ARRANGMENTPREVIEW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTUREEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTURESTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DESKTOPSWITCH;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_FOREGROUND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_IME_KEY_NOTIFICATION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZEEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZESTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZEEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZESTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SOUND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPDROPPED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPGRABBED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPOVERTARGET;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_CANCELLED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHSTART;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
||||
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_START;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_CARET;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END_APPLICATION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_LAYOUT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_START_APPLICATION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_REGION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SCROLL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SIMPLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_ACCELERATORCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CLOAKED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CONTENTSCROLLED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CREATE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DEFACTIONCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESCRIPTIONCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCANCEL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCOMPLETE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGDROPPED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGENTER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGLEAVE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_FOCUS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HELPCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_CHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_SHOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_INVOKED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LIVEREGIONCHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_NAMECHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_PARENTCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_REORDER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONADD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONREMOVE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONWITHIN;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SHOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_STATECHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTSELECTIONCHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_UNCLOAKED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_VALUECHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_START;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ALERT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ARRANGMENTPREVIEW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTUREEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTURESTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DESKTOPSWITCH;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_FOREGROUND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_IME_KEY_NOTIFICATION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZEEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZESTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZEEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZESTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SOUND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPDROPPED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPGRABBED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPOVERTARGET;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_CANCELLED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Display)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Display, JsonSchema)]
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
pub enum WinEvent {
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::UI::Accessibility::SetWinEventHook;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::PeekMessageW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::PM_REMOVE;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::Accessibility::SetWinEventHook;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PeekMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PM_REMOVE;
|
||||
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_callbacks;
|
||||
@@ -45,10 +43,10 @@ impl WinEventListener {
|
||||
let hook = self.hook.clone();
|
||||
let outgoing = self.outgoing_events.lock().clone();
|
||||
|
||||
thread::spawn(move || unsafe {
|
||||
std::thread::spawn(move || unsafe {
|
||||
let hook_ref = SetWinEventHook(
|
||||
EVENT_MIN as u32,
|
||||
EVENT_MAX as u32,
|
||||
EVENT_MIN,
|
||||
EVENT_MAX,
|
||||
None,
|
||||
Some(windows_callbacks::win_event_hook),
|
||||
0,
|
||||
@@ -97,7 +95,7 @@ impl MessageLoop {
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(sleep));
|
||||
std::thread::sleep(Duration::from_millis(sleep));
|
||||
|
||||
if !cb(value) {
|
||||
break;
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Flip;
|
||||
use komorebi_core::Axis;
|
||||
use komorebi_core::CustomLayout;
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::DefaultLayout;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::static_config::WorkspaceConfig;
|
||||
use crate::window::Window;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::REMOVE_TITLEBARS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||
pub struct Workspace {
|
||||
#[getset(set = "pub")]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
name: Option<String>,
|
||||
containers: Ring<Container>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
@@ -37,10 +47,12 @@ pub struct Workspace {
|
||||
maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
floating_windows: Vec<Window>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
layout: Layout,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
layout_rules: Vec<(usize, Layout)>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
layout_flip: Option<Flip>,
|
||||
layout_flip: Option<Axis>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
workspace_padding: Option<i32>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
@@ -48,8 +60,7 @@ pub struct Workspace {
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
latest_layout: Vec<Rect>,
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
resize_dimensions: Vec<Option<Rect>>,
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
tile: bool,
|
||||
@@ -67,10 +78,11 @@ impl Default for Workspace {
|
||||
maximized_window_restore_idx: None,
|
||||
monocle_container_restore_idx: None,
|
||||
floating_windows: Vec::default(),
|
||||
layout: Layout::BSP,
|
||||
layout: Layout::Default(DefaultLayout::BSP),
|
||||
layout_rules: vec![],
|
||||
layout_flip: None,
|
||||
workspace_padding: Option::from(10),
|
||||
container_padding: Option::from(10),
|
||||
workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)),
|
||||
container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)),
|
||||
latest_layout: vec![],
|
||||
resize_dimensions: vec![],
|
||||
tile: true,
|
||||
@@ -79,6 +91,46 @@ impl Default for Workspace {
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> {
|
||||
self.name = Option::from(config.name.clone());
|
||||
|
||||
if config.container_padding.is_some() {
|
||||
self.set_container_padding(config.container_padding);
|
||||
}
|
||||
|
||||
if config.workspace_padding.is_some() {
|
||||
self.set_workspace_padding(config.workspace_padding);
|
||||
}
|
||||
|
||||
if let Some(layout) = &config.layout {
|
||||
self.layout = Layout::Default(*layout);
|
||||
}
|
||||
|
||||
if let Some(pathbuf) = &config.custom_layout {
|
||||
let layout = CustomLayout::from_path_buf(pathbuf.clone())?;
|
||||
self.layout = Layout::Custom(layout);
|
||||
}
|
||||
|
||||
if let Some(layout_rules) = &config.layout_rules {
|
||||
let mut all_rules = vec![];
|
||||
for (count, rule) in layout_rules {
|
||||
all_rules.push((*count, Layout::Default(*rule)));
|
||||
}
|
||||
|
||||
self.set_layout_rules(all_rules);
|
||||
}
|
||||
|
||||
if let Some(layout_rules) = &config.custom_layout_rules {
|
||||
let rules = self.layout_rules_mut();
|
||||
for (count, pathbuf) in layout_rules {
|
||||
let rule = CustomLayout::from_path_buf(pathbuf.clone())?;
|
||||
rules.push((*count, Layout::Custom(rule)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hide(&mut self) {
|
||||
for container in self.containers_mut() {
|
||||
for window in container.windows_mut() {
|
||||
@@ -101,7 +153,7 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(&mut self) -> Result<()> {
|
||||
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||
let idx = self.focused_container_idx();
|
||||
let mut to_focus = None;
|
||||
for (i, container) in self.containers_mut().iter_mut().enumerate() {
|
||||
@@ -132,40 +184,92 @@ impl Workspace {
|
||||
// Maximised windows should always be drawn at the top of the Z order
|
||||
if let Some(window) = to_focus {
|
||||
if self.maximized_window().is_none() {
|
||||
window.focus()?;
|
||||
window.focus(mouse_follows_focus)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, work_area: &Rect, invisible_borders: &Rect) -> Result<()> {
|
||||
let mut adjusted_work_area = *work_area;
|
||||
pub fn update(
|
||||
&mut self,
|
||||
work_area: &Rect,
|
||||
offset: Option<Rect>,
|
||||
invisible_borders: &Rect,
|
||||
) -> Result<()> {
|
||||
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let container_padding = self.container_padding();
|
||||
let mut adjusted_work_area = offset.map_or_else(
|
||||
|| *work_area,
|
||||
|offset| {
|
||||
let mut with_offset = *work_area;
|
||||
with_offset.left += offset.left;
|
||||
with_offset.top += offset.top;
|
||||
with_offset.right -= offset.right;
|
||||
with_offset.bottom -= offset.bottom;
|
||||
|
||||
with_offset
|
||||
},
|
||||
);
|
||||
|
||||
adjusted_work_area.add_padding(self.workspace_padding());
|
||||
|
||||
self.enforce_resize_constraints();
|
||||
|
||||
if !self.layout_rules().is_empty() {
|
||||
let mut updated_layout = None;
|
||||
|
||||
for rule in self.layout_rules() {
|
||||
if self.containers().len() >= rule.0 {
|
||||
updated_layout = Option::from(rule.1.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(updated_layout) = updated_layout {
|
||||
if !matches!(updated_layout, Layout::Default(DefaultLayout::BSP)) {
|
||||
self.set_layout_flip(None);
|
||||
}
|
||||
|
||||
self.set_layout(updated_layout);
|
||||
}
|
||||
}
|
||||
|
||||
if *self.tile() {
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window) = container.focused_window_mut() {
|
||||
adjusted_work_area.add_padding(container_padding);
|
||||
window.set_position(&adjusted_work_area, invisible_borders, true)?;
|
||||
};
|
||||
} else if let Some(window) = self.maximized_window_mut() {
|
||||
window.maximize();
|
||||
} else if !self.containers().is_empty() {
|
||||
let layouts = self.layout().calculate(
|
||||
let layouts = self.layout().as_boxed_arrangement().calculate(
|
||||
&adjusted_work_area,
|
||||
NonZeroUsize::new(self.containers().len()).context(
|
||||
"there must be at least one container to calculate a workspace layout",
|
||||
)?,
|
||||
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"there must be at least one container to calculate a workspace layout"
|
||||
)
|
||||
})?,
|
||||
self.container_padding(),
|
||||
self.layout_flip(),
|
||||
self.resize_dimensions(),
|
||||
);
|
||||
|
||||
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
||||
let no_titlebar = NO_TITLEBAR.lock().clone();
|
||||
|
||||
let windows = self.visible_windows_mut();
|
||||
for (i, window) in windows.into_iter().enumerate() {
|
||||
if let (Some(window), Some(layout)) = (window, layouts.get(i)) {
|
||||
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
|
||||
window.remove_title_bar()?;
|
||||
} else if no_titlebar.contains(&window.exe()?) {
|
||||
window.add_title_bar()?;
|
||||
}
|
||||
|
||||
window.set_position(layout, invisible_borders, false)?;
|
||||
}
|
||||
}
|
||||
@@ -295,6 +399,45 @@ impl Workspace {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn contains_managed_window(&self, hwnd: isize) -> bool {
|
||||
for container in self.containers() {
|
||||
if container.contains_window(hwnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
if hwnd == window.hwnd {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container() {
|
||||
if container.contains_window(hwnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_focused_window_monocle_or_maximized(&self) -> Result<bool> {
|
||||
let hwnd = WindowsApi::foreground_window()?;
|
||||
if let Some(window) = self.maximized_window() {
|
||||
if hwnd == window.hwnd {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container() {
|
||||
if container.contains_window(hwnd) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for container in self.containers() {
|
||||
if container.contains_window(hwnd) {
|
||||
@@ -324,12 +467,24 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn promote_container(&mut self) -> Result<()> {
|
||||
let resize = self.resize_dimensions_mut().remove(0);
|
||||
let container = self
|
||||
.remove_focused_container()
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
self.containers_mut().push_front(container);
|
||||
self.resize_dimensions_mut().insert(0, None);
|
||||
self.focus_container(0);
|
||||
|
||||
let primary_idx = match self.layout() {
|
||||
Layout::Default(_) => 0,
|
||||
Layout::Custom(layout) => layout.first_container_idx(
|
||||
layout
|
||||
.primary_idx()
|
||||
.ok_or_else(|| anyhow!("this custom layout does not have a primary column"))?,
|
||||
),
|
||||
};
|
||||
|
||||
self.containers_mut().insert(primary_idx, container);
|
||||
self.resize_dimensions_mut().insert(primary_idx, resize);
|
||||
|
||||
self.focus_container(primary_idx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -339,9 +494,20 @@ impl Workspace {
|
||||
self.focus_last_container();
|
||||
}
|
||||
|
||||
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
|
||||
self.resize_dimensions_mut().remove(idx);
|
||||
self.containers_mut().remove(idx)
|
||||
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
|
||||
self.containers_mut().insert(idx, container);
|
||||
}
|
||||
|
||||
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
|
||||
if idx < self.resize_dimensions().len() {
|
||||
self.resize_dimensions_mut().remove(idx);
|
||||
}
|
||||
|
||||
if idx < self.containers().len() {
|
||||
return self.containers_mut().remove(idx);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {
|
||||
@@ -382,6 +548,7 @@ impl Workspace {
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
if window.hwnd == hwnd {
|
||||
window.unmaximize();
|
||||
self.set_maximized_window(None);
|
||||
self.set_maximized_window_restore_idx(None);
|
||||
return Ok(());
|
||||
@@ -416,9 +583,11 @@ impl Workspace {
|
||||
if self.resize_dimensions().get(container_idx).is_some() {
|
||||
self.resize_dimensions_mut().remove(container_idx);
|
||||
}
|
||||
}
|
||||
|
||||
self.focus_previous_container();
|
||||
self.focus_previous_container();
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -431,21 +600,28 @@ impl Workspace {
|
||||
container
|
||||
}
|
||||
|
||||
pub fn remove_container(&mut self, idx: usize) -> Option<Container> {
|
||||
let container = self.remove_container_by_idx(idx);
|
||||
self.focus_previous_container();
|
||||
|
||||
container
|
||||
}
|
||||
|
||||
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
|
||||
if direction.is_valid(
|
||||
self.layout(),
|
||||
let len = NonZeroUsize::new(self.containers().len())?;
|
||||
|
||||
direction.destination(
|
||||
self.layout().as_boxed_direction().as_ref(),
|
||||
self.layout_flip(),
|
||||
self.focused_container_idx(),
|
||||
self.containers().len(),
|
||||
) {
|
||||
Option::from(direction.new_idx(
|
||||
self.layout(),
|
||||
self.layout_flip(),
|
||||
self.containers.focused_idx(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
len,
|
||||
)
|
||||
}
|
||||
pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option<usize> {
|
||||
Option::from(direction.next_idx(
|
||||
self.focused_container_idx(),
|
||||
NonZeroUsize::new(self.containers().len())?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> {
|
||||
@@ -554,22 +730,44 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn new_floating_window(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let window = if let Some(maximized_window) = self.maximized_window() {
|
||||
let window = *maximized_window;
|
||||
self.set_maximized_window(None);
|
||||
self.set_maximized_window_restore_idx(None);
|
||||
window
|
||||
} else if let Some(monocle_container) = self.monocle_container_mut() {
|
||||
let window = monocle_container
|
||||
.remove_focused_window()
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
if monocle_container.windows().is_empty() {
|
||||
self.set_monocle_container(None);
|
||||
self.set_monocle_container_restore_idx(None);
|
||||
} else {
|
||||
monocle_container.load_focused_window();
|
||||
}
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
self.resize_dimensions_mut().remove(focused_idx);
|
||||
window
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
let focused_idx = self.focused_container_idx();
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
self.resize_dimensions_mut().remove(focused_idx);
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
|
||||
window
|
||||
};
|
||||
|
||||
self.floating_windows_mut().push(window);
|
||||
|
||||
@@ -655,6 +853,53 @@ impl Workspace {
|
||||
|
||||
pub fn new_maximized_window(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let foreground_hwnd = WindowsApi::foreground_window()?;
|
||||
let mut floating_window = None;
|
||||
|
||||
if !self.floating_windows().is_empty() {
|
||||
let mut focused_floating_window_idx = None;
|
||||
for (i, w) in self.floating_windows().iter().enumerate() {
|
||||
if w.hwnd == foreground_hwnd {
|
||||
focused_floating_window_idx = Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(idx) = focused_floating_window_idx {
|
||||
floating_window = Option::from(self.floating_windows_mut().remove(idx));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(floating_window) = floating_window {
|
||||
self.set_maximized_window(Option::from(floating_window));
|
||||
self.set_maximized_window_restore_idx(Option::from(focused_idx));
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let monocle_restore_idx = self.monocle_container_restore_idx();
|
||||
if let Some(monocle_container) = self.monocle_container_mut() {
|
||||
let window = monocle_container
|
||||
.remove_focused_window()
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if monocle_container.windows().is_empty() {
|
||||
self.set_monocle_container(None);
|
||||
self.set_monocle_container_restore_idx(None);
|
||||
} else {
|
||||
monocle_container.load_focused_window();
|
||||
}
|
||||
|
||||
self.set_maximized_window(Option::from(window));
|
||||
self.set_maximized_window_restore_idx(monocle_restore_idx);
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
@@ -666,7 +911,9 @@ impl Workspace {
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
self.resize_dimensions_mut().remove(focused_idx);
|
||||
if self.resize_dimensions().get(focused_idx).is_some() {
|
||||
self.resize_dimensions_mut().remove(focused_idx);
|
||||
}
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
|
||||
453
komorebic.lib.ahk
Normal file
453
komorebic.lib.ahk
Normal file
@@ -0,0 +1,453 @@
|
||||
; Generated by komorebic.exe
|
||||
|
||||
Start(ffm, await_configuration, tcp_port) {
|
||||
RunWait("komorebic.exe start " ffm " --await-configuration " await_configuration " --tcp-port " tcp_port, , "Hide")
|
||||
}
|
||||
|
||||
Stop() {
|
||||
RunWait("komorebic.exe stop", , "Hide")
|
||||
}
|
||||
|
||||
State() {
|
||||
RunWait("komorebic.exe state", , "Hide")
|
||||
}
|
||||
|
||||
Query(state_query) {
|
||||
RunWait("komorebic.exe query " state_query, , "Hide")
|
||||
}
|
||||
|
||||
Subscribe(named_pipe) {
|
||||
RunWait("komorebic.exe subscribe " named_pipe, , "Hide")
|
||||
}
|
||||
|
||||
Unsubscribe(named_pipe) {
|
||||
RunWait("komorebic.exe unsubscribe " named_pipe, , "Hide")
|
||||
}
|
||||
|
||||
Log() {
|
||||
RunWait("komorebic.exe log", , "Hide")
|
||||
}
|
||||
|
||||
QuickSaveResize() {
|
||||
RunWait("komorebic.exe quick-save-resize", , "Hide")
|
||||
}
|
||||
|
||||
QuickLoadResize() {
|
||||
RunWait("komorebic.exe quick-load-resize", , "Hide")
|
||||
}
|
||||
|
||||
SaveResize(path) {
|
||||
RunWait("komorebic.exe save-resize " path, , "Hide")
|
||||
}
|
||||
|
||||
LoadResize(path) {
|
||||
RunWait("komorebic.exe load-resize " path, , "Hide")
|
||||
}
|
||||
|
||||
Focus(operation_direction) {
|
||||
RunWait("komorebic.exe focus " operation_direction, , "Hide")
|
||||
}
|
||||
|
||||
Move(operation_direction) {
|
||||
RunWait("komorebic.exe move " operation_direction, , "Hide")
|
||||
}
|
||||
|
||||
Minimize() {
|
||||
RunWait("komorebic.exe minimize", , "Hide")
|
||||
}
|
||||
|
||||
Close() {
|
||||
RunWait("komorebic.exe close", , "Hide")
|
||||
}
|
||||
|
||||
ForceFocus() {
|
||||
RunWait("komorebic.exe force-focus", , "Hide")
|
||||
}
|
||||
|
||||
CycleFocus(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-focus " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
CycleMove(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-move " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
Stack(operation_direction) {
|
||||
RunWait("komorebic.exe stack " operation_direction, , "Hide")
|
||||
}
|
||||
|
||||
Resize(edge, sizing) {
|
||||
RunWait("komorebic.exe resize " edge " " sizing, , "Hide")
|
||||
}
|
||||
|
||||
ResizeAxis(axis, sizing) {
|
||||
RunWait("komorebic.exe resize-axis " axis " " sizing, , "Hide")
|
||||
}
|
||||
|
||||
Unstack() {
|
||||
RunWait("komorebic.exe unstack", , "Hide")
|
||||
}
|
||||
|
||||
CycleStack(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-stack " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
MoveToMonitor(target) {
|
||||
RunWait("komorebic.exe move-to-monitor " target, , "Hide")
|
||||
}
|
||||
|
||||
CycleMoveToMonitor(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-move-to-monitor " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
MoveToWorkspace(target) {
|
||||
RunWait("komorebic.exe move-to-workspace " target, , "Hide")
|
||||
}
|
||||
|
||||
MoveToNamedWorkspace(workspace) {
|
||||
RunWait("komorebic.exe move-to-named-workspace " workspace, , "Hide")
|
||||
}
|
||||
|
||||
CycleMoveToWorkspace(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-move-to-workspace " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
SendToMonitor(target) {
|
||||
RunWait("komorebic.exe send-to-monitor " target, , "Hide")
|
||||
}
|
||||
|
||||
CycleSendToMonitor(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-send-to-monitor " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
SendToWorkspace(target) {
|
||||
RunWait("komorebic.exe send-to-workspace " target, , "Hide")
|
||||
}
|
||||
|
||||
SendToNamedWorkspace(workspace) {
|
||||
RunWait("komorebic.exe send-to-named-workspace " workspace, , "Hide")
|
||||
}
|
||||
|
||||
CycleSendToWorkspace(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-send-to-workspace " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
SendToMonitorWorkspace(target_monitor, target_workspace) {
|
||||
RunWait("komorebic.exe send-to-monitor-workspace " target_monitor " " target_workspace, , "Hide")
|
||||
}
|
||||
|
||||
FocusMonitor(target) {
|
||||
RunWait("komorebic.exe focus-monitor " target, , "Hide")
|
||||
}
|
||||
|
||||
FocusWorkspace(target) {
|
||||
RunWait("komorebic.exe focus-workspace " target, , "Hide")
|
||||
}
|
||||
|
||||
FocusMonitorWorkspace(target_monitor, target_workspace) {
|
||||
RunWait("komorebic.exe focus-monitor-workspace " target_monitor " " target_workspace, , "Hide")
|
||||
}
|
||||
|
||||
FocusNamedWorkspace(workspace) {
|
||||
RunWait("komorebic.exe focus-named-workspace " workspace, , "Hide")
|
||||
}
|
||||
|
||||
CycleMonitor(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-monitor " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
CycleWorkspace(cycle_direction) {
|
||||
RunWait("komorebic.exe cycle-workspace " cycle_direction, , "Hide")
|
||||
}
|
||||
|
||||
MoveWorkspaceToMonitor(target) {
|
||||
RunWait("komorebic.exe move-workspace-to-monitor " target, , "Hide")
|
||||
}
|
||||
|
||||
NewWorkspace() {
|
||||
RunWait("komorebic.exe new-workspace", , "Hide")
|
||||
}
|
||||
|
||||
ResizeDelta(pixels) {
|
||||
RunWait("komorebic.exe resize-delta " pixels, , "Hide")
|
||||
}
|
||||
|
||||
InvisibleBorders(left, top, right, bottom) {
|
||||
RunWait("komorebic.exe invisible-borders " left " " top " " right " " bottom, , "Hide")
|
||||
}
|
||||
|
||||
GlobalWorkAreaOffset(left, top, right, bottom) {
|
||||
RunWait("komorebic.exe global-work-area-offset " left " " top " " right " " bottom, , "Hide")
|
||||
}
|
||||
|
||||
MonitorWorkAreaOffset(monitor, left, top, right, bottom) {
|
||||
RunWait("komorebic.exe monitor-work-area-offset " monitor " " left " " top " " right " " bottom, , "Hide")
|
||||
}
|
||||
|
||||
AdjustContainerPadding(sizing, adjustment) {
|
||||
RunWait("komorebic.exe adjust-container-padding " sizing " " adjustment, , "Hide")
|
||||
}
|
||||
|
||||
AdjustWorkspacePadding(sizing, adjustment) {
|
||||
RunWait("komorebic.exe adjust-workspace-padding " sizing " " adjustment, , "Hide")
|
||||
}
|
||||
|
||||
ChangeLayout(default_layout) {
|
||||
RunWait("komorebic.exe change-layout " default_layout, , "Hide")
|
||||
}
|
||||
|
||||
LoadCustomLayout(path) {
|
||||
RunWait("komorebic.exe load-custom-layout " path, , "Hide")
|
||||
}
|
||||
|
||||
FlipLayout(axis) {
|
||||
RunWait("komorebic.exe flip-layout " axis, , "Hide")
|
||||
}
|
||||
|
||||
Promote() {
|
||||
RunWait("komorebic.exe promote", , "Hide")
|
||||
}
|
||||
|
||||
PromoteFocus() {
|
||||
RunWait("komorebic.exe promote-focus", , "Hide")
|
||||
}
|
||||
|
||||
Retile() {
|
||||
RunWait("komorebic.exe retile", , "Hide")
|
||||
}
|
||||
|
||||
MonitorIndexPreference(index_preference, left, top, right, bottom) {
|
||||
RunWait("komorebic.exe monitor-index-preference " index_preference " " left " " top " " right " " bottom, , "Hide")
|
||||
}
|
||||
|
||||
EnsureWorkspaces(monitor, workspace_count) {
|
||||
RunWait("komorebic.exe ensure-workspaces " monitor " " workspace_count, , "Hide")
|
||||
}
|
||||
|
||||
EnsureNamedWorkspaces(monitor, names) {
|
||||
RunWait("komorebic.exe ensure-named-workspaces " monitor " " names, , "Hide")
|
||||
}
|
||||
|
||||
ContainerPadding(monitor, workspace, size) {
|
||||
RunWait("komorebic.exe container-padding " monitor " " workspace " " size, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceContainerPadding(workspace, size) {
|
||||
RunWait("komorebic.exe named-workspace-container-padding " workspace " " size, , "Hide")
|
||||
}
|
||||
|
||||
WorkspacePadding(monitor, workspace, size) {
|
||||
RunWait("komorebic.exe workspace-padding " monitor " " workspace " " size, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspacePadding(workspace, size) {
|
||||
RunWait("komorebic.exe named-workspace-padding " workspace " " size, , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceLayout(monitor, workspace, value) {
|
||||
RunWait("komorebic.exe workspace-layout " monitor " " workspace " " value, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceLayout(workspace, value) {
|
||||
RunWait("komorebic.exe named-workspace-layout " workspace " " value, , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceCustomLayout(monitor, workspace, path) {
|
||||
RunWait("komorebic.exe workspace-custom-layout " monitor " " workspace " " path, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceCustomLayout(workspace, path) {
|
||||
RunWait("komorebic.exe named-workspace-custom-layout " workspace " " path, , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceLayoutRule(monitor, workspace, at_container_count, layout) {
|
||||
RunWait("komorebic.exe workspace-layout-rule " monitor " " workspace " " at_container_count " " layout, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceLayoutRule(workspace, at_container_count, layout) {
|
||||
RunWait("komorebic.exe named-workspace-layout-rule " workspace " " at_container_count " " layout, , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceCustomLayoutRule(monitor, workspace, at_container_count, path) {
|
||||
RunWait("komorebic.exe workspace-custom-layout-rule " monitor " " workspace " " at_container_count " " path, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceCustomLayoutRule(workspace, at_container_count, path) {
|
||||
RunWait("komorebic.exe named-workspace-custom-layout-rule " workspace " " at_container_count " " path, , "Hide")
|
||||
}
|
||||
|
||||
ClearWorkspaceLayoutRules(monitor, workspace) {
|
||||
RunWait("komorebic.exe clear-workspace-layout-rules " monitor " " workspace, , "Hide")
|
||||
}
|
||||
|
||||
ClearNamedWorkspaceLayoutRules(workspace) {
|
||||
RunWait("komorebic.exe clear-named-workspace-layout-rules " workspace, , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceTiling(monitor, workspace, value) {
|
||||
RunWait("komorebic.exe workspace-tiling " monitor " " workspace " " value, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceTiling(workspace, value) {
|
||||
RunWait("komorebic.exe named-workspace-tiling " workspace " " value, , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceName(monitor, workspace, value) {
|
||||
RunWait("komorebic.exe workspace-name " monitor " " workspace " " value, , "Hide")
|
||||
}
|
||||
|
||||
ToggleWindowContainerBehaviour() {
|
||||
RunWait("komorebic.exe toggle-window-container-behaviour", , "Hide")
|
||||
}
|
||||
|
||||
TogglePause() {
|
||||
RunWait("komorebic.exe toggle-pause", , "Hide")
|
||||
}
|
||||
|
||||
ToggleTiling() {
|
||||
RunWait("komorebic.exe toggle-tiling", , "Hide")
|
||||
}
|
||||
|
||||
ToggleFloat() {
|
||||
RunWait("komorebic.exe toggle-float", , "Hide")
|
||||
}
|
||||
|
||||
ToggleMonocle() {
|
||||
RunWait("komorebic.exe toggle-monocle", , "Hide")
|
||||
}
|
||||
|
||||
ToggleMaximize() {
|
||||
RunWait("komorebic.exe toggle-maximize", , "Hide")
|
||||
}
|
||||
|
||||
RestoreWindows() {
|
||||
RunWait("komorebic.exe restore-windows", , "Hide")
|
||||
}
|
||||
|
||||
Manage() {
|
||||
RunWait("komorebic.exe manage", , "Hide")
|
||||
}
|
||||
|
||||
Unmanage() {
|
||||
RunWait("komorebic.exe unmanage", , "Hide")
|
||||
}
|
||||
|
||||
ReloadConfiguration() {
|
||||
RunWait("komorebic.exe reload-configuration", , "Hide")
|
||||
}
|
||||
|
||||
WatchConfiguration(boolean_state) {
|
||||
RunWait("komorebic.exe watch-configuration " boolean_state, , "Hide")
|
||||
}
|
||||
|
||||
CompleteConfiguration() {
|
||||
RunWait("komorebic.exe complete-configuration", , "Hide")
|
||||
}
|
||||
|
||||
AltFocusHack(boolean_state) {
|
||||
RunWait("komorebic.exe alt-focus-hack " boolean_state, , "Hide")
|
||||
}
|
||||
|
||||
WindowHidingBehaviour(hiding_behaviour) {
|
||||
RunWait("komorebic.exe window-hiding-behaviour " hiding_behaviour, , "Hide")
|
||||
}
|
||||
|
||||
CrossMonitorMoveBehaviour(move_behaviour) {
|
||||
RunWait("komorebic.exe cross-monitor-move-behaviour " move_behaviour, , "Hide")
|
||||
}
|
||||
|
||||
ToggleCrossMonitorMoveBehaviour() {
|
||||
RunWait("komorebic.exe toggle-cross-monitor-move-behaviour", , "Hide")
|
||||
}
|
||||
|
||||
UnmanagedWindowOperationBehaviour(operation_behaviour) {
|
||||
RunWait("komorebic.exe unmanaged-window-operation-behaviour " operation_behaviour, , "Hide")
|
||||
}
|
||||
|
||||
FloatRule(identifier, id) {
|
||||
RunWait("komorebic.exe float-rule " identifier " `"" id "`"", , "Hide")
|
||||
}
|
||||
|
||||
ManageRule(identifier, id) {
|
||||
RunWait("komorebic.exe manage-rule " identifier " `"" id "`"", , "Hide")
|
||||
}
|
||||
|
||||
WorkspaceRule(identifier, id, monitor, workspace) {
|
||||
RunWait("komorebic.exe workspace-rule " identifier " `"" id "`" " monitor " " workspace, , "Hide")
|
||||
}
|
||||
|
||||
NamedWorkspaceRule(identifier, id, workspace) {
|
||||
RunWait("komorebic.exe named-workspace-rule " identifier " `"" id "`" " workspace, , "Hide")
|
||||
}
|
||||
|
||||
IdentifyObjectNameChangeApplication(identifier, id) {
|
||||
RunWait("komorebic.exe identify-object-name-change-application " identifier " `"" id "`"", , "Hide")
|
||||
}
|
||||
|
||||
IdentifyTrayApplication(identifier, id) {
|
||||
RunWait("komorebic.exe identify-tray-application " identifier " `"" id "`"", , "Hide")
|
||||
}
|
||||
|
||||
IdentifyLayeredApplication(identifier, id) {
|
||||
RunWait("komorebic.exe identify-layered-application " identifier " `"" id "`"", , "Hide")
|
||||
}
|
||||
|
||||
IdentifyBorderOverflowApplication(identifier, id) {
|
||||
RunWait("komorebic.exe identify-border-overflow-application " identifier " `"" id "`"", , "Hide")
|
||||
}
|
||||
|
||||
ActiveWindowBorder(boolean_state) {
|
||||
RunWait("komorebic.exe active-window-border " boolean_state, , "Hide")
|
||||
}
|
||||
|
||||
ActiveWindowBorderColour(r, g, b, window_kind) {
|
||||
RunWait("komorebic.exe active-window-border-colour " r " " g " " b " --window-kind " window_kind, , "Hide")
|
||||
}
|
||||
|
||||
ActiveWindowBorderWidth(width) {
|
||||
RunWait("komorebic.exe active-window-border-width " width, , "Hide")
|
||||
}
|
||||
|
||||
ActiveWindowBorderOffset(offset) {
|
||||
RunWait("komorebic.exe active-window-border-offset " offset, , "Hide")
|
||||
}
|
||||
|
||||
FocusFollowsMouse(boolean_state, implementation) {
|
||||
RunWait("komorebic.exe focus-follows-mouse " boolean_state " --implementation " implementation, , "Hide")
|
||||
}
|
||||
|
||||
ToggleFocusFollowsMouse(implementation) {
|
||||
RunWait("komorebic.exe toggle-focus-follows-mouse --implementation " implementation, , "Hide")
|
||||
}
|
||||
|
||||
MouseFollowsFocus(boolean_state) {
|
||||
RunWait("komorebic.exe mouse-follows-focus " boolean_state, , "Hide")
|
||||
}
|
||||
|
||||
ToggleMouseFollowsFocus() {
|
||||
RunWait("komorebic.exe toggle-mouse-follows-focus", , "Hide")
|
||||
}
|
||||
|
||||
AhkLibrary() {
|
||||
RunWait("komorebic.exe ahk-library", , "Hide")
|
||||
}
|
||||
|
||||
AhkAppSpecificConfiguration(path, override_path) {
|
||||
RunWait("komorebic.exe ahk-app-specific-configuration " path " " override_path, , "Hide")
|
||||
}
|
||||
|
||||
PwshAppSpecificConfiguration(path, override_path) {
|
||||
RunWait("komorebic.exe pwsh-app-specific-configuration " path " " override_path, , "Hide")
|
||||
}
|
||||
|
||||
FormatAppSpecificConfiguration(path) {
|
||||
RunWait("komorebic.exe format-app-specific-configuration " path, , "Hide")
|
||||
}
|
||||
|
||||
NotificationSchema() {
|
||||
RunWait("komorebic.exe notification-schema", , "Hide")
|
||||
}
|
||||
|
||||
SocketSchema() {
|
||||
RunWait("komorebic.exe socket-schema", , "Hide")
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
; Generated by komorebic.exe
|
||||
|
||||
Start(ffm) {
|
||||
Run, komorebic.exe start --ffm %ffm%, , Hide
|
||||
}
|
||||
|
||||
Stop() {
|
||||
Run, komorebic.exe stop, , Hide
|
||||
}
|
||||
|
||||
State() {
|
||||
Run, komorebic.exe state, , Hide
|
||||
}
|
||||
|
||||
Query(state_query) {
|
||||
Run, komorebic.exe query %state_query%, , Hide
|
||||
}
|
||||
|
||||
Log() {
|
||||
Run, komorebic.exe log, , Hide
|
||||
}
|
||||
|
||||
Focus(operation_direction) {
|
||||
Run, komorebic.exe focus %operation_direction%, , Hide
|
||||
}
|
||||
|
||||
Move(operation_direction) {
|
||||
Run, komorebic.exe move %operation_direction%, , Hide
|
||||
}
|
||||
|
||||
Stack(operation_direction) {
|
||||
Run, komorebic.exe stack %operation_direction%, , Hide
|
||||
}
|
||||
|
||||
Resize(edge, sizing) {
|
||||
Run, komorebic.exe resize %edge% %sizing%, , Hide
|
||||
}
|
||||
|
||||
Unstack() {
|
||||
Run, komorebic.exe unstack, , Hide
|
||||
}
|
||||
|
||||
CycleStack(cycle_direction) {
|
||||
Run, komorebic.exe cycle-stack %cycle_direction%, , Hide
|
||||
}
|
||||
|
||||
MoveToMonitor(target) {
|
||||
Run, komorebic.exe move-to-monitor %target%, , Hide
|
||||
}
|
||||
|
||||
MoveToWorkspace(target) {
|
||||
Run, komorebic.exe move-to-workspace %target%, , Hide
|
||||
}
|
||||
|
||||
SendToMonitor(target) {
|
||||
Run, komorebic.exe send-to-monitor %target%, , Hide
|
||||
}
|
||||
|
||||
SendToWorkspace(target) {
|
||||
Run, komorebic.exe send-to-workspace %target%, , Hide
|
||||
}
|
||||
|
||||
FocusMonitor(target) {
|
||||
Run, komorebic.exe focus-monitor %target%, , Hide
|
||||
}
|
||||
|
||||
FocusWorkspace(target) {
|
||||
Run, komorebic.exe focus-workspace %target%, , Hide
|
||||
}
|
||||
|
||||
NewWorkspace() {
|
||||
Run, komorebic.exe new-workspace, , Hide
|
||||
}
|
||||
|
||||
InvisibleBorders(left, top, right, bottom) {
|
||||
Run, komorebic.exe invisible-borders %left% %top% %right% %bottom%, , Hide
|
||||
}
|
||||
|
||||
AdjustContainerPadding(sizing, adjustment) {
|
||||
Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide
|
||||
}
|
||||
|
||||
AdjustWorkspacePadding(sizing, adjustment) {
|
||||
Run, komorebic.exe adjust-workspace-padding %sizing% %adjustment%, , Hide
|
||||
}
|
||||
|
||||
ChangeLayout(layout) {
|
||||
Run, komorebic.exe change-layout %layout%, , Hide
|
||||
}
|
||||
|
||||
FlipLayout(flip) {
|
||||
Run, komorebic.exe flip-layout %flip%, , Hide
|
||||
}
|
||||
|
||||
Promote() {
|
||||
Run, komorebic.exe promote, , Hide
|
||||
}
|
||||
|
||||
Retile() {
|
||||
Run, komorebic.exe retile, , Hide
|
||||
}
|
||||
|
||||
EnsureWorkspaces(monitor, workspace_count) {
|
||||
Run, komorebic.exe ensure-workspaces %monitor% %workspace_count%, , Hide
|
||||
}
|
||||
|
||||
ContainerPadding(monitor, workspace, size) {
|
||||
Run, komorebic.exe container-padding %monitor% %workspace% %size%, , Hide
|
||||
}
|
||||
|
||||
WorkspacePadding(monitor, workspace, size) {
|
||||
Run, komorebic.exe workspace-padding %monitor% %workspace% %size%, , Hide
|
||||
}
|
||||
|
||||
WorkspaceLayout(monitor, workspace, value) {
|
||||
Run, komorebic.exe workspace-layout %monitor% %workspace% %value%, , Hide
|
||||
}
|
||||
|
||||
WorkspaceTiling(monitor, workspace, value) {
|
||||
Run, komorebic.exe workspace-tiling %monitor% %workspace% %value%, , Hide
|
||||
}
|
||||
|
||||
WorkspaceName(monitor, workspace, value) {
|
||||
Run, komorebic.exe workspace-name %monitor% %workspace% %value%, , Hide
|
||||
}
|
||||
|
||||
TogglePause() {
|
||||
Run, komorebic.exe toggle-pause, , Hide
|
||||
}
|
||||
|
||||
ToggleTiling() {
|
||||
Run, komorebic.exe toggle-tiling, , Hide
|
||||
}
|
||||
|
||||
ToggleFloat() {
|
||||
Run, komorebic.exe toggle-float, , Hide
|
||||
}
|
||||
|
||||
ToggleMonocle() {
|
||||
Run, komorebic.exe toggle-monocle, , Hide
|
||||
}
|
||||
|
||||
ToggleMaximize() {
|
||||
Run, komorebic.exe toggle-maximize, , Hide
|
||||
}
|
||||
|
||||
RestoreWindows() {
|
||||
Run, komorebic.exe restore-windows, , Hide
|
||||
}
|
||||
|
||||
Manage() {
|
||||
Run, komorebic.exe manage, , Hide
|
||||
}
|
||||
|
||||
Unmanage() {
|
||||
Run, komorebic.exe unmanage, , Hide
|
||||
}
|
||||
|
||||
ReloadConfiguration() {
|
||||
Run, komorebic.exe reload-configuration, , Hide
|
||||
}
|
||||
|
||||
WatchConfiguration(boolean_state) {
|
||||
Run, komorebic.exe watch-configuration %boolean_state%, , Hide
|
||||
}
|
||||
|
||||
FloatRule(identifier, id) {
|
||||
Run, komorebic.exe float-rule %identifier% %id%, , Hide
|
||||
}
|
||||
|
||||
ManageRule(identifier, id) {
|
||||
Run, komorebic.exe manage-rule %identifier% %id%, , Hide
|
||||
}
|
||||
|
||||
WorkspaceRule(identifier, id, monitor, workspace) {
|
||||
Run, komorebic.exe workspace-rule %identifier% %id% %monitor% %workspace%, , Hide
|
||||
}
|
||||
|
||||
IdentifyTrayApplication(identifier, id) {
|
||||
Run, komorebic.exe identify-tray-application %identifier% %id%, , Hide
|
||||
}
|
||||
|
||||
IdentifyBorderOverflow(identifier, id) {
|
||||
Run, komorebic.exe identify-border-overflow %identifier% %id%, , Hide
|
||||
}
|
||||
|
||||
FocusFollowsMouse(boolean_state, implementation) {
|
||||
Run, komorebic.exe focus-follows-mouse %boolean_state% --implementation %implementation%, , Hide
|
||||
}
|
||||
|
||||
ToggleFocusFollowsMouse(implementation) {
|
||||
Run, komorebic.exe toggle-focus-follows-mouse --implementation %implementation%, , Hide
|
||||
}
|
||||
|
||||
AhkLibrary() {
|
||||
Run, komorebic.exe ahk-library, , Hide
|
||||
}
|
||||
@@ -1,27 +1,32 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.4"
|
||||
version = "0.1.16"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
||||
categories = ["cli", "tiling-window-manager", "windows"]
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bindings = { package = "bindings", path = "../bindings" }
|
||||
derive-ahk = { path = "../derive-ahk" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
clap = "3.0.0-beta.4"
|
||||
color-eyre = "0.5"
|
||||
dirs = "3"
|
||||
clap = { version = "4", features = ["derive", "wrap_help"] }
|
||||
color-eyre = "0.6"
|
||||
dirs = "5"
|
||||
fs-tail = "0.1"
|
||||
heck = "0.3"
|
||||
heck = "0.4"
|
||||
lazy_static = "1"
|
||||
paste = "1"
|
||||
powershell_script = "0.2"
|
||||
powershell_script = "1.0"
|
||||
reqwest = { version = "0.11", features = ["blocking"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
sysinfo = "0.29"
|
||||
uds_windows = "1"
|
||||
which = "4"
|
||||
windows = { workspace = true }
|
||||
File diff suppressed because it is too large
Load Diff
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
imports_granularity = "Item"
|
||||
574
schema.json
Normal file
574
schema.json
Normal file
@@ -0,0 +1,574 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "StaticConfig",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active_window_border": {
|
||||
"description": "Display an active window border (default: false)",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"active_window_border_colours": {
|
||||
"description": "Active window border colours for different container types",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ActiveWindowBorderColours"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alt_focus_hack": {
|
||||
"description": "Always send the ALT key when using focus commands (default: false)",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"app_specific_configuration_path": {
|
||||
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"border_offset": {
|
||||
"description": "Offset of the active window border (default: None)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rect"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"border_overflow_applications": {
|
||||
"description": "Identify border overflow applications",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"border_width": {
|
||||
"description": "Width of the active window border (default: 20)",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"cross_monitor_move_behaviour": {
|
||||
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MoveBehaviour"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_container_padding": {
|
||||
"description": "Global default container padding (default: 10)",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"default_workspace_padding": {
|
||||
"description": "Global default workspace padding (default: 10)",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"float_rules": {
|
||||
"description": "Individual window floating rules",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"focus_follows_mouse": {
|
||||
"description": "Determine focus follows mouse implementation (default: None)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FocusFollowsMouseImplementation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"global_work_area_offset": {
|
||||
"description": "Global work area (space used for tiling) offset (default: None)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rect"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invisible_borders": {
|
||||
"description": "Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rect"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"layered_applications": {
|
||||
"description": "Identify applications that have the WS_EX_LAYERED extended window style",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"manage_rules": {
|
||||
"description": "Individual window force-manage rules",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"monitors": {
|
||||
"description": "Monitor and workspace configurations",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/MonitorConfig"
|
||||
}
|
||||
},
|
||||
"mouse_follows_focus": {
|
||||
"description": "Enable or disable mouse follows focus (default: true)",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"object_name_change_applications": {
|
||||
"description": "Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"resize_delta": {
|
||||
"description": "Delta to resize windows by (default 50)",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"tray_and_multi_window_applications": {
|
||||
"description": "Identify tray and multi-window applications",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"unmanaged_window_operation_behaviour": {
|
||||
"description": "Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OperationBehaviour"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"window_container_behaviour": {
|
||||
"description": "Determine what happens when a new window is opened (default: Create)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/WindowContainerBehaviour"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"window_hiding_behaviour": {
|
||||
"description": "Which Windows signal to use when hiding windows (default: minimize)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HidingBehaviour"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"ActiveWindowBorderColours": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"monocle",
|
||||
"single",
|
||||
"stack"
|
||||
],
|
||||
"properties": {
|
||||
"monocle": {
|
||||
"description": "Border colour when the container is in monocle mode",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rgb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"single": {
|
||||
"description": "Border colour when the container contains a single window",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rgb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stack": {
|
||||
"description": "Border colour when the container contains multiple windows",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rgb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApplicationIdentifier": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Exe",
|
||||
"Class",
|
||||
"Title"
|
||||
]
|
||||
},
|
||||
"DefaultLayout": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"BSP",
|
||||
"Columns",
|
||||
"Rows",
|
||||
"VerticalStack",
|
||||
"HorizontalStack",
|
||||
"UltrawideVerticalStack"
|
||||
]
|
||||
},
|
||||
"FocusFollowsMouseImplementation": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A custom FFM implementation (slightly more CPU-intensive)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Komorebi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The native (legacy) Windows FFM implementation",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Windows"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"HidingBehaviour": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Hide"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Minimize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Use the undocumented SetCloak Win32 function to hide windows when switching workspaces (has foregrounding issues)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Cloak"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"IdWithIdentifier": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/definitions/ApplicationIdentifier"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MonitorConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"workspaces"
|
||||
],
|
||||
"properties": {
|
||||
"work_area_offset": {
|
||||
"description": "Monitor-specific work area offset (default: None)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rect"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaces": {
|
||||
"description": "Workspace configurations",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/WorkspaceConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"MoveBehaviour": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Swap the window container with the window container at the edge of the adjacent monitor",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Swap"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Insert the window container into the focused workspace on the adjacent monitor",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Insert"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"OperationBehaviour": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Process komorebic commands on temporarily unmanaged/floated windows",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Op"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ignore komorebic commands on temporarily unmanaged/floated windows",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NoOp"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"Rect": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
"top"
|
||||
],
|
||||
"properties": {
|
||||
"bottom": {
|
||||
"description": "The bottom point in a Win32 Rect",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"left": {
|
||||
"description": "The left point in a Win32 Rect",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"right": {
|
||||
"description": "The right point in a Win32 Rect",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"top": {
|
||||
"description": "The top point in a Win32 Rect",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rgb": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"b",
|
||||
"g",
|
||||
"r"
|
||||
],
|
||||
"properties": {
|
||||
"b": {
|
||||
"description": "Blue",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"g": {
|
||||
"description": "Green",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"r": {
|
||||
"description": "Red",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"WindowContainerBehaviour": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Create a new container for each new window",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Append new windows to the focused window container",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Append"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"WorkspaceConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"container_padding": {
|
||||
"description": "Container padding (default: global)",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"custom_layout": {
|
||||
"description": "Custom Layout (default: None)",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"custom_layout_rules": {
|
||||
"description": "Layout rules (default: None)",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"initial_workspace_rules": {
|
||||
"description": "Initial workspace application rules",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"description": "Layout (default: BSP)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DefaultLayout"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"layout_rules": {
|
||||
"description": "Layout rules (default: None)",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/DefaultLayout"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"workspace_padding": {
|
||||
"description": "Container padding (default: global)",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"workspace_rules": {
|
||||
"description": "Permanent workspace application rules",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/IdWithIdentifier"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
whkdrc.sample
Normal file
63
whkdrc.sample
Normal file
@@ -0,0 +1,63 @@
|
||||
.shell powershell
|
||||
|
||||
# Reload whkd configuration
|
||||
# alt + o : taskkill /f /im whkd.exe && start /b whkd # if shell is cmd
|
||||
alt + o : taskkill /f /im whkd.exe && Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
|
||||
alt + shift + o : komorebic reload-configuration
|
||||
|
||||
# App shortcuts - these require shell to be pwsh / powershell
|
||||
# The apps will be focused if open, or launched if not open
|
||||
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
||||
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
|
||||
|
||||
# Focus windows
|
||||
alt + h : komorebic focus left
|
||||
alt + j : komorebic focus down
|
||||
alt + k : komorebic focus up
|
||||
alt + l : komorebic focus right
|
||||
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
|
||||
|
||||
# Move windows
|
||||
alt + shift + h : komorebic move left
|
||||
alt + shift + j : komorebic move down
|
||||
alt + shift + k : komorebic move up
|
||||
alt + shift + l : komorebic move right
|
||||
alt + shift + return : komorebic promote
|
||||
|
||||
# Stack windows
|
||||
alt + left : komorebic stack left
|
||||
alt + down : komorebic stack down
|
||||
alt + up : komorebic stack up
|
||||
alt + right : komorebic stack right
|
||||
alt + oem_1 : komorebic unstack # oem_1 is ;
|
||||
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
|
||||
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
|
||||
|
||||
# Resize
|
||||
alt + oem_plus : komorebic resize-axis horizontal increase
|
||||
alt + oem_minus : komorebic resize-axis horizontal decrease
|
||||
alt + shift + oem_plus : komorebic resize-axis vertical increase
|
||||
alt + shift + oem_minus : komorebic resize-axis vertical decrease
|
||||
|
||||
# Manipulate windows
|
||||
alt + t : komorebic toggle-float
|
||||
alt + shift + f : komorebic toggle-monocle
|
||||
|
||||
# Window manager options
|
||||
alt + shift + r : komorebic retile
|
||||
alt + p : komorebic toggle-pause
|
||||
|
||||
# Layouts
|
||||
alt + x : komorebic flip-layout horizontal
|
||||
alt + y : komorebic flip-layout vertical
|
||||
|
||||
# Workspaces
|
||||
alt + 1 : komorebic focus-workspace 0
|
||||
alt + 2 : komorebic focus-workspace 1
|
||||
alt + 3 : komorebic focus-workspace 2
|
||||
|
||||
# Move windows across workspaces
|
||||
alt + shift + 1 : komorebic move-to-workspace 0
|
||||
alt + shift + 2 : komorebic move-to-workspace 1
|
||||
alt + shift + 3 : komorebic move-to-workspace 2
|
||||
BIN
wix/License.rtf
Normal file
BIN
wix/License.rtf
Normal file
Binary file not shown.
174
wix/main.wxs
Normal file
174
wix/main.wxs
Normal file
@@ -0,0 +1,174 @@
|
||||
<?xml version='1.0' encoding='windows-1252'?>
|
||||
<!--
|
||||
Copyright (C) 2017 Christopher R. Field.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
The "cargo wix" subcommand provides a variety of predefined variables available
|
||||
for customization of this template. The values for each variable are set at
|
||||
installer creation time. The following variables are available:
|
||||
|
||||
TargetTriple = The rustc target triple name.
|
||||
TargetEnv = The rustc target environment. This is typically either
|
||||
"msvc" or "gnu" depending on the toolchain downloaded and
|
||||
installed.
|
||||
TargetVendor = The rustc target vendor. This is typically "pc", but Rust
|
||||
does support other vendors, like "uwp".
|
||||
CargoTargetBinDir = The complete path to the binary (exe). The default would
|
||||
be "target\release\<BINARY_NAME>.exe" where
|
||||
"<BINARY_NAME>" is replaced with the name of each binary
|
||||
target defined in the package's manifest (Cargo.toml). If
|
||||
a different rustc target triple is used than the host,
|
||||
i.e. cross-compiling, then the default path would be
|
||||
"target\<CARGO_TARGET>\<CARGO_PROFILE>\<BINARY_NAME>.exe",
|
||||
where "<CARGO_TARGET>" is replaced with the "CargoTarget"
|
||||
variable value and "<CARGO_PROFILE>" is replaced with the
|
||||
value from the `CargoProfile` variable.
|
||||
CargoTargetDir = The path to the directory for the build artifacts, i.e.
|
||||
"target".
|
||||
CargoProfile = Either "debug" or `release` depending on the build
|
||||
profile. The default is "release".
|
||||
Version = The version for the installer. The default is the
|
||||
"Major.Minor.Fix" semantic versioning number of the Rust
|
||||
package.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Please do not remove these pre-processor If-Else blocks. These are used with
|
||||
the `cargo wix` subcommand to automatically determine the installation
|
||||
destination for 32-bit versus 64-bit installers. Removal of these lines will
|
||||
cause installation errors.
|
||||
-->
|
||||
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder"?>
|
||||
<?else ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder"?>
|
||||
<?endif ?>
|
||||
|
||||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||
|
||||
<Product Id='*' Name='komorebi' UpgradeCode='F8B967B5-7E7B-4E3A-895B-B789EC898B54' Manufacturer='LGUG2Z' Language='1033' Codepage='1252' Version='$(var.Version)'>
|
||||
|
||||
<Package Id='*' Keywords='Installer' Description='A tiling window manager for Windows' Manufacturer='LGUG2Z' InstallerVersion='450' Languages='1033' Compressed='yes' InstallScope='perMachine' SummaryCodepage='1252' />
|
||||
|
||||
<MajorUpgrade Schedule='afterInstallInitialize' DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.' />
|
||||
|
||||
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1' />
|
||||
<Property Id='DiskPrompt' Value='komorebi Installation' />
|
||||
|
||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
|
||||
<Directory Id='APPLICATIONFOLDER' Name='komorebi'>
|
||||
<!--
|
||||
Disabling the license sidecar file in the installer is a two step process:
|
||||
|
||||
1. Comment out or remove the `Component` tag along with its contents.
|
||||
2. Comment out or remove the `ComponentRef` tag with the "License" Id
|
||||
attribute value further down in this file.
|
||||
-->
|
||||
<Component Id='License' Guid='*'>
|
||||
<File Id='LicenseFile' Name='License.rtf' DiskId='1' Source='wix\License.rtf' KeyPath='yes' />
|
||||
</Component>
|
||||
|
||||
<Directory Id='Bin' Name='bin'>
|
||||
<Component Id='Path' Guid='6C6DF276-06C4-4675-BDED-48C5C2BC9BC5' KeyPath='yes'>
|
||||
<Environment Id='PATH' Name='PATH' Value='[Bin]' Permanent='no' Part='last' Action='set' System='yes' />
|
||||
</Component>
|
||||
<Component Id='binary0' Guid='*'>
|
||||
<File Id='exe0' Name='komorebi.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi.exe' KeyPath='yes' />
|
||||
</Component>
|
||||
<Component Id='binary1' Guid='*'>
|
||||
<File Id='exe1' Name='komorebic.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic.exe' KeyPath='yes' />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature Id='Binaries' Title='Application' Description='Installs all binaries and the license.' Level='1' ConfigurableDirectory='APPLICATIONFOLDER' AllowAdvertise='no' Display='expand' Absent='disallow'>
|
||||
<!--
|
||||
Comment out or remove the following `ComponentRef` tag to remove
|
||||
the license sidecar file from the installer.
|
||||
-->
|
||||
<ComponentRef Id='License' />
|
||||
|
||||
<ComponentRef Id='binary0' />
|
||||
|
||||
<ComponentRef Id='binary1' />
|
||||
|
||||
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
|
||||
<ComponentRef Id='Path' />
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize' />
|
||||
|
||||
|
||||
<!--
|
||||
Uncomment the following `Icon` and `Property` tags to change the product icon.
|
||||
|
||||
The product icon is the graphic that appears in the Add/Remove
|
||||
Programs control panel for the application.
|
||||
-->
|
||||
<!--<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>-->
|
||||
<!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->
|
||||
|
||||
<Property Id='ARPHELPLINK' Value='https://github.com/LGUG2Z/komorebi' />
|
||||
|
||||
<UI>
|
||||
<UIRef Id='WixUI_FeatureTree' />
|
||||
<!--
|
||||
Disabling the EULA dialog in the installer is a two step process:
|
||||
|
||||
1. Uncomment the following two `Publish` tags
|
||||
2. Comment out or remove the `<WiXVariable Id='WixUILicenseRtf'...` tag further down
|
||||
|
||||
-->
|
||||
<!--<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>-->
|
||||
<!--<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>-->
|
||||
|
||||
</UI>
|
||||
|
||||
<!--
|
||||
Disabling the EULA dialog in the installer requires commenting out
|
||||
or removing the following `WixVariable` tag
|
||||
-->
|
||||
<WixVariable Id='WixUILicenseRtf' Value='wix\License.rtf' />
|
||||
|
||||
|
||||
<!--
|
||||
Uncomment the next `WixVaraible` tag to customize the installer's
|
||||
Graphical User Interface (GUI) and add a custom banner image across
|
||||
the top of each screen. See the WiX Toolset documentation for details
|
||||
about customization.
|
||||
|
||||
The banner BMP dimensions are 493 x 58 pixels.
|
||||
-->
|
||||
<!--<WixVariable Id='WixUIBannerBmp' Value='wix\Banner.bmp'/>-->
|
||||
|
||||
|
||||
<!--
|
||||
Uncomment the next `WixVariable` tag to customize the installer's
|
||||
Graphical User Interface (GUI) and add a custom image to the first
|
||||
dialog, or screen. See the WiX Toolset documentation for details about
|
||||
customization.
|
||||
|
||||
The dialog BMP dimensions are 493 x 312 pixels.
|
||||
-->
|
||||
<!--<WixVariable Id='WixUIDialogBmp' Value='wix\Dialog.bmp'/>-->
|
||||
|
||||
</Product>
|
||||
|
||||
</Wix>
|
||||
Reference in New Issue
Block a user