Compare commits

...

28 Commits

Author SHA1 Message Date
LGUG2Z
c83ea24666 fix(wm): store all logs in tempdir files 2024-01-19 09:59:21 -08:00
KamQb
21a5be0404 fix(component): fix ahk invocation by adding quotes around arguments
Fixes ahk launch uf user has spaces in arguments for ahk (ahk exe path or komorebi home directory)
2024-01-14 20:30:02 -08:00
LGUG2Z
7cb2965969 feat(cli): add asc path validation to check cmd 2024-01-14 15:41:07 -08:00
LGUG2Z
d6e83e1778 feat(cli): add last focused workspace cmd
This commit adds a new komorebic command, focus-last-workspace, which
switches to the last focused workspace on the focused monitor (if there
is one).
2024-01-11 18:51:41 -08:00
dependabot[bot]
8f30612220 chore(deps): bump sysinfo from 0.29.11 to 0.30.5
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.11 to 0.30.5.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 11:51:29 -08:00
LGUG2Z
f3e41490b2 fix(cli): typo in autostart generation 2024-01-08 11:09:17 -08:00
LGUG2Z
5a6dcef7ea fix(wm): update border colour on ws change
This commit ensures that the border colour is set appropriately for
monocle and stack containers after workspace switching operations.

fix #621
2024-01-08 10:22:13 -08:00
dependabot[bot]
0e96cd65b8 chore(deps): bump quote from 1.0.33 to 1.0.35
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.33 to 1.0.35.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.33...1.0.35)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 10:15:53 -08:00
LGUG2Z
025cb08b3e fix(wm): identify monitor dpi correctly
This commit uses a more reliable Win32 API call to determine the scaling
factor / DPI of user monitors, so that the scale of a window can be
correctly adjusted when moved between monitors with different DPI
settings.

fix #622
2024-01-06 14:30:11 -08:00
LGUG2Z
f9c69e51aa chore(funding): add kofi for paypal sponsors 2024-01-03 11:27:28 -08:00
dependabot[bot]
8a7c75b9a7 chore(deps): bump ctrlc from 3.4.1 to 3.4.2
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.1...3.4.2)

---
updated-dependencies:
- dependency-name: ctrlc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-03 09:25:24 -08:00
dependabot[bot]
ed3b053323 chore(deps): bump crossbeam-channel from 0.5.8 to 0.5.10
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.8 to 0.5.10.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.8...crossbeam-channel-0.5.10)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-03 09:25:15 -08:00
LGUG2Z
a1a7e6c2bf docs(readme): add tech for palestine badge 2024-01-02 13:35:11 -08:00
LGUG2Z
b4ae043b9c feat(cli): add visible-windows cmd
This commit adds a new komorebic command, "visible-windows", to make
tracking down ghost windows easier. The returned JSON structure will try
to use the device id to identify a monitor if it is available, or fall
back to the monitor index. Thanks to raggi on Discord for suggesting
this command!
2024-01-02 10:37:22 -08:00
dependabot[bot]
d2a06a11ac chore(deps): bump actions/upload-artifact from 3 to 4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-31 19:19:34 -08:00
dependabot[bot]
e6bf30b567 chore(deps): bump serde_yaml from 0.9.27 to 0.9.29
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.27 to 0.9.29.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.27...0.9.29)

---
updated-dependencies:
- dependency-name: serde_yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-31 19:19:25 -08:00
dependabot[bot]
326d5bae42 chore(deps): bump proc-macro2 from 1.0.70 to 1.0.71
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.70 to 1.0.71.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.70...1.0.71)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-31 19:19:17 -08:00
dependabot[bot]
d23e3e7c51 chore(deps): bump crossbeam-utils from 0.8.17 to 0.8.18
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.17 to 0.8.18.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.17...crossbeam-utils-0.8.18)

