Compare commits

...

172 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
LGUG2Z
ef61239580 chore(release): v0.1.19 2023-11-10 16:23:53 -08:00
LGUG2Z
96bf37b98d fix(config): disable tiling for ws without layouts
This commit ensures that when a layout or a custom layout is not defined
for a workspace in the static configuration file, Workspace.tile will be
set to false. Thanks to M. Kichel on Discord for pointing out the need
for this!
2023-11-10 12:47:21 -08:00
LGUG2Z
5fd90d222d fix(docs): quote $env:userprofile in quickstart to handle spaces 2023-11-08 07:58:25 -08:00
dependabot[bot]
6ffdc1e90e chore(deps): bump which from 4.4.2 to 5.0.0
Bumps [which](https://github.com/harryfei/which-rs) from 4.4.2 to 5.0.0.
- [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/harryfei/which-rs/commits)

---
updated-dependencies:
- dependency-name: which
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 11:11:35 -08:00
LGUG2Z
2595fa601f fix(config): typo in layered app id loading 2023-11-04 12:08:36 -07:00
LGUG2Z
41cd5ec222 chore(deps): cargo update 2023-11-03 09:46:37 -07:00
dependabot[bot]
e75ab17602 chore(deps): bump serde from 1.0.188 to 1.0.190
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.188 to 1.0.190.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.188...v1.0.190)

---
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-03 09:29:18 -07:00
dependabot[bot]
79dfa45ec4 chore(deps): bump clap from 4.4.6 to 4.4.7
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.6 to 4.4.7.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.6...v4.4.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-03 09:29:09 -07:00
dependabot[bot]
8771aab3f3 chore(deps): bump quote from 1.0.30 to 1.0.33 (#573)
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.30 to 1.0.33.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.30...1.0.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-03 09:28:47 -07:00
LGUG2Z
f659deb5e3 fix(wm): correct border overflow handling logic
This commit fixes a regression introduced in the regex rule matching
refactor.

Invisible borders should be removed from applications that are not
identified as border overflow applications, not vice versa.

This is because applications that are overflowing their borders
effectively erase the invisible borders, while applications that are not
overflowing their borders leave the system's invisible borders
visibility intact.

It is this latter group that we should be targeting with the
should_remove_border variable.
2023-11-03 09:24:43 -07:00
Kushashwa Ravi Shrimali
e9fb9297e9 feat(cli): add cycle-layout command (#556)
* Command to ToggleLayout

* Just improve logic of figuring out next layout

* Addr review: rename to "Cycle" instead of toggle, and add a small comment

* As per review comments, implement cycle method on DefaultLayout

* I forgot to remove this, my bad

* feat(cli): fixups for cycle-layout cmd

* Update komorebic/src/main.rs

Co-authored-by: Kushashwa Ravi Shrimali <kushashwaravishrimali@gmail.com>

---------

Co-authored-by: LGUG2Z <jadeiqbal@fastmail.com>
Co-authored-by: جاد <LGUG2Z@users.noreply.github.com>
2023-10-26 21:19:24 -07:00
جاد
557d1962a7 Merge pull request #565 from LGUG2Z/dependabot/cargo/proc-macro2-1.0.69
chore(deps): bump proc-macro2 from 1.0.64 to 1.0.69
2023-10-26 16:00:56 -07:00
جاد
d7991d2087 Merge pull request #566 from LGUG2Z/dependabot/cargo/serde_json-1.0.107
chore(deps): bump serde_json from 1.0.103 to 1.0.107
2023-10-26 15:58:33 -07:00
جاد
5606f1c5c4 Merge pull request #567 from LGUG2Z/dependabot/cargo/ctrlc-3.4.1
chore(deps): bump ctrlc from 3.4.0 to 3.4.1
2023-10-26 15:58:17 -07:00
dependabot[bot]
41a627e7dd chore(deps): bump ctrlc from 3.4.0 to 3.4.1
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.0...3.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 14:25:46 +00:00
dependabot[bot]
2b49ade205 chore(deps): bump serde_json from 1.0.103 to 1.0.107
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.103 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.103...v1.0.107)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 14:25:38 +00:00
dependabot[bot]
a0bebc7be8 chore(deps): bump proc-macro2 from 1.0.64 to 1.0.69
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.64 to 1.0.69.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.64...1.0.69)

