Compare commits

..

32 Commits

Author SHA1 Message Date
LGUG2Z
23cc38b1d3 refactor(cli): update animation cmd names 2023-12-03 09:06:46 -08:00
LGUG2Z
a8aad69ecb feat(config): add animation options 2023-12-03 09:06:46 -08:00
LGUG2Z
e24835a4ae fix(animations): update mouse follows focus
This commit ensures that the UpdateFocusedWindowBorder
WindowManagerEvent which is handled in process_event is used to position
the mouse correctly on the moved window once the animation has been
completed.

A pending clippy lint in animation.rs has also been addressed.
2023-12-03 09:06:46 -08:00
LGUG2Z
50b12431fa fix(animations): fix border updates + apply lints
This commit fixes an issue where the active window border would not
properly update when animations were enabled. Various clippy lints have
also been addressed, but I still need to look into the infinite loop
lint that has been marked with a todo comment.
2023-12-03 09:06:46 -08:00
thearturca
95df970860 Feature: Animation. Need fixes
At current state animation is working. Configs are available for it.
animation enable/disable
animation-duration {duration in ms}
animation-ease -e {ease func. See --help}

Two major problems for my implementation:
1. Google Chrome window is broken and I don't know how to implement fix
   for it.
2. Border window does not update properly with window. Dont know how to
   fix it.
2023-12-03 09:06:46 -08:00
thearturca
9d1c0ad790 feature: animation
Commit to pull changes from master
2023-12-03 09:06:46 -08:00
LGUG2Z
d9eea34266 chore(just): update justfile 2023-12-03 09:06:35 -08:00
Amr Bashir
7078b065f4 feat(cli): make --config optional for enable-autostart (#596)
* feat(cli): make `--config` optional for `enable-autostart`

* actually make it optional

* remove unnecessary `action` attribute
2023-12-03 09:00:27 -08:00
Amr Bashir
900051a24b feat(cli): add --no-console to autostart without a console window (#598)
* feat(cli): autostart without a console window

This moves `komorebic` logic into a `lib.rs` file and calls it from `main.rs` (normal behavior) and then there is a second binary `komorebic-no-console` binary that uses `#![windows_subsystem = "windows"]` which tells the linker to not attach a console window to this binary.

* Revert "feat(cli): autostart without a console window"

This reverts commit 08494b46dd.

* feat(cli): autostart without a console window

This creates a second binary `komorebic-no-console` binary that uses `#![windows_subsystem = "windows"]` which tells the linker to not attach a console window to this binary and its only job is to run and pass its args to `komorebic`.

* add behind `--no-console` flag

* reference the new binary in wix

* remove no-console

* fix typo
2023-12-03 09:00:04 -08:00
LGUG2Z
1c589491a9 refactor(clippy): apply various lint fixes 2023-12-02 13:47:49 -08:00
LGUG2Z
5a99a688a6 feat(asc): add json schema cmd and output
This commit adds a command to output a JSON Schema for the
applications.yaml file maintained in the
komorebi-application-specific-configuration repo, and also adds an
up-to-date version of the JSON Schema as a file in the root of this
repository so that users can reference it as an autocompletion source.
2023-12-02 13:42:23 -08:00
LGUG2Z
8980e3b16e feat(config): make static config flag optional
This commit makes the --config flag on komorebi.exe optional, and
updates the configuration loading logic to try to find a komorebi.json
file in the HOME_DIR, which is either $Env:KOMOREBI_CONFIG_HOME or
$Env:PROFILE.

This unlocks the way for Amr's PR to make the --config flag optional on
the enable-autostart command.

re #596
2023-12-02 13:27:48 -08:00
LGUG2Z
ca9eccf247 chore(deps): cargo update 2023-12-02 09:17:37 -08:00
LGUG2Z
e04ba0e033 feat(win32): set foregroundlocktimeout explicitly
After another round of deep diving to find a workaround to all of the
mechanisms within Windows that prevent a process from changing the
focused window, I came across this gist which I saw setting
SPI_SETFOREGROUNDLOCKTIMEOUT to 0:
https://gist.github.com/EBNull/1419093

This tentatively seems like it works and it removes the need for the
alt_focus_hack setting.

However, according to this StackOverflow discussion, it seems like on
Win10+ changes to ForegroundLockTimeout in the registry are no longer
respected and changes made via SPI_SETFOREGROUNDLOCKTIMEOUT are not
persisted:
https://stackoverflow.com/questions/73735129/setforegroundwindow-relationship-between-the-foregroundlocktimeout-registry-val

Therefore on starting, komorebi will now check the value with
SPI_GETFOREGROUNDLOCKTIMEOUT and if it is not 0, it will be set to 0.

Logging has been added to inform the user of the changes that are
happening.
2023-12-01 16:21:34 -08:00
LGUG2Z
8afad7246f fix(config): make win32 calls for windows ffm
This commit ensures that the required calls are made to the system to
enable and disable the Windows focus-follows-mouse implementation when
users make changes to the focus_follows_mouse option in the static
config file.

fix #603
2023-11-29 21:44:44 -08:00
dependabot[bot]
8e9d63cd36 chore(deps): bump proc-macro2 from 1.0.69 to 1.0.70
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.69 to 1.0.70.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.69...1.0.70)

---
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-11-28 17:59:04 -08:00
dependabot[bot]
a673c69744 chore(deps): bump sysinfo from 0.29.10 to 0.29.11
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.10 to 0.29.11.
- [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-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 17:58:55 -08:00
dependabot[bot]
c0b65677a2 chore(deps): bump serde from 1.0.192 to 1.0.193
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.192 to 1.0.193.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.192...v1.0.193)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 17:58:45 -08:00
dependabot[bot]
0cf7147e39 chore(deps): bump openssl from 0.10.59 to 0.10.60
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.59 to 0.10.60.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.59...openssl-v0.10.60)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 17:31:19 -08:00
amrbashir
b39e65642b refactor(rust): cleanup path handling
- Avoids unnecessary string allocation when tracing paths
- Replaces `mut path & path.push()` with `path.join()`
- Avoids unncessary cloning of paths where applicable
- Use `dunce` crate to remove `UNC` prefix
- Improve performance of resolving `~` by avoiding unnecessary string allocations
- Resolve `~`, `$Env:USERPROFILE` and `$HOME` consistenly between different code paths
- Use `PathBuf` instead of `String` for paths in CLI args

I may have missed a couple of places but I think I covered 90% of path handling in the codebase
2023-11-26 15:25:43 -08:00
LGUG2Z
a68f3843b7 feat(cli): add ffm flag to enable-autostart
The user dumbhighrank mentioned on Discord that it was not possible to
call enable-autostart with the --ffm flag, as one might expect to be
able to do, given that it is accepted by the start command. This commit
introduces the --ffm flag for the enable-autostart command.
2023-11-24 14:40:51 -08:00
dependabot[bot]
b3e989c6c7 chore(deps): bump winreg from 0.51.0 to 0.52.0
Bumps [winreg](https://github.com/gentoo90/winreg-rs) from 0.51.0 to 0.52.0.
- [Release notes](https://github.com/gentoo90/winreg-rs/releases)
- [Changelog](https://github.com/gentoo90/winreg-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gentoo90/winreg-rs/compare/v0.51.0...v0.52.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 21:39:52 -08:00
dependabot[bot]
4534a1511e chore(deps): bump tracing-appender from 0.2.2 to 0.2.3
Bumps [tracing-appender](https://github.com/tokio-rs/tracing) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-appender-0.2.2...tracing-appender-0.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 21:39:44 -08:00
dependabot[bot]
1f87e3acd2 chore(deps): bump tracing-subscriber from 0.3.17 to 0.3.18
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.17 to 0.3.18.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.17...tracing-subscriber-0.3.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 21:39:34 -08:00
LGUG2Z
f67d0c7b9b docs(readme): update donations with link to pcrf 2023-11-22 21:36:49 -08:00
LGUG2Z
aa0277d58c feat(cli): add quickstart cmd
This commit adds a new komorebic quickstart command to handle the
downloading of example configuration files, which is currently done
manually by the user if they are following the quickstart guide on the
README.

re #585
2023-11-19 17:40:51 -08:00
LGUG2Z
42ac13e0bd feat(config): autopopulate matcher for exes
This commit updates the fmt-asc command to automatically populate unset
matching strategies for Exe matching rules to be "Equals"
2023-11-17 17:56:28 -08:00
LGUG2Z
2a1d87d41b fix(cli): pick up static cfg in check cmd 2023-11-16 15:12:08 -08:00
LGUG2Z
0d1595e543 feat(cli): add focused ws padding cmds
This commit adds dedicated komorebic commands for setting the padding
values of the focused workspace and containers.

re #570
2023-11-16 15:05:14 -08:00
LGUG2Z
4283dfb0ba chore(deps): bump windows crates from 0.51 to 0.52 2023-11-16 14:40:27 -08:00
dependabot[bot]
2a6aa09b07 chore(deps): bump schemars from 0.8.15 to 0.8.16
Bumps [schemars](https://github.com/GREsau/schemars) from 0.8.15 to 0.8.16.
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v0.8.15...v0.8.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 08:15:48 -08:00
LGUG2Z
8c6bd13511 fix(ahk): override derive-ahk output for stop cmd
This commit adds a last-second override the string output of the
derive-ahk proc macro for the stop command, which unfortunately ends up
in a broken state in komorebic.lib.ahk with the addition of a 'whkd'
flag in v0.1.19.

fix #578
2023-11-12 18:20:12 -08:00
31 changed files with 1664 additions and 695 deletions

View File

@@ -26,6 +26,15 @@ builds:
post:
- mkdir -p dist/windows_amd64
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64_v1\komorebic.exe"
- id: komorebic-no-console
main: dummy.go
goos: ["windows"]
goarch: ["amd64"]
binary: komorebic-no-console
hooks:
post:
- mkdir -p dist/windows_amd64
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic-no-console.exe" ".\dist\komorebic_no_console_windows_amd64_v1\komorebic-no-console.exe"
archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"

282
Cargo.lock generated
View File

@@ -148,9 +148,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.8"
version = "4.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
dependencies = [
"clap_builder",
"clap_derive",
@@ -158,9 +158,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.8"
version = "4.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
dependencies = [
"anstream",
"anstyle",
@@ -204,9 +204,9 @@ dependencies = [
[[package]]
name = "color-spantrace"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
dependencies = [
"once_cell",
"owo-colors",
@@ -222,9 +222,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "core-foundation"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
@@ -232,9 +232,9 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "crossbeam-channel"
@@ -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"
@@ -357,19 +363,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.6"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
name = "eyre"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd"
dependencies = [
"indenter",
"once_cell",
@@ -422,9 +428,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@@ -546,15 +552,15 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.28.0"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "h2"
version = "0.3.21"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
dependencies = [
"bytes",
"fnv",
@@ -562,7 +568,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap 1.9.3",
"indexmap",
"slab",
"tokio",
"tokio-util",
@@ -571,15 +577,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.12.3"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
@@ -614,9 +614,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
@@ -685,9 +685,9 @@ dependencies = [
[[package]]
name = "idna"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -699,16 +699,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.1.0"
@@ -716,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.2",
"hashbrown",
]
[[package]]
@@ -762,9 +752,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "js-sys"
version = "0.3.65"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
dependencies = [
"wasm-bindgen",
]
@@ -815,7 +805,7 @@ dependencies = [
"windows-implement",
"windows-interface",
"winput",
"winreg 0.51.0",
"winreg 0.52.0",
]
[[package]]
@@ -824,6 +814,8 @@ version = "0.1.19"
dependencies = [
"clap",
"color-eyre",
"dirs",
"dunce",
"schemars",
"serde",
"serde_json",
@@ -840,6 +832,7 @@ dependencies = [
"color-eyre",
"derive-ahk",
"dirs",
"dunce",
"fs-tail",
"heck",
"komorebi-core",
@@ -856,6 +849,10 @@ dependencies = [
"windows",
]
[[package]]
name = "komorebic-no-console"
version = "0.1.19"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -887,9 +884,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "lock_api"
@@ -1122,9 +1119,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 +1151,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 +1213,7 @@ dependencies = [
"redox_syscall 0.4.1",
"smallvec",
"thread-id",
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
@@ -1227,9 +1224,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "percent-encoding"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
@@ -1238,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap 2.1.0",
"indexmap",
]
[[package]]
@@ -1303,9 +1300,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",
]
@@ -1488,15 +1485,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.21"
version = "0.38.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1531,9 +1528,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 +1540,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 +1581,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",
@@ -1642,7 +1639,7 @@ version = "0.9.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
dependencies = [
"indexmap 2.1.0",
"indexmap",
"itoa",
"ryu",
"serde",
@@ -1745,9 +1742,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 +1942,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 +1985,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 +1996,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",
@@ -2059,9 +2057,9 @@ checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
name = "url"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
@@ -2119,9 +2117,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@@ -2129,9 +2127,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
@@ -2144,9 +2142,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.38"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -2156,9 +2154,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2166,9 +2164,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
@@ -2179,15 +2177,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "web-sys"
version = "0.3.65"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -2251,30 +2249,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 +2281,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 +2311,16 @@ 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]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
@@ -2331,6 +2338,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 +2365,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 +2383,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 +2401,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 +2419,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 +2437,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 +2455,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 +2473,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 +2500,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",

View File

@@ -6,14 +6,18 @@ members = [
"komorebi",
"komorebi-core",
"komorebic",
"komorebic-no-console",
]
[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",

View File

@@ -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

View File

@@ -13,24 +13,23 @@ fmt:
prettier --write .github/FUNDING.yml
prettier --write .github/workflows/windows.yaml
install-komorebic:
cargo +stable install --path komorebic --locked
install-target target:
cargo +stable install --path {{ target }} --locked
install-komorebi:
cargo +stable install --path komorebi --locked
install:
just install-komorebic
just install-komorebi
komorebic ahk-asc '~/komorebi-application-specific-configuration/applications.yaml'
komorebic pwsh-asc '~/komorebi-application-specific-configuration/applications.yaml'
prepare:
komorebic ahk-asc '~/.config/komorebi/applications.yaml'
komorebic pwsh-asc '~/.config/komorebi/applications.yaml'
cat '~/.config/komorebi/komorebi.generated.ps1' >komorebi.generated.ps1
cat '~/.config/komorebi/komorebi.generated.ahk' >komorebi.generated.ahk
cat '~/.config/komorebi/komorebic.lib_newV2.ahk' >komorebic.lib.ahk
install:
just install-target komorebic
just install-target komorebic-no-console
just install-target komorebi
run:
just install-komorebic
cargo +stable run --bin komorebi --locked -- -a
cargo +stable run --bin komorebi --locked
warn $RUST_LOG="warn":
just run

View File

@@ -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 }

View File

@@ -0,0 +1,43 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[strum(serialize_all = "snake_case")]
pub enum EaseEnum {
Linear,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseInCubic,
EaseInOutCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
}

View File

@@ -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)?)
}

View File

@@ -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)

View File

@@ -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;
@@ -12,6 +14,7 @@ use serde::Serialize;
use strum::Display;
use strum::EnumString;
pub use animation::EaseEnum;
pub use arrangement::Arrangement;
pub use arrangement::Axis;
pub use custom_layout::CustomLayout;
@@ -22,6 +25,7 @@ pub use layout::Layout;
pub use operation_direction::OperationDirection;
pub use rect::Rect;
pub mod animation;
pub mod arrangement;
pub mod config_generation;
pub mod custom_layout;
@@ -102,8 +106,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),
@@ -123,6 +129,9 @@ pub enum SocketMessage {
WatchConfiguration(bool),
CompleteConfiguration,
AltFocusHack(bool),
Animation(bool),
AnimationDuration(u64),
AnimationEase(EaseEnum),
ActiveWindowBorder(bool),
ActiveWindowBorderColour(WindowKind, u32, u32, u32),
ActiveWindowBorderWidth(i32),
@@ -151,6 +160,7 @@ pub enum SocketMessage {
ToggleTitleBars,
AddSubscriber(String),
RemoveSubscriber(String),
ApplicationSpecificConfigurationSchema,
NotificationSchema,
SocketSchema,
StaticConfigSchema,
@@ -296,3 +306,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
})
}

View File

@@ -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 = []

510
komorebi/src/animation.rs Normal file
View File

@@ -0,0 +1,510 @@
use color_eyre::Result;
use komorebi_core::EaseEnum;
use komorebi_core::Rect;
use schemars::JsonSchema;
use std::f64::consts::PI;
use std::time::Duration;
use std::time::Instant;
use crate::ANIMATION_EASE;
pub trait Ease {
fn evaluate(t: f64) -> f64;
}
pub struct Linear;
impl Ease for Linear {
fn evaluate(t: f64) -> f64 {
t
}
}
pub struct EaseInSine;
impl Ease for EaseInSine {
fn evaluate(t: f64) -> f64 {
1.0 - f64::cos((t * PI) / 2.0)
}
}
pub struct EaseOutSine;
impl Ease for EaseOutSine {
fn evaluate(t: f64) -> f64 {
f64::sin((t * PI) / 2.0)
}
}
pub struct EaseInOutSine;
impl Ease for EaseInOutSine {
fn evaluate(t: f64) -> f64 {
-(f64::cos(PI * t) - 1.0) / 2.0
}
}
pub struct EaseInQuad;
impl Ease for EaseInQuad {
fn evaluate(t: f64) -> f64 {
t * t
}
}
pub struct EaseOutQuad;
impl Ease for EaseOutQuad {
fn evaluate(t: f64) -> f64 {
(1.0 - t).mul_add(-1.0 - t, 1.0)
}
}
pub struct EaseInOutQuad;
impl Ease for EaseInOutQuad {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(2) / 2.0
}
}
}
pub struct EaseInCubic;
impl Ease for EaseInCubic {
fn evaluate(t: f64) -> f64 {
t * t * t
}
}
pub struct EaseOutCubic;
impl Ease for EaseOutCubic {
fn evaluate(t: f64) -> f64 {
1.0 - (1.0 - t).powi(3)
}
}
pub struct EaseInOutCubic;
impl Ease for EaseInOutCubic {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(3) / 2.0
}
}
}
pub struct EaseInQuart;
impl Ease for EaseInQuart {
fn evaluate(t: f64) -> f64 {
t * t * t * t
}
}
pub struct EaseOutQuart;
impl Ease for EaseOutQuart {
fn evaluate(t: f64) -> f64 {
1.0 - (1.0 - t).powi(4)
}
}
pub struct EaseInOutQuart;
impl Ease for EaseInOutQuart {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(4) / 2.0
}
}
}
pub struct EaseInQuint;
impl Ease for EaseInQuint {
fn evaluate(t: f64) -> f64 {
t * t * t * t * t
}
}
pub struct EaseOutQuint;
impl Ease for EaseOutQuint {
fn evaluate(t: f64) -> f64 {
1.0 - (1.0 - t).powi(5)
}
}
pub struct EaseInOutQuint;
impl Ease for EaseInOutQuint {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
16.0 * t * t * t * t
} else {
1.0 - (-2.0f64).mul_add(t, 2.0).powi(5) / 2.0
}
}
}
pub struct EaseInExpo;
impl Ease for EaseInExpo {
fn evaluate(t: f64) -> f64 {
if t == 0.0 {
return t;
}
10.0f64.mul_add(t, -10.0).exp2()
}
}
pub struct EaseOutExpo;
impl Ease for EaseOutExpo {
fn evaluate(t: f64) -> f64 {
if (t - 1.0).abs() < f64::EPSILON {
return t;
}
1.0 - (-10.0 * t).exp2()
}
}
pub struct EaseInOutExpo;
impl Ease for EaseInOutExpo {
fn evaluate(t: f64) -> f64 {
if t == 0.0 || (t - 1.0).abs() < f64::EPSILON {
return t;
}
if t < 0.5 {
20.0f64.mul_add(t, -10.0).exp2() / 2.0
} else {
(2.0 - (-20.0f64).mul_add(t, 10.0).exp2()) / 2.0
}
}
}
pub struct EaseInCirc;
impl Ease for EaseInCirc {
fn evaluate(t: f64) -> f64 {
1.0 - f64::sqrt(t.mul_add(-t, 1.0))
}
}
pub struct EaseOutCirc;
impl Ease for EaseOutCirc {
fn evaluate(t: f64) -> f64 {
f64::sqrt((t - 1.0).mul_add(-(t - 1.0), 1.0))
}
}
pub struct EaseInOutCirc;
impl Ease for EaseInOutCirc {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
(1.0 - f64::sqrt((2.0 * t).mul_add(-(2.0 * t), 1.0))) / 2.0
} else {
(f64::sqrt(
(-2.0f64)
.mul_add(t, 2.0)
.mul_add(-(-2.0f64).mul_add(t, 2.0), 1.0),
) + 1.0)
/ 2.0
}
}
}
pub struct EaseInBack;
impl Ease for EaseInBack {
fn evaluate(t: f64) -> f64 {
let c1 = 1.70158;
let c3 = c1 + 1.0;
(c3 * t * t).mul_add(t, -c1 * t * t)
}
}
pub struct EaseOutBack;
impl Ease for EaseOutBack {
fn evaluate(t: f64) -> f64 {
let c1: f64 = 1.70158;
let c3: f64 = c1 + 1.0;
c1.mul_add((t - 1.0).powi(2), c3.mul_add((t - 1.0).powi(3), 1.0))
}
}
pub struct EaseInOutBack;
impl Ease for EaseInOutBack {
fn evaluate(t: f64) -> f64 {
let c1: f64 = 1.70158;
let c2: f64 = c1 * 1.525;
if t < 0.5 {
((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0).mul_add(t, -c2)) / 2.0
} else {
((2.0f64.mul_add(t, -2.0))
.powi(2)
.mul_add((c2 + 1.0).mul_add(t.mul_add(2.0, -2.0), c2), 2.0))
/ 2.0
}
}
}
pub struct EaseInElastic;
impl Ease for EaseInElastic {
fn evaluate(t: f64) -> f64 {
if (t - 1.0).abs() < f64::EPSILON || t == 0.0 {
return t;
}
let c4 = (2.0 * PI) / 3.0;
-(10.0f64.mul_add(t, -10.0).exp2()) * f64::sin(t.mul_add(10.0, -10.75) * c4)
}
}
pub struct EaseOutElastic;
impl Ease for EaseOutElastic {
fn evaluate(t: f64) -> f64 {
if (t - 1.0).abs() < f64::EPSILON || t == 0.0 {
return t;
}
let c4 = (2.0 * PI) / 3.0;
(-10.0 * t)
.exp2()
.mul_add(f64::sin(t.mul_add(10.0, -0.75) * c4), 1.0)
}
}
pub struct EaseInOutElastic;
impl Ease for EaseInOutElastic {
fn evaluate(t: f64) -> f64 {
if (t - 1.0).abs() < f64::EPSILON || t == 0.0 {
return t;
}
let c5 = (2.0 * PI) / 4.5;
if t < 0.5 {
-(20.0f64.mul_add(t, -10.0).exp2() * f64::sin(20.0f64.mul_add(t, -11.125) * c5)) / 2.0
} else {
((-20.0f64).mul_add(t, 10.0).exp2() * f64::sin(20.0f64.mul_add(t, -11.125) * c5)) / 2.0
+ 1.0
}
}
}
pub struct EaseInBounce;
impl Ease for EaseInBounce {
fn evaluate(t: f64) -> f64 {
1.0 - EaseOutBounce::evaluate(1.0 - t)
}
}
pub struct EaseOutBounce;
impl Ease for EaseOutBounce {
fn evaluate(t: f64) -> f64 {
let mut time = t;
let n1 = 7.5625;
let d1 = 2.75;
if t < 1.0 / d1 {
n1 * time * time
} else if time < 2.0 / d1 {
time -= 1.5 / d1;
(n1 * time).mul_add(time, 0.75)
} else if time < 2.5 / d1 {
time -= 2.25 / d1;
(n1 * time).mul_add(time, 0.9375)
} else {
time -= 2.625 / d1;
(n1 * time).mul_add(time, 0.984_375)
}
}
}
pub struct EaseInOutBounce;
impl Ease for EaseInOutBounce {
fn evaluate(t: f64) -> f64 {
if t < 0.5 {
(1.0 - EaseOutBounce::evaluate(2.0f64.mul_add(-t, 1.0))) / 2.0
} else {
(1.0 + EaseOutBounce::evaluate(2.0f64.mul_add(t, -1.0))) / 2.0
}
}
}
fn apply_ease_func(t: f64) -> f64 {
let ease = *ANIMATION_EASE.lock();
match ease {
EaseEnum::Linear => Linear::evaluate(t),
EaseEnum::EaseInSine => EaseInSine::evaluate(t),
EaseEnum::EaseOutSine => EaseOutSine::evaluate(t),
EaseEnum::EaseInOutSine => EaseInOutSine::evaluate(t),
EaseEnum::EaseInQuad => EaseInQuad::evaluate(t),
EaseEnum::EaseOutQuad => EaseOutQuad::evaluate(t),
EaseEnum::EaseInOutQuad => EaseInOutQuad::evaluate(t),
EaseEnum::EaseInCubic => EaseInCubic::evaluate(t),
EaseEnum::EaseInOutCubic => EaseInOutCubic::evaluate(t),
EaseEnum::EaseInQuart => EaseInQuart::evaluate(t),
EaseEnum::EaseOutQuart => EaseOutQuart::evaluate(t),
EaseEnum::EaseInOutQuart => EaseInOutQuart::evaluate(t),
EaseEnum::EaseInQuint => EaseInQuint::evaluate(t),
EaseEnum::EaseOutQuint => EaseOutQuint::evaluate(t),
EaseEnum::EaseInOutQuint => EaseInOutQuint::evaluate(t),
EaseEnum::EaseInExpo => EaseInExpo::evaluate(t),
EaseEnum::EaseOutExpo => EaseOutExpo::evaluate(t),
EaseEnum::EaseInOutExpo => EaseInOutExpo::evaluate(t),
EaseEnum::EaseInCirc => EaseInCirc::evaluate(t),
EaseEnum::EaseOutCirc => EaseOutCirc::evaluate(t),
EaseEnum::EaseInOutCirc => EaseInOutCirc::evaluate(t),
EaseEnum::EaseInBack => EaseInBack::evaluate(t),
EaseEnum::EaseOutBack => EaseOutBack::evaluate(t),
EaseEnum::EaseInOutBack => EaseInOutBack::evaluate(t),
EaseEnum::EaseInElastic => EaseInElastic::evaluate(t),
EaseEnum::EaseOutElastic => EaseOutElastic::evaluate(t),
EaseEnum::EaseInOutElastic => EaseInOutElastic::evaluate(t),
EaseEnum::EaseInBounce => EaseInBounce::evaluate(t),
EaseEnum::EaseOutBounce => EaseOutBounce::evaluate(t),
EaseEnum::EaseInOutBounce => EaseInOutBounce::evaluate(t),
}
}
#[derive(Debug, Default, Clone, Copy, JsonSchema)]
pub struct Animation {
// is_cancel: AtomicBool,
// pub in_progress: AtomicBool,
is_cancel: bool,
pub in_progress: bool,
}
// impl Default for Animation {
// fn default() -> Self {
// Animation {
// // I'm not sure if this is the right way to do it
// // I've tried to use Arc<Mutex<bool>> but it dooes not implement Copy trait
// // and I dont want to rewrite everything cause I'm not experienced with rust
// // Down here you can see the idea I've tried to achive like in any other OOP language
// // My thought is that in order to prevent Google Chrome breaking render window
// // I need to cancel animation if user starting new window movement. So window stops
// // moving at one point and then fires new animation.
// // But my approach does not work because of rust borrowing rules and wired pointers
// // lifetime annotation that I dont know how to use.
// is_cancel: false,
// in_progress: false,
// // is_cancel: AtomicBool::new(false),
// // in_progress: AtomicBool::new(false),
// }
// }
// }
impl Animation {
pub fn cancel(&mut self) {
if !self.in_progress {
return;
}
self.is_cancel = true;
let max_duration = Duration::from_secs(1);
let spent_duration = Instant::now();
while self.in_progress {
if spent_duration.elapsed() >= max_duration {
self.in_progress = false;
}
std::thread::sleep(Duration::from_millis(16));
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn lerp(x: i32, new_x: i32, t: f64) -> i32 {
let time = apply_ease_func(t);
f64::from(new_x - x).mul_add(time, f64::from(x)) as i32
}
pub fn lerp_rect(original_rect: &Rect, new_rect: &Rect, t: f64) -> Rect {
Rect {
left: Self::lerp(original_rect.left, new_rect.left, t),
top: Self::lerp(original_rect.top, new_rect.top, t),
right: Self::lerp(original_rect.right, new_rect.right, t),
bottom: Self::lerp(original_rect.bottom, new_rect.bottom, t),
}
}
#[allow(clippy::cast_precision_loss)]
pub fn animate(
&mut self,
duration: Duration,
mut f: impl FnMut(f64) -> Result<()>,
) -> Result<()> {
self.in_progress = true;
// set target frame time to match 240 fps (my max refresh rate of monitor)
// probably not the best way to do it is take actual monitor refresh rate
// or make it configurable
let target_frame_time = Duration::from_millis(1000 / 240);
let mut progress = 0.0;
let animation_start = Instant::now();
// start animation
while progress < 1.0 {
// check if animation is cancelled
if self.is_cancel {
// cancel animation
// set all flags
self.is_cancel = !self.is_cancel;
self.in_progress = false;
return Ok(());
}
let tick_start = Instant::now();
// calculate progress
progress = animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
f(progress).ok();
// sleep until next frame
while tick_start.elapsed() < target_frame_time {
std::thread::sleep(target_frame_time - tick_start.elapsed());
}
}
self.in_progress = false;
// limit progress to 1.0 if animation took longer
if progress > 1.0 {
progress = 1.0;
}
// process animation for 1.0 to set target position
f(progress)
}
}

View File

@@ -16,17 +16,18 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use clap::Parser;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::Backoff;
use komorebi_core::EaseEnum;
use lazy_static::lazy_static;
use os_info::Version;
#[cfg(feature = "deadlock_detection")]
@@ -66,6 +67,7 @@ use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;
mod animation;
mod border;
mod com;
mod container;
@@ -215,6 +217,8 @@ lazy_static! {
static ref BORDER_OFFSET: Arc<Mutex<Option<Rect>>> =
Arc::new(Mutex::new(None));
static ref ANIMATION_EASE: Arc<Mutex<EaseEnum>> = Arc::new(Mutex::new(EaseEnum::Linear));
// Use app-specific titlebar removal options where possible
// eg. Windows Terminal, IntelliJ IDEA, Firefox
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
@@ -238,6 +242,8 @@ pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(20);
// 0 0 0 aka pure black, I doubt anyone will want this as a border colour
pub const TRANSPARENCY_COLOUR: u32 = 0;
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_ENABLED: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_DURATION: AtomicU64 = AtomicU64::new(250);
pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0);
@@ -252,7 +258,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 +311,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 +321,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 +399,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) => {
@@ -466,16 +455,16 @@ fn detect_deadlocks() {
#[clap(author, about, version)]
struct Opts {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(action, short, long = "ffm")]
#[clap(short, long = "ffm")]
focus_follows_mouse: bool,
/// Wait for 'komorebic complete-configuration' to be sent before processing events
#[clap(action, short, long)]
#[clap(short, long)]
await_configuration: bool,
/// Start a TCP server on the given port to allow the direct sending of SocketMessages
#[clap(action, short, long)]
#[clap(short, long)]
tcp_port: Option<usize>,
/// Path to a static configuration JSON file
#[clap(action, short, long)]
#[clap(short, long)]
config: Option<PathBuf>,
}
@@ -514,6 +503,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();
@@ -525,10 +516,22 @@ fn main() -> Result<()> {
Hidden::create("komorebi-hidden")?;
let wm = if let Some(config) = &opts.config {
let static_config = opts.config.map_or_else(
|| {
let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() {
Option::from(komorebi_json)
} else {
None
}
},
Option::from,
);
let wm = if let Some(config) = &static_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(
@@ -542,7 +545,8 @@ fn main() -> Result<()> {
};
wm.lock().init()?;
if let Some(config) = &opts.config {
if let Some(config) = &static_config {
StaticConfig::postload(config, &wm)?;
}
@@ -556,10 +560,8 @@ fn main() -> Result<()> {
listen_for_commands_tcp(wm.clone(), port);
}
if opts.config.is_none() {
std::thread::spawn(|| {
load_configuration().expect("could not load configuration");
});
if static_config.is_none() {
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
if opts.await_configuration {
let backoff = Backoff::new();

View File

@@ -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

View File

@@ -20,6 +20,7 @@ use parking_lot::Mutex;
use schemars::schema_for;
use uds_windows::UnixStream;
use komorebi_core::config_generation::ApplicationConfiguration;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::ApplicationIdentifier;
@@ -46,6 +47,9 @@ use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::ALT_FOCUS_HACK;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_EASE;
use crate::ANIMATION_ENABLED;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_MONOCLE;
use crate::BORDER_COLOUR_SINGLE;
@@ -322,6 +326,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 +476,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 +510,7 @@ impl WindowManager {
monitor_idx,
workspace_idx,
at_container_count,
path.clone(),
path,
)?;
}
SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {
@@ -494,7 +520,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 +561,7 @@ impl WindowManager {
monitor_idx,
workspace_idx,
at_container_count,
path.clone(),
path,
)?;
}
}
@@ -669,10 +695,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 +715,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 +1032,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 +1045,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 +1063,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)?;
@@ -1129,6 +1143,15 @@ impl WindowManager {
self.hide_border()?;
}
}
SocketMessage::Animation(enable) => {
ANIMATION_ENABLED.store(enable, Ordering::SeqCst);
}
SocketMessage::AnimationDuration(duration) => {
ANIMATION_DURATION.store(duration, Ordering::SeqCst);
}
SocketMessage::AnimationEase(ease) => {
*ANIMATION_EASE.lock() = ease;
}
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
match kind {
WindowKind::Single => {
@@ -1166,12 +1189,18 @@ impl WindowManager {
SocketMessage::AltFocusHack(enable) => {
ALT_FOCUS_HACK.store(enable, Ordering::SeqCst);
}
SocketMessage::ApplicationSpecificConfigurationSchema => {
let asc = schema_for!(Vec<ApplicationConfiguration>);
let schema = serde_json::to_string_pretty(&asc)?;
let socket = DATA_DIR.join("komorebic.sock");
let mut stream = UnixStream::connect(socket)?;
stream.write_all(schema.as_bytes())?;
}
SocketMessage::NotificationSchema => {
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
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 +1208,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 +1216,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())?;
@@ -1284,7 +1307,7 @@ impl WindowManager {
| SocketMessage::CycleMoveWindow(_)
| SocketMessage::MoveWindow(_) => {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let foreground_window = Window::new(foreground);
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;

View File

@@ -515,7 +515,8 @@ impl WindowManager {
WindowManagerEvent::DisplayChange(..)
| WindowManagerEvent::MouseCapture(..)
| WindowManagerEvent::Cloak(..)
| WindowManagerEvent::Uncloak(..) => {}
| WindowManagerEvent::Uncloak(..)
| WindowManagerEvent::UpdateFocusedWindowBorder(..) => {}
};
if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) {
@@ -529,7 +530,8 @@ impl WindowManager {
| WindowManagerEvent::Show(_, window)
| WindowManagerEvent::FocusChange(_, window)
| WindowManagerEvent::Hide(_, window)
| WindowManagerEvent::Minimize(_, window) => {
| WindowManagerEvent::Minimize(_, window)
| WindowManagerEvent::UpdateFocusedWindowBorder(window) => {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
let mut target_window = None;
let mut target_window_is_monocle = false;
@@ -595,6 +597,10 @@ impl WindowManager {
WindowsApi::invalidate_border_rect()?;
border.set_position(target_window, &self.invisible_borders, activate)?;
if matches!(event, WindowManagerEvent::UpdateFocusedWindowBorder(_)) {
window.focus(self.mouse_follows_focus)?;
}
if activate {
BORDER_HIDDEN.store(false, Ordering::SeqCst);
}

View File

@@ -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),
}
}

View File

@@ -4,8 +4,12 @@ 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::ANIMATION_DURATION;
use crate::ANIMATION_EASE;
use crate::ANIMATION_ENABLED;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_MONOCLE;
use crate::BORDER_COLOUR_SINGLE;
@@ -29,15 +33,16 @@ 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::EaseEnum;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::HidingBehaviour;
use komorebi_core::Layout;
@@ -311,6 +316,15 @@ pub struct StaticConfig {
/// Set monitor index preferences
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor_index_preferences: Option<HashMap<usize, Rect>>,
/// Enable or disable animations (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub animation: Option<bool>,
/// Set the animation ease function (default: Linear)
#[serde(skip_serializing_if = "Option::is_none")]
pub animation_ease: Option<EaseEnum>,
/// Set the animation duration in ms (default: 250)
#[serde(skip_serializing_if = "Option::is_none")]
pub animation_duration: Option<u64>,
}
impl From<&WindowManager> for StaticConfig {
@@ -431,6 +445,9 @@ impl From<&WindowManager> for StaticConfig {
layered_applications: None,
object_name_change_applications: None,
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
animation: Option::from(ANIMATION_ENABLED.load(Ordering::SeqCst)),
animation_duration: Option::from(ANIMATION_DURATION.load(Ordering::SeqCst)),
animation_ease: Option::from(*ANIMATION_EASE.lock()),
}
}
}
@@ -448,6 +465,19 @@ impl StaticConfig {
*window_hiding_behaviour = behaviour;
}
if let Some(animation) = self.animation {
ANIMATION_ENABLED.store(animation, Ordering::SeqCst);
}
if let Some(duration) = self.animation_duration {
ANIMATION_DURATION.store(duration, Ordering::SeqCst);
}
if let Some(ease) = self.animation_ease {
let mut animation_ease = ANIMATION_EASE.lock();
*animation_ease = ease;
}
if let Some(hack) = self.alt_focus_hack {
ALT_FOCUS_HACK.store(hack, Ordering::SeqCst);
}
@@ -616,13 +646,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 +787,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 +831,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 +982,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(())

View File

@@ -1,10 +1,14 @@
use crate::com::SetCloak;
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use std::sync::atomic::Ordering;
use std::time::Duration;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
@@ -25,6 +29,7 @@ use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
use komorebi_core::Rect;
use crate::animation::Animation;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::window_manager_event::WindowManagerEvent;
@@ -44,6 +49,7 @@ use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy, JsonSchema)]
pub struct Window {
pub(crate) hwnd: isize,
animation: Animation,
}
impl Display for Window {
@@ -103,6 +109,14 @@ impl Serialize for Window {
}
impl Window {
// for instantiation of animation struct
pub fn new(hwnd: isize) -> Self {
Self {
hwnd,
animation: Animation::default(),
}
}
pub const fn hwnd(self) -> HWND {
HWND(self.hwnd)
}
@@ -122,6 +136,47 @@ impl Window {
true,
)
}
pub fn animate_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
let hwnd = self.hwnd();
let curr_rect = WindowsApi::window_rect(hwnd).unwrap();
if curr_rect.left == layout.left
&& curr_rect.top == layout.top
&& curr_rect.bottom == layout.bottom
&& curr_rect.right == layout.right
{
WindowsApi::position_window(hwnd, layout, top)
} else {
let target_rect = *layout;
let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst));
let mut animation = self.animation;
let self_copied = *self;
std::thread::spawn(move || {
animation.animate(duration, |progress: f64| {
let new_rect = Animation::lerp_rect(&curr_rect, &target_rect, progress);
if progress < 1.0 {
// using MoveWindow because it runs faster than SetWindowPos
// so animation have more fps and feel smoother
WindowsApi::move_window(hwnd, &new_rect, true)?;
} else {
WindowsApi::position_window(hwnd, &new_rect, top)?;
if WindowsApi::foreground_window()? == self_copied.hwnd {
WINEVENT_CALLBACK_CHANNEL
.lock()
.0
.send(WindowManagerEvent::UpdateFocusedWindowBorder(self_copied))?;
}
}
Ok(())
})
});
Ok(())
}
}
pub fn set_position(
&mut self,
@@ -154,7 +209,17 @@ impl Window {
rect.bottom += invisible_borders.bottom;
}
WindowsApi::position_window(self.hwnd(), &rect, top)
if ANIMATION_ENABLED.load(Ordering::SeqCst) {
// check if animation is in progress
if self.animation.in_progress {
// wait for cancel animation
self.animation.cancel();
}
self.animate_position(&rect, top)
} else {
WindowsApi::position_window(self.hwnd(), &rect, top)
}
}
pub fn hide(self) {
@@ -241,7 +306,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 +317,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 +367,7 @@ impl Window {
}
match WindowsApi::set_foreground_window(self.hwnd()) {
Ok(_) => {
Ok(()) => {
foregrounded = true;
}
Err(error) => {
@@ -334,7 +399,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(): {}",

View File

@@ -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 => {}
@@ -215,7 +217,7 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn show_border(&self) -> Result<()> {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let foreground_window = Window::new(foreground);
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;
@@ -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(())
}
@@ -603,7 +589,7 @@ impl WindowManager {
// Hide the window we are about to remove if it is on the currently focused workspace
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
Window { hwnd: op.hwnd }.hide();
Window::new(op.hwnd).hide();
should_update_focused_workspace = true;
}
@@ -633,7 +619,7 @@ impl WindowManager {
.get_mut(op.target_workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
target_workspace.new_container_for_window(Window { hwnd: op.hwnd });
target_workspace.new_container_for_window(Window::new(op.hwnd));
}
// Only re-tile the focused workspace if we need to
@@ -677,14 +663,14 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn manage_focused_window(&mut self) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Manage(Window { hwnd });
let event = WindowManagerEvent::Manage(Window::new(hwnd));
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
}
#[tracing::instrument(skip(self))]
pub fn unmanage_focused_window(&mut self) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Unmanage(Window { hwnd });
let event = WindowManagerEvent::Unmanage(Window::new(hwnd));
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
}
@@ -722,13 +708,14 @@ impl WindowManager {
];
if !known_hwnd {
let class = Window { hwnd }.class()?;
let class = Window::new(hwnd).class()?;
// Some applications (Electron/Chromium-based, explorer) have (invisible?) overlays
// windows that we need to look beyond to find the actual window to raise
if overlay_classes.contains(&class) {
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?)
if let Some(exe_hwnd) =
workspace.hwnd_from_exe(&Window::new(hwnd).exe()?)
{
hwnd = exe_hwnd;
known_hwnd = true;
@@ -739,11 +726,11 @@ impl WindowManager {
}
if known_hwnd {
let event = WindowManagerEvent::Raise(Window { hwnd });
let event = WindowManagerEvent::Raise(Window::new(hwnd));
self.has_pending_raise_op = true;
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
} else {
tracing::debug!("not raising unknown window: {}", Window { hwnd });
tracing::debug!("not raising unknown window: {}", Window::new(hwnd,));
Ok(())
}
}
@@ -857,9 +844,7 @@ impl WindowManager {
} else if let Ok(window) = self.focused_window_mut() {
window.focus(self.mouse_follows_focus)?;
} else {
let desktop_window = Window {
hwnd: WindowsApi::desktop_window()?,
};
let desktop_window = Window::new(WindowsApi::desktop_window()?);
let rect = self.focused_monitor_size()?;
WindowsApi::center_cursor_in_rect(&rect)?;
@@ -868,7 +853,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 +987,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 +1103,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 +1213,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 +1375,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 +1402,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 +1487,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 +1706,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 +1834,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 +1868,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 +1969,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 +2150,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(())

View File

@@ -27,6 +27,7 @@ pub enum WindowManagerEvent {
Unmanage(Window),
Raise(Window),
DisplayChange(Window),
UpdateFocusedWindowBorder(Window),
}
impl Display for WindowManagerEvent {
@@ -77,6 +78,9 @@ impl Display for WindowManagerEvent {
Self::DisplayChange(window) => {
write!(f, "DisplayChange (Window: {window})")
}
Self::UpdateFocusedWindowBorder(window) => {
write!(f, "UpdateFocusedBorderWindow (Window: {window})")
}
}
}
}
@@ -97,7 +101,8 @@ impl WindowManagerEvent {
| Self::Raise(window)
| Self::Manage(window)
| Self::DisplayChange(window)
| Self::Unmanage(window) => window,
| Self::Unmanage(window)
| Self::UpdateFocusedWindowBorder(window) => window,
}
}

View File

@@ -81,6 +81,7 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use windows::Win32::UI::WindowsAndMessaging::MoveWindow;
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassA;
@@ -105,7 +106,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;
@@ -352,6 +355,20 @@ impl WindowsApi {
.process()
}
pub fn move_window(hwnd: HWND, layout: &Rect, repaint: bool) -> Result<()> {
unsafe {
MoveWindow(
hwnd,
layout.left,
layout.top,
layout.right,
layout.bottom,
repaint,
)
}
.process()
}
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
@@ -368,7 +385,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 +696,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,
std::ptr::null_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() };

View File

@@ -104,7 +104,7 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let is_minimized = WindowsApi::is_iconic(hwnd);
if is_visible && is_window && !is_minimized {
let window = Window { hwnd: hwnd.0 };
let window = Window::new(hwnd.0);
if let Ok(should_manage) = window.should_manage(None) {
if should_manage {
@@ -132,7 +132,7 @@ pub extern "system" fn win_event_hook(
return;
}
let window = Window { hwnd: hwnd.0 };
let window = Window::new(hwnd.0);
let winevent = unsafe { ::std::mem::transmute(event) };
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
@@ -196,7 +196,7 @@ pub extern "system" fn hidden_window(
unsafe {
match message {
WM_DISPLAYCHANGE => {
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
let event_type = WindowManagerEvent::DisplayChange(Window::new(window.0));
WINEVENT_CALLBACK_CHANNEL
.lock()
.0
@@ -211,7 +211,7 @@ pub extern "system" fn hidden_window(
if wparam.0 as u32 == SPI_SETWORKAREA.0
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
{
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
let event_type = WindowManagerEvent::DisplayChange(Window::new(window.0));
WINEVENT_CALLBACK_CHANNEL
.lock()
.0
@@ -224,7 +224,7 @@ pub extern "system" fn hidden_window(
WM_DEVICECHANGE => {
#[allow(clippy::cast_possible_truncation)]
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
let event_type = WindowManagerEvent::DisplayChange(Window::new(window.0));
WINEVENT_CALLBACK_CHANNEL
.lock()
.0

View File

@@ -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);
}

View File

@@ -19,11 +19,15 @@ use komorebi_core::Layout;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use crate::border::Border;
use crate::container::Container;
use crate::ring::Ring;
use crate::static_config::WorkspaceConfig;
use crate::window::Window;
use crate::windows_api::WindowsApi;
use crate::ANIMATION_ENABLED;
use crate::BORDER_HIDDEN;
use crate::BORDER_HWND;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
@@ -108,7 +112,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 +133,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)));
}
}
@@ -244,6 +248,12 @@ impl Workspace {
}
if *self.tile() {
if ANIMATION_ENABLED.load(Ordering::SeqCst) {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.hide()?;
BORDER_HIDDEN.store(true, Ordering::SeqCst);
}
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding);

View File

@@ -0,0 +1,15 @@
[package]
name = "komorebic-no-console"
version = "0.1.19"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
repository = "https://github.com/LGUG2Z/komorebi"
license = "MIT"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -0,0 +1,19 @@
#![windows_subsystem = "windows"]
use std::io;
use std::os::windows::process::CommandExt;
use std::process::Command;
const CREATE_NO_WINDOW: u32 = 0x08000000;
fn main() -> io::Result<()> {
let mut current_exe = std::env::current_exe().expect("unable to get exec path");
current_exe.pop();
let komorebic_exe = current_exe.join("komorebic.exe");
Command::new(komorebic_exe)
.args(std::env::args_os().skip(1))
.creation_flags(CREATE_NO_WINDOW)
.status()
.map(|_| ())
}

View File

@@ -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 }

File diff suppressed because it is too large Load Diff

128
schema.asc.json Normal file
View File

@@ -0,0 +1,128 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Array_of_ApplicationConfiguration",
"type": "array",
"items": {
"$ref": "#/definitions/ApplicationConfiguration"
},
"definitions": {
"ApplicationConfiguration": {
"type": "object",
"required": [
"identifier",
"name"
],
"properties": {
"float_identifiers": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifierAndComment"
}
},
"identifier": {
"$ref": "#/definitions/IdWithIdentifier"
},
"name": {
"type": "string"
},
"options": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/ApplicationOptions"
}
}
}
},
"ApplicationIdentifier": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title"
]
},
"ApplicationOptions": {
"type": "string",
"enum": [
"object_name_change",
"layered",
"border_overflow",
"tray_and_multi_window",
"force"
]
},
"IdWithIdentifier": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"$ref": "#/definitions/ApplicationIdentifier"
},
"matching_strategy": {
"anyOf": [
{
"$ref": "#/definitions/MatchingStrategy"
},
{
"type": "null"
}
]
}
}
},
"IdWithIdentifierAndComment": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"comment": {
"type": [
"string",
"null"
]
},
"id": {
"type": "string"
},
"kind": {
"$ref": "#/definitions/ApplicationIdentifier"
},
"matching_strategy": {
"anyOf": [
{
"$ref": "#/definitions/MatchingStrategy"
},
{
"type": "null"
}
]
}
}
},
"MatchingStrategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex"
]
}
}
}

View File

@@ -92,6 +92,9 @@
<Component Id='binary1' Guid='*'>
<File Id='exe1' Name='komorebic.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic.exe' KeyPath='yes' />
</Component>
<Component Id='binary2' Guid='*'>
<File Id='exe2' Name='komorebic-no-console.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic-no-console.exe' KeyPath='yes' />
</Component>
</Directory>
</Directory>
</Directory>
@@ -108,6 +111,8 @@
<ComponentRef Id='binary1' />
<ComponentRef Id='binary2' />
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
<ComponentRef Id='Path' />
</Feature>