mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-15 01:37:42 +01:00
Compare commits
19 Commits
v0.1.19
...
feature/fo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b101ea5916 | ||
|
|
8afad7246f | ||
|
|
8e9d63cd36 | ||
|
|
a673c69744 | ||
|
|
c0b65677a2 | ||
|
|
0cf7147e39 | ||
|
|
b39e65642b | ||
|
|
a68f3843b7 | ||
|
|
b3e989c6c7 | ||
|
|
4534a1511e | ||
|
|
1f87e3acd2 | ||
|
|
f67d0c7b9b | ||
|
|
aa0277d58c | ||
|
|
42ac13e0bd | ||
|
|
2a1d87d41b | ||
|
|
0d1595e543 | ||
|
|
4283dfb0ba | ||
|
|
2a6aa09b07 | ||
|
|
8c6bd13511 |
141
Cargo.lock
generated
141
Cargo.lock
generated
@@ -328,6 +328,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.16"
|
||||
@@ -815,7 +821,7 @@ dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"winput",
|
||||
"winreg 0.51.0",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -824,6 +830,8 @@ version = "0.1.19"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
"dunce",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -840,6 +848,7 @@ dependencies = [
|
||||
"color-eyre",
|
||||
"derive-ahk",
|
||||
"dirs",
|
||||
"dunce",
|
||||
"fs-tail",
|
||||
"heck",
|
||||
"komorebi-core",
|
||||
@@ -1122,9 +1131,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.59"
|
||||
version = "0.10.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
|
||||
checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -1154,9 +1163,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.95"
|
||||
version = "0.9.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
|
||||
checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -1216,7 +1225,7 @@ dependencies = [
|
||||
"redox_syscall 0.4.1",
|
||||
"smallvec",
|
||||
"thread-id",
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1303,9 +1312,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1531,9 +1540,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.15"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -1543,9 +1552,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.15"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1584,18 +1593,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.192"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.192"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1745,9 +1754,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.29.10"
|
||||
version = "0.29.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5"
|
||||
checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"core-foundation-sys",
|
||||
@@ -1945,11 +1954,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -1987,9 +1997,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.4"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -1998,9 +2008,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
@@ -2251,30 +2261,30 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.51.1"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.51.1"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.51.1"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb2b158efec5af20d8846836622f50a87e6556b9153a42772fa047f773c0e555"
|
||||
checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2283,9 +2293,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.51.1"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0546e63e1ce64c04403d2311fa0e3ab5ae3a367bd524b4a38d8d8d18c70cfa76"
|
||||
checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2313,7 +2323,7 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2331,6 +2341,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -2343,6 +2368,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -2355,6 +2386,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -2367,6 +2404,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -2379,6 +2422,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -2391,6 +2440,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -2403,6 +2458,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -2415,6 +2476,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winput"
|
||||
version = "0.2.5"
|
||||
@@ -2436,9 +2503,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.51.0"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"windows-sys 0.48.0",
|
||||
|
||||
@@ -9,11 +9,14 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
windows-interface = { version = "0.51" }
|
||||
windows-implement = { version = "0.51" }
|
||||
windows-interface = { version = "0.52" }
|
||||
windows-implement = { version = "0.52" }
|
||||
dunce = "1"
|
||||
dirs = "5"
|
||||
color-eyre = "0.6"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.51"
|
||||
version = "0.52"
|
||||
features = [
|
||||
"implement",
|
||||
"Win32_System_Com",
|
||||
|
||||
33
README.md
33
README.md
@@ -3,9 +3,6 @@
|
||||
Tiling Window Management for Windows.
|
||||
|
||||
<p>
|
||||
<a href="https://bdsmovement.net">
|
||||
<img alt="Stand With Palestine" src="https://img.shields.io/badge/Stand_With_Palestine-%F0%9F%87%B5%F0%9F%87%B8-white">
|
||||
</a>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LGUG2Z/komorebi/.github/workflows/windows.yaml">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LGUG2Z/komorebi">
|
||||
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/LGUG2Z/komorebi/total">
|
||||
@@ -16,11 +13,14 @@ Tiling Window Management for Windows.
|
||||
<a href="https://github.com/sponsors/LGUG2Z">
|
||||
<img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/LGUG2Z">
|
||||
</a>
|
||||
<a href="https://ko-fi.com/lgug2z">
|
||||
<img alt="Ko-fi" src="https://img.shields.io/badge/kofi-tip-green">
|
||||
</a>
|
||||
<a href="https://notado.app/feeds/jado/software-development">
|
||||
<img alt="Notado Feed" src="https://img.shields.io/badge/Notado-Subscribe-informational">
|
||||
</a>
|
||||
<a href="https://twitter.com/LGUG2Z">
|
||||
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/LGUG2Z?style=flat">
|
||||
<a href="https://www.youtube.com/channel/UCeai3-do-9O4MNy9_xjO6mg?sub_confirmation=1">
|
||||
<img alt="YouTube" src="https://img.shields.io/youtube/channel/subscribers/UCeai3-do-9O4MNy9_xjO6mg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -88,21 +88,20 @@ Articles, blog posts, demos, and videos about _komorebi_ can be added to this li
|
||||
_komorebi_ is a free and open-source project, and one that encourages you to make charitable donations if
|
||||
you find the software to be useful and have the financial means.
|
||||
|
||||
I encourage you to make a charitable donation
|
||||
to [Fresh Start Refugee](https://www.freshstartrefugee.org/donate) before
|
||||
you consider sponsoring me on GitHub.
|
||||
I encourage you to make a charitable donation to the [Palestine Children's
|
||||
Relief Fund](https://pcrf1.app.neoncrm.com/forms/gaza-recovery) and [Fresh
|
||||
Start Refugee](https://www.freshstartrefugee.org/donate) before you consider
|
||||
sponsoring me on GitHub.
|
||||
|
||||
## GitHub Sponsors
|
||||
## Project Sponsorship
|
||||
|
||||
[GitHub Sponsors is enabled for this project](https://github.com/sponsors/LGUG2Z). Users who sponsor my work
|
||||
on `komorebi` at any of the predefined monthly tiers will be given access to a private fork of this repository where I
|
||||
push features-in-progress that are not yet quite ready to be pushed on the main repository.
|
||||
[GitHub Sponsors is enabled for this
|
||||
project](https://github.com/sponsors/LGUG2Z). Unfortunately I don't have
|
||||
anything specific to offer besides my gratitude and shout outs at the end of
|
||||
_komorebi_ live development videos and tutorials.
|
||||
|
||||
There will never be any feature of `komorebi` that is gated behind sponsorship; every new feature will always be
|
||||
available for free in the public repository once it meets the requisite level of code quality and completion.
|
||||
|
||||
Features-in-progress that are available in early access will be tagged in the issues with
|
||||
an ["early access" label](https://github.com/LGUG2Z/komorebi/issues?q=is%3Aopen+is%3Aissue+label%3A%22early+access%22).
|
||||
If you would like to tip or sponsor the project but are unable to use GitHub
|
||||
Sponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z).
|
||||
|
||||
## Demonstrations
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
color-eyre = "0.6"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
strum = { version = "0.25", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
color-eyre = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
|
||||
@@ -98,6 +98,19 @@ pub struct ApplicationConfiguration {
|
||||
pub float_identifiers: Option<Vec<IdWithIdentifierAndComment>>,
|
||||
}
|
||||
|
||||
impl ApplicationConfiguration {
|
||||
pub fn populate_default_matching_strategies(&mut self) {
|
||||
if self.identifier.matching_strategy.is_none() {
|
||||
match self.identifier.kind {
|
||||
ApplicationIdentifier::Exe => {
|
||||
self.identifier.matching_strategy = Option::from(MatchingStrategy::Equals);
|
||||
}
|
||||
ApplicationIdentifier::Class | ApplicationIdentifier::Title => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApplicationConfigurationGenerator;
|
||||
|
||||
@@ -108,6 +121,10 @@ impl ApplicationConfigurationGenerator {
|
||||
|
||||
pub fn format(content: &str) -> Result<String> {
|
||||
let mut cfgen = Self::load(content)?;
|
||||
for cfg in &mut cfgen {
|
||||
cfg.populate_default_matching_strategies();
|
||||
}
|
||||
|
||||
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Ok(serde_yaml::to_string(&cfgen)?)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@@ -31,23 +32,20 @@ impl DerefMut for CustomLayout {
|
||||
}
|
||||
|
||||
impl CustomLayout {
|
||||
pub fn from_path_buf(path: PathBuf) -> Result<Self> {
|
||||
let invalid_filetype = anyhow!("custom layouts must be json or yaml files");
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let path = path.as_ref();
|
||||
let layout: Self = match path.extension() {
|
||||
Some(extension) => {
|
||||
if extension == "yaml" || extension == "yml" {
|
||||
serde_yaml::from_reader(BufReader::new(File::open(path)?))?
|
||||
} else if extension == "json" {
|
||||
serde_json::from_reader(BufReader::new(File::open(path)?))?
|
||||
} else {
|
||||
return Err(invalid_filetype);
|
||||
}
|
||||
Some(extension) if extension == "yaml" || extension == "yml" => {
|
||||
serde_json::from_reader(BufReader::new(File::open(path)?))?
|
||||
}
|
||||
None => return Err(invalid_filetype),
|
||||
Some(extension) if extension == "json" => {
|
||||
serde_json::from_reader(BufReader::new(File::open(path)?))?
|
||||
}
|
||||
_ => return Err(anyhow!("custom layouts must be json or yaml files")),
|
||||
};
|
||||
|
||||
if !layout.is_valid() {
|
||||
return Err(anyhow!("the layout file provided was invalid"));
|
||||
bail!("the layout file provided was invalid");
|
||||
}
|
||||
|
||||
Ok(layout)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc, clippy::use_self)]
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@@ -102,8 +104,10 @@ pub enum SocketMessage {
|
||||
FocusNamedWorkspace(String),
|
||||
ContainerPadding(usize, usize, i32),
|
||||
NamedWorkspaceContainerPadding(String, i32),
|
||||
FocusedWorkspaceContainerPadding(i32),
|
||||
WorkspacePadding(usize, usize, i32),
|
||||
NamedWorkspacePadding(String, i32),
|
||||
FocusedWorkspacePadding(i32),
|
||||
WorkspaceTiling(usize, usize, bool),
|
||||
NamedWorkspaceTiling(String, bool),
|
||||
WorkspaceName(usize, usize, String),
|
||||
@@ -296,3 +300,36 @@ impl Sizing {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_home_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
|
||||
let mut resolved_path = PathBuf::new();
|
||||
let mut resolved = false;
|
||||
for c in path.as_ref().components() {
|
||||
match c {
|
||||
std::path::Component::Normal(c)
|
||||
if (c == "~" || c == "$Env:USERPROFILE" || c == "$HOME") && !resolved =>
|
||||
{
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
|
||||
resolved_path.extend(home.components());
|
||||
resolved = true;
|
||||
}
|
||||
|
||||
_ => resolved_path.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
let parent = resolved_path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("cannot parse parent directory"))?;
|
||||
|
||||
Ok(if parent.is_dir() {
|
||||
let file = resolved_path
|
||||
.components()
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("cannot parse filename"))?;
|
||||
dunce::canonicalize(parent)?.join(file)
|
||||
} else {
|
||||
resolved_path
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,11 +15,9 @@ komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
bitflags = "2"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
color-eyre = "0.6"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
ctrlc = "3"
|
||||
dirs = "5"
|
||||
getset = "0.1"
|
||||
hotwatch = "0.4"
|
||||
lazy_static = "1"
|
||||
@@ -41,10 +39,12 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uds_windows = "1"
|
||||
which = "5"
|
||||
winput = "0.2"
|
||||
winreg = "0.51"
|
||||
winreg = "0.52"
|
||||
windows-interface = { workspace = true }
|
||||
windows-implement = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
|
||||
[features]
|
||||
deadlock_detection = []
|
||||
|
||||
@@ -22,7 +22,6 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
@@ -252,7 +251,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "komorebi.log");
|
||||
let appender = tracing_appender::rolling::never(&*DATA_DIR, "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);
|
||||
@@ -305,13 +304,8 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
}
|
||||
|
||||
pub fn load_configuration() -> Result<()> {
|
||||
let home = HOME_DIR.clone();
|
||||
|
||||
let mut config_pwsh = home.clone();
|
||||
config_pwsh.push("komorebi.ps1");
|
||||
|
||||
let mut config_ahk = home;
|
||||
config_ahk.push("komorebi.ahk");
|
||||
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
||||
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
||||
|
||||
if config_pwsh.exists() {
|
||||
let powershell_exe = if which("pwsh.exe").is_ok() {
|
||||
@@ -320,25 +314,13 @@ pub fn load_configuration() -> Result<()> {
|
||||
"powershell.exe"
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_pwsh
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
tracing::info!("loading configuration file: {}", config_pwsh.display());
|
||||
|
||||
Command::new(powershell_exe)
|
||||
.arg(config_pwsh.as_os_str())
|
||||
.output()?;
|
||||
} else if config_ahk.exists() && which(&*AHK_EXE).is_ok() {
|
||||
tracing::info!(
|
||||
"loading configuration file: {}",
|
||||
config_ahk
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
tracing::info!("loading configuration file: {}", config_ahk.display());
|
||||
|
||||
Command::new(&*AHK_EXE)
|
||||
.arg(config_ahk.as_os_str())
|
||||
@@ -410,7 +392,7 @@ pub fn notify_subscribers(notification: &str) -> Result<()> {
|
||||
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
|
||||
for (subscriber, pipe) in &mut *subscriptions {
|
||||
match writeln!(pipe, "{notification}") {
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
tracing::debug!("pushed notification to subscriber: {}", subscriber);
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -514,6 +496,8 @@ fn main() -> Result<()> {
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
|
||||
WindowsApi::foreground_lock_timeout()?;
|
||||
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
detect_deadlocks();
|
||||
|
||||
@@ -528,7 +512,7 @@ fn main() -> Result<()> {
|
||||
let wm = if let Some(config) = &opts.config {
|
||||
tracing::info!(
|
||||
"creating window manager from static configuration file: {}",
|
||||
config.as_os_str().to_str().unwrap()
|
||||
config.display()
|
||||
);
|
||||
|
||||
Arc::new(Mutex::new(StaticConfig::preload(
|
||||
@@ -557,9 +541,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if opts.config.is_none() {
|
||||
std::thread::spawn(|| {
|
||||
load_configuration().expect("could not load configuration");
|
||||
});
|
||||
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
|
||||
|
||||
if opts.await_configuration {
|
||||
let backoff = Backoff::new();
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
@@ -120,9 +121,7 @@ impl Monitor {
|
||||
.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"
|
||||
));
|
||||
bail!("cannot move native maximized window to another monitor or workspace");
|
||||
}
|
||||
|
||||
let container = workspace
|
||||
|
||||
@@ -322,6 +322,28 @@ impl WindowManager {
|
||||
monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::FocusedWorkspaceContainerPadding(adjustment) => {
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let focused_monitor = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
|
||||
|
||||
self.set_container_padding(focused_monitor_idx, focused_workspace_idx, adjustment)?;
|
||||
}
|
||||
SocketMessage::FocusedWorkspacePadding(adjustment) => {
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let focused_monitor = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
|
||||
|
||||
self.set_workspace_padding(focused_monitor_idx, focused_workspace_idx, adjustment)?;
|
||||
}
|
||||
SocketMessage::AdjustContainerPadding(sizing, adjustment) => {
|
||||
self.adjust_container_padding(sizing, adjustment)?;
|
||||
}
|
||||
@@ -450,10 +472,10 @@ impl WindowManager {
|
||||
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
|
||||
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
|
||||
SocketMessage::ChangeLayoutCustom(ref path) => {
|
||||
self.change_workspace_custom_layout(path.clone())?;
|
||||
self.change_workspace_custom_layout(path)?;
|
||||
}
|
||||
SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, ref path) => {
|
||||
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path.clone())?;
|
||||
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;
|
||||
}
|
||||
SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => {
|
||||
self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;
|
||||
@@ -484,7 +506,7 @@ impl WindowManager {
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
at_container_count,
|
||||
path.clone(),
|
||||
path,
|
||||
)?;
|
||||
}
|
||||
SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {
|
||||
@@ -494,7 +516,7 @@ impl WindowManager {
|
||||
if let Some((monitor_idx, workspace_idx)) =
|
||||
self.monitor_workspace_index_by_name(workspace)
|
||||
{
|
||||
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path.clone())?;
|
||||
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::NamedWorkspaceTiling(ref workspace, tile) => {
|
||||
@@ -535,7 +557,7 @@ impl WindowManager {
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
at_container_count,
|
||||
path.clone(),
|
||||
path,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -669,10 +691,7 @@ impl WindowManager {
|
||||
Err(error) => error.to_string(),
|
||||
};
|
||||
|
||||
let mut socket = DATA_DIR.clone();
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(state.as_bytes())?;
|
||||
}
|
||||
@@ -692,10 +711,7 @@ impl WindowManager {
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let mut socket = DATA_DIR.clone();
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(response.as_bytes())?;
|
||||
}
|
||||
@@ -1012,8 +1028,7 @@ impl WindowManager {
|
||||
let workspace = self.focused_workspace()?;
|
||||
let resize = workspace.resize_dimensions();
|
||||
|
||||
let mut quicksave_json = std::env::temp_dir();
|
||||
quicksave_json.push("komorebi.quicksave.json");
|
||||
let quicksave_json = std::env::temp_dir().join("komorebi.quicksave.json");
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
@@ -1026,15 +1041,10 @@ impl WindowManager {
|
||||
SocketMessage::QuickLoad => {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let mut quicksave_json = std::env::temp_dir();
|
||||
quicksave_json.push("komorebi.quicksave.json");
|
||||
let quicksave_json = std::env::temp_dir().join("komorebi.quicksave.json");
|
||||
|
||||
let file = File::open(&quicksave_json).map_err(|_| {
|
||||
anyhow!(
|
||||
"no quicksave found at {}",
|
||||
quicksave_json.display().to_string()
|
||||
)
|
||||
})?;
|
||||
let file = File::open(&quicksave_json)
|
||||
.map_err(|_| anyhow!("no quicksave found at {}", quicksave_json.display()))?;
|
||||
|
||||
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
|
||||
|
||||
@@ -1049,15 +1059,15 @@ impl WindowManager {
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(path.clone())?;
|
||||
.open(path)?;
|
||||
|
||||
serde_json::to_writer_pretty(&file, &resize)?;
|
||||
}
|
||||
SocketMessage::Load(ref path) => {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let file = File::open(path)
|
||||
.map_err(|_| anyhow!("no file found at {}", path.display().to_string()))?;
|
||||
let file =
|
||||
File::open(path).map_err(|_| anyhow!("no file found at {}", path.display()))?;
|
||||
|
||||
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
|
||||
|
||||
@@ -1169,9 +1179,7 @@ impl WindowManager {
|
||||
SocketMessage::NotificationSchema => {
|
||||
let notification = schema_for!(Notification);
|
||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||
let mut socket = DATA_DIR.clone();
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(schema.as_bytes())?;
|
||||
@@ -1179,9 +1187,7 @@ impl WindowManager {
|
||||
SocketMessage::SocketSchema => {
|
||||
let socket_message = schema_for!(SocketMessage);
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
let mut socket = DATA_DIR.clone();
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(schema.as_bytes())?;
|
||||
@@ -1189,18 +1195,14 @@ impl WindowManager {
|
||||
SocketMessage::StaticConfigSchema => {
|
||||
let socket_message = schema_for!(StaticConfig);
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
let mut socket = DATA_DIR.clone();
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(schema.as_bytes())?;
|
||||
}
|
||||
SocketMessage::GenerateStaticConfig => {
|
||||
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
|
||||
let mut socket = DATA_DIR.clone();
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.write_all(config.as_bytes())?;
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
|
||||
Event::MouseMoveRelative { .. } => {
|
||||
if !ignore_movement {
|
||||
match wm.lock().raise_window_at_cursor_pos() {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::ALT_FOCUS_HACK;
|
||||
use crate::BORDER_COLOUR_CURRENT;
|
||||
@@ -29,13 +30,13 @@ use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WORKSPACE_RULES;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use dirs::home_dir;
|
||||
use hotwatch::notify::DebouncedEvent;
|
||||
use hotwatch::Hotwatch;
|
||||
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
||||
use komorebi_core::config_generation::ApplicationOptions;
|
||||
use komorebi_core::config_generation::IdWithIdentifier;
|
||||
use komorebi_core::config_generation::MatchingStrategy;
|
||||
use komorebi_core::resolve_home_path;
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::DefaultLayout;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
@@ -616,13 +617,8 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
if let Some(path) = &self.app_specific_configuration_path {
|
||||
let stringified = path.to_string_lossy();
|
||||
let stringified = stringified.replace(
|
||||
"$Env:USERPROFILE",
|
||||
&home_dir().expect("no home dir").to_string_lossy(),
|
||||
);
|
||||
|
||||
let content = std::fs::read_to_string(stringified)?;
|
||||
let path = resolve_home_path(path)?;
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let asc = ApplicationConfigurationGenerator::load(&content)?;
|
||||
|
||||
for mut entry in asc {
|
||||
@@ -762,7 +758,7 @@ impl StaticConfig {
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
@@ -806,6 +802,14 @@ impl StaticConfig {
|
||||
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
match value.focus_follows_mouse {
|
||||
None => WindowsApi::disable_focus_follows_mouse()?,
|
||||
Some(FocusFollowsMouseImplementation::Windows) => {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Komorebi) => {}
|
||||
};
|
||||
|
||||
let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;
|
||||
|
||||
wm.hotwatch.watch(path, move |event| match event {
|
||||
@@ -949,6 +953,15 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
wm.work_area_offset = value.global_work_area_offset;
|
||||
|
||||
match value.focus_follows_mouse {
|
||||
None => WindowsApi::disable_focus_follows_mouse()?,
|
||||
Some(FocusFollowsMouseImplementation::Windows) => {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Komorebi) => {}
|
||||
};
|
||||
|
||||
wm.focus_follows_mouse = value.focus_follows_mouse;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -241,7 +241,7 @@ impl Window {
|
||||
|
||||
// Raise Window to foreground
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set as foreground window, but continuing execution of raise(): {}",
|
||||
@@ -252,7 +252,7 @@ impl Window {
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
match WindowsApi::set_focus(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set focus, but continuing execution of raise(): {}",
|
||||
@@ -302,7 +302,7 @@ impl Window {
|
||||
}
|
||||
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
foregrounded = true;
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -334,7 +334,7 @@ impl Window {
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
match WindowsApi::set_focus(self.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"could not set focus, but continuing execution of focus(): {}",
|
||||
|
||||
@@ -3,11 +3,13 @@ use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::ErrorKind;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use hotwatch::notify::DebouncedEvent;
|
||||
@@ -166,7 +168,7 @@ impl WindowManager {
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
@@ -247,13 +249,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn watch_configuration(&mut self, enable: bool) -> Result<()> {
|
||||
let home = HOME_DIR.clone();
|
||||
|
||||
let mut config_pwsh = home.clone();
|
||||
config_pwsh.push("komorebi.ps1");
|
||||
|
||||
let mut config_ahk = home;
|
||||
config_ahk.push("komorebi.ahk");
|
||||
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
||||
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
||||
|
||||
if config_pwsh.exists() {
|
||||
self.configure_watcher(enable, config_pwsh)?;
|
||||
@@ -265,50 +262,39 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
fn configure_watcher(&mut self, enable: bool, config: PathBuf) -> Result<()> {
|
||||
if config.exists() {
|
||||
if enable {
|
||||
tracing::info!(
|
||||
"watching configuration for changes: {}",
|
||||
config
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.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
|
||||
match self.hotwatch.unwatch(config.clone()) {
|
||||
Ok(_) => {}
|
||||
Err(error) => match error {
|
||||
hotwatch::Error::Notify(error) => match error {
|
||||
hotwatch::notify::Error::WatchNotFound => {}
|
||||
error => return Err(error.into()),
|
||||
},
|
||||
error @ hotwatch::Error::Io(_) => return Err(error.into()),
|
||||
if enable {
|
||||
tracing::info!("watching configuration for changes: {}", config.display());
|
||||
// Always make absolutely sure that there isn't an already existing watch, because
|
||||
// hotwatch allows multiple watches to be registered for the same path
|
||||
match self.hotwatch.unwatch(&config) {
|
||||
Ok(()) => {}
|
||||
Err(error) => match error {
|
||||
hotwatch::Error::Notify(error) => match error {
|
||||
hotwatch::notify::Error::WatchNotFound => {}
|
||||
error => return Err(error.into()),
|
||||
},
|
||||
error @ hotwatch::Error::Io(_) => return Err(error.into()),
|
||||
},
|
||||
}
|
||||
|
||||
self.hotwatch.watch(config, |event| match event {
|
||||
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
||||
// a NoticeRemove, presumably because of the use of swap files?
|
||||
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
|
||||
std::thread::spawn(|| {
|
||||
load_configuration().expect("could not load configuration");
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
})?;
|
||||
} else {
|
||||
tracing::info!(
|
||||
"no longer watching configuration for changes: {}",
|
||||
config.display()
|
||||
);
|
||||
|
||||
self.hotwatch.watch(config, |event| match event {
|
||||
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
||||
// a NoticeRemove, presumably because of the use of swap files?
|
||||
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
|
||||
std::thread::spawn(|| {
|
||||
load_configuration().expect("could not load configuration");
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
})?;
|
||||
} else {
|
||||
tracing::info!(
|
||||
"no longer watching configuration for changes: {}",
|
||||
config
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
||||
);
|
||||
|
||||
self.hotwatch.unwatch(config)?;
|
||||
};
|
||||
}
|
||||
self.hotwatch.unwatch(config)?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -868,7 +854,7 @@ impl WindowManager {
|
||||
// attach to the thread of the desktop window always seems to result in "Access is
|
||||
// denied (os error 5)"
|
||||
match WindowsApi::set_foreground_window(desktop_window.hwnd()) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::warn!("{} {}:{}", error, file!(), line!());
|
||||
}
|
||||
@@ -1002,9 +988,7 @@ impl WindowManager {
|
||||
let workspace = self.focused_workspace()?;
|
||||
let focused_hwnd = WindowsApi::foreground_window()?;
|
||||
if !workspace.contains_managed_window(focused_hwnd) {
|
||||
return Err(anyhow!(
|
||||
"ignoring commands while active window is not managed by komorebi"
|
||||
));
|
||||
bail!("ignoring commands while active window is not managed by komorebi");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1120,9 +1104,7 @@ impl WindowManager {
|
||||
.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"
|
||||
));
|
||||
bail!("cannot move native maximized window to another monitor or workspace");
|
||||
}
|
||||
|
||||
let container = workspace
|
||||
@@ -1232,9 +1214,7 @@ impl WindowManager {
|
||||
// removing this messes up the monitor / container / window index somewhere
|
||||
// and results in the wrong window getting moved across the monitor boundary
|
||||
if workspace.is_focused_window_monocle_or_maximized()? {
|
||||
return Err(anyhow!(
|
||||
"ignoring command while active window is in monocle mode or maximized"
|
||||
));
|
||||
bail!("ignoring command while active window is in monocle mode or maximized");
|
||||
}
|
||||
|
||||
tracing::info!("moving container");
|
||||
@@ -1396,9 +1376,7 @@ impl WindowManager {
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
if workspace.is_focused_window_monocle_or_maximized()? {
|
||||
return Err(anyhow!(
|
||||
"ignoring command while active window is in monocle mode or maximized"
|
||||
));
|
||||
bail!("ignoring command while active window is in monocle mode or maximized");
|
||||
}
|
||||
|
||||
tracing::info!("moving container");
|
||||
@@ -1425,7 +1403,7 @@ impl WindowManager {
|
||||
.ok_or_else(|| anyhow!("there must be at least one window in a container"))?;
|
||||
|
||||
if len.get() == 1 {
|
||||
return Err(anyhow!("there is only one window in this container"));
|
||||
bail!("there is only one window in this container");
|
||||
}
|
||||
|
||||
let current_idx = container.focused_window_idx();
|
||||
@@ -1510,7 +1488,7 @@ impl WindowManager {
|
||||
tracing::info!("removing window");
|
||||
|
||||
if self.focused_container()?.windows().len() == 1 {
|
||||
return Err(anyhow!("a container must have at least one window"));
|
||||
bail!("a container must have at least one window");
|
||||
}
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -1729,10 +1707,13 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn change_workspace_custom_layout(&mut self, path: PathBuf) -> Result<()> {
|
||||
pub fn change_workspace_custom_layout<P>(&mut self, path: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path> + std::fmt::Debug,
|
||||
{
|
||||
tracing::info!("changing layout");
|
||||
|
||||
let layout = CustomLayout::from_path_buf(path)?;
|
||||
let layout = CustomLayout::from_path(path)?;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
match workspace.layout() {
|
||||
@@ -1854,13 +1835,16 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn add_workspace_layout_custom_rule(
|
||||
pub fn add_workspace_layout_custom_rule<P>(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
at_container_count: usize,
|
||||
path: PathBuf,
|
||||
) -> Result<()> {
|
||||
path: P,
|
||||
) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path> + std::fmt::Debug,
|
||||
{
|
||||
tracing::info!("setting workspace layout");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
@@ -1885,7 +1869,7 @@ impl WindowManager {
|
||||
.get_mut(workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let layout = CustomLayout::from_path_buf(path)?;
|
||||
let layout = CustomLayout::from_path(path)?;
|
||||
|
||||
let rules: &mut Vec<(usize, Layout)> = workspace.layout_rules_mut();
|
||||
rules.retain(|pair| pair.0 != at_container_count);
|
||||
@@ -1986,14 +1970,17 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn set_workspace_layout_custom(
|
||||
pub fn set_workspace_layout_custom<P>(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
path: PathBuf,
|
||||
) -> Result<()> {
|
||||
path: P,
|
||||
) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path> + std::fmt::Debug,
|
||||
{
|
||||
tracing::info!("setting workspace layout");
|
||||
let layout = CustomLayout::from_path_buf(path)?;
|
||||
let layout = CustomLayout::from_path(path)?;
|
||||
let invisible_borders = self.invisible_borders;
|
||||
let offset = self.work_area_offset;
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
@@ -2164,7 +2151,7 @@ impl WindowManager {
|
||||
if self.monitors().get(idx).is_some() {
|
||||
self.monitors.focus(idx);
|
||||
} else {
|
||||
return Err(anyhow!("this is not a valid monitor index"));
|
||||
bail!("this is not a valid monitor index");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -105,7 +105,9 @@ use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
|
||||
@@ -368,7 +370,7 @@ impl WindowsApi {
|
||||
|
||||
pub fn close_window(hwnd: HWND) -> Result<()> {
|
||||
match Self::post_message(hwnd, WM_CLOSE, WPARAM(0), LPARAM(0)) {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err(anyhow!("could not close window")),
|
||||
}
|
||||
}
|
||||
@@ -679,6 +681,42 @@ impl WindowsApi {
|
||||
.process()
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn foreground_lock_timeout() -> Result<()> {
|
||||
let mut value: u32 = 0;
|
||||
|
||||
Self::system_parameters_info_w(
|
||||
SPI_GETFOREGROUNDLOCKTIMEOUT,
|
||||
0,
|
||||
std::ptr::addr_of_mut!(value).cast(),
|
||||
SPIF_SENDCHANGE,
|
||||
)?;
|
||||
|
||||
tracing::info!("current value of ForegroundLockTimeout is {value}");
|
||||
|
||||
if value != 0 {
|
||||
tracing::info!("updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating");
|
||||
|
||||
Self::system_parameters_info_w(
|
||||
SPI_SETFOREGROUNDLOCKTIMEOUT,
|
||||
0,
|
||||
0 as *mut c_void,
|
||||
SPIF_SENDCHANGE,
|
||||
)?;
|
||||
|
||||
Self::system_parameters_info_w(
|
||||
SPI_GETFOREGROUNDLOCKTIMEOUT,
|
||||
0,
|
||||
std::ptr::addr_of_mut!(value).cast(),
|
||||
SPIF_SENDCHANGE,
|
||||
)?;
|
||||
|
||||
tracing::info!("updated value of ForegroundLockTimeout is now {value}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn focus_follows_mouse() -> Result<bool> {
|
||||
let mut is_enabled: BOOL = unsafe { std::mem::zeroed() };
|
||||
|
||||
@@ -61,7 +61,7 @@ impl WinEventListener {
|
||||
MessageLoop::start(10, |_msg| {
|
||||
if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().1.try_recv() {
|
||||
match outgoing.send(event) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
tracing::error!("{}", error);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
if let Some(pathbuf) = &config.custom_layout {
|
||||
let layout = CustomLayout::from_path_buf(pathbuf.clone())?;
|
||||
let layout = CustomLayout::from_path(pathbuf)?;
|
||||
self.layout = Layout::Custom(layout);
|
||||
self.tile = true;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ impl Workspace {
|
||||
if let Some(layout_rules) = &config.custom_layout_rules {
|
||||
let rules = self.layout_rules_mut();
|
||||
for (count, pathbuf) in layout_rules {
|
||||
let rule = CustomLayout::from_path_buf(pathbuf.clone())?;
|
||||
let rule = CustomLayout::from_path(pathbuf)?;
|
||||
rules.push((*count, Layout::Custom(rule)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ derive-ahk = { path = "../derive-ahk" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
clap = { version = "4", features = ["derive", "wrap_help"] }
|
||||
color-eyre = "0.6"
|
||||
dirs = "5"
|
||||
fs-tail = "0.1"
|
||||
heck = "0.4"
|
||||
lazy_static = "1"
|
||||
@@ -30,3 +28,6 @@ sysinfo = "0.29"
|
||||
uds_windows = "1"
|
||||
which = "5"
|
||||
windows = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
@@ -16,9 +16,11 @@ use std::time::Duration;
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use fs_tail::TailedFile;
|
||||
use heck::ToKebabCase;
|
||||
use komorebi_core::resolve_home_path;
|
||||
use lazy_static::lazy_static;
|
||||
use paste::paste;
|
||||
use sysinfo::SystemExt;
|
||||
@@ -259,7 +261,7 @@ pub struct WorkspaceCustomLayout {
|
||||
workspace: usize,
|
||||
|
||||
/// JSON or YAML file from which the custom layout definition should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -268,7 +270,7 @@ pub struct NamedWorkspaceCustomLayout {
|
||||
workspace: String,
|
||||
|
||||
/// JSON or YAML file from which the custom layout definition should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -310,7 +312,7 @@ pub struct WorkspaceCustomLayoutRule {
|
||||
at_container_count: usize,
|
||||
|
||||
/// JSON or YAML file from which the custom layout definition should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -322,7 +324,7 @@ pub struct NamedWorkspaceCustomLayoutRule {
|
||||
at_container_count: usize,
|
||||
|
||||
/// JSON or YAML file from which the custom layout definition should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -431,6 +433,24 @@ pub struct SendToMonitorWorkspace {
|
||||
target_workspace: usize,
|
||||
}
|
||||
|
||||
macro_rules! gen_focused_workspace_padding_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
$(
|
||||
#[derive(clap::Parser, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
/// Pixels size to set as an integer
|
||||
size: i32,
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
gen_focused_workspace_padding_subcommand_args! {
|
||||
FocusedWorkspaceContainerPadding,
|
||||
FocusedWorkspacePadding,
|
||||
}
|
||||
|
||||
macro_rules! gen_padding_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
@@ -640,19 +660,19 @@ struct Stop {
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct SaveResize {
|
||||
/// File to which the resize layout dimensions should be saved
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct LoadResize {
|
||||
/// File from which the resize layout dimensions should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct LoadCustomLayout {
|
||||
/// JSON or YAML file from which the custom layout definition should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -670,23 +690,23 @@ struct Unsubscribe {
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct AhkAppSpecificConfiguration {
|
||||
/// YAML file from which the application-specific configurations should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
/// Optional YAML file of overrides to apply over the first file
|
||||
override_path: Option<String>,
|
||||
override_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct PwshAppSpecificConfiguration {
|
||||
/// YAML file from which the application-specific configurations should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
/// Optional YAML file of overrides to apply over the first file
|
||||
override_path: Option<String>,
|
||||
override_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct FormatAppSpecificConfiguration {
|
||||
/// YAML file from which the application-specific configurations should be loaded
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -699,7 +719,10 @@ struct AltFocusHack {
|
||||
struct EnableAutostart {
|
||||
/// Path to a static configuration JSON file
|
||||
#[clap(action, short, long)]
|
||||
config: String,
|
||||
config: PathBuf,
|
||||
/// Enable komorebi's custom focus-follows-mouse implementation
|
||||
#[clap(action, short, long = "ffm")]
|
||||
ffm: bool,
|
||||
/// Enable autostart of whkd
|
||||
#[clap(action, long)]
|
||||
whkd: bool,
|
||||
@@ -717,6 +740,8 @@ struct Opts {
|
||||
|
||||
#[derive(Parser, AhkLibrary)]
|
||||
enum SubCommand {
|
||||
/// Gather example configurations for a new-user quickstart
|
||||
Quickstart,
|
||||
/// Start komorebi.exe as a background process
|
||||
Start(Start),
|
||||
/// Stop the komorebi.exe process and restore all hidden windows
|
||||
@@ -857,6 +882,12 @@ enum SubCommand {
|
||||
/// Set offsets for a monitor to exclude parts of the work area from tiling
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MonitorWorkAreaOffset(MonitorWorkAreaOffset),
|
||||
/// Set container padding on the focused workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusedWorkspaceContainerPadding(FocusedWorkspaceContainerPadding),
|
||||
/// Set workspace padding on the focused workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusedWorkspacePadding(FocusedWorkspacePadding),
|
||||
/// Adjust container padding on the focused workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
AdjustContainerPadding(AdjustContainerPadding),
|
||||
@@ -1077,15 +1108,48 @@ pub fn send_message(bytes: &[u8]) -> Result<()> {
|
||||
Ok(stream.write_all(bytes)?)
|
||||
}
|
||||
|
||||
fn with_komorebic_socket<F: Fn() -> Result<()>>(f: F) -> Result<()> {
|
||||
let socket = DATA_DIR.join("komorebic.sock");
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(()) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
f()?;
|
||||
|
||||
let listener = UnixListener::bind(socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn startup_dir() -> Result<PathBuf> {
|
||||
let home_dir = dirs::home_dir().expect("unable to obtain user's home folder");
|
||||
let app_data = home_dir.join("AppData");
|
||||
let roaming = app_data.join("Roaming");
|
||||
let microsoft = roaming.join("Microsoft");
|
||||
let windows = microsoft.join("Windows");
|
||||
let start_menu = windows.join("Start Menu");
|
||||
let programs = start_menu.join("Programs");
|
||||
let startup = programs.join("Startup");
|
||||
let startup = dirs::home_dir()
|
||||
.expect("unable to obtain user's home folder")
|
||||
.join("AppData")
|
||||
.join("Roaming")
|
||||
.join("Microsoft")
|
||||
.join("Windows")
|
||||
.join("Start Menu")
|
||||
.join("Programs")
|
||||
.join("Startup");
|
||||
|
||||
if !startup.is_dir() {
|
||||
std::fs::create_dir_all(&startup)?;
|
||||
@@ -1099,16 +1163,54 @@ fn main() -> Result<()> {
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::EnableAutostart(args) => {
|
||||
let mut current_exe = std::env::current_exe().expect("unable to get exec path");
|
||||
current_exe.pop();
|
||||
SubCommand::Quickstart => {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let komorebic_exe = current_exe.join("komorebic.exe");
|
||||
let home_dir = dirs::home_dir().expect("could not find home dir");
|
||||
let config_dir = home_dir.join(".config");
|
||||
std::fs::create_dir_all(&config_dir)?;
|
||||
|
||||
let komorebi_json = reqwest::blocking::get(
|
||||
format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/v{version}/komorebi.example.json")
|
||||
)?.text()?;
|
||||
std::fs::write(HOME_DIR.join("komorebi.json"), komorebi_json)?;
|
||||
|
||||
let applications_yaml = reqwest::blocking::get(
|
||||
"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml"
|
||||
)?
|
||||
.text()?;
|
||||
std::fs::write(HOME_DIR.join("applications.yaml"), applications_yaml)?;
|
||||
|
||||
let whkdrc = reqwest::blocking::get(format!(
|
||||
"https://raw.githubusercontent.com/LGUG2Z/komorebi/v{version}/whkdrc.sample"
|
||||
))?
|
||||
.text()?;
|
||||
std::fs::write(config_dir.join("whkdrc"), whkdrc)?;
|
||||
|
||||
println!("Example ~/komorebi.json, ~/.config/whkdrc and latest ~/applications.yaml files downloaded");
|
||||
println!(
|
||||
"You can now run komorebic start -c \"$Env:USERPROFILE\\komorebi.json\" --whkd"
|
||||
);
|
||||
}
|
||||
SubCommand::EnableAutostart(args) => {
|
||||
let mut current_exe_dir = std::env::current_exe().expect("unable to get exec path");
|
||||
current_exe_dir.pop();
|
||||
|
||||
let komorebic_exe = current_exe_dir.join("komorebic.exe");
|
||||
let komorebic_exe = dunce::simplified(&komorebic_exe);
|
||||
|
||||
let startup_dir = startup_dir()?;
|
||||
let shortcut_file = startup_dir.join("komorebi.lnk");
|
||||
let shortcut_file = dunce::simplified(&shortcut_file);
|
||||
|
||||
let mut arguments = format!("start --config {}", args.config);
|
||||
let mut arguments = format!(
|
||||
"start --config {}",
|
||||
dunce::canonicalize(args.config)?.display()
|
||||
);
|
||||
|
||||
if args.ffm {
|
||||
arguments.push_str(" --ffm");
|
||||
}
|
||||
|
||||
if args.whkd {
|
||||
arguments.push_str(" --whkd");
|
||||
@@ -1117,12 +1219,12 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
Command::new("powershell")
|
||||
.arg("-c")
|
||||
.arg("$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()")
|
||||
.env("SHORTCUT_PATH", shortcut_file.to_string_lossy().to_string())
|
||||
.env("TARGET_PATH", komorebic_exe.to_string_lossy().to_string())
|
||||
.env("TARGET_ARGS", arguments)
|
||||
.output()?;
|
||||
.arg("-c")
|
||||
.arg("$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()")
|
||||
.env("SHORTCUT_PATH", shortcut_file.as_os_str())
|
||||
.env("TARGET_PATH", komorebic_exe.as_os_str())
|
||||
.env("TARGET_ARGS", arguments)
|
||||
.output()?;
|
||||
}
|
||||
SubCommand::DisableAutostart => {
|
||||
let startup_dir = startup_dir()?;
|
||||
@@ -1133,10 +1235,9 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
SubCommand::Check => {
|
||||
let home = HOME_DIR.clone();
|
||||
let home_lossy_string = home.to_string_lossy();
|
||||
let home_display = HOME_DIR.display();
|
||||
if HAS_CUSTOM_CONFIG_HOME.load(Ordering::SeqCst) {
|
||||
println!("KOMOREBI_CONFIG_HOME detected: {home_lossy_string}\n");
|
||||
println!("KOMOREBI_CONFIG_HOME detected: {home_display}\n");
|
||||
} else {
|
||||
println!(
|
||||
"No KOMOREBI_CONFIG_HOME detected, defaulting to {}\n",
|
||||
@@ -1146,19 +1247,25 @@ fn main() -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
println!("Looking for configuration files in {home_lossy_string}\n");
|
||||
println!("Looking for configuration files in {home_display}\n");
|
||||
|
||||
let mut config_pwsh = home.clone();
|
||||
config_pwsh.push("komorebi.ps1");
|
||||
let static_config = HOME_DIR.join("komorebi.json");
|
||||
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
||||
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
||||
let config_whkd = dirs::home_dir()
|
||||
.expect("no home dir found")
|
||||
.join(".config")
|
||||
.join("whkdrc");
|
||||
|
||||
let mut config_ahk = home.clone();
|
||||
config_ahk.push("komorebi.ahk");
|
||||
|
||||
if config_pwsh.exists() {
|
||||
if static_config.exists() {
|
||||
println!("Found komorebi.json; this file can be passed to the start command with the --config flag\n");
|
||||
if config_whkd.exists() {
|
||||
println!("Found ~/.config/whkdrc; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n");
|
||||
} else {
|
||||
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
|
||||
}
|
||||
} else if config_pwsh.exists() {
|
||||
println!("Found komorebi.ps1; this file will be autoloaded by komorebi\n");
|
||||
let mut config_whkd = dirs::home_dir().expect("could not find home dir");
|
||||
config_whkd.push(".config");
|
||||
config_whkd.push("whkdrc");
|
||||
if config_whkd.exists() {
|
||||
println!("Found ~/.config/whkdrc; key bindings will be loaded from here when whkd is started\n");
|
||||
} else {
|
||||
@@ -1167,13 +1274,12 @@ fn main() -> Result<()> {
|
||||
} else if config_ahk.exists() {
|
||||
println!("Found komorebi.ahk; this file will be autoloaded by komorebi\n");
|
||||
} else {
|
||||
println!("No komorebi configuration found in {home_lossy_string}\n");
|
||||
println!("No komorebi configuration found in {home_display}\n");
|
||||
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
|
||||
}
|
||||
}
|
||||
SubCommand::AhkLibrary => {
|
||||
let mut library = HOME_DIR.clone();
|
||||
library.push("komorebic.lib.ahk");
|
||||
let library = HOME_DIR.join("komorebic.lib.ahk");
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
@@ -1181,15 +1287,16 @@ fn main() -> Result<()> {
|
||||
.open(library.clone())?;
|
||||
|
||||
let output: String = SubCommand::generate_ahk_library();
|
||||
let fixed_output = output.replace("%id%", "\"%id%\"");
|
||||
let fixed_id = output.replace("%id%", "\"%id%\"");
|
||||
let fixed_stop_def = fixed_id.replace("Stop(whkd)", "Stop()");
|
||||
let fixed_output =
|
||||
fixed_stop_def.replace("komorebic.exe stop --whkd %whkd%", "komorebic.exe stop");
|
||||
|
||||
file.write_all(fixed_output.as_bytes())?;
|
||||
|
||||
println!(
|
||||
"\nAHKv1 helper library for komorebic written to {}",
|
||||
library.to_str().ok_or_else(|| anyhow!(
|
||||
"could not find the path to the generated ahk lib file"
|
||||
))?
|
||||
library.to_string_lossy()
|
||||
);
|
||||
|
||||
println!("\nYou can convert this file to AHKv2 syntax using https://github.com/mmikeww/AHK-v2-script-converter");
|
||||
@@ -1201,8 +1308,7 @@ fn main() -> Result<()> {
|
||||
println!("\n#Include komorebic.lib.ahk");
|
||||
}
|
||||
SubCommand::Log => {
|
||||
let mut color_log = std::env::temp_dir();
|
||||
color_log.push("komorebi.log");
|
||||
let color_log = std::env::temp_dir().join("komorebi.log");
|
||||
let file = TailedFile::new(File::open(color_log)?);
|
||||
let locked = file.lock();
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
@@ -1355,6 +1461,12 @@ fn main() -> Result<()> {
|
||||
&SocketMessage::NamedWorkspacePadding(arg.workspace, arg.size).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::FocusedWorkspacePadding(arg) => {
|
||||
send_message(&SocketMessage::FocusedWorkspacePadding(arg.size).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::FocusedWorkspaceContainerPadding(arg) => {
|
||||
send_message(&SocketMessage::FocusedWorkspaceContainerPadding(arg.size).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::AdjustWorkspacePadding(arg) => {
|
||||
send_message(
|
||||
&SocketMessage::AdjustWorkspacePadding(arg.sizing, arg.adjustment).as_bytes()?,
|
||||
@@ -1396,7 +1508,7 @@ fn main() -> Result<()> {
|
||||
&SocketMessage::WorkspaceLayoutCustom(
|
||||
arg.monitor,
|
||||
arg.workspace,
|
||||
resolve_windows_path(&arg.path)?,
|
||||
resolve_home_path(arg.path)?,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
@@ -1405,7 +1517,7 @@ fn main() -> Result<()> {
|
||||
send_message(
|
||||
&SocketMessage::NamedWorkspaceLayoutCustom(
|
||||
arg.workspace,
|
||||
resolve_windows_path(&arg.path)?,
|
||||
resolve_home_path(arg.path)?,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
@@ -1437,7 +1549,7 @@ fn main() -> Result<()> {
|
||||
arg.monitor,
|
||||
arg.workspace,
|
||||
arg.at_container_count,
|
||||
resolve_windows_path(&arg.path)?,
|
||||
resolve_home_path(arg.path)?,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
@@ -1447,7 +1559,7 @@ fn main() -> Result<()> {
|
||||
&SocketMessage::NamedWorkspaceLayoutCustomRule(
|
||||
arg.workspace,
|
||||
arg.at_container_count,
|
||||
resolve_windows_path(&arg.path)?,
|
||||
resolve_home_path(arg.path)?,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
@@ -1483,11 +1595,11 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if arg.whkd && which("whkd").is_err() {
|
||||
return Err(anyhow!("could not find whkd, please make sure it is installed before using the --whkd flag"));
|
||||
bail!("could not find whkd, please make sure it is installed before using the --whkd flag");
|
||||
}
|
||||
|
||||
if arg.ahk && which(&ahk).is_err() {
|
||||
return Err(anyhow!("could not find autohotkey, please make sure it is installed before using the --ahk flag"));
|
||||
bail!("could not find autohotkey, please make sure it is installed before using the --ahk flag");
|
||||
}
|
||||
|
||||
let mut buf: PathBuf;
|
||||
@@ -1504,7 +1616,7 @@ fn main() -> Result<()> {
|
||||
buf.pop(); // %USERPROFILE%\scoop\shims
|
||||
buf.pop(); // %USERPROFILE%\scoop
|
||||
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
|
||||
Option::from(buf.to_str().ok_or_else(|| {
|
||||
Some(buf.to_str().ok_or_else(|| {
|
||||
anyhow!("cannot create a string from the scoop komorebi path")
|
||||
})?)
|
||||
}
|
||||
@@ -1515,17 +1627,13 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut flags = vec![];
|
||||
if let Some(config) = arg.config {
|
||||
let path = resolve_windows_path(config.as_os_str().to_str().unwrap())?;
|
||||
let path = resolve_home_path(config)?;
|
||||
if !path.is_file() {
|
||||
return Err(anyhow!("could not find file: {}", path.to_string_lossy()));
|
||||
bail!("could not find file: {}", path.display());
|
||||
}
|
||||
|
||||
flags.push(format!(
|
||||
"'--config=\"{}\"'",
|
||||
path.as_os_str()
|
||||
.to_string_lossy()
|
||||
.trim_start_matches(r"\\?\"),
|
||||
));
|
||||
// we don't need to replace UNC prefix here as `resolve_home_path` already did
|
||||
flags.push(format!("'--config=\"{}\"'", path.display()));
|
||||
}
|
||||
|
||||
if arg.ffm {
|
||||
@@ -1580,12 +1688,12 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if arg.whkd {
|
||||
let script = r#"
|
||||
let script = r"
|
||||
if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
||||
{
|
||||
Start-Process whkd -WindowStyle hidden
|
||||
}
|
||||
"#;
|
||||
";
|
||||
match powershell_script::run(script) {
|
||||
Ok(_) => {
|
||||
println!("{script}");
|
||||
@@ -1597,15 +1705,14 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
||||
}
|
||||
|
||||
if arg.ahk {
|
||||
let home = HOME_DIR.clone();
|
||||
let mut config_ahk = home;
|
||||
config_ahk.push("komorebi.ahk");
|
||||
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
||||
let config_ahk = dunce::simplified(&config_ahk);
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
Start-Process {ahk} {config} -WindowStyle hidden
|
||||
"#,
|
||||
config = config_ahk.as_os_str().to_string_lossy()
|
||||
config = config_ahk.display()
|
||||
);
|
||||
|
||||
match powershell_script::run(&script) {
|
||||
@@ -1620,9 +1727,9 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
||||
}
|
||||
SubCommand::Stop(arg) => {
|
||||
if arg.whkd {
|
||||
let script = r#"
|
||||
let script = r"
|
||||
Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
"#;
|
||||
";
|
||||
match powershell_script::run(script) {
|
||||
Ok(_) => {
|
||||
println!("{script}");
|
||||
@@ -1687,7 +1794,7 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
}
|
||||
SubCommand::LoadCustomLayout(arg) => {
|
||||
send_message(
|
||||
&SocketMessage::ChangeLayoutCustom(resolve_windows_path(&arg.path)?).as_bytes()?,
|
||||
&SocketMessage::ChangeLayoutCustom(resolve_home_path(arg.path)?).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::FlipLayout(arg) => {
|
||||
@@ -1753,74 +1860,12 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
)?;
|
||||
}
|
||||
SubCommand::State => {
|
||||
let home = DATA_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.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)?;
|
||||
|
||||
send_message(&SocketMessage::State.as_bytes()?)?;
|
||||
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
with_komorebic_socket(|| send_message(&SocketMessage::State.as_bytes()?))?;
|
||||
}
|
||||
SubCommand::Query(arg) => {
|
||||
let home = DATA_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.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)?;
|
||||
|
||||
send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)?;
|
||||
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
with_komorebic_socket(|| {
|
||||
send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)
|
||||
})?;
|
||||
}
|
||||
SubCommand::RestoreWindows => {
|
||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||
@@ -1884,9 +1929,7 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
match target.identifier {
|
||||
ApplicationIdentifier::Exe => {}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"this command requires applications to be identified by their exe"
|
||||
));
|
||||
bail!("this command requires applications to be identified by their exe");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1908,10 +1951,10 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
send_message(&SocketMessage::QuickLoad.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::SaveResize(arg) => {
|
||||
send_message(&SocketMessage::Save(resolve_windows_path(&arg.path)?).as_bytes()?)?;
|
||||
send_message(&SocketMessage::Save(resolve_home_path(arg.path)?).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::LoadResize(arg) => {
|
||||
send_message(&SocketMessage::Load(resolve_windows_path(&arg.path)?).as_bytes()?)?;
|
||||
send_message(&SocketMessage::Load(resolve_home_path(arg.path)?).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::Subscribe(arg) => {
|
||||
send_message(&SocketMessage::AddSubscriber(arg.named_pipe).as_bytes()?)?;
|
||||
@@ -1964,10 +2007,9 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
)?;
|
||||
}
|
||||
SubCommand::AhkAppSpecificConfiguration(arg) => {
|
||||
let content = std::fs::read_to_string(resolve_windows_path(&arg.path)?)?;
|
||||
let content = std::fs::read_to_string(resolve_home_path(arg.path)?)?;
|
||||
let lines = if let Some(override_path) = arg.override_path {
|
||||
let override_content =
|
||||
std::fs::read_to_string(resolve_windows_path(&override_path)?)?;
|
||||
let override_content = std::fs::read_to_string(resolve_home_path(override_path)?)?;
|
||||
|
||||
ApplicationConfigurationGenerator::generate_ahk(
|
||||
&content,
|
||||
@@ -1977,28 +2019,24 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
ApplicationConfigurationGenerator::generate_ahk(&content, None)?
|
||||
};
|
||||
|
||||
let mut generated_config = HOME_DIR.clone();
|
||||
generated_config.push("komorebi.generated.ahk");
|
||||
let generated_config = HOME_DIR.join("komorebi.generated.ahk");
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(generated_config.clone())?;
|
||||
.open(&generated_config)?;
|
||||
|
||||
file.write_all(lines.join("\n").as_bytes())?;
|
||||
|
||||
println!(
|
||||
"\nApplication-specific generated configuration written to {}",
|
||||
generated_config.to_str().ok_or_else(|| anyhow!(
|
||||
"could not find the path to the generated configuration file"
|
||||
))?
|
||||
generated_config.display()
|
||||
);
|
||||
}
|
||||
SubCommand::PwshAppSpecificConfiguration(arg) => {
|
||||
let content = std::fs::read_to_string(resolve_windows_path(&arg.path)?)?;
|
||||
let content = std::fs::read_to_string(resolve_home_path(arg.path)?)?;
|
||||
let lines = if let Some(override_path) = arg.override_path {
|
||||
let override_content =
|
||||
std::fs::read_to_string(resolve_windows_path(&override_path)?)?;
|
||||
let override_content = std::fs::read_to_string(resolve_home_path(override_path)?)?;
|
||||
|
||||
ApplicationConfigurationGenerator::generate_pwsh(
|
||||
&content,
|
||||
@@ -2008,25 +2046,22 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
ApplicationConfigurationGenerator::generate_pwsh(&content, None)?
|
||||
};
|
||||
|
||||
let mut generated_config = HOME_DIR.clone();
|
||||
generated_config.push("komorebi.generated.ps1");
|
||||
let generated_config = HOME_DIR.join("komorebi.generated.ps1");
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(generated_config.clone())?;
|
||||
.open(&generated_config)?;
|
||||
|
||||
file.write_all(lines.join("\n").as_bytes())?;
|
||||
|
||||
println!(
|
||||
"\nApplication-specific generated configuration written to {}",
|
||||
generated_config.to_str().ok_or_else(|| anyhow!(
|
||||
"could not find the path to the generated configuration file"
|
||||
))?
|
||||
generated_config.display()
|
||||
);
|
||||
}
|
||||
SubCommand::FormatAppSpecificConfiguration(arg) => {
|
||||
let file_path = resolve_windows_path(&arg.path)?;
|
||||
let file_path = resolve_home_path(arg.path)?;
|
||||
let content = std::fs::read_to_string(&file_path)?;
|
||||
let formatted_content = ApplicationConfigurationGenerator::format(&content)?;
|
||||
|
||||
@@ -2044,8 +2079,7 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
let content = reqwest::blocking::get("https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml")?
|
||||
.text()?;
|
||||
|
||||
let mut output_file = HOME_DIR.clone();
|
||||
output_file.push("applications.yaml");
|
||||
let output_file = HOME_DIR.join("applications.yaml");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
@@ -2055,189 +2089,31 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
||||
|
||||
file.write_all(content.as_bytes())?;
|
||||
|
||||
let output_path = output_file.to_str().unwrap().to_string();
|
||||
let output_path = output_path.replace('\\', "/");
|
||||
|
||||
println!("Latest version of applications.yaml from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n");
|
||||
println!(
|
||||
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{output_path}\"",
|
||||
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
|
||||
output_file.display()
|
||||
);
|
||||
}
|
||||
SubCommand::NotificationSchema => {
|
||||
let home = DATA_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.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());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&SocketMessage::NotificationSchema.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
with_komorebic_socket(|| send_message(&SocketMessage::NotificationSchema.as_bytes()?))?;
|
||||
}
|
||||
SubCommand::SocketSchema => {
|
||||
let home = DATA_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.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());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&SocketMessage::SocketSchema.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
with_komorebic_socket(|| send_message(&SocketMessage::SocketSchema.as_bytes()?))?;
|
||||
}
|
||||
SubCommand::StaticConfigSchema => {
|
||||
let home = DATA_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.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());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&SocketMessage::StaticConfigSchema.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
with_komorebic_socket(|| send_message(&SocketMessage::StaticConfigSchema.as_bytes()?))?;
|
||||
}
|
||||
SubCommand::GenerateStaticConfig => {
|
||||
let home = DATA_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.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());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&SocketMessage::GenerateStaticConfig.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
with_komorebic_socket(|| {
|
||||
send_message(&SocketMessage::GenerateStaticConfig.as_bytes()?)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_windows_path(raw_path: &str) -> Result<PathBuf> {
|
||||
let path = if raw_path.starts_with('~') {
|
||||
raw_path.replacen(
|
||||
'~',
|
||||
&dirs::home_dir()
|
||||
.ok_or_else(|| anyhow!("there is no home directory"))?
|
||||
.display()
|
||||
.to_string(),
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
raw_path.to_string()
|
||||
};
|
||||
|
||||
let full_path = PathBuf::from(path);
|
||||
|
||||
let parent = full_path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("cannot parse directory"))?;
|
||||
|
||||
Ok(if parent.is_dir() {
|
||||
let file = full_path
|
||||
.components()
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("cannot parse filename"))?;
|
||||
|
||||
let mut canonicalized = std::fs::canonicalize(parent)?;
|
||||
canonicalized.push(file);
|
||||
canonicalized
|
||||
} else {
|
||||
full_path
|
||||
})
|
||||
}
|
||||
|
||||
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
|
||||
// BOOL is returned but does not signify whether or not the operation was succesful
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
||||
|
||||
Reference in New Issue
Block a user