---
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-10-23 14:25:29 +00:00
جاد
038256de6b fix(docs): typo whkrc -> whkdrc 2023-10-22 17:34:56 -07:00
sitiom
8c30b894fe ci(winget): change Winget Releaser job to ubuntu-latest (#518) 2023-10-22 10:58:33 -07:00
LGUG2Z
fce86397a5 feat(cli): add autostart commands
This commit introduce two new commands, enable-autostart and
disable-autostart, to help create shortcuts for users in shell:startup
to automatically start komorebi after logging in.
2023-10-22 10:57:05 -07:00
dependabot[bot]
f55b10caa0 chore(deps): bump sysinfo from 0.29.9 to 0.29.10 (#561)
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.9 to 0.29.10.
- [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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 08:24:45 -07:00
dependabot[bot]
ce9d23e72e chore(deps): bump tracing from 0.1.37 to 0.1.39 (#562)
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.37 to 0.1.39.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.37...tracing-0.1.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 08:24:33 -07:00
dependabot[bot]
d79e54fad8 chore(deps): bump rustix from 0.37.23 to 0.37.25 (#563)
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.23 to 0.37.25.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.37.23...v0.37.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 08:10:44 -07:00
dependabot[bot]
a15c769bfc chore(deps): bump reqwest from 0.11.18 to 0.11.22 (#559)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.18 to 0.11.22.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.18...v0.11.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 08:58:59 -07:00
dependabot[bot]
89aa0387d7 chore(deps): bump winreg from 0.50.0 to 0.51.0 (#558)
Bumps [winreg](https://github.com/gentoo90/winreg-rs) from 0.50.0 to 0.51.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.50.0...v0.51.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 08:20:04 -07:00
LGUG2Z
1e23a11b94 docs(readme): update badges 2023-10-08 11:11:03 -07:00
Artemiy
e5a8146927 feat(wm): ultrawide layout resizing (#545)
* Allow different resize constraints for layouts

Change Workspace::enforce_resize_constraints to enforce constraints differently for different layouts
Add enforce_no_resize method for all but bsp layout resize_dimensions

* Add resize constraints for UltrawideVerticalStack layout

Add Workspace::enforce_resize_for_ultrawide method to apply resize
constraints for ultrawide vertical stack layout.

* feat(wm): Use resize_dimensions in calculate for ultrawide layout

Add function calculate_ultrawide_adjustment to calculate adjustments for
individual containers in ultrawide vertical stack layout
Refactor ultrawide layout generation in separate function and use
calculated adjustments

* feat(wm): Enable ultrawide layout in DefaultLayout::resize

* feat(wm): refactor ultrawide resize calculation

Add some helper function and descriptive variable names in calculate_ultrawide_adjustment
Apply clippy lints
2023-10-06 17:33:37 -07:00
LGUG2Z
b048e7c3aa feat(rules): implement all matching strategies
This commit ensures that matching strategies can be used wherever
IdWithIdentifier is used, and that they are respected for users opting
to use the static configuration file format.

Some thought and planning needs to go into how this can be backported to
dynamic configurations via the CLI without breaking existing user
configurations.

re #60
2023-10-06 12:14:58 -07:00
dependabot[bot]
3ff3961381 chore(deps): bump clap from 4.4.2 to 4.4.6
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.2 to 4.4.6.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.4.2...v4.4.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 08:10:15 -07:00
LGUG2Z
0797316ee6 feat(rules): add explicit matching strategies
This commit is the first in a series of commits which will pave the way
for regex rule matching support in komorebi.

For now, in order to maintain backwards compat and not break anything,
all rules without a matching strategy will get assigned as using the
"Legacy" strategy.

This and the "Equals" strategy are the only two which have been
implemented so far.

There should not be any breaking changes in this commit, not any
functionality lost for users with pre-existing configurations.

re #60
2023-09-22 15:05:20 -07:00
LGUG2Z
57cc02f083 fix(config): align border opts in st/dyn configs
This commit aligns the border option naming and arguments between the
dynamic and static configuration approaches.

The previously named border_width and border_offset options in the
static config will be replaced by active_window_border_width and
active_window_border_offset in v0.1.19.

Similarly the option for the offset will now take a single signed
integer, as it does when using the komorebic command.

re #526
2023-09-21 12:32:30 -07:00
LGUG2Z
45912745d6 chore(deps): bump windows crates from 0.48 to 0.51 2023-09-12 09:55:09 -07:00
dependabot[bot]
0088ca8cdb chore(deps): bump serde from 1.0.185 to 1.0.188
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.185 to 1.0.188.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.185...v1.0.188)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 16:36:59 -07:00
dependabot[bot]
6c912b660e chore(deps): bump clap from 4.3.19 to 4.4.2
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.19 to 4.4.2.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.3.19...v4.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 16:36:50 -07:00
dependabot[bot]
f02b7229fd chore(deps): bump serde_yaml from 0.9.23 to 0.9.25
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.23 to 0.9.25.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.23...0.9.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 16:36:41 -07:00
LGUG2Z
ffaab771f3 fix(cli): remedy regression in start cmd
This commit remedies a regression noticed by user @notkvwu in #493,
which results in 'komorebic start' failing, due to an empty ArgumentList
being passed to the PS Start-Process command.

This has been fixed by ensuring that the ArgumentList is only passed
when the user has specified flags on the start command.

fix #493
2023-09-11 13:33:11 -07:00
LGUG2Z
710a8d76ea feat(cli): add ahk flag to static config start cmd
This commit introduces a new --ahk flag to the start command, which
works similarly to the --whkd, and attempts to load an ahk key binding
configuration script on startup.

The attempt is made from within the komorebic process, rather than the
komorebi process.

resolve #529
2023-09-11 11:26:35 -07:00
dependabot[bot]
09792a7924 chore(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 15:13:05 -07:00
dependabot[bot]
c1061dd3be chore(deps): bump serde from 1.0.179 to 1.0.185
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.179 to 1.0.185.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.179...v1.0.185)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 15:09:46 -07:00
dependabot[bot]
f8a096090f chore(deps): bump quote from 1.0.29 to 1.0.30
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.29 to 1.0.30.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.29...1.0.30)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 15:09:39 -07:00
dependabot[bot]
e4ccd45176 chore(deps): bump sysinfo from 0.29.7 to 0.29.9
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.7 to 0.29.9.
- [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-09-10 15:09:31 -07:00
LGUG2Z
f4e0fb9469 feat(cli): add opt to stop whkd alongside wm
This commit adds a '--whkd' flag to the 'komorebic stop' command, which
will also stop any running instance of whkd when stopping the window
manager.

This flag defaults to false, and should not change the existing
behaviour that users are used to.
2023-09-09 22:02:44 -07:00
LGUG2Z
d8892f3ff2 feat(config): add monitor index prefs to static
This commit adds an "monitor_index_preferences" key to the static config
schema, which was missed during the initial rollout of the static
configuration files. To help with testing, these indexes have also been
exposed on the State struct.

resolve #522
2023-09-01 08:59:22 -07:00
dependabot[bot]
fec56b1971 chore(deps): bump clap from 4.3.12 to 4.3.19
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.12 to 4.3.19.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.3.12...v4.3.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-10 10:54:43 -07:00
dependabot[bot]
15aedf1ead chore(deps): bump serde from 1.0.171 to 1.0.179
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.171 to 1.0.179.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.171...v1.0.179)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-10 10:54:31 -07:00
dependabot[bot]
86ed7e1297 chore(deps): bump sysinfo from 0.29.4 to 0.29.7
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.4 to 0.29.7.
- [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-08-10 10:54:17 -07:00
p1scescom
c132665ac7 fix 2023-08-02 18:22:37 +09:00
Matt Blacker
cfe4062369 fix(config): gen border colours only if set 2023-07-19 08:10:42 -07:00
Matt Blacker
acf201d9bc feat(config): border colours in static config gen 2023-07-19 08:09:16 -07:00
dependabot[bot]
fa4d14799e chore(deps): bump paste from 1.0.13 to 1.0.14
Bumps [paste](https://github.com/dtolnay/paste) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/dtolnay/paste/releases)
- [Commits](https://github.com/dtolnay/paste/compare/1.0.13...1.0.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 18:10:16 -07:00
dependabot[bot]
6979f4e699 chore(deps): bump serde_json from 1.0.102 to 1.0.103
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.102 to 1.0.103.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.102...v1.0.103)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 18:10:07 -07:00
dependabot[bot]
d3585ad720 chore(deps): bump serde_yaml from 0.9.22 to 0.9.23
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.22 to 0.9.23.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.22...0.9.23)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 18:09:58 -07:00
LGUG2Z
e240bc7706 docs(readme): add static conf migration tutorial video 2023-07-15 16:19:45 -07:00
LGUG2Z
0ed732d085 chore(release): v0.1.18 2023-07-14 14:58:58 -07:00
Johnny Nguyen
fa11594103 fix(cli): support for spaces in config path 2023-07-14 14:57:29 -07:00
LGUG2Z
88aaf1f368 docs(readme): add new quickstart video 2023-07-14 11:27:32 -07:00
LGUG2Z
c5bf62fac0 docs(readme): add note on enabling long paths 2023-07-14 11:04:53 -07:00
LGUG2Z
528a0bf9ff docs(readme): fix typo in quickstart 2023-07-14 09:06:58 -07:00
LGUG2Z
f73d1bf0da chore(release): v0.1.17 2023-07-13 08:58:38 -07:00
LGUG2Z
4510cccc3e feat(config): add static json loader and whkd flag
This commit is an implementation of a static JSON configuration loader.

An example komorebi.json configuration file has been added.

The application-specific configurations can be loaded directly from a
file, and workspace configuration can be defined declaratively in the
JSON. Individual rules etc. can also be added directly in the static
configuration as one-offs.

A JSONSchema can be generated using komorebic's static-config-schema
command. This should be added to something like SchemaStore later.

Loading from static configuration is significantly faster on startup, as
the lock does not have to be reacquired for every command that is sent
over the socket.

When loading configuration from a static JSON file, a hotwatch instance
will automatically be created to listen to file changes and apply any
updates to both the global and window manager configuration state.

A new --whkd flag has been added to the komorebic start command to
optionally start whkd in a background process.

A new komorebic command 'generate-static-config' has been added to help
existing users migrate to a static JSON config file. Currently, custom
layout file path information can not be automatically populated in the
output of this command and must be added manually by the user if
required.

A new komorebic command 'fetch-asc' has been added to help users update
to the latest version of the application-specific configurations
in-place.

resolve #427
2023-07-13 08:12:43 -07:00
dependabot[bot]
b5035fbb5d chore(deps): bump sysinfo from 0.29.2 to 0.29.4
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.2 to 0.29.4.
- [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-07-11 08:57:24 -07:00
dependabot[bot]
1a703690c9 chore(deps): bump serde_yaml from 0.9.21 to 0.9.22
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.21 to 0.9.22.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.21...0.9.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-11 08:57:14 -07:00
dependabot[bot]
039f69b70b chore(deps): bump clap from 4.3.8 to 4.3.11
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.8 to 4.3.11.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.3.8...v4.3.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-11 08:57:02 -07:00
LGUG2Z
f7dccfe0f5 fix(wm): lars' secret sauce for min-width apps
This commit applies a technique shared by Lars from GlazeWM and first
noticed by J.SH on Discord to add the SWP_NOSENDCHANGING flag when
positioning windows to overcome any hard-coded minimum width
restrictions an application might have.
2023-07-09 19:44:44 -07:00
dependabot[bot]
fc7198823b chore(deps): bump quote from 1.0.28 to 1.0.29
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.28 to 1.0.29.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.28...1.0.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 18:14:41 -07:00
dependabot[bot]
c3637665e9 chore(deps): bump serde from 1.0.164 to 1.0.165
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.164 to 1.0.165.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.164...v1.0.165)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 18:14:30 -07:00
dependabot[bot]
45046a0e1e chore(deps): bump net2 from 0.2.38 to 0.2.39
Bumps [net2](https://github.com/deprecrated/net2-rs) from 0.2.38 to 0.2.39.
- [Commits](https://github.com/deprecrated/net2-rs/compare/0.2.38...0.2.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 18:14:20 -07:00
Mitsuhiro Tanda
63dee21257 fix(wm): refer AHK_EXE 2023-07-06 18:14:02 -07:00
LGUG2Z
2675f747bb fix(wm): apply custom layouts when containers >= columns
Thanks to @thesobercoder and @olivoil for opening my eyes on this one.

This commit reduces the number of containers required before a custom
layout can be triggered. Please see the closed issue for more discussion
and rationale behind this change.

fix #390
2023-07-01 11:32:52 -07:00
dependabot[bot]
5b9730823e chore(deps): bump strum from 0.24.1 to 0.25.0
Bumps [strum](https://github.com/Peternator7/strum) from 0.24.1 to 0.25.0.
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 11:39:42 -07:00
dependabot[bot]
84da706d64 chore(deps): bump proc-macro2 from 1.0.59 to 1.0.63
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.59 to 1.0.63.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.59...1.0.63)

---
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-06-26 11:39:16 -07:00
dependabot[bot]
cf7a01695e chore(deps): bump clap from 4.3.4 to 4.3.8
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.4 to 4.3.8.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.3.4...v4.3.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 11:39:03 -07:00
Yusuf007R
510650cb94 refactor(wm): renaming SwapWorkspacesToMonitor to SwapWorkspacesWithMonitor 2023-06-24 07:04:07 -07:00
Yusuf007R
6fe2290d74 feat(swap-workspaces-monitor): command to swap focused monitor workspaces with another monitor
Basically this commit adds a command that allows you to swap two monitors, well it actually swaps
the workspaces between the monitors.
2023-06-24 07:04:07 -07:00
dependabot[bot]
647f5b7ded chore(deps): bump sysinfo from 0.29.0 to 0.29.2
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.0 to 0.29.2.
- [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-06-22 08:51:22 -07:00
dependabot[bot]
933122b073 chore(deps): bump serde from 1.0.163 to 1.0.164
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.163 to 1.0.164.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.163...v1.0.164)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 08:51:09 -07:00
dependabot[bot]
9b62d5408d chore(deps): bump clap from 4.3.1 to 4.3.4
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.1 to 4.3.4.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.3.1...v4.3.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 08:50:58 -07:00
LGUG2Z
087b08612d feat(wm): allow f32 width % for custom layouts
This commit allows users to provide an f32 value for the WidthPercentage
on the primary column of a custom layout.
2023-06-22 08:49:47 -07:00
LGUG2Z
c4be0636f7 fix(wm): update border on cycle-move
This commit ensures that the active window border is updated when the
cycle-move command is called.

fix #467
2023-06-14 07:49:47 -07:00
LGUG2Z
6df91d7d40 fix(wm): manually close process handles
ZiN on Discord noted that handles returned by OpenProcess must be closed
manually (this is not implemented in the Drop trait for handle, it
seems)

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess#remarks
2023-06-12 16:47:12 -07:00
dependabot[bot]
b0737ff603 chore(deps): bump quote from 1.0.27 to 1.0.28
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.27 to 1.0.28.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.27...1.0.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 08:39:50 -07:00
dependabot[bot]
8ff0963203 chore(deps): bump clap from 4.3.0 to 4.3.1
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.3.0...clap_complete-v4.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 08:39:42 -07:00
dependabot[bot]
ce326f31bc chore(deps): bump ctrlc from 3.3.1 to 3.4.0
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.3.1...3.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 08:39:15 -07:00
LGUG2Z
2050d9a3fa fix(wm): add permaignore_classes for electron jank 2023-06-05 09:58:37 -07:00
LGUG2Z
8e47bfdba6 fix(wm): allow any id to override a float rule 2023-06-05 08:45:55 -07:00
LGUG2Z
9103ce2b2b feat(wm): add focus-workspaces cmd
This commit adds a new command, focus-workspaces, to allow the user to
change workspaces across all monitors at the same time. I'm not
convinced of the stability of this command and I would strongly
discourage using komorebi in this manner.

resolve #426
2023-06-03 17:30:43 -07:00
dependabot[bot]
7ba7067c96 chore(deps): bump proc-macro2 from 1.0.57 to 1.0.59
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.57 to 1.0.59.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.57...1.0.59)

---
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-05-31 07:00:19 -07:00
dependabot[bot]
a51f2387aa chore(deps): bump bitflags from 2.2.1 to 2.3.1
Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.2.1 to 2.3.1.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.2.1...2.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-31 07:00:07 -07:00
dependabot[bot]
e4189c19ce chore(deps): bump clap from 4.2.7 to 4.3.0
Bumps [clap](https://github.com/clap-rs/clap) from 4.2.7 to 4.3.0.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.2.7...clap_complete-v4.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-31 06:59:56 -07:00
dependabot[bot]
1a2be3bc02 chore(deps): bump ctrlc from 3.2.5 to 3.3.1
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.2.5 to 3.3.1.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.2.5...3.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 08:14:43 -07:00
LGUG2Z
c37ba42825 feat(wm): reintroduce remove-title-bar cmds
This commit reintroduces some old code from the feature/remove-titlebars
branch. This feature is very unstable and it is strongly advised that
nobody actually uses it. Wherever possible, please use the "remove
titlebar" functionality provided directly within an application.
2023-05-15 13:49:45 -07:00
dependabot[bot]
d0c081feae chore(deps): bump proc-macro2 from 1.0.56 to 1.0.57
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.56 to 1.0.57.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.56...1.0.57)

---
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-05-15 08:54:00 -07:00
dependabot[bot]
0027c7d1de chore(deps): bump serde from 1.0.162 to 1.0.163
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.162 to 1.0.163.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.162...v1.0.163)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 08:53:43 -07:00
LGUG2Z
096729c2bd chore(release): v0.1.16 2023-05-08 08:58:16 -07:00
LGUG2Z
de5efd9b35 docs(asc): update generated rules 2023-05-08 08:56:39 -07:00
dependabot[bot]
294c14e6a6 chore(deps): bump sysinfo from 0.28.4 to 0.29.0
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.28.4 to 0.29.0.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 08:48:24 -07:00
dependabot[bot]
4d26cdee32 chore(deps): bump serde from 1.0.160 to 1.0.162
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.160 to 1.0.162.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.160...1.0.162)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 08:48:10 -07:00
dependabot[bot]
6748d7e4a9 chore(deps): bump clap from 4.2.5 to 4.2.7
Bumps [clap](https://github.com/clap-rs/clap) from 4.2.5 to 4.2.7.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.2.5...v4.2.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 08:47:58 -07:00
dependabot[bot]
7f350341bb chore(deps): bump clap from 4.2.4 to 4.2.5
Bumps [clap](https://github.com/clap-rs/clap) from 4.2.4 to 4.2.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.2.4...v4.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 10:31:56 -07:00
dependabot[bot]
33dcadfce3 chore(deps): bump dirs from 5.0.0 to 5.0.1
Bumps [dirs](https://github.com/soc/dirs-rs) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/soc/dirs-rs/releases)
- [Commits](https://github.com/soc/dirs-rs/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 10:31:42 -07:00
dependabot[bot]
52236679a5 chore(deps): bump bitflags from 1.3.2 to 2.2.1
Bumps [bitflags](https://github.com/bitflags/bitflags) from 1.3.2 to 2.2.1.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/1.3.2...2.2.1)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 06:08:39 -07:00
LGUG2Z
9431bac4ad docs(readme): add section on using ahk 2023-04-30 13:06:45 -07:00
LGUG2Z
e4a9719f4f feat(ahk): add sample v2 configuration file 2023-04-30 13:06:45 -07:00
LGUG2Z
e044a5a16f fix(ahk): implement syntax corrections by @sitiom
This commit implements the suggestions made by @sitiom on Discord:
https://discord.com/channels/898554690126630914/898554690608967786/1097403455267667968

re #324
2023-04-30 13:06:45 -07:00
LGUG2Z
60d3ecd8aa feat(ahk): update cfgen, reintroduce ahk files
This commit updates the config generator used by the ahk-asc command to
emit AHKv2 syntax.

An AHKv2 syntax-compatible komorebic.lib.ahk has been (re)introduced to
the repo root as a file to be distributed. This file is created by
taking the AHKv1 syntax output of ahk-library and automatically
converting it to AHKv2 using the automatic script converter by @mmikeww
available on GitHub.

Given that ahk-library is still being used to emit AHKv1 syntax in this
pipeline, it will remain in the repo.

The justfile has been updated to automate as much of this as possible
(the converter still needs to be run manually).

re #324
2023-04-30 13:06:45 -07:00
Alvin Truong
27e0758089 feat(workspace-rules): add InitialWorkspaceRule and InitialNamedWorkspaceRule command
- Added the two new commands to prevent breaking changes due to AHK
  users.
2023-04-30 12:40:01 -07:00
Alvin Truong
7d1aef8203 refactor(workspace_rules): reduce code duplication 2023-04-30 12:40:01 -07:00
Alvin Truong
b273617f44 style: cargo +nightly fmt --all 2023-04-30 12:40:01 -07:00
Alvin Truong
4306a7bafe feat(workspace-rule): add ability to only apply workspace rule on first
show of app
2023-04-30 12:40:01 -07:00
LGUG2Z
b647fdf01a feat(cli): add check command for useful info
This commit adds a new komorebic command, check, to output useful
information that will aid in understanding unexpected behaviour reported
by users.
2023-04-28 17:43:27 -07:00
LGUG2Z
9f16cb91a9 docs(readme): embed more tutorial videos 2023-04-26 11:53:22 -07:00
LGUG2Z
2520c4abe1 feat(wm): add border colour for monocle
This commit adds a new WindowKind to allow the user to set a unique
colour to identify when a window container in monocle mode is active.

resolve #404
2023-04-24 13:08:29 -07:00
dependabot[bot]
d32661ec2d chore(deps): bump tracing-subscriber from 0.3.16 to 0.3.17
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.16 to 0.3.17.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.16...tracing-subscriber-0.3.17)

---
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-04-24 08:20:03 -07:00
dependabot[bot]
9df99f28cf chore(deps): bump clap from 4.2.2 to 4.2.4
Bumps [clap](https://github.com/clap-rs/clap) from 4.2.2 to 4.2.4.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.2.2...v4.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 08:19:44 -07:00
جاد
7327bb9a70 docs(readme): add link to quickstart video on youtube 2023-04-20 12:28:28 -07:00
dependabot[bot]
b9a9d20c66 chore(deps): bump dirs from 4.0.0 to 5.0.0
Bumps [dirs](https://github.com/soc/dirs-rs) from 4.0.0 to 5.0.0.
- [Release notes](https://github.com/soc/dirs-rs/releases)
- [Commits](https://github.com/soc/dirs-rs/commits)

---
updated-dependencies:
- dependency-name: dirs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 08:44:42 -07:00
Alex Rumak
f89224c5d4 Fix default whkd configuration file
The whkd process does not start with an s - unless something is wrong or buggy on my computer, it is just "whkd.exe" and so it should be that in this file as well.
2023-04-16 13:19:24 -07:00
LGUG2Z
e68cf6fa91 chore(deps): bump windows crates from 0.44 to 0.48 2023-04-15 21:47:37 -07:00
LGUG2Z
b50326ed27 chore(deps): cargo update 2023-04-15 21:28:06 -07:00
LGUG2Z
66f2395840 fix(wm): update border correctly on (un)stack
This commit ensures that when active, the border is drawn correctly
after the stack and unstack commands.

fix #396
2023-04-15 21:26:38 -07:00
Alvin Truong
7828c403ba fix(komorebic): global-work-area-offset duplicate command and alias runtime error 2023-04-15 19:54:18 -07:00
dependabot[bot]
366cd4ff91 chore(deps): bump winreg from 0.11.0 to 0.50.0
Bumps [winreg](https://github.com/gentoo90/winreg-rs) from 0.11.0 to 0.50.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.11.0...v0.50.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-04-10 14:00:00 -07:00
dependabot[bot]
a3ee513003 chore(deps): bump os_info from 3.6.0 to 3.7.0
Bumps [os_info](https://github.com/stanislav-tkach/os_info) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/stanislav-tkach/os_info/releases)
- [Changelog](https://github.com/stanislav-tkach/os_info/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stanislav-tkach/os_info/compare/v3.6.0...v3.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 13:59:17 -07:00
dependabot[bot]
0de3fa62f0 chore(deps): bump proc-macro2 from 1.0.54 to 1.0.56
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.54 to 1.0.56.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.54...1.0.56)

---
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-04-04 08:58:44 -07:00
dependabot[bot]
aadf80f3be chore(deps): bump clap from 4.1.13 to 4.2.1
Bumps [clap](https://github.com/clap-rs/clap) from 4.1.13 to 4.2.1.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.1.13...v4.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-04 08:58:27 -07:00
dependabot[bot]
0a3f27d5ad chore(deps): bump sysinfo from 0.28.1 to 0.28.4
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.28.1 to 0.28.4.
- [Release notes](https://github.com/GuillaumeGomez/sysinfo/releases)
- [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-04-04 08:57:59 -07:00
dependabot[bot]
9c5b380412 chore(deps): bump serde_json from 1.0.93 to 1.0.94
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.93 to 1.0.94.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.93...v1.0.94)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-28 12:18:53 -07:00
dependabot[bot]
587c5a2636 chore(deps): bump serde_yaml from 0.9.17 to 0.9.19
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.17 to 0.9.19.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.17...0.9.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-28 12:18:42 -07:00
dependabot[bot]
b5ca0bfd45 chore(deps): bump paste from 1.0.11 to 1.0.12
Bumps [paste](https://github.com/dtolnay/paste) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/dtolnay/paste/releases)
- [Commits](https://github.com/dtolnay/paste/compare/1.0.11...1.0.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-28 12:18:28 -07:00
dependabot[bot]
30fc1ef538 chore(deps): bump crossbeam-utils from 0.8.14 to 0.8.15
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.14 to 0.8.15.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.14...crossbeam-utils-0.8.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 16:47:05 -07:00
dependabot[bot]
76d0a38165 chore(deps): bump clap from 4.1.6 to 4.1.13
Bumps [clap](https://github.com/clap-rs/clap) from 4.1.6 to 4.1.13.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.1.6...v4.1.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 16:46:47 -07:00
dependabot[bot]
34d65dddba chore(deps): bump syn from 1.0.107 to 1.0.109
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.107 to 1.0.109.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.107...1.0.109)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 13:26:53 -08:00
dependabot[bot]
a8e7f02b0a chore(deps): bump sysinfo from 0.28.0 to 0.28.1
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.28.0 to 0.28.1.
- [Release notes](https://github.com/GuillaumeGomez/sysinfo/releases)
- [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-02-27 13:26:33 -08:00
dependabot[bot]
1a59b3a2e3 chore(deps): bump schemars from 0.8.11 to 0.8.12
Bumps [schemars](https://github.com/GREsau/schemars) from 0.8.11 to 0.8.12.
- [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.11...v0.8.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 13:26:12 -08:00
LGUG2Z
cd7606540a docs(whkd): set powershell as default in whkdrc
A number of people have been tripped up by not having the latest version
of PowerShell installed, which is referenced by the binary "pwsh", so
this commit sets the default .shell in whkdrc.sample to "powershell"
which _should_ come installed with every version of Windows since
Windows 10.

resolve #365
2023-02-27 13:24:49 -08:00
LGUG2Z
f41e8c151e feat(wm): add cycle move/send to monitor cmds
This commit introduces cyclical commands for moving (with focus) or
sending (without focus) windows between adjacent monitors.

resolve #363
2023-02-24 16:59:16 -08:00
dependabot[bot]
8ce49f5868 chore(deps): bump winreg from 0.10.1 to 0.11.0
Bumps [winreg](https://github.com/gentoo90/winreg-rs) from 0.10.1 to 0.11.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.10.1...v0.11.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-02-20 08:29:38 -08:00
dependabot[bot]
72d20d5745 chore(deps): bump sysinfo from 0.27.7 to 0.28.0
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.27.7 to 0.28.0.
- [Release notes](https://github.com/GuillaumeGomez/sysinfo/releases)
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 08:29:10 -08:00
dependabot[bot]
38686a1167 chore(deps): bump clap from 4.1.4 to 4.1.6
Bumps [clap](https://github.com/clap-rs/clap) from 4.1.4 to 4.1.6.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.1.4...v4.1.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 08:28:25 -08:00
LGUG2Z
d9648ddd0c fix(readme): correct whkdrc locations in quickstart 2023-02-19 09:05:15 -08:00
48 changed files with 7546 additions and 1459 deletions

3
.czrc Normal file
View File

@@ -0,0 +1,3 @@
{
"path": "cz-conventional-changelog"
}

View File

@@ -32,6 +32,20 @@ OS Name: Microsoft Windows 11 Pro
OS Version: 10.0.22000 N/A Build 22000
```
**`komorebic check` Output**
Provide the output of `komorebic check`
For example:
```
No KOMOREBI_CONFIG_HOME detected, defaulting to C:\Users\LGUG2Z
Looking for configuration files in C:\Users\LGUG2Z
No komorebi configuration found in C:\Users\LGUG2Z
If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration
```
**Additional context**
Add any other context about the problem here.

View File

@@ -28,7 +28,7 @@ jobs:
target:
- x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prep cargo dirs
@@ -104,7 +104,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
with:
version: latest
args: release --skip-validate --rm-dist --release-notes=CHANGELOG.md
args: release --skip-validate --clean --release-notes=CHANGELOG.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
@@ -116,7 +116,7 @@ jobs:
winget:
name: Publish to WinGet
runs-on: windows-latest
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')

View File

@@ -26,13 +26,19 @@ 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:
- replacements:
windows: pc-windows-msvc
amd64: x86_64
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"
format: zip
name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Arch }}-{{ .Os }}"
files:
- LICENSE
- CHANGELOG.md

1622
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,38 @@
[workspace]
resolver = "2"
members = [
"derive-ahk",
"komorebi",
"komorebi-core",
"komorebic",
"komorebic-no-console",
]
[workspace.dependencies]
windows-interface = { version = "0.52" }
windows-implement = { version = "0.52" }
dunce = "1"
dirs = "5"
color-eyre = "0.6"
[workspace.dependencies.windows]
version = "0.52"
features = [
"implement",
"Win32_System_Com",
"Win32_UI_Shell_Common", # for IObjectArray
"Win32_Foundation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_System_LibraryLoader",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_UI_Accessibility",
"Win32_UI_HiDpi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging",
"Win32_System_SystemServices"
]

154
README.md
View File

@@ -13,14 +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://jeezy.substack.com">
<img alt="Substack Read" src="https://img.shields.io/badge/Substack-Read-orange">
</a>
<a href="https://twitter.com/LGUG2Z">
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/LGUG2Z">
<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
@@ -160,8 +159,21 @@ This means that:
### Quickstart
It highly recommended that you enable support for long paths in Windows by running the following command in an
Administrator Terminal before starting with this quickstart guide:
```powershell
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
```
Make sure that you have either the [Scoop Package Manager](https://scoop.sh) or WinGet installed, then run the following
commands at a PowerShell prompt.
commands at a PowerShell prompt. If you are using WinGet, make sure that you open a new terminal window or reload your
profile after running the installation steps. Since this is not required when using `scoop`, I personally recommend that
you use `scoop` for this process.
As of v0.1.17, the quickstart recommends the use of a static configuration file. If you would like to see older versions
of this quickstart which recommend the use of dynamic configuration scripts, please refer to
the [README file of v0.1.16](https://github.com/LGUG2Z/komorebi/tree/v0.1.16).
```powershell
# if using scoop
@@ -173,25 +185,52 @@ scoop install komorebi
winget install LGUG2Z.whkd
winget install LGUG2Z.komorebi
# save the latest generated app-specific config tweaks and fixes to ~/komorebi.generated.ps1
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.generated.ps1 -OutFile $Env:USERPROFILE\komorebi.generated.ps1
# save the example configuration to ~/komorebi.json
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.19/komorebi.example.json -OutFile "$Env:USERPROFILE\komorebi.json"
# save the sample komorebi configuration file to ~/komorebi.ps1
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.sample.ps1 -OutFile $Env:USERPROFILE\komorebi.ps1
# save the latest generated app-specific config tweaks and fixes
komorebic fetch-app-specific-configuration
# ensure the ~/.config folder exists
mkdir $Env:USERPROFILE\.config -ea 0
mkdir "$Env:USERPROFILE\.config" -ea 0
# save the sample whkdrc file with key bindings to ~/.config/whkdrc
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/whkdrc -OutFile $Env:USERPROFILE\whkdrc
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.19/whkdrc.sample -OutFile "$Env:USERPROFILE\.config\whkdrc"
# start komorebi
komorebic start --await-configuration
# start komorebi and whkd
komorebic start -c "$Env:USERPROFILE\komorebi.json" --whkd
```
Thanks to [@sitiom](https://github.com/sitiom) for getting _komorebi_ added to both the popular Scoop Extras bucket and
to WinGet.
You can watch a walkthrough video of this quickstart below on YouTube.
[![Watch the quickstart walkthrough video](https://img.youtube.com/vi/hDDxtvpjpHs/hqdefault.jpg)](https://www.youtube.com/watch?v=hDDxtvpjpHs)
#### Using Autohotkey
If you would like to use Autohotkey, please make sure you have AutoHotKey v2 installed.
Generally, users who opt for AHK will have specific needs that can only be addressed by the advanced functionality of AHK,
and so they are assumed to be able to craft their own configuration files.
If you would like to try out AHK, a simple sample configuration powered by `komorebic.lib.ahk` is provided as a starting
point. This sample configuration does not take into account the use of a static configuration file; if you choose to use
a static configuration file alongside AHK, you can remove all the configuration options from your `komorebi.ahk` and use
it solely to handle hotkey bindings.
```powershell
# save the latest generated komorebic library to ~/komorebic.lib.ahk
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.19/komorebic.lib.ahk -OutFile $Env:USERPROFILE\komorebic.lib.ahk
# save the latest generated app-specific config tweaks and fixes to ~/komorebi.generated.ahk
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.19/komorebi.generated.ahk -OutFile $Env:USERPROFILE\komorebi.generated.ahk
# save the sample komorebi configuration file to ~/komorebi.ahk
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.19/komorebi.sample.ahk -OutFile $Env:USERPROFILE\komorebi.ahk
```
### GitHub Releases
Prebuilt binaries are available on the [releases page](https://github.com/LGUG2Z/komorebi/releases) in a `zip` archive.
@@ -214,7 +253,12 @@ cargo install --path komorebic --locked
### Running
Run `komorebic start --await-configuration` at a Powershell prompt, and you will see the following output:
`komorebi` can be run in two ways, using either a static configuration file or a dynamic configuration script.
The quickstart covers running with a static configuration file.
If you would like to use a dynamic configuration script, ensure that you have a `komorebi.ps1` or `komorebi.ahk` file
present, run `komorebic start --await-configuration` at a Powershell prompt, and you will see the following output:
```
Start-Process komorebi.exe -ArgumentList '--await-configuration' -WindowStyle hidden
@@ -224,15 +268,39 @@ Waiting for komorebi.exe to start...Started!
This means that `komorebi` is now running in the background, tiling all your windows, and listening for commands sent to
it by `komorebic`. You can similarly stop the process by running `komorebic stop`.
For further information on running with a dynamic configuration script, please refer to
the quickstart section in the [README file of v0.1.16](https://github.com/LGUG2Z/komorebi/tree/v0.1.16)
### Configuring
If you followed the quickstart, `komorebi` will find the sample `komorebi.ps1` file in your `$Env:USERPROFILE` directory
and automatically load it. This file also starts `whkd` using the sample `whkrc` file in your `$Env:USERPROFILE\.config`
directory.
If you followed the quickstart, `komorebi.json` will be the single place where you declaratively configure the behaviour
of the window manager. There is a [complete JSON Schema for this configuration file](schema.json) available to provide
users with auto-completions in their editors.
If you are running with a dynamic configuration script as recommended in v0.1.16 and earlier, `komorebi` will find the
sample `komorebi.ps1` file in your `$Env:USERPROFILE` directory and automatically load it. This file also starts `whkd` using the sample `whkdrc` file
in your `$Env:USERPROFILE\.config` directory.
Alternatively, if you have AutoHotKey installed and a `komorebi.ahk` file in `$Env:UserProfile` directory, `komorebi`
will automatically try to load it when starting.
#### Migrating to a Static Configuration File
If you have been using `komorebi` with a dynamic configuration script and wish to migrate to using a static
configuration file, once you have `komorebi` running in the desired configuration state, you can
run `komorebic generate-static-config`.
This will print a static configuration that mostly represents your current configuration to the terminal.
There are four configuration options that you may need to set yourself, if you make use of them:
- Custom layouts paths for workspaces
- Custom layout rules for workspaces
- The applications.yaml path
- Any individual application rules you have that are not in applications.yaml
[![Watch the tutorial video](https://img.youtube.com/vi/yqCAOJgL3C0/hqdefault.jpg)](https://www.youtube.com/watch?v=yqCAOJgL3C0)
#### Configuration with `komorebic`
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
@@ -255,6 +323,9 @@ key combinations in the `whkdrc` file.
Additionally, you may run `komorebic.exe ahk-library` to generate a helper library for AutoHotKey which wraps
every `komorebic` command in a native AHK function.
The output of this command is in AHKv1 syntax. It must be manually converted to AHKv2 syntax
using [this tool](https://github.com/mmikeww/AHK-v2-script-converter) or something similar.
If you include the generated library at the top of your `~/komorebi.ahk` configuration file, you will be able to call
any of the functions that it contains.
@@ -262,13 +333,11 @@ any of the functions that it contains.
❗️**NOTE**: This section is only relevant for people who wish to use AutoHotKey instead of [`whkd`](https://github.com/LGUG2Z/whkd).
The generated helper library for AutoHotKey currently only supports AutoHotKey v1.1.
The preferred way to install AutoHotKey for use with `komorebi` is to install it via `scoop`:
```powershell
scoop bucket add versions
scoop install autohotkey1.1
scoop install autohotkey
```
If you install AutoHotKey using a different method, the name of the executable file may differ from the name given by
@@ -310,6 +379,8 @@ exist in this folder.
#### Generating Common Application-Specific Configurations
❗️**NOTE**: This section is only relevant for people who use dynamic configuration scripts.
A curated selection of application-specific configurations can be generated to
help ease the setup for first-time users.
[`komorebi-application-specific-configuration`](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
@@ -359,10 +430,15 @@ komorebic.exe active-window-border-colour [R G B] --window-kind single
# optionally, if you want a different colour for stacks of windows
komorebic.exe active-window-border-colour [R G B] --window-kind stack
# optionally, if you want a different colour for windows in monocle mode
komorebic.exe active-window-border-colour [R G B] --window-kind monocle
```
It is important to note that the active window border will only apply to windows managed by `komorebi`.
[![Watch the tutorial video](https://img.youtube.com/vi/ywiAvoMV_gE/hqdefault.jpg)](https://www.youtube.com/watch?v=ywiAvoMV_gE)
#### Removing Gaps
If you would like to remove all gaps from a given workspace, both between windows themselves, and between the monitor edges and the windows, you can set the following two configuration options to `0` for the desired monitors and workspaces:
@@ -372,9 +448,11 @@ komorebic.exe container-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
komorebic.exe workspace-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
```
[![Watch the tutorial video](https://img.youtube.com/vi/eGr07mymgWE/hqdefault.jpg)](https://www.youtube.com/watch?v=eGr07mymgWE)
#### Multiple Layout Changes on Startup
❗️**NOTE**: If you followed the quickstart and are using the sample configurations, this is already the default behaviour.
❗️**NOTE**: This section is only relevant for people who use dynamic configuration scripts.
Depending on what is in your configuration, when `komorebi` is started, you may experience the layout rapidly being adjusted
with many retile events.
@@ -478,10 +556,12 @@ By default, the mouse will move to the center of the window when the focus is ch
behaviour is know is 'mouse follows focus'. To disable this behaviour across all workspaces, add the following command
to your configuration file:
```ahk
Run, komorebic.exe toggle-mouse-follows-focus, , Hide
```powershell
komorebic.exe mouse-follows-focus disable
```
[![Watch the tutorial video](https://img.youtube.com/vi/LBoyXQiNINc/hqdefault.jpg)](https://www.youtube.com/watch?v=LBoyXQiNINc)
#### Saving and Loading Resized Layouts
If you create a BSP layout through various resize adjustments that you want to be able to restore easily in the future,
@@ -553,6 +633,8 @@ YAML
configuration: Horizontal
```
[![Watch the tutorial video](https://img.youtube.com/vi/SgmBHKEOcQ4/hqdefault.jpg)](https://www.youtube.com/watch?v=SgmBHKEOcQ4)
#### Dynamically Changing Layouts Based on Number of Visible Window Containers
With `komorebi` it is possible to define rules to automatically change the layout on a specified workspace when a

View File

@@ -13,20 +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
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
install:
just install-komorebic
just install-komorebi
cat '~/.config/komorebi/komorebi.generated.ps1' > komorebi.generated.ps1
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

@@ -1,21 +1,18 @@
[package]
name = "komorebi-core"
version = "0.1.15"
version = "0.1.19"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[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.24", features = ["derive"] }
strum = { version = "0.25", features = ["derive"] }
schemars = "0.8"
[dependencies.windows]
version = "0.44"
features = [
"Win32_Foundation",
]
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

@@ -130,85 +130,7 @@ impl Arrangement for DefaultLayout {
layouts
}
Self::UltrawideVerticalStack => {
let mut layouts: Vec<Rect> = vec![];
let primary_right = match len {
1 => area.right,
_ => area.right / 2,
};
let secondary_right = match len {
1 => 0,
2 => area.right - primary_right,
_ => (area.right - primary_right) / 2,
};
let (primary_left, secondary_left, stack_left) = match len {
1 => (area.left, 0, 0),
2 => {
let mut primary = area.left + secondary_right;
let mut secondary = area.left;
match layout_flip {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
primary = area.left;
secondary = area.left + primary_right;
}
_ => {}
}
(primary, secondary, 0)
}
_ => {
let primary = area.left + secondary_right;
let mut secondary = area.left;
let mut stack = area.left + primary_right + secondary_right;
match layout_flip {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
secondary = area.left + primary_right + secondary_right;
stack = area.left;
}
_ => {}
}
(primary, secondary, stack)
}
};
if len >= 1 {
layouts.push(Rect {
left: primary_left,
top: area.top,
right: primary_right,
bottom: area.bottom,
});
if len >= 2 {
layouts.push(Rect {
left: secondary_left,
top: area.top,
right: secondary_right,
bottom: area.bottom,
});
if len > 2 {
layouts.append(&mut rows(
&Rect {
left: stack_left,
top: area.top,
right: secondary_right,
bottom: area.bottom,
},
len - 2,
));
}
}
}
layouts
}
Self::UltrawideVerticalStack => ultrawide(area, len, layout_flip, resize_dimensions),
};
dimensions
@@ -231,7 +153,7 @@ impl Arrangement for CustomLayout {
let mut dimensions = vec![];
let container_count = len.get();
if container_count <= self.len() {
if container_count < self.len() {
let mut layouts = columns(area, container_count);
dimensions.append(&mut layouts);
} else {
@@ -586,3 +508,190 @@ fn recursive_fibonacci(
res
}
}
fn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
let len = resize_dimensions.len();
let mut result = vec![Rect::default(); len];
match len {
// One container can't be resized
0 | 1 => (),
2 => {
let (primary, secondary) = result.split_at_mut(1);
let primary = &mut primary[0];
let secondary = &mut secondary[0];
// With two containers on screen container 0 is on the right
if let Some(resize_primary) = resize_dimensions[0] {
resize_left(primary, resize_primary.left);
resize_right(secondary, resize_primary.left);
}
if let Some(resize_secondary) = resize_dimensions[1] {
resize_left(primary, resize_secondary.right);
resize_right(secondary, resize_secondary.right);
}
}
_ => {
let (primary, rest) = result.split_at_mut(1);
let (secondary, tertiary) = rest.split_at_mut(1);
let primary = &mut primary[0];
let secondary = &mut secondary[0];
// With three or more containers container 0 is in the center
if let Some(resize_primary) = resize_dimensions[0] {
resize_left(primary, resize_primary.left);
resize_right(primary, resize_primary.right);
resize_right(secondary, resize_primary.left);
for vertical_element in &mut *tertiary {
resize_left(vertical_element, resize_primary.right);
}
}
// Container 1 is on the left
if let Some(resize_secondary) = resize_dimensions[1] {
resize_left(primary, resize_secondary.right);
resize_right(secondary, resize_secondary.right);
}
// Handle stack on the right
for (i, rect) in resize_dimensions[2..].iter().enumerate() {
if let Some(rect) = rect {
resize_right(primary, rect.left);
tertiary
.iter_mut()
.for_each(|vertical_element| resize_left(vertical_element, rect.left));
// Containers in stack except first can be resized up displacing container
// above them
if i != 0 {
resize_bottom(&mut tertiary[i - 1], rect.top);
resize_top(&mut tertiary[i], rect.top);
}
// Containers in stack except last can be resized down displacing container
// below them
if i != tertiary.len() - 1 {
resize_bottom(&mut tertiary[i], rect.bottom);
resize_top(&mut tertiary[i + 1], rect.bottom);
}
}
}
}
};
result
}
fn resize_left(rect: &mut Rect, resize: i32) {
rect.left += resize / 2;
rect.right += -resize / 2;
}
fn resize_right(rect: &mut Rect, resize: i32) {
rect.right += resize / 2;
}
fn resize_top(rect: &mut Rect, resize: i32) {
rect.top += resize / 2;
rect.bottom += -resize / 2;
}
fn resize_bottom(rect: &mut Rect, resize: i32) {
rect.bottom += resize / 2;
}
fn ultrawide(
area: &Rect,
len: usize,
layout_flip: Option<Axis>,
resize_dimensions: &[Option<Rect>],
) -> Vec<Rect> {
let mut layouts: Vec<Rect> = vec![];
let primary_right = match len {
1 => area.right,
_ => area.right / 2,
};
let secondary_right = match len {
1 => 0,
2 => area.right - primary_right,
_ => (area.right - primary_right) / 2,
};
let (primary_left, secondary_left, stack_left) = match len {
1 => (area.left, 0, 0),
2 => {
let mut primary = area.left + secondary_right;
let mut secondary = area.left;
match layout_flip {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
primary = area.left;
secondary = area.left + primary_right;
}
_ => {}
}
(primary, secondary, 0)
}
_ => {
let primary = area.left + secondary_right;
let mut secondary = area.left;
let mut stack = area.left + primary_right + secondary_right;
match layout_flip {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
secondary = area.left + primary_right + secondary_right;
stack = area.left;
}
_ => {}
}
(primary, secondary, stack)
}
};
if len >= 1 {
layouts.push(Rect {
left: primary_left,
top: area.top,
right: primary_right,
bottom: area.bottom,
});
if len >= 2 {
layouts.push(Rect {
left: secondary_left,
top: area.top,
right: secondary_right,
bottom: area.bottom,
});
if len > 2 {
layouts.append(&mut rows(
&Rect {
left: stack_left,
top: area.top,
right: secondary_right,
bottom: area.bottom,
},
len - 2,
));
}
}
}
let adjustment = calculate_ultrawide_adjustment(resize_dimensions);
layouts
.iter_mut()
.zip(adjustment.iter())
.for_each(|(layout, adjustment)| {
layout.top += adjustment.top;
layout.bottom += adjustment.bottom;
layout.left += adjustment.left;
layout.right += adjustment.right;
});
layouts
}

View File

@@ -24,31 +24,19 @@ impl ApplicationOptions {
pub fn raw_cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
match self {
ApplicationOptions::ObjectNameChange => {
format!(
"komorebic.exe identify-object-name-change-application {} \"{}\"",
kind, id
)
format!("komorebic.exe identify-object-name-change-application {kind} \"{id}\"",)
}
ApplicationOptions::Layered => {
format!(
"komorebic.exe identify-layered-application {} \"{}\"",
kind, id
)
format!("komorebic.exe identify-layered-application {kind} \"{id}\"",)
}
ApplicationOptions::BorderOverflow => {
format!(
"komorebic.exe identify-border-overflow-application {} \"{}\"",
kind, id
)
format!("komorebic.exe identify-border-overflow-application {kind} \"{id}\"",)
}
ApplicationOptions::TrayAndMultiWindow => {
format!(
"komorebic.exe identify-tray-application {} \"{}\"",
kind, id
)
format!("komorebic.exe identify-tray-application {kind} \"{id}\"",)
}
ApplicationOptions::Force => {
format!("komorebic.exe manage-rule {} \"{}\"", kind, id)
format!("komorebic.exe manage-rule {kind} \"{id}\"")
}
}
}
@@ -56,16 +44,28 @@ impl ApplicationOptions {
#[must_use]
pub fn cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
format!(
"Run, {}, , Hide",
"RunWait('{}', , \"Hide\")",
ApplicationOptions::raw_cfgen(self, kind, id)
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifier {
pub kind: ApplicationIdentifier,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub matching_strategy: Option<MatchingStrategy>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum MatchingStrategy {
Legacy,
Equals,
StartsWith,
EndsWith,
Contains,
Regex,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -74,6 +74,18 @@ pub struct IdWithIdentifierAndComment {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub matching_strategy: Option<MatchingStrategy>,
}
impl From<IdWithIdentifierAndComment> for IdWithIdentifier {
fn from(value: IdWithIdentifierAndComment) -> Self {
Self {
kind: value.kind,
id: value.id.clone(),
matching_strategy: value.matching_strategy,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -86,16 +98,33 @@ 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;
impl ApplicationConfigurationGenerator {
fn load(content: &str) -> Result<Vec<ApplicationConfiguration>> {
pub fn load(content: &str) -> Result<Vec<ApplicationConfiguration>> {
Ok(serde_yaml::from_str(content)?)
}
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)?)
}
@@ -143,7 +172,7 @@ impl ApplicationConfigurationGenerator {
lines.push(format!("# {}", app.name));
if let Some(options) = app.options {
for opt in options {
if let ApplicationOptions::TrayAndMultiWindow = opt {
if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {
lines.push(String::from("# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
}
@@ -161,7 +190,7 @@ impl ApplicationConfigurationGenerator {
float_rules.push(float_rule.clone());
if let Some(comment) = float.comment {
lines.push(format!("# {}", comment));
lines.push(format!("# {comment}"));
};
lines.push(float_rule);
@@ -184,12 +213,7 @@ impl ApplicationConfigurationGenerator {
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
let mut lines = vec![
String::from("; Generated by komorebic.exe"),
String::from("; To use this file, add the line below to the top of your komorebi.ahk configuration file"),
String::from("; #Include %A_ScriptDir%\\komorebi.generated.ahk"),
String::new()
];
let mut lines = vec![String::from("; Generated by komorebic.exe"), String::new()];
let mut float_rules = vec![];
@@ -197,7 +221,7 @@ impl ApplicationConfigurationGenerator {
lines.push(format!("; {}", app.name));
if let Some(options) = app.options {
for opt in options {
if let ApplicationOptions::TrayAndMultiWindow = opt {
if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {
lines.push(String::from("; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
}
@@ -208,7 +232,7 @@ impl ApplicationConfigurationGenerator {
if let Some(float_identifiers) = app.float_identifiers {
for float in float_identifiers {
let float_rule = format!(
"Run, komorebic.exe float-rule {} \"{}\", , Hide",
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
float.kind, float.id
);
@@ -217,7 +241,7 @@ impl ApplicationConfigurationGenerator {
float_rules.push(float_rule.clone());
if let Some(comment) = float.comment {
lines.push(format!("; {}", comment));
lines.push(format!("; {comment}"));
};
lines.push(float_rule);

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)
@@ -72,7 +70,7 @@ impl CustomLayout {
}
#[must_use]
pub fn primary_width_percentage(&self) -> Option<usize> {
pub fn primary_width_percentage(&self) -> Option<f32> {
for column in self.iter() {
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(percentage))) = column
{
@@ -83,7 +81,7 @@ impl CustomLayout {
None
}
pub fn set_primary_width_percentage(&mut self, percentage: usize) {
pub fn set_primary_width_percentage(&mut self, percentage: f32) {
for column in self.iter_mut() {
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(current))) = column {
*current = percentage;
@@ -262,7 +260,7 @@ pub enum Column {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnWidth {
WidthPercentage(usize),
WidthPercentage(f32),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]

View File

@@ -20,6 +20,7 @@ pub enum DefaultLayout {
VerticalStack,
HorizontalStack,
UltrawideVerticalStack,
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
}
impl DefaultLayout {
@@ -33,7 +34,7 @@ impl DefaultLayout {
sizing: Sizing,
delta: i32,
) -> Option<Rect> {
if !matches!(self, Self::BSP) {
if !matches!(self, Self::BSP) && !matches!(self, Self::UltrawideVerticalStack) {
return None;
};
@@ -125,4 +126,28 @@ impl DefaultLayout {
Option::from(r)
}
}
#[must_use]
pub const fn cycle_next(self) -> Self {
match self {
Self::BSP => Self::Columns,
Self::Columns => Self::Rows,
Self::Rows => Self::VerticalStack,
Self::VerticalStack => Self::HorizontalStack,
Self::HorizontalStack => Self::UltrawideVerticalStack,
Self::UltrawideVerticalStack => Self::BSP,
}
}
#[must_use]
pub const fn cycle_previous(self) -> Self {
match self {
Self::BSP => Self::UltrawideVerticalStack,
Self::UltrawideVerticalStack => Self::HorizontalStack,
Self::HorizontalStack => Self::VerticalStack,
Self::VerticalStack => Self::Rows,
Self::Rows => Self::Columns,
Self::Columns => Self::BSP,
}
}
}

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;
@@ -46,15 +50,18 @@ pub enum SocketMessage {
UnstackWindow,
CycleStack(CycleDirection),
MoveContainerToMonitorNumber(usize),
CycleMoveContainerToMonitor(CycleDirection),
MoveContainerToWorkspaceNumber(usize),
MoveContainerToNamedWorkspace(String),
CycleMoveContainerToWorkspace(CycleDirection),
SendContainerToMonitorNumber(usize),
CycleSendContainerToMonitor(CycleDirection),
SendContainerToWorkspaceNumber(usize),
CycleSendContainerToWorkspace(CycleDirection),
SendContainerToMonitorWorkspaceNumber(usize, usize),
SendContainerToNamedWorkspace(String),
MoveWorkspaceToMonitorNumber(usize),
SwapWorkspacesToMonitorNumber(usize),
ForceFocus,
Close,
Minimize,
@@ -74,6 +81,7 @@ pub enum SocketMessage {
AdjustContainerPadding(Sizing, i32),
AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout),
CycleLayout(CycleDirection),
ChangeLayoutCustom(PathBuf),
FlipLayout(Axis),
// Monitor and Workspace Commands
@@ -93,12 +101,15 @@ pub enum SocketMessage {
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusWorkspaceNumber(usize),
FocusWorkspaceNumbers(usize),
FocusMonitorWorkspaceNumber(usize, usize),
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),
@@ -114,9 +125,13 @@ pub enum SocketMessage {
ClearNamedWorkspaceLayoutRules(String),
// Configuration
ReloadConfiguration,
ReloadStaticConfiguration(PathBuf),
WatchConfiguration(bool),
CompleteConfiguration,
AltFocusHack(bool),
Animation(bool),
AnimationDuration(u64),
AnimationEase(EaseEnum),
ActiveWindowBorder(bool),
ActiveWindowBorderColour(WindowKind, u32, u32, u32),
ActiveWindowBorderWidth(i32),
@@ -125,6 +140,8 @@ pub enum SocketMessage {
WorkAreaOffset(Rect),
MonitorWorkAreaOffset(usize, Rect),
ResizeDelta(i32),
InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),
InitialNamedWorkspaceRule(ApplicationIdentifier, String, String),
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
NamedWorkspaceRule(ApplicationIdentifier, String, String),
FloatRule(ApplicationIdentifier, String),
@@ -139,10 +156,15 @@ pub enum SocketMessage {
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
MouseFollowsFocus(bool),
ToggleMouseFollowsFocus,
RemoveTitleBar(ApplicationIdentifier, String),
ToggleTitleBars,
AddSubscriber(String),
RemoveSubscriber(String),
ApplicationSpecificConfigurationSchema,
NotificationSchema,
SocketSchema,
StaticConfigSchema,
GenerateStaticConfig,
}
impl SocketMessage {
@@ -166,6 +188,7 @@ impl FromStr for SocketMessage {
pub enum WindowKind {
Single,
Stack,
Monocle,
}
#[derive(
@@ -180,13 +203,25 @@ pub enum StateQuery {
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationIdentifier {
#[serde(alias = "exe")]
Exe,
#[serde(alias = "class")]
Class,
#[serde(alias = "title")]
Title,
}
@@ -195,7 +230,9 @@ pub enum ApplicationIdentifier {
)]
#[strum(serialize_all = "snake_case")]
pub enum FocusFollowsMouseImplementation {
/// A custom FFM implementation (slightly more CPU-intensive)
Komorebi,
/// The native (legacy) Windows FFM implementation
Windows,
}
@@ -204,7 +241,9 @@ pub enum FocusFollowsMouseImplementation {
)]
#[strum(serialize_all = "snake_case")]
pub enum WindowContainerBehaviour {
/// Create a new container for each new window
Create,
/// Append new windows to the focused window container
Append,
}
@@ -213,7 +252,9 @@ pub enum WindowContainerBehaviour {
)]
#[strum(serialize_all = "snake_case")]
pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor
Swap,
/// Insert the window container into the focused workspace on the adjacent monitor
Insert,
}
@@ -222,8 +263,11 @@ pub enum MoveBehaviour {
)]
#[strum(serialize_all = "snake_case")]
pub enum HidingBehaviour {
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
Hide,
/// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
Minimize,
/// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces (has foregrounding issues)
Cloak,
}
@@ -232,7 +276,9 @@ pub enum HidingBehaviour {
)]
#[strum(serialize_all = "snake_case")]
pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows
Op,
/// Ignore komorebic commands on temporarily unmanaged/floated windows
NoOp,
}
@@ -260,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

@@ -5,9 +5,13 @@ use windows::Win32::Foundation::RECT;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
pub struct Rect {
/// The left point in a Win32 Rect
pub left: i32,
/// The top point in a Win32 Rect
pub top: i32,
/// The right point in a Win32 Rect
pub right: i32,
/// The bottom point in a Win32 Rect
pub bottom: i32,
}

26
komorebi.example.json Normal file
View File

@@ -0,0 +1,26 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/master/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",
"alt_focus_hack": true,
"default_workspace_padding": 20,
"default_container_padding": 20,
"active_window_border": false,
"active_window_border_colours": {
"single": { "r": 66, "g": 165, "b": 245 },
"stack": { "r": 256, "g": 165, "b": 66 },
"monocle": { "r": 255, "g": 51, "b": 153 }
},
"monitors": [
{
"workspaces": [
{ "name": "I", "layout": "BSP" },
{ "name": "II", "layout": "VerticalStack" },
{ "name": "III", "layout": "HorizontalStack" },
{ "name": "IV", "layout": "UltrawideVerticalStack" },
{ "name": "V", "layout": "Rows" }
]
}
]
}

594
komorebi.generated.ahk Normal file
View File

@@ -0,0 +1,594 @@
; Generated by komorebic.exe
; 1Password
RunWait('komorebic.exe float-rule exe "1Password.exe"', , "Hide")
; Ableton Live
; Targets VST2 windows
RunWait('komorebic.exe float-rule class "AbletonVstPlugClass"', , "Hide")
; Targets VST3 windows
RunWait('komorebic.exe float-rule class "Vst3PlugWindow"', , "Hide")
; Adobe Creative Cloud
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application class "CreativeCloudDesktopWindowClass"', , "Hide")
; Adobe Photoshop
RunWait('komorebic.exe identify-border-overflow-application class "Photoshop"', , "Hide")
; Affinity Photo 2
RunWait('komorebic.exe manage-rule title "Affinity Photo 2"', , "Hide")
RunWait('komorebic.exe float-rule exe "Photo.exe"', , "Hide")
; Akiflow
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Akiflow.exe"', , "Hide")
; Android Studio
RunWait('komorebic.exe identify-object-name-change-application exe "studio64.exe"', , "Hide")
; Anki
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "anki.exe"', , "Hide")
; ArmCord
RunWait('komorebic.exe identify-border-overflow-application exe "ArmCord.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ArmCord.exe"', , "Hide")
; AutoHotkey
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "AutoHotkeyU64.exe"', , "Hide")
RunWait('komorebic.exe float-rule title "Window Spy"', , "Hide")
RunWait('komorebic.exe float-rule exe "AutoHotkeyUX.exe"', , "Hide")
; Beeper
RunWait('komorebic.exe identify-border-overflow-application exe "Beeper.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Beeper.exe"', , "Hide")
; Bitwarden
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Bitwarden.exe"', , "Hide")
; Bloxstrap
RunWait('komorebic.exe float-rule exe "Bloxstrap.exe"', , "Hide")
; Calculator
RunWait('komorebic.exe float-rule title "Calculator"', , "Hide")
; Clash Verge
RunWait('komorebic.exe identify-border-overflow-application exe "Clash Verge.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Clash Verge.exe"', , "Hide")
; Clementine
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "clementine.exe"', , "Hide")
; CopyQ
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "copyq.exe"', , "Hide")
; Credential Manager UI Host
; Targets the Windows popup prompting you for a PIN instead of a password on 1Password etc.
RunWait('komorebic.exe float-rule exe "CredentialUIBroker.exe"', , "Hide")
; Cron
RunWait('komorebic.exe identify-border-overflow-application exe "Cron.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Cron.exe"', , "Hide")
; DS4Windows
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "DS4Windows.exe"', , "Hide")
; Delphi applications
; Target hidden window spawned by Delphi applications
RunWait('komorebic.exe float-rule class "TApplication"', , "Hide")
; Target Inno Setup installers
RunWait('komorebic.exe float-rule class "TWizardForm"', , "Hide")
; Discord
RunWait('komorebic.exe identify-border-overflow-application exe "Discord.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Discord.exe"', , "Hide")
; DiscordCanary
RunWait('komorebic.exe identify-border-overflow-application exe "DiscordCanary.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "DiscordCanary.exe"', , "Hide")
; DiscordDevelopment
RunWait('komorebic.exe identify-border-overflow-application exe "DiscordDevelopment.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "DiscordDevelopment.exe"', , "Hide")
; DiscordPTB
RunWait('komorebic.exe identify-border-overflow-application exe "DiscordPTB.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "DiscordPTB.exe"', , "Hide")
; Docker Desktop
RunWait('komorebic.exe identify-border-overflow-application exe "Docker Desktop.exe"', , "Hide")
; Dropbox
RunWait('komorebic.exe float-rule exe "Dropbox.exe"', , "Hide")
; ElectronMail
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ElectronMail.exe"', , "Hide")
; Element
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Element.exe"', , "Hide")
; Elephicon
RunWait('komorebic.exe float-rule exe "Elephicon.exe"', , "Hide")
; ElevenClock
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ElevenClock.exe"', , "Hide")
; Elgato Camera Hub
RunWait('komorebic.exe float-rule exe "Camera Hub.exe"', , "Hide")
; Elgato Control Center
RunWait('komorebic.exe float-rule exe "ControlCenter.exe"', , "Hide")
; Elgato Wave Link
RunWait('komorebic.exe float-rule exe "WaveLink.exe"', , "Hide")
; Epic Games Launcher
RunWait('komorebic.exe identify-border-overflow-application exe "EpicGamesLauncher.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "EpicGamesLauncher.exe"', , "Hide")
; Everything
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Everything.exe"', , "Hide")
; Figma
RunWait('komorebic.exe identify-border-overflow-application exe "Figma.exe"', , "Hide")
; Flow Launcher
RunWait('komorebic.exe identify-border-overflow-application exe "Flow.Launcher.exe"', , "Hide")
; GOG Galaxy
RunWait('komorebic.exe identify-border-overflow-application exe "GalaxyClient.exe"', , "Hide")
RunWait('komorebic.exe manage-rule exe "GalaxyClient.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "GalaxyClient.exe"', , "Hide")
; Targets a hidden window spawned by GOG Galaxy
RunWait('komorebic.exe float-rule class "Chrome_RenderWidgetHostHWND"', , "Hide")
; GoPro Webcam
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application class "GoPro Webcam"', , "Hide")
; Godot Manager
RunWait('komorebic.exe identify-border-overflow-application exe "GodotManager.exe"', , "Hide")
RunWait('komorebic.exe manage-rule exe "GodotManager.exe"', , "Hide")
RunWait('komorebic.exe identify-object-name-change-application exe "GodotManager.exe"', , "Hide")
; Golden Dict
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "GoldenDict.exe"', , "Hide")
; Google Chrome
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "chrome.exe"', , "Hide")
; Google Drive
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "GoogleDriveFS.exe"', , "Hide")
RunWait('komorebic.exe float-rule exe "GoogleDriveFS.exe"', , "Hide")
; Houdoku
RunWait('komorebic.exe identify-border-overflow-application exe "Houdoku.exe"', , "Hide")
; IntelliJ IDEA
RunWait('komorebic.exe identify-object-name-change-application exe "idea64.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "idea64.exe"', , "Hide")
; Targets JetBrains IDE popups and floating windows
RunWait('komorebic.exe float-rule class "SunAwtDialog"', , "Hide")
; Itch.io
RunWait('komorebic.exe identify-border-overflow-application exe "itch.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "itch.exe"', , "Hide")
; Keyviz
RunWait('komorebic.exe float-rule exe "keyviz.exe"', , "Hide")
; Kleopatra
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "kleopatra.exe"', , "Hide")
; Kotatogram
RunWait('komorebic.exe identify-border-overflow-application exe "Kotatogram.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Kotatogram.exe"', , "Hide")
; LocalSend
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "localsend_app.exe"', , "Hide")
; Logi Bolt
RunWait('komorebic.exe float-rule exe "LogiBolt.exe"', , "Hide")
; LogiTune
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "LogiTune.exe"', , "Hide")
RunWait('komorebic.exe float-rule exe "LogiTune.exe"', , "Hide")
; Logitech G HUB
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "lghub.exe"', , "Hide")
RunWait('komorebic.exe identify-border-overflow-application exe "lghub.exe"', , "Hide")
; Logitech Options
RunWait('komorebic.exe float-rule exe "LogiOptionsUI.exe"', , "Hide")
; Mailspring
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "mailspring.exe"', , "Hide")
; ManyCam
RunWait('komorebic.exe identify-border-overflow-application exe "ManyCam.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ManyCam.exe"', , "Hide")
; Mica For Everyone
; Microsoft Excel
RunWait('komorebic.exe identify-border-overflow-application exe "EXCEL.EXE"', , "Hide")
RunWait('komorebic.exe identify-layered-application exe "EXCEL.EXE"', , "Hide")
; Targets a hidden window spawned by Microsoft Office applications
RunWait('komorebic.exe float-rule class "_WwB"', , "Hide")
; Microsoft Outlook
RunWait('komorebic.exe identify-border-overflow-application exe "OUTLOOK.EXE"', , "Hide")
RunWait('komorebic.exe identify-layered-application exe "OUTLOOK.EXE"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "OUTLOOK.EXE"', , "Hide")
; Microsoft PC Manager
RunWait('komorebic.exe float-rule exe "MSPCManager.exe"', , "Hide")
; Microsoft PowerPoint
RunWait('komorebic.exe identify-border-overflow-application exe "POWERPNT.EXE"', , "Hide")
RunWait('komorebic.exe identify-layered-application exe "POWERPNT.EXE"', , "Hide")
; Microsoft Teams
RunWait('komorebic.exe identify-border-overflow-application exe "Teams.exe"', , "Hide")
; Target Teams pop-up notification windows
RunWait('komorebic.exe float-rule title "Microsoft Teams Notification"', , "Hide")
; Target Teams call in progress windows
RunWait('komorebic.exe float-rule title "Microsoft Teams Call"', , "Hide")
; Microsoft Word
RunWait('komorebic.exe identify-border-overflow-application exe "WINWORD.EXE"', , "Hide")
RunWait('komorebic.exe identify-layered-application exe "WINWORD.EXE"', , "Hide")
; Modern Flyouts
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ModernFlyoutsHost.exe"', , "Hide")
; Mozilla Firefox
RunWait('komorebic.exe identify-object-name-change-application exe "firefox.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "firefox.exe"', , "Hide")
; Targets invisible windows spawned by Firefox to show tab previews in the taskbar
RunWait('komorebic.exe float-rule class "MozillaTaskbarPreviewClass"', , "Hide")
; NVIDIA GeForce Experience
RunWait('komorebic.exe identify-border-overflow-application exe "NVIDIA GeForce Experience.exe"', , "Hide")
; NZXT CAM
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "NZXT CAM.exe"', , "Hide")
; NetEase Cloud Music
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "cloudmusic.exe"', , "Hide")
; NiceHash Miner
RunWait('komorebic.exe identify-border-overflow-application exe "nhm_app.exe"', , "Hide")
RunWait('komorebic.exe manage-rule exe "nhm_app.exe"', , "Hide")
; NohBoard
RunWait('komorebic.exe float-rule exe "NohBoard.exe"', , "Hide")
; Notion Enhanced
RunWait('komorebic.exe identify-border-overflow-application exe "Notion Enhanced.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Notion Enhanced.exe"', , "Hide")
; OBS Studio (32-bit)
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "obs32.exe"', , "Hide")
; OBS Studio (64-bit)
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "obs64.exe"', , "Hide")
; ONLYOFFICE Editors
RunWait('komorebic.exe identify-border-overflow-application class "DocEditorsWindowClass"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application class "DocEditorsWindowClass"', , "Hide")
; Obsidian
RunWait('komorebic.exe identify-border-overflow-application exe "Obsidian.exe"', , "Hide")
RunWait('komorebic.exe manage-rule exe "Obsidian.exe"', , "Hide")
; OneDrive
RunWait('komorebic.exe float-rule class "OneDriveReactNativeWin32WindowClass"', , "Hide")
; OneQuick
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "OneQuick.exe"', , "Hide")
; OpenRGB
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "OpenRGB.exe"', , "Hide")
; Paradox Launcher
RunWait('komorebic.exe float-rule exe "Paradox Launcher.exe"', , "Hide")
; Playnite
RunWait('komorebic.exe identify-border-overflow-application exe "Playnite.DesktopApp.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Playnite.DesktopApp.exe"', , "Hide")
; Target fullscreen app
RunWait('komorebic.exe float-rule exe "Playnite.FullscreenApp.exe"', , "Hide")
; Plexamp
RunWait('komorebic.exe identify-border-overflow-application exe "Plexamp.exe"', , "Hide")
; PowerToys
; Target color picker dialog
RunWait('komorebic.exe float-rule exe "PowerToys.ColorPickerUI.exe"', , "Hide")
; Target image resizer dialog
RunWait('komorebic.exe float-rule exe "PowerToys.ImageResizer.exe"', , "Hide")
; Target Peek popup
RunWait('komorebic.exe float-rule exe "PowerToys.Peek.UI.exe"', , "Hide")
; Process Hacker
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ProcessHacker.exe"', , "Hide")
RunWait('komorebic.exe float-rule exe "ProcessHacker.exe"', , "Hide")
; ProtonVPN
RunWait('komorebic.exe identify-border-overflow-application exe "ProtonVPN.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ProtonVPN.exe"', , "Hide")
; PyCharm
RunWait('komorebic.exe identify-object-name-change-application exe "pycharm64.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "pycharm64.exe"', , "Hide")
; QQ
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "QQ.exe"', , "Hide")
RunWait('komorebic.exe float-rule title "图片查看器"', , "Hide")
RunWait('komorebic.exe float-rule title "群聊的聊天记录"', , "Hide")
RunWait('komorebic.exe float-rule title "语音通话"', , "Hide")
; QtScrcpy
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "QtScrcpy.exe"', , "Hide")
; QuickLook
RunWait('komorebic.exe float-rule exe "QuickLook.exe"', , "Hide")
; RepoZ
RunWait('komorebic.exe float-rule exe "RepoZ.exe"', , "Hide")
; Rider
RunWait('komorebic.exe identify-object-name-change-application exe "rider64.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "rider64.exe"', , "Hide")
; Roblox FPS Unlocker
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "rbxfpsunlocker.exe"', , "Hide")
; RoundedTB
RunWait('komorebic.exe float-rule exe "RoundedTB.exe"', , "Hide")
; RoundedTB
RunWait('komorebic.exe identify-border-overflow-application exe "RoundedTB.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "RoundedTB.exe"', , "Hide")
; RustRover
RunWait('komorebic.exe identify-object-name-change-application exe "rustrover64.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "rustrover64.exe"', , "Hide")
; Sandboxie Plus
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "SandMan.exe"', , "Hide")
; ShareX
RunWait('komorebic.exe identify-border-overflow-application exe "ShareX.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ShareX.exe"', , "Hide")
; Sideloadly
RunWait('komorebic.exe float-rule exe "sideloadly.exe"', , "Hide")
; Signal
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Signal.exe"', , "Hide")
; SiriKali
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "sirikali.exe"', , "Hide")
; Slack
RunWait('komorebic.exe identify-border-overflow-application exe "Slack.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Slack.exe"', , "Hide")
; Slack
RunWait('komorebic.exe identify-border-overflow-application exe "slack.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "slack.exe"', , "Hide")
; Smart Install Maker
; Target hidden window spawned by installer
RunWait('komorebic.exe float-rule class "obj_App"', , "Hide")
; Target installer
RunWait('komorebic.exe float-rule class "obj_Form"', , "Hide")
; SoulseekQt
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "SoulseekQt.exe"', , "Hide")
; Spotify
RunWait('komorebic.exe identify-border-overflow-application exe "Spotify.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Spotify.exe"', , "Hide")
; Steam
RunWait('komorebic.exe identify-border-overflow-application class "vguiPopupWindow"', , "Hide")
; Steam Beta
RunWait('komorebic.exe identify-border-overflow-application class "SDL_app"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application class "SDL_app"', , "Hide")
; Target notification toast popups
RunWait('komorebic.exe float-rule title "notificationtoasts_"', , "Hide")
; Stremio
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "stremio.exe"', , "Hide")
; System Informer
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "SystemInformer.exe"', , "Hide")
RunWait('komorebic.exe float-rule exe "SystemInformer.exe"', , "Hide")
; SystemSettings
RunWait('komorebic.exe float-rule class "Shell_Dialog"', , "Hide")
; Task Manager
RunWait('komorebic.exe float-rule class "TaskManagerWindow"', , "Hide")
; Telegram
RunWait('komorebic.exe identify-border-overflow-application exe "Telegram.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Telegram.exe"', , "Hide")
; TickTick
RunWait('komorebic.exe identify-border-overflow-application exe "TickTick.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "TickTick.exe"', , "Hide")
; TouchCursor
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "tcconfig.exe"', , "Hide")
RunWait('komorebic.exe float-rule exe "tcconfig.exe"', , "Hide")
; TranslucentTB
RunWait('komorebic.exe float-rule exe "TranslucentTB.exe"', , "Hide")
; TranslucentTB
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "TranslucentTB.exe"', , "Hide")
; Unity Hub
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "Unity Hub.exe"', , "Hide")
; Unreal Editor
RunWait('komorebic.exe identify-border-overflow-application exe "UnrealEditor.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "UnrealEditor.exe"', , "Hide")
; VRCX
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "VRCX.exe"', , "Hide")
; Visual Studio
RunWait('komorebic.exe identify-object-name-change-application exe "devenv.exe"', , "Hide")
; Visual Studio Code
RunWait('komorebic.exe identify-border-overflow-application exe "Code.exe"', , "Hide")
; Visual Studio Code - Insiders
RunWait('komorebic.exe identify-border-overflow-application exe "Code - Insiders.exe"', , "Hide")
; Voice.ai
RunWait('komorebic.exe identify-border-overflow-application exe "VoiceAI.exe"', , "Hide")
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "VoiceAI.exe"', , "Hide")
; WebTorrent Desktop
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "WebTorrent.exe"', , "Hide")
; WinZip (32-bit)
RunWait('komorebic.exe float-rule exe "winzip32.exe"', , "Hide")
; WinZip (64-bit)
RunWait('komorebic.exe float-rule exe "winzip64.exe"', , "Hide")
; Windows Console (conhost.exe)
RunWait('komorebic.exe manage-rule class "ConsoleWindowClass"', , "Hide")
; Windows Explorer
; Targets copy/move operation windows
RunWait('komorebic.exe float-rule class "OperationStatusWindow"', , "Hide")
RunWait('komorebic.exe float-rule title "Control Panel"', , "Hide")
; Windows Installer
RunWait('komorebic.exe float-rule exe "msiexec.exe"', , "Hide")
; Windows Subsystem for Android
; Targets splash/startup screen
RunWait('komorebic.exe float-rule class "android(splash)"', , "Hide")
; WingetUI
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "WingetUI.exe"', , "Hide")
; WingetUI
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "wingetui.exe"', , "Hide")
; Wox
; Targets a hidden window spawned by Wox
RunWait('komorebic.exe float-rule title "Hotkey sink"', , "Hide")
; XAMPP Control Panel
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "xampp-control.exe"', , "Hide")
; Zoom
RunWait('komorebic.exe float-rule exe "Zoom.exe"', , "Hide")
; mpv
RunWait('komorebic.exe identify-object-name-change-application class "mpv"', , "Hide")
; mpv.net
RunWait('komorebic.exe identify-object-name-change-application exe "mpvnet.exe"', , "Hide")
; paint.net
RunWait('komorebic.exe float-rule exe "paintdotnet.exe"', , "Hide")
; pinentry
RunWait('komorebic.exe float-rule exe "pinentry.exe"', , "Hide")
; qBittorrent
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "qbittorrent.exe"', , "Hide")
; ueli
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
RunWait('komorebic.exe identify-tray-application exe "ueli.exe"', , "Hide")
RunWait('komorebic.exe float-rule exe "ueli.exe"', , "Hide")

View File

@@ -16,6 +16,21 @@ komorebic.exe identify-tray-application class "CreativeCloudDesktopWindowClass"
# Adobe Photoshop
komorebic.exe identify-border-overflow-application class "Photoshop"
# Affinity Photo 2
komorebic.exe manage-rule title "Affinity Photo 2"
komorebic.exe float-rule exe "Photo.exe"
# Akiflow
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Akiflow.exe"
# Android Studio
komorebic.exe identify-object-name-change-application exe "studio64.exe"
# Anki
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "anki.exe"
# ArmCord
komorebic.exe identify-border-overflow-application exe "ArmCord.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
@@ -25,6 +40,7 @@ komorebic.exe identify-tray-application exe "ArmCord.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "AutoHotkeyU64.exe"
komorebic.exe float-rule title "Window Spy"
komorebic.exe float-rule exe "AutoHotkeyUX.exe"
# Beeper
komorebic.exe identify-border-overflow-application exe "Beeper.exe"
@@ -41,6 +57,19 @@ komorebic.exe float-rule exe "Bloxstrap.exe"
# Calculator
komorebic.exe float-rule title "Calculator"
# Clash Verge
komorebic.exe identify-border-overflow-application exe "Clash Verge.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Clash Verge.exe"
# Clementine
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "clementine.exe"
# CopyQ
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "copyq.exe"
# Credential Manager UI Host
# Targets the Windows popup prompting you for a PIN instead of a password on 1Password etc.
komorebic.exe float-rule exe "CredentialUIBroker.exe"
@@ -50,6 +79,10 @@ komorebic.exe identify-border-overflow-application exe "Cron.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Cron.exe"
# DS4Windows
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "DS4Windows.exe"
# Delphi applications
# Target hidden window spawned by Delphi applications
komorebic.exe float-rule class "TApplication"
@@ -76,6 +109,12 @@ komorebic.exe identify-border-overflow-application exe "DiscordPTB.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "DiscordPTB.exe"
# Docker Desktop
komorebic.exe identify-border-overflow-application exe "Docker Desktop.exe"
# Dropbox
komorebic.exe float-rule exe "Dropbox.exe"
# ElectronMail
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "ElectronMail.exe"
@@ -84,6 +123,9 @@ komorebic.exe identify-tray-application exe "ElectronMail.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Element.exe"
# Elephicon
komorebic.exe float-rule exe "Elephicon.exe"
# ElevenClock
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "ElevenClock.exe"
@@ -102,6 +144,13 @@ komorebic.exe identify-border-overflow-application exe "EpicGamesLauncher.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "EpicGamesLauncher.exe"
# Everything
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Everything.exe"
# Figma
komorebic.exe identify-border-overflow-application exe "Figma.exe"
# Flow Launcher
komorebic.exe identify-border-overflow-application exe "Flow.Launcher.exe"
@@ -122,6 +171,10 @@ komorebic.exe identify-border-overflow-application exe "GodotManager.exe"
komorebic.exe manage-rule exe "GodotManager.exe"
komorebic.exe identify-object-name-change-application exe "GodotManager.exe"
# Golden Dict
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "GoldenDict.exe"
# Google Chrome
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "chrome.exe"
@@ -129,6 +182,7 @@ komorebic.exe identify-tray-application exe "chrome.exe"
# Google Drive
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "GoogleDriveFS.exe"
komorebic.exe float-rule exe "GoogleDriveFS.exe"
# Houdoku
komorebic.exe identify-border-overflow-application exe "Houdoku.exe"
@@ -232,6 +286,14 @@ komorebic.exe float-rule class "MozillaTaskbarPreviewClass"
# NVIDIA GeForce Experience
komorebic.exe identify-border-overflow-application exe "NVIDIA GeForce Experience.exe"
# NZXT CAM
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "NZXT CAM.exe"
# NetEase Cloud Music
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "cloudmusic.exe"
# NiceHash Miner
komorebic.exe identify-border-overflow-application exe "nhm_app.exe"
komorebic.exe manage-rule exe "nhm_app.exe"
@@ -261,6 +323,13 @@ komorebic.exe identify-tray-application class "DocEditorsWindowClass"
komorebic.exe identify-border-overflow-application exe "Obsidian.exe"
komorebic.exe manage-rule exe "Obsidian.exe"
# OneDrive
komorebic.exe float-rule class "OneDriveReactNativeWin32WindowClass"
# OneQuick
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "OneQuick.exe"
# OpenRGB
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "OpenRGB.exe"
@@ -268,6 +337,13 @@ komorebic.exe identify-tray-application exe "OpenRGB.exe"
# Paradox Launcher
komorebic.exe float-rule exe "Paradox Launcher.exe"
# Playnite
komorebic.exe identify-border-overflow-application exe "Playnite.DesktopApp.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Playnite.DesktopApp.exe"
# Target fullscreen app
komorebic.exe float-rule exe "Playnite.FullscreenApp.exe"
# Plexamp
komorebic.exe identify-border-overflow-application exe "Plexamp.exe"
@@ -276,6 +352,8 @@ komorebic.exe identify-border-overflow-application exe "Plexamp.exe"
komorebic.exe float-rule exe "PowerToys.ColorPickerUI.exe"
# Target image resizer dialog
komorebic.exe float-rule exe "PowerToys.ImageResizer.exe"
# Target Peek popup
komorebic.exe float-rule exe "PowerToys.Peek.UI.exe"
# Process Hacker
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
@@ -292,6 +370,13 @@ komorebic.exe identify-object-name-change-application exe "pycharm64.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "pycharm64.exe"
# QQ
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "QQ.exe"
komorebic.exe float-rule title "图片查看器"
komorebic.exe float-rule title "群聊的聊天记录"
komorebic.exe float-rule title "语音通话"
# QtScrcpy
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "QtScrcpy.exe"
@@ -319,6 +404,15 @@ komorebic.exe identify-border-overflow-application exe "RoundedTB.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "RoundedTB.exe"
# RustRover
komorebic.exe identify-object-name-change-application exe "rustrover64.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "rustrover64.exe"
# Sandboxie Plus
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "SandMan.exe"
# ShareX
komorebic.exe identify-border-overflow-application exe "ShareX.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
@@ -329,7 +423,7 @@ komorebic.exe float-rule exe "sideloadly.exe"
# Signal
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "signal.exe"
komorebic.exe identify-tray-application exe "Signal.exe"
# SiriKali
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
@@ -337,13 +431,11 @@ komorebic.exe identify-tray-application exe "sirikali.exe"
# Slack
komorebic.exe identify-border-overflow-application exe "Slack.exe"
komorebic.exe manage-rule exe "Slack.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Slack.exe"
# Slack
komorebic.exe identify-border-overflow-application exe "slack.exe"
komorebic.exe manage-rule exe "slack.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "slack.exe"
@@ -365,6 +457,13 @@ komorebic.exe identify-tray-application exe "Spotify.exe"
# Steam
komorebic.exe identify-border-overflow-application class "vguiPopupWindow"
# Steam Beta
komorebic.exe identify-border-overflow-application class "SDL_app"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application class "SDL_app"
# Target notification toast popups
komorebic.exe float-rule title "notificationtoasts_"
# Stremio
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "stremio.exe"
@@ -385,6 +484,11 @@ komorebic.exe identify-border-overflow-application exe "Telegram.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Telegram.exe"
# TickTick
komorebic.exe identify-border-overflow-application exe "TickTick.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "TickTick.exe"
# TouchCursor
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "tcconfig.exe"
@@ -397,6 +501,10 @@ komorebic.exe float-rule exe "TranslucentTB.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "TranslucentTB.exe"
# Unity Hub
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "Unity Hub.exe"
# Unreal Editor
komorebic.exe identify-border-overflow-application exe "UnrealEditor.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
@@ -412,6 +520,9 @@ komorebic.exe identify-object-name-change-application exe "devenv.exe"
# Visual Studio Code
komorebic.exe identify-border-overflow-application exe "Code.exe"
# Visual Studio Code - Insiders
komorebic.exe identify-border-overflow-application exe "Code - Insiders.exe"
# Voice.ai
komorebic.exe identify-border-overflow-application exe "VoiceAI.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
@@ -421,6 +532,12 @@ komorebic.exe identify-tray-application exe "VoiceAI.exe"
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "WebTorrent.exe"
# WinZip (32-bit)
komorebic.exe float-rule exe "winzip32.exe"
# WinZip (64-bit)
komorebic.exe float-rule exe "winzip64.exe"
# Windows Console (conhost.exe)
komorebic.exe manage-rule class "ConsoleWindowClass"
@@ -432,6 +549,10 @@ komorebic.exe float-rule title "Control Panel"
# Windows Installer
komorebic.exe float-rule exe "msiexec.exe"
# Windows Subsystem for Android
# Targets splash/startup screen
komorebic.exe float-rule class "android(splash)"
# WingetUI
# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
komorebic.exe identify-tray-application exe "WingetUI.exe"
@@ -451,6 +572,9 @@ komorebic.exe identify-tray-application exe "xampp-control.exe"
# Zoom
komorebic.exe float-rule exe "Zoom.exe"
# mpv
komorebic.exe identify-object-name-change-application class "mpv"
# mpv.net
komorebic.exe identify-object-name-change-application exe "mpvnet.exe"

93
komorebi.sample.ahk Normal file
View File

@@ -0,0 +1,93 @@
#SingleInstance Force
; Load library
#Include komorebic.lib.ahk
; Load configuration
#Include komorebi.generated.ahk
; Send the ALT key whenever changing focus to force focus changes
AltFocusHack("enable")
; Default to cloaking windows when switching workspaces
WindowHidingBehaviour("cloak")
; Set cross-monitor move behaviour to insert instead of swap
CrossMonitorMoveBehaviour("Insert")
; Enable hot reloading of changes to this file
WatchConfiguration("enable")
; Create named workspaces I-V on monitor 0
EnsureNamedWorkspaces(0, "I II III IV V")
; You can do the same thing for secondary monitors too
; EnsureNamedWorkspaces(1, "A B C D E F")
; Assign layouts to workspaces, possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack
NamedWorkspaceLayout("I", "bsp")
; Set the gaps around the edge of the screen for a workspace
NamedWorkspacePadding("I", 20)
; Set the gaps between the containers for a workspace
NamedWorkspaceContainerPadding("I", 20)
; You can assign specific apps to named workspaces
; NamedWorkspaceRule("exe", "Firefox.exe", "III")
; Configure the invisible border dimensions
InvisibleBorders(7, 0, 14, 7)
; Uncomment the next lines if you want a visual border around the active window
; ActiveWindowBorderColour(66, 165, 245, "single")
; ActiveWindowBorderColour(256, 165, 66, "stack")
; ActiveWindowBorderColour(255, 51, 153, "monocle")
CompleteConfiguration()
; Focus windows
!h::Focus("left")
!j::Focus("down")
!k::Focus("up")
!l::Focus("right")
!+[::CycleFocus("previous")
!+]::CycleFocus("next")
; Move windows
!+h::Move("left")
!+j::Move("down")
!+k::Move("up")
!+l::Move("right")
!+Enter::Promote()
; Stack windows
!Left::Stack("left")
!Right::Stack("right")
!Up::Stack("up")
!Down::Stack("down")
!;::Unstack()
![::CycleStack("previous")
!]::CycleStack("next")
; Resize
!=::ResizeAxis("horizontal", "increase")
!-::ResizeAxis("horizontal", "decrease")
!+=::ResizeAxis("vertical", "increase")
!+-::ResizeAxis("vertical", "decrease")
; Manipulate windows
!t::ToggleFloat()
!+f::ToggleMonocle()
; Window manager options
!+r::Retile()
!p::TogglePause()
; Layouts
!x::FlipLayout("horizontal")
!y::FlipLayout("vertical")
; Workspaces
!1::FocusWorkspace(0)
!2::FocusWorkspace(1)
!3::FocusWorkspace(2)
; Move windows across workspaces
!+1::MoveToWorkspace(0)
!+2::MoveToWorkspace(1)
!+3::MoveToWorkspace(2)

View File

@@ -7,28 +7,27 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
# Send the ALT key whenever changing focus to force focus changes
komorebic alt-focus-hack enable
# Default to minimizing windows when switching workspaces
# Default to cloaking windows when switching workspaces
komorebic window-hiding-behaviour cloak
# Set cross-monitor move behaviour to insert instead of swap
komorebic cross-monitor-move-behaviour insert
# Enable hot reloading of changes to this file
komorebic watch-configuration enable
# create named workspaces I-V on monitor 0
# Create named workspaces I-V on monitor 0
komorebic ensure-named-workspaces 0 I II III IV V
# you can do the same thing for secondary monitors too
# You can do the same thing for secondary monitors too
# komorebic ensure-named-workspaces 1 A B C D E F
# assign layouts to workspaces, possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack
# Assign layouts to workspaces, possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack
komorebic named-workspace-layout I bsp
# set the gaps around the edge of the screen for a workspace
# Set the gaps around the edge of the screen for a workspace
komorebic named-workspace-padding I 20
# set the gaps between the containers for a workspace
# Set the gaps between the containers for a workspace
komorebic named-workspace-container-padding I 20
# you can assign specific apps to named workspaces
# You can assign specific apps to named workspaces
# komorebic named-workspace-rule exe "Firefox.exe" III
# Configure the invisible border dimensions
@@ -37,6 +36,7 @@ komorebic invisible-borders 7 0 14 7
# Uncomment the next lines if you want a visual border around the active window
# komorebic active-window-border-colour 66 165 245 --window-kind single
# komorebic active-window-border-colour 256 165 66 --window-kind stack
# komorebic active-window-border-colour 255 51 153 --window-kind monocle
# komorebic active-window-border enable
komorebic complete-configuration

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.15"
version = "0.1.19"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -13,56 +13,38 @@ edition = "2021"
[dependencies]
komorebi-core = { path = "../komorebi-core" }
bitflags = "1"
bitflags = "2"
clap = { version = "4", features = ["derive"] }
color-eyre = "0.6"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = "3"
dirs = "4"
getset = "0.1"
hotwatch = "0.4"
lazy_static = "1"
miow = "0.5"
nanoid = "0.4"
net2 = "0.2"
os_info = "3.6"
os_info = "3.7"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
paste = "1"
regex = "1"
schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.24", features = ["derive"] }
sysinfo = "0.27"
strum = { version = "0.25", features = ["derive"] }
sysinfo = "0.29"
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uds_windows = "1"
which = "4"
which = "5"
winput = "0.2"
winreg = "0.10"
windows-interface = { version = "0.44" }
windows-implement = { version = "0.44" }
[dependencies.windows]
version = "0.44"
features = [
"implement",
"Win32_System_Com",
"Win32_UI_Shell_Common", # for IObjectArray
"Win32_Foundation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_System_LibraryLoader",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_UI_Accessibility",
"Win32_UI_HiDpi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging",
"Win32_System_SystemServices"
]
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

@@ -14,6 +14,7 @@ use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use komorebi_core::Rect;
use crate::window::should_act;
use crate::window::Window;
use crate::windows_callbacks;
use crate::WindowsApi;
@@ -21,6 +22,7 @@ use crate::BORDER_HWND;
use crate::BORDER_OFFSET;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::BORDER_RECT;
use crate::REGEX_IDENTIFIERS;
use crate::TRANSPARENCY_COLOUR;
use crate::WINDOWS_11;
@@ -46,7 +48,7 @@ impl Border {
let class_name = PCSTR(name.as_ptr());
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
let window_class = WNDCLASSA {
hInstance: instance,
hInstance: instance.into(),
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(windows_callbacks::border_window),
@@ -108,19 +110,24 @@ impl Border {
Self::create("komorebi-border-window")?;
}
let mut should_expand_border = false;
let mut rect = WindowsApi::window_rect(window.hwnd())?;
rect.top -= invisible_borders.bottom;
rect.bottom += invisible_borders.bottom;
let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
if border_overflows.contains(&window.title()?)
|| border_overflows.contains(&window.exe()?)
|| border_overflows.contains(&window.class()?)
{
should_expand_border = true;
}
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let title = &window.title()?;
let exe_name = &window.exe()?;
let class = &window.class()?;
let should_expand_border = should_act(
title,
exe_name,
class,
&border_overflows,
&regex_identifiers,
);
if should_expand_border {
rect.left -= invisible_borders.left;

View File

@@ -10,8 +10,8 @@ use interfaces::IServiceProvider;
use std::ffi::c_void;
use windows::core::ComInterface;
use windows::core::Interface;
use windows::core::Vtable;
use windows::Win32::Foundation::HWND;
use windows::Win32::System::Com::CoCreateInstance;
use windows::Win32::System::Com::CoInitializeEx;

View File

@@ -39,7 +39,7 @@ impl Hidden {
let class_name = PCSTR(name.as_ptr());
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
let window_class = WNDCLASSA {
hInstance: instance,
hInstance: instance.into(),
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(windows_callbacks::hidden_window),

View File

@@ -1,5 +1,10 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::redundant_pub_crate)]
#![allow(
clippy::missing_errors_doc,
clippy::redundant_pub_crate,
clippy::significant_drop_tightening,
clippy::significant_drop_in_scrutinee
)]
use std::collections::HashMap;
use std::fs::File;
@@ -11,22 +16,24 @@ 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")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use regex::Regex;
use schemars::JsonSchema;
use serde::Serialize;
use sysinfo::Process;
@@ -40,6 +47,9 @@ use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use crate::hidden::Hidden;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
use komorebi_core::Rect;
use komorebi_core::SocketMessage;
@@ -48,6 +58,7 @@ use crate::process_command::listen_for_commands;
use crate::process_command::listen_for_commands_tcp;
use crate::process_event::listen_for_events;
use crate::process_movement::listen_for_movements;
use crate::static_config::StaticConfig;
use crate::window_manager::State;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
@@ -56,6 +67,7 @@ use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;
mod animation;
mod border;
mod com;
mod container;
@@ -65,6 +77,7 @@ mod process_command;
mod process_event;
mod process_movement;
mod set_window_position;
mod static_config;
mod styles;
mod window;
mod window_manager;
@@ -75,35 +88,87 @@ mod winevent;
mod winevent_listener;
mod workspace;
type WorkspaceRule = (usize, usize, bool);
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("steam.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
]));
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> =
Arc::new(Mutex::new(vec![
"explorer.exe".to_string(),
"firefox.exe".to_string(),
"chrome.exe".to_string(),
"idea64.exe".to_string(),
"ApplicationFrameHost.exe".to_string(),
"steam.exe".to_string(),
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("explorer.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("firefox.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("chrome.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("idea64.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("ApplicationFrameHost.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("steam.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}
]));
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"firefox.exe".to_string(),
"idea64.exe".to_string(),
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("firefox.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("idea64.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
]));
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, WorkspaceRule>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
// mstsc.exe creates these on Windows 11 when a WSL process is launched
// https://github.com/LGUG2Z/komorebi/issues/74
"OPContainerClass".to_string(),
"IHWindowClass".to_string()
IdWithIdentifier {
kind: ApplicationIdentifier::Class,
id: String::from("OPContainerClass"),
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Class,
id: String::from("IHWindowClass"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}
]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"Chrome_RenderWidgetHostHWND".to_string(),
]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![]));
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"X410.exe".to_string(),
"vcxsrv.exe".to_string(),
@@ -122,8 +187,7 @@ lazy_static! {
home
} else {
panic!(
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
);
}
})
@@ -152,8 +216,17 @@ 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![]));
}
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
@@ -163,10 +236,14 @@ pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);
pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_MONOCLE: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0);
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);
@@ -181,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);
@@ -234,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.clone();
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() {
@@ -249,27 +321,15 @@ 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("autohotkey.exe")
Command::new(&*AHK_EXE)
.arg(config_ahk.as_os_str())
.output()?;
}
@@ -277,6 +337,7 @@ pub fn load_configuration() -> Result<()> {
Ok(())
}
#[must_use]
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
@@ -297,7 +358,7 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
// This is the path on Windows 11
if current.is_none() {
current = hkcu
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
.open_subkey(r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops")
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
@@ -336,9 +397,9 @@ pub struct Notification {
pub fn notify_subscribers(notification: &str) -> Result<()> {
let mut stale_subscriptions = vec![];
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
for (subscriber, pipe) in subscriptions.iter_mut() {
match writeln!(pipe, "{}", notification) {
Ok(_) => {
for (subscriber, pipe) in &mut *subscriptions {
match writeln!(pipe, "{notification}") {
Ok(()) => {
tracing::debug!("pushed notification to subscriber: {}", subscriber);
}
Err(error) => {
@@ -394,92 +455,113 @@ 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(short, long)]
config: Option<PathBuf>,
}
#[tracing::instrument]
#[allow(clippy::nonminimal_bool)]
#[allow(clippy::cognitive_complexity)]
fn main() -> Result<()> {
let opts: Opts = Opts::parse();
CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
let arg_count = std::env::args().count();
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
WindowsApi::set_process_dpi_awareness_context()?;
let has_valid_args = arg_count == 1
|| (arg_count == 2
&& (opts.await_configuration || opts.focus_follows_mouse || opts.tcp_port.is_some()))
|| (arg_count == 3 && opts.await_configuration && opts.focus_follows_mouse)
|| (arg_count == 3 && opts.tcp_port.is_some() && opts.focus_follows_mouse)
|| (arg_count == 3 && opts.tcp_port.is_some() && opts.await_configuration)
|| (arg_count == 4
&& (opts.focus_follows_mouse && opts.await_configuration && opts.tcp_port.is_some()));
let session_id = WindowsApi::process_id_to_session_id()?;
SESSION_ID.store(session_id, Ordering::SeqCst);
if has_valid_args {
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
WindowsApi::set_process_dpi_awareness_context()?;
let mut system = sysinfo::System::new_all();
system.refresh_processes();
let session_id = WindowsApi::process_id_to_session_id()?;
SESSION_ID.store(session_id, Ordering::SeqCst);
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe").collect();
let mut system = sysinfo::System::new_all();
system.refresh_processes();
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe").collect();
if matched_procs.len() > 1 {
let mut len = matched_procs.len();
for proc in matched_procs {
if proc.root().ends_with("shims") {
len -= 1;
}
}
if len > 1 {
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
if matched_procs.len() > 1 {
let mut len = matched_procs.len();
for proc in matched_procs {
if proc.root().ends_with("shims") {
len -= 1;
}
}
// File logging worker guard has to have an assignment in the main fn to work
let (_guard, _color_guard) = setup()?;
if len > 1 {
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
}
}
#[cfg(feature = "deadlock_detection")]
detect_deadlocks();
// File logging worker guard has to have an assignment in the main fn to work
let (_guard, _color_guard) = setup()?;
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
crossbeam_channel::unbounded();
WindowsApi::foreground_lock_timeout()?;
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
winevent_listener.start();
#[cfg(feature = "deadlock_detection")]
detect_deadlocks();
Hidden::create("komorebi-hidden")?;
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
crossbeam_channel::unbounded();
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
winevent_listener.start();
Hidden::create("komorebi-hidden")?;
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()
);
Arc::new(Mutex::new(StaticConfig::preload(
config,
Arc::new(Mutex::new(incoming)),
)?))
} else {
Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
incoming,
)))?));
)))?))
};
wm.lock().init()?;
listen_for_commands(wm.clone());
wm.lock().init()?;
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
};
if let Some(config) = &static_config {
StaticConfig::postload(config, &wm)?;
}
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
listen_for_commands(wm.clone());
std::thread::spawn(|| {
load_configuration().expect("could not load configuration");
});
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
};
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
if static_config.is_none() {
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
if opts.await_configuration {
let backoff = Backoff::new();
@@ -487,36 +569,34 @@ fn main() -> Result<()> {
backoff.snooze();
}
}
wm.lock().retile_all(false)?;
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {
ctrlc_sender
.send(())
.expect("could not send signal on ctrl-c channel");
})?;
ctrlc_receiver
.recv()
.expect("could not receive signal on ctrl-c channel");
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
wm.lock().restore_all_windows();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
std::process::exit(130);
}
Ok(())
wm.lock().retile_all(false)?;
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {
ctrlc_sender
.send(())
.expect("could not send signal on ctrl-c channel");
})?;
ctrlc_receiver
.recv()
.expect("could not receive signal on ctrl-c channel");
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
wm.lock().restore_all_windows()?;
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
std::process::exit(130);
}

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;
@@ -105,6 +106,10 @@ impl Monitor {
}
}
pub fn remove_workspaces(&mut self) -> VecDeque<Workspace> {
self.workspaces_mut().drain(..).collect()
}
#[tracing::instrument(skip(self))]
pub fn move_container_to_workspace(
&mut self,
@@ -116,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,9 @@ 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;
use komorebi_core::Axis;
use komorebi_core::FocusFollowsMouseImplementation;
@@ -36,6 +39,7 @@ use komorebi_core::WindowKind;
use crate::border::Border;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::static_config::StaticConfig;
use crate::window::Window;
use crate::window_manager;
use crate::window_manager::WindowManager;
@@ -43,7 +47,11 @@ 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;
use crate::BORDER_COLOUR_STACK;
use crate::BORDER_ENABLED;
@@ -60,7 +68,9 @@ use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
@@ -94,7 +104,7 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
#[tracing::instrument]
pub fn listen_for_commands_tcp(wm: Arc<Mutex<WindowManager>>, port: usize) {
let listener =
TcpListener::bind(format!("0.0.0.0:{}", port)).expect("could not start tcp server");
TcpListener::bind(format!("0.0.0.0:{port}")).expect("could not start tcp server");
std::thread::spawn(move || {
tracing::info!("listening on 0.0.0.0:43663");
@@ -214,36 +224,60 @@ impl WindowManager {
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
}
}
SocketMessage::WorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
SocketMessage::InitialWorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
self.handle_initial_workspace_rules(id, monitor_idx, workspace_idx)?;
}
SocketMessage::InitialNamedWorkspaceRule(_, ref id, ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
let mut workspace_rules = WORKSPACE_RULES.lock();
workspace_rules.insert(id.to_string(), (monitor_idx, workspace_idx));
self.handle_initial_workspace_rules(id, monitor_idx, workspace_idx)?;
}
self.enforce_workspace_rules()?;
}
SocketMessage::WorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
self.handle_definitive_workspace_rules(id, monitor_idx, workspace_idx)?;
}
SocketMessage::NamedWorkspaceRule(_, ref id, ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
{
let mut workspace_rules = WORKSPACE_RULES.lock();
workspace_rules.insert(id.to_string(), (monitor_idx, workspace_idx));
}
self.enforce_workspace_rules()?;
self.handle_definitive_workspace_rules(id, monitor_idx, workspace_idx)?;
}
}
SocketMessage::ManageRule(_, ref id) => {
SocketMessage::ManageRule(identifier, ref id) => {
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
if !manage_identifiers.contains(id) {
manage_identifiers.push(id.to_string());
let mut should_push = true;
for m in &*manage_identifiers {
if m.id.eq(id) {
should_push = false;
}
}
if should_push {
manage_identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
});
}
}
SocketMessage::FloatRule(identifier, ref id) => {
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
if !float_identifiers.contains(id) {
float_identifiers.push(id.to_string());
let mut should_push = true;
for f in &*float_identifiers {
if f.id.eq(id) {
should_push = false;
}
}
if should_push {
float_identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
});
}
let invisible_borders = self.invisible_borders;
@@ -255,9 +289,8 @@ impl WindowManager {
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace"))?
.containers()
.iter()
{
for window in container.windows().iter() {
for window in container.windows() {
match identifier {
ApplicationIdentifier::Exe => {
if window.exe()? == *id {
@@ -293,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)?;
}
@@ -321,6 +376,18 @@ impl WindowManager {
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SwapWorkspacesToMonitorNumber(monitor_idx) => {
self.swap_focused_monitor(monitor_idx)?;
}
SocketMessage::CycleMoveContainerToMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false)?;
}
@@ -343,6 +410,15 @@ impl WindowManager {
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, false)?;
}
SocketMessage::CycleSendContainerToMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.move_container_to_monitor(monitor_idx, None, false)?;
}
SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), false)?;
}
@@ -398,11 +474,12 @@ impl WindowManager {
SocketMessage::Retile => self.retile_all(false)?,
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
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)?;
@@ -433,7 +510,7 @@ impl WindowManager {
monitor_idx,
workspace_idx,
at_container_count,
path.clone(),
path,
)?;
}
SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {
@@ -443,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) => {
@@ -484,7 +561,7 @@ impl WindowManager {
monitor_idx,
workspace_idx,
at_container_count,
path.clone(),
path,
)?;
}
}
@@ -536,6 +613,29 @@ impl WindowManager {
self.show_border()?;
};
}
SocketMessage::FocusWorkspaceNumbers(workspace_idx) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
let focused_monitor_idx = self.focused_monitor_idx();
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
if i != focused_monitor_idx {
monitor.focus_workspace(workspace_idx)?;
monitor.load_focused_workspace(false)?;
}
}
self.focus_workspace(workspace_idx)?;
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
@@ -556,7 +656,7 @@ impl WindowManager {
tracing::info!(
"received stop command, restoring all hidden windows and terminating process"
);
self.restore_all_windows();
self.restore_all_windows()?;
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
@@ -595,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())?;
}
@@ -618,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())?;
}
@@ -637,17 +731,18 @@ impl WindowManager {
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if matches!(axis, Axis::Horizontal) {
#[allow(clippy::cast_precision_loss)]
let percentage = custom
.primary_width_percentage()
.unwrap_or(100 / custom.len());
.unwrap_or(100.0 / (custom.len() as f32));
if no_layout_rules {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
custom.set_primary_width_percentage(percentage + 5.0);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
custom.set_primary_width_percentage(percentage - 5.0);
}
}
} else {
@@ -656,10 +751,12 @@ impl WindowManager {
if let Layout::Custom(ref mut custom) = rule.1 {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
custom
.set_primary_width_percentage(percentage + 5.0);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
custom
.set_primary_width_percentage(percentage - 5.0);
}
}
}
@@ -667,8 +764,8 @@ impl WindowManager {
}
}
}
// Otherwise proceed with the resizing logic for individual window containers in the
// assumed BSP layout
// Otherwise proceed with the resizing logic for individual window containers in the
// assumed BSP layout
} else {
match axis {
Axis::Horizontal => {
@@ -752,9 +849,10 @@ impl WindowManager {
}
}
FocusFollowsMouseImplementation::Windows => {
if let Some(FocusFollowsMouseImplementation::Komorebi) =
self.focus_follows_mouse
{
if matches!(
self.focus_follows_mouse,
Some(FocusFollowsMouseImplementation::Komorebi)
) {
tracing::warn!(
"the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled"
);
@@ -799,9 +897,10 @@ impl WindowManager {
}
}
FocusFollowsMouseImplementation::Windows => {
if let Some(FocusFollowsMouseImplementation::Komorebi) =
self.focus_follows_mouse
{
if matches!(
self.focus_follows_mouse,
Some(FocusFollowsMouseImplementation::Komorebi)
) {
tracing::warn!(
"the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled"
);
@@ -826,6 +925,9 @@ impl WindowManager {
SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
}
SocketMessage::ReloadStaticConfiguration(ref pathbuf) => {
self.reload_static_configuration(pathbuf)?;
}
SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
@@ -835,28 +937,75 @@ impl WindowManager {
SocketMessage::WatchConfiguration(enable) => {
self.watch_configuration(enable)?;
}
SocketMessage::IdentifyBorderOverflowApplication(_, ref id) => {
SocketMessage::IdentifyBorderOverflowApplication(identifier, ref id) => {
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
let mut should_push = true;
for i in &*identifiers {
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
});
}
}
SocketMessage::IdentifyObjectNameChangeApplication(_, ref id) => {
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
let mut should_push = true;
for i in &*identifiers {
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
});
}
}
SocketMessage::IdentifyTrayApplication(_, ref id) => {
SocketMessage::IdentifyTrayApplication(identifier, ref id) => {
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
let mut should_push = true;
for i in &*identifiers {
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
});
}
}
SocketMessage::IdentifyLayeredApplication(_, ref id) => {
SocketMessage::IdentifyLayeredApplication(identifier, ref id) => {
let mut identifiers = LAYERED_WHITELIST.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
let mut should_push = true;
for i in &*identifiers {
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
});
}
}
SocketMessage::ManageFocusedWindow => {
@@ -883,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)
@@ -897,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)?;
@@ -920,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)?;
@@ -937,7 +1080,7 @@ impl WindowManager {
}
SocketMessage::AddSubscriber(ref subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
let pipe_path = format!(r"\\.\pipe\{}", subscriber);
let pipe_path = format!(r"\\.\pipe\{subscriber}");
let pipe = connect(&pipe_path).map_err(|_| {
anyhow!("the named pipe '{}' has not yet been created; please create it before running this command", pipe_path)
})?;
@@ -1000,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 => {
@@ -1009,6 +1161,9 @@ impl WindowManager {
WindowKind::Stack => {
BORDER_COLOUR_STACK.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
}
WindowKind::Monocle => {
BORDER_COLOUR_MONOCLE.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
}
}
WindowsApi::invalidate_border_rect()?;
@@ -1034,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())?;
@@ -1047,17 +1208,77 @@ 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())?;
}
SocketMessage::StaticConfigSchema => {
let socket_message = schema_for!(StaticConfig);
let schema = serde_json::to_string_pretty(&socket_message)?;
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 socket = DATA_DIR.join("komorebic.sock");
let mut stream = UnixStream::connect(socket)?;
stream.write_all(config.as_bytes())?;
}
SocketMessage::RemoveTitleBar(_, ref id) => {
let mut identifiers = NO_TITLEBAR.lock();
if !identifiers.contains(id) {
identifiers.push(id.clone());
}
}
SocketMessage::ToggleTitleBars => {
let current = REMOVE_TITLEBARS.load(Ordering::SeqCst);
REMOVE_TITLEBARS.store(!current, Ordering::SeqCst);
self.update_focused_workspace(false)?;
}
};
match message {
SocketMessage::ToggleMonocle => {
let current = BORDER_COLOUR_CURRENT.load(Ordering::SeqCst);
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
if monocle != 0 {
if current == monocle {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
} else {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
}
}
}
SocketMessage::StackWindow(_) => {
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
if stack != 0 {
BORDER_COLOUR_CURRENT
.store(BORDER_COLOUR_STACK.load(Ordering::SeqCst), Ordering::SeqCst);
}
}
SocketMessage::UnstackWindow => {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
}
_ => {}
}
match message {
SocketMessage::ChangeLayout(_)
| SocketMessage::CycleLayout(_)
| SocketMessage::ChangeLayoutCustom(_)
| SocketMessage::FlipLayout(_)
| SocketMessage::ManageFocusedWindow
@@ -1071,6 +1292,8 @@ impl WindowManager {
| SocketMessage::ToggleMaximize
| SocketMessage::Promote
| SocketMessage::PromoteFocus
| SocketMessage::StackWindow(_)
| SocketMessage::UnstackWindow
| SocketMessage::Retile
// Adding this one so that changes can be seen instantly after
// modifying the active window border offset
@@ -1081,9 +1304,10 @@ impl WindowManager {
| SocketMessage::FocusWindow(_)
| SocketMessage::InvisibleBorders(_)
| SocketMessage::WorkAreaOffset(_)
| 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;
@@ -1121,6 +1345,51 @@ impl WindowManager {
tracing::info!("processed");
Ok(())
}
#[tracing::instrument(skip(self))]
fn handle_initial_workspace_rules(
&mut self,
id: &String,
monitor_idx: usize,
workspace_idx: usize,
) -> Result<()> {
self.handle_workspace_rules(id, monitor_idx, workspace_idx, true)?;
Ok(())
}
#[tracing::instrument(skip(self))]
fn handle_definitive_workspace_rules(
&mut self,
id: &String,
monitor_idx: usize,
workspace_idx: usize,
) -> Result<()> {
self.handle_workspace_rules(id, monitor_idx, workspace_idx, false)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn handle_workspace_rules(
&mut self,
id: &String,
monitor_idx: usize,
workspace_idx: usize,
initial_workspace_rule: bool,
) -> Result<()> {
{
let mut workspace_rules = WORKSPACE_RULES.lock();
workspace_rules.insert(
id.to_string(),
(monitor_idx, workspace_idx, initial_workspace_rule),
);
}
self.enforce_workspace_rules()?;
Ok(())
}
}
pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, stream: UnixStream) -> Result<()> {
@@ -1169,11 +1438,8 @@ pub fn read_commands_tcp(
break;
}
Ok(size) => {
let message = if let Ok(message) =
SocketMessage::from_str(&String::from_utf8_lossy(&buf[..size]))
{
message
} else {
let Ok(message) = SocketMessage::from_str(&String::from_utf8_lossy(&buf[..size]))
else {
tracing::warn!("client sent an invalid message, disconnecting: {addr}");
let mut connections = TCP_CONNECTIONS.lock();
connections.remove(addr);

View File

@@ -15,12 +15,14 @@ use komorebi_core::WindowContainerBehaviour;
use crate::border::Border;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::window::should_act;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_MONOCLE;
use crate::BORDER_COLOUR_SINGLE;
use crate::BORDER_COLOUR_STACK;
use crate::BORDER_ENABLED;
@@ -28,6 +30,7 @@ use crate::BORDER_HIDDEN;
use crate::BORDER_HWND;
use crate::DATA_DIR;
use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
#[tracing::instrument]
@@ -149,6 +152,10 @@ impl WindowManager {
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
self.update_focused_workspace(false)?;
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
already_moved_window_handles.remove(&window.hwnd);
}
WindowManagerEvent::Minimize(_, window) => {
let mut hide = false;
@@ -174,15 +181,26 @@ impl WindowManager {
{
let tray_and_multi_window_identifiers =
TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let title = &window.title()?;
let exe_name = &window.exe()?;
let class = &window.class()?;
// We don't want to purge windows that have been deliberately hidden by us, eg. when
// they are not on the top of a container stack.
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
let should_act = should_act(
title,
exe_name,
class,
&tray_and_multi_window_identifiers,
&regex_identifiers,
);
if ((!window.is_window()
|| tray_and_multi_window_identifiers.contains(&window.exe()?))
|| tray_and_multi_window_identifiers.contains(&window.class()?))
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
if !window.is_window()
|| should_act
|| !programmatically_hidden_hwnds.contains(&window.hwnd)
{
hide = true;
}
@@ -192,6 +210,10 @@ impl WindowManager {
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
self.update_focused_workspace(false)?;
}
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
already_moved_window_handles.remove(&window.hwnd);
}
WindowManagerEvent::FocusChange(_, window) => {
let workspace = self.focused_workspace_mut()?;
@@ -493,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) {
@@ -507,9 +530,11 @@ 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;
if self
.focused_workspace()?
.floating_windows()
@@ -523,6 +548,7 @@ impl WindowManager {
if let Some(monocle_container) = self.focused_workspace()?.monocle_container() {
if let Some(window) = monocle_container.focused_window() {
target_window = Option::from(*window);
target_window_is_monocle = true;
}
}
@@ -539,7 +565,12 @@ impl WindowManager {
let container_size = self.focused_container()?.windows().len();
target_window = Option::from(*self.focused_window()?);
if container_size > 1 {
if target_window_is_monocle {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
} else if container_size > 1 {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_STACK.load(Ordering::SeqCst),
Ordering::SeqCst,
@@ -566,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

@@ -18,7 +18,10 @@ pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
loop {
let focus_follows_mouse = wm.lock().focus_follows_mouse;
if let Some(FocusFollowsMouseImplementation::Komorebi) = focus_follows_mouse {
if matches!(
focus_follows_mouse,
Some(FocusFollowsMouseImplementation::Komorebi)
) {
match receiver.next_event() {
// Don't want to send any raise events while we are dragging or resizing
Event::MouseButton { action, .. } => match action {
@@ -28,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

@@ -0,0 +1,998 @@
use crate::border::Border;
use crate::current_virtual_desktop;
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;
use crate::BORDER_COLOUR_STACK;
use crate::BORDER_ENABLED;
use crate::BORDER_HWND;
use crate::BORDER_OFFSET;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::BORDER_WIDTH;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REGEX_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_RULES;
use color_eyre::Result;
use crossbeam_channel::Receiver;
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;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationBehaviour;
use komorebi_core::Rect;
use komorebi_core::SocketMessage;
use komorebi_core::WindowContainerBehaviour;
use parking_lot::Mutex;
use regex::Regex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::HashSet;
use std::io::ErrorKind;
use std::io::Write;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Rgb {
/// Red
pub r: u32,
/// Green
pub g: u32,
/// Blue
pub b: u32,
}
impl From<u32> for Rgb {
fn from(value: u32) -> Self {
Self {
r: value & 0xff,
g: value >> 8 & 0xff,
b: value >> 16 & 0xff,
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ActiveWindowBorderColours {
/// Border colour when the container contains a single window
pub single: Rgb,
/// Border colour when the container contains multiple windows
pub stack: Rgb,
/// Border colour when the container is in monocle mode
pub monocle: Rgb,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceConfig {
/// Name
pub name: String,
/// Layout (default: BSP)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout: Option<DefaultLayout>,
/// Custom Layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_layout: Option<PathBuf>,
/// Layout rules (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
/// Layout rules (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub container_padding: Option<i32>,
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
/// Initial workspace application rules
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_workspace_rules: Option<Vec<IdWithIdentifier>>,
/// Permanent workspace application rules
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_rules: Option<Vec<IdWithIdentifier>>,
}
impl From<&Workspace> for WorkspaceConfig {
fn from(value: &Workspace) -> Self {
let mut layout_rules = HashMap::new();
for (threshold, layout) in value.layout_rules() {
match layout {
Layout::Default(value) => {
layout_rules.insert(*threshold, *value);
}
Layout::Custom(_) => {}
}
}
let workspace_rules = WORKSPACE_RULES.lock();
let mut initial_ws_rules = vec![];
let mut ws_rules = vec![];
for (identifier, (_, _, is_initial)) in &*workspace_rules {
if identifier.ends_with("exe") {
let rule = IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: identifier.clone(),
matching_strategy: None,
};
if *is_initial {
initial_ws_rules.push(rule);
} else {
ws_rules.push(rule);
}
}
}
let initial_ws_rules = if initial_ws_rules.is_empty() {
None
} else {
Option::from(initial_ws_rules)
};
let ws_rules = if ws_rules.is_empty() {
None
} else {
Option::from(ws_rules)
};
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
let container_padding = value.container_padding().and_then(|container_padding| {
if container_padding == default_container_padding {
None
} else {
Option::from(container_padding)
}
});
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
if workspace_padding == default_workspace_padding {
None
} else {
Option::from(workspace_padding)
}
});
Self {
name: value
.name()
.clone()
.unwrap_or_else(|| String::from("unnamed")),
layout: match value.layout() {
Layout::Default(layout) => Option::from(*layout),
// TODO: figure out how we might resolve file references in the future
Layout::Custom(_) => None,
},
custom_layout: None,
layout_rules: Option::from(layout_rules),
// TODO: figure out how we might resolve file references in the future
custom_layout_rules: None,
container_padding,
workspace_padding,
initial_workspace_rules: initial_ws_rules,
workspace_rules: ws_rules,
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct MonitorConfig {
/// Workspace configurations
pub workspaces: Vec<WorkspaceConfig>,
/// Monitor-specific work area offset (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub work_area_offset: Option<Rect>,
}
impl From<&Monitor> for MonitorConfig {
fn from(value: &Monitor) -> Self {
let mut workspaces = vec![];
for w in value.workspaces() {
workspaces.push(WorkspaceConfig::from(w));
}
Self {
workspaces,
work_area_offset: value.work_area_offset(),
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct StaticConfig {
/// Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to
#[serde(skip_serializing_if = "Option::is_none")]
pub invisible_borders: Option<Rect>,
/// Delta to resize windows by (default 50)
#[serde(skip_serializing_if = "Option::is_none")]
pub resize_delta: Option<i32>,
/// Determine what happens when a new window is opened (default: Create)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
#[serde(skip_serializing_if = "Option::is_none")]
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
/// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)
#[serde(skip_serializing_if = "Option::is_none")]
pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,
/// Determine focus follows mouse implementation (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
/// Enable or disable mouse follows focus (default: true)
#[serde(skip_serializing_if = "Option::is_none")]
pub mouse_follows_focus: Option<bool>,
/// Path to applications.yaml from komorebi-application-specific-configurations (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub app_specific_configuration_path: Option<PathBuf>,
/// DEPRECATED from v0.1.19: use active_window_border_width instead
#[serde(skip_serializing_if = "Option::is_none")]
pub border_width: Option<i32>,
/// DEPRECATED from v0.1.19: use active_window_border_offset instead
#[serde(skip_serializing_if = "Option::is_none")]
pub border_offset: Option<Rect>,
/// Width of the active window border (default: 20)
#[serde(skip_serializing_if = "Option::is_none")]
pub active_window_border_width: Option<i32>,
/// Offset of the active window border (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub active_window_border_offset: Option<i32>,
/// Display an active window border (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub active_window_border: Option<bool>,
/// Active window border colours for different container types
#[serde(skip_serializing_if = "Option::is_none")]
pub active_window_border_colours: Option<ActiveWindowBorderColours>,
/// Global default workspace padding (default: 10)
#[serde(skip_serializing_if = "Option::is_none")]
pub default_workspace_padding: Option<i32>,
/// Global default container padding (default: 10)
#[serde(skip_serializing_if = "Option::is_none")]
pub default_container_padding: Option<i32>,
/// Monitor and workspace configurations
#[serde(skip_serializing_if = "Option::is_none")]
pub monitors: Option<Vec<MonitorConfig>>,
/// Always send the ALT key when using focus commands (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub alt_focus_hack: Option<bool>,
/// Which Windows signal to use when hiding windows (default: minimize)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_hiding_behaviour: Option<HidingBehaviour>,
/// Global work area (space used for tiling) offset (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub global_work_area_offset: Option<Rect>,
/// Individual window floating rules
#[serde(skip_serializing_if = "Option::is_none")]
pub float_rules: Option<Vec<IdWithIdentifier>>,
/// Individual window force-manage rules
#[serde(skip_serializing_if = "Option::is_none")]
pub manage_rules: Option<Vec<IdWithIdentifier>>,
/// Identify border overflow applications
#[serde(skip_serializing_if = "Option::is_none")]
pub border_overflow_applications: Option<Vec<IdWithIdentifier>>,
/// Identify tray and multi-window applications
#[serde(skip_serializing_if = "Option::is_none")]
pub tray_and_multi_window_applications: Option<Vec<IdWithIdentifier>>,
/// Identify applications that have the WS_EX_LAYERED extended window style
#[serde(skip_serializing_if = "Option::is_none")]
pub layered_applications: Option<Vec<IdWithIdentifier>>,
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
#[serde(skip_serializing_if = "Option::is_none")]
pub object_name_change_applications: Option<Vec<IdWithIdentifier>>,
/// 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 {
#[allow(clippy::too_many_lines)]
fn from(value: &WindowManager) -> Self {
let default_invisible_borders = Rect {
left: 7,
top: 0,
right: 14,
bottom: 7,
};
let mut monitors = vec![];
for m in value.monitors() {
monitors.push(MonitorConfig::from(m));
}
let mut to_remove = vec![];
let workspace_rules = WORKSPACE_RULES.lock();
for (m_idx, m) in monitors.iter().enumerate() {
for (w_idx, w) in m.workspaces.iter().enumerate() {
if let Some(rules) = &w.initial_workspace_rules {
for iwsr in rules {
for (identifier, (monitor_idx, workspace_idx, _)) in &*workspace_rules {
if iwsr.id.eq(identifier)
&& (*monitor_idx != m_idx || *workspace_idx != w_idx)
{
to_remove.push((m_idx, w_idx, iwsr.id.clone()));
}
}
}
}
if let Some(rules) = &w.workspace_rules {
for wsr in rules {
for (identifier, (monitor_idx, workspace_idx, _)) in &*workspace_rules {
if wsr.id.eq(identifier)
&& (*monitor_idx != m_idx || *workspace_idx != w_idx)
{
to_remove.push((m_idx, w_idx, wsr.id.clone()));
}
}
}
}
}
}
for (m_idx, w_idx, id) in to_remove {
if let Some(monitor) = monitors.get_mut(m_idx) {
if let Some(workspace) = monitor.workspaces.get_mut(w_idx) {
if let Some(rules) = &mut workspace.workspace_rules {
rules.retain(|r| r.id != id);
}
if let Some(rules) = &mut workspace.initial_workspace_rules {
rules.retain(|r| r.id != id);
}
}
}
}
let border_colours = if BORDER_COLOUR_SINGLE.load(Ordering::SeqCst) == 0 {
None
} else {
Option::from(ActiveWindowBorderColours {
single: Rgb::from(BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)),
stack: Rgb::from(if BORDER_COLOUR_STACK.load(Ordering::SeqCst) == 0 {
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
} else {
BORDER_COLOUR_STACK.load(Ordering::SeqCst)
}),
monocle: Rgb::from(if BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst) == 0 {
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
} else {
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst)
}),
})
};
Self {
invisible_borders: if value.invisible_borders == default_invisible_borders {
None
} else {
Option::from(value.invisible_borders)
},
resize_delta: Option::from(value.resize_delta),
window_container_behaviour: Option::from(value.window_container_behaviour),
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
unmanaged_window_operation_behaviour: Option::from(
value.unmanaged_window_operation_behaviour,
),
focus_follows_mouse: value.focus_follows_mouse,
mouse_follows_focus: Option::from(value.mouse_follows_focus),
app_specific_configuration_path: None,
active_window_border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)),
active_window_border_offset: BORDER_OFFSET
.lock()
.map_or(None, |offset| Option::from(offset.left)),
border_width: None,
border_offset: None,
active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)),
active_window_border_colours: border_colours,
default_workspace_padding: Option::from(
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
),
default_container_padding: Option::from(
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
),
monitors: Option::from(monitors),
alt_focus_hack: Option::from(ALT_FOCUS_HACK.load(Ordering::SeqCst)),
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
global_work_area_offset: value.work_area_offset,
float_rules: None,
manage_rules: None,
border_overflow_applications: None,
tray_and_multi_window_applications: None,
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()),
}
}
}
impl StaticConfig {
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
fn apply_globals(&mut self) -> Result<()> {
if let Some(monitor_index_preferences) = &self.monitor_index_preferences {
let mut preferences = MONITOR_INDEX_PREFERENCES.lock();
*preferences = monitor_index_preferences.clone();
}
if let Some(behaviour) = self.window_hiding_behaviour {
let mut window_hiding_behaviour = HIDING_BEHAVIOUR.lock();
*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);
}
if let Some(container) = self.default_container_padding {
DEFAULT_CONTAINER_PADDING.store(container, Ordering::SeqCst);
}
if let Some(workspace) = self.default_workspace_padding {
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
}
self.active_window_border_width.map_or_else(
|| {
BORDER_WIDTH.store(20, Ordering::SeqCst);
},
|width| {
BORDER_WIDTH.store(width, Ordering::SeqCst);
},
);
self.active_window_border_offset.map_or_else(
|| {
let mut border_offset = BORDER_OFFSET.lock();
*border_offset = None;
},
|offset| {
let new_border_offset = Rect {
left: offset,
top: offset,
right: offset * 2,
bottom: offset * 2,
};
let mut border_offset = BORDER_OFFSET.lock();
*border_offset = Some(new_border_offset);
},
);
if let Some(colours) = &self.active_window_border_colours {
BORDER_COLOUR_SINGLE.store(
colours.single.r | (colours.single.g << 8) | (colours.single.b << 16),
Ordering::SeqCst,
);
BORDER_COLOUR_CURRENT.store(
colours.single.r | (colours.single.g << 8) | (colours.single.b << 16),
Ordering::SeqCst,
);
BORDER_COLOUR_STACK.store(
colours.stack.r | (colours.stack.g << 8) | (colours.stack.b << 16),
Ordering::SeqCst,
);
BORDER_COLOUR_MONOCLE.store(
colours.monocle.r | (colours.monocle.g << 8) | (colours.monocle.b << 16),
Ordering::SeqCst,
);
}
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
let mut border_overflow_identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
let mut layered_identifiers = LAYERED_WHITELIST.lock();
if let Some(float) = &mut self.float_rules {
for identifier in float {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !float_identifiers.contains(identifier) {
float_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(manage) = &mut self.manage_rules {
for identifier in manage {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !manage_identifiers.contains(identifier) {
manage_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(identifiers) = &mut self.object_name_change_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !object_name_change_identifiers.contains(identifier) {
object_name_change_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(identifiers) = &mut self.layered_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !layered_identifiers.contains(identifier) {
layered_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(identifiers) = &mut self.border_overflow_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !border_overflow_identifiers.contains(identifier) {
border_overflow_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(identifiers) = &mut self.tray_and_multi_window_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !tray_and_multi_window_identifiers.contains(identifier) {
tray_and_multi_window_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(path) = &self.app_specific_configuration_path {
let path = resolve_home_path(path)?;
let content = std::fs::read_to_string(path)?;
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(float) = entry.float_identifiers {
for f in float {
let mut without_comment: IdWithIdentifier = f.into();
if without_comment.matching_strategy.is_none() {
without_comment.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !float_identifiers.contains(&without_comment) {
float_identifiers.push(without_comment.clone());
if matches!(
without_comment.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&without_comment.id)?;
regex_identifiers.insert(without_comment.id.clone(), re);
}
}
}
}
if let Some(options) = entry.options {
for o in options {
match o {
ApplicationOptions::ObjectNameChange => {
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !object_name_change_identifiers.contains(&entry.identifier) {
object_name_change_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::Layered => {
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !layered_identifiers.contains(&entry.identifier) {
layered_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::BorderOverflow => {
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !border_overflow_identifiers.contains(&entry.identifier) {
border_overflow_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::TrayAndMultiWindow => {
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !tray_and_multi_window_identifiers.contains(&entry.identifier) {
tray_and_multi_window_identifiers
.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::Force => {
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !manage_identifiers.contains(&entry.identifier) {
manage_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
}
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
pub fn preload(
path: &PathBuf,
incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>,
) -> Result<WindowManager> {
let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?;
value.apply_globals()?;
let socket = DATA_DIR.join("komorebi.sock");
match std::fs::remove_file(&socket) {
Ok(()) => {}
Err(error) => match error.kind() {
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
ErrorKind::NotFound => {}
_ => {
return Err(error.into());
}
},
};
let listener = UnixListener::bind(&socket)?;
let mut wm = WindowManager {
monitors: Ring::default(),
monitor_cache: HashMap::new(),
incoming_events: incoming,
command_listener: listener,
is_paused: false,
invisible_borders: value.invisible_borders.unwrap_or(Rect {
left: 7,
top: 0,
right: 14,
bottom: 7,
}),
virtual_desktop_id: current_virtual_desktop(),
work_area_offset: value.global_work_area_offset,
window_container_behaviour: value
.window_container_behaviour
.unwrap_or(WindowContainerBehaviour::Create),
cross_monitor_move_behaviour: value
.cross_monitor_move_behaviour
.unwrap_or(MoveBehaviour::Swap),
unmanaged_window_operation_behaviour: value
.unmanaged_window_operation_behaviour
.unwrap_or(OperationBehaviour::Op),
resize_delta: value.resize_delta.unwrap_or(50),
focus_follows_mouse: value.focus_follows_mouse,
mouse_follows_focus: value.mouse_follows_focus.unwrap_or(true),
hotwatch: Hotwatch::new()?,
has_pending_raise_op: false,
pending_move_op: None,
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 {
// 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(_) => {
let socket = DATA_DIR.join("komorebi.sock");
let mut stream =
UnixStream::connect(socket).expect("could not connect to komorebi.sock");
stream
.write_all(&bytes)
.expect("could not write to komorebi.sock");
}
_ => {}
})?;
Ok(wm)
}
pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> Result<()> {
let content = std::fs::read_to_string(path)?;
let value: Self = serde_json::from_str(&content)?;
let mut wm = wm.lock();
if let Some(monitors) = value.monitors {
for (i, monitor) in monitors.iter().enumerate() {
if let Some(m) = wm.monitors_mut().get_mut(i) {
m.ensure_workspace_count(monitor.workspaces.len());
m.set_work_area_offset(monitor.work_area_offset);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
ws.load_static_config(
monitor
.workspaces
.get(j)
.expect("no static workspace config"),
)?;
}
}
for (j, ws) in monitor.workspaces.iter().enumerate() {
if let Some(rules) = &ws.workspace_rules {
for r in rules {
wm.handle_workspace_rules(&r.id, i, j, false)?;
}
}
if let Some(rules) = &ws.initial_workspace_rules {
for r in rules {
wm.handle_workspace_rules(&r.id, i, j, true)?;
}
}
}
}
}
if value.active_window_border == Some(true) {
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
Border::create("komorebi-border-window")?;
}
BORDER_ENABLED.store(true, Ordering::SeqCst);
wm.show_border()?;
}
Ok(())
}
pub fn reload(path: &PathBuf, wm: &mut WindowManager) -> Result<()> {
let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?;
value.apply_globals()?;
if let Some(monitors) = value.monitors {
for (i, monitor) in monitors.iter().enumerate() {
if let Some(m) = wm.monitors_mut().get_mut(i) {
m.ensure_workspace_count(monitor.workspaces.len());
m.set_work_area_offset(monitor.work_area_offset);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
ws.load_static_config(
monitor
.workspaces
.get(j)
.expect("no static workspace config"),
)?;
}
}
for (j, ws) in monitor.workspaces.iter().enumerate() {
if let Some(rules) = &ws.workspace_rules {
for r in rules {
wm.handle_workspace_rules(&r.id, i, j, false)?;
}
}
if let Some(rules) = &ws.initial_workspace_rules {
for r in rules {
wm.handle_workspace_rules(&r.id, i, j, true)?;
}
}
}
}
}
if value.active_window_border == Some(true) {
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
Border::create("komorebi-border-window")?;
}
BORDER_ENABLED.store(true, Ordering::SeqCst);
wm.show_border()?;
} else {
BORDER_ENABLED.store(false, Ordering::SeqCst);
wm.hide_border()?;
}
if let Some(val) = value.invisible_borders {
wm.invisible_borders = val;
}
if let Some(val) = value.window_container_behaviour {
wm.window_container_behaviour = val;
}
if let Some(val) = value.cross_monitor_move_behaviour {
wm.cross_monitor_move_behaviour = val;
}
if let Some(val) = value.unmanaged_window_operation_behaviour {
wm.unmanaged_window_operation_behaviour = val;
}
if let Some(val) = value.resize_delta {
wm.resize_delta = val;
}
if let Some(val) = value.mouse_follows_focus {
wm.mouse_follows_focus = val;
}
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,12 +1,20 @@
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;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingStrategy;
use regex::Regex;
use schemars::JsonSchema;
use serde::ser::Error;
use serde::ser::SerializeStruct;
@@ -21,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;
@@ -32,11 +41,15 @@ use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy, JsonSchema)]
pub struct Window {
pub(crate) hwnd: isize,
animation: Animation,
}
impl Display for Window {
@@ -44,20 +57,20 @@ impl Display for Window {
let mut display = format!("(hwnd: {}", self.hwnd);
if let Ok(title) = self.title() {
write!(display, ", title: {}", title)?;
write!(display, ", title: {title}")?;
}
if let Ok(exe) = self.exe() {
write!(display, ", exe: {}", exe)?;
write!(display, ", exe: {exe}")?;
}
if let Ok(class) = self.class() {
write!(display, ", class: {}", class)?;
write!(display, ", class: {class}")?;
}
write!(display, ")")?;
write!(f, "{}", display)
write!(f, "{display}")
}
}
@@ -96,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)
}
@@ -115,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,
@@ -123,15 +185,21 @@ impl Window {
top: bool,
) -> Result<()> {
let mut rect = *layout;
let mut should_remove_border = true;
let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
if border_overflows.contains(&self.title()?)
|| border_overflows.contains(&self.exe()?)
|| border_overflows.contains(&self.class()?)
{
should_remove_border = false;
}
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let title = &self.title()?;
let class = &self.class()?;
let exe_name = &self.exe()?;
let should_remove_border = !should_act(
title,
exe_name,
class,
&border_overflows,
&regex_identifiers,
);
if should_remove_border {
// Remove the invisible borders
@@ -141,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) {
@@ -228,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(): {}",
@@ -239,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(): {}",
@@ -289,7 +367,7 @@ impl Window {
}
match WindowsApi::set_foreground_window(self.hwnd()) {
Ok(_) => {
Ok(()) => {
foregrounded = true;
}
Err(error) => {
@@ -321,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(): {}",
@@ -346,23 +424,22 @@ impl Window {
pub fn transparent(self) -> Result<()> {
let mut ex_style = self.ex_style()?;
ex_style.insert(ExtendedWindowStyle::LAYERED);
self.update_ex_style(ex_style)?;
WindowsApi::set_transparent(self.hwnd());
Ok(())
self.update_ex_style(&ex_style)?;
WindowsApi::set_transparent(self.hwnd())
}
pub fn opaque(self) -> Result<()> {
let mut ex_style = self.ex_style()?;
ex_style.remove(ExtendedWindowStyle::LAYERED);
self.update_ex_style(ex_style)
self.update_ex_style(&ex_style)
}
#[allow(dead_code)]
pub fn update_style(self, style: WindowStyle) -> Result<()> {
pub fn update_style(self, style: &WindowStyle) -> Result<()> {
WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?)
}
pub fn update_ex_style(self, style: ExtendedWindowStyle) -> Result<()> {
pub fn update_ex_style(self, style: &ExtendedWindowStyle) -> Result<()> {
WindowsApi::update_ex_style(self.hwnd(), isize::try_from(style.bits())?)
}
@@ -382,7 +459,10 @@ impl Window {
pub fn exe(self) -> Result<String> {
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
WindowsApi::exe(WindowsApi::process_handle(process_id)?)
let handle = WindowsApi::process_handle(process_id)?;
let exe = WindowsApi::exe(handle);
WindowsApi::close_process(handle)?;
exe
}
pub fn class(self) -> Result<String> {
@@ -397,6 +477,20 @@ impl Window {
WindowsApi::is_window(self.hwnd())
}
pub fn remove_title_bar(self) -> Result<()> {
let mut style = self.style()?;
style.remove(WindowStyle::CAPTION);
style.remove(WindowStyle::THICKFRAME);
self.update_style(&style)
}
pub fn add_title_bar(self) -> Result<()> {
let mut style = self.style()?;
style.insert(WindowStyle::CAPTION);
style.insert(WindowStyle::THICKFRAME);
self.update_style(&style)
}
#[tracing::instrument(fields(exe, title))]
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
if let Some(WindowManagerEvent::DisplayChange(_)) = event {
@@ -427,7 +521,7 @@ impl Window {
// If not allowing cloaked windows, we need to ensure the window is not cloaked
(false, false) => {
if let (Ok(title), Ok(exe_name), Ok(class)) = (self.title(), self.exe(), self.class()) {
return Ok(window_is_eligible(&title, &exe_name, &class, self.style()?, self.ex_style()?, event));
return Ok(window_is_eligible(&title, &exe_name, &class, &self.style()?, &self.ex_style()?, event));
}
}
_ => {}
@@ -441,59 +535,49 @@ fn window_is_eligible(
title: &String,
exe_name: &String,
class: &String,
style: WindowStyle,
ex_style: ExtendedWindowStyle,
style: &WindowStyle,
ex_style: &ExtendedWindowStyle,
event: Option<WindowManagerEvent>,
) -> bool {
let mut should_float = false;
let mut matched_identifier = None;
{
let float_identifiers = FLOAT_IDENTIFIERS.lock();
for identifier in float_identifiers.iter() {
if title.starts_with(identifier) || title.ends_with(identifier) {
should_float = true;
matched_identifier = Option::from(ApplicationIdentifier::Title);
}
if class.starts_with(identifier) || class.ends_with(identifier) {
should_float = true;
matched_identifier = Option::from(ApplicationIdentifier::Class);
}
if identifier == exe_name {
should_float = true;
matched_identifier = Option::from(ApplicationIdentifier::Exe);
}
let permaignore_classes = PERMAIGNORE_CLASSES.lock();
if permaignore_classes.contains(class) {
return false;
}
};
}
let managed_override = {
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
matched_identifier.map_or_else(
|| {
manage_identifiers.contains(exe_name)
|| manage_identifiers.contains(class)
|| manage_identifiers.contains(title)
},
|matched_identifier| match matched_identifier {
ApplicationIdentifier::Exe => manage_identifiers.contains(exe_name),
ApplicationIdentifier::Class => manage_identifiers.contains(class),
ApplicationIdentifier::Title => manage_identifiers.contains(title),
},
)
};
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let float_identifiers = FLOAT_IDENTIFIERS.lock();
let should_float = should_act(
title,
exe_name,
class,
&float_identifiers,
&regex_identifiers,
);
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
let managed_override = should_act(
title,
exe_name,
class,
&manage_identifiers,
&regex_identifiers,
);
if should_float && !managed_override {
return false;
}
let allow_layered = {
let layered_whitelist = LAYERED_WHITELIST.lock();
layered_whitelist.contains(exe_name)
|| layered_whitelist.contains(class)
|| layered_whitelist.contains(title)
};
let layered_whitelist = LAYERED_WHITELIST.lock();
let allow_layered = should_act(
title,
exe_name,
class,
&layered_whitelist,
&regex_identifiers,
);
// TODO: might need this for transparency
// let allow_layered = true;
@@ -503,7 +587,12 @@ fn window_is_eligible(
wsl2_ui_processes.contains(exe_name)
};
if (allow_wsl2_gui || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
let allow_titlebar_removed = {
let titlebars_removed = NO_TITLEBAR.lock();
titlebars_removed.contains(exe_name)
};
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
// Get a lot of dupe events coming through that make the redrawing go crazy
// on FocusChange events if I don't filter out this one. But, if we are
@@ -519,3 +608,131 @@ fn window_is_eligible(
false
}
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
pub fn should_act(
title: &str,
exe_name: &str,
class: &str,
identifiers: &[IdWithIdentifier],
regex_identifiers: &HashMap<String, Regex>,
) -> bool {
let mut should_act = false;
for identifier in identifiers {
match identifier.matching_strategy {
None => {
panic!("there is no matching strategy identified for this rule");
}
Some(MatchingStrategy::Legacy) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.eq(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Equals) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.eq(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::StartsWith) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.starts_with(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::EndsWith) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.ends_with(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Contains) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.contains(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Regex) => match identifier.kind {
ApplicationIdentifier::Title => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(title) {
should_act = true;
}
}
}
ApplicationIdentifier::Class => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(class) {
should_act = true;
}
}
}
ApplicationIdentifier::Exe => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(exe_name) {
should_act = true;
}
}
}
},
}
}
should_act
}

View File

@@ -1,12 +1,15 @@
use std::collections::HashMap;
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;
@@ -16,6 +19,7 @@ use schemars::JsonSchema;
use serde::Serialize;
use uds_windows::UnixListener;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::custom_layout::CustomLayout;
use komorebi_core::Arrangement;
use komorebi_core::Axis;
@@ -36,6 +40,7 @@ use crate::current_virtual_desktop;
use crate::load_configuration;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::static_config::StaticConfig;
use crate::window::Window;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
@@ -48,7 +53,10 @@ use crate::FLOAT_IDENTIFIERS;
use crate::HOME_DIR;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_RULES;
@@ -71,8 +79,10 @@ pub struct WindowManager {
pub virtual_desktop_id: Option<Vec<u8>>,
pub has_pending_raise_op: bool,
pub pending_move_op: Option<(usize, usize, usize)>,
pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Serialize, JsonSchema)]
pub struct State {
pub monitors: Ring<Monitor>,
@@ -85,12 +95,14 @@ pub struct State {
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub mouse_follows_focus: bool,
pub has_pending_raise_op: bool,
pub float_identifiers: Vec<String>,
pub manage_identifiers: Vec<String>,
pub layered_whitelist: Vec<String>,
pub tray_and_multi_window_identifiers: Vec<String>,
pub border_overflow_identifiers: Vec<String>,
pub name_change_on_launch_identifiers: Vec<String>,
pub remove_titlebars: bool,
pub float_identifiers: Vec<IdWithIdentifier>,
pub manage_identifiers: Vec<IdWithIdentifier>,
pub layered_whitelist: Vec<IdWithIdentifier>,
pub tray_and_multi_window_identifiers: Vec<IdWithIdentifier>,
pub border_overflow_identifiers: Vec<IdWithIdentifier>,
pub name_change_on_launch_identifiers: Vec<IdWithIdentifier>,
pub monitor_index_preferences: HashMap<usize, Rect>,
}
impl AsRef<Self> for WindowManager {
@@ -112,12 +124,14 @@ impl From<&WindowManager> for State {
focus_follows_mouse: wm.focus_follows_mouse,
mouse_follows_focus: wm.mouse_follows_focus,
has_pending_raise_op: wm.has_pending_raise_op,
remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst),
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
layered_whitelist: LAYERED_WHITELIST.lock().clone(),
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
border_overflow_identifiers: BORDER_OVERFLOW_IDENTIFIERS.lock().clone(),
name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),
monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(),
}
}
}
@@ -154,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 => {}
@@ -189,6 +203,7 @@ impl WindowManager {
hotwatch: Hotwatch::new()?,
has_pending_raise_op: false,
pending_move_op: None,
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
})
}
@@ -202,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;
@@ -226,15 +241,16 @@ impl WindowManager {
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
}
#[tracing::instrument(skip(self))]
pub fn reload_static_configuration(&mut self, pathbuf: &PathBuf) -> Result<()> {
tracing::info!("reloading static configuration");
StaticConfig::reload(pathbuf, self)
}
#[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.clone();
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)?;
@@ -246,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(())
}
@@ -477,6 +482,35 @@ impl WindowManager {
Ok(())
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip(self))]
fn add_window_handle_to_move_based_on_workspace_rule(
&self,
window_title: &String,
hwnd: isize,
origin_monitor_idx: usize,
origin_workspace_idx: usize,
target_monitor_idx: usize,
target_workspace_idx: usize,
to_move: &mut Vec<EnforceWorkspaceRuleOp>,
) -> () {
tracing::info!(
"{} should be on monitor {}, workspace {}",
window_title,
target_monitor_idx,
target_workspace_idx
);
// Create an operation outline and save it for later in the fn
to_move.push(EnforceWorkspaceRuleOp {
hwnd,
origin_monitor_idx,
origin_workspace_idx,
target_monitor_idx,
target_workspace_idx,
});
}
#[tracing::instrument(skip(self))]
pub fn enforce_workspace_rules(&mut self) -> Result<()> {
let mut to_move = vec![];
@@ -494,41 +528,43 @@ impl WindowManager {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
// And all the visible windows (at the top of a container)
for window in workspace.visible_windows().into_iter().flatten() {
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
let mut found_workspace_rule = workspace_rules.get(&window.exe()?);
if found_workspace_rule.is_none() {
found_workspace_rule = workspace_rules.get(&window.title()?);
}
// If the executable names or titles of any of those windows are in our rules map
if let Some((monitor_idx, workspace_idx)) = workspace_rules.get(&window.exe()?)
if let Some((monitor_idx, workspace_idx, apply_on_first_show_only)) =
found_workspace_rule
{
tracing::info!(
"{} should be on monitor {}, workspace {}",
window.title()?,
*monitor_idx,
*workspace_idx
);
if *apply_on_first_show_only {
if !already_moved_window_handles.contains(&window.hwnd) {
already_moved_window_handles.insert(window.hwnd);
// Create an operation outline and save it for later in the fn
to_move.push(EnforceWorkspaceRuleOp {
hwnd: window.hwnd,
origin_monitor_idx: i,
origin_workspace_idx: j,
target_monitor_idx: *monitor_idx,
target_workspace_idx: *workspace_idx,
});
} else if let Some((monitor_idx, workspace_idx)) =
workspace_rules.get(&window.title()?)
{
tracing::info!(
"{} should be on monitor {}, workspace {}",
window.title()?,
*monitor_idx,
*workspace_idx
);
to_move.push(EnforceWorkspaceRuleOp {
hwnd: window.hwnd,
origin_monitor_idx: i,
origin_workspace_idx: j,
target_monitor_idx: *monitor_idx,
target_workspace_idx: *workspace_idx,
});
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
*monitor_idx,
*workspace_idx,
&mut to_move,
);
}
} else {
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
*monitor_idx,
*workspace_idx,
&mut to_move,
);
}
}
}
}
@@ -553,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;
}
@@ -583,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
@@ -627,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)?)
}
@@ -672,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;
@@ -689,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(())
}
}
@@ -807,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)?;
@@ -818,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!());
}
@@ -921,35 +956,130 @@ impl WindowManager {
}
#[tracing::instrument(skip(self))]
pub fn restore_all_windows(&mut self) {
pub fn restore_all_windows(&mut self) -> Result<()> {
tracing::info!("restoring all hidden windows");
let no_titlebar = NO_TITLEBAR.lock();
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
for containers in workspace.containers_mut() {
for window in containers.windows_mut() {
if no_titlebar.contains(&window.exe()?) {
window.add_title_bar()?;
}
window.restore();
}
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
fn handle_unmanaged_window_behaviour(&self) -> Result<()> {
if let OperationBehaviour::NoOp = self.unmanaged_window_operation_behaviour {
if matches!(
self.unmanaged_window_operation_behaviour,
OperationBehaviour::NoOp
) {
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");
}
}
Ok(())
}
pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
self.monitors_mut()
.get_mut(idx)
.ok_or_else(|| anyhow!("there is no monitor"))?
.update_focused_workspace(offset, &invisible_borders)
}
#[tracing::instrument(skip(self))]
pub fn swap_monitor_workspaces(&mut self, first_idx: usize, second_idx: usize) -> Result<()> {
tracing::info!("swaping monitors");
if first_idx == second_idx {
return Ok(());
}
let mouse_follows_focus = self.mouse_follows_focus;
let first_focused_workspace = {
let first_monitor = self
.monitors()
.get(first_idx)
.ok_or_else(|| anyhow!("There is no monitor"))?;
first_monitor.focused_workspace_idx()
};
let second_focused_workspace = {
let second_monitor = self
.monitors()
.get(second_idx)
.ok_or_else(|| anyhow!("There is no monitor"))?;
second_monitor.focused_workspace_idx()
};
// Swap workspaces between the first and second monitors
let first_workspaces = self
.monitors_mut()
.get_mut(first_idx)
.ok_or_else(|| anyhow!("There is no monitor"))?
.remove_workspaces();
let second_workspaces = self
.monitors_mut()
.get_mut(second_idx)
.ok_or_else(|| anyhow!("There is no monitor"))?
.remove_workspaces();
self.monitors_mut()
.get_mut(first_idx)
.ok_or_else(|| anyhow!("There is no monitor"))?
.workspaces_mut()
.extend(second_workspaces);
self.monitors_mut()
.get_mut(second_idx)
.ok_or_else(|| anyhow!("There is no monitor"))?
.workspaces_mut()
.extend(first_workspaces);
// Set the focused workspaces for the first and second monitors
if let Some(first_monitor) = self.monitors_mut().get_mut(first_idx) {
first_monitor.focus_workspace(second_focused_workspace)?;
first_monitor.load_focused_workspace(mouse_follows_focus)?;
}
if let Some(second_monitor) = self.monitors_mut().get_mut(second_idx) {
second_monitor.focus_workspace(first_focused_workspace)?;
second_monitor.load_focused_workspace(mouse_follows_focus)?;
}
self.update_focused_workspace_by_monitor_idx(second_idx)?;
self.update_focused_workspace_by_monitor_idx(first_idx)
}
#[tracing::instrument(skip(self))]
pub fn swap_focused_monitor(&mut self, idx: usize) -> Result<()> {
tracing::info!("swapping focused monitor");
let focused_monitor_idx = self.focused_monitor_idx();
let mouse_follows_focus = self.mouse_follows_focus;
self.swap_monitor_workspaces(focused_monitor_idx, idx)?;
self.update_focused_workspace(mouse_follows_focus)
}
#[tracing::instrument(skip(self))]
pub fn move_container_to_monitor(
&mut self,
@@ -973,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
@@ -1085,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");
@@ -1249,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");
@@ -1278,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();
@@ -1363,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()?;
@@ -1559,10 +1683,36 @@ impl WindowManager {
}
#[tracing::instrument(skip(self))]
pub fn change_workspace_custom_layout(&mut self, path: PathBuf) -> Result<()> {
pub fn cycle_layout(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("cycling layout");
let workspace = self.focused_workspace_mut()?;
let current_layout = workspace.layout();
match current_layout {
Layout::Default(current) => {
let new_layout = match direction {
CycleDirection::Previous => current.cycle_previous(),
CycleDirection::Next => current.cycle_next(),
};
tracing::info!("next layout: {new_layout}");
workspace.set_layout(Layout::Default(new_layout));
}
Layout::Custom(_) => {}
}
self.update_focused_workspace(self.mouse_follows_focus)
}
#[tracing::instrument(skip(self))]
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() {
@@ -1684,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;
@@ -1715,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);
@@ -1816,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();
@@ -1994,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

@@ -4,9 +4,11 @@ use std::fmt::Formatter;
use schemars::JsonSchema;
use serde::Serialize;
use crate::window::should_act;
use crate::window::Window;
use crate::winevent::WinEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REGEX_IDENTIFIERS;
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
#[serde(tag = "type", content = "content")]
@@ -25,68 +27,59 @@ pub enum WindowManagerEvent {
Unmanage(Window),
Raise(Window),
DisplayChange(Window),
UpdateFocusedWindowBorder(Window),
}
impl Display for WindowManagerEvent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Manage(window) => {
write!(f, "Manage (Window: {})", window)
write!(f, "Manage (Window: {window})")
}
Self::Unmanage(window) => {
write!(f, "Unmanage (Window: {})", window)
write!(f, "Unmanage (Window: {window})")
}
Self::Destroy(winevent, window) => {
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
write!(f, "Destroy (WinEvent: {winevent}, Window: {window})")
}
Self::FocusChange(winevent, window) => {
write!(
f,
"FocusChange (WinEvent: {}, Window: {})",
winevent, window
)
write!(f, "FocusChange (WinEvent: {winevent}, Window: {window})",)
}
Self::Hide(winevent, window) => {
write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window)
write!(f, "Hide (WinEvent: {winevent}, Window: {window})")
}
Self::Cloak(winevent, window) => {
write!(f, "Cloak (WinEvent: {}, Window: {})", winevent, window)
write!(f, "Cloak (WinEvent: {winevent}, Window: {window})")
}
Self::Minimize(winevent, window) => {
write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window)
write!(f, "Minimize (WinEvent: {winevent}, Window: {window})")
}
Self::Show(winevent, window) => {
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
write!(f, "Show (WinEvent: {winevent}, Window: {window})")
}
Self::Uncloak(winevent, window) => {
write!(f, "Uncloak (WinEvent: {}, Window: {})", winevent, window)
write!(f, "Uncloak (WinEvent: {winevent}, Window: {window})")
}
Self::MoveResizeStart(winevent, window) => {
write!(
f,
"MoveResizeStart (WinEvent: {}, Window: {})",
winevent, window
"MoveResizeStart (WinEvent: {winevent}, Window: {window})",
)
}
Self::MoveResizeEnd(winevent, window) => {
write!(
f,
"MoveResizeEnd (WinEvent: {}, Window: {})",
winevent, window
)
write!(f, "MoveResizeEnd (WinEvent: {winevent}, Window: {window})",)
}
Self::MouseCapture(winevent, window) => {
write!(
f,
"MouseCapture (WinEvent: {}, Window: {})",
winevent, window
)
write!(f, "MouseCapture (WinEvent: {winevent}, Window: {window})",)
}
Self::Raise(window) => {
write!(f, "Raise (Window: {})", window)
write!(f, "Raise (Window: {window})")
}
Self::DisplayChange(window) => {
write!(f, "DisplayChange (Window: {})", window)
write!(f, "DisplayChange (Window: {window})")
}
Self::UpdateFocusedWindowBorder(window) => {
write!(f, "UpdateFocusedBorderWindow (Window: {window})")
}
}
}
@@ -108,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,
}
}
@@ -144,11 +138,21 @@ impl WindowManagerEvent {
// [yatta\src\windows_event.rs:110] event = 32779 ObjectLocationChange
let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
if object_name_change_on_launch.contains(&window.exe().ok()?)
|| object_name_change_on_launch.contains(&window.class().ok()?)
|| object_name_change_on_launch.contains(&window.title().ok()?)
{
let title = &window.title().ok()?;
let exe_name = &window.exe().ok()?;
let class = &window.class().ok()?;
let should_trigger = should_act(
title,
exe_name,
class,
&object_name_change_on_launch,
&regex_identifiers,
);
if should_trigger {
Option::from(Self::Show(winevent, window))
} else {
None

View File

@@ -11,10 +11,11 @@ use color_eyre::Result;
use windows::core::Result as WindowsCrateResult;
use windows::core::PCSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HMODULE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::POINT;
@@ -80,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;
@@ -104,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;
@@ -233,9 +237,7 @@ impl WindowsApi {
}
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
unsafe { EnumWindows(callback, LPARAM(callback_data_address)) }
.ok()
.process()
unsafe { EnumWindows(callback, LPARAM(callback_data_address)) }.process()
}
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
@@ -274,9 +276,7 @@ impl WindowsApi {
}
pub fn allow_set_foreground_window(process_id: u32) -> Result<()> {
unsafe { AllowSetForegroundWindow(process_id) }
.ok()
.process()
unsafe { AllowSetForegroundWindow(process_id) }.process()
}
pub fn monitor_from_window(hwnd: HWND) -> isize {
@@ -302,14 +302,17 @@ impl WindowsApi {
}
pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
let flags = SetWindowPosition::NO_ACTIVATE;
let flags = SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::NO_SEND_CHANGING
| SetWindowPosition::NO_COPY_BITS
| SetWindowPosition::FRAME_CHANGED;
let position = if top { HWND_TOPMOST } else { HWND_BOTTOM };
Self::set_window_pos(hwnd, layout, position, flags.bits())
}
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
unsafe { BringWindowToTop(hwnd) }.ok().process()
unsafe { BringWindowToTop(hwnd) }.process()
}
pub fn raise_window(hwnd: HWND) -> Result<()> {
@@ -349,7 +352,20 @@ impl WindowsApi {
SET_WINDOW_POS_FLAGS(flags),
)
}
.ok()
.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()
}
@@ -364,14 +380,12 @@ impl WindowsApi {
}
fn post_message(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> Result<()> {
unsafe { PostMessageW(hwnd, message, wparam, lparam) }
.ok()
.process()
unsafe { PostMessageW(hwnd, message, wparam, lparam) }.process()
}
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")),
}
}
@@ -432,18 +446,18 @@ impl WindowsApi {
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
let mut rect = unsafe { std::mem::zeroed() };
unsafe { GetWindowRect(hwnd, &mut rect) }.ok().process()?;
unsafe { GetWindowRect(hwnd, &mut rect) }.process()?;
Ok(Rect::from(rect))
}
fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
unsafe { SetCursorPos(x, y) }.ok().process()
unsafe { SetCursorPos(x, y) }.process()
}
pub fn cursor_pos() -> Result<POINT> {
let mut cursor_pos = POINT::default();
unsafe { GetCursorPos(&mut cursor_pos) }.ok().process()?;
unsafe { GetCursorPos(&mut cursor_pos) }.process()?;
Ok(cursor_pos)
}
@@ -485,7 +499,7 @@ impl WindowsApi {
let mut session_id = 0;
unsafe {
if ProcessIdToSessionId(process_id, &mut session_id).as_bool() {
if ProcessIdToSessionId(process_id, &mut session_id).is_ok() {
Ok(session_id)
} else {
Err(anyhow!("could not determine current session id"))
@@ -524,6 +538,8 @@ impl WindowsApi {
}
fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result<isize> {
// Can return 0, which does not always mean that an error has occurred
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
Result::from(WindowsResult::from(unsafe {
GetWindowLongPtrW(hwnd, index)
}))
@@ -558,6 +574,10 @@ impl WindowsApi {
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }.process()
}
pub fn close_process(handle: HANDLE) -> Result<()> {
unsafe { CloseHandle(handle) }.process()
}
pub fn process_handle(process_id: u32) -> Result<HANDLE> {
Self::open_process(PROCESS_QUERY_INFORMATION, false, process_id)
}
@@ -570,7 +590,6 @@ impl WindowsApi {
unsafe {
QueryFullProcessImageNameW(handle, PROCESS_NAME_WIN32, PWSTR(text_ptr), &mut len)
}
.ok()
.process()?;
Ok(String::from_utf16(&path[..len as usize])?)
@@ -663,7 +682,6 @@ impl WindowsApi {
pub fn set_process_dpi_awareness_context() -> Result<()> {
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }
.ok()
.process()
}
@@ -671,22 +689,57 @@ impl WindowsApi {
pub fn system_parameters_info_w(
action: SYSTEM_PARAMETERS_INFO_ACTION,
ui_param: u32,
pv_param: *const c_void,
pv_param: *mut c_void,
update_flags: SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
) -> Result<()> {
unsafe { SystemParametersInfoW(action, ui_param, Option::from(pv_param), update_flags) }
.ok()
.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 is_enabled: BOOL = unsafe { std::mem::zeroed() };
let mut is_enabled: BOOL = unsafe { std::mem::zeroed() };
Self::system_parameters_info_w(
SPI_GETACTIVEWINDOWTRACKING,
0,
std::ptr::addr_of!(is_enabled).cast(),
std::ptr::addr_of_mut!(is_enabled).cast(),
SPIF_SENDCHANGE,
)?;
@@ -698,7 +751,7 @@ impl WindowsApi {
Self::system_parameters_info_w(
SPI_SETACTIVEWINDOWTRACKING,
0,
1 as *const c_void,
1 as *mut c_void,
SPIF_SENDCHANGE,
)
}
@@ -708,12 +761,12 @@ impl WindowsApi {
Self::system_parameters_info_w(
SPI_SETACTIVEWINDOWTRACKING,
0,
std::ptr::null::<c_void>(),
std::ptr::null_mut::<c_void>(),
SPIF_SENDCHANGE,
)
}
pub fn module_handle_w() -> Result<HINSTANCE> {
pub fn module_handle_w() -> Result<HMODULE> {
unsafe { GetModuleHandleW(None) }.process()
}
@@ -750,7 +803,7 @@ impl WindowsApi {
.process()
}
pub fn create_border_window(name: PCSTR, instance: HINSTANCE) -> Result<isize> {
pub fn create_border_window(name: PCSTR, instance: HMODULE) -> Result<isize> {
unsafe {
let hwnd = CreateWindowExA(
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
@@ -767,22 +820,24 @@ impl WindowsApi {
None,
);
SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY);
SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY)?;
hwnd
}
.process()
}
pub fn set_transparent(hwnd: HWND) {
pub fn set_transparent(hwnd: HWND) -> Result<()> {
unsafe {
#[allow(clippy::cast_sign_loss)]
// TODO: alpha should be configurable
SetLayeredWindowAttributes(hwnd, COLORREF(-1i32 as u32), 150, LWA_ALPHA);
SetLayeredWindowAttributes(hwnd, COLORREF(-1i32 as u32), 150, LWA_ALPHA)?;
}
Ok(())
}
pub fn create_hidden_window(name: PCSTR, instance: HINSTANCE) -> Result<isize> {
pub fn create_hidden_window(name: PCSTR, instance: HMODULE) -> Result<isize> {
unsafe {
CreateWindowExA(
WS_EX_NOACTIVATE,

View File

@@ -18,10 +18,10 @@ use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::System::SystemServices::DBT_DEVNODES_CHANGED;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
use windows::Win32::UI::WindowsAndMessaging::SPI_ICONVERTICALSPACING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
@@ -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) {
@@ -158,7 +158,7 @@ pub extern "system" fn border_window(
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message as u32 {
match message {
WM_PAINT => {
let border_rect = *BORDER_RECT.lock();
let mut ps = PAINTSTRUCT::default();
@@ -194,9 +194,9 @@ pub extern "system" fn hidden_window(
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message as u32 {
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

@@ -12,17 +12,27 @@ use schemars::JsonSchema;
use serde::Serialize;
use komorebi_core::Axis;
use komorebi_core::CustomLayout;
use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
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;
use crate::NO_TITLEBAR;
use crate::REMOVE_TITLEBARS;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
pub struct Workspace {
@@ -75,8 +85,8 @@ impl Default for Workspace {
layout: Layout::Default(DefaultLayout::BSP),
layout_rules: vec![],
layout_flip: None,
workspace_padding: Option::from(10),
container_padding: Option::from(10),
workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)),
container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)),
latest_layout: vec![],
resize_dimensions: vec![],
tile: true,
@@ -85,6 +95,52 @@ impl Default for Workspace {
}
impl Workspace {
pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> {
self.name = Option::from(config.name.clone());
if config.container_padding.is_some() {
self.set_container_padding(config.container_padding);
}
if config.workspace_padding.is_some() {
self.set_workspace_padding(config.workspace_padding);
}
if let Some(layout) = &config.layout {
self.layout = Layout::Default(*layout);
self.tile = true;
}
if let Some(pathbuf) = &config.custom_layout {
let layout = CustomLayout::from_path(pathbuf)?;
self.layout = Layout::Custom(layout);
self.tile = true;
}
if config.custom_layout.is_none() && config.layout.is_none() {
self.tile = false;
}
if let Some(layout_rules) = &config.layout_rules {
let mut all_rules = vec![];
for (count, rule) in layout_rules {
all_rules.push((*count, Layout::Default(*rule)));
}
self.set_layout_rules(all_rules);
}
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(pathbuf)?;
rules.push((*count, Layout::Custom(rule)));
}
}
Ok(())
}
pub fn hide(&mut self) {
for container in self.containers_mut() {
for window in container.windows_mut() {
@@ -192,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);
@@ -212,9 +274,18 @@ impl Workspace {
self.resize_dimensions(),
);
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
let no_titlebar = NO_TITLEBAR.lock().clone();
let windows = self.visible_windows_mut();
for (i, window) in windows.into_iter().enumerate() {
if let (Some(window), Some(layout)) = (window, layouts.get(i)) {
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
window.remove_title_bar()?;
} else if no_titlebar.contains(&window.exe()?) {
window.add_title_bar()?;
}
window.set_position(layout, invisible_borders, false)?;
}
}
@@ -720,6 +791,16 @@ impl Workspace {
}
fn enforce_resize_constraints(&mut self) {
match self.layout {
Layout::Default(DefaultLayout::BSP) => self.enforce_resize_constraints_for_bsp(),
Layout::Default(DefaultLayout::UltrawideVerticalStack) => {
self.enforce_resize_for_ultrawide();
}
_ => self.enforce_no_resize(),
}
}
fn enforce_resize_constraints_for_bsp(&mut self) {
for (i, rect) in self.resize_dimensions_mut().iter_mut().enumerate() {
if let Some(rect) = rect {
// Even containers can't be resized to the bottom
@@ -745,6 +826,72 @@ impl Workspace {
}
}
fn enforce_resize_for_ultrawide(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
// Single window can not be resized at all
0 | 1 => self.enforce_no_resize(),
// Two windows can only be resized in the middle
2 => {
// Zero is actually on the right
if let Some(mut right) = resize_dimensions[0] {
right.top = 0;
right.bottom = 0;
right.right = 0;
}
// One is on the left
if let Some(mut left) = resize_dimensions[1] {
left.top = 0;
left.bottom = 0;
left.left = 0;
}
}
// Three or more windows means 0 is in center, 1 is at the left, 2.. are a vertical
// stack on the right
_ => {
// Central can be resized left or right
if let Some(mut right) = resize_dimensions[0] {
right.top = 0;
right.bottom = 0;
}
// Left one can only be resized to the right
if let Some(mut left) = resize_dimensions[1] {
left.top = 0;
left.bottom = 0;
left.left = 0;
}
// Handle stack on the right
let stack_size = resize_dimensions[2..].len();
for (i, rect) in resize_dimensions[2..].iter_mut().enumerate() {
if let Some(rect) = rect {
// No containers can resize to the right
rect.right = 0;
// First container in stack cant resize up
if i == 0 {
rect.top = 0;
} else if i == stack_size - 1 {
// Last cant be resized to the bottom
rect.bottom = 0;
}
}
}
}
}
}
fn enforce_no_resize(&mut self) {
for rect in self.resize_dimensions_mut().iter_mut().flatten() {
rect.left = 0;
rect.right = 0;
rect.top = 0;
rect.bottom = 0;
}
}
pub fn new_monocle_container(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let container = self

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(|_| ())
}

457
komorebic.lib.ahk Normal file
View File

@@ -0,0 +1,457 @@
; Generated by komorebic.exe
Start(ffm, await_configuration, tcp_port) {
RunWait("komorebic.exe start " ffm " --await-configuration " await_configuration " --tcp-port " tcp_port, , "Hide")
}
Stop() {
RunWait("komorebic.exe stop", , "Hide")
}
State() {
RunWait("komorebic.exe state", , "Hide")
}
Query(state_query) {
RunWait("komorebic.exe query " state_query, , "Hide")
}
Subscribe(named_pipe) {
RunWait("komorebic.exe subscribe " named_pipe, , "Hide")
}
Unsubscribe(named_pipe) {
RunWait("komorebic.exe unsubscribe " named_pipe, , "Hide")
}
Log() {
RunWait("komorebic.exe log", , "Hide")
}
QuickSaveResize() {
RunWait("komorebic.exe quick-save-resize", , "Hide")
}
QuickLoadResize() {
RunWait("komorebic.exe quick-load-resize", , "Hide")
}
SaveResize(path) {
RunWait("komorebic.exe save-resize " path, , "Hide")
}
LoadResize(path) {
RunWait("komorebic.exe load-resize " path, , "Hide")
}
Focus(operation_direction) {
RunWait("komorebic.exe focus " operation_direction, , "Hide")
}
Move(operation_direction) {
RunWait("komorebic.exe move " operation_direction, , "Hide")
}
Minimize() {
RunWait("komorebic.exe minimize", , "Hide")
}
Close() {
RunWait("komorebic.exe close", , "Hide")
}
ForceFocus() {
RunWait("komorebic.exe force-focus", , "Hide")
}
CycleFocus(cycle_direction) {
RunWait("komorebic.exe cycle-focus " cycle_direction, , "Hide")
}
CycleMove(cycle_direction) {
RunWait("komorebic.exe cycle-move " cycle_direction, , "Hide")
}
Stack(operation_direction) {
RunWait("komorebic.exe stack " operation_direction, , "Hide")
}
Resize(edge, sizing) {
RunWait("komorebic.exe resize " edge " " sizing, , "Hide")
}
ResizeAxis(axis, sizing) {
RunWait("komorebic.exe resize-axis " axis " " sizing, , "Hide")
}
Unstack() {
RunWait("komorebic.exe unstack", , "Hide")
}
CycleStack(cycle_direction) {
RunWait("komorebic.exe cycle-stack " cycle_direction, , "Hide")
}
MoveToMonitor(target) {
RunWait("komorebic.exe move-to-monitor " target, , "Hide")
}
CycleMoveToMonitor(cycle_direction) {
RunWait("komorebic.exe cycle-move-to-monitor " cycle_direction, , "Hide")
}
MoveToWorkspace(target) {
RunWait("komorebic.exe move-to-workspace " target, , "Hide")
}
MoveToNamedWorkspace(workspace) {
RunWait("komorebic.exe move-to-named-workspace " workspace, , "Hide")
}
CycleMoveToWorkspace(cycle_direction) {
RunWait("komorebic.exe cycle-move-to-workspace " cycle_direction, , "Hide")
}
SendToMonitor(target) {
RunWait("komorebic.exe send-to-monitor " target, , "Hide")
}
CycleSendToMonitor(cycle_direction) {
RunWait("komorebic.exe cycle-send-to-monitor " cycle_direction, , "Hide")
}
SendToWorkspace(target) {
RunWait("komorebic.exe send-to-workspace " target, , "Hide")
}
SendToNamedWorkspace(workspace) {
RunWait("komorebic.exe send-to-named-workspace " workspace, , "Hide")
}
CycleSendToWorkspace(cycle_direction) {
RunWait("komorebic.exe cycle-send-to-workspace " cycle_direction, , "Hide")
}
SendToMonitorWorkspace(target_monitor, target_workspace) {
RunWait("komorebic.exe send-to-monitor-workspace " target_monitor " " target_workspace, , "Hide")
}
FocusMonitor(target) {
RunWait("komorebic.exe focus-monitor " target, , "Hide")
}
FocusWorkspace(target) {
RunWait("komorebic.exe focus-workspace " target, , "Hide")
}
FocusMonitorWorkspace(target_monitor, target_workspace) {
RunWait("komorebic.exe focus-monitor-workspace " target_monitor " " target_workspace, , "Hide")
}
FocusNamedWorkspace(workspace) {
RunWait("komorebic.exe focus-named-workspace " workspace, , "Hide")
}
CycleMonitor(cycle_direction) {
RunWait("komorebic.exe cycle-monitor " cycle_direction, , "Hide")
}
CycleWorkspace(cycle_direction) {
RunWait("komorebic.exe cycle-workspace " cycle_direction, , "Hide")
}
MoveWorkspaceToMonitor(target) {
RunWait("komorebic.exe move-workspace-to-monitor " target, , "Hide")
}
NewWorkspace() {
RunWait("komorebic.exe new-workspace", , "Hide")
}
ResizeDelta(pixels) {
RunWait("komorebic.exe resize-delta " pixels, , "Hide")
}
InvisibleBorders(left, top, right, bottom) {
RunWait("komorebic.exe invisible-borders " left " " top " " right " " bottom, , "Hide")
}
GlobalWorkAreaOffset(left, top, right, bottom) {
RunWait("komorebic.exe global-work-area-offset " left " " top " " right " " bottom, , "Hide")
}
MonitorWorkAreaOffset(monitor, left, top, right, bottom) {
RunWait("komorebic.exe monitor-work-area-offset " monitor " " left " " top " " right " " bottom, , "Hide")
}
AdjustContainerPadding(sizing, adjustment) {
RunWait("komorebic.exe adjust-container-padding " sizing " " adjustment, , "Hide")
}
AdjustWorkspacePadding(sizing, adjustment) {
RunWait("komorebic.exe adjust-workspace-padding " sizing " " adjustment, , "Hide")
}
ChangeLayout(default_layout) {
RunWait("komorebic.exe change-layout " default_layout, , "Hide")
}
CycleLayout(operation_direction) {
RunWait("komorebic.exe cycle-layout " operation_direction, , "Hide")
}
LoadCustomLayout(path) {
RunWait("komorebic.exe load-custom-layout " path, , "Hide")
}
FlipLayout(axis) {
RunWait("komorebic.exe flip-layout " axis, , "Hide")
}
Promote() {
RunWait("komorebic.exe promote", , "Hide")
}
PromoteFocus() {
RunWait("komorebic.exe promote-focus", , "Hide")
}
Retile() {
RunWait("komorebic.exe retile", , "Hide")
}
MonitorIndexPreference(index_preference, left, top, right, bottom) {
RunWait("komorebic.exe monitor-index-preference " index_preference " " left " " top " " right " " bottom, , "Hide")
}
EnsureWorkspaces(monitor, workspace_count) {
RunWait("komorebic.exe ensure-workspaces " monitor " " workspace_count, , "Hide")
}
EnsureNamedWorkspaces(monitor, names) {
RunWait("komorebic.exe ensure-named-workspaces " monitor " " names, , "Hide")
}
ContainerPadding(monitor, workspace, size) {
RunWait("komorebic.exe container-padding " monitor " " workspace " " size, , "Hide")
}
NamedWorkspaceContainerPadding(workspace, size) {
RunWait("komorebic.exe named-workspace-container-padding " workspace " " size, , "Hide")
}
WorkspacePadding(monitor, workspace, size) {
RunWait("komorebic.exe workspace-padding " monitor " " workspace " " size, , "Hide")
}
NamedWorkspacePadding(workspace, size) {
RunWait("komorebic.exe named-workspace-padding " workspace " " size, , "Hide")
}
WorkspaceLayout(monitor, workspace, value) {
RunWait("komorebic.exe workspace-layout " monitor " " workspace " " value, , "Hide")
}
NamedWorkspaceLayout(workspace, value) {
RunWait("komorebic.exe named-workspace-layout " workspace " " value, , "Hide")
}
WorkspaceCustomLayout(monitor, workspace, path) {
RunWait("komorebic.exe workspace-custom-layout " monitor " " workspace " " path, , "Hide")
}
NamedWorkspaceCustomLayout(workspace, path) {
RunWait("komorebic.exe named-workspace-custom-layout " workspace " " path, , "Hide")
}
WorkspaceLayoutRule(monitor, workspace, at_container_count, layout) {
RunWait("komorebic.exe workspace-layout-rule " monitor " " workspace " " at_container_count " " layout, , "Hide")
}
NamedWorkspaceLayoutRule(workspace, at_container_count, layout) {
RunWait("komorebic.exe named-workspace-layout-rule " workspace " " at_container_count " " layout, , "Hide")
}
WorkspaceCustomLayoutRule(monitor, workspace, at_container_count, path) {
RunWait("komorebic.exe workspace-custom-layout-rule " monitor " " workspace " " at_container_count " " path, , "Hide")
}
NamedWorkspaceCustomLayoutRule(workspace, at_container_count, path) {
RunWait("komorebic.exe named-workspace-custom-layout-rule " workspace " " at_container_count " " path, , "Hide")
}
ClearWorkspaceLayoutRules(monitor, workspace) {
RunWait("komorebic.exe clear-workspace-layout-rules " monitor " " workspace, , "Hide")
}
ClearNamedWorkspaceLayoutRules(workspace) {
RunWait("komorebic.exe clear-named-workspace-layout-rules " workspace, , "Hide")
}
WorkspaceTiling(monitor, workspace, value) {
RunWait("komorebic.exe workspace-tiling " monitor " " workspace " " value, , "Hide")
}
NamedWorkspaceTiling(workspace, value) {
RunWait("komorebic.exe named-workspace-tiling " workspace " " value, , "Hide")
}
WorkspaceName(monitor, workspace, value) {
RunWait("komorebic.exe workspace-name " monitor " " workspace " " value, , "Hide")
}
ToggleWindowContainerBehaviour() {
RunWait("komorebic.exe toggle-window-container-behaviour", , "Hide")
}
TogglePause() {
RunWait("komorebic.exe toggle-pause", , "Hide")
}
ToggleTiling() {
RunWait("komorebic.exe toggle-tiling", , "Hide")
}
ToggleFloat() {
RunWait("komorebic.exe toggle-float", , "Hide")
}
ToggleMonocle() {
RunWait("komorebic.exe toggle-monocle", , "Hide")
}
ToggleMaximize() {
RunWait("komorebic.exe toggle-maximize", , "Hide")
}
RestoreWindows() {
RunWait("komorebic.exe restore-windows", , "Hide")
}
Manage() {
RunWait("komorebic.exe manage", , "Hide")
}
Unmanage() {
RunWait("komorebic.exe unmanage", , "Hide")
}
ReloadConfiguration() {
RunWait("komorebic.exe reload-configuration", , "Hide")
}
WatchConfiguration(boolean_state) {
RunWait("komorebic.exe watch-configuration " boolean_state, , "Hide")
}
CompleteConfiguration() {
RunWait("komorebic.exe complete-configuration", , "Hide")
}
AltFocusHack(boolean_state) {
RunWait("komorebic.exe alt-focus-hack " boolean_state, , "Hide")
}
WindowHidingBehaviour(hiding_behaviour) {
RunWait("komorebic.exe window-hiding-behaviour " hiding_behaviour, , "Hide")
}
CrossMonitorMoveBehaviour(move_behaviour) {
RunWait("komorebic.exe cross-monitor-move-behaviour " move_behaviour, , "Hide")
}
ToggleCrossMonitorMoveBehaviour() {
RunWait("komorebic.exe toggle-cross-monitor-move-behaviour", , "Hide")
}
UnmanagedWindowOperationBehaviour(operation_behaviour) {
RunWait("komorebic.exe unmanaged-window-operation-behaviour " operation_behaviour, , "Hide")
}
FloatRule(identifier, id) {
RunWait("komorebic.exe float-rule " identifier " `"" id "`"", , "Hide")
}
ManageRule(identifier, id) {
RunWait("komorebic.exe manage-rule " identifier " `"" id "`"", , "Hide")
}
WorkspaceRule(identifier, id, monitor, workspace) {
RunWait("komorebic.exe workspace-rule " identifier " `"" id "`" " monitor " " workspace, , "Hide")
}
NamedWorkspaceRule(identifier, id, workspace) {
RunWait("komorebic.exe named-workspace-rule " identifier " `"" id "`" " workspace, , "Hide")
}
IdentifyObjectNameChangeApplication(identifier, id) {
RunWait("komorebic.exe identify-object-name-change-application " identifier " `"" id "`"", , "Hide")
}
IdentifyTrayApplication(identifier, id) {
RunWait("komorebic.exe identify-tray-application " identifier " `"" id "`"", , "Hide")
}
IdentifyLayeredApplication(identifier, id) {
RunWait("komorebic.exe identify-layered-application " identifier " `"" id "`"", , "Hide")
}
IdentifyBorderOverflowApplication(identifier, id) {
RunWait("komorebic.exe identify-border-overflow-application " identifier " `"" id "`"", , "Hide")
}
ActiveWindowBorder(boolean_state) {
RunWait("komorebic.exe active-window-border " boolean_state, , "Hide")
}
ActiveWindowBorderColour(r, g, b, window_kind) {
RunWait("komorebic.exe active-window-border-colour " r " " g " " b " --window-kind " window_kind, , "Hide")
}
ActiveWindowBorderWidth(width) {
RunWait("komorebic.exe active-window-border-width " width, , "Hide")
}
ActiveWindowBorderOffset(offset) {
RunWait("komorebic.exe active-window-border-offset " offset, , "Hide")
}
FocusFollowsMouse(boolean_state, implementation) {
RunWait("komorebic.exe focus-follows-mouse " boolean_state " --implementation " implementation, , "Hide")
}
ToggleFocusFollowsMouse(implementation) {
RunWait("komorebic.exe toggle-focus-follows-mouse --implementation " implementation, , "Hide")
}
MouseFollowsFocus(boolean_state) {
RunWait("komorebic.exe mouse-follows-focus " boolean_state, , "Hide")
}
ToggleMouseFollowsFocus() {
RunWait("komorebic.exe toggle-mouse-follows-focus", , "Hide")
}
AhkLibrary() {
RunWait("komorebic.exe ahk-library", , "Hide")
}
AhkAppSpecificConfiguration(path, override_path) {
RunWait("komorebic.exe ahk-app-specific-configuration " path " " override_path, , "Hide")
}
PwshAppSpecificConfiguration(path, override_path) {
RunWait("komorebic.exe pwsh-app-specific-configuration " path " " override_path, , "Hide")
}
FormatAppSpecificConfiguration(path) {
RunWait("komorebic.exe format-app-specific-configuration " path, , "Hide")
}
NotificationSchema() {
RunWait("komorebic.exe notification-schema", , "Hide")
}
SocketSchema() {
RunWait("komorebic.exe socket-schema", , "Hide")
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.15"
version = "0.1.19"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
@@ -15,22 +15,19 @@ derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" }
clap = { version = "4", features = ["derive", "wrap_help"] }
color-eyre = "0.6"
dirs = "4"
fs-tail = "0.1"
heck = "0.4"
lazy_static = "1"
paste = "1"
powershell_script = "1.0"
reqwest = { version = "0.11", features = ["blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
sysinfo = "0.27"
sysinfo = "0.29"
uds_windows = "1"
[dependencies.windows]
version = "0.44"
features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging"
]
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"
]
}
}
}

621
schema.json Normal file
View File

@@ -0,0 +1,621 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"type": "object",
"properties": {
"active_window_border": {
"description": "Display an active window border (default: false)",
"type": [
"boolean",
"null"
]
},
"active_window_border_colours": {
"description": "Active window border colours for different container types",
"anyOf": [
{
"$ref": "#/definitions/ActiveWindowBorderColours"
},
{
"type": "null"
}
]
},
"active_window_border_offset": {
"description": "Offset of the active window border (default: None)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"active_window_border_width": {
"description": "Width of the active window border (default: 20)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"alt_focus_hack": {
"description": "Always send the ALT key when using focus commands (default: false)",
"type": [
"boolean",
"null"
]
},
"app_specific_configuration_path": {
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
"type": [
"string",
"null"
]
},
"border_offset": {
"description": "DEPRECATED from v0.1.19: use active_window_border_offset instead",
"anyOf": [
{
"$ref": "#/definitions/Rect"
},
{
"type": "null"
}
]
},
"border_overflow_applications": {
"description": "Identify border overflow applications",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"border_width": {
"description": "DEPRECATED from v0.1.19: use active_window_border_width instead",
"type": [
"integer",
"null"
],
"format": "int32"
},
"cross_monitor_move_behaviour": {
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
"anyOf": [
{
"$ref": "#/definitions/MoveBehaviour"
},
{
"type": "null"
}
]
},
"default_container_padding": {
"description": "Global default container padding (default: 10)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"default_workspace_padding": {
"description": "Global default workspace padding (default: 10)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"float_rules": {
"description": "Individual window floating rules",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"focus_follows_mouse": {
"description": "Determine focus follows mouse implementation (default: None)",
"anyOf": [
{
"$ref": "#/definitions/FocusFollowsMouseImplementation"
},
{
"type": "null"
}
]
},
"global_work_area_offset": {
"description": "Global work area (space used for tiling) offset (default: None)",
"anyOf": [
{
"$ref": "#/definitions/Rect"
},
{
"type": "null"
}
]
},
"invisible_borders": {
"description": "Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to",
"anyOf": [
{
"$ref": "#/definitions/Rect"
},
{
"type": "null"
}
]
},
"layered_applications": {
"description": "Identify applications that have the WS_EX_LAYERED extended window style",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"manage_rules": {
"description": "Individual window force-manage rules",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"monitor_index_preferences": {
"description": "Set monitor index preferences",
"type": [
"object",
"null"
],
"additionalProperties": {
"$ref": "#/definitions/Rect"
}
},
"monitors": {
"description": "Monitor and workspace configurations",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/MonitorConfig"
}
},
"mouse_follows_focus": {
"description": "Enable or disable mouse follows focus (default: true)",
"type": [
"boolean",
"null"
]
},
"object_name_change_applications": {
"description": "Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"resize_delta": {
"description": "Delta to resize windows by (default 50)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"tray_and_multi_window_applications": {
"description": "Identify tray and multi-window applications",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"unmanaged_window_operation_behaviour": {
"description": "Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)",
"anyOf": [
{
"$ref": "#/definitions/OperationBehaviour"
},
{
"type": "null"
}
]
},
"window_container_behaviour": {
"description": "Determine what happens when a new window is opened (default: Create)",
"anyOf": [
{
"$ref": "#/definitions/WindowContainerBehaviour"
},
{
"type": "null"
}
]
},
"window_hiding_behaviour": {
"description": "Which Windows signal to use when hiding windows (default: minimize)",
"anyOf": [
{
"$ref": "#/definitions/HidingBehaviour"
},
{
"type": "null"
}
]
}
},
"definitions": {
"ActiveWindowBorderColours": {
"type": "object",
"required": [
"monocle",
"single",
"stack"
],
"properties": {
"monocle": {
"description": "Border colour when the container is in monocle mode",
"allOf": [
{
"$ref": "#/definitions/Rgb"
}
]
},
"single": {
"description": "Border colour when the container contains a single window",
"allOf": [
{
"$ref": "#/definitions/Rgb"
}
]
},
"stack": {
"description": "Border colour when the container contains multiple windows",
"allOf": [
{
"$ref": "#/definitions/Rgb"
}
]
}
}
},
"ApplicationIdentifier": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title"
]
},
"DefaultLayout": {
"type": "string",
"enum": [
"BSP",
"Columns",
"Rows",
"VerticalStack",
"HorizontalStack",
"UltrawideVerticalStack"
]
},
"FocusFollowsMouseImplementation": {
"oneOf": [
{
"description": "A custom FFM implementation (slightly more CPU-intensive)",
"type": "string",
"enum": [
"Komorebi"
]
},
{
"description": "The native (legacy) Windows FFM implementation",
"type": "string",
"enum": [
"Windows"
]
}
]
},
"HidingBehaviour": {
"oneOf": [
{
"description": "Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)",
"type": "string",
"enum": [
"Hide"
]
},
{
"description": "Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)",
"type": "string",
"enum": [
"Minimize"
]
},
{
"description": "Use the undocumented SetCloak Win32 function to hide windows when switching workspaces (has foregrounding issues)",
"type": "string",
"enum": [
"Cloak"
]
}
]
},
"IdWithIdentifier": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"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"
]
},
"MonitorConfig": {
"type": "object",
"required": [
"workspaces"
],
"properties": {
"work_area_offset": {
"description": "Monitor-specific work area offset (default: None)",
"anyOf": [
{
"$ref": "#/definitions/Rect"
},
{
"type": "null"
}
]
},
"workspaces": {
"description": "Workspace configurations",
"type": "array",
"items": {
"$ref": "#/definitions/WorkspaceConfig"
}
}
}
},
"MoveBehaviour": {
"oneOf": [
{
"description": "Swap the window container with the window container at the edge of the adjacent monitor",
"type": "string",
"enum": [
"Swap"
]
},
{
"description": "Insert the window container into the focused workspace on the adjacent monitor",
"type": "string",
"enum": [
"Insert"
]
}
]
},
"OperationBehaviour": {
"oneOf": [
{
"description": "Process komorebic commands on temporarily unmanaged/floated windows",
"type": "string",
"enum": [
"Op"
]
},
{
"description": "Ignore komorebic commands on temporarily unmanaged/floated windows",
"type": "string",
"enum": [
"NoOp"
]
}
]
},
"Rect": {
"type": "object",
"required": [
"bottom",
"left",
"right",
"top"
],
"properties": {
"bottom": {
"description": "The bottom point in a Win32 Rect",
"type": "integer",
"format": "int32"
},
"left": {
"description": "The left point in a Win32 Rect",
"type": "integer",
"format": "int32"
},
"right": {
"description": "The right point in a Win32 Rect",
"type": "integer",
"format": "int32"
},
"top": {
"description": "The top point in a Win32 Rect",
"type": "integer",
"format": "int32"
}
}
},
"Rgb": {
"type": "object",
"required": [
"b",
"g",
"r"
],
"properties": {
"b": {
"description": "Blue",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"g": {
"description": "Green",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"r": {
"description": "Red",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
"WindowContainerBehaviour": {
"oneOf": [
{
"description": "Create a new container for each new window",
"type": "string",
"enum": [
"Create"
]
},
{
"description": "Append new windows to the focused window container",
"type": "string",
"enum": [
"Append"
]
}
]
},
"WorkspaceConfig": {
"type": "object",
"required": [
"name"
],
"properties": {
"container_padding": {
"description": "Container padding (default: global)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"custom_layout": {
"description": "Custom Layout (default: None)",
"type": [
"string",
"null"
]
},
"custom_layout_rules": {
"description": "Layout rules (default: None)",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "string"
}
},
"initial_workspace_rules": {
"description": "Initial workspace application rules",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
},
"layout": {
"description": "Layout (default: BSP)",
"anyOf": [
{
"$ref": "#/definitions/DefaultLayout"
},
{
"type": "null"
}
]
},
"layout_rules": {
"description": "Layout rules (default: None)",
"type": [
"object",
"null"
],
"additionalProperties": {
"$ref": "#/definitions/DefaultLayout"
}
},
"name": {
"description": "Name",
"type": "string"
},
"workspace_padding": {
"description": "Container padding (default: global)",
"type": [
"integer",
"null"
],
"format": "int32"
},
"workspace_rules": {
"description": "Permanent workspace application rules",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/IdWithIdentifier"
}
}
}
}
}
}

View File

@@ -1,16 +1,16 @@
.shell pwsh
.shell powershell
# reload swhkd configuration
# alt + o : taskkill /f /im swhkd.exe && start /b swhkd # if shell is cmd
alt + o : taskkill /f /im swhkd.exe && Start-Process swhkd -WindowStyle hidden # if shell is pwsh / powershell
# Reload whkd configuration
# alt + o : taskkill /f /im whkd.exe && start /b whkd # if shell is cmd
alt + o : taskkill /f /im whkd.exe && Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
alt + shift + o : komorebic reload-configuration
# app shortcuts - these require shell to be pwsh / powershell
# the apps will be focused if open, or launched if not open
# App shortcuts - these require shell to be pwsh / powershell
# The apps will be focused if open, or launched if not open
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
# focus windows
# Focus windows
alt + h : komorebic focus left
alt + j : komorebic focus down
alt + k : komorebic focus up
@@ -18,14 +18,14 @@ alt + l : komorebic focus right
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
# move windows
# Move windows
alt + shift + h : komorebic move left
alt + shift + j : komorebic move down
alt + shift + k : komorebic move up
alt + shift + l : komorebic move right
alt + shift + return : komorebic promote
# stack windows
# Stack windows
alt + left : komorebic stack left
alt + down : komorebic stack down
alt + up : komorebic stack up
@@ -34,31 +34,30 @@ alt + oem_1 : komorebic unstack # oem_1 is ;
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
# resize
# Resize
alt + oem_plus : komorebic resize-axis horizontal increase
alt + oem_minus : komorebic resize-axis horizontal decrease
alt + shift + oem_plus : komorebic resize-axis vertical increase
alt + shift + oem_minus : komorebic resize-axis vertical decrease
# manipulate windows
# Manipulate windows
alt + t : komorebic toggle-float
alt + shift + f : komorebic toggle-monocle
# window manager options
# Window manager options
alt + shift + r : komorebic retile
alt + p : komorebic toggle-pause
alt + 0 : komorebic toggle-focus-follows-mouse
# layouts
# Layouts
alt + x : komorebic flip-layout horizontal
alt + y : komorebic flip-layout vertical
# workspaces
# Workspaces
alt + 1 : komorebic focus-workspace 0
alt + 2 : komorebic focus-workspace 1
alt + 3 : komorebic focus-workspace 2
# move windows across workspaces
# Move windows across workspaces
alt + shift + 1 : komorebic move-to-workspace 0
alt + shift + 2 : komorebic move-to-workspace 1
alt + shift + 3 : komorebic move-to-workspace 2

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>