---
updated-dependencies:
- dependency-name: crossbeam-utils
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-31 19:19:10 -08:00
dependabot[bot]
0eeba6cd0e chore(deps): bump uds_windows from 1.0.2 to 1.1.0
Bumps [uds_windows](https://github.com/haraldh/rust_uds_windows) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/haraldh/rust_uds_windows/releases)
- [Changelog](https://github.com/haraldh/rust_uds_windows/blob/v1.1.0/CHANGELOG.md)
- [Commits](https://github.com/haraldh/rust_uds_windows/compare/v1.0.2...v1.1.0)

---
updated-dependencies:
- dependency-name: uds_windows
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-24 15:00:20 -08:00
LGUG2Z
cf86b2cf98 refactor(windows-rs): use "wide" fns uniformly
This commit builds on @EBNull's comment shedding light on the uses of
the -A and -W functions in the Win32 API, and standardizes the calling
of the -W functions across the project.

Since UTF-16 String handling is a bit lacking in the Rust standard
library, I have pulled in the widestring crate to use the
from_slice_truncate fn to automagically remove all of the trailing null
chars when handling values returned from the various Win32 -W fns.

Comment ref: 657ac441ae (r135643553)
2023-12-23 16:46:30 -08:00
LGUG2Z
e221d96785 fix(wm): handle monitor index pref edge case
This commit handles an edge case where the first registered display
monitor has an index preference that is greater than the current length
of the Ring data structure storing the monitors.

re #612
2023-12-22 18:00:02 -08:00
LGUG2Z
d3bc78097c fix(wm): pass *const u8 to enum_display_devices
Thanks to @ids1024 for pointing out that the failing system calls were
likely due to optimizations being made with the release profile's
opt-level=3 and to @saethlin for pointing out that in the previous
commit I was returning a pointer to a temporary that was about to be
deallocated.

https://fosstodon.org/@ids1024/111627094548141620
https://hachyderm.io/@saethlin/111627135615930244

With this commit, the display ids are now successfully returned from
calls to EnumDisplayDevicesA on release builds.
2023-12-22 17:44:44 -08:00
LGUG2Z
657ac441ae feat(wm): use device id for monitor index pref
This commit begins to build on some of the knowledge shared by EBNull in
allowing users to specify monitor index preferences using physical
device identifiers. This does not presently go all the way to EDIDs, but
the display model and what I believe is a port identifier on the display
adapter(s) can be used to uniquely identify a display in most use cases.

However, I believe I may have unfortunately run into a bug in either
windows-rs or Rust itself, as when the code calling EnumDisplayDevices
is called, it always fails when running a release build, and always
succeeds when running a debug build. This needs to be investigated
further.

re #612
2023-12-22 17:01:59 -08:00
dependabot[bot]
0696a00bd3 chore(deps): bump unsafe-libyaml from 0.2.9 to 0.2.10
Bumps [unsafe-libyaml](https://github.com/dtolnay/unsafe-libyaml) from 0.2.9 to 0.2.10.
- [Release notes](https://github.com/dtolnay/unsafe-libyaml/releases)
- [Commits](https://github.com/dtolnay/unsafe-libyaml/compare/0.2.9...0.2.10)

---
updated-dependencies:
- dependency-name: unsafe-libyaml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 23:26:46 -05:00
dependabot[bot]
5111dbdfb9 chore(deps): bump crossbeam-utils from 0.8.16 to 0.8.17
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.16 to 0.8.17.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.16...crossbeam-utils-0.8.17)

---
updated-dependencies:
- dependency-name: crossbeam-utils
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 23:26:34 -05:00
dependabot[bot]
dd12f0fc8a chore(deps): bump thiserror from 1.0.50 to 1.0.51
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.50 to 1.0.51.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.50...1.0.51)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 23:26:20 -05:00
dependabot[bot]
76a8695218 chore(deps): bump clap from 4.4.10 to 4.4.11
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.10 to 4.4.11.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.10...v4.4.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-12 15:49:57 -08:00
LGUG2Z
ddfcf8b76f feat(config): add rudimentary config syntax checks
This commit attempts to introduce miette to provide users with quick
feedback when there may be syntax errors such as trailing commas in
their komorebi.json configuration file.

The lines and columns reported by serde_json don't actually line up with
the visualization of where we want to indicate a syntax error on the
miette Report. Some hackery has been done, but this should be improved
upon. Notably, this hackery does not accurately reflect the location of
a syntax error when the syntax error is a missing comma after a string
value.
2023-12-03 12:56:40 -08:00
20 changed files with 596 additions and 106 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: LGUG2Z
ko_fi: lgug2z

View File

@@ -81,7 +81,7 @@ jobs:
cargo install cargo-wix
cargo wix -p komorebi --nocapture -I .\wix\main.wxs --target x86_64-pc-windows-msvc
- name: Upload the built artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: komorebi-${{ matrix.target }}
path: |

197
Cargo.lock generated
View File

@@ -95,6 +95,15 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "backtrace-ext"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
dependencies = [
"backtrace",
]
[[package]]
name = "base64"
version = "0.21.5"
@@ -148,9 +157,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.10"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
dependencies = [
"clap_builder",
"clap_derive",
@@ -158,15 +167,15 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.9"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
"terminal_size 0.3.0",
]
[[package]]
@@ -238,9 +247,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@@ -272,21 +281,21 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "ctrlc"
version = "3.4.1"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf"
checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b"
dependencies = [
"nix",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -744,6 +753,23 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "is_ci"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
name = "itoa"
version = "1.0.9"
@@ -801,6 +827,7 @@ dependencies = [
"tracing-subscriber",
"uds_windows",
"which",
"widestring",
"windows",
"windows-implement",
"windows-interface",
@@ -837,6 +864,7 @@ dependencies = [
"heck",
"komorebi-core",
"lazy_static",
"miette",
"paste",
"powershell_script",
"reqwest",
@@ -844,6 +872,7 @@ dependencies = [
"serde_json",
"serde_yaml",
"sysinfo",
"thiserror",
"uds_windows",
"which",
"windows",
@@ -928,6 +957,38 @@ dependencies = [
"autocfg",
]
[[package]]
name = "miette"
version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
dependencies = [
"backtrace",
"backtrace-ext",
"is-terminal",
"miette-derive",
"once_cell",
"owo-colors",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"terminal_size 0.1.17",
"textwrap",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "mime"
version = "0.3.17"
@@ -1300,18 +1361,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@@ -1635,9 +1696,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.27"
version = "0.9.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129"
dependencies = [
"indexmap",
"itoa",
@@ -1670,6 +1731,12 @@ version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "smawk"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
[[package]]
name = "socket2"
version = "0.4.10"
@@ -1718,6 +1785,34 @@ dependencies = [
"syn 2.0.39",
]
[[package]]
name = "supports-color"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
dependencies = [
"is-terminal",
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
dependencies = [
"is-terminal",
]
[[package]]
name = "supports-unicode"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
dependencies = [
"is-terminal",
]
[[package]]
name = "syn"
version = "1.0.109"
@@ -1742,9 +1837,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.29.11"
version = "0.30.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666"
checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
@@ -1752,7 +1847,7 @@ dependencies = [
"ntapi",
"once_cell",
"rayon",
"winapi 0.3.9",
"windows",
]
[[package]]
@@ -1789,6 +1884,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
@@ -1800,19 +1905,30 @@ dependencies = [
]
[[package]]
name = "thiserror"
version = "1.0.50"
name = "textwrap"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
dependencies = [
"proc-macro2",
"quote",
@@ -2020,10 +2136,11 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "uds_windows"
version = "1.0.2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d"
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
dependencies = [
"memoffset",
"tempfile",
"winapi 0.3.9",
]
@@ -2040,6 +2157,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-linebreak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
@@ -2050,10 +2173,16 @@ dependencies = [
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.9"
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "unsafe-libyaml"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
[[package]]
name = "url"
@@ -2204,6 +2333,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "winapi"
version = "0.2.8"

View File

@@ -3,6 +3,9 @@
Tiling Window Management for Windows.
<p>
<a href="https://techforpalestine.org/learn-more">
<img alt="Tech for Palestine" src="https://badge.techforpalestine.org/default">
</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">

View File

@@ -28,7 +28,7 @@ install:
just install-target komorebi
run:
just install-komorebic
just install-target komorebic
cargo +stable run --bin komorebi --locked
warn $RUST_LOG="warn":

View File

@@ -84,6 +84,7 @@ pub enum SocketMessage {
FlipLayout(Axis),
// Monitor and Workspace Commands
MonitorIndexPreference(usize, i32, i32, i32, i32),
DisplayIndexPreference(usize, String),
EnsureWorkspaces(usize, usize),
EnsureNamedWorkspaces(usize, Vec<String>),
NewWorkspace,
@@ -98,6 +99,7 @@ pub enum SocketMessage {
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusLastWorkspace,
FocusWorkspaceNumber(usize),
FocusWorkspaceNumbers(usize),
FocusMonitorWorkspaceNumber(usize, usize),
@@ -146,6 +148,7 @@ pub enum SocketMessage {
IdentifyLayeredApplication(ApplicationIdentifier, String),
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
State,
VisibleWindows,
Query(StateQuery),
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),

View File

@@ -32,7 +32,7 @@ schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.25", features = ["derive"] }
sysinfo = "0.29"
sysinfo = "0.30"
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
@@ -45,6 +45,7 @@ windows-implement = { workspace = true }
windows = { workspace = true }
color-eyre = { workspace = true }
dirs = { workspace = true }
widestring = "1"
[features]
deadlock_detection = []

View File

@@ -2,15 +2,15 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use color_eyre::Result;
use windows::core::PCSTR;
use windows::core::PCWSTR;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA;
use windows::Win32::UI::WindowsAndMessaging::FindWindowA;
use windows::Win32::UI::WindowsAndMessaging::GetMessageA;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::FindWindowW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use komorebi_core::Rect;
@@ -43,11 +43,11 @@ impl Border {
}
pub fn create(name: &str) -> Result<()> {
let name = format!("{name}\0");
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
let instance = WindowsApi::module_handle_w()?;
let class_name = PCSTR(name.as_ptr());
let class_name = PCWSTR(name.as_ptr());
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
let window_class = WNDCLASSA {
let window_class = WNDCLASSW {
hInstance: instance.into(),
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
@@ -56,18 +56,18 @@ impl Border {
..Default::default()
};
let _atom = WindowsApi::register_class_a(&window_class)?;
let _atom = WindowsApi::register_class_w(&window_class)?;
let name_cl = name.clone();
std::thread::spawn(move || -> Result<()> {
let hwnd = WindowsApi::create_border_window(PCSTR(name_cl.as_ptr()), instance)?;
let hwnd = WindowsApi::create_border_window(PCWSTR(name_cl.as_ptr()), instance)?;
let border = Self::from(hwnd);
let mut message = MSG::default();
unsafe {
while GetMessageA(&mut message, border.hwnd(), 0, 0).into() {
DispatchMessageA(&message);
while GetMessageW(&mut message, border.hwnd(), 0, 0).into() {
DispatchMessageW(&message);
std::thread::sleep(Duration::from_millis(10));
}
}
@@ -77,7 +77,7 @@ impl Border {
let mut hwnd = HWND(0);
while hwnd == HWND(0) {
hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) };
hwnd = unsafe { FindWindowW(PCWSTR(name.as_ptr()), PCWSTR::null()) };
}
BORDER_HWND.store(hwnd.0, Ordering::SeqCst);

View File

@@ -2,15 +2,15 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use color_eyre::Result;
use windows::core::PCSTR;
use windows::core::PCWSTR;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA;
use windows::Win32::UI::WindowsAndMessaging::FindWindowA;
use windows::Win32::UI::WindowsAndMessaging::GetMessageA;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::FindWindowW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use crate::windows_callbacks;
use crate::WindowsApi;
@@ -34,11 +34,11 @@ impl Hidden {
}
pub fn create(name: &str) -> Result<()> {
let name = format!("{name}\0");
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
let instance = WindowsApi::module_handle_w()?;
let class_name = PCSTR(name.as_ptr());
let class_name = PCWSTR(name.as_ptr());
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
let window_class = WNDCLASSA {
let window_class = WNDCLASSW {
hInstance: instance.into(),
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
@@ -47,18 +47,18 @@ impl Hidden {
..Default::default()
};
let _atom = WindowsApi::register_class_a(&window_class)?;
let _atom = WindowsApi::register_class_w(&window_class)?;
let name_cl = name.clone();
std::thread::spawn(move || -> Result<()> {
let hwnd = WindowsApi::create_hidden_window(PCSTR(name_cl.as_ptr()), instance)?;
let hwnd = WindowsApi::create_hidden_window(PCWSTR(name_cl.as_ptr()), instance)?;
let hidden = Self::from(hwnd);
let mut message = MSG::default();
unsafe {
while GetMessageA(&mut message, hidden.hwnd(), 0, 0).into() {
DispatchMessageA(&message);
while GetMessageW(&mut message, hidden.hwnd(), 0, 0).into() {
DispatchMessageW(&message);
std::thread::sleep(Duration::from_millis(10));
}
}
@@ -68,7 +68,7 @@ impl Hidden {
let mut hwnd = HWND(0);
while hwnd == HWND(0) {
hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) };
hwnd = unsafe { FindWindowW(PCWSTR(name.as_ptr()), PCWSTR::null()) };
}
HIDDEN_HWND.store(hwnd.0, Ordering::SeqCst);

View File

@@ -35,8 +35,6 @@ use regex::Regex;
use schemars::JsonSchema;
use serde::Serialize;
use sysinfo::Process;
use sysinfo::ProcessExt;
use sysinfo::SystemExt;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
@@ -143,6 +141,8 @@ lazy_static! {
]));
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref DISPLAY_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, String>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, WorkspaceRule>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
@@ -251,7 +251,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
std::env::set_var("RUST_LOG", "info");
}
let appender = tracing_appender::rolling::never(&*DATA_DIR, "komorebi.log");
let appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi_plaintext.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);
@@ -482,8 +482,10 @@ fn main() -> Result<()> {
if matched_procs.len() > 1 {
let mut len = matched_procs.len();
for proc in matched_procs {
if proc.root().ends_with("shims") {
len -= 1;
if let Some(root) = proc.root() {
if root.ends_with("shims") {
len -= 1;
}
}
}

View File

@@ -24,6 +24,10 @@ pub struct Monitor {
#[getset(get = "pub", set = "pub")]
name: String,
#[getset(get = "pub", set = "pub")]
device: Option<String>,
#[getset(get = "pub", set = "pub")]
device_id: Option<String>,
#[getset(get = "pub", set = "pub")]
size: Rect,
#[getset(get = "pub", set = "pub")]
work_area_size: Rect,
@@ -31,6 +35,9 @@ pub struct Monitor {
work_area_offset: Option<Rect>,
workspaces: Ring<Workspace>,
#[serde(skip_serializing)]
#[getset(get_copy = "pub", set = "pub")]
last_focused_workspace: Option<usize>,
#[serde(skip_serializing)]
#[getset(get_mut = "pub")]
workspace_names: HashMap<usize, String>,
}
@@ -44,10 +51,13 @@ pub fn new(id: isize, size: Rect, work_area_size: Rect, name: String) -> Monitor
Monitor {
id,
name,
device: None,
device_id: None,
size,
work_area_size,
work_area_offset: None,
workspaces,
last_focused_workspace: None,
workspace_names: HashMap::default(),
}
}

View File

@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
@@ -59,6 +60,7 @@ use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::BORDER_WIDTH;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::INITIAL_CONFIGURATION_LOADED;
@@ -170,6 +172,23 @@ impl WindowManager {
_ => {}
};
match message {
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
if let Some(monitor) = self.focused_monitor_mut() {
let idx = monitor.focused_workspace_idx();
monitor.set_last_focused_workspace(Option::from(idx));
}
}
SocketMessage::FocusMonitorWorkspaceNumber(target_monitor_idx, _) => {
let idx = self.focused_workspace_idx_for_monitor_idx(target_monitor_idx)?;
if let Some(monitor) = self.monitors_mut().get_mut(target_monitor_idx) {
monitor.set_last_focused_workspace(Option::from(idx));
}
}
_ => {}
};
match message {
SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
@@ -596,6 +615,33 @@ impl WindowManager {
self.show_border()?;
};
}
SocketMessage::FocusLastWorkspace => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
let idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_idx();
if let Some(monitor) = self.focused_monitor_mut() {
if let Some(last_focused_workspace) = monitor.last_focused_workspace() {
self.focus_workspace(last_focused_workspace)?;
}
}
self.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?
.set_last_focused_workspace(Option::from(idx));
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
@@ -673,6 +719,10 @@ impl WindowManager {
},
);
}
SocketMessage::DisplayIndexPreference(index_preference, ref display) => {
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.insert(index_preference, display.clone());
}
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {
self.ensure_workspaces_for_monitor(monitor_idx, workspace_count)?;
}
@@ -696,6 +746,32 @@ impl WindowManager {
let mut stream = UnixStream::connect(socket)?;
stream.write_all(state.as_bytes())?;
}
SocketMessage::VisibleWindows => {
let mut monitor_visible_windows = HashMap::new();
for (index, monitor) in self.monitors().iter().enumerate() {
if let Some(ws) = monitor.focused_workspace() {
monitor_visible_windows.insert(
monitor
.device_id()
.clone()
.unwrap_or_else(|| format!("{index}")),
ws.visible_window_details().clone(),
);
}
}
let visible_windows_state =
match serde_json::to_string_pretty(&monitor_visible_windows) {
Ok(state) => state,
Err(error) => error.to_string(),
};
let socket = DATA_DIR.join("komorebic.sock");
let mut stream = UnixStream::connect(socket)?;
stream.write_all(visible_windows_state.as_bytes())?;
}
SocketMessage::Query(query) => {
let response = match query {
StateQuery::FocusedMonitorIndex => self.focused_monitor_idx(),
@@ -1293,13 +1369,32 @@ impl WindowManager {
| SocketMessage::InvisibleBorders(_)
| SocketMessage::WorkAreaOffset(_)
| SocketMessage::CycleMoveWindow(_)
| SocketMessage::MoveWindow(_) => {
| SocketMessage::MoveWindow(_)
| SocketMessage::CycleFocusMonitor(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusMonitorNumber(_)
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
| SocketMessage::FocusWorkspaceNumber(_) => {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
if monocle != 0 && self.focused_workspace()?.monocle_container().is_some() {
BORDER_COLOUR_CURRENT.store(
monocle,
Ordering::SeqCst,
);
}
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
if stack != 0 && self.focused_container()?.windows().len() > 1 {
BORDER_COLOUR_CURRENT
.store(stack, Ordering::SeqCst);
}
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(foreground_window, &self.invisible_borders, false)?;
}

View File

@@ -19,6 +19,7 @@ use crate::BORDER_WIDTH;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::LAYERED_WHITELIST;
@@ -312,6 +313,9 @@ pub struct StaticConfig {
/// Set monitor index preferences
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor_index_preferences: Option<HashMap<usize, Rect>>,
/// Set display index preferences
#[serde(skip_serializing_if = "Option::is_none")]
pub display_index_preferences: Option<HashMap<usize, String>>,
}
impl From<&WindowManager> for StaticConfig {
@@ -432,6 +436,7 @@ impl From<&WindowManager> for StaticConfig {
layered_applications: None,
object_name_change_applications: None,
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()),
}
}
}
@@ -444,6 +449,11 @@ impl StaticConfig {
*preferences = monitor_index_preferences.clone();
}
if let Some(display_index_preferences) = &self.display_index_preferences {
let mut preferences = DISPLAY_INDEX_PREFERENCES.lock();
*preferences = display_index_preferences.clone();
}
if let Some(behaviour) = self.window_hiding_behaviour {
let mut window_hiding_behaviour = HIDING_BEHAVIOUR.lock();
*window_hiding_behaviour = behaviour;

View File

@@ -6,6 +6,7 @@ use std::fmt::Formatter;
use std::fmt::Write as _;
use std::sync::atomic::Ordering;
use color_eyre::eyre;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use komorebi_core::config_generation::IdWithIdentifier;
@@ -46,6 +47,26 @@ pub struct Window {
pub(crate) hwnd: isize,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct WindowDetails {
pub title: String,
pub exe: String,
pub class: String,
}
impl TryFrom<Window> for WindowDetails {
type Error = eyre::ErrReport;
fn try_from(value: Window) -> std::result::Result<Self, Self::Error> {
Ok(Self {
title: value.title()?,
exe: value.exe()?,
class: value.class()?,
})
}
}
impl Display for Window {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut display = format!("(hwnd: {}", self.hwnd);

View File

@@ -49,6 +49,7 @@ use crate::workspace::Workspace;
use crate::BORDER_HWND;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::HOME_DIR;
use crate::LAYERED_WHITELIST;
@@ -103,6 +104,7 @@ pub struct State {
pub border_overflow_identifiers: Vec<IdWithIdentifier>,
pub name_change_on_launch_identifiers: Vec<IdWithIdentifier>,
pub monitor_index_preferences: HashMap<usize, Rect>,
pub display_index_preferences: HashMap<usize, String>,
}
impl AsRef<Self> for WindowManager {
@@ -132,6 +134,7 @@ impl From<&WindowManager> for State {
border_overflow_identifiers: BORDER_OVERFLOW_IDENTIFIERS.lock().clone(),
name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),
monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(),
display_index_preferences: DISPLAY_INDEX_PREFERENCES.lock().clone(),
}
}
}
@@ -1319,7 +1322,7 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
.id();
if !WindowsApi::monitors_have_same_scale_factor(a, b)? {
if !WindowsApi::monitors_have_same_dpi(a, b)? {
self.update_focused_workspace(self.mouse_follows_focus)?;
}
}
@@ -2195,6 +2198,14 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no workspace"))
}
pub fn focused_workspace_idx_for_monitor_idx(&self, idx: usize) -> Result<usize> {
Ok(self
.monitors()
.get(idx)
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
.focused_workspace_idx())
}
pub fn focused_workspace_for_monitor_idx(&self, idx: usize) -> Result<&Workspace> {
self.monitors()
.get(idx)

View File

@@ -1,15 +1,14 @@
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::ffi::c_void;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::Error;
use color_eyre::Result;
use widestring::U16CStr;
use windows::core::Result as WindowsCrateResult;
use windows::core::PCSTR;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::BOOL;
@@ -30,11 +29,13 @@ use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::EnumDisplayDevicesW;
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
use windows::Win32::Graphics::Gdi::DISPLAY_DEVICEW;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
@@ -51,8 +52,10 @@ use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
@@ -63,11 +66,9 @@ use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
use windows::Win32::UI::Shell::Common::DEVICE_SCALE_FACTOR;
use windows::Win32::UI::Shell::GetScaleFactorForMonitor;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExA;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
@@ -83,7 +84,7 @@ use windows::Win32::UI::WindowsAndMessaging::IsWindow;
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassA;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
@@ -93,6 +94,7 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
@@ -117,7 +119,7 @@ use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
@@ -164,8 +166,8 @@ impl_from_integer_for_windows_result!(usize, isize, u16, u32, i32);
impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
fn from(result: WindowsResult<T, E>) -> Self {
match result {
WindowsResult::Err(error) => Self::Err(error),
WindowsResult::Ok(ok) => Self::Ok(ok),
WindowsResult::Err(error) => Err(error),
WindowsResult::Ok(ok) => Ok(ok),
}
}
}
@@ -221,7 +223,7 @@ impl WindowsApi {
let mut monitors: Vec<(String, isize)> = vec![];
let monitors_ref: &mut Vec<(String, isize)> = monitors.as_mut();
Self::enum_display_monitors(
Option::Some(windows_callbacks::valid_display_monitors),
Some(windows_callbacks::valid_display_monitors),
monitors_ref as *mut Vec<(String, isize)> as isize,
)?;
@@ -230,9 +232,46 @@ impl WindowsApi {
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
Self::enum_display_monitors(
Option::Some(windows_callbacks::enum_display_monitor),
Some(windows_callbacks::enum_display_monitor),
monitors as *mut Ring<Monitor> as isize,
)
)?;
Ok(())
}
pub fn enum_display_devices(
index: u32,
lp_device: Option<*const u16>,
) -> Result<DISPLAY_DEVICEW> {
#[allow(clippy::option_if_let_else)]
let lp_device = match lp_device {
None => PCWSTR::null(),
Some(lp_device) => PCWSTR(lp_device),
};
let mut display_device = DISPLAY_DEVICEW {
cb: u32::try_from(std::mem::size_of::<DISPLAY_DEVICEW>())?,
..Default::default()
};
match unsafe {
EnumDisplayDevicesW(
lp_device,
index,
std::ptr::addr_of_mut!(display_device),
EDD_GET_DEVICE_INTERFACE_NAME,
)
}
.ok()
{
Ok(_) => {}
Err(error) => {
tracing::error!("enum_display_devices: {}", error);
return Err(error.into());
}
}
Ok(display_device)
}
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
@@ -245,7 +284,7 @@ impl WindowsApi {
if let Some(workspace) = monitor.workspaces_mut().front_mut() {
// EnumWindows will enumerate through windows on all monitors
Self::enum_windows(
Option::Some(windows_callbacks::enum_window),
Some(windows_callbacks::enum_window),
workspace.containers_mut() as *mut VecDeque<Container> as isize,
)?;
@@ -650,10 +689,10 @@ impl WindowsApi {
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
let ex_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
let name = OsString::from_wide(&ex_info.szDevice);
let name = name
let name = U16CStr::from_slice_truncate(&ex_info.szDevice)
.expect("monitor name was not a valid u16 c string")
.to_ustring()
.to_string_lossy()
.replace('\u{0000}', "")
.trim_start_matches(r"\\.\")
.to_string();
@@ -759,19 +798,33 @@ impl WindowsApi {
unsafe { CreateSolidBrush(COLORREF(colour)) }
}
pub fn register_class_a(window_class: &WNDCLASSA) -> Result<u16> {
Result::from(WindowsResult::from(unsafe { RegisterClassA(window_class) }))
pub fn register_class_w(window_class: &WNDCLASSW) -> Result<u16> {
Result::from(WindowsResult::from(unsafe { RegisterClassW(window_class) }))
}
pub fn scale_factor_for_monitor(hmonitor: isize) -> Result<DEVICE_SCALE_FACTOR> {
unsafe { GetScaleFactorForMonitor(HMONITOR(hmonitor)) }.process()
pub fn dpi_for_monitor(hmonitor: isize) -> Result<f32> {
let mut dpi_x = u32::default();
let mut dpi_y = u32::default();
unsafe {
GetDpiForMonitor(
HMONITOR(hmonitor),
MDT_EFFECTIVE_DPI,
std::ptr::addr_of_mut!(dpi_x),
std::ptr::addr_of_mut!(dpi_y),
)
}
.process()?;
#[allow(clippy::cast_precision_loss)]
Ok(dpi_y as f32 / 96.0)
}
pub fn monitors_have_same_scale_factor(a: isize, b: isize) -> Result<bool> {
let a = Self::scale_factor_for_monitor(a)?;
let b = Self::scale_factor_for_monitor(b)?;
pub fn monitors_have_same_dpi(hmonitor_a: isize, hmonitor_b: isize) -> Result<bool> {
let dpi_a = Self::dpi_for_monitor(hmonitor_a)?;
let dpi_b = Self::dpi_for_monitor(hmonitor_b)?;
Ok(a == b)
Ok((dpi_a - dpi_b).abs() < f32::EPSILON)
}
pub fn round_corners(hwnd: isize) -> Result<()> {
@@ -788,9 +841,9 @@ impl WindowsApi {
.process()
}
pub fn create_border_window(name: PCSTR, instance: HMODULE) -> Result<isize> {
pub fn create_border_window(name: PCWSTR, instance: HMODULE) -> Result<isize> {
unsafe {
let hwnd = CreateWindowExA(
let hwnd = CreateWindowExW(
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
name,
name,
@@ -822,9 +875,9 @@ impl WindowsApi {
Ok(())
}
pub fn create_hidden_window(name: PCSTR, instance: HMODULE) -> Result<isize> {
pub fn create_hidden_window(name: PCWSTR, instance: HMODULE) -> Result<isize> {
unsafe {
CreateWindowExA(
CreateWindowExW(
WS_EX_NOACTIVATE,
name,
name,

View File

@@ -1,5 +1,6 @@
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use widestring::U16CStr;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::COLORREF;
@@ -40,6 +41,7 @@ use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_RECT;
use crate::BORDER_WIDTH;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::TRANSPARENCY_COLOUR;
@@ -72,7 +74,38 @@ pub extern "system" fn enum_display_monitor(
}
}
if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
let current_index = monitors.elements().len();
if let Ok(mut m) = WindowsApi::monitor(hmonitor.0) {
#[allow(clippy::cast_possible_truncation)]
if let Ok(d) = WindowsApi::enum_display_devices(current_index as u32, None) {
let name = U16CStr::from_slice_truncate(d.DeviceName.as_ref())
.expect("display device name was not a valid u16 c string")
.to_ustring()
.to_string_lossy()
.trim_start_matches(r"\\.\")
.to_string();
if name.eq(m.name()) {
if let Ok(device) = WindowsApi::enum_display_devices(0, Some(d.DeviceName.as_ptr()))
{
let id = U16CStr::from_slice_truncate(device.DeviceID.as_ref())
.expect("display device id was not a valid u16 c string")
.to_ustring()
.to_string_lossy()
.trim_start_matches(r"\\?\")
.to_string();
let mut split: Vec<_> = id.split('#').collect();
split.remove(0);
split.remove(split.len() - 1);
m.set_device(Option::from(split[0].to_string()));
m.set_device_id(Option::from(split.join("-")));
}
}
}
let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
let mut index_preference = None;
for (index, monitor_size) in &*monitor_index_preferences {
@@ -81,7 +114,18 @@ pub extern "system" fn enum_display_monitor(
}
}
if let Some(preference) = index_preference {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
for (index, device) in &*display_index_preferences {
if let Some(known_device) = m.device_id() {
if device == known_device {
index_preference = Option::from(index);
}
}
}
if monitors.elements().is_empty() {
monitors.elements_mut().push_back(m);
} else if let Some(preference) = index_preference {
let current_len = monitors.elements().len();
if *preference > current_len {
monitors.elements_mut().reserve(1);

View File

@@ -23,6 +23,7 @@ use crate::container::Container;
use crate::ring::Ring;
use crate::static_config::WorkspaceConfig;
use crate::window::Window;
use crate::window::WindowDetails;
use crate::windows_api::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
@@ -1087,6 +1088,20 @@ impl Workspace {
vec
}
pub fn visible_window_details(&self) -> Vec<WindowDetails> {
let mut vec: Vec<WindowDetails> = vec![];
for container in self.containers() {
if let Some(focused) = container.focused_window() {
if let Ok(details) = (*focused).try_into() {
vec.push(details);
}
}
}
vec
}
pub fn visible_windows_mut(&mut self) -> Vec<Option<&mut Window>> {
let mut vec = vec![];
for container in self.containers_mut() {

View File

@@ -15,19 +15,21 @@ derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" }
clap = { version = "4", features = ["derive", "wrap_help"] }
color-eyre = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
fs-tail = "0.1"
heck = "0.4"
lazy_static = "1"
miette = { version = "5", features = ["fancy"] }
paste = "1"
powershell_script = "1.0"
reqwest = { version = "0.11", features = ["blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
sysinfo = "0.29"
sysinfo = "0.30"
thiserror = "1"
uds_windows = "1"
which = "5"
windows = { workspace = true }
color-eyre = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
windows = { workspace = true }

View File

@@ -7,6 +7,7 @@ use std::io::BufRead;
use std::io::BufReader;
use std::io::ErrorKind;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
@@ -22,8 +23,11 @@ use fs_tail::TailedFile;
use heck::ToKebabCase;
use komorebi_core::resolve_home_path;
use lazy_static::lazy_static;
use miette::NamedSource;
use miette::Report;
use miette::SourceOffset;
use miette::SourceSpan;
use paste::paste;
use sysinfo::SystemExt;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
use which::which;
@@ -82,6 +86,17 @@ trait AhkFunction {
fn generate_ahk_function() -> String;
}
#[derive(thiserror::Error, Debug, miette::Diagnostic)]
#[error("{message}")]
#[diagnostic(code(komorebi::configuration), help("try fixing this syntax error"))]
struct ConfigurationError {
message: String,
#[source_code]
src: NamedSource,
#[label("This bit here")]
bad_bit: SourceSpan,
}
#[derive(Copy, Clone, ValueEnum)]
enum BooleanState {
Enable,
@@ -401,6 +416,14 @@ struct MonitorIndexPreference {
bottom: i32,
}
#[derive(Parser, AhkFunction)]
struct DisplayIndexPreference {
/// Preferred monitor index (zero-indexed)
index_preference: usize,
/// Display name as identified in komorebic state
display: String,
}
#[derive(Parser, AhkFunction)]
struct EnsureWorkspaces {
/// Monitor index (zero-indexed)
@@ -750,6 +773,8 @@ enum SubCommand {
Check,
/// Show a JSON representation of the current window manager state
State,
/// Show a JSON representation of visible windows
VisibleWindows,
/// Query the current window manager state
#[clap(arg_required_else_help = true)]
Query(Query),
@@ -844,6 +869,8 @@ enum SubCommand {
/// Focus the specified monitor
#[clap(arg_required_else_help = true)]
FocusMonitor(FocusMonitor),
/// Focus the last focused workspace on the focused monitor
FocusLastWorkspace,
/// Focus the specified workspace on the focused monitor
#[clap(arg_required_else_help = true)]
FocusWorkspace(FocusWorkspace),
@@ -915,6 +942,9 @@ enum SubCommand {
/// Set the monitor index preference for a monitor identified using its size
#[clap(arg_required_else_help = true)]
MonitorIndexPreference(MonitorIndexPreference),
/// Set the display index preference for a monitor identified using its display name
#[clap(arg_required_else_help = true)]
DisplayIndexPreference(DisplayIndexPreference),
/// Create at least this many workspaces for the specified monitor
#[clap(arg_required_else_help = true)]
EnsureWorkspaces(EnsureWorkspaces),
@@ -1207,7 +1237,7 @@ fn main() -> Result<()> {
let mut arguments = String::from("start");
if let Some(config) = args.config {
arguments.push_str("--config ");
arguments.push_str(" --config ");
arguments.push_str(&config.to_string_lossy());
}
@@ -1261,7 +1291,49 @@ fn main() -> Result<()> {
.join("whkdrc");
if static_config.exists() {
let config_source = std::fs::read_to_string(&static_config)?;
let lines: Vec<_> = config_source.lines().collect();
let parsed_config = serde_json::from_str::<serde_json::Value>(&config_source);
if let Err(serde_error) = &parsed_config {
let line = lines[serde_error.line() - 2];
let offset = SourceOffset::from_location(
config_source.clone(),
serde_error.line() - 1,
line.len(),
);
let error_string = serde_error.to_string();
let msgs: Vec<_> = error_string.split(" at ").collect();
let diagnostic = ConfigurationError {
message: msgs[0].to_string(),
src: NamedSource::new("komorebi.json", config_source.clone()),
bad_bit: SourceSpan::new(offset, 2.into()),
};
println!("{:?}", Report::new(diagnostic));
}
println!("Found komorebi.json; this file can be passed to the start command with the --config flag\n");
if let Ok(config) = &parsed_config {
if let Some(asc_path) = config.get("app_specific_configuration_path") {
let normalized_asc_path = asc_path
.to_string()
.replace(
"$Env:USERPROFILE",
&dirs::home_dir().unwrap().to_string_lossy(),
)
.replace('"', "")
.replace('\\', "/");
if !Path::exists(Path::new(&normalized_asc_path)) {
println!("Application specific configuration file path '{normalized_asc_path}' does not exist. Try running 'komorebic fetch-asc'\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 {
@@ -1713,7 +1785,7 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
let script = format!(
r#"
Start-Process {ahk} {config} -WindowStyle hidden
Start-Process '{ahk}' '{config}' -WindowStyle hidden
"#,
config = config_ahk.display()
);
@@ -1806,6 +1878,9 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
SubCommand::FocusMonitor(arg) => {
send_message(&SocketMessage::FocusMonitorNumber(arg.target).as_bytes()?)?;
}
SubCommand::FocusLastWorkspace => {
send_message(&SocketMessage::FocusLastWorkspace.as_bytes()?)?;
}
SubCommand::FocusWorkspace(arg) => {
send_message(&SocketMessage::FocusWorkspaceNumber(arg.target).as_bytes()?)?;
}
@@ -1851,6 +1926,12 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
.as_bytes()?,
)?;
}
SubCommand::DisplayIndexPreference(arg) => {
send_message(
&SocketMessage::DisplayIndexPreference(arg.index_preference, arg.display)
.as_bytes()?,
)?;
}
SubCommand::EnsureWorkspaces(workspaces) => {
send_message(
&SocketMessage::EnsureWorkspaces(workspaces.monitor, workspaces.workspace_count)
@@ -1865,6 +1946,9 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
SubCommand::State => {
with_komorebic_socket(|| send_message(&SocketMessage::State.as_bytes()?))?;
}
SubCommand::VisibleWindows => {
with_komorebic_socket(|| send_message(&SocketMessage::VisibleWindows.as_bytes()?))?;
}
SubCommand::Query(arg) => {
with_komorebic_socket(|| {
send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)