mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-15 06:13:36 +01:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cab8b4ad52 | ||
|
|
05777c34b9 | ||
|
|
5094001862 | ||
|
|
bc08e177a1 | ||
|
|
87fe718754 | ||
|
|
fb4fe4d9c3 | ||
|
|
b61b03b1c9 | ||
|
|
a02cd699a0 | ||
|
|
2c876701d8 | ||
|
|
c42739591f | ||
|
|
381253da20 | ||
|
|
cf7532330b | ||
|
|
ffb86458f0 | ||
|
|
292bdb282f | ||
|
|
1625ca6e5d | ||
|
|
df07409a2f | ||
|
|
2e86b607b2 | ||
|
|
6f7e87799b | ||
|
|
4e9b294835 | ||
|
|
8ffe6f78b7 | ||
|
|
42b9305dfe | ||
|
|
1eba8aa01d | ||
|
|
74811fbe13 | ||
|
|
209cd82892 | ||
|
|
98f731ba13 | ||
|
|
c7bf09e34b | ||
|
|
0725549d45 | ||
|
|
13b335cecc | ||
|
|
23aada05d0 | ||
|
|
f11dcbc0cb | ||
|
|
564ee89c84 |
21
.github/dependabot.yml
vendored
Normal file
21
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
assignees:
|
||||
- "LGUG2Z"
|
||||
commit-message:
|
||||
prefix: chore
|
||||
include: scope
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
assignees:
|
||||
- "LGUG2Z"
|
||||
commit-message:
|
||||
prefix: chore
|
||||
include: scope
|
||||
2
.github/workflows/windows.yaml
vendored
2
.github/workflows/windows.yaml
vendored
@@ -20,6 +20,8 @@ jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -52,6 +52,9 @@ scoop:
|
||||
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 "Run 'cp $original_dir\komorebi.sample.ahk $Env:UserProfile\komorebi.ahk' to get started with the sample configuration"
|
||||
- Write-Host "Once you have a configuration file in place, you can run 'komorebic start' to start the window manager"
|
||||
- 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"
|
||||
|
||||
225
Cargo.lock
generated
225
Cargo.lock
generated
@@ -160,6 +160,37 @@ dependencies = [
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "com"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a30a2b2a013da986dc5cc3eda3d19c0d59d53f835be1b2356eb8d00f000c793"
|
||||
dependencies = [
|
||||
"com_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "com_macros"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7606b05842fea68ddcc89e8053b8860ebcb2a0ba8d6abfe3a148e5d5a8d3f0c1"
|
||||
dependencies = [
|
||||
"com_macros_support",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "com_macros_support"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97e9a6d20f4ac8830e309a455d7e9416e65c6af5a97c88c55fbb4c2012e107da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-sha1"
|
||||
version = "0.2.0"
|
||||
@@ -226,6 +257,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-ahk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "3.0.2"
|
||||
@@ -270,10 +310,25 @@ checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.10",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||
|
||||
[[package]]
|
||||
name = "fs-tail"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73c8ee8694b2ad6d79aa976ad8572ca376c0450290041e6e3ae75147356b6ad2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
@@ -414,6 +469,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
@@ -425,9 +489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
@@ -441,7 +505,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebi"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"bitflags",
|
||||
@@ -450,12 +514,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
"ctrlc",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"getset",
|
||||
"hotwatch",
|
||||
"komorebi-core",
|
||||
"lazy_static",
|
||||
"nanoid",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -466,11 +530,12 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"uds_windows",
|
||||
"which",
|
||||
"winvd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"clap",
|
||||
@@ -482,12 +547,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebic"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"derive-ahk",
|
||||
"dirs",
|
||||
"fs-tail",
|
||||
"heck",
|
||||
"komorebi-core",
|
||||
"paste",
|
||||
"powershell_script",
|
||||
@@ -510,9 +578,18 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.99"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
|
||||
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -534,9 +611,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -691,9 +768,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.26.0"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386"
|
||||
checksum = "ee2766204889d09937d00bfbb7fec56bb2a199e2ade963cab19185d8a6104c7c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -716,12 +793,50 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"petgraph",
|
||||
"redox_syscall 0.2.10",
|
||||
"smallvec",
|
||||
"thread-id",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
@@ -884,6 +999,12 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
@@ -900,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -965,18 +1086,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.127"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
||||
checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.127"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
||||
checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1044,9 +1165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.74"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
||||
checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1055,9 +1176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.20.0"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0af066e6272f2175c1783cfc2ebf3e2d8dfe2c182b00677fdeccbf8291af83fb"
|
||||
checksum = "4786f320388e6031aa78c68455553f4e3425deeeb40565fecba3d101c1faf21f"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"core-foundation-sys",
|
||||
@@ -1096,6 +1217,17 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
@@ -1151,9 +1283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
|
||||
checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
@@ -1191,9 +1323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab69019741fca4d98be3c62d2b75254528b5432233fd8a4d2739fec20278de48"
|
||||
checksum = "b9cbe87a2fa7e35900ce5de20220a582a9483a7063811defce79d7cbd59d4cfe"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"chrono",
|
||||
@@ -1324,9 +1456,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68088239696c06152844eadc03d262f088932cce50c67e4ace86e19d95e976fe"
|
||||
checksum = "ef84dd25f4c69a271b1bba394532bf400523b43169de21dfc715e8f8e491053d"
|
||||
dependencies = [
|
||||
"const-sha1",
|
||||
"windows_gen",
|
||||
@@ -1335,18 +1467,47 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_gen"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf583322dc423ee021035b358e535015f7fd163058a31e2d37b99a939141121d"
|
||||
checksum = "ac7bb21b8ff5e801232b72a6ff554b4cc0cef9ed9238188c3ca78fe3968a7e5d"
|
||||
dependencies = [
|
||||
"windows_quote",
|
||||
"windows_reader",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_macros"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58acfb8832e9f707f8997bd161e537a1c1f603e60a5bd9c3cf53484fdcc998f3"
|
||||
checksum = "5566b8c51118769e4a9094a688bf1233a3f36aacbfc78f3b15817fe0b6e0442f"
|
||||
dependencies = [
|
||||
"syn",
|
||||
"windows_gen",
|
||||
"windows_quote",
|
||||
"windows_reader",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_quote"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4af8236a9493c38855f95cdd11b38b342512a5df4ee7473cffa828b5ebb0e39c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_reader"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab"
|
||||
|
||||
[[package]]
|
||||
name = "winvd"
|
||||
version = "0.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bab2d5c745381b9c72797230150ec62244e693064fa0d654b5c4e6c75132a56"
|
||||
dependencies = [
|
||||
"com",
|
||||
"crossbeam-channel",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
members = [
|
||||
"bindings",
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"komorebic"
|
||||
|
||||
257
README.md
257
README.md
@@ -59,6 +59,8 @@ This means that:
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 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).
|
||||
@@ -68,6 +70,8 @@ using [`setx`](https://docs.microsoft.com/en-us/windows-server/administration/wi
|
||||
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:
|
||||
|
||||
@@ -79,6 +83,8 @@ 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
|
||||
required, so make sure you have also installed
|
||||
@@ -91,6 +97,8 @@ cargo install --path komorebi --locked
|
||||
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:
|
||||
@@ -102,17 +110,45 @@ Start-Process komorebi -WindowStyle hidden
|
||||
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`.
|
||||
|
||||
### 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 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.
|
||||
|
||||
There is also tentative support for loading a AutoHotKey v2, if the file is named `komorebi.ahk2` and
|
||||
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).
|
||||
|
||||
### Common First-Time Tips
|
||||
|
||||
#### Floating Windows
|
||||
|
||||
Sometimes you will want a specific application to never be tiled, and instead float all the time. You add add rules to
|
||||
enforce this behaviour:
|
||||
|
||||
```powershell
|
||||
komorebic.exe float-rule title "Control Panel"
|
||||
# komorebic.exe float-rule exe [EXE NAME]
|
||||
# komorebic.exe float-rule class [CLASS NAME]
|
||||
```
|
||||
|
||||
#### Windows Not Getting Managed
|
||||
|
||||
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:
|
||||
|
||||
```powershell
|
||||
komorebic.exe manage-rule exe TIM.exe
|
||||
# komorebic.exe manage-rule class [CLASS NAME]
|
||||
# komorebic.exe manage-rule title [TITLE]
|
||||
```
|
||||
|
||||
#### Tray Applications
|
||||
|
||||
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
|
||||
@@ -120,10 +156,11 @@ this application appropriately by identifying it via the executable name or the
|
||||
|
||||
```powershell
|
||||
komorebic.exe identify-tray-application exe Discord.exe
|
||||
komorebic.exe identify-tray-application exe Telegram.exe
|
||||
# komorebic.exe identify-tray-application class [CLASS NAME]
|
||||
# komorebic.exe identify-tray-application title [TITLE]
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## Configuration with `komorebic`
|
||||
|
||||
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
|
||||
@@ -133,6 +170,66 @@ You can run `komorebic.exe` to get a full list of the commands that you can use
|
||||
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
|
||||
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
|
||||
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
|
||||
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)
|
||||
```
|
||||
|
||||
### AutoHotKey Helper Library for `komorebic`
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Multi-monitor
|
||||
@@ -141,8 +238,10 @@ each command.
|
||||
- [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
|
||||
- [x] Move focused window container to workspace
|
||||
- [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
|
||||
@@ -152,18 +251,20 @@ each command.
|
||||
- [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
|
||||
- [x] Floating rules based on window title
|
||||
- [x] Floating rules based on window class
|
||||
- [x] Identify 'close/minimize to tray' applications
|
||||
- [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 'close/minimize to tray' applications by exe name and class
|
||||
- [x] Toggle floating windows
|
||||
- [x] Toggle monocle window
|
||||
- [x] Toggle native maximization
|
||||
- [x] Toggle focus follows mouse
|
||||
- [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
|
||||
|
||||
## Development
|
||||
@@ -184,7 +285,7 @@ If you use IntelliJ, you should enable the following settings to ensure that cod
|
||||
the IDE for completions and navigation:
|
||||
|
||||
- Set `Expand declarative macros`
|
||||
to `Use new engine` [here](jetbrains://idea/settings?name=Languages+%26+Frameworks--Rust)
|
||||
to `Use new engine` under "Settings > Langauges & Frameworks > Rust"
|
||||
- Enable the following experimental features:
|
||||
- `org.rust.cargo.evaluate.build.scripts`
|
||||
- `org.rust.macros.proc`
|
||||
@@ -200,134 +301,26 @@ 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`.
|
||||
|
||||
### Restoring Windows
|
||||
|
||||
Running `komorebic restore-windows` will read the list of window handles and forcibly restore them, regardless of
|
||||
whether the main `komorebi` process is running.
|
||||
|
||||
### Panics and Deadlocks
|
||||
|
||||
If `komorebi` ever stops responding, it is most likely either due to either a panic or a deadlock. In the case of a
|
||||
panic, this will be reported in the log. In the case of a deadlock, there will not be any errors in the log, but the
|
||||
process and the log will appear frozen.
|
||||
|
||||
If you believe you have encountered a deadlock, you can compile `komorebi` with `--features deadlock_detection` and try
|
||||
reproducing the deadlock again. This will check for deadlocks every 5 seconds in the background, and if a deadlock is
|
||||
found, information about it will appear in the log which can be shared when opening an issu which can be shared when
|
||||
opening an issue.
|
||||
|
||||
## Window Manager State and Integrations
|
||||
|
||||
The current state of the window manager can be queried using the `komorebic state` command, which returns a JSON
|
||||
representation of the `WindowManager` struct.
|
||||
representation of the `State` struct, which includes the current state of `WindowManager`.
|
||||
|
||||
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).
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": {
|
||||
"elements": [
|
||||
{
|
||||
"id": 65537,
|
||||
"monitor_size": {
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"right": 3840,
|
||||
"bottom": 2160
|
||||
},
|
||||
"work_area_size": {
|
||||
"left": 0,
|
||||
"top": 40,
|
||||
"right": 3840,
|
||||
"bottom": 2120
|
||||
},
|
||||
"workspaces": {
|
||||
"elements": [
|
||||
{
|
||||
"name": "bsp",
|
||||
"containers": {
|
||||
"elements": [
|
||||
{
|
||||
"windows": {
|
||||
"elements": [
|
||||
{
|
||||
"hwnd": 2623596,
|
||||
"title": "komorebi – README.md",
|
||||
"exe": "idea64.exe",
|
||||
"class": "SunAwtFrame",
|
||||
"rect": {
|
||||
"left": 8,
|
||||
"top": 60,
|
||||
"right": 1914,
|
||||
"bottom": 2092
|
||||
}
|
||||
}
|
||||
],
|
||||
"focused": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"windows": {
|
||||
"elements": [
|
||||
{
|
||||
"hwnd": 198266,
|
||||
"title": "LGUG2Z/komorebi: A(nother) tiling window manager for Windows 10 based on binary space partitioning - Mozilla Firefox",
|
||||
"exe": "firefox.exe",
|
||||
"class": "MozillaWindowClass",
|
||||
"rect": {
|
||||
"left": 1918,
|
||||
"top": 60,
|
||||
"right": 1914,
|
||||
"bottom": 1042
|
||||
}
|
||||
}
|
||||
],
|
||||
"focused": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"windows": {
|
||||
"elements": [
|
||||
{
|
||||
"hwnd": 1247352,
|
||||
"title": "Windows PowerShell",
|
||||
"exe": "WindowsTerminal.exe",
|
||||
"class": "CASCADIA_HOSTING_WINDOW_CLASS",
|
||||
"rect": {
|
||||
"left": 1918,
|
||||
"top": 1110,
|
||||
"right": 959,
|
||||
"bottom": 1042
|
||||
}
|
||||
}
|
||||
],
|
||||
"focused": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"windows": {
|
||||
"elements": [
|
||||
{
|
||||
"hwnd": 395464,
|
||||
"title": "Signal",
|
||||
"exe": "Signal.exe",
|
||||
"class": "Chrome_WidgetWin_1",
|
||||
"rect": {
|
||||
"left": 2873,
|
||||
"top": 1110,
|
||||
"right": 959,
|
||||
"bottom": 1042
|
||||
}
|
||||
}
|
||||
],
|
||||
"focused": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"focused": 2
|
||||
},
|
||||
"monocle_container": null,
|
||||
"floating_windows": [],
|
||||
"layout": "BSP",
|
||||
"layout_flip": null,
|
||||
"workspace_padding": 10,
|
||||
"container_padding": 10
|
||||
}
|
||||
],
|
||||
"focused": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"focused": 0
|
||||
},
|
||||
"is_paused": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
windows = "0.18"
|
||||
windows = "0.19"
|
||||
|
||||
[build-dependencies]
|
||||
windows = "0.18"
|
||||
windows = "0.19"
|
||||
|
||||
14
derive-ahk/Cargo.toml
Normal file
14
derive-ahk/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "derive-ahk"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
120
derive-ahk/src/lib.rs
Normal file
120
derive-ahk/src/lib.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![no_implicit_prelude]
|
||||
|
||||
use ::std::clone::Clone;
|
||||
use ::std::convert::From;
|
||||
use ::std::convert::Into;
|
||||
use ::std::iter::Extend;
|
||||
use ::std::iter::Iterator;
|
||||
use ::std::matches;
|
||||
use ::std::string::ToString;
|
||||
use ::std::unreachable;
|
||||
|
||||
use ::quote::quote;
|
||||
use ::std::option::Option::Some;
|
||||
use ::syn::parse_macro_input;
|
||||
use ::syn::Data;
|
||||
use ::syn::DataEnum;
|
||||
use ::syn::DeriveInput;
|
||||
use ::syn::Fields;
|
||||
use ::syn::FieldsNamed;
|
||||
use ::syn::FieldsUnnamed;
|
||||
|
||||
#[proc_macro_derive(AhkFunction)]
|
||||
pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = input.ident;
|
||||
|
||||
match input.data {
|
||||
Data::Struct(s) => match s.fields {
|
||||
Fields::Named(FieldsNamed { named, .. }) => {
|
||||
let idents = named.iter().map(|f| &f.ident);
|
||||
let arguments = quote! {#(#idents), *}.to_string();
|
||||
|
||||
let idents = named.iter().map(|f| &f.ident);
|
||||
let called_arguments = quote! {#(%#idents%) *}
|
||||
.to_string()
|
||||
.replace(" %", "%")
|
||||
.replace("% ", "%")
|
||||
.replace("%%", "% %");
|
||||
|
||||
quote! {
|
||||
impl AhkFunction for #name {
|
||||
fn generate_ahk_function() -> String {
|
||||
::std::format!(r#"
|
||||
{}({}) {{
|
||||
Run, komorebic.exe {} {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
#arguments,
|
||||
stringify!(#name).to_kebab_case(),
|
||||
#called_arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("only to be used on structs with named fields"),
|
||||
},
|
||||
_ => unreachable!("only to be used on structs"),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AhkLibrary)]
|
||||
pub fn ahk_library(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = input.ident;
|
||||
|
||||
match input.data {
|
||||
Data::Enum(DataEnum { variants, .. }) => {
|
||||
let enums = variants.iter().filter(|&v| {
|
||||
matches!(v.fields, Fields::Unit) || matches!(v.fields, Fields::Unnamed(..))
|
||||
});
|
||||
|
||||
let mut stream = ::proc_macro2::TokenStream::new();
|
||||
|
||||
for variant in enums.clone() {
|
||||
match &variant.fields {
|
||||
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
|
||||
for field in unnamed {
|
||||
stream.extend(quote! {
|
||||
v.push(#field::generate_ahk_function());
|
||||
});
|
||||
}
|
||||
}
|
||||
Fields::Unit => {
|
||||
let name = &variant.ident;
|
||||
stream.extend(quote! {
|
||||
v.push(::std::format!(r#"
|
||||
{}() {{
|
||||
Run, komorebic.exe {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
::std::stringify!(#name).to_kebab_case()
|
||||
));
|
||||
});
|
||||
}
|
||||
Fields::Named(_) => {
|
||||
unreachable!("only to be used with unnamed and unit fields");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl #name {
|
||||
fn generate_ahk_library() -> String {
|
||||
let mut v: Vec<String> = vec![String::from("; Generated by komorebic.exe")];
|
||||
|
||||
#stream
|
||||
|
||||
v.join("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("only to be used on enums"),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -12,7 +12,8 @@ pub enum CycleDirection {
|
||||
}
|
||||
|
||||
impl CycleDirection {
|
||||
pub fn next_idx(&self, idx: usize, len: usize) -> usize {
|
||||
#[must_use]
|
||||
pub const fn next_idx(&self, idx: usize, len: usize) -> usize {
|
||||
match self {
|
||||
CycleDirection::Previous => {
|
||||
if idx == 0 {
|
||||
|
||||
@@ -20,13 +20,15 @@ pub enum Layout {
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum LayoutFlip {
|
||||
pub enum Flip {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
HorizontalAndVertical,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
#[must_use]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn resize(
|
||||
&self,
|
||||
unaltered: &Rect,
|
||||
@@ -35,10 +37,14 @@ impl Layout {
|
||||
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 = if let Some(step) = step { step } else { 50 };
|
||||
let resize_step = step.unwrap_or(50);
|
||||
|
||||
match edge {
|
||||
OperationDirection::Left => match sizing {
|
||||
@@ -117,19 +123,21 @@ impl Layout {
|
||||
},
|
||||
};
|
||||
|
||||
if !r.eq(&Rect::default()) {
|
||||
Option::from(r)
|
||||
} else {
|
||||
if r.eq(&Rect::default()) {
|
||||
None
|
||||
} else {
|
||||
Option::from(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[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<LayoutFlip>,
|
||||
layout_flip: Option<Flip>,
|
||||
resize_dimensions: &[Option<Rect>],
|
||||
) -> Vec<Rect> {
|
||||
let len = usize::from(len);
|
||||
@@ -187,24 +195,6 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn next(&mut self) {
|
||||
match self {
|
||||
Layout::BSP => *self = Layout::Columns,
|
||||
Layout::Columns => *self = Layout::Rows,
|
||||
Layout::Rows => *self = Layout::BSP,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
match self {
|
||||
Layout::BSP => *self = Layout::Rows,
|
||||
Layout::Columns => *self = Layout::BSP,
|
||||
Layout::Rows => *self = Layout::Columns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {
|
||||
let mut resize_adjustments = resize_dimensions.to_vec();
|
||||
|
||||
@@ -213,6 +203,7 @@ fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Optio
|
||||
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 {
|
||||
@@ -291,7 +282,7 @@ fn recursive_fibonacci(
|
||||
idx: usize,
|
||||
count: usize,
|
||||
area: &Rect,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
layout_flip: Option<Flip>,
|
||||
resize_adjustments: Vec<Option<Rect>>,
|
||||
) -> Vec<Rect> {
|
||||
let mut a = *area;
|
||||
@@ -313,37 +304,37 @@ fn recursive_fibonacci(
|
||||
|
||||
let (main_x, alt_x, alt_y, main_y);
|
||||
|
||||
match layout_flip {
|
||||
Some(flip) => match flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
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;
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
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;
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => {
|
||||
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;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
main_y = resized.top;
|
||||
alt_y = resized.top + half_resized_height;
|
||||
}
|
||||
} 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 {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgEnum;
|
||||
@@ -8,8 +11,8 @@ use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
pub use cycle_direction::CycleDirection;
|
||||
pub use layout::Flip;
|
||||
pub use layout::Layout;
|
||||
pub use layout::LayoutFlip;
|
||||
pub use operation_direction::OperationDirection;
|
||||
pub use rect::Rect;
|
||||
|
||||
@@ -29,14 +32,19 @@ pub enum SocketMessage {
|
||||
CycleStack(CycleDirection),
|
||||
MoveContainerToMonitorNumber(usize),
|
||||
MoveContainerToWorkspaceNumber(usize),
|
||||
SendContainerToMonitorNumber(usize),
|
||||
SendContainerToWorkspaceNumber(usize),
|
||||
Promote,
|
||||
ToggleFloat,
|
||||
ToggleMonocle,
|
||||
ToggleMaximize,
|
||||
// Current Workspace Commands
|
||||
ManageFocusedWindow,
|
||||
UnmanageFocusedWindow,
|
||||
AdjustContainerPadding(Sizing, i32),
|
||||
AdjustWorkspacePadding(Sizing, i32),
|
||||
ChangeLayout(Layout),
|
||||
FlipLayout(LayoutFlip),
|
||||
FlipLayout(Flip),
|
||||
// Monitor and Workspace Commands
|
||||
EnsureWorkspaces(usize, usize),
|
||||
NewWorkspace,
|
||||
@@ -54,12 +62,13 @@ pub enum SocketMessage {
|
||||
// Configuration
|
||||
ReloadConfiguration,
|
||||
WatchConfiguration(bool),
|
||||
FloatClass(String),
|
||||
FloatExe(String),
|
||||
FloatTitle(String),
|
||||
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
|
||||
FloatRule(ApplicationIdentifier, String),
|
||||
ManageRule(ApplicationIdentifier, String),
|
||||
IdentifyTrayApplication(ApplicationIdentifier, String),
|
||||
State,
|
||||
FocusFollowsMouse(bool),
|
||||
ToggleFocusFollowsMouse,
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
@@ -96,7 +105,8 @@ pub enum Sizing {
|
||||
}
|
||||
|
||||
impl Sizing {
|
||||
pub fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {
|
||||
#[must_use]
|
||||
pub const fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {
|
||||
match self {
|
||||
Sizing::Increase => value + adjustment,
|
||||
Sizing::Decrease => {
|
||||
|
||||
@@ -4,8 +4,8 @@ use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::Flip;
|
||||
use crate::Layout;
|
||||
use crate::LayoutFlip;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@@ -17,59 +17,46 @@ pub enum OperationDirection {
|
||||
}
|
||||
|
||||
impl OperationDirection {
|
||||
pub fn opposite(self) -> Self {
|
||||
#[must_use]
|
||||
pub const fn opposite(self) -> Self {
|
||||
match self {
|
||||
OperationDirection::Left => OperationDirection::Right,
|
||||
OperationDirection::Right => OperationDirection::Left,
|
||||
OperationDirection::Up => OperationDirection::Down,
|
||||
OperationDirection::Down => OperationDirection::Up,
|
||||
Self::Left => Self::Right,
|
||||
Self::Right => Self::Left,
|
||||
Self::Up => Self::Down,
|
||||
Self::Down => Self::Up,
|
||||
}
|
||||
}
|
||||
|
||||
fn flip_direction(
|
||||
direction: &OperationDirection,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
) -> OperationDirection {
|
||||
if let Some(flip) = layout_flip {
|
||||
match direction {
|
||||
OperationDirection::Left => match flip {
|
||||
LayoutFlip::Horizontal | LayoutFlip::HorizontalAndVertical => {
|
||||
OperationDirection::Right
|
||||
}
|
||||
_ => *direction,
|
||||
},
|
||||
OperationDirection::Right => match flip {
|
||||
LayoutFlip::Horizontal | LayoutFlip::HorizontalAndVertical => {
|
||||
OperationDirection::Left
|
||||
}
|
||||
_ => *direction,
|
||||
},
|
||||
OperationDirection::Up => match flip {
|
||||
LayoutFlip::Vertical | LayoutFlip::HorizontalAndVertical => {
|
||||
OperationDirection::Down
|
||||
}
|
||||
_ => *direction,
|
||||
},
|
||||
OperationDirection::Down => match flip {
|
||||
LayoutFlip::Vertical | LayoutFlip::HorizontalAndVertical => {
|
||||
OperationDirection::Up
|
||||
}
|
||||
_ => *direction,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
*direction
|
||||
}
|
||||
fn flip_direction(direction: Self, layout_flip: Option<Flip>) -> Self {
|
||||
layout_flip.map_or(direction, |flip| match direction {
|
||||
Self::Left => match flip {
|
||||
Flip::Horizontal | Flip::HorizontalAndVertical => Self::Right,
|
||||
Flip::Vertical => direction,
|
||||
},
|
||||
Self::Right => match flip {
|
||||
Flip::Horizontal | Flip::HorizontalAndVertical => Self::Left,
|
||||
Flip::Vertical => direction,
|
||||
},
|
||||
Self::Up => match flip {
|
||||
Flip::Vertical | Flip::HorizontalAndVertical => Self::Down,
|
||||
Flip::Horizontal => direction,
|
||||
},
|
||||
Self::Down => match flip {
|
||||
Flip::Vertical | Flip::HorizontalAndVertical => Self::Up,
|
||||
Flip::Horizontal => direction,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid(
|
||||
&self,
|
||||
self,
|
||||
layout: Layout,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
layout_flip: Option<Flip>,
|
||||
idx: usize,
|
||||
len: usize,
|
||||
) -> bool {
|
||||
match OperationDirection::flip_direction(self, layout_flip) {
|
||||
match Self::flip_direction(self, layout_flip) {
|
||||
OperationDirection::Up => match layout {
|
||||
Layout::BSP => len > 2 && idx != 0 && idx != 1,
|
||||
Layout::Columns => false,
|
||||
@@ -93,9 +80,10 @@ impl OperationDirection {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_idx(&self, layout: Layout, layout_flip: Option<LayoutFlip>, idx: usize) -> usize {
|
||||
match OperationDirection::flip_direction(self, layout_flip) {
|
||||
OperationDirection::Up => match layout {
|
||||
#[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
|
||||
@@ -106,11 +94,11 @@ impl OperationDirection {
|
||||
Layout::Columns => unreachable!(),
|
||||
Layout::Rows => idx - 1,
|
||||
},
|
||||
OperationDirection::Down => match layout {
|
||||
Self::Down => match layout {
|
||||
Layout::BSP | Layout::Rows => idx + 1,
|
||||
Layout::Columns => unreachable!(),
|
||||
},
|
||||
OperationDirection::Left => match layout {
|
||||
Self::Left => match layout {
|
||||
Layout::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 2
|
||||
@@ -121,7 +109,7 @@ impl OperationDirection {
|
||||
Layout::Columns => idx - 1,
|
||||
Layout::Rows => unreachable!(),
|
||||
},
|
||||
OperationDirection::Right => match layout {
|
||||
Self::Right => match layout {
|
||||
Layout::BSP | Layout::Columns => idx + 1,
|
||||
Layout::Rows => unreachable!(),
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ pub struct Rect {
|
||||
|
||||
impl Default for Rect {
|
||||
fn default() -> Self {
|
||||
Rect {
|
||||
Self {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
@@ -23,7 +23,7 @@ impl Default for Rect {
|
||||
|
||||
impl From<RECT> for Rect {
|
||||
fn from(rect: RECT) -> Self {
|
||||
Rect {
|
||||
Self {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right - rect.left,
|
||||
@@ -42,7 +42,8 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, point: (i32, i32)) -> bool {
|
||||
#[must_use]
|
||||
pub const fn contains_point(&self, point: (i32, i32)) -> bool {
|
||||
point.0 >= self.left
|
||||
&& point.0 <= self.left + self.right
|
||||
&& point.1 >= self.top
|
||||
|
||||
@@ -29,6 +29,10 @@ Run, komorebic.exe workspace-layout 0 1 columns, , Hide
|
||||
; Set the floaty layout to not tile any windows
|
||||
Run, komorebic.exe workspace-tiling 0 4 disable, , Hide
|
||||
|
||||
; 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
|
||||
|
||||
; Always float IntelliJ popups, matching on class
|
||||
Run, komorebic.exe float-rule class SunAwtDialog, , Hide
|
||||
; Always float Control Panel, matching on title
|
||||
@@ -42,6 +46,9 @@ Run, komorebic.exe float-rule exe wincompose.exe, , Hide
|
||||
Run, komorebic.exe float-rule title Calculator, , Hide
|
||||
Run, komorebic.exe float-rule exe 1Password.exe, , Hide
|
||||
|
||||
; Always manage forcibly these applications that don't automatically get picked up by komorebi
|
||||
Run, komorebic.exe manage-rule exe TIM.exe, , Hide
|
||||
|
||||
; Identify applications that close to the tray
|
||||
Run, komorebic.exe identify-tray-application exe Discord.exe, , Hide
|
||||
|
||||
@@ -129,6 +136,11 @@ return
|
||||
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
|
||||
|
||||
223
komorebi.sample.with.lib.ahk
Normal file
223
komorebi.sample.with.lib.ahk
Normal file
@@ -0,0 +1,223 @@
|
||||
#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 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")
|
||||
|
||||
; 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
|
||||
|
||||
; Toggle focus follows mouse
|
||||
!0::
|
||||
ToggleFocusFollowsMouse()
|
||||
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,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -15,11 +15,11 @@ crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
ctrlc = "3"
|
||||
dirs = "3"
|
||||
eyre = "0.6"
|
||||
getset = "0.1"
|
||||
hotwatch = "0.4"
|
||||
lazy_static = "1"
|
||||
nanoid = "0.4"
|
||||
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
|
||||
paste = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
@@ -29,4 +29,8 @@ tracing = "0.1"
|
||||
tracing-appender = "0.1"
|
||||
tracing-subscriber = "0.2"
|
||||
uds_windows = "1"
|
||||
which = "4"
|
||||
which = "4"
|
||||
winvd = "0.0.20"
|
||||
|
||||
[features]
|
||||
deadlock_detection = []
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::thread;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use lazy_static::lazy_static;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use parking_lot::deadlock;
|
||||
use parking_lot::Mutex;
|
||||
use sysinfo::SystemExt;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
@@ -18,6 +25,7 @@ use which::which;
|
||||
|
||||
use crate::process_command::listen_for_commands;
|
||||
use crate::process_event::listen_for_events;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
@@ -40,9 +48,6 @@ mod winevent_listener;
|
||||
mod workspace;
|
||||
|
||||
lazy_static! {
|
||||
static ref FLOAT_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_EXES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_TITLES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
|
||||
@@ -54,15 +59,19 @@ lazy_static! {
|
||||
"chrome.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
"ApplicationFrameHost.exe".to_string(),
|
||||
"steam.exe".to_string()
|
||||
"steam.exe".to_string(),
|
||||
]));
|
||||
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"firefox.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
]));
|
||||
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
|
||||
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![]));
|
||||
}
|
||||
|
||||
fn setup() -> Result<WorkerGuard> {
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
}
|
||||
@@ -73,9 +82,11 @@ fn setup() -> Result<WorkerGuard> {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let appender = tracing_appender::rolling::never(home, "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);
|
||||
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::fmt::Subscriber::builder()
|
||||
@@ -85,6 +96,11 @@ fn setup() -> Result<WorkerGuard> {
|
||||
tracing_subscriber::fmt::Layer::default()
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false),
|
||||
)
|
||||
.with(
|
||||
tracing_subscriber::fmt::Layer::default()
|
||||
.with_writer(color_non_blocking)
|
||||
.with_ansi(true),
|
||||
),
|
||||
)?;
|
||||
|
||||
@@ -113,11 +129,11 @@ fn setup() -> Result<WorkerGuard> {
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(guard)
|
||||
Ok((guard, color_guard))
|
||||
}
|
||||
|
||||
pub fn load_configuration() -> Result<()> {
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
|
||||
let mut config_v1 = home.clone();
|
||||
config_v1.push("komorebi.ahk");
|
||||
@@ -131,7 +147,7 @@ pub fn load_configuration() -> Result<()> {
|
||||
config_v1
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.context("cannot convert path to string")?
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("autohotkey.exe")
|
||||
@@ -143,7 +159,7 @@ pub fn load_configuration() -> Result<()> {
|
||||
config_v2
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.context("cannot convert path to string")?
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
Command::new("AutoHotkey64.exe")
|
||||
@@ -154,6 +170,29 @@ pub fn load_configuration() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
#[tracing::instrument]
|
||||
fn detect_deadlocks() {
|
||||
// Create a background thread which checks for deadlocks every 10s
|
||||
thread::spawn(move || loop {
|
||||
tracing::info!("running deadlock detector");
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
let deadlocks = deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::error!("{} deadlocks detected", deadlocks.len());
|
||||
for (i, threads) in deadlocks.iter().enumerate() {
|
||||
tracing::error!("deadlock #{}", i);
|
||||
for t in threads {
|
||||
tracing::error!("thread id: {:#?}", t.thread_id());
|
||||
tracing::error!("{:#?}", t.backtrace());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn main() -> Result<()> {
|
||||
match std::env::args().count() {
|
||||
@@ -167,7 +206,10 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let _guard = setup()?;
|
||||
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)?;
|
||||
@@ -178,11 +220,11 @@ fn main() -> Result<()> {
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(window_manager::new(Arc::new(Mutex::new(
|
||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
incoming,
|
||||
)))?));
|
||||
|
||||
wm.lock().unwrap().init()?;
|
||||
wm.lock().init()?;
|
||||
listen_for_commands(wm.clone());
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
@@ -203,7 +245,7 @@ fn main() -> Result<()> {
|
||||
"received ctrl-c, restoring all hidden windows and terminating process"
|
||||
);
|
||||
|
||||
wm.lock().unwrap().restore_all_windows();
|
||||
wm.lock().restore_all_windows();
|
||||
std::process::exit(130);
|
||||
}
|
||||
_ => Ok(()),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
@@ -56,7 +56,7 @@ impl Monitor {
|
||||
pub fn add_container(&mut self, container: Container) -> Result<()> {
|
||||
let workspace = self
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?;
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
workspace.add_container(container);
|
||||
|
||||
@@ -70,16 +70,25 @@ impl Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_to_workspace(
|
||||
&mut self,
|
||||
target_workspace_idx: usize,
|
||||
follow: bool,
|
||||
) -> Result<()> {
|
||||
let container = self
|
||||
let workspace = self
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
if workspace.maximized_window().is_some() {
|
||||
return Err(anyhow!(
|
||||
"cannot move native maximized window to another monitor or workspace"
|
||||
));
|
||||
}
|
||||
|
||||
let container = workspace
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let workspaces = self.workspaces_mut();
|
||||
|
||||
@@ -120,7 +129,7 @@ impl Monitor {
|
||||
if name.is_some() {
|
||||
self.workspaces_mut()
|
||||
.get_mut(idx)
|
||||
.context("there is no workspace")?
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||
.set_name(name);
|
||||
}
|
||||
}
|
||||
@@ -136,7 +145,7 @@ impl Monitor {
|
||||
let work_area = *self.work_area_size();
|
||||
|
||||
self.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||
.update(&work_area)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,11 +3,11 @@ use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use parking_lot::Mutex;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
@@ -16,17 +16,16 @@ use komorebi_core::SocketMessage;
|
||||
use crate::window_manager;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::FLOAT_CLASSES;
|
||||
use crate::FLOAT_EXES;
|
||||
use crate::FLOAT_TITLES;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||
use crate::WORKSPACE_RULES;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||
let listener = wm
|
||||
.lock()
|
||||
.unwrap()
|
||||
.command_listener
|
||||
.try_clone()
|
||||
.expect("could not clone unix listener");
|
||||
@@ -35,7 +34,7 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||
tracing::info!("listening");
|
||||
for client in listener.incoming() {
|
||||
match client {
|
||||
Ok(stream) => match wm.lock().unwrap().read_commands(stream) {
|
||||
Ok(stream) => match wm.lock().read_commands(stream) {
|
||||
Ok(()) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
},
|
||||
@@ -51,6 +50,8 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||
impl WindowManager {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn process_command(&mut self, message: SocketMessage) -> Result<()> {
|
||||
self.validate_virtual_desktop_id();
|
||||
|
||||
match message {
|
||||
SocketMessage::Promote => self.promote_container_to_front()?,
|
||||
SocketMessage::FocusWindow(direction) => {
|
||||
@@ -66,28 +67,31 @@ impl WindowManager {
|
||||
}
|
||||
SocketMessage::ToggleFloat => self.toggle_float()?,
|
||||
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
|
||||
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
|
||||
SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {
|
||||
self.set_container_padding(monitor_idx, workspace_idx, size)?;
|
||||
}
|
||||
SocketMessage::WorkspacePadding(monitor_idx, workspace_idx, size) => {
|
||||
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
|
||||
}
|
||||
SocketMessage::FloatClass(target) => {
|
||||
let mut float_classes = FLOAT_CLASSES.lock().unwrap();
|
||||
if !float_classes.contains(&target) {
|
||||
float_classes.push(target);
|
||||
SocketMessage::WorkspaceRule(_, id, monitor_idx, workspace_idx) => {
|
||||
{
|
||||
let mut workspace_rules = WORKSPACE_RULES.lock();
|
||||
workspace_rules.insert(id, (monitor_idx, workspace_idx));
|
||||
}
|
||||
|
||||
self.enforce_workspace_rules()?;
|
||||
}
|
||||
SocketMessage::ManageRule(_, id) => {
|
||||
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
if !manage_identifiers.contains(&id) {
|
||||
manage_identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::FloatExe(target) => {
|
||||
let mut float_exes = FLOAT_EXES.lock().unwrap();
|
||||
if !float_exes.contains(&target) {
|
||||
float_exes.push(target);
|
||||
}
|
||||
}
|
||||
SocketMessage::FloatTitle(target) => {
|
||||
let mut float_titles = FLOAT_TITLES.lock().unwrap();
|
||||
if !float_titles.contains(&target) {
|
||||
float_titles.push(target);
|
||||
SocketMessage::FloatRule(_, id) => {
|
||||
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||
if !float_identifiers.contains(&id) {
|
||||
float_identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::AdjustContainerPadding(sizing, adjustment) => {
|
||||
@@ -102,6 +106,12 @@ impl WindowManager {
|
||||
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
|
||||
self.move_container_to_monitor(monitor_idx, true)?;
|
||||
}
|
||||
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
|
||||
self.move_container_to_workspace(workspace_idx, false)?;
|
||||
}
|
||||
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
|
||||
self.move_container_to_monitor(monitor_idx, false)?;
|
||||
}
|
||||
SocketMessage::TogglePause => {
|
||||
tracing::info!("pausing");
|
||||
self.is_paused = !self.is_paused;
|
||||
@@ -118,7 +128,7 @@ impl WindowManager {
|
||||
let work_area = *monitor.work_area_size();
|
||||
let workspace = monitor
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?;
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
// Reset any resize adjustments if we want to force a retile
|
||||
for resize in workspace.resize_dimensions_mut() {
|
||||
@@ -157,7 +167,8 @@ impl WindowManager {
|
||||
}
|
||||
SocketMessage::State => {
|
||||
let state = serde_json::to_string_pretty(&window_manager::State::from(self))?;
|
||||
let mut socket = dirs::home_dir().context("there is no home directory")?;
|
||||
let mut socket =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
@@ -174,6 +185,13 @@ impl WindowManager {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
}
|
||||
}
|
||||
SocketMessage::ToggleFocusFollowsMouse => {
|
||||
if WindowsApi::focus_follows_mouse()? {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
} else {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
}
|
||||
}
|
||||
SocketMessage::ReloadConfiguration => {
|
||||
Self::reload_configuration();
|
||||
}
|
||||
@@ -182,19 +200,25 @@ impl WindowManager {
|
||||
}
|
||||
SocketMessage::IdentifyTrayApplication(identifier, id) => match identifier {
|
||||
ApplicationIdentifier::Exe => {
|
||||
let mut exes = TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap();
|
||||
let mut exes = TRAY_AND_MULTI_WINDOW_EXES.lock();
|
||||
if !exes.contains(&id) {
|
||||
exes.push(id);
|
||||
}
|
||||
}
|
||||
ApplicationIdentifier::Class => {
|
||||
let mut classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap();
|
||||
let mut classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock();
|
||||
if !classes.contains(&id) {
|
||||
classes.push(id);
|
||||
}
|
||||
}
|
||||
ApplicationIdentifier::Title => {}
|
||||
},
|
||||
SocketMessage::ManageFocusedWindow => {
|
||||
self.manage_focused_window()?;
|
||||
}
|
||||
SocketMessage::UnmanageFocusedWindow => {
|
||||
self.unmanage_focused_window()?;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("processed");
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::select;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
@@ -20,7 +20,7 @@ use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||
let receiver = wm.lock().unwrap().incoming_events.lock().unwrap().clone();
|
||||
let receiver = wm.lock().incoming_events.lock().clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
tracing::info!("listening");
|
||||
@@ -28,7 +28,7 @@ pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||
select! {
|
||||
recv(receiver) -> mut maybe_event => {
|
||||
if let Ok(event) = maybe_event.as_mut() {
|
||||
match wm.lock().unwrap().process_event(event) {
|
||||
match wm.lock().process_event(event) {
|
||||
Ok(()) => {},
|
||||
Err(error) => tracing::error!("{}", error)
|
||||
}
|
||||
@@ -48,6 +48,8 @@ impl WindowManager {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.validate_virtual_desktop_id();
|
||||
|
||||
// Make sure we have the most recently focused monitor from any event
|
||||
match event {
|
||||
WindowManagerEvent::FocusChange(_, window)
|
||||
@@ -55,7 +57,7 @@ impl WindowManager {
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||
let monitor_idx = self
|
||||
.monitor_idx_from_window(*window)
|
||||
.context("there is no monitor associated with this window, it may have already been destroyed")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
@@ -79,33 +81,46 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
self.enforce_workspace_rules()?;
|
||||
|
||||
if matches!(event, WindowManagerEvent::MouseCapture(..)) {
|
||||
tracing::trace!("only reaping orphans for mouse capture event");
|
||||
tracing::trace!(
|
||||
"only reaping orphans and enforcing workspace rules for mouse capture event"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match event {
|
||||
WindowManagerEvent::Minimize(_, window) | WindowManagerEvent::Destroy(_, window) => {
|
||||
WindowManagerEvent::Minimize(_, window)
|
||||
| WindowManagerEvent::Destroy(_, window)
|
||||
| WindowManagerEvent::Unmanage(window) => {
|
||||
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
|
||||
// minimized or destroyed. Applications that close to the tray also do the same,
|
||||
// and will have is_window() return true, as the process is still running even if
|
||||
// the window is not visible.
|
||||
let tray_and_multi_window_exes = TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap();
|
||||
let tray_and_multi_window_classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap();
|
||||
|
||||
// We don't want to purge windows that have been deliberately hidden by us, eg. when
|
||||
// they are not on the top of a container stack.
|
||||
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap();
|
||||
|
||||
if (!window.is_window() || tray_and_multi_window_exes.contains(&window.exe()?))
|
||||
|| tray_and_multi_window_classes.contains(&window.class()?)
|
||||
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
|
||||
{
|
||||
let tray_and_multi_window_exes = TRAY_AND_MULTI_WINDOW_EXES.lock();
|
||||
let tray_and_multi_window_classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock();
|
||||
|
||||
// We don't want to purge windows that have been deliberately hidden by us, eg. when
|
||||
// they are not on the top of a container stack.
|
||||
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
|
||||
if (!window.is_window() || tray_and_multi_window_exes.contains(&window.exe()?))
|
||||
|| tray_and_multi_window_classes.contains(&window.class()?)
|
||||
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
|
||||
{
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
|
||||
if hide {
|
||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
@@ -120,10 +135,16 @@ impl WindowManager {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(w) = workspace.maximized_window() {
|
||||
if w.hwnd == window.hwnd {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
self.focused_workspace_mut()?
|
||||
.focus_container_by_window(window.hwnd)?;
|
||||
}
|
||||
WindowManagerEvent::Show(_, window) => {
|
||||
WindowManagerEvent::Show(_, window) | WindowManagerEvent::Manage(window) => {
|
||||
let mut switch_to = None;
|
||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
||||
@@ -133,16 +154,16 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(indices) = switch_to {
|
||||
if self.focused_monitor_idx() != indices.0
|
||||
&& self
|
||||
if let Some((known_monitor_idx, known_workspace_idx)) = switch_to {
|
||||
if self.focused_monitor_idx() != known_monitor_idx
|
||||
|| self
|
||||
.focused_monitor()
|
||||
.context("there is no monitor")?
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx()
|
||||
!= indices.1
|
||||
!= known_workspace_idx
|
||||
{
|
||||
self.focus_monitor(indices.0)?;
|
||||
self.focus_workspace(indices.1)?;
|
||||
self.focus_monitor(known_monitor_idx)?;
|
||||
self.focus_workspace(known_workspace_idx)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -170,7 +191,7 @@ impl WindowManager {
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if workspace.containers().is_empty() || !workspace.contains_window(window.hwnd) {
|
||||
if !workspace.contains_window(window.hwnd) {
|
||||
workspace.new_container_for_window(*window);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
@@ -189,7 +210,7 @@ impl WindowManager {
|
||||
let old_position = *workspace
|
||||
.latest_layout()
|
||||
.get(focused_idx)
|
||||
.context("there is no latest layout")?;
|
||||
.ok_or_else(|| anyhow!("there is no latest layout"))?;
|
||||
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||
|
||||
// See Window.set_position() in window.rs for comments
|
||||
@@ -267,6 +288,11 @@ impl WindowManager {
|
||||
WindowManagerEvent::MouseCapture(..) => {}
|
||||
};
|
||||
|
||||
// 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()?)?;
|
||||
}
|
||||
|
||||
tracing::trace!("updating list of known hwnds");
|
||||
let mut known_hwnds = vec![];
|
||||
for monitor in self.monitors() {
|
||||
@@ -279,7 +305,8 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
let mut hwnd_json = dirs::home_dir().context("there is no home directory")?;
|
||||
let mut hwnd_json =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Serialize;
|
||||
@@ -15,11 +15,10 @@ use crate::styles::GwlExStyle;
|
||||
use crate::styles::GwlStyle;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::FLOAT_CLASSES;
|
||||
use crate::FLOAT_EXES;
|
||||
use crate::FLOAT_TITLES;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Window {
|
||||
@@ -71,6 +70,21 @@ impl Window {
|
||||
HWND(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
|
||||
let half_width = work_area.right / 2;
|
||||
let half_weight = work_area.bottom / 2;
|
||||
|
||||
self.set_position(
|
||||
&Rect {
|
||||
left: work_area.left + ((work_area.right - half_width) / 2),
|
||||
top: work_area.top + ((work_area.bottom - half_weight) / 2),
|
||||
right: half_width,
|
||||
bottom: half_weight,
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
|
||||
// NOTE: This is how the border variable below was calculated; every time this code was
|
||||
// run on any window in any position, the generated border was always the same, so I am
|
||||
@@ -110,7 +124,7 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn hide(self) {
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap();
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
|
||||
programmatically_hidden_hwnds.push(self.hwnd);
|
||||
}
|
||||
@@ -119,7 +133,7 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn restore(self) {
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap();
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if let Some(idx) = programmatically_hidden_hwnds
|
||||
.iter()
|
||||
.position(|&hwnd| hwnd == self.hwnd)
|
||||
@@ -130,6 +144,18 @@ impl Window {
|
||||
WindowsApi::restore_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn maximize(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);
|
||||
}
|
||||
|
||||
WindowsApi::maximize_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn focus(self) -> Result<()> {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
@@ -161,12 +187,12 @@ impl Window {
|
||||
|
||||
pub fn style(self) -> Result<GwlStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
|
||||
GwlStyle::from_bits(bits).context("there is no gwl style")
|
||||
GwlStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
|
||||
}
|
||||
|
||||
pub fn ex_style(self) -> Result<GwlExStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
|
||||
GwlExStyle::from_bits(bits).context("there is no gwl style")
|
||||
GwlExStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
|
||||
}
|
||||
|
||||
pub fn title(self) -> Result<String> {
|
||||
@@ -192,10 +218,6 @@ impl Window {
|
||||
|
||||
#[tracing::instrument(fields(exe, title))]
|
||||
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
|
||||
let classes = FLOAT_CLASSES.lock().unwrap();
|
||||
let exes = FLOAT_EXES.lock().unwrap();
|
||||
let titles = FLOAT_TITLES.lock().unwrap();
|
||||
|
||||
if self.title().is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -212,22 +234,25 @@ impl Window {
|
||||
(true, _) |
|
||||
// If not allowing cloaked windows, we need to ensure the window is not cloaked
|
||||
(false, false) => {
|
||||
if let (Ok(title), Ok(exe_name)) = (self.title(), self.exe()) {
|
||||
if titles.contains(&title) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if exes.contains(&exe_name) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Ok(class) = self.class() {
|
||||
if classes.contains(&class) {
|
||||
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 allow_layered = LAYERED_EXE_WHITELIST.lock().unwrap().contains(&exe_name);
|
||||
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()?;
|
||||
@@ -240,20 +265,17 @@ impl Window {
|
||||
// allowing a specific layered window on the whitelist (like Steam), it should
|
||||
// pass this check
|
||||
&& (allow_layered || !ex_style.contains(GwlExStyle::LAYERED))
|
||||
|| managed_override
|
||||
{
|
||||
Ok(true)
|
||||
} else {
|
||||
if event.is_some() {
|
||||
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
return Ok(true);
|
||||
} else if event.is_some() {
|
||||
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
_ => Ok(false),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,21 @@ use std::io::ErrorKind;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use hotwatch::notify::DebouncedEvent;
|
||||
use hotwatch::Hotwatch;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixListener;
|
||||
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::Flip;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::LayoutFlip;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
@@ -28,13 +29,14 @@ use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::FLOAT_CLASSES;
|
||||
use crate::FLOAT_EXES;
|
||||
use crate::FLOAT_TITLES;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||
use crate::WORKSPACE_RULES;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowManager {
|
||||
@@ -43,15 +45,15 @@ pub struct WindowManager {
|
||||
pub command_listener: UnixListener,
|
||||
pub is_paused: bool,
|
||||
pub hotwatch: Hotwatch,
|
||||
pub virtual_desktop_id: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct State {
|
||||
pub monitors: Ring<Monitor>,
|
||||
pub is_paused: bool,
|
||||
pub float_classes: Vec<String>,
|
||||
pub float_exes: Vec<String>,
|
||||
pub float_titles: Vec<String>,
|
||||
pub float_identifiers: Vec<String>,
|
||||
pub manage_identifiers: Vec<String>,
|
||||
pub layered_exe_whitelist: Vec<String>,
|
||||
pub tray_and_multi_window_exes: Vec<String>,
|
||||
pub tray_and_multi_window_classes: Vec<String>,
|
||||
@@ -63,48 +65,74 @@ impl From<&mut WindowManager> for State {
|
||||
Self {
|
||||
monitors: wm.monitors.clone(),
|
||||
is_paused: wm.is_paused,
|
||||
float_classes: FLOAT_CLASSES.lock().unwrap().clone(),
|
||||
float_exes: FLOAT_EXES.lock().unwrap().clone(),
|
||||
float_titles: FLOAT_TITLES.lock().unwrap().clone(),
|
||||
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().unwrap().clone(),
|
||||
tray_and_multi_window_exes: TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap().clone(),
|
||||
tray_and_multi_window_classes: TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap().clone(),
|
||||
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
|
||||
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
|
||||
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(),
|
||||
tray_and_multi_window_exes: TRAY_AND_MULTI_WINDOW_EXES.lock().clone(),
|
||||
tray_and_multi_window_classes: TRAY_AND_MULTI_WINDOW_CLASSES.lock().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_ring_elements!(WindowManager, Monitor);
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<WindowManager> {
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let mut socket = home;
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct EnforceWorkspaceRuleOp {
|
||||
hwnd: isize,
|
||||
origin_monitor_idx: usize,
|
||||
origin_workspace_idx: usize,
|
||||
target_monitor_idx: usize,
|
||||
target_workspace_idx: usize,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
},
|
||||
};
|
||||
impl EnforceWorkspaceRuleOp {
|
||||
const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
|
||||
self.origin_monitor_idx == monitor_idx && self.origin_workspace_idx == workspace_idx
|
||||
}
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
const fn is_target(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
|
||||
self.target_monitor_idx == monitor_idx && self.target_workspace_idx == workspace_idx
|
||||
}
|
||||
|
||||
Ok(WindowManager {
|
||||
monitors: Ring::default(),
|
||||
incoming_events: incoming,
|
||||
command_listener: listener,
|
||||
is_paused: false,
|
||||
hotwatch: Hotwatch::new()?,
|
||||
})
|
||||
const fn is_enforced(&self) -> bool {
|
||||
(self.origin_monitor_idx == self.target_monitor_idx)
|
||||
&& (self.origin_workspace_idx == self.target_workspace_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
#[tracing::instrument]
|
||||
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let mut socket = home;
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
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 virtual_desktop_id = winvd::helpers::get_current_desktop_number().ok();
|
||||
|
||||
Ok(Self {
|
||||
monitors: Ring::default(),
|
||||
incoming_events: incoming,
|
||||
command_listener: listener,
|
||||
is_paused: false,
|
||||
hotwatch: Hotwatch::new()?,
|
||||
virtual_desktop_id,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
tracing::info!("initialising");
|
||||
@@ -121,7 +149,7 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn watch_configuration(&mut self, enable: bool) -> Result<()> {
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
|
||||
let mut config_v1 = home.clone();
|
||||
config_v1.push("komorebi.ahk");
|
||||
@@ -146,7 +174,7 @@ impl WindowManager {
|
||||
config
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.context("cannot convert path to string")?
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
// Always make absolutely sure that there isn't an already existing watch, because
|
||||
// hotwatch allows multiple watches to be registered for the same path
|
||||
@@ -177,7 +205,7 @@ impl WindowManager {
|
||||
config
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.context("cannot convert path to string")?
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
self.hotwatch.unwatch(config)?;
|
||||
@@ -187,16 +215,167 @@ impl WindowManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn enforce_workspace_rules(&mut self) -> Result<()> {
|
||||
let mut to_move = vec![];
|
||||
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
let focused_workspace_idx = self
|
||||
.monitors()
|
||||
.get(focused_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
|
||||
.focused_workspace_idx();
|
||||
|
||||
let workspace_rules = WORKSPACE_RULES.lock();
|
||||
// Go through all the monitors and workspaces
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
||||
// And all the visible windows (at the top of a container)
|
||||
for window in workspace.visible_windows().into_iter().flatten() {
|
||||
// If the executable names or titles of any of those windows are in our rules map
|
||||
if let Some((monitor_idx, workspace_idx)) = workspace_rules.get(&window.exe()?)
|
||||
{
|
||||
tracing::info!(
|
||||
"{} should be on monitor {}, workspace {}",
|
||||
window.title()?,
|
||||
*monitor_idx,
|
||||
*workspace_idx
|
||||
);
|
||||
|
||||
// Create an operation outline and save it for later in the fn
|
||||
to_move.push(EnforceWorkspaceRuleOp {
|
||||
hwnd: window.hwnd,
|
||||
origin_monitor_idx: i,
|
||||
origin_workspace_idx: j,
|
||||
target_monitor_idx: *monitor_idx,
|
||||
target_workspace_idx: *workspace_idx,
|
||||
});
|
||||
} else if let Some((monitor_idx, workspace_idx)) =
|
||||
workspace_rules.get(&window.title()?)
|
||||
{
|
||||
tracing::info!(
|
||||
"{} should be on monitor {}, workspace {}",
|
||||
window.title()?,
|
||||
*monitor_idx,
|
||||
*workspace_idx
|
||||
);
|
||||
|
||||
to_move.push(EnforceWorkspaceRuleOp {
|
||||
hwnd: window.hwnd,
|
||||
origin_monitor_idx: i,
|
||||
origin_workspace_idx: j,
|
||||
target_monitor_idx: *monitor_idx,
|
||||
target_workspace_idx: *workspace_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only retain operations where the target is not the current workspace
|
||||
to_move.retain(|op| !op.is_target(focused_monitor_idx, focused_workspace_idx));
|
||||
// Only retain operations where the rule has not already been enforced
|
||||
to_move.retain(|op| !op.is_enforced());
|
||||
|
||||
let mut should_update_focused_workspace = false;
|
||||
|
||||
// Parse the operation and remove any windows that are not placed according to their rules
|
||||
for op in &to_move {
|
||||
let origin_workspace = self
|
||||
.monitors_mut()
|
||||
.get_mut(op.origin_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(op.origin_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
|
||||
|
||||
// Hide the window we are about to remove if it is on the currently focused workspace
|
||||
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
|
||||
Window { hwnd: op.hwnd }.hide();
|
||||
should_update_focused_workspace = true;
|
||||
}
|
||||
|
||||
origin_workspace.remove_window(op.hwnd)?;
|
||||
}
|
||||
|
||||
// Parse the operation again and associate those removed windows with the workspace that
|
||||
// their rules have defined for them
|
||||
for op in &to_move {
|
||||
let target_monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(op.target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor with that index"))?;
|
||||
|
||||
// The very first time this fn is called, the workspace might not even exist yet
|
||||
if target_monitor
|
||||
.workspaces()
|
||||
.get(op.target_workspace_idx)
|
||||
.is_none()
|
||||
{
|
||||
// If it doesn't, let's make sure it does for the next step
|
||||
target_monitor.ensure_workspace_count(op.target_workspace_idx + 1);
|
||||
}
|
||||
|
||||
let target_workspace = target_monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(op.target_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
|
||||
|
||||
target_workspace.new_container_for_window(Window { hwnd: op.hwnd });
|
||||
}
|
||||
|
||||
// Only re-tile the focused workspace if we need to
|
||||
if should_update_focused_workspace {
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn validate_virtual_desktop_id(&self) {
|
||||
let virtual_desktop_id = winvd::helpers::get_current_desktop_number().ok();
|
||||
if let (Some(id), Some(virtual_desktop_id)) = (virtual_desktop_id, self.virtual_desktop_id)
|
||||
{
|
||||
if id != virtual_desktop_id {
|
||||
tracing::warn!(
|
||||
"ignoring events while not on virtual desktop {}",
|
||||
virtual_desktop_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn manage_focused_window(&mut self) -> Result<()> {
|
||||
let hwnd = WindowsApi::foreground_window()?;
|
||||
let event = WindowManagerEvent::Manage(Window { hwnd });
|
||||
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn unmanage_focused_window(&mut self) -> Result<()> {
|
||||
let hwnd = WindowsApi::foreground_window()?;
|
||||
let event = WindowManagerEvent::Unmanage(Window { hwnd });
|
||||
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||
tracing::info!("updating");
|
||||
|
||||
self.focused_monitor_mut()
|
||||
.context("there is no monitor")?
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.update_focused_workspace()?;
|
||||
|
||||
if mouse_follows_focus {
|
||||
if let Ok(window) = self.focused_window_mut() {
|
||||
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
||||
window.focus()?;
|
||||
} else if let Some(container) = self.focused_workspace()?.monocle_container() {
|
||||
if let Some(window) = container.focused_window() {
|
||||
window.focus()?;
|
||||
}
|
||||
} else if let Ok(window) = self.focused_window_mut() {
|
||||
window.focus()?;
|
||||
} else {
|
||||
let desktop_window = Window {
|
||||
@@ -207,7 +386,7 @@ impl WindowManager {
|
||||
// attach to the thread of the desktop window always seems to result in "Access is
|
||||
// denied (os error 5)"
|
||||
WindowsApi::set_foreground_window(desktop_window.hwnd())
|
||||
.map_err(|error| eyre::anyhow!("{} {}:{}", error, file!(), line!()))?;
|
||||
.map_err(|error| anyhow!("{} {}:{}", error, file!(), line!()))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +409,7 @@ impl WindowManager {
|
||||
let focused_idx_resize = workspace
|
||||
.resize_dimensions()
|
||||
.get(focused_idx)
|
||||
.context("there is no resize adjustment for this container")?;
|
||||
.ok_or_else(|| anyhow!("there is no resize adjustment for this container"))?;
|
||||
|
||||
if direction.is_valid(
|
||||
workspace.layout(),
|
||||
@@ -254,28 +433,28 @@ impl WindowManager {
|
||||
// can flip them however they need to be flipped once the resizing has been done
|
||||
if let Some(flip) = workspace.layout_flip() {
|
||||
match flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
Flip::Horizontal => {
|
||||
if matches!(direction, OperationDirection::Left)
|
||||
|| matches!(direction, OperationDirection::Right)
|
||||
{
|
||||
direction = direction.opposite();
|
||||
}
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
Flip::Vertical => {
|
||||
if matches!(direction, OperationDirection::Up)
|
||||
|| matches!(direction, OperationDirection::Down)
|
||||
{
|
||||
direction = direction.opposite();
|
||||
}
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => direction = direction.opposite(),
|
||||
Flip::HorizontalAndVertical => direction = direction.opposite(),
|
||||
}
|
||||
}
|
||||
|
||||
let resize = workspace.layout().resize(
|
||||
unaltered
|
||||
.get(focused_idx)
|
||||
.context("there is no last layout")?,
|
||||
.ok_or_else(|| anyhow!("there is no last layout"))?,
|
||||
focused_idx_resize,
|
||||
direction,
|
||||
sizing,
|
||||
@@ -309,17 +488,27 @@ impl WindowManager {
|
||||
pub fn move_container_to_monitor(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
tracing::info!("moving container");
|
||||
|
||||
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
|
||||
let container = monitor
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
let workspace = monitor
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
if workspace.maximized_window().is_some() {
|
||||
return Err(anyhow!(
|
||||
"cannot move native maximized window to another monitor or workspace"
|
||||
));
|
||||
}
|
||||
|
||||
let container = workspace
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let target_monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
target_monitor.add_container(container)?;
|
||||
target_monitor.load_focused_workspace()?;
|
||||
@@ -335,9 +524,13 @@ impl WindowManager {
|
||||
pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
tracing::info!("moving container");
|
||||
|
||||
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
monitor.move_container_to_workspace(idx, follow)?;
|
||||
monitor.load_focused_workspace()?;
|
||||
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
@@ -348,7 +541,7 @@ impl WindowManager {
|
||||
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.context("this is not a valid direction from the current position")?;
|
||||
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
||||
|
||||
workspace.focus_container(new_idx);
|
||||
self.focused_window_mut()?.focus()?;
|
||||
@@ -365,7 +558,7 @@ impl WindowManager {
|
||||
let current_idx = workspace.focused_container_idx();
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.context("this is not a valid direction from the current position")?;
|
||||
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
||||
|
||||
workspace.swap_containers(current_idx, new_idx);
|
||||
workspace.focus_container(new_idx);
|
||||
@@ -379,7 +572,7 @@ impl WindowManager {
|
||||
let container = self.focused_container_mut()?;
|
||||
|
||||
if container.windows().len() == 1 {
|
||||
return Err(eyre::anyhow!("there is only one window in this container"));
|
||||
return Err(anyhow!("there is only one window in this container"));
|
||||
}
|
||||
|
||||
let current_idx = container.focused_window_idx();
|
||||
@@ -406,9 +599,9 @@ impl WindowManager {
|
||||
);
|
||||
|
||||
if is_valid {
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.context("this is not a valid direction from the current position")?;
|
||||
let new_idx = workspace.new_idx_for_direction(direction).ok_or_else(|| {
|
||||
anyhow!("this is not a valid direction from the current position")
|
||||
})?;
|
||||
|
||||
let adjusted_new_index = if new_idx > current_container_idx {
|
||||
new_idx - 1
|
||||
@@ -437,7 +630,7 @@ impl WindowManager {
|
||||
tracing::info!("removing window");
|
||||
|
||||
if self.focused_container()?.windows().len() == 1 {
|
||||
return Err(eyre::anyhow!("a container must have at least one window"));
|
||||
return Err(anyhow!("a container must have at least one window"));
|
||||
}
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -455,7 +648,7 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn toggle_float(&mut self) -> Result<()> {
|
||||
let hwnd = WindowsApi::top_visible_window()?;
|
||||
let hwnd = WindowsApi::foreground_window()?;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let mut is_floating_window = false;
|
||||
@@ -468,11 +661,11 @@ impl WindowManager {
|
||||
|
||||
if is_floating_window {
|
||||
self.unfloat_window()?;
|
||||
self.update_focused_workspace(true)
|
||||
} else {
|
||||
self.float_window()?;
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
self.update_focused_workspace(is_floating_window)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -487,19 +680,9 @@ impl WindowManager {
|
||||
let window = workspace
|
||||
.floating_windows_mut()
|
||||
.last_mut()
|
||||
.context("there is no floating window")?;
|
||||
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
||||
|
||||
let half_width = work_area.right / 2;
|
||||
let half_weight = work_area.bottom / 2;
|
||||
|
||||
let center = Rect {
|
||||
left: work_area.left + ((work_area.right - half_width) / 2),
|
||||
top: work_area.top + ((work_area.bottom - half_weight) / 2),
|
||||
right: half_width,
|
||||
bottom: half_weight,
|
||||
};
|
||||
|
||||
window.set_position(¢er, true)?;
|
||||
window.center(&work_area)?;
|
||||
window.focus()?;
|
||||
|
||||
Ok(())
|
||||
@@ -542,7 +725,35 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn flip_layout(&mut self, layout_flip: LayoutFlip) -> Result<()> {
|
||||
pub fn toggle_maximize(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
match workspace.maximized_window() {
|
||||
None => self.maximize_window()?,
|
||||
Some(_) => self.unmaximize_window()?,
|
||||
}
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn maximize_window(&mut self) -> Result<()> {
|
||||
tracing::info!("maximizing windowj");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.new_maximized_window()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn unmaximize_window(&mut self) -> Result<()> {
|
||||
tracing::info!("unmaximizing window");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.reintegrate_maximized_window()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn flip_layout(&mut self, layout_flip: Flip) -> Result<()> {
|
||||
tracing::info!("flipping layout");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -554,28 +765,28 @@ impl WindowManager {
|
||||
}
|
||||
Some(current_layout_flip) => {
|
||||
match current_layout_flip {
|
||||
LayoutFlip::Horizontal => match layout_flip {
|
||||
LayoutFlip::Horizontal => workspace.set_layout_flip(None),
|
||||
LayoutFlip::Vertical => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
LayoutFlip::HorizontalAndVertical => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
},
|
||||
LayoutFlip::Vertical => match layout_flip {
|
||||
LayoutFlip::Horizontal => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
LayoutFlip::Vertical => workspace.set_layout_flip(None),
|
||||
LayoutFlip::HorizontalAndVertical => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
},
|
||||
LayoutFlip::HorizontalAndVertical => match layout_flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
workspace.set_layout_flip(Option::from(LayoutFlip::Vertical))
|
||||
Flip::Horizontal => match layout_flip {
|
||||
Flip::Horizontal => workspace.set_layout_flip(None),
|
||||
Flip::Vertical => {
|
||||
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
workspace.set_layout_flip(Option::from(LayoutFlip::Horizontal))
|
||||
Flip::HorizontalAndVertical => {
|
||||
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => workspace.set_layout_flip(None),
|
||||
},
|
||||
Flip::Vertical => match layout_flip {
|
||||
Flip::Horizontal => {
|
||||
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
||||
}
|
||||
Flip::Vertical => workspace.set_layout_flip(None),
|
||||
Flip::HorizontalAndVertical => {
|
||||
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
||||
}
|
||||
},
|
||||
Flip::HorizontalAndVertical => match layout_flip {
|
||||
Flip::Horizontal => workspace.set_layout_flip(Option::from(Flip::Vertical)),
|
||||
Flip::Vertical => workspace.set_layout_flip(Option::from(Flip::Horizontal)),
|
||||
Flip::HorizontalAndVertical => workspace.set_layout_flip(None),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -601,7 +812,7 @@ impl WindowManager {
|
||||
|
||||
let padding = workspace
|
||||
.workspace_padding()
|
||||
.context("there is no workspace padding")?;
|
||||
.ok_or_else(|| anyhow!("there is no workspace padding"))?;
|
||||
|
||||
workspace.set_workspace_padding(Option::from(sizing.adjust_by(padding, adjustment)));
|
||||
|
||||
@@ -616,7 +827,7 @@ impl WindowManager {
|
||||
|
||||
let padding = workspace
|
||||
.container_padding()
|
||||
.context("there is no container padding")?;
|
||||
.ok_or_else(|| anyhow!("there is no container padding"))?;
|
||||
|
||||
workspace.set_container_padding(Option::from(sizing.adjust_by(padding, adjustment)));
|
||||
|
||||
@@ -633,12 +844,12 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
workspace.set_tile(tile);
|
||||
|
||||
@@ -659,7 +870,7 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let work_area = *monitor.work_area_size();
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
@@ -667,7 +878,7 @@ impl WindowManager {
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
workspace.set_layout(layout);
|
||||
|
||||
@@ -691,7 +902,7 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
monitor.ensure_workspace_count(workspace_count);
|
||||
|
||||
@@ -710,12 +921,12 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
workspace.set_workspace_padding(Option::from(size));
|
||||
|
||||
@@ -734,12 +945,12 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
workspace.set_name(Option::from(name.clone()));
|
||||
monitor.workspace_names_mut().insert(workspace_idx, name);
|
||||
@@ -759,12 +970,12 @@ impl WindowManager {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
workspace.set_container_padding(Option::from(size));
|
||||
|
||||
@@ -774,7 +985,7 @@ impl WindowManager {
|
||||
pub fn focused_monitor_work_area(&self) -> Result<Rect> {
|
||||
Ok(*self
|
||||
.focused_monitor()
|
||||
.context("there is no monitor")?
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.work_area_size())
|
||||
}
|
||||
|
||||
@@ -785,7 +996,7 @@ impl WindowManager {
|
||||
if self.monitors().get(idx).is_some() {
|
||||
self.monitors.focus(idx);
|
||||
} else {
|
||||
return Err(eyre::anyhow!("this is not a valid monitor index"));
|
||||
return Err(anyhow!("this is not a valid monitor index"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -805,16 +1016,16 @@ impl WindowManager {
|
||||
|
||||
pub fn focused_workspace(&self) -> Result<&Workspace> {
|
||||
self.focused_monitor()
|
||||
.context("there is no monitor")?
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace()
|
||||
.context("there is no workspace")
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))
|
||||
}
|
||||
|
||||
pub fn focused_workspace_mut(&mut self) -> Result<&mut Workspace> {
|
||||
self.focused_monitor_mut()
|
||||
.context("there is no monitor")?
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -823,7 +1034,7 @@ impl WindowManager {
|
||||
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.context("there is no workspace")?;
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
monitor.focus_workspace(idx)?;
|
||||
monitor.load_focused_workspace()?;
|
||||
@@ -837,7 +1048,7 @@ impl WindowManager {
|
||||
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.context("there is no workspace")?;
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
monitor.focus_workspace(monitor.new_workspace_idx())?;
|
||||
monitor.load_focused_workspace()?;
|
||||
@@ -848,18 +1059,18 @@ impl WindowManager {
|
||||
pub fn focused_container(&self) -> Result<&Container> {
|
||||
self.focused_workspace()?
|
||||
.focused_container()
|
||||
.context("there is no container")
|
||||
.ok_or_else(|| anyhow!("there is no container"))
|
||||
}
|
||||
|
||||
pub fn focused_container_mut(&mut self) -> Result<&mut Container> {
|
||||
self.focused_workspace_mut()?
|
||||
.focused_container_mut()
|
||||
.context("there is no container")
|
||||
.ok_or_else(|| anyhow!("there is no container"))
|
||||
}
|
||||
|
||||
fn focused_window_mut(&mut self) -> Result<&mut Window> {
|
||||
self.focused_container_mut()?
|
||||
.focused_window_mut()
|
||||
.context("there is no window")
|
||||
.ok_or_else(|| anyhow!("there is no window"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::fmt::Formatter;
|
||||
|
||||
use crate::window::Window;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum WindowManagerEvent {
|
||||
@@ -13,11 +14,19 @@ pub enum WindowManagerEvent {
|
||||
Show(WinEvent, Window),
|
||||
MoveResizeEnd(WinEvent, Window),
|
||||
MouseCapture(WinEvent, Window),
|
||||
Manage(Window),
|
||||
Unmanage(Window),
|
||||
}
|
||||
|
||||
impl Display for WindowManagerEvent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WindowManagerEvent::Manage(window) => {
|
||||
write!(f, "Manage (Window: {})", window)
|
||||
}
|
||||
WindowManagerEvent::Unmanage(window) => {
|
||||
write!(f, "Unmanage (Window: {})", window)
|
||||
}
|
||||
WindowManagerEvent::Destroy(winevent, window) => {
|
||||
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
@@ -64,28 +73,48 @@ impl WindowManagerEvent {
|
||||
| WindowManagerEvent::Minimize(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window)
|
||||
| WindowManagerEvent::MouseCapture(_, window) => window,
|
||||
| WindowManagerEvent::MouseCapture(_, window)
|
||||
| WindowManagerEvent::Manage(window)
|
||||
| WindowManagerEvent::Unmanage(window) => window,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
||||
pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
||||
match winevent {
|
||||
WinEvent::ObjectDestroy => Some(Self::Destroy(winevent, window)),
|
||||
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),
|
||||
|
||||
WinEvent::ObjectCloaked | WinEvent::ObjectHide => Some(Self::Hide(winevent, window)),
|
||||
WinEvent::ObjectCloaked | WinEvent::ObjectHide => {
|
||||
Option::from(Self::Hide(winevent, window))
|
||||
}
|
||||
|
||||
WinEvent::SystemMinimizeStart => Some(Self::Minimize(winevent, window)),
|
||||
WinEvent::SystemMinimizeStart => Option::from(Self::Minimize(winevent, window)),
|
||||
|
||||
WinEvent::ObjectShow | WinEvent::ObjectUncloaked | WinEvent::SystemMinimizeEnd => {
|
||||
Some(Self::Show(winevent, window))
|
||||
Option::from(Self::Show(winevent, window))
|
||||
}
|
||||
|
||||
WinEvent::ObjectFocus | WinEvent::SystemForeground => {
|
||||
Some(Self::FocusChange(winevent, window))
|
||||
Option::from(Self::FocusChange(winevent, window))
|
||||
}
|
||||
WinEvent::SystemMoveSizeEnd => Some(Self::MoveResizeEnd(winevent, window)),
|
||||
WinEvent::SystemMoveSizeEnd => Option::from(Self::MoveResizeEnd(winevent, window)),
|
||||
WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => {
|
||||
Some(Self::MouseCapture(winevent, window))
|
||||
Option::from(Self::MouseCapture(winevent, window))
|
||||
}
|
||||
WinEvent::ObjectNameChange => {
|
||||
// Some apps like Firefox don't send ObjectCreate or ObjectShow on launch
|
||||
// This spams the message queue, but I don't know what else to do. On launch
|
||||
// it only sends the following WinEvents :/
|
||||
//
|
||||
// [yatta\src\windows_event.rs:110] event = 32780 ObjectNameChange
|
||||
// [yatta\src\windows_event.rs:110] event = 32779 ObjectLocationChange
|
||||
|
||||
let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
||||
|
||||
if object_name_change_on_launch.contains(&window.exe().ok()?) {
|
||||
Option::from(Self::Show(winevent, window))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::c_void;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use eyre::Error;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::BOOL;
|
||||
use bindings::Windows::Win32::Foundation::HANDLE;
|
||||
@@ -42,6 +42,7 @@ 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;
|
||||
@@ -66,8 +67,10 @@ 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;
|
||||
@@ -259,6 +262,14 @@ impl WindowsApi {
|
||||
Self::show_window(hwnd, SW_RESTORE);
|
||||
}
|
||||
|
||||
pub fn maximize_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_MAXIMIZE);
|
||||
}
|
||||
|
||||
pub fn foreground_window() -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { GetForegroundWindow() }))
|
||||
}
|
||||
|
||||
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
|
||||
match WindowsResult::from(unsafe { SetForegroundWindow(hwnd) }) {
|
||||
WindowsResult::Ok(_) => Ok(()),
|
||||
@@ -275,6 +286,7 @@ impl WindowsApi {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn top_window() -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { GetTopWindow(HWND::NULL).0 }))
|
||||
}
|
||||
@@ -283,12 +295,14 @@ impl WindowsApi {
|
||||
Result::from(WindowsResult::from(unsafe { GetDesktopWindow() }))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn next_window(hwnd: HWND) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetWindow(hwnd, GW_HWNDNEXT).0
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn top_visible_window() -> Result<isize> {
|
||||
let hwnd = Self::top_window()?;
|
||||
let mut next_hwnd = hwnd;
|
||||
@@ -301,7 +315,7 @@ impl WindowsApi {
|
||||
next_hwnd = Self::next_window(HWND(next_hwnd))?;
|
||||
}
|
||||
|
||||
Err(eyre::anyhow!("could not find next window"))
|
||||
Err(anyhow!("could not find next window"))
|
||||
}
|
||||
|
||||
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
|
||||
@@ -450,7 +464,7 @@ impl WindowsApi {
|
||||
Ok(Self::exe_path(handle)?
|
||||
.split('\\')
|
||||
.last()
|
||||
.context("there is no last element")?
|
||||
.ok_or_else(|| anyhow!("there is no last element"))?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
@@ -544,6 +558,19 @@ impl WindowsApi {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn focus_follows_mouse() -> Result<bool> {
|
||||
let mut is_enabled: BOOL = unsafe { std::mem::zeroed() };
|
||||
|
||||
Self::system_parameters_info_w(
|
||||
SPI_GETACTIVEWINDOWTRACKING,
|
||||
0,
|
||||
(&mut is_enabled as *mut BOOL).cast(),
|
||||
SPIF_SENDCHANGE,
|
||||
)?;
|
||||
|
||||
Ok(is_enabled.into())
|
||||
}
|
||||
|
||||
pub fn enable_focus_follows_mouse() -> Result<()> {
|
||||
Self::system_parameters_info_w(
|
||||
SPI_SETACTIVEWINDOWTRACKING,
|
||||
|
||||
@@ -14,9 +14,7 @@ use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
|
||||
pub extern "system" fn enum_display_monitor(
|
||||
hmonitor: HMONITOR,
|
||||
@@ -71,37 +69,15 @@ pub extern "system" fn win_event_hook(
|
||||
let window = Window { hwnd: hwnd.0 };
|
||||
|
||||
let winevent = unsafe { ::std::mem::transmute(event) };
|
||||
let event_type = if let Some(event) = WindowManagerEvent::from_win_event(winevent, window) {
|
||||
event
|
||||
} else {
|
||||
// Some apps like Firefox don't send ObjectCreate or ObjectShow on launch
|
||||
// This spams the message queue, but I don't know what else to do. On launch
|
||||
// it only sends the following WinEvents :/
|
||||
//
|
||||
// [yatta\src\windows_event.rs:110] event = 32780 ObjectNameChange
|
||||
// [yatta\src\windows_event.rs:110] event = 32779 ObjectLocationChange
|
||||
let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock().unwrap();
|
||||
|
||||
if let Ok(exe) = window.exe() {
|
||||
if winevent == WinEvent::ObjectNameChange {
|
||||
if object_name_change_on_launch.contains(&exe) {
|
||||
WindowManagerEvent::Show(winevent, window)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
|
||||
None => return,
|
||||
Some(event) => event,
|
||||
};
|
||||
|
||||
if let Ok(should_manage) = window.should_manage(Option::from(event_type)) {
|
||||
if should_manage {
|
||||
WINEVENT_CALLBACK_CHANNEL
|
||||
.lock()
|
||||
.unwrap()
|
||||
.0
|
||||
.send(event_type)
|
||||
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
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;
|
||||
@@ -43,7 +43,7 @@ pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener
|
||||
impl WinEventListener {
|
||||
pub fn start(self) {
|
||||
let hook = self.hook.clone();
|
||||
let outgoing = self.outgoing_events.lock().unwrap().clone();
|
||||
let outgoing = self.outgoing_events.lock().clone();
|
||||
|
||||
thread::spawn(move || unsafe {
|
||||
let hook_ref = SetWinEventHook(
|
||||
@@ -61,7 +61,7 @@ impl WinEventListener {
|
||||
// The code in the callback doesn't work in its own loop, needs to be within
|
||||
// the MessageLoop callback for the winevent callback to even fire
|
||||
MessageLoop::start(10, |_msg| {
|
||||
if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().unwrap().1.try_recv() {
|
||||
if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().1.try_recv() {
|
||||
match outgoing.send(event) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
@@ -9,8 +10,8 @@ use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Flip;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::LayoutFlip;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
@@ -28,13 +29,18 @@ pub struct Workspace {
|
||||
monocle_container: Option<Container>,
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
monocle_restore_idx: Option<usize>,
|
||||
monocle_container_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
maximized_window: Option<Window>,
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
floating_windows: Vec<Window>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
layout: Layout,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
layout_flip: Option<Flip>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
workspace_padding: Option<i32>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
@@ -57,7 +63,9 @@ impl Default for Workspace {
|
||||
name: None,
|
||||
containers: Ring::default(),
|
||||
monocle_container: None,
|
||||
monocle_restore_idx: None,
|
||||
maximized_window: None,
|
||||
maximized_window_restore_idx: None,
|
||||
monocle_container_restore_idx: None,
|
||||
floating_windows: Vec::default(),
|
||||
layout: Layout::BSP,
|
||||
layout_flip: None,
|
||||
@@ -77,6 +85,20 @@ impl Workspace {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.hide();
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
for window in container.windows_mut() {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(&mut self) -> Result<()> {
|
||||
@@ -87,14 +109,31 @@ impl Workspace {
|
||||
window.restore();
|
||||
|
||||
if idx == i {
|
||||
to_focus = Option::from(window);
|
||||
to_focus = Option::from(*window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
for window in container.windows_mut() {
|
||||
window.restore();
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
window.restore();
|
||||
}
|
||||
|
||||
// Do this here to make sure that an error doesn't stop the restoration of other windows
|
||||
// Maximised windows should always be drawn at the top of the Z order
|
||||
if let Some(window) = to_focus {
|
||||
window.focus()?;
|
||||
if self.maximized_window().is_none() {
|
||||
window.focus()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -110,7 +149,9 @@ impl Workspace {
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window) = container.focused_window_mut() {
|
||||
window.set_position(&adjusted_work_area, true)?;
|
||||
}
|
||||
};
|
||||
} else if let Some(window) = self.maximized_window_mut() {
|
||||
window.maximize();
|
||||
} else if !self.containers().is_empty() {
|
||||
let layouts = self.layout().calculate(
|
||||
&adjusted_work_area,
|
||||
@@ -189,16 +230,16 @@ impl Workspace {
|
||||
pub fn focus_container_by_window(&mut self, hwnd: isize) -> Result<()> {
|
||||
let container_idx = self
|
||||
.container_idx_for_window(hwnd)
|
||||
.context("there is no container/window")?;
|
||||
.ok_or_else(|| anyhow!("there is no container/window"))?;
|
||||
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.get_mut(container_idx)
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let window_idx = container
|
||||
.idx_for_window(hwnd)
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
container.focus_window(window_idx);
|
||||
self.focus_container(container_idx);
|
||||
@@ -223,8 +264,26 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for x in self.containers() {
|
||||
if x.contains_window(hwnd) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
if hwnd == window.hwnd {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -235,7 +294,7 @@ impl Workspace {
|
||||
pub fn promote_container(&mut self) -> Result<()> {
|
||||
let container = self
|
||||
.remove_focused_container()
|
||||
.context("there is no 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);
|
||||
@@ -245,7 +304,7 @@ impl Workspace {
|
||||
|
||||
pub fn add_container(&mut self, container: Container) {
|
||||
self.containers_mut().push_back(container);
|
||||
self.focus_container(self.containers().len() - 1);
|
||||
self.focus_last_container();
|
||||
}
|
||||
|
||||
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
|
||||
@@ -270,29 +329,56 @@ impl Workspace {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window_idx) = container
|
||||
.windows()
|
||||
.iter()
|
||||
.position(|window| window.hwnd == hwnd)
|
||||
{
|
||||
container
|
||||
.remove_window_by_idx(window_idx)
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.set_monocle_container(None);
|
||||
self.set_monocle_container_restore_idx(None);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
if window.hwnd == hwnd {
|
||||
self.set_maximized_window(None);
|
||||
self.set_maximized_window_restore_idx(None);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let container_idx = self
|
||||
.container_idx_for_window(hwnd)
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.get_mut(container_idx)
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let window_idx = container
|
||||
.windows()
|
||||
.iter()
|
||||
.position(|window| window.hwnd == hwnd)
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
container
|
||||
.remove_window_by_idx(window_idx)
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut()
|
||||
.remove(container_idx)
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
// Whenever a container is empty, we need to remove any resize dimensions for it too
|
||||
if self.resize_dimensions().get(container_idx).is_some() {
|
||||
@@ -300,9 +386,7 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
if container_idx != 0 {
|
||||
self.focus_container(container_idx - 1);
|
||||
}
|
||||
self.focus_previous_container();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -310,10 +394,7 @@ impl Workspace {
|
||||
pub fn remove_focused_container(&mut self) -> Option<Container> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let container = self.remove_container_by_idx(focused_idx);
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
}
|
||||
self.focus_previous_container();
|
||||
|
||||
container
|
||||
}
|
||||
@@ -340,11 +421,11 @@ impl Workspace {
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
// This is a little messy
|
||||
let adjusted_target_container_index = if container.windows().is_empty() {
|
||||
@@ -364,13 +445,13 @@ impl Workspace {
|
||||
let target_container = self
|
||||
.containers_mut()
|
||||
.get_mut(adjusted_target_container_index)
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
target_container.add_window(window);
|
||||
|
||||
self.focus_container(adjusted_target_container_index);
|
||||
self.focused_container_mut()
|
||||
.context("there is no container")?
|
||||
.ok_or_else(|| anyhow!("there is no container"))?
|
||||
.load_focused_window();
|
||||
|
||||
Ok(())
|
||||
@@ -381,11 +462,11 @@ impl Workspace {
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_container_idx);
|
||||
@@ -405,7 +486,7 @@ impl Workspace {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let window = self
|
||||
.remove_focused_floating_window()
|
||||
.context("there is no floating window")?;
|
||||
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
||||
|
||||
let mut container = Container::default();
|
||||
container.add_window(window);
|
||||
@@ -416,7 +497,11 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn new_container_for_window(&mut self, window: Window) {
|
||||
let next_idx = self.focused_container_idx() + 1;
|
||||
let next_idx = if self.containers().is_empty() {
|
||||
0
|
||||
} else {
|
||||
self.focused_container_idx() + 1
|
||||
};
|
||||
|
||||
let mut container = Container::default();
|
||||
container.add_window(window);
|
||||
@@ -441,11 +526,11 @@ impl Workspace {
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
@@ -490,22 +575,19 @@ impl Workspace {
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.remove(focused_idx)
|
||||
.context("there is not container")?;
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
// We don't remove any resize adjustments for a monocle, because when this container is
|
||||
// inevitably reintegrated, it would be weird if it doesn't go back to the dimensions
|
||||
// it had before
|
||||
|
||||
self.set_monocle_container(Option::from(container));
|
||||
self.set_monocle_restore_idx(Option::from(focused_idx));
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
}
|
||||
self.set_monocle_container_restore_idx(Option::from(focused_idx));
|
||||
self.focus_previous_container();
|
||||
|
||||
self.monocle_container_mut()
|
||||
.as_mut()
|
||||
.context("there is no monocle container")?
|
||||
.ok_or_else(|| anyhow!("there is no monocle container"))?
|
||||
.load_focused_window();
|
||||
|
||||
Ok(())
|
||||
@@ -513,13 +595,13 @@ impl Workspace {
|
||||
|
||||
pub fn reintegrate_monocle_container(&mut self) -> Result<()> {
|
||||
let restore_idx = self
|
||||
.monocle_restore_idx()
|
||||
.context("there is no monocle restore index")?;
|
||||
.monocle_container_restore_idx()
|
||||
.ok_or_else(|| anyhow!("there is no monocle restore index"))?;
|
||||
|
||||
let container = self
|
||||
.monocle_container_mut()
|
||||
.as_ref()
|
||||
.context("there is no monocle container")?;
|
||||
.ok_or_else(|| anyhow!("there is no monocle container"))?;
|
||||
|
||||
let container = container.clone();
|
||||
if restore_idx > self.containers().len() - 1 {
|
||||
@@ -530,10 +612,73 @@ impl Workspace {
|
||||
self.containers_mut().insert(restore_idx, container);
|
||||
self.focus_container(restore_idx);
|
||||
self.focused_container_mut()
|
||||
.context("there is no container")?
|
||||
.ok_or_else(|| anyhow!("there is no container"))?
|
||||
.load_focused_window();
|
||||
|
||||
self.set_monocle_container(None);
|
||||
self.set_monocle_container_restore_idx(None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_maximized_window(&mut self) -> Result<()> {
|
||||
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();
|
||||
}
|
||||
|
||||
self.set_maximized_window(Option::from(window));
|
||||
self.set_maximized_window_restore_idx(Option::from(focused_idx));
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
}
|
||||
|
||||
self.focus_previous_container();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reintegrate_maximized_window(&mut self) -> Result<()> {
|
||||
let restore_idx = self
|
||||
.maximized_window_restore_idx()
|
||||
.ok_or_else(|| anyhow!("there is no monocle restore index"))?;
|
||||
|
||||
let window = self
|
||||
.maximized_window()
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("there is no monocle container"))?;
|
||||
|
||||
let window = *window;
|
||||
if !self.containers().is_empty() && restore_idx > self.containers().len() - 1 {
|
||||
self.containers_mut()
|
||||
.resize(restore_idx, Container::default());
|
||||
}
|
||||
|
||||
let mut container = Container::default();
|
||||
container.windows_mut().push_back(window);
|
||||
self.containers_mut().insert(restore_idx, container);
|
||||
|
||||
self.focus_container(restore_idx);
|
||||
|
||||
self.focused_container_mut()
|
||||
.ok_or_else(|| anyhow!("there is no container"))?
|
||||
.load_focused_window();
|
||||
|
||||
self.set_maximized_window(None);
|
||||
self.set_maximized_window_restore_idx(None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -551,7 +696,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn remove_focused_floating_window(&mut self) -> Option<Window> {
|
||||
let hwnd = WindowsApi::top_visible_window().ok()?;
|
||||
let hwnd = WindowsApi::foreground_window().ok()?;
|
||||
|
||||
let mut idx = None;
|
||||
for (i, window) in self.floating_windows.iter().enumerate() {
|
||||
@@ -572,6 +717,15 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visible_windows(&self) -> Vec<Option<&Window>> {
|
||||
let mut vec = vec![];
|
||||
for container in self.containers() {
|
||||
vec.push(container.focused_window());
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
pub fn visible_windows_mut(&mut self) -> Vec<Option<&mut Window>> {
|
||||
let mut vec = vec![];
|
||||
for container in self.containers_mut() {
|
||||
@@ -580,4 +734,16 @@ impl Workspace {
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
fn focus_previous_container(&mut self) {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_last_container(&mut self) {
|
||||
self.focus_container(self.containers().len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
185
komorebic.lib.sample.ahk
Normal file
185
komorebic.lib.sample.ahk
Normal file
@@ -0,0 +1,185 @@
|
||||
; Generated by komorebic.exe
|
||||
|
||||
Start() {
|
||||
Run, komorebic.exe start, , Hide
|
||||
}
|
||||
|
||||
Stop() {
|
||||
Run, komorebic.exe stop, , Hide
|
||||
}
|
||||
|
||||
State() {
|
||||
Run, komorebic.exe state, , 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
FocusFollowsMouse(boolean_state) {
|
||||
Run, komorebic.exe focus-follows-mouse %boolean_state%, , Hide
|
||||
}
|
||||
|
||||
ToggleFocusFollowsMouse() {
|
||||
Run, komorebic.exe toggle-focus-follows-mouse, , Hide
|
||||
}
|
||||
|
||||
AhkLibrary() {
|
||||
Run, komorebic.exe ahk-library, , Hide
|
||||
}
|
||||
@@ -1,19 +1,27 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
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"
|
||||
|
||||
# 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"
|
||||
fs-tail = "0.1"
|
||||
heck = "0.3"
|
||||
paste = "1"
|
||||
powershell_script = "0.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
uds_windows = "1"
|
||||
uds_windows = "1"
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::stringify;
|
||||
|
||||
use clap::AppSettings;
|
||||
use clap::ArgEnum;
|
||||
use clap::Clap;
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use fs_tail::TailedFile;
|
||||
use heck::KebabCase;
|
||||
use paste::paste;
|
||||
use uds_windows::UnixListener;
|
||||
use uds_windows::UnixStream;
|
||||
@@ -19,14 +26,24 @@ use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||
use derive_ahk::AhkFunction;
|
||||
use derive_ahk::AhkLibrary;
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::Flip;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::LayoutFlip;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Sizing;
|
||||
use komorebi_core::SocketMessage;
|
||||
|
||||
trait AhkLibrary {
|
||||
fn generate_ahk_library() -> String;
|
||||
}
|
||||
|
||||
trait AhkFunction {
|
||||
fn generate_ahk_function() -> String;
|
||||
}
|
||||
|
||||
#[derive(ArgEnum)]
|
||||
enum BooleanState {
|
||||
Enable,
|
||||
@@ -47,7 +64,7 @@ macro_rules! gen_enum_subcommand_args {
|
||||
( $( $name:ident: $element:ty ),+ ) => {
|
||||
$(
|
||||
paste! {
|
||||
#[derive(clap::Clap)]
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
#[clap(arg_enum)]
|
||||
[<$element:snake>]: $element
|
||||
@@ -62,7 +79,8 @@ gen_enum_subcommand_args! {
|
||||
Move: OperationDirection,
|
||||
Stack: OperationDirection,
|
||||
CycleStack: CycleDirection,
|
||||
FlipLayout: LayoutFlip,
|
||||
FlipLayout: Flip,
|
||||
ChangeLayout: Layout,
|
||||
WatchConfiguration: BooleanState,
|
||||
FocusFollowsMouse: BooleanState
|
||||
}
|
||||
@@ -71,7 +89,7 @@ macro_rules! gen_target_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
$(
|
||||
#[derive(clap::Clap)]
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
/// Target index (zero-indexed)
|
||||
target: usize,
|
||||
@@ -83,6 +101,8 @@ macro_rules! gen_target_subcommand_args {
|
||||
gen_target_subcommand_args! {
|
||||
MoveToMonitor,
|
||||
MoveToWorkspace,
|
||||
SendToMonitor,
|
||||
SendToWorkspace,
|
||||
FocusMonitor,
|
||||
FocusWorkspace
|
||||
}
|
||||
@@ -95,7 +115,7 @@ macro_rules! gen_workspace_subcommand_args {
|
||||
( $( $name:ident: $(#[enum] $(@$arg_enum:tt)?)? $value:ty ),+ ) => (
|
||||
paste! {
|
||||
$(
|
||||
#[derive(clap::Clap)]
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct [<Workspace $name>] {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
@@ -121,7 +141,7 @@ gen_workspace_subcommand_args! {
|
||||
Tiling: #[enum] BooleanState
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct Resize {
|
||||
#[clap(arg_enum)]
|
||||
edge: OperationDirection,
|
||||
@@ -129,7 +149,7 @@ struct Resize {
|
||||
sizing: Sizing,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct EnsureWorkspaces {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
@@ -137,41 +157,89 @@ struct EnsureWorkspaces {
|
||||
workspace_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct Padding {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
/// Workspace index on the specified monitor (zero-indexed)
|
||||
workspace: usize,
|
||||
/// Pixels to pad with as an integer
|
||||
size: i32,
|
||||
macro_rules! gen_padding_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
/// Workspace index on the specified monitor (zero-indexed)
|
||||
workspace: usize,
|
||||
/// Pixels to pad with as an integer
|
||||
size: i32,
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct PaddingAdjustment {
|
||||
#[clap(arg_enum)]
|
||||
sizing: Sizing,
|
||||
/// Pixels to adjust by as an integer
|
||||
adjustment: i32,
|
||||
gen_padding_subcommand_args! {
|
||||
ContainerPadding,
|
||||
WorkspacePadding
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct ApplicationTarget {
|
||||
macro_rules! gen_padding_adjustment_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
#[clap(arg_enum)]
|
||||
sizing: Sizing,
|
||||
/// Pixels to adjust by as an integer
|
||||
adjustment: i32,
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
gen_padding_adjustment_subcommand_args! {
|
||||
AdjustContainerPadding,
|
||||
AdjustWorkspacePadding
|
||||
}
|
||||
|
||||
macro_rules! gen_application_target_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
#[clap(arg_enum)]
|
||||
identifier: ApplicationIdentifier,
|
||||
/// Identifier as a string
|
||||
id: String,
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
gen_application_target_subcommand_args! {
|
||||
FloatRule,
|
||||
ManageRule,
|
||||
IdentifyTrayApplication
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct WorkspaceRule {
|
||||
#[clap(arg_enum)]
|
||||
identifier: ApplicationIdentifier,
|
||||
/// Identifier as a string
|
||||
id: String,
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
/// Workspace index on the specified monitor (zero-indexed)
|
||||
workspace: usize,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[clap(version = "0.1.0", author = "Jade Iqbal <jadeiqbal@fastmail.com>")]
|
||||
#[clap(setting = AppSettings::DeriveDisplayOrder)]
|
||||
#[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)]
|
||||
struct Opts {
|
||||
#[clap(subcommand)]
|
||||
subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Clap, AhkLibrary)]
|
||||
enum SubCommand {
|
||||
/// Start komorebi.exe as a background process
|
||||
Start,
|
||||
@@ -179,6 +247,8 @@ enum SubCommand {
|
||||
Stop,
|
||||
/// Show a JSON representation of the current window manager state
|
||||
State,
|
||||
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||
Log,
|
||||
/// Change focus to the window in the specified direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
Focus(Focus),
|
||||
@@ -202,6 +272,12 @@ enum SubCommand {
|
||||
/// Move the focused window to the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
MoveToWorkspace(MoveToWorkspace),
|
||||
/// Send the focused window to the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
SendToMonitor(SendToMonitor),
|
||||
/// Send the focused window to the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
SendToWorkspace(SendToWorkspace),
|
||||
/// Focus the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
FocusMonitor(FocusMonitor),
|
||||
@@ -212,11 +288,15 @@ enum SubCommand {
|
||||
NewWorkspace,
|
||||
/// Adjust container padding on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
AdjustContainerPadding(PaddingAdjustment),
|
||||
AdjustContainerPadding(AdjustContainerPadding),
|
||||
/// Adjust workspace padding on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
AdjustWorkspacePadding(PaddingAdjustment),
|
||||
AdjustWorkspacePadding(AdjustWorkspacePadding),
|
||||
/// Set the layout on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
ChangeLayout(ChangeLayout),
|
||||
/// Flip the layout on the focused workspace (BSP only)
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
FlipLayout(FlipLayout),
|
||||
/// Promote the focused window to the top of the tree
|
||||
Promote,
|
||||
@@ -227,10 +307,10 @@ enum SubCommand {
|
||||
EnsureWorkspaces(EnsureWorkspaces),
|
||||
/// Set the container padding for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
ContainerPadding(Padding),
|
||||
ContainerPadding(ContainerPadding),
|
||||
/// Set the workspace padding for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
WorkspacePadding(Padding),
|
||||
WorkspacePadding(WorkspacePadding),
|
||||
/// Set the layout for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
WorkspaceLayout(WorkspaceLayout),
|
||||
@@ -248,21 +328,38 @@ enum SubCommand {
|
||||
ToggleFloat,
|
||||
/// Toggle monocle mode for the focused container
|
||||
ToggleMonocle,
|
||||
/// Toggle native maximization for the focused window
|
||||
ToggleMaximize,
|
||||
/// Restore all hidden windows (debugging command)
|
||||
RestoreWindows,
|
||||
/// Force komorebi to manage the focused window
|
||||
Manage,
|
||||
/// Unmanage a window that was forcibly managed
|
||||
Unmanage,
|
||||
/// Reload ~/komorebi.ahk (if it exists)
|
||||
ReloadConfiguration,
|
||||
/// Toggle the automatic reloading of ~/komorebi.ahk (if it exists)
|
||||
/// Enable or disable watching of ~/komorebi.ahk (if it exists)
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
WatchConfiguration(WatchConfiguration),
|
||||
/// Add a rule to always float the specified application
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
FloatRule(ApplicationTarget),
|
||||
FloatRule(FloatRule),
|
||||
/// Add a rule to always manage the specified application
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
ManageRule(ManageRule),
|
||||
/// Add a rule to associate an application with a workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
WorkspaceRule(WorkspaceRule),
|
||||
/// Identify an application that closes to the system tray
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
IdentifyTrayApplication(ApplicationTarget),
|
||||
IdentifyTrayApplication(IdentifyTrayApplication),
|
||||
/// Enable or disable focus follows mouse for the operating system
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
FocusFollowsMouse(FocusFollowsMouse),
|
||||
/// Toggle focus follows mouse for the operating system
|
||||
ToggleFocusFollowsMouse,
|
||||
/// Generate a library of AutoHotKey helper functions
|
||||
AhkLibrary,
|
||||
}
|
||||
|
||||
pub fn send_message(bytes: &[u8]) -> Result<()> {
|
||||
@@ -274,10 +371,44 @@ pub fn send_message(bytes: &[u8]) -> Result<()> {
|
||||
Ok(stream.write_all(&*bytes)?)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn main() -> Result<()> {
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::AhkLibrary => {
|
||||
let mut library = dirs::home_dir().context("there is no home directory")?;
|
||||
library.push("komorebic.lib.ahk");
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(library.clone())?;
|
||||
|
||||
file.write_all(SubCommand::generate_ahk_library().as_bytes())?;
|
||||
|
||||
println!(
|
||||
"\nAHK helper library for komorebic written to {}",
|
||||
library
|
||||
.to_str()
|
||||
.context("could not find the path to the generated ahk lib file")?
|
||||
);
|
||||
|
||||
println!(
|
||||
"\nYou can include the library at the top of your ~/komorebi.ahk config with this line:"
|
||||
);
|
||||
|
||||
println!("\n#Include %A_ScriptDir%\\komorebic.lib.ahk");
|
||||
}
|
||||
SubCommand::Log => {
|
||||
let mut color_log = std::env::temp_dir();
|
||||
color_log.push("komorebi.log");
|
||||
let file = TailedFile::new(File::open(color_log)?);
|
||||
let locked = file.lock();
|
||||
for line in locked.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
}
|
||||
SubCommand::Focus(arg) => {
|
||||
send_message(&*SocketMessage::FocusWindow(arg.operation_direction).as_bytes()?)?;
|
||||
}
|
||||
@@ -299,6 +430,12 @@ fn main() -> Result<()> {
|
||||
SubCommand::MoveToWorkspace(arg) => {
|
||||
send_message(&*SocketMessage::MoveContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::SendToMonitor(arg) => {
|
||||
send_message(&*SocketMessage::SendContainerToMonitorNumber(arg.target).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::SendToWorkspace(arg) => {
|
||||
send_message(&*SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ContainerPadding(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)
|
||||
@@ -321,6 +458,9 @@ fn main() -> Result<()> {
|
||||
&*SocketMessage::AdjustContainerPadding(arg.sizing, arg.adjustment).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::ToggleFocusFollowsMouse => {
|
||||
send_message(&*SocketMessage::ToggleFocusFollowsMouse.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ToggleTiling => {
|
||||
send_message(&*SocketMessage::ToggleTiling.as_bytes()?)?;
|
||||
}
|
||||
@@ -330,6 +470,9 @@ fn main() -> Result<()> {
|
||||
SubCommand::ToggleMonocle => {
|
||||
send_message(&*SocketMessage::ToggleMonocle.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ToggleMaximize => {
|
||||
send_message(&*SocketMessage::ToggleMaximize.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::WorkspaceLayout(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceLayout(arg.monitor, arg.workspace, arg.value)
|
||||
@@ -383,17 +526,18 @@ fn main() -> Result<()> {
|
||||
SubCommand::Stop => {
|
||||
send_message(&*SocketMessage::Stop.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::FloatRule(arg) => match arg.identifier {
|
||||
ApplicationIdentifier::Exe => {
|
||||
send_message(&*SocketMessage::FloatExe(arg.id).as_bytes()?)?;
|
||||
}
|
||||
ApplicationIdentifier::Class => {
|
||||
send_message(&*SocketMessage::FloatClass(arg.id).as_bytes()?)?;
|
||||
}
|
||||
ApplicationIdentifier::Title => {
|
||||
send_message(&*SocketMessage::FloatTitle(arg.id).as_bytes()?)?;
|
||||
}
|
||||
},
|
||||
SubCommand::FloatRule(arg) => {
|
||||
send_message(&*SocketMessage::FloatRule(arg.identifier, arg.id).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ManageRule(arg) => {
|
||||
send_message(&*SocketMessage::ManageRule(arg.identifier, arg.id).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::WorkspaceRule(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceRule(arg.identifier, arg.id, arg.monitor, arg.workspace)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::Stack(arg) => {
|
||||
send_message(&*SocketMessage::StackWindow(arg.operation_direction).as_bytes()?)?;
|
||||
}
|
||||
@@ -403,8 +547,11 @@ fn main() -> Result<()> {
|
||||
SubCommand::CycleStack(arg) => {
|
||||
send_message(&*SocketMessage::CycleStack(arg.cycle_direction).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ChangeLayout(arg) => {
|
||||
send_message(&*SocketMessage::ChangeLayout(arg.layout).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::FlipLayout(arg) => {
|
||||
send_message(&*SocketMessage::FlipLayout(arg.layout_flip).as_bytes()?)?;
|
||||
send_message(&*SocketMessage::FlipLayout(arg.flip).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::FocusMonitor(arg) => {
|
||||
send_message(&*SocketMessage::FocusMonitorNumber(arg.target).as_bytes()?)?;
|
||||
@@ -451,13 +598,13 @@ fn main() -> Result<()> {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?)
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error)
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,6 +647,12 @@ fn main() -> Result<()> {
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::Manage => {
|
||||
send_message(&*SocketMessage::ManageFocusedWindow.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::Unmanage => {
|
||||
send_message(&*SocketMessage::UnmanageFocusedWindow.as_bytes()?)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user