Compare commits

...

14 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
22 changed files with 1121 additions and 133 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"

141
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"
@@ -363,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",
@@ -428,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",
]
@@ -552,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",
@@ -568,7 +568,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap 1.9.3",
"indexmap",
"slab",
"tokio",
"tokio-util",
@@ -577,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"
@@ -620,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",
@@ -691,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",
@@ -705,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"
@@ -722,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.2",
"hashbrown",
]
[[package]]
@@ -768,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",
]
@@ -865,6 +849,10 @@ dependencies = [
"windows",
]
[[package]]
name = "komorebic-no-console"
version = "0.1.19"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -896,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"
@@ -1236,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"
@@ -1247,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap 2.1.0",
"indexmap",
]
[[package]]
@@ -1497,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]]
@@ -1651,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",
@@ -2069,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",
@@ -2129,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",
@@ -2139,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",
@@ -2154,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",
@@ -2166,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",
@@ -2176,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",
@@ -2189,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",
@@ -2326,6 +2314,15 @@ dependencies = [
"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]]
name = "windows-targets"
version = "0.48.5"

View File

@@ -6,6 +6,7 @@ members = [
"komorebi",
"komorebi-core",
"komorebic",
"komorebic-no-console",
]
[workspace.dependencies]

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

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

@@ -14,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;
@@ -24,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;
@@ -127,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),
@@ -155,6 +160,7 @@ pub enum SocketMessage {
ToggleTitleBars,
AddSubscriber(String),
RemoveSubscriber(String),
ApplicationSpecificConfigurationSchema,
NotificationSchema,
SocketSchema,
StaticConfigSchema,

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,6 +16,7 @@ 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")]
@@ -26,6 +27,7 @@ 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")]
@@ -65,6 +67,7 @@ use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;
mod animation;
mod border;
mod com;
mod container;
@@ -214,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![]));
@@ -237,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);
@@ -448,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>,
}
@@ -496,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();
@@ -507,7 +516,19 @@ 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.display()
@@ -524,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)?;
}
@@ -538,7 +560,7 @@ fn main() -> Result<()> {
listen_for_commands_tcp(wm.clone(), port);
}
if opts.config.is_none() {
if static_config.is_none() {
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
if opts.await_configuration {

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;
@@ -1139,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 => {
@@ -1176,6 +1189,14 @@ 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)?;
@@ -1286,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

@@ -7,6 +7,9 @@ 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;
@@ -39,6 +42,7 @@ 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;
@@ -312,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 {
@@ -432,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()),
}
}
}
@@ -449,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);
}

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

View File

@@ -217,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;
@@ -589,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;
}
@@ -619,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
@@ -663,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)?)
}
@@ -708,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;
@@ -725,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(())
}
}
@@ -843,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)?;

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

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

@@ -21,6 +21,7 @@ use color_eyre::Result;
use fs_tail::TailedFile;
use heck::ToKebabCase;
use komorebi_core::resolve_home_path;
use komorebi_core::EaseEnum;
use lazy_static::lazy_static;
use paste::paste;
use sysinfo::SystemExt;
@@ -627,33 +628,52 @@ struct ActiveWindowBorderOffset {
offset: i32,
}
#[derive(Parser, AhkFunction)]
struct Animation {
#[clap(value_enum)]
boolean_state: BooleanState,
}
#[derive(Parser, AhkFunction)]
struct AnimationDuration {
/// Desired animation durations in ms
duration: u64,
}
#[derive(Parser, AhkFunction)]
struct AnimationEase {
/// Desired ease function for animation
#[clap(value_enum, short, long, default_value = "linear")]
ease_func: EaseEnum,
}
#[derive(Parser, AhkFunction)]
#[allow(clippy::struct_excessive_bools)]
struct Start {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(action, short, long = "ffm")]
#[clap(short, long = "ffm")]
ffm: bool,
/// Path to a static configuration JSON file
#[clap(action, short, long)]
#[clap(short, long)]
config: Option<PathBuf>,
/// 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>,
/// Start whkd in a background process
#[clap(action, long)]
#[clap(long)]
whkd: bool,
/// Start autohotkey configuration file
#[clap(action, long)]
#[clap(long)]
ahk: bool,
}
#[derive(Parser, AhkFunction)]
struct Stop {
/// Stop whkd if it is running as a background process
#[clap(action, long)]
#[clap(long)]
whkd: bool,
}
@@ -719,15 +739,15 @@ struct AltFocusHack {
struct EnableAutostart {
/// Path to a static configuration JSON file
#[clap(action, short, long)]
config: PathBuf,
config: Option<PathBuf>,
/// Enable komorebi's custom focus-follows-mouse implementation
#[clap(action, short, long = "ffm")]
#[clap(short, long = "ffm")]
ffm: bool,
/// Enable autostart of whkd
#[clap(action, long)]
#[clap(long)]
whkd: bool,
/// Enable autostart of ahk
#[clap(action, long)]
#[clap(long)]
ahk: bool,
}
@@ -1059,6 +1079,15 @@ enum SubCommand {
/// Set the offset for the active window border
#[clap(arg_required_else_help = true)]
ActiveWindowBorderOffset(ActiveWindowBorderOffset),
/// Enable or disable the window move animation
#[clap(arg_required_else_help = true)]
Animation(Animation),
/// Set the duration for the window move animation in ms
#[clap(arg_required_else_help = true)]
AnimationDuration(AnimationDuration),
/// Set the ease function for the window move animation
#[clap(arg_required_else_help = true)]
AnimationEase(AnimationEase),
/// Enable or disable focus follows mouse for the operating system
#[clap(arg_required_else_help = true)]
FocusFollowsMouse(FocusFollowsMouse),
@@ -1087,6 +1116,9 @@ enum SubCommand {
/// Fetch the latest version of applications.yaml from komorebi-application-specific-configuration
#[clap(alias = "fetch-asc")]
FetchAppSpecificConfiguration,
/// Generate a JSON Schema for applications.yaml
#[clap(alias = "asc-schema")]
ApplicationSpecificConfigurationSchema,
/// Generate a JSON Schema of subscription notifications
NotificationSchema,
/// Generate a JSON Schema of socket messages
@@ -1096,7 +1128,6 @@ enum SubCommand {
/// Generates a static configuration JSON file based on the current window manager state
GenerateStaticConfig,
/// Generates the komorebi.lnk shortcut in shell:startup to autostart komorebi
#[clap(arg_required_else_help = true)]
EnableAutostart(EnableAutostart),
/// Deletes the komorebi.lnk shortcut in shell:startup to disable autostart
DisableAutostart,
@@ -1193,20 +1224,21 @@ fn main() -> Result<()> {
);
}
SubCommand::EnableAutostart(args) => {
let mut current_exe_dir = std::env::current_exe().expect("unable to get exec path");
current_exe_dir.pop();
let komorebic_exe = current_exe_dir.join("komorebic.exe");
let mut current_exe = std::env::current_exe().expect("unable to get exec path");
current_exe.pop();
let komorebic_exe = current_exe.join("komorebic-no-console.exe");
let komorebic_exe = dunce::simplified(&komorebic_exe);
let startup_dir = startup_dir()?;
let shortcut_file = startup_dir.join("komorebi.lnk");
let shortcut_file = dunce::simplified(&shortcut_file);
let mut arguments = format!(
"start --config {}",
dunce::canonicalize(args.config)?.display()
);
let mut arguments = String::from("start");
if let Some(config) = args.config {
arguments.push_str("--config ");
arguments.push_str(&config.to_string_lossy());
}
if args.ffm {
arguments.push_str(" --ffm");
@@ -1983,6 +2015,15 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
SubCommand::ActiveWindowBorderOffset(arg) => {
send_message(&SocketMessage::ActiveWindowBorderOffset(arg.offset).as_bytes()?)?;
}
SubCommand::Animation(arg) => {
send_message(&SocketMessage::Animation(arg.boolean_state.into()).as_bytes()?)?;
}
SubCommand::AnimationDuration(arg) => {
send_message(&SocketMessage::AnimationDuration(arg.duration).as_bytes()?)?;
}
SubCommand::AnimationEase(arg) => {
send_message(&SocketMessage::AnimationEase(arg.ease_func).as_bytes()?)?;
}
SubCommand::ResizeDelta(arg) => {
send_message(&SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
}
@@ -2095,6 +2136,11 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
output_file.display()
);
}
SubCommand::ApplicationSpecificConfigurationSchema => {
with_komorebic_socket(|| {
send_message(&SocketMessage::ApplicationSpecificConfigurationSchema.as_bytes()?)
})?;
}
SubCommand::NotificationSchema => {
with_komorebic_socket(|| send_message(&SocketMessage::NotificationSchema.as_bytes()?))?;
}

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>