Compare commits

...

175 Commits

Author SHA1 Message Date
LGUG2Z
f669231517 chore(release): v0.1.11 2022-08-11 13:35:47 -07:00
LGUG2Z
4d8afc96c9 fix(wm): enforce active border enabled checks
This commit enforces a check to ensure that the active-window-border
configuration is enabled before trying to redraw a border than has been
hidden by a drag or move event.
2022-08-11 10:50:44 -07:00
جاد
c154c32b3d docs(issues): add bug and feature templates 2022-08-11 08:37:19 -07:00
LGUG2Z
2618d8f529 fix(wm): skip border update when ws tiling disabled
This commit ensures that active window border updates in the event
processing loop are skipped if the current workspace has tiling
disabled. Previously this check was not enforced so the border would
reappear on a workspace that had disabled tiling after new events had
been processed.
2022-08-11 06:53:35 -07:00
LGUG2Z
4dfab7d65f fix(wm): disable active border alongside tiling
This commit ensures that when the workspace-tiling command is called to
disable tiling for a workspace, that the border is also disabled for the
duration that tiling is diabled. This was previously only implemented
for the toggle-tiling command.
2022-08-10 22:30:24 -07:00
جاد
075c0602a7 docs(readme): update screenshot 2022-08-10 14:53:35 -07:00
LGUG2Z
83d9232d0b chore(deps): cargo update 2022-08-10 08:21:27 -07:00
LGUG2Z
84f74fc5a6 perf(wm): reduce border jank on ws switch
This commit introduces a few changes to reduce border jank, especially
when switching workspaces:

- The border is hidden before the windows start to reorganize when a
  workspace switching command is received instead of after
- Avoid unncessary window.focus() call when switching workspace
- Use WindowManager.focused_window() instead of the window received from
  the WindowManagerEvent when updating or setting the active border
  position as it more accurately matches user expectations when
  switching back to a workspace to find the focused window being the one
  that you left when you switched away
2022-08-09 10:42:10 -07:00
LGUG2Z
01b2c52460 fix(wm): handle settings window focus gracefully
This commit ensures that failures to focus on the Settings and similar
windows on Windows 11 will allow execution to continue so that the
active window border location can be updated without exiting early from
the command handler with a propagated error.
2022-08-08 19:00:10 -07:00
LGUG2Z
dae77d87b9 fix(wm): rm layout_flip for custom layouts
This commit ensures that when a custom layout is loaded, either manually
or via a workspace layout rule trigger threshold being amtched, any
layout_flip property for the impacted workspace will be removed, to
allow for key bindings to work as expected on the custom layout.
2022-08-08 14:20:36 -07:00
LGUG2Z
07bd53876f feat(wm): add active window border
This commit adds an optional active window border with a user-defined
colour. This is achieved by spawning a dedicated "border window" and
constantly placing it behind the focused window, or hiding it whenever
necessary.

Some constraints to note:

- The border will only be applied to windows managed by komorebi
- This means that if you temporarily float a window, it will lose the
  active window border
- There are some issues where parts of the border will be broken by
  applications like Zoom, even if Zoom is behind the currently focused
  window
- You probably want to turn off window shadows globally in Advanced
  System Settings -> Performance for the borders to have a consistent
  colour all the way around the window
- There is some inevitable jank due to trying to reposition both the
  focused window and the "border window" behind it simultaneously
- There are no borders for unfocused windows

resolve #182
2022-08-08 09:59:26 -07:00
LGUG2Z
8c051d9f5e fix(wm): prevent focus/move from monocle/max windows 2022-08-07 20:29:25 -07:00
LGUG2Z
7e12f6f4a9 docs(readme): update yaml example to use tags 2022-08-05 09:02:00 -07:00
dependabot[bot]
67b00fd06d chore(deps): bump serde_yaml from 0.8.26 to 0.9.2
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.26 to 0.9.2.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.26...0.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:43:52 -07:00
dependabot[bot]
ce2c55bbd8 chore(deps): bump clap from 3.2.14 to 3.2.16
Bumps [clap](https://github.com/clap-rs/clap) from 3.2.14 to 3.2.16.
- [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/v3.2.14...v3.2.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:43:40 -07:00
dependabot[bot]
ec47526de1 chore(deps): bump tracing from 0.1.35 to 0.1.36
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.35 to 0.1.36.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.35...tracing-0.1.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:43:26 -07:00
dependabot[bot]
81ea4569e1 chore(deps): bump proc-macro2 from 1.0.40 to 1.0.42
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.40 to 1.0.42.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.40...1.0.42)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:43:10 -07:00
dependabot[bot]
7b3f03bd6a chore(deps): bump sysinfo from 0.24.7 to 0.25.1
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.24.7 to 0.25.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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:42:53 -07:00
LGUG2Z
a6d46dbf45 feat(wm): optionally await config completion
This commit introduces a new flag to komorebi and komorebic,
--await-configuration, which when enabled, will stop the process from
processing window manager events and updating the layout until the
'komorebic complete-configuration' command has been sent.

This should typically be added at the end of a user's komorebi.ahk
configuration file if they decide to enable this feature.

resolve #190
2022-07-31 09:44:43 -07:00
LGUG2Z
5b91e22114 fix(wm): maximize monocle + floating windows
This commit ensures that monocle containers and floating windows are
considered validate candidates for the 'toggle-maximize' command and are
handled accordingly if the command is called when they are in the
foreground.

fix #194
2022-07-28 16:45:53 -07:00
LGUG2Z
091e9c3e56 fix(wm): float monocle + max windows
This commit ensures that monocle containers and maximized windows are
considered valid candidates for the 'toggle-float' command and are
handled accordingly if the command is called when they are in the
foreground.

fix #193
2022-07-28 16:44:33 -07:00
LGUG2Z
a7d29a7344 docs(readme): add badges 2022-07-28 13:30:03 -07:00
LGUG2Z
763c710770 fix(cli): smarter windows path resolution
This commit improves Windows path resolution so that when people run the
ahk-asc command with "applications.yaml" as an argument, without having
".\" prepended, the command will still succeed as expected.

fix #192
2022-07-27 20:52:55 -07:00
LGUG2Z
f8cf70ee1d style(rust): specify stable channel 2022-07-27 07:48:01 -07:00
LGUG2Z
89bd7d2465 fix(wm): normalize max hwnd before minimizing
This commit ensures that if a window maximized using the 'komorebic
toggle-maximize' is minimized using the UI, on the receipt of the
Minimize WindowManagerEvent, the window will be normalized with
SW_NORMAL before being removed from the window manager state.

This ensures that if the window is later managed again, the user will be
able to toggle-maximize normally again as expected.
2022-07-26 12:57:22 -07:00
LGUG2Z
eec628f7f1 fix(wm): enforce strict manage override checks
This commit ensures that when a window has matched a float rule, the
managed override rule will only apply to that window if the override
identifier is of the same kind (exe, title, class) as the float rule
identifier.

This ensures that the wm isn't constantly trying to allow and disallow
certain windows such as Slack's hidden window, resulting in an infinite
show/hide and retile loop.
2022-07-25 20:53:50 -07:00
LGUG2Z
77ae5bc2f4 docs(readme): add github sponsors early access section 2022-07-24 20:00:23 -07:00
LGUG2Z
87a0aaee0c chore(deps): cargo update 2022-07-24 14:44:19 -07:00
LGUG2Z
fc5bb892f9 feat(dpi): default to per monitor v2
This commit ensures that the process starts with
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 set by default.

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext

fix #187
2022-07-24 07:45:43 -07:00
جاد
26ec574452 Create FUNDING.yml 2022-07-23 06:09:10 -07:00
LGUG2Z
1c7a5ccb42 chore(deps): bump windows-rs from 0.38 to 0.39 2022-07-21 06:53:29 -07:00
LGUG2Z
876439b96b chore(release): v0.1.10 2022-07-19 09:58:03 -07:00
LGUG2Z
d1b1a9e006 fix(scoop): account for all shims on startup check
This commit fixes a bug with the startup check which tries to ensure
that there is only ever one instance of komorebi running at any given
time.

Previously, only one shim was being checked for, but if a user runs
'komorebic start' multiple times, multiple shims will be active, causing
the check to mistakenly pass.

The changes in this commit now account for N active shims.
2022-07-19 09:54:17 -07:00
LGUG2Z
04791f427b refactor(clippy): apply explicit_auto_deref lint fixes 2022-07-05 13:11:22 -07:00
LGUG2Z
e9bccd0316 docs(readme): add faq section for gapless config
resolve #172
2022-07-05 13:09:52 -07:00
dependabot[bot]
29201b6b94 chore(deps): bump serde_json from 1.0.81 to 1.0.82
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.81 to 1.0.82.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.81...v1.0.82)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:40:54 -07:00
dependabot[bot]
8efce49f2c chore(deps): bump serde from 1.0.137 to 1.0.138
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.137 to 1.0.138.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.137...v1.0.138)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:40:12 -07:00
dependabot[bot]
6c022f8d69 chore(deps): bump tracing-subscriber from 0.3.11 to 0.3.14
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.11 to 0.3.14.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.11...tracing-subscriber-0.3.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:39:17 -07:00
dependabot[bot]
748659db35 chore(deps): bump quote from 1.0.19 to 1.0.20
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.19 to 1.0.20.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.19...1.0.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:07:21 -07:00
dependabot[bot]
91c7f0588c chore(deps): bump proc-macro2 from 1.0.39 to 1.0.40
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.39 to 1.0.40.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.39...1.0.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:07:14 -07:00
dependabot[bot]
60ec439d06 chore(deps): bump crossbeam-utils from 0.8.9 to 0.8.10
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.9 to 0.8.10.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.9...crossbeam-utils-0.8.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:05:58 -07:00
dependabot[bot]
ead175ddbc chore(deps): bump clap from 3.2.5 to 3.2.8
Bumps [clap](https://github.com/clap-rs/clap) from 3.2.5 to 3.2.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/v3.2.5...v3.2.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 09:05:33 -07:00
dependabot[bot]
5dd3e76602 chore(deps): bump sysinfo from 0.23.13 to 0.24.5
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.23.13 to 0.24.5.
- [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>
2022-07-04 09:05:07 -07:00
LGUG2Z
ebcd7ce224 fix(cli): tag enum arg for ws layout rule cmd
This commit ensures that the final argument of the
'workspace-layout-rule' command, which takes a variant of the
DefaultLayout enum, is properly labelled with the #[arg_enum] tag so
that serialization and deserialization works as expected with other
commands that take a DefaultLayout enum variant as an arg.

fix #171
2022-07-03 11:05:26 -07:00
LGUG2Z
b982021573 fix(wm): ensure manage > float rule priority
This commit ensures that manage rules have priority over float rules.
This is useful for applications such as Steam, where all windows
including pop-ups have the same class name.

The class name can be used with a float rule to ensure that all Steam
pop-up windows are ignored, and then the title "Steam" can be used with
a manage rule to ensure that the main Steam window does get managed.

fix #163
2022-06-29 07:12:29 -07:00
LGUG2Z
3c84bfd27e feat(wm): add cross-monitor move behaviour config
This commit provides two new commands, to set and toggle the behaviour
(swap, insert) when moving window containers across monitor boundaries.
MoveBehaviour::Swap has been selected as the default as this seems to be
the default on bspwm.

resolve #145
2022-06-27 09:14:09 -07:00
LGUG2Z
c874bfc7bf feat(wm): swap with last container across monitors
This commit ensures that when moving across a monitor boundary, the
origin window container will be swapped with the last focused window
container on the other side of the monitor boundary.

If there is no window container on the other side of the window
boundary, it will be treated as a move instead of a swap.

re #145
2022-06-27 09:13:43 -07:00
LGUG2Z
85f9c381e5 feat(wm): focus to last container across monitors
This commit ensures that when focusing across a monitor boundary, the
focus will go to the last focused window container on the focused
workspace on the other side of a given window boundary.

re #145
2022-06-27 09:13:43 -07:00
LGUG2Z
336a4e358f feat(wm): move/focus across monitor edges
This commit introduces the ability to operate across monitor boundaries
with the 'move' and 'focus' commands.

When operating down or to the right, the target index of the monitor in
that direction will be 0. When operating up or to the left, the target
index will either be len() - 1 if focusing, or len() if moving.

re #145
2022-06-27 09:13:43 -07:00
Amr Bashir
4576078b96 refactor(wm): store rt files under %LOCALAPPDATA% (#164) 2022-06-27 09:09:49 -07:00
LGUG2Z
39971774ea chore(deps): bump windows-rs from 0.37 to 0.38 2022-06-23 08:42:44 -07:00
LGUG2Z
7da431081e fix(wm): identify overflow apps on drag move
This commit ensures that border overflow applications are correctly
identified as being moved instead of being resized when dragged to a new
position using the mouse.

fix #159
2022-06-21 16:41:11 -07:00
LGUG2Z
7cc69a4a40 feat(config): add ahk exe override env vars
This commit introduces two new environment variables to override the exe
name that komorebi uses to verify that AutoHotKey is installed before
launching and reloading 'komorebi.ahk' configuration files.

resolve #147
2022-06-18 20:40:24 -07:00
LGUG2Z
5f325a7458 refactor(wm): correct naming in resize-axis logic 2022-06-14 16:59:15 -07:00
LGUG2Z
34a7b2eb0c chore(deps): cargo update 2022-06-14 16:56:12 -07:00
LGUG2Z
b08eb0d50c fix(wm): allow resize-axis on custom layout rule
This commit ensures that a custom layout that is set by a layout rule
will correctly have the width of the primary column increased or
decreased when resize-axis is called with Axis::Horizontal.

re #154
2022-06-14 16:54:47 -07:00
LGUG2Z
005a95b1e6 chore(deps): cargo update 2022-06-11 20:08:16 -07:00
LGUG2Z
092e36b8b3 feat(wm): allow valid attach_thread_input failures
This commit allows the Window.focus() fn to continue execution if
AttachThreadInput fails, as there are valid situations in which this
might fail, but the focusing of the window may/should still succeed.

fix #156
2022-06-11 20:04:48 -07:00
LGUG2Z
70be6f4ea4 feat(wm): start/end match on some float rules
This commit tweaks how float rule matching for titles and classes works.
Previously, they required an exact match to be triggered.

This change allows starts_with and ends_with matches on classes and
window titles to also trigger a float rule for applications that
dynamically change their window titles or window classes.

Exe matches are still required to be exact.
2022-06-05 15:18:13 -07:00
dependabot[bot]
e09d55e71a chore(deps): bump parking_lot from 0.12.0 to 0.12.1 (#149)
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.12.0...0.12.1)

---
updated-dependencies:
- dependency-name: parking_lot
  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>
2022-06-01 07:27:58 -07:00
dependabot[bot]
7cef7b53b5 chore(deps): bump powershell_script from 0.3.2 to 1.0.2 (#140)
Bumps [powershell_script](https://github.com/cfsamson/powershell-script) from 0.3.2 to 1.0.2.
- [Release notes](https://github.com/cfsamson/powershell-script/releases)
- [Commits](https://github.com/cfsamson/powershell-script/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-26 10:29:26 -07:00
LGUG2Z
8594e72d31 perf(logging): avoid extra heap allocations 2022-05-26 09:48:42 -07:00
LGUG2Z
bc22ab699f chore(deps): bump windows-rs from 0.36 to 0.37 2022-05-26 09:46:49 -07:00
dependabot[bot]
b3844af1f3 chore(deps): bump goreleaser/goreleaser-action from 2 to 3 (#146)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-26 09:34:51 -07:00
LGUG2Z
b43f03ce83 chore(release): v0.1.9 2022-05-17 16:23:08 -07:00
dependabot[bot]
5c1cfe7b2e chore(deps): bump clap from 3.1.14 to 3.1.15 (#141)
Bumps [clap](https://github.com/clap-rs/clap) from 3.1.14 to 3.1.15.
- [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/v3.1.14...v3.1.15)

---
updated-dependencies:
- dependency-name: clap
  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>
2022-05-04 13:48:16 -07:00
dependabot[bot]
6269e5972c chore(deps): bump serde_json from 1.0.79 to 1.0.81 (#142)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.79 to 1.0.81.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.79...v1.0.81)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2022-05-04 12:31:54 -07:00
dependabot[bot]
ffa0b0b55e chore(deps): bump serde_yaml from 0.8.23 to 0.8.24 (#139)
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.23 to 0.8.24.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.23...0.8.24)

---
updated-dependencies:
- dependency-name: serde_yaml
  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>
2022-05-04 12:30:58 -07:00
dependabot[bot]
66199c5b15 chore(deps): bump sysinfo from 0.23.10 to 0.23.11 (#143)
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.23.10 to 0.23.11.
- [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>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 12:30:14 -07:00
dependabot[bot]
aa42a64a48 chore(deps): bump windows from 0.36.0 to 0.36.1 (#138)
Bumps [windows](https://github.com/microsoft/windows-rs) from 0.36.0 to 0.36.1.
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Commits](https://github.com/microsoft/windows-rs/compare/0.36.0...0.36.1)

---
updated-dependencies:
- dependency-name: windows
  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>
2022-05-04 12:19:18 -07:00
dependabot[bot]
17f1923423 chore(deps): bump syn from 1.0.91 to 1.0.92 (#137)
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.91 to 1.0.92.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.91...1.0.92)

---
updated-dependencies:
- dependency-name: syn
  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>
2022-05-04 12:19:03 -07:00
dependabot[bot]
5cfc3e831b chore(deps): bump serde from 1.0.136 to 1.0.137 (#135)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.136 to 1.0.137.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.136...v1.0.137)

---
updated-dependencies:
- dependency-name: serde
  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>
2022-05-04 12:18:40 -07:00
dependabot[bot]
aaf0434053 chore(deps): bump ctrlc from 3.2.1 to 3.2.2 (#134)
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.2.1...3.2.2)

---
updated-dependencies:
- dependency-name: ctrlc
  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>
2022-05-04 12:18:27 -07:00
dependabot[bot]
2224479c30 chore(deps): bump clap from 3.1.10 to 3.1.14 (#136)
Bumps [clap](https://github.com/clap-rs/clap) from 3.1.10 to 3.1.14.
- [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/v3.1.10...v3.1.14)

---
updated-dependencies:
- dependency-name: clap
  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>
2022-05-04 12:18:10 -07:00
LGUG2Z
957588f60d chore(deps): bump windows-rs from 0.35 to 0.36 2022-04-26 12:05:20 -07:00
LGUG2Z
d111d68c0b fix(windows): cmp vs. input type in result processor 2022-04-25 13:12:23 -07:00
LGUG2Z
a10b13c799 fix(windows): ensure result processor is type-agnostic 2022-04-21 13:59:14 -07:00
LGUG2Z
1e69c65c25 fix(wm): ignore polling updates from com hwnds
This commit ensures that what monitor reconciliation is triggered from a
MonitorPoll event, the focused monitor is only updated when the HWND
associated with the event is known not to be tied to a specific (in this
case, the primary) monitor.

This ensures that silent state updates do not occur and avoids
unexpected behaviour when performing operations relative to the
currently focused window on a non-primary display (focus, move etc.)
2022-04-20 16:44:19 -07:00
LGUG2Z
711ab8d59b feat(wm): add cmd for unmanaged hwnd op behaviour
This commit adds a new command, 'unmanaged-window-operation-behaviour'
which allows the user to configure their desired behaviour in situations
when sending window container commands which operate on the focused
window container in the workspace state, but having an unmanaged window
as the foreground hwnd.

The default previously was previously Op (and this remains the default
with these new changes), but the user can now select NoOp, which will
return an error when the focused hwnd is unmanaged and not allow any
write operations to take place on the focused workspace state.

resolve #133
2022-04-19 17:13:27 -07:00
LGUG2Z
e1c36c9190 fix(wm): update origin ws after container removal
This commit ensures that the origin workspace will be updated after a
container is removed to be sent to a target workspace (specified, or
currently focused) on another monitor.

With this change in place, moving window containers to another monitor
should not result in a ghost container that remains until the next
retile on the origin workspace.

fix #132
2022-04-19 11:10:22 -07:00
LGUG2Z
686d013734 fix(windows): reintroduce hwnd val checks
@riverar pointed out on Discord that I had my if and else clauses here
mixed up. This commit reintroduces null value checks for HWNDs returned
from Windows API calls.
2022-04-15 17:31:57 -07:00
LGUG2Z
fad4cbf019 fix(ahk): quote app ids in generated code
Previously, generated AHK did not surround with quotes inputs which
could contain spaces such as application titles. This commit ensures
that in any generated AHK code where an application id is passed to a
komorebic.exe command as input, that input will always be quoted.

This fixes bugs related to float rules, manage rules and other
application identification commands not being properly executed via
komorebic called from the generated AHK files when the app id contained
a space.
2022-04-15 12:22:20 -07:00
LGUG2Z
839f8c9bf7 fix(windows): remove hwnd val checks on 0.35
Small commit to temporarily handle a regression introduced by my changes
when upgrading from 0.34 to 0.35.

Checking for a 0 HWND value results in an Err being propagated in fns
like GetForegroundWindow, while the error message just reads "The
operation completed successfully. (os error 0)".

This behaviour was causing regressions in features such as window
floating which seems to be resolved by removing the 0 HWND check.
2022-04-15 08:28:07 -07:00
LGUG2Z
5d468ae70a docs(readme): add cfgen explanation and demos 2022-04-14 17:21:09 -07:00
LGUG2Z
93edcfaa2f Merge branch 'master' into feature/config-generation 2022-04-13 19:40:37 -07:00
LGUG2Z
02a3220cbd chore(deps): bump windows-rs from 0.34 to 0.35 2022-04-13 19:39:26 -07:00
dependabot[bot]
4b6a7c05e0 chore(deps): bump actions/upload-artifact from 2 to 3 (#129)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-12 10:03:51 -07:00
LGUG2Z
304158cb1f feat(config): add cfgen override merging
This commit adds a second optional argument to the ahk-asc command which
can take an override yaml file. This file can include either entirely
new entries that are not suitable for the asc definitions in the
community repo, or overrides for entries that exist in the community asc
definitions files which will take precedence in the generated ahk file.

This can be useful for example, when the default behaviour for an app is
to minimise to system tray, but that option has been disabled on a
user's computer, making the 'tray_and_multi_window' option no longer
appropriate for their komorebi configuration.

In the case of wanting to override an existing entry, only the "name"
key needs to match; upon a match the entry from the community asc
definitions will be entirely replaced with the entry from the override
definitions.

re #62
2022-04-03 18:17:39 -07:00
LGUG2Z
c426c06c01 feat(config): add fmt cmd & float rule comments
This commit adds a fmt command which allows users to prepare PRs to the
configuration repository in a unified way.

The 'custom' formatter basically just ensures that the yaml array is
sorted by application name to make for easier diffs.

Serializing of Option::None has been disabled to keep the yaml file more
concise.

Finally, an option for adding comments to float rules has been included
as some of these rules can be quite esoteric and there is value in
having them annotated with comments in the configuration to preserve and
pass down the knowledge.

The config generation command has been renamed to
'ahk-app-specific-configuration' (with a short alias of 'ahk-asc') to
emphasise that an ahk file is being generated (similar to
'ahk-library').

re #62
2022-04-03 13:43:51 -07:00
Eric Reeves
c2cc21d09d float-rule Syntax Error Fixed (#127) 2022-04-02 08:45:57 -07:00
LGUG2Z
09a24b89e5 feat(config): add cfgen for apps based on yaml def
This commit introduces a configuration generator for
application-specific config options passed to the cli via a file path.

The hope is to have a public repository that any user can contribute
application-specific configs and fixes to, and for the generated AHK to
be available to any new user as part of the initial setup to make the
onboarding as frictionless as possible.

re #62
2022-04-01 18:25:05 -07:00
LGUG2Z
4686d5e346 feat(wm): add cmd to id layered apps
Users on Discord noted that Microsoft Office applications were not being
handled correctly by the wm. After some investigation it was clear that
this was because the application windows had WS_EX_LAYERED set.

This had only been seen once before with Steam, and so a whitelist for
layered applications was previously added to the codebase with steam.exe
hard-coded, but this had not been exposed via the cli.

This commit adds a command to allow users to specify layered
applications which should be managed, and also renames the whitelist to
reflect that classes can also be used to identify applications on the
whitelist.

A section has been added to the README to guide Microsoft Office users
to guide Microsoft Office users in configuring komorebi to correctly
handle Office applications with Word given as an example.

This commit also renames the identify-border-overflow command to
identify-border-overflow-application for consistency, while retaining
the previous command as an alias to maintain compatibility with existing
user configurations.

It should be noted however, that those like me who are using the
generated komorebic AHK library, will have to update any AHK function
calls to use IdentifyBorderOverflowApplication().

resolve #124
2022-03-30 11:04:41 -07:00
LGUG2Z
532adc9c6c docs(readme): fix heading for dynamic layouts section 2022-03-29 09:39:20 -07:00
LGUG2Z
a4e8286327 feat(wm): allow cycling for max & monacle windows
This commit introduces focus cycling behaviour for a workspace when
either a maximized window or a monocle window exists.

Now, the container in the cycle direction relative to the current window
container will take the maximized or monocle window container space
whenever the cycle-focus command is called.

resolve #97
2022-03-29 07:31:18 -07:00
LGUG2Z
75234caa98 feat(wm): add dynamic layout selection rules
This commit adds a new feature which allows the user to specify a set of
rules for a specific workspace that will be used to calculate which
layout to apply to that workspace at any given time.

The rule consists of a usize, which identifies the threshold of window
containers which need to be visible on the workspace to activate the
rule, and a layout, which will be applied to the workspace when the rule
is activated.

Both default and custom layouts can be used in workspace layout rules.

When a workspace has layout rules in effect, manually changing the
layout will not work again until the rules for that workspace have been
cleared.

This feature came about after trying but failing to modify the custom
layout code in such a way that the width percentage of a primary column
in a custom layout might be propagated to the fallback columnar layout
when the tertiary column threshold is not met.

Although this new feature introduces more complexity, it is strictly
opt-in and can be completely ignored if the user has no interest in
adjusting layouts based on the visible window count.

re #121
2022-03-28 14:49:16 -07:00
LGUG2Z
31b8be1481 feat(wm): add cmd to id apps with odd launch event
A user on the Discord noted that PyCharm windows were not being managed
as expected when initially launched. After some digging this seems to be
the same issue that was addressed for IntelliJ and Firefox early on in
development, where these applications send EVENT_OBJECT_NAMECHANGE on
launch instead of the regular event when drawing a new window.

The OBJECT_NAME_CHANGE_ON_LAUNCH vec was not previously exposed via
komorebic to allow users to identify other applications that exhibit the
same behaviour. This commit adds a command to allow users to specify
further applications in their configuration files.
2022-03-28 10:08:34 -07:00
dependabot[bot]
634bc04d76 chore(deps): bump actions/cache from 2 to 3 (#123)
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-21 08:46:35 -07:00
LGUG2Z
3b30c10ebb chore(deps): bump minor and patch versions with cargo update 2022-03-18 16:44:22 -07:00
LGUG2Z
3eade94032 chore(deps): bump windows-rs from 0.33 to 0.34 2022-03-18 16:43:31 -07:00
dependabot[bot]
e46f1f4f6d chore(deps): bump actions/checkout from 2 to 3 (#122)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [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/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 07:59:33 -08:00
dependabot[bot]
45ea630e6a chore(deps): bump powershell_script from 0.2.1 to 0.3.2 (#118)
Bumps [powershell_script](https://github.com/cfsamson/powershell-script) from 0.2.1 to 0.3.2.
- [Release notes](https://github.com/cfsamson/powershell-script/releases)
- [Commits](https://github.com/cfsamson/powershell-script/commits)

---
updated-dependencies:
- dependency-name: powershell_script
  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>
Co-authored-by: LGUG2Z <jadeiqbal@fastmail.com>
2022-03-03 17:24:52 -08:00
LGUG2Z
ed01bb674f refactor(clap): fix deprecations 2022-03-03 16:25:57 -08:00
dependabot[bot]
a9534fa49c chore(deps): bump clap from 3.0.14 to 3.1.3
Bumps [clap](https://github.com/clap-rs/clap) from 3.0.14 to 3.1.3.
- [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/v3.0.14...v3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 16:25:57 -08:00
dependabot[bot]
d4c0c35f3a chore(deps): bump windows from 0.32.0 to 0.33.0
Bumps [windows](https://github.com/microsoft/windows-rs) from 0.32.0 to 0.33.0.
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Commits](https://github.com/microsoft/windows-rs/compare/0.32.0...0.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 16:07:46 -08:00
dependabot[bot]
f6e0f5ab81 chore(deps): bump crossbeam-utils from 0.8.6 to 0.8.7
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.6 to 0.8.7.
- [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.6...crossbeam-utils-0.8.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 08:25:41 -08:00
dependabot[bot]
51139b9e0c chore(deps): bump color-eyre from 0.6.0 to 0.6.1
Bumps [color-eyre](https://github.com/yaahc/color-eyre) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/yaahc/color-eyre/releases)
- [Changelog](https://github.com/yaahc/color-eyre/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yaahc/color-eyre/compare/v0.6.0...v0.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 08:25:26 -08:00
dependabot[bot]
d7f1190152 chore(deps): bump strum from 0.23.0 to 0.24.0
Bumps [strum](https://github.com/Peternator7/strum) from 0.23.0 to 0.24.0.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [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>
2022-03-01 08:10:15 -08:00
dependabot[bot]
b62d77501a chore(deps): bump tracing-appender from 0.2.0 to 0.2.1
Bumps [tracing-appender](https://github.com/tokio-rs/tracing) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-appender-0.2.0...tracing-appender-0.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 08:09:43 -08:00
dependabot[bot]
7cb60ca7c5 chore(deps): bump sysinfo from 0.23.0 to 0.23.5
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.23.0 to 0.23.5.
- [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>
2022-03-01 08:09:28 -08:00
dependabot[bot]
43edf13bb2 chore(deps): bump serde_json from 1.0.78 to 1.0.79
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.78 to 1.0.79.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.78...v1.0.79)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 08:09:06 -08:00
LGUG2Z
cd894655db refactor(clippy): impl as_ref on wm struct 2022-02-05 13:59:05 -08:00
LGUG2Z
02c54734fb feat(wm): add send-to-monitor-workspace cmd
This commit adds a dedicated command to send a window container to a
specific workspace on a target monitor.
2022-02-05 13:42:16 -08:00
LGUG2Z
4a3f7ee34e chore(deps): bump windows-rs from 0.30 to 0.32 2022-02-03 14:21:07 -08:00
LGUG2Z
2db0d888c1 feat(subscriptions): add cmd to gen json schema
This commit introduces the 'notification-schema' command to generate a
JSON schema of the Notification struct which gets sent when notifying
subscribers of updates.
2022-02-01 12:38:11 -08:00
LGUG2Z
cf5a41b5eb chore(deps): cargo update 2022-02-01 10:13:50 -08:00
dependabot[bot]
e4ee298606 chore(deps): bump sysinfo from 0.22.5 to 0.23.0
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.22.5 to 0.23.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>
2022-02-01 08:41:24 -08:00
dependabot[bot]
38c0b25a1c chore(deps): bump serde from 1.0.133 to 1.0.136
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.133 to 1.0.136.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.133...v1.0.136)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 08:10:36 -08:00
dependabot[bot]
d1b6a63af5 chore(deps): bump color-eyre from 0.5.11 to 0.6.0
Bumps [color-eyre](https://github.com/yaahc/color-eyre) from 0.5.11 to 0.6.0.
- [Release notes](https://github.com/yaahc/color-eyre/releases)
- [Changelog](https://github.com/yaahc/color-eyre/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yaahc/color-eyre/compare/v0.5.11...v0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 08:10:23 -08:00
dependabot[bot]
c246b209c4 chore(deps): bump which from 4.2.2 to 4.2.4
Bumps [which](https://github.com/harryfei/which-rs) from 4.2.2 to 4.2.4.
- [Release notes](https://github.com/harryfei/which-rs/releases)
- [Commits](https://github.com/harryfei/which-rs/compare/4.2.2...4.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 08:06:39 -08:00
dependabot[bot]
a2e1b8c967 chore(deps): bump parking_lot from 0.11.2 to 0.12.0
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.11.2 to 0.12.0.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.11.2...0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 07:59:09 -08:00
dependabot[bot]
cb387025d2 chore(deps): bump tracing-subscriber from 0.3.6 to 0.3.7
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.6 to 0.3.7.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.6...tracing-subscriber-0.3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 07:58:13 -08:00
dependabot[bot]
6655d290f2 chore(deps): bump quote from 1.0.14 to 1.0.15
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.14 to 1.0.15.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.14...1.0.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 07:57:52 -08:00
dependabot[bot]
999f2ae2d4 chore(deps): bump clap from 3.0.8 to 3.0.13
Bumps [clap](https://github.com/clap-rs/clap) from 3.0.8 to 3.0.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/v3.0.8...v3.0.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 07:57:39 -08:00
dependabot[bot]
cddc69d2bf chore(deps): bump serde_json from 1.0.75 to 1.0.78
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.75 to 1.0.78.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.75...v1.0.78)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 07:57:18 -08:00
LGUG2Z
43b2366378 feat(config): allow users to define config dir
This commit introduces a change to allow users to set a custom
configuration directory for Komorebi to address concerns about $HOME
getting cluttered.

The custom directory can be set with the environment variable
$Env:KOMOREBI_CONFIG_HOME (this should probably be done in $PROFILE).

If this variable is not set, komorebi will default to using
the $HOME directory.

resolve #61
2022-01-28 09:35:42 -08:00
LGUG2Z
e67425f841 docs(readme): update scoop install instructions 2022-01-28 08:35:44 -08:00
LGUG2Z
b2a34204c6 chore(release): v0.1.8 2022-01-27 11:10:29 -08:00
LGUG2Z
d18283969a fix(scoop): allow duplicate shim process
This commit addresses issues that users have been faced with when
installing komorebi with scoop, which resulted in komorebi exiting
almost immediately without providing any feedback as to what had
happened.

Scoop launches komorebi using an exe shim of the same name, which
results in two komorebi.exe named processes running at the same time.

This situation then fails the startup check which attempts to ensure
that only one instance of komorebi.exe ever runs at any given time.

The process startup check has been updated to allow for two komorebi.exe
named processes to be running if one of them is recognised as a Scoop
shim process.

fix #95
2022-01-27 11:07:23 -08:00
LGUG2Z
0138a313c0 docs(readme): update discord invite link 2022-01-17 09:12:12 -08:00
LGUG2Z
c62ddb3c42 chore(release): v0.1.7 2022-01-17 08:19:09 -08:00
LGUG2Z
87e8eb48a6 fix(wm): update state for any new float rules
This update ensures that whenever a new float rule is added, the focused
workspaces on all monitors will be checked to see if there are any
currently managed windows which match that rule. If so, the matching
window(s) will be removed from the workspace and the workspace will be
updated.

Matching windows on non-focused workspaces will not be removed, as these
windows may be hidden, and removing them could result in these windows
being inaccessible, requiring them to be killed before they can be
relaunched

fix #93
2022-01-17 08:13:56 -08:00
LGUG2Z
5f1356b3e2 docs(readme): update link to aik2's post 2022-01-14 08:17:09 -08:00
LGUG2Z
00df672352 chore(deps): bump heck from 0.3.3 to 0.4.0 2022-01-13 14:13:12 -08:00
dependabot[bot]
2b83ff8148 chore(deps): bump sysinfo from 0.21.2 to 0.22.4
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.21.2 to 0.22.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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-13 14:09:12 -08:00
LGUG2Z
749e247d85 chore(deps): bump windows-rs to 0.30 2022-01-13 14:08:43 -08:00
LGUG2Z
e70086b681 docs(readme): add links to articles, blogs etc 2022-01-11 07:37:39 -08:00
LGUG2Z
39685dd615 feat(wm): add cmd to move ws to other monitors
This commit adds a new komorebic command to move the entire focused
workspace and all managed windows and containers to a target monitor
index. Windows that have been excluded from management using various
rules will not be moved as they are not tracked in the window manager
state.

resolve #88
2022-01-07 08:52:18 -08:00
LGUG2Z
228ef78d7f chore(deps): bump clap to 3.0.0 2021-12-31 15:19:14 -08:00
LGUG2Z
e2ae9b1207 chore(deps): bump clap to latest rc 2021-12-31 08:33:29 -08:00
LGUG2Z
5e3f1cbb44 fix(wm): send state errors to komorebic
This commit ensures that errors are sent to komorebic in response to the
state command if they occur, so that komorebic is not left hanging
indefinitely waiting for a successful response that will never come.
2021-12-06 16:43:22 -08:00
LGUG2Z
9fd4dbf044 fix(wm): handle empty vd reg values gracefully
This commit fixes a regression introduced in
85fe20ebba, where running komorebi before
creating and interacting with virtual desktops via the task view on
Windows 10 would cause komorebi to panic when it could not find the
CurrentVirtualDesktop key in the registry, as it only gets populated
after interacting with virtual desktops via the task view in a new
session.
2021-12-03 22:24:50 -08:00
LGUG2Z
9be248bc03 feat(wm): add cmd to focus ws on target monitor
This commit adds a new command which allows the focusing of workspaces
on monitors other than the currently focused monitor by specifying a
monitor index.

Sending this command to komorebi will make the target monitor index the
currently focused monitor.

resolve #85
2021-12-03 14:52:14 -08:00
LGUG2Z
85fe20ebba refactor(wm): validate virtual desktops via reg
This commit refactors the validations that ensure that only commands and
events originating on the same virtual desktop that komorebi was started
on are managed.

This was previously handled by the winvd crate which relied on
undocumented APIs that broke as of Windows 11. This method, while not
very elegant, seems like the best solution for now.

In short, komorebi checks the registry (which has different paths on
Win10 and Win11...) to keep track of the current virtual desktop id.

This is problematic because we just end up comparing byte arrays, and
there is no meaningful representation of the ids that are being
compared, not even a GUID.  Nevertheless, it works and it ensures that
komorebi is limited to operating on a single virtual desktop.

resolve #77
2021-12-02 18:44:44 -08:00
LGUG2Z
409d374b72 fix(wm): enforce virtual desktop validation
This commit fixes a regression introduced in
2e86b607b2
which broke virtual desktop id validation on Windows 10.
2021-12-02 15:59:09 -08:00
dependabot[bot]
1fb0a7cd6e chore(deps): bump tracing-subscriber from 0.3.1 to 0.3.3
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.1 to 0.3.3.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.1...tracing-subscriber-0.3.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 11:51:46 -08:00
dependabot[bot]
d0e46515c5 chore(deps): bump strum from 0.21.0 to 0.23.0
Bumps [strum](https://github.com/Peternator7/strum) from 0.21.0 to 0.23.0.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [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>
2021-12-01 11:51:31 -08:00
dependabot[bot]
e01bbd9f74 chore(deps): bump sysinfo from 0.20.5 to 0.21.1
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.20.5 to 0.21.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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 09:00:33 -08:00
dependabot[bot]
be53ea2c24 chore(deps): bump syn from 1.0.81 to 1.0.82
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.81 to 1.0.82.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.81...1.0.82)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 09:00:16 -08:00
dependabot[bot]
d49279e888 chore(deps): bump getset from 0.1.1 to 0.1.2
Bumps [getset](https://github.com/Hoverbear/getset) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/Hoverbear/getset/releases)
- [Commits](https://github.com/Hoverbear/getset/compare/0.1.1...0.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 09:00:05 -08:00
dependabot[bot]
daa2912945 chore(deps): bump serde_json from 1.0.71 to 1.0.72
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.71 to 1.0.72.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.71...v1.0.72)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 08:59:36 -08:00
dependabot[bot]
2c515d54f7 chore(deps): bump miow from 0.3.7 to 0.4.0
Bumps [miow](https://github.com/yoshuawuyts/miow) from 0.3.7 to 0.4.0.
- [Release notes](https://github.com/yoshuawuyts/miow/releases)
- [Changelog](https://github.com/yoshuawuyts/miow/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yoshuawuyts/miow/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 08:59:18 -08:00
LGUG2Z
f9785bef55 feat(wm): add option to either minimize or hide
This commit adds a command to let the user decide if they want windows
to be hidden with SW_HIDE or minimized with SW_MINIMIZE when workspaces
are changed or window container stacks are cycled.

After a modest amount of local testing, SW_MINIMIZE does not appear to
introduce any regressions, and given that alt-tabbing is a common
workflow on Windows, it makes sense to have minimizing be the default
setting to ease the onboarding experience for new users.

resolve #72
2021-11-20 15:18:45 -08:00
LGUG2Z
0519ebddbf fix(wm): ignore mstsc.exe helper classes on w11
This commit adds OPContainerClass and IHWindowClass to the
FLOAT_IDENTIFIERS global vec, to ignore by default the extra invisible
input and output handling windows created by mstsc.exe when WSL is
launched on Windows 11.

fix #74
2021-11-20 08:32:43 -08:00
LGUG2Z
b1ca0a3e3c feat(wm): enforce last known layout on unpause
This commit ensures that the last known layout is restored on a
workspace when a command to unpause has been received.
2021-11-19 16:34:38 -08:00
LGUG2Z
84ccfedad4 chore(deps): bump windows-rs from 0.26 to 0.28 2021-11-18 14:09:54 -08:00
LGUG2Z
adcb38fed9 chore(deps): bump windows-rs from 0.25 to 0.26 2021-11-15 12:49:51 -08:00
LGUG2Z
4a19edaab2 feat(wm): impl drag to move in append mode
This commit ensures that when a window is dragged over another window container while
WindowContainerBehaviour::Append is set, the window will be removed from its current
container and appended to the target container instead of swapping the positions of the two
containers, as would be the case for WindowContainerBehaviour::Create.

re #72
2021-11-15 12:42:49 -08:00
LGUG2Z
676b643faf feat(wm): add cmd to toggle new window behaviour
This commit introduces a new command, toggle-new-window-behaviour, which
can be used to toggle how new windows on the screen will be handled.

The default setting is to add a new window in a dedicated container, but
when toggled, new windows will be stacked on top of the currently
focused window container.

This can be useful if you only want to use a certain number of columns,
and when you have enough windows on the screen for them, you can toggle
the new window behaviour to start appending to the existing column
stacks.

This commit also fixes a bug where stacked windows being closed did
cause the next window underneath in the stack to be shown.

re #72
2021-11-13 14:31:02 -08:00
LGUG2Z
7f74640dbd chore(deps): bump windows-rs and serde-json 2021-11-05 15:22:45 -07:00
LGUG2Z
c247426b8e ci(artifacts): include .pdb files in archives
resolve #70
2021-11-03 11:10:32 -07:00
LGUG2Z
4d7ccc5519 feat(wm): allow resize-axis for custom primary col
This commit allows the resize-axis cmd on Axis::Horizontal to operate on
the Primary column of a CustomLayout.

Note that this will only operate on a CustomLayout that has met the
window count threshold to enable the tertiary column. If it has not, the
layout will render as DefaultLayout::Columns, which does not support the
resize-axis cmd.
2021-11-03 10:04:51 -07:00
LGUG2Z
71e28b33e3 feat(wm): add resize-delta cmd
This commit adds a command to set the resize delta used under the hood
by the resize-edge and resize-axis commands. The resize delta defaults
to 50 pixels as was hard-coded previously.
2021-11-02 14:16:29 -07:00
LGUG2Z
40226a2bbd refactor(wm): update cmds & keep compat w/ aliases
This commit updates a number of komorebic subcommand names while
maintaining their old names as aliases in order to preserve backwards
compatibility.

Resize becomes ResizeEdge, to complement ResizeAxis, and all of the
commands for saving and loading BSP resize adjustments (QuickSave,
QuickLoad, Save, Load) are now post-fixed with "Resize" in order to make
it clear that these commands are not related to custom layout saving and
loading.
2021-11-02 14:16:29 -07:00
LGUG2Z
2814349228 feat(wm): add cmd to resize by axis
This commit adds a new command to resize by axis. Resizing is still
limited to the BSP layout. This command is intended to be bound to mouse
wheel up and down events, with different modified keys determining the
axis to operate on.
2021-11-02 14:16:29 -07:00
LGUG2Z
d627a1a771 chore(deps): bump windows-rs from 0.23 to 0.24 2021-11-02 13:24:23 -07:00
LGUG2Z
78683ce7b3 chore(deps): bump clap from 3.0.0-beta.4 to beta.5 2021-11-01 14:56:42 -07:00
LGUG2Z
a1ca4f03c3 chore(deps): bump tracing-subscriber and tracing-appender 2021-11-01 14:45:36 -07:00
dependabot[bot]
147a56c274 chore(deps): bump syn from 1.0.80 to 1.0.81
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.80 to 1.0.81.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.80...1.0.81)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 14:39:13 -07:00
LGUG2Z
127254b7ac feat(wm): disable windows ffm on stop and ctrl-c
This commit disables the native Windows ffm implementation as part of
the stop command and the ctrl-c handler.

resolve #69
2021-11-01 13:59:40 -07:00
LGUG2Z
4e6e2b3aa8 feat(wm): mouse follows focus enable/disable cmd
This commit adds a command to explicitly specify the desired state of
mouse follows focus to complement the previously added toggle command.
2021-10-30 16:05:04 -07:00
LGUG2Z
a55069df48 feat(wm): add mouse follows focus toggle
This commit adds a toggle for the mouse follows focus behaviour that has
been the default for komorebi until now.

resolve #63
2021-10-29 13:41:55 -07:00
LGUG2Z
7fd545ca35 fix(wm): handle cross-monitor drag/move events
This commit ensures that when a window is dragged across a monitor
boundary, the ownership of the window container will be transferred to
the target monitor's currently focused workspace.

In order to achieve this, a new WindowManagerEvent variant has been
added, MoveResizeStart, which will store an optional pending_move_op on
the WindowManager struct. This must be consumed at the beginning of the
handler for MoveResizeEnd.

This is necessary because as soon as the window is dragged across a
monitor boundary, an event is sent (and handled) to update the currently
focused monitor and workspace as the target monitor and workspace, and
we still need to have the information about the original monitor,
workspace and container in order to make comparisons and ultimately
remove the origin container to be able to transfer it.

fix #58
2021-10-29 09:32:36 -07:00
LGUG2Z
14e63292e1 chore(deps): bump windows from 0.22 to 0.23 2021-10-28 14:15:21 -07:00
LGUG2Z
18f34babfa chore(deps): bump windows from 0.21 to 0.22
This commit bumps the version of the windows-rs and deprecates the
bindings crate in favour of using the pre-packaged APIs that are
available as of 0.22.
2021-10-28 09:01:10 -07:00
LGUG2Z
2f7ae6f15f build(just): add justfile for common tasks 2021-10-27 19:21:54 -07:00
47 changed files with 4489 additions and 1547 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: LGUG2Z

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]: Short descriptive title"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See bug
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots and Videos**
Add screenshots and videos to help explain your problem.
**Operating System**
Provide the output of `systeminfo | grep "^OS Name\|^OS Version"`
For example:
```
OS Name: Microsoft Windows 11 Pro
OS Version: 10.0.22000 N/A Build 22000
```
**Additional context**
Add any other context about the problem here.
In particular, if you have any other AHK scripts or software running that handle any aspect of window management or manipulation

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEAT]: Short descriptive title"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -28,7 +28,7 @@ jobs:
target:
- x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Prep cargo dirs
@@ -42,7 +42,7 @@ jobs:
echo "TARGET=${{ matrix.target }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
echo "SKIP_TESTS=" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
- name: Cache cargo registry, git trees and binaries
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
@@ -55,7 +55,7 @@ jobs:
echo "::set-output name=rust_hash::$(rustc -Vv | grep commit-hash | awk '{print $2}')"
shell: bash
- name: Cache cargo build
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: target
key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }}
@@ -77,12 +77,14 @@ jobs:
run: |
cargo build --locked --release --target ${{ matrix.target }}
- name: Upload the built artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: komorebi-${{ matrix.target }}
path: |
target/${{ matrix.target }}/release/komorebi.exe
target/${{ matrix.target }}/release/komorebic.exe
target/${{ matrix.target }}/release/komorebi.pdb
target/${{ matrix.target }}/release/komorebic.pdb
retention-days: 7
- name: Generate changelog
if: startsWith(github.ref, 'refs/tags/')
@@ -91,7 +93,7 @@ jobs:
if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi
kokai release --no-emoji --add-links github:commits,issues --ref "$(git tag --points-at HEAD)" >"CHANGELOG.md"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v3
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest

View File

@@ -16,7 +16,7 @@ builds:
hooks:
post:
- mkdir -p dist/windows_amd64
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64\komorebi.exe"
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64_v1\komorebi.exe"
- id: komorebic
main: dummy.go
goos: ["windows"]
@@ -25,7 +25,7 @@ builds:
hooks:
post:
- mkdir -p dist/windows_amd64
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64\komorebic.exe"
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64_v1\komorebic.exe"
archives:
- replacements:
@@ -42,19 +42,4 @@ checksum:
name_template: checksums.txt
changelog:
sort: asc
scoop:
bucket:
owner: LGUG2Z
name: komorebi-bucket
token: "{{ .Env.SCOOP_TOKEN }}"
homepage: https://github.com/LGUG2Z/komorebi
description: A tiling window manager for Windows
license: MIT
pre_install:
- if (Get-Process -Name komorebi -ErrorAction SilentlyContinue) { komorebic stop }
post_install:
- Write-Host "`nRun 'cp $original_dir\komorebi.sample.ahk $Env:UserProfile\komorebi.ahk' to get started with the sample configuration"
- Write-Host "`nRun 'komorebic ahk-library' if you would like to generate an AHK helper library to use in your configuration"
- Write-Host "`nOnce you have a configuration file in place, you can run 'komorebic start' to start the window manager"
sort: asc

872
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,8 @@
[workspace]
members = [
"bindings",
"derive-ahk",
"komorebi",
"komorebi-core",
"komorebic"
"komorebic",
]

411
README.md
View File

@@ -2,7 +2,14 @@
Tiling Window Management for Windows.
![screenshot](https://i.ibb.co/BTqNS45/komorebi.png)
![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/LGUG2Z/komorebi/Windows/master)
![GitHub](https://img.shields.io/github/license/LGUG2Z/komorebi)
![GitHub all releases](https://img.shields.io/github/downloads/LGUG2Z/komorebi/total)
![GitHub commits since latest release (by date) for a branch](https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest/master)
![Discord](https://img.shields.io/discord/898554690126630914?label=discord)
![GitHub Sponsors](https://img.shields.io/github/sponsors/LGUG2Z)
![screenshot](https://user-images.githubusercontent.com/13164844/184027064-f5a6cec2-2865-4d65-a549-a1f1da589abf.png)
## About
@@ -18,10 +25,44 @@ Translations of this document can be found in the project wiki:
- [komorebi 中文用户指南](https://github.com/LGUG2Z/komorebi/wiki/README-zh) (by [@crosstyan](https://github.com/crosstyan))
There is a [Discord server](https://discord.gg/vzBmPm6RkQ) available for _komorebi_-related discussion, help,
There is a [Discord server](https://discord.gg/mGkn66PHkx) available for _komorebi_-related discussion, help,
troubleshooting etc. If you have any specific feature requests or bugs to report, please create an issue in this
repository.
Articles, blog posts, demos, and videos about _komorebi_ can be added to this list by PR:
- [Moving to Windows from Linux Pt 1](https://kvwu.io/posts/moving-to-windows/)
- [Windows 下的现代化平铺窗口管理器 komorebi](https://zhuanlan.zhihu.com/p/455064481)
## GitHub Sponsors Early Access
[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.
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).
## Demonstrations
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
11 with a terminal emulator, a web browser and a code editor. The original
video can be viewed
[here](https://twitter.com/haxibami/status/1501560766578659332).
https://user-images.githubusercontent.com/13164844/163496447-20c3ff0a-c5d8-40d1-9cc8-156c4cebf12e.mp4
[@aik2mlj](https://github.com/aik2mlj) showing _komorebi_ running on Windows 11
with multiple workspaces, terminal emulators, a web browser, and the
[yasb](https://github.com/DenBot/yasb) status bar with the _komorebi_ workspace
widget enabled. The original video can be viewed
[here](https://zhuanlan.zhihu.com/p/455064481).
https://user-images.githubusercontent.com/13164844/163496414-a9cde3d1-b8a7-4a7a-96fb-a8985380bc70.mp4
## Description
_komorebi_ only responds to [WinEvents](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) and the
@@ -83,14 +124,16 @@ PowerShell prompt), and then move the binaries to that directory.
If you use the [Scoop](https://scoop.sh/) command line installer, you can run the following commands to install the
binaries from the latest GitHub Release:
```
scoop bucket add komorebi https://github.com/LGUG2Z/komorebi-bucket
```powershell
scoop bucket add extras
scoop install komorebi
```
If you install _komorebi_ using Scoop, the binaries will automatically be added to your `Path` and a command will be
shown for you to run in order to get started using the sample configuration file.
Thanks to [@sitiom](https://github.com/sitiom) for getting _komorebi_ added to the popular Scoop Extras bucket.
### Building from Source
If you prefer to compile _komorebi_ from source, you will need
@@ -131,8 +174,122 @@ the `AutoHotKey64.exe` executable for AutoHotKey v2 is in your `Path`. If both `
exist in your home directory, only `komorebi.ahk` will be loaded. An example of an AutoHotKey v2 configuration file
for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778dabf693ce9236c57b201cd).
#### Using Different AHK Executables
The preferred way to install AutoHotKey for use with `komorebi` is to install it via `scoop`:
```powershell
scoop install autohotkey
```
If you install AutoHotKey using a different method, the name of the executable file may differ from the name given by
`scoop`, and thus what is expected by default in `komorebi`.
You may override the executables that `komorebi` looks for to launch and reload `komorebi.ahk` configuration files using
by setting one of the following two environment variables depending on which version of AutoHotKey you wish to use:
- `$Env:KOMOREBI_AHK_V1_EXE`
- `$Env:KOMOREBI_AHK_V2_EXE`
Please keep in mind that even when setting a custom executable name using these environment variables, the executables
are still required to be in your `Path`.
### Common First-Time Tips
#### Generating Common Application-Specific Configurations
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)
contains YAML definitions of settings that are known to make tricky
applications behave as expected. These YAML definitions can be used to generate
an AHK file which you can import at the start of your own `komorebi.ahk` file,
leaving you to focus primarily on your desired keybindings and workspace
configurations.
If you have settings for an application that you think should be part of this
curated selection, please open a PR on the configuration repository.
In the event that your PR is not accepted, or if you find there are any
settings that you wish to override, this can easily be done using an override
file.
```powershell
# Clone and enter the repository
git clone https://github.com/LGUG2Z/komorebi-application-specific-configuration.git
cd komorebi-application-specific-configuration
# Use komorebic to generate an AHK file
komorebic.exe ahk-app-specific-configuration applications.yaml
# Application-specific generated configuration written to C:\Users\LGUG2Z\.config\komorebi\komorebi.generated.ahk
#
# You can include the generated configuration at the top of your komorebi.ahk config with this line:
#
# #Include %A_ScriptDir%\komorebi.generated.ahk
# Optionally, provide an override file that follows the same schema as the second argument
komorebic.exe ahk-app-specific-configuration applications.yaml overrides.yaml
```
#### Setting a Custom KOMOREBI_CONFIG_HOME Directory
If you do not want to keep _komorebi_-related files in your `$Env:UserProfile` directory, you can specify a custom directory
by setting the `$Env:KOMOREBI_CONFIG_HOME` environment variable.
For example, to use the `~/.config/komorebi` directory:
```powershell
# Run this command to make sure that the directory has been created
mkdir -p ~/.config/komorebi
# Run this command to open up your PowerShell profile configuration in Notepad
notepad $PROFILE
# Add this line (with your login user!) to the bottom of your PowerShell profile configuration
$Env:KOMOREBI_CONFIG_HOME = 'C:\Users\LGUG2Z\.config\komorebi'
# Save the changes and then reload the PowerShell profile
. $PROFILE
```
If you already have configuration files that you wish to keep, move them to the `~/.config/komorebi` directory.
The next time you run `komorebic start`, any files created by or loaded by _komorebi_ will be placed or expected to
exist in this folder.
#### Adding an Active Window Border
If you would like to add a visual border around the currently focused window, two commands are available:
```powershell
komorebic.exe active-window-border [enable|disable]
komorebic.exe active-window-border-colour [R G B]
```
It is important to note that the active window border will only apply to windows managed by `komorebi`.
#### 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:
```powershell
komorebic.exe container-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
komorebic.exe workspace padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
```
#### Multiple Layout Changes on Startup
Depending on what is in your configuration, when `komorebi` is started, you may experience the layout rapidly being adjusted
with many retile events.
If you would like to avoid this, you can start `komorebi` with a flag which tells `komorebi` to wait until all configuration
has been loaded before listening to and responding to window manager events: `komorebic start --await-configuration`.
If you start `komorebi` with the `--await-configuration` flag, you _must_ send the `komorebic complete-configuration` command
at the end of your `komorebi.ahk` config. The layout will not be updated and `komorebi` will not respond to `komorebic`
commands until this command has been received.
#### Floating Windows
Sometimes you will want a specific application to never be tiled, and instead float all the time. You add add rules to
@@ -168,6 +325,24 @@ komorebic.exe identify-tray-application exe Discord.exe
# komorebic.exe identify-tray-application title [TITLE]
```
#### Microsoft Office Applications
Microsoft Office applications such as Word and Excel require certain configuration options to be set in order to be
managed correctly. Below is an example of configuring Microsoft Word to be managed correctly by _komorebi_.
```powershell
# This only needs to be added once
komorebic.exe float-rule class _WwB
# Repeat these for other office applications such as EXCEL.EXE etc
# Note that the capitalised EXE is important here- double check the
# exact case for the name and the file extension in Task Manager or
# the AHK Window Spy
komorebic.exe identify-layered-application exe WINWORD.EXE
komorebic.exe identify-border-overflow-application exe WINWORD.EXE
```
#### Focus Follows Mouse
`komorebi` supports two focus-follows-mouse implementations; the native Windows Xmouse implementation, which treats the
@@ -188,6 +363,16 @@ passing it as an argument to the `--implementation` flag:
komorebic.exe toggle-focus-follows-mouse --implementation komorebi
```
#### Mouse Follows Focus
By default, the mouse will move to the center of the window when the focus is changed in a given direction. This
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
```
#### 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,
@@ -252,15 +437,38 @@ YAML
```yaml
- column: Secondary
configuration:
Horizontal: 2 # max number of rows,
configuration: !Horizontal 2 # max number of rows
- column: Primary
configuration:
WidthPercentage: 45 # percentage of screen
configuration: !WidthPercentage 50 # percentage of screen
- column: Tertiary
configuration: Horizontal
```
#### 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
threshold of window containers is met.
```powershell
# On the first workspace of the first monitor (0 0)
# When there are one or more window containers visible on the screen (1)
# Use the bsp layout (bsp)
komorebic workspace-layout-rule 0 0 1 bsp
# On the first workspace of the first monitor (0 0)
# When there are five or more window containers visible on the screen (five)
# Use the custom layout stored in the home directory (~/custom.yaml)
komorebic workspace-custom-layout-rule 0 0 5 ~/custom.yaml
```
However, if you add workspace layout rules, you will not be able to manually change the layout of a workspace until all
layout rules for that workspace have been cleared.
```powershell
# If you decide that workspace layout rules are not for you, you can remove them from that same workspace like this
komorebic clear-workspace-layout-rules 0 0
```
## Configuration with `komorebic`
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
@@ -272,69 +480,92 @@ keybindings with. You can run `komorebic.exe <COMMAND> --help` to get a full exp
each command.
```
start Start komorebi.exe as a background process
stop Stop the komorebi.exe process and restore all hidden windows
state Show a JSON representation of the current window manager state
query Query the current window manager state
subscribe Subscribe to komorebi events
unsubscribe Unsubscribe from komorebi events
log Tail komorebi.exe's process logs (cancel with Ctrl-C)
quick-save Quicksave the current resize layout dimensions
quick-load Load the last quicksaved resize layout dimensions
save Save the current resize layout dimensions to a file
load Load the resize layout dimensions from a file
focus Change focus to the window in the specified direction
move Move the focused window in the specified direction
cycle-focus Change focus to the window in the specified cycle direction
cycle-move Move the focused window in the specified cycle direction
stack Stack the focused window in the specified direction
resize Resize the focused window in the specified direction
unstack Unstack the focused window
cycle-stack Cycle the focused stack in the specified cycle direction
move-to-monitor Move the focused window to the specified monitor
move-to-workspace Move the focused window to the specified workspace
send-to-monitor Send the focused window to the specified monitor
send-to-workspace Send the focused window to the specified workspace
focus-monitor Focus the specified monitor
focus-workspace Focus the specified workspace on the focused monitor
cycle-monitor Focus the monitor in the given cycle direction
cycle-workspace Focus the workspace in the given cycle direction
new-workspace Create and append a new workspace on the focused monitor
invisible-borders Set the invisible border dimensions around each window
work-area-offset Set offsets to exclude parts of the work area from tiling
adjust-container-padding Adjust container padding on the focused workspace
adjust-workspace-padding Adjust workspace padding on the focused workspace
change-layout Set the layout on the focused workspace
load-custom-layout Load a custom layout from file for the focused workspace
flip-layout Flip the layout on the focused workspace (BSP only)
promote Promote the focused window to the top of the tree
retile Force the retiling of all managed windows
ensure-workspaces Create at least this many workspaces for the specified monitor
container-padding Set the container padding for the specified workspace
workspace-padding Set the workspace padding for the specified workspace
workspace-layout Set the layout for the specified workspace
workspace-custom-layout Set a custom layout for the specified workspace
workspace-tiling Enable or disable window tiling for the specified workspace
workspace-name Set the workspace name for the specified workspace
toggle-pause Toggle the window manager on and off across all monitors
toggle-tiling Toggle window tiling on the focused workspace
toggle-float Toggle floating mode for the focused window
toggle-monocle Toggle monocle mode for the focused container
toggle-maximize Toggle native maximization for the focused window
restore-windows Restore all hidden windows (debugging command)
manage Force komorebi to manage the focused window
unmanage Unmanage a window that was forcibly managed
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
float-rule Add a rule to always float the specified application
manage-rule Add a rule to always manage the specified application
workspace-rule Add a rule to associate an application with a workspace
identify-tray-application Identify an application that closes to the system tray
identify-border-overflow Identify an application that has overflowing borders
focus-follows-mouse Enable or disable focus follows mouse for the operating system
toggle-focus-follows-mouse Toggle focus follows mouse for the operating system
ahk-library Generate a library of AutoHotKey helper functions
help Print this message or the help of the given subcommand(s)
start Start komorebi.exe as a background process
stop Stop the komorebi.exe process and restore all hidden windows
state Show a JSON representation of the current window manager state
query Query the current window manager state
subscribe Subscribe to komorebi events
unsubscribe Unsubscribe from komorebi events
log Tail komorebi.exe's process logs (cancel with Ctrl-C)
quick-save-resize Quicksave the current resize layout dimensions
quick-load-resize Load the last quicksaved resize layout dimensions
save-resize Save the current resize layout dimensions to a file
load-resize Load the resize layout dimensions from a file
focus Change focus to the window in the specified direction
move Move the focused window in the specified direction
cycle-focus Change focus to the window in the specified cycle direction
cycle-move Move the focused window in the specified cycle direction
stack Stack the focused window in the specified direction
resize-edge Resize the focused window in the specified direction
resize-axis Resize the focused window or primary column along the specified axis
unstack Unstack the focused window
cycle-stack Cycle the focused stack in the specified cycle direction
move-to-monitor Move the focused window to the specified monitor
move-to-workspace Move the focused window to the specified workspace
send-to-monitor Send the focused window to the specified monitor
send-to-workspace Send the focused window to the specified workspace
send-to-monitor-workspace Send the focused window to the specified monitor workspace
focus-monitor Focus the specified monitor
focus-workspace Focus the specified workspace on the focused monitor
focus-monitor-workspace Focus the specified workspace on the target monitor
cycle-monitor Focus the monitor in the given cycle direction
cycle-workspace Focus the workspace in the given cycle direction
move-workspace-to-monitor Move the focused workspace to the specified monitor
new-workspace Create and append a new workspace on the focused monitor
resize-delta Set the resize delta (used by resize-edge and resize-axis)
invisible-borders Set the invisible border dimensions around each window
work-area-offset Set offsets to exclude parts of the work area from tiling
adjust-container-padding Adjust container padding on the focused workspace
adjust-workspace-padding Adjust workspace padding on the focused workspace
change-layout Set the layout on the focused workspace
load-custom-layout Load a custom layout from file for the focused workspace
flip-layout Flip the layout on the focused workspace (BSP only)
promote Promote the focused window to the top of the tree
retile Force the retiling of all managed windows
ensure-workspaces Create at least this many workspaces for the specified monitor
container-padding Set the container padding for the specified workspace
workspace-padding Set the workspace padding for the specified workspace
workspace-layout Set the layout for the specified workspace
workspace-custom-layout Set a custom layout for the specified workspace
workspace-layout-rule Add a dynamic layout rule for the specified workspace
workspace-custom-layout-rule Add a dynamic custom layout for the specified workspace
clear-workspace-layout-rules Clear all dynamic layout rules for the specified workspace
workspace-tiling Enable or disable window tiling for the specified workspace
workspace-name Set the workspace name for the specified workspace
toggle-window-container-behaviour Toggle the behaviour for new windows (stacking or dynamic tiling)
toggle-pause Toggle window tiling on the focused workspace
toggle-tiling Toggle window tiling on the focused workspace
toggle-float Toggle floating mode for the focused window
toggle-monocle Toggle monocle mode for the focused container
toggle-maximize Toggle native maximization for the focused window
restore-windows Restore all hidden windows (debugging command)
manage Force komorebi to manage the focused window
unmanage Unmanage a window that was forcibly managed
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
complete-configuration Signal that the final configuration option has been sent
window-hiding-behaviour Set the window behaviour when switching workspaces / cycling stacks
cross-monitor-move-behaviour Set the behaviour when moving windows across monitor boundaries
toggle-cross-monitor-move-behaviour Toggle the behaviour when moving windows across monitor boundaries
unmanaged-window-operation-behaviour Set the operation behaviour when the focused window is not managed
float-rule Add a rule to always float the specified application
manage-rule Add a rule to always manage the specified application
workspace-rule Add a rule to associate an application with a workspace
identify-object-name-change-application Identify an application that sends EVENT_OBJECT_NAMECHANGE on launch
identify-tray-application Identify an application that closes to the system tray
identify-layered-application Identify an application that has WS_EX_LAYERED, but should still be managed
identify-border-overflow-application Identify an application that has overflowing borders
active-window-border Enable or disable the active window border
active-window-border-colour Set the colour for the active window border
focus-follows-mouse Enable or disable focus follows mouse for the operating system
toggle-focus-follows-mouse Toggle focus follows mouse for the operating system
mouse-follows-focus Enable or disable mouse follows focus on all workspaces
toggle-mouse-follows-focus Toggle mouse follows focus on all workspaces
ahk-library Generate a library of AutoHotKey helper functions
ahk-app-specific-configuration Generate common app-specific configurations and fixes to use in komorebi.ahk
format-app-specific-configuration Format a YAML file for use with the 'ahk-app-specific-configuration' command
notification-schema Generate a JSON Schema of subscription notifications
help Print this message or the help of the given subcommand(s)
```
### AutoHotKey Helper Library for `komorebic`
@@ -354,13 +585,18 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Window stacks
- [x] Cycle through stacked windows
- [x] Change focused window by direction
- [x] Change focused window by direction across monitor boundary
- [x] Move focused window container in direction
- [x] Move focused window container in direction across monitor boundary
- [x] Move focused window container to monitor and follow
- [x] Move focused window container to workspace follow
- [x] Send focused window container to monitor
- [x] Send focused window container to workspace
- [x] Move focused workspace to monitor
- [x] Mouse follows focused container
- [x] Resize window container in direction
- [x] Resize window container on axis
- [x] Set custom resize delta
- [ ] Resize child window containers by split ratio
- [x] Quicksave and quickload layouts with resize dimensions
- [x] Save and load layouts with resize dimensions to/from specific files
@@ -375,6 +611,7 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Main half-width window with horizontal stack layout (`vertical-stack`)
- [x] 2x Main window (half and quarter-width) with horizontal stack layout (`ultrawide-vertical-stack`)
- [x] Load custom layouts from JSON and YAML representations
- [x] Dynamically select layout based on the number of open windows
- [x] Floating rules based on exe name, window title and class
- [x] Workspace rules based on exe name and window class
- [x] Additional manage rules based on exe name and window class
@@ -385,6 +622,7 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Toggle floating windows
- [x] Toggle monocle window
- [x] Toggle native maximization
- [x] Toggle mouse follows focus
- [x] Toggle Xmouse/Windows focus follows mouse implementation
- [x] Toggle Komorebi focus follows mouse implementation (desktop and system tray-aware)
- [x] Toggle automatic tiling
@@ -422,14 +660,14 @@ the IDE for completions and navigation:
## Logs and Debugging
Logs from `komorebi` will be appended to `~/komorebi.log`; this file is never rotated or overwritten, so it will keep
Logs from `komorebi` will be appended to `%LOCALAPPDATA%/komorebi/komorebi.log`; this file is never rotated or overwritten, so it will keep
growing until it is deleted by the user.
Whenever running the `komorebic stop` command or sending a Ctrl-C signal to `komorebi` directly, the `komorebi` process
ensures that all hidden windows are restored before termination.
If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known
to `komorebi` are stored and continuously updated in `~/komorebi.hwnd.json`.
to `komorebi` are stored and continuously updated in `%LOCALAPPDATA%/komorebi//komorebi.hwnd.json`.
### Restoring Windows
@@ -470,16 +708,16 @@ Note that you do not have to include the full path of the named pipe, just the n
If the named pipe exists, `komorebi` will start pushing JSON data of successfully handled events and messages:
```json lines
{"event":{"type":"AddSubscriber","content":"yasb"},"state":{...}}
{"event":{"type":"FocusWindow","content":"Left"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{...}}
{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{...}}
{"event":{"type":"FocusWindow","content":"Right"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}...}
{"event":{"type":"FocusWindow","content":"Down"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{...}}
{"event":{"type":"FocusWindow","content":"Up"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{...}}
{"event":{"type":"AddSubscriber","content":"yasb"},"state":{}}
{"event":{"type":"FocusWindow","content":"Left"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{}}
{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{}}
{"event":{"type":"FocusWindow","content":"Right"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}}
{"event":{"type":"FocusWindow","content":"Down"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{}}
{"event":{"type":"FocusWindow","content":"Up"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}}
```
You may then filter on the `type` key to listen to the events that you are interested in. For a full list of possible
@@ -489,3 +727,10 @@ in `komorebi-core`.
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Python
by [@denBot](https://github.com/denBot) can be
found [here](https://gist.github.com/denBot/4136279812f87819f86d99eba77c1ee0).
### Subscription Event Notification Schema
A [JSON Schema](https://json-schema.org/) of the event notifications emitted to subscribers can be generated with
the `komorebic notification-schema` command. The output of this command can be redirected to the clipboard or a file,
which can be used with services such as [Quicktype](https://app.quicktype.io/) to generate type definitions in different
programming languages.

View File

@@ -1,13 +0,0 @@
[package]
name = "bindings"
version = "0.1.0"
authors = ["Jade Iqbal"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
windows = "0.21"
[build-dependencies]
windows = "0.21"

View File

@@ -1,26 +0,0 @@
fn main() {
windows::build!(
Windows::Win32::Foundation::RECT,
Windows::Win32::Foundation::POINT,
Windows::Win32::Foundation::BOOL,
Windows::Win32::Foundation::PWSTR,
Windows::Win32::Foundation::HWND,
Windows::Win32::Foundation::LPARAM,
// error: `Windows.Win32.Graphics.Dwm.DWMWA_CLOAKED` not found in metadata
Windows::Win32::Graphics::Dwm::*,
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
Windows::Win32::Graphics::Gdi::*,
Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS,
Windows::Win32::System::Threading::PROCESS_NAME_FORMAT,
Windows::Win32::System::Threading::OpenProcess,
Windows::Win32::System::Threading::QueryFullProcessImageNameW,
Windows::Win32::System::Threading::GetCurrentThreadId,
Windows::Win32::System::Threading::AttachThreadInput,
Windows::Win32::System::Threading::GetCurrentProcessId,
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
Windows::Win32::UI::Accessibility::SetWinEventHook,
Windows::Win32::UI::Accessibility::HWINEVENTHOOK,
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
Windows::Win32::UI::WindowsAndMessaging::*,
);
}

View File

@@ -1,4 +0,0 @@
pub use windows::Handle;
pub use windows::Result;
::windows::include_bindings!();

42
justfile Normal file
View File

@@ -0,0 +1,42 @@
set shell := ["cmd.exe", "/C"]
export RUST_BACKTRACE := "full"
clean:
cargo clean
fmt:
cargo +nightly fmt
cargo +stable clippy
prettier --write README.md
install-komorebic:
cargo +stable install --path komorebic --locked
install-komorebi:
cargo +stable install --path komorebi --locked
install:
just install-komorebic
just install-komorebi
komorebic ahk-library
cat '%USERPROFILE%\.config\komorebi\komorebic.lib.ahk' > komorebic.lib.sample.ahk
run:
just install-komorebic
cargo +stable run --bin komorebi --locked
warn $RUST_LOG="warn":
just run
info $RUST_LOG="info":
just run
debug $RUST_LOG="debug":
just run
trace $RUST_LOG="trace":
just run
deadlock $RUST_LOG="trace":
just install-komorebic
cargo +stable run --bin komorebi --locked --features deadlock_detection

View File

@@ -1,16 +1,21 @@
[package]
name = "komorebi-core"
version = "0.1.6"
version = "0.1.11"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bindings = { package = "bindings", path = "../bindings" }
clap = "3.0.0-beta.4"
color-eyre = "0.5"
clap = { version = "3", features = ["derive"] }
color-eyre = "0.6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.8"
strum = { version = "0.21", features = ["derive"] }
serde_yaml = "0.9"
strum = { version = "0.24", features = ["derive"] }
schemars = "0.8"
[dependencies.windows]
version = "0.39"
features = [
"Win32_Foundation",
]

View File

@@ -1,6 +1,7 @@
use std::num::NonZeroUsize;
use clap::ArgEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -19,7 +20,7 @@ pub trait Arrangement {
area: &Rect,
len: NonZeroUsize,
container_padding: Option<i32>,
layout_flip: Option<Flip>,
layout_flip: Option<Axis>,
resize_dimensions: &[Option<Rect>],
) -> Vec<Rect>;
}
@@ -31,21 +32,21 @@ impl Arrangement for DefaultLayout {
area: &Rect,
len: NonZeroUsize,
container_padding: Option<i32>,
layout_flip: Option<Flip>,
layout_flip: Option<Axis>,
resize_dimensions: &[Option<Rect>],
) -> Vec<Rect> {
let len = usize::from(len);
let mut dimensions = match self {
DefaultLayout::BSP => recursive_fibonacci(
Self::BSP => recursive_fibonacci(
0,
len,
area,
layout_flip,
calculate_resize_adjustments(resize_dimensions),
),
DefaultLayout::Columns => columns(area, len),
DefaultLayout::Rows => rows(area, len),
DefaultLayout::VerticalStack => {
Self::Columns => columns(area, len),
Self::Rows => rows(area, len),
Self::VerticalStack => {
let mut layouts: Vec<Rect> = vec![];
let primary_right = match len {
@@ -57,7 +58,7 @@ impl Arrangement for DefaultLayout {
let mut stack_left = area.left + primary_right;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
main_left = main_left + area.right - primary_right;
stack_left = area.left;
}
@@ -87,7 +88,7 @@ impl Arrangement for DefaultLayout {
layouts
}
DefaultLayout::HorizontalStack => {
Self::HorizontalStack => {
let mut layouts: Vec<Rect> = vec![];
let bottom = match len {
@@ -99,7 +100,7 @@ impl Arrangement for DefaultLayout {
let mut stack_top = area.top + bottom;
match layout_flip {
Some(Flip::Vertical | Flip::HorizontalAndVertical) if len > 1 => {
Some(Axis::Vertical | Axis::HorizontalAndVertical) if len > 1 => {
main_top = main_top + area.bottom - bottom;
stack_top = area.top;
}
@@ -129,7 +130,7 @@ impl Arrangement for DefaultLayout {
layouts
}
DefaultLayout::UltrawideVerticalStack => {
Self::UltrawideVerticalStack => {
let mut layouts: Vec<Rect> = vec![];
let primary_right = match len {
@@ -150,7 +151,7 @@ impl Arrangement for DefaultLayout {
let mut secondary = area.left;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
primary = area.left;
secondary = area.left + primary_right;
}
@@ -165,7 +166,7 @@ impl Arrangement for DefaultLayout {
let mut stack = area.left + primary_right + secondary_right;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
secondary = area.left + primary_right + secondary_right;
stack = area.left;
}
@@ -224,7 +225,7 @@ impl Arrangement for CustomLayout {
area: &Rect,
len: NonZeroUsize,
container_padding: Option<i32>,
_layout_flip: Option<Flip>,
_layout_flip: Option<Axis>,
_resize_dimensions: &[Option<Rect>],
) -> Vec<Rect> {
let mut dimensions = vec![];
@@ -341,9 +342,9 @@ impl Arrangement for CustomLayout {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum Flip {
pub enum Axis {
Horizontal,
Vertical,
HorizontalAndVertical,
@@ -474,11 +475,12 @@ fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Optio
cleaned_resize_adjustments
}
#[allow(clippy::only_used_in_recursion)]
fn recursive_fibonacci(
idx: usize,
count: usize,
area: &Rect,
layout_flip: Option<Flip>,
layout_flip: Option<Axis>,
resize_adjustments: Vec<Option<Rect>>,
) -> Vec<Rect> {
let mut a = *area;
@@ -502,21 +504,21 @@ fn recursive_fibonacci(
if let Some(flip) = layout_flip {
match flip {
Flip::Horizontal => {
Axis::Horizontal => {
main_x = resized.left + half_width + (half_width - half_resized_width);
alt_x = resized.left;
alt_y = resized.top + half_resized_height;
main_y = resized.top;
}
Flip::Vertical => {
Axis::Vertical => {
main_y = resized.top + half_height + (half_height - half_resized_height);
alt_y = resized.top;
main_x = resized.left;
alt_x = resized.left + half_resized_width;
}
Flip::HorizontalAndVertical => {
Axis::HorizontalAndVertical => {
main_x = resized.left + half_width + (half_width - half_resized_width);
alt_x = resized.left;
main_y = resized.top + half_height + (half_height - half_resized_height);

View File

@@ -0,0 +1,176 @@
use clap::ArgEnum;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use crate::ApplicationIdentifier;
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationOptions {
ObjectNameChange,
Layered,
BorderOverflow,
TrayAndMultiWindow,
Force,
}
impl ApplicationOptions {
#[must_use]
pub fn cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
format!(
"Run, {}, , Hide",
match self {
ApplicationOptions::ObjectNameChange => {
format!(
"komorebic.exe identify-object-name-change-application {} \"{}\"",
kind, id
)
}
ApplicationOptions::Layered => {
format!(
"komorebic.exe identify-layered-application {} \"{}\"",
kind, id
)
}
ApplicationOptions::BorderOverflow => {
format!(
"komorebic.exe identify-border-overflow-application {} \"{}\"",
kind, id
)
}
ApplicationOptions::TrayAndMultiWindow => {
format!(
"komorebic.exe identify-tray-application {} \"{}\"",
kind, id
)
}
ApplicationOptions::Force => {
format!("komorebic.exe manage-rule {} \"{}\"", kind, id)
}
}
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifier {
kind: ApplicationIdentifier,
id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifierAndComment {
kind: ApplicationIdentifier,
id: String,
#[serde(skip_serializing_if = "Option::is_none")]
comment: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationConfiguration {
name: String,
identifier: IdWithIdentifier,
#[serde(skip_serializing_if = "Option::is_none")]
options: Option<Vec<ApplicationOptions>>,
#[serde(skip_serializing_if = "Option::is_none")]
float_identifiers: Option<Vec<IdWithIdentifierAndComment>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationConfigurationGenerator;
impl ApplicationConfigurationGenerator {
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)?;
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
Ok(serde_yaml::to_string(&cfgen)?)
}
fn merge(base_content: &str, override_content: &str) -> Result<Vec<ApplicationConfiguration>> {
let base_cfgen = Self::load(base_content)?;
let override_cfgen = Self::load(override_content)?;
let mut final_cfgen = base_cfgen.clone();
for entry in override_cfgen {
let mut replace_idx = None;
for (idx, base_entry) in base_cfgen.iter().enumerate() {
if base_entry.name == entry.name {
replace_idx = Option::from(idx);
}
}
match replace_idx {
None => final_cfgen.push(entry),
Some(idx) => final_cfgen[idx] = entry,
}
}
Ok(final_cfgen)
}
pub fn generate_ahk(base_content: &str, override_content: Option<&str>) -> Result<Vec<String>> {
let mut cfgen = if let Some(override_content) = override_content {
Self::merge(base_content, override_content)?
} else {
Self::load(base_content)?
};
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::from("")
];
let mut float_rules = vec![];
for app in cfgen {
lines.push(format!("; {}", app.name));
if let Some(options) = app.options {
for opt in options {
if let ApplicationOptions::TrayAndMultiWindow = opt {
lines.push(String::from("; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
}
lines.push(opt.cfgen(&app.identifier.kind, &app.identifier.id));
}
}
if let Some(float_identifiers) = app.float_identifiers {
for float in float_identifiers {
let float_rule = format!(
"Run, komorebic.exe float-rule {} \"{}\", , Hide",
float.kind, float.id
);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
if let Some(comment) = float.comment {
lines.push(format!("; {}", comment));
};
lines.push(float_rule);
}
}
}
lines.push(String::from(""));
}
Ok(lines)
}
}

View File

@@ -2,16 +2,18 @@ use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::PathBuf;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::Rect;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct CustomLayout(Vec<Column>);
impl Deref for CustomLayout {
@@ -22,6 +24,12 @@ impl Deref for CustomLayout {
}
}
impl DerefMut for CustomLayout {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl CustomLayout {
pub fn from_path_buf(path: PathBuf) -> Result<Self> {
let invalid_filetype = anyhow!("custom layouts must be json or yaml files");
@@ -75,6 +83,14 @@ impl CustomLayout {
None
}
pub fn set_primary_width_percentage(&mut self, percentage: usize) {
for column in self.iter_mut() {
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(current))) = column {
*current = percentage;
}
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
// A valid layout must have at least one column
@@ -236,7 +252,7 @@ impl CustomLayout {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "column", content = "configuration")]
pub enum Column {
Primary(Option<ColumnWidth>),
@@ -244,18 +260,18 @@ pub enum Column {
Tertiary(ColumnSplit),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnWidth {
WidthPercentage(usize),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnSplit {
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnSplitWithCapacity {
Horizontal(usize),
Vertical(usize),

View File

@@ -1,12 +1,13 @@
use std::num::NonZeroUsize;
use clap::ArgEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum CycleDirection {
Previous,

View File

@@ -1,4 +1,5 @@
use clap::ArgEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -8,7 +9,7 @@ use crate::OperationDirection;
use crate::Rect;
use crate::Sizing;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum DefaultLayout {
BSP,
@@ -21,14 +22,14 @@ pub enum DefaultLayout {
impl DefaultLayout {
#[must_use]
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::cast_precision_loss, clippy::only_used_in_recursion)]
pub fn resize(
&self,
unaltered: &Rect,
resize: &Option<Rect>,
edge: OperationDirection,
sizing: Sizing,
step: Option<i32>,
delta: i32,
) -> Option<Rect> {
if !matches!(self, Self::BSP) {
return None;
@@ -37,7 +38,7 @@ impl DefaultLayout {
let max_divisor = 1.005;
let mut r = resize.unwrap_or_default();
let resize_step = step.unwrap_or(50);
let resize_delta = delta;
match edge {
OperationDirection::Left => match sizing {
@@ -52,65 +53,65 @@ impl DefaultLayout {
// with index 0. I don't think it's worth trying to defensively program
// against this; if people end up in this situation they are better off
// just hitting the retile command
let diff = ((r.left + -resize_step) as f32).abs();
let diff = ((r.left + -resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.left += -resize_step;
r.left += -resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.left - -resize_step) as f32).abs();
let diff = ((r.left - -resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.left -= -resize_step;
r.left -= -resize_delta;
}
}
},
OperationDirection::Up => match sizing {
Sizing::Increase => {
let diff = ((r.top + resize_step) as f32).abs();
let diff = ((r.top + resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.top += -resize_step;
r.top += -resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.top - resize_step) as f32).abs();
let diff = ((r.top - resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.top -= -resize_step;
r.top -= -resize_delta;
}
}
},
OperationDirection::Right => match sizing {
Sizing::Increase => {
let diff = ((r.right + resize_step) as f32).abs();
let diff = ((r.right + resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.right += resize_step;
r.right += resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.right - resize_step) as f32).abs();
let diff = ((r.right - resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.right -= resize_step;
r.right -= resize_delta;
}
}
},
OperationDirection::Down => match sizing {
Sizing::Increase => {
let diff = ((r.bottom + resize_step) as f32).abs();
let diff = ((r.bottom + resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.bottom += resize_step;
r.bottom += resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.bottom - resize_step) as f32).abs();
let diff = ((r.bottom - resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.bottom -= resize_step;
r.bottom -= resize_delta;
}
}
},

View File

@@ -72,34 +72,34 @@ impl Direction for DefaultLayout {
) -> bool {
match op_direction {
OperationDirection::Up => match self {
DefaultLayout::BSP => count > 2 && idx != 0 && idx != 1,
DefaultLayout::Columns => false,
DefaultLayout::Rows | DefaultLayout::HorizontalStack => idx != 0,
DefaultLayout::VerticalStack => idx != 0 && idx != 1,
DefaultLayout::UltrawideVerticalStack => idx > 2,
Self::BSP => count > 2 && idx != 0 && idx != 1,
Self::Columns => false,
Self::Rows | Self::HorizontalStack => idx != 0,
Self::VerticalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx > 2,
},
OperationDirection::Down => match self {
DefaultLayout::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
DefaultLayout::Columns => false,
DefaultLayout::Rows => idx != count - 1,
DefaultLayout::VerticalStack => idx != 0 && idx != count - 1,
DefaultLayout::HorizontalStack => idx == 0,
DefaultLayout::UltrawideVerticalStack => idx > 1 && idx != count - 1,
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
Self::Columns => false,
Self::Rows => idx != count - 1,
Self::VerticalStack => idx != 0 && idx != count - 1,
Self::HorizontalStack => idx == 0,
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
},
OperationDirection::Left => match self {
DefaultLayout::BSP => count > 1 && idx != 0,
DefaultLayout::Columns | DefaultLayout::VerticalStack => idx != 0,
DefaultLayout::Rows => false,
DefaultLayout::HorizontalStack => idx != 0 && idx != 1,
DefaultLayout::UltrawideVerticalStack => count > 1 && idx != 1,
Self::BSP => count > 1 && idx != 0,
Self::Columns | Self::VerticalStack => idx != 0,
Self::Rows => false,
Self::HorizontalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => count > 1 && idx != 1,
},
OperationDirection::Right => match self {
DefaultLayout::BSP => count > 1 && idx % 2 == 0 && idx != count - 1,
DefaultLayout::Columns => idx != count - 1,
DefaultLayout::Rows => false,
DefaultLayout::VerticalStack => idx == 0,
DefaultLayout::HorizontalStack => idx != 0 && idx != count - 1,
DefaultLayout::UltrawideVerticalStack => match count {
Self::BSP => count > 1 && idx % 2 == 0 && idx != count - 1,
Self::Columns => idx != count - 1,
Self::Rows => false,
Self::VerticalStack => idx == 0,
Self::HorizontalStack => idx != 0 && idx != count - 1,
Self::UltrawideVerticalStack => match count {
0 | 1 => false,
2 => idx != 0,
_ => idx < 2,
@@ -110,45 +110,40 @@ impl Direction for DefaultLayout {
fn up_index(&self, idx: usize) -> usize {
match self {
DefaultLayout::BSP => {
Self::BSP => {
if idx % 2 == 0 {
idx - 1
} else {
idx - 2
}
}
DefaultLayout::Columns => unreachable!(),
DefaultLayout::Rows
| DefaultLayout::VerticalStack
| DefaultLayout::UltrawideVerticalStack => idx - 1,
DefaultLayout::HorizontalStack => 0,
Self::Columns => unreachable!(),
Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx - 1,
Self::HorizontalStack => 0,
}
}
fn down_index(&self, idx: usize) -> usize {
match self {
DefaultLayout::BSP
| DefaultLayout::Rows
| DefaultLayout::VerticalStack
| DefaultLayout::UltrawideVerticalStack => idx + 1,
DefaultLayout::Columns => unreachable!(),
DefaultLayout::HorizontalStack => 1,
Self::BSP | Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx + 1,
Self::Columns => unreachable!(),
Self::HorizontalStack => 1,
}
}
fn left_index(&self, idx: usize) -> usize {
match self {
DefaultLayout::BSP => {
Self::BSP => {
if idx % 2 == 0 {
idx - 2
} else {
idx - 1
}
}
DefaultLayout::Columns | DefaultLayout::HorizontalStack => idx - 1,
DefaultLayout::Rows => unreachable!(),
DefaultLayout::VerticalStack => 0,
DefaultLayout::UltrawideVerticalStack => match idx {
Self::Columns | Self::HorizontalStack => idx - 1,
Self::Rows => unreachable!(),
Self::VerticalStack => 0,
Self::UltrawideVerticalStack => match idx {
0 => 1,
1 => unreachable!(),
_ => 0,
@@ -158,10 +153,10 @@ impl Direction for DefaultLayout {
fn right_index(&self, idx: usize) -> usize {
match self {
DefaultLayout::BSP | DefaultLayout::Columns | DefaultLayout::HorizontalStack => idx + 1,
DefaultLayout::Rows => unreachable!(),
DefaultLayout::VerticalStack => 1,
DefaultLayout::UltrawideVerticalStack => match idx {
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
Self::Rows => unreachable!(),
Self::VerticalStack => 1,
Self::UltrawideVerticalStack => match idx {
1 => 0,
0 => 2,
_ => unreachable!(),

View File

@@ -1,3 +1,4 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -6,7 +7,7 @@ use crate::CustomLayout;
use crate::DefaultLayout;
use crate::Direction;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum Layout {
Default(DefaultLayout),
Custom(CustomLayout),

View File

@@ -1,18 +1,19 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_errors_doc, clippy::use_self)]
use std::path::PathBuf;
use std::str::FromStr;
use clap::ArgEnum;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
pub use arrangement::Arrangement;
pub use arrangement::Flip;
pub use arrangement::Axis;
pub use custom_layout::CustomLayout;
pub use cycle_direction::CycleDirection;
pub use default_layout::DefaultLayout;
@@ -22,6 +23,7 @@ pub use operation_direction::OperationDirection;
pub use rect::Rect;
pub mod arrangement;
pub mod config_generation;
pub mod custom_layout;
pub mod cycle_direction;
pub mod default_layout;
@@ -30,7 +32,7 @@ pub mod layout;
pub mod operation_direction;
pub mod rect;
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum SocketMessage {
// Window / Container Commands
@@ -39,17 +41,25 @@ pub enum SocketMessage {
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
ResizeWindow(OperationDirection, Sizing),
ResizeWindowEdge(OperationDirection, Sizing),
ResizeWindowAxis(Axis, Sizing),
UnstackWindow,
CycleStack(CycleDirection),
MoveContainerToMonitorNumber(usize),
MoveContainerToWorkspaceNumber(usize),
SendContainerToMonitorNumber(usize),
SendContainerToWorkspaceNumber(usize),
SendContainerToMonitorWorkspaceNumber(usize, usize),
MoveWorkspaceToMonitorNumber(usize),
Promote,
ToggleFloat,
ToggleMonocle,
ToggleMaximize,
ToggleWindowContainerBehaviour,
WindowHidingBehaviour(HidingBehaviour),
ToggleCrossMonitorMoveBehaviour,
CrossMonitorMoveBehaviour(MoveBehaviour),
UnmanagedWindowOperationBehaviour(OperationBehaviour),
// Current Workspace Commands
ManageFocusedWindow,
UnmanageFocusedWindow,
@@ -57,7 +67,7 @@ pub enum SocketMessage {
AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout),
ChangeLayoutCustom(PathBuf),
FlipLayout(Flip),
FlipLayout(Axis),
// Monitor and Workspace Commands
EnsureWorkspaces(usize, usize),
NewWorkspace,
@@ -73,28 +83,41 @@ pub enum SocketMessage {
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusWorkspaceNumber(usize),
FocusMonitorWorkspaceNumber(usize, usize),
ContainerPadding(usize, usize, i32),
WorkspacePadding(usize, usize, i32),
WorkspaceTiling(usize, usize, bool),
WorkspaceName(usize, usize, String),
WorkspaceLayout(usize, usize, DefaultLayout),
WorkspaceLayoutCustom(usize, usize, PathBuf),
WorkspaceLayoutRule(usize, usize, usize, DefaultLayout),
WorkspaceLayoutCustomRule(usize, usize, usize, PathBuf),
ClearWorkspaceLayoutRules(usize, usize),
// Configuration
ReloadConfiguration,
WatchConfiguration(bool),
CompleteConfiguration,
ActiveWindowBorder(bool),
ActiveWindowBorderColour(u32, u32, u32),
InvisibleBorders(Rect),
WorkAreaOffset(Rect),
ResizeDelta(i32),
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
FloatRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),
IdentifyObjectNameChangeApplication(ApplicationIdentifier, String),
IdentifyTrayApplication(ApplicationIdentifier, String),
IdentifyBorderOverflow(ApplicationIdentifier, String),
IdentifyLayeredApplication(ApplicationIdentifier, String),
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
State,
Query(StateQuery),
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
MouseFollowsFocus(bool),
ToggleMouseFollowsFocus,
AddSubscriber(String),
RemoveSubscriber(String),
NotificationSchema,
}
impl SocketMessage {
@@ -111,7 +134,7 @@ impl FromStr for SocketMessage {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum StateQuery {
FocusedMonitorIndex,
@@ -120,22 +143,51 @@ pub enum StateQuery {
FocusedWindowIndex,
}
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationIdentifier {
Exe,
Class,
Title,
}
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum FocusFollowsMouseImplementation {
Komorebi,
Windows,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum WindowContainerBehaviour {
Create,
Append,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum MoveBehaviour {
Swap,
Insert,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum HidingBehaviour {
Hide,
Minimize,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum OperationBehaviour {
Op,
NoOp,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum Sizing {
Increase,
@@ -146,8 +198,8 @@ impl Sizing {
#[must_use]
pub const fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {
match self {
Sizing::Increase => value + adjustment,
Sizing::Decrease => {
Self::Increase => value + adjustment,
Self::Decrease => {
if value > 0 && value - adjustment >= 0 {
value - adjustment
} else {

View File

@@ -1,15 +1,16 @@
use std::num::NonZeroUsize;
use clap::ArgEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use crate::direction::Direction;
use crate::Flip;
use crate::Axis;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum OperationDirection {
Left,
@@ -29,23 +30,23 @@ impl OperationDirection {
}
}
fn flip(self, layout_flip: Option<Flip>) -> Self {
fn flip(self, layout_flip: Option<Axis>) -> Self {
layout_flip.map_or(self, |flip| match self {
Self::Left => match flip {
Flip::Horizontal | Flip::HorizontalAndVertical => Self::Right,
Flip::Vertical => self,
Axis::Horizontal | Axis::HorizontalAndVertical => Self::Right,
Axis::Vertical => self,
},
Self::Right => match flip {
Flip::Horizontal | Flip::HorizontalAndVertical => Self::Left,
Flip::Vertical => self,
Axis::Horizontal | Axis::HorizontalAndVertical => Self::Left,
Axis::Vertical => self,
},
Self::Up => match flip {
Flip::Vertical | Flip::HorizontalAndVertical => Self::Down,
Flip::Horizontal => self,
Axis::Vertical | Axis::HorizontalAndVertical => Self::Down,
Axis::Horizontal => self,
},
Self::Down => match flip {
Flip::Vertical | Flip::HorizontalAndVertical => Self::Up,
Flip::Horizontal => self,
Axis::Vertical | Axis::HorizontalAndVertical => Self::Up,
Axis::Horizontal => self,
},
})
}
@@ -54,7 +55,7 @@ impl OperationDirection {
pub fn destination(
self,
layout: &dyn Direction,
layout_flip: Option<Flip>,
layout_flip: Option<Axis>,
idx: usize,
len: NonZeroUsize,
) -> Option<usize> {

View File

@@ -1,9 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use windows::Win32::Foundation::RECT;
use bindings::Windows::Win32::Foundation::RECT;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
pub struct Rect {
pub left: i32,
pub top: i32,

View File

@@ -1,6 +1,9 @@
#SingleInstance Force
#Include %A_ScriptDir%\komorebic.lib.ahk
; Default to minimizing windows when switching workspaces
WindowHidingBehaviour("minimize")
; Enable hot reloading of changes to this file
WatchConfiguration("enable")
@@ -34,7 +37,7 @@ WorkspaceTiling(0, 4, "disable") ; Everything floats here
; Configure floating rules
FloatRule("class", "SunAwtDialog") ; All the IntelliJ popups
FloatRule("title", "Control Panek")
FloatRule("title", "Control Panel")
FloatRule("class", "TaskManagerWindow")
FloatRule("exe", "Wally.exe")
FloatRule("exe", "wincompose.exe")

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.6"
version = "0.1.11"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -11,12 +11,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bindings = { package = "bindings", path = "../bindings" }
komorebi-core = { path = "../komorebi-core" }
bitflags = "1"
clap = "3.0.0-beta.4"
color-eyre = "0.5"
clap = { version = "3", features = ["derive"] }
color-eyre = "0.6"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = "3"
@@ -24,21 +23,40 @@ dirs = "4"
getset = "0.1"
hotwatch = "0.4"
lazy_static = "1"
miow = "0.4"
nanoid = "0.4"
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
os_info = "3.4"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
paste = "1"
schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.21", features = ["derive"] }
sysinfo = "0.20"
strum = { version = "0.24", features = ["derive"] }
sysinfo = "0.25"
tracing = "0.1"
tracing-appender = "0.1"
tracing-subscriber = "0.2"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uds_windows = "1"
which = "4"
winput = "0.2"
winvd = "0.0.20"
miow = "0.3"
winreg = "0.10"
[dependencies.windows]
version = "0.39"
features = [
"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"
]
[features]
deadlock_detection = []

119
komorebi/src/border.rs Normal file
View File

@@ -0,0 +1,119 @@
use std::sync::atomic::Ordering;
use std::time::Duration;
use color_eyre::Result;
use komorebi_core::Rect;
use windows::core::PCSTR;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA;
use windows::Win32::UI::WindowsAndMessaging::FindWindowA;
use windows::Win32::UI::WindowsAndMessaging::GetMessageA;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use crate::window::Window;
use crate::windows_callbacks;
use crate::WindowsApi;
use crate::BORDER_HWND;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::WINDOWS_11;
#[derive(Debug, Clone, Copy)]
pub struct Border {
pub(crate) hwnd: isize,
}
impl From<isize> for Border {
fn from(hwnd: isize) -> Self {
Self { hwnd }
}
}
impl Border {
pub const fn hwnd(self) -> HWND {
HWND(self.hwnd)
}
pub fn create(name: &str) -> Result<()> {
let name = format!("{name}\0");
let instance = WindowsApi::module_handle_w()?;
let class_name = PCSTR(name.as_ptr());
let brush = WindowsApi::create_solid_brush(255, 140, 0);
let window_class = WNDCLASSA {
hInstance: instance,
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(windows_callbacks::border_window),
hbrBackground: brush,
..Default::default()
};
let _atom = WindowsApi::register_class_a(&window_class)?;
let name_cl = name.clone();
std::thread::spawn(move || -> Result<()> {
let hwnd = WindowsApi::create_border_window(PCSTR(name_cl.as_ptr()), instance)?;
let border = Self::from(hwnd);
let mut message = MSG::default();
unsafe {
while GetMessageA(&mut message, border.hwnd(), 0, 0).into() {
DispatchMessageA(&message);
std::thread::sleep(Duration::from_millis(10));
}
}
Ok(())
});
let mut hwnd = HWND(0);
while hwnd == HWND(0) {
hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) };
}
BORDER_HWND.store(hwnd.0, Ordering::SeqCst);
if *WINDOWS_11 {
WindowsApi::round_corners(hwnd.0)?;
}
Ok(())
}
pub fn hide(self) -> Result<()> {
WindowsApi::hide_border_window(self.hwnd())
}
pub fn set_position(
self,
window: Window,
invisible_borders: &Rect,
activate: bool,
) -> Result<()> {
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;
}
if should_expand_border {
rect.left -= invisible_borders.left;
rect.top -= invisible_borders.top;
rect.right += invisible_borders.right;
rect.bottom += invisible_borders.bottom;
}
WindowsApi::position_border_window(self.hwnd(), &rect, activate)
}
}

View File

@@ -2,12 +2,13 @@ use std::collections::VecDeque;
use getset::Getters;
use nanoid::nanoid;
use schemars::JsonSchema;
use serde::Serialize;
use crate::ring::Ring;
use crate::window::Window;
#[derive(Debug, Clone, Serialize, Getters)]
#[derive(Debug, Clone, Serialize, Getters, JsonSchema)]
pub struct Container {
#[serde(skip_serializing)]
#[getset(get = "pub")]
@@ -78,18 +79,18 @@ impl Container {
}
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
self.windows_mut().remove(idx)
let window = self.windows_mut().remove(idx);
if idx != 0 {
self.focus_window(idx - 1);
};
window
}
pub fn remove_focused_window(&mut self) -> Option<Window> {
let focused_idx = self.focused_window_idx();
let window = self.remove_window_by_idx(focused_idx);
if focused_idx != 0 {
self.focus_window(focused_idx - 1);
}
window
self.remove_window_by_idx(focused_idx)
}
pub fn add_window(&mut self, window: Window) {

View File

@@ -1,36 +1,46 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_errors_doc, clippy::redundant_pub_crate)]
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[cfg(feature = "deadlock_detection")]
use std::thread;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use clap::Clap;
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 lazy_static::lazy_static;
use os_info::Version;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Serialize;
use sysinfo::Process;
use sysinfo::ProcessExt;
use sysinfo::SystemExt;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use which::which;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use komorebi_core::HidingBehaviour;
use komorebi_core::SocketMessage;
use crate::border::Border;
use crate::process_command::listen_for_commands;
use crate::process_event::listen_for_events;
use crate::process_movement::listen_for_movements;
@@ -42,6 +52,7 @@ use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;
mod border;
mod container;
mod monitor;
mod process_command;
@@ -60,7 +71,7 @@ mod workspace;
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
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>>> =
Arc::new(Mutex::new(vec![
@@ -78,7 +89,12 @@ lazy_static! {
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
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 FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = 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()
]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"X410.exe".to_string(),
@@ -87,9 +103,62 @@ lazy_static! {
]));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
Arc::new(Mutex::new(HidingBehaviour::Minimize));
static ref HOME_DIR: PathBuf = {
if let Ok(home_path) = std::env::var("KOMOREBI_CONFIG_HOME") {
let home = PathBuf::from(&home_path);
if home.as_path().is_dir() {
home
} else {
panic!(
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
}
} else {
dirs::home_dir().expect("there is no home directory")
}
};
static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
static ref AHK_V1_EXE: String = {
let mut ahk_v1: String = String::from("autohotkey.exe");
if let Ok(komorebi_ahk_v1_exe) = std::env::var("KOMOREBI_AHK_V1_EXE") {
if which(&komorebi_ahk_v1_exe).is_ok() {
ahk_v1 = komorebi_ahk_v1_exe;
}
}
ahk_v1
};
static ref AHK_V2_EXE: String = {
let mut ahk_v2: String = String::from("AutoHotkey64.exe");
if let Ok(komorebi_ahk_v2_exe) = std::env::var("KOMOREBI_AHK_V2_EXE") {
if which(&komorebi_ahk_v2_exe).is_ok() {
ahk_v2 = komorebi_ahk_v2_exe;
}
}
ahk_v2
};
static ref WINDOWS_11: bool = {
matches!(
os_info::get().version(),
Version::Semantic(_, _, x) if x >= &22000
)
};
}
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);
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false);
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
@@ -102,8 +171,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
std::env::set_var("RUST_LOG", "info");
}
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let appender = tracing_appender::rolling::never(home, "komorebi.log");
let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "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);
@@ -133,27 +201,30 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
// occurred to be recorded.
std::panic::set_hook(Box::new(|panic| {
// If the panic has a source location, record it as structured fields.
if let Some(location) = panic.location() {
// On nightly Rust, where the `PanicInfo` type also exposes a
// `message()` method returning just the message, we could record
// just the message instead of the entire `fmt::Display`
// implementation, avoiding the duplciated location
tracing::error!(
message = %panic,
panic.file = location.file(),
panic.line = location.line(),
panic.column = location.column(),
);
} else {
tracing::error!(message = %panic);
}
panic.location().map_or_else(
|| {
tracing::error!(message = %panic);
},
|location| {
// On nightly Rust, where the `PanicInfo` type also exposes a
// `message()` method returning just the message, we could record
// just the message instead of the entire `fmt::Display`
// implementation, avoiding the duplciated location
tracing::error!(
message = %panic,
panic.file = location.file(),
panic.line = location.line(),
panic.column = location.column(),
);
},
);
}));
Ok((guard, color_guard))
}
pub fn load_configuration() -> Result<()> {
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let home = HOME_DIR.clone();
let mut config_v1 = home.clone();
config_v1.push("komorebi.ahk");
@@ -161,7 +232,7 @@ pub fn load_configuration() -> Result<()> {
let mut config_v2 = home;
config_v2.push("komorebi.ahk2");
if config_v1.exists() && which("autohotkey.exe").is_ok() {
if config_v1.exists() && which(&*AHK_V1_EXE).is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v1
@@ -173,7 +244,7 @@ pub fn load_configuration() -> Result<()> {
Command::new("autohotkey.exe")
.arg(config_v1.as_os_str())
.output()?;
} else if config_v2.exists() && which("AutoHotkey64.exe").is_ok() {
} else if config_v2.exists() && which(&*AHK_V2_EXE).is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v2
@@ -185,19 +256,64 @@ pub fn load_configuration() -> Result<()> {
Command::new("AutoHotkey64.exe")
.arg(config_v2.as_os_str())
.output()?;
} else {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
};
Ok(())
}
#[derive(Debug, Serialize)]
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
// This is the path on Windows 10
let mut current = hkcu
.open_subkey(format!(
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
SESSION_ID.load(Ordering::SeqCst)
))
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
// This is the path on Windows 11
if current.is_none() {
current = hkcu
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
}
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
// exist until one has been created in the task view
// The registry value will also not exist on user login if virtual desktops have been created
// but the task view has not been initiated
// In both of these cases, we return None, and the virtual desktop validation will never run. In
// the latter case, if the user desires this validation after initiating the task view, komorebi
// should be restarted, and then when this // fn runs again for the first time, it will pick up
// the value of CurrentVirtualDesktop and validate against it accordingly
current
}
#[derive(Debug, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
Socket(SocketMessage),
}
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, JsonSchema)]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,
@@ -241,9 +357,9 @@ pub fn notify_subscribers(notification: &str) -> Result<()> {
#[tracing::instrument]
fn detect_deadlocks() {
// Create a background thread which checks for deadlocks every 10s
thread::spawn(move || loop {
std::thread::spawn(move || loop {
tracing::info!("running deadlock detector");
thread::sleep(Duration::from_secs(5));
std::thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
@@ -260,12 +376,15 @@ fn detect_deadlocks() {
});
}
#[derive(Clap)]
#[derive(Parser)]
#[clap(author, about, version)]
struct Opts {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(long = "ffm")]
#[clap(action, short, long = "ffm")]
focus_follows_mouse: bool,
/// Wait for 'komorebic complete-configuration' to be sent before processing events
#[clap(action, short, long)]
await_configuration: bool,
}
#[tracing::instrument]
@@ -274,15 +393,31 @@ fn main() -> Result<()> {
CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
let arg_count = std::env::args().count();
let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst));
let has_valid_args = arg_count == 1
|| (arg_count == 2 && (opts.await_configuration || opts.focus_follows_mouse))
|| (arg_count == 3 && opts.await_configuration && opts.focus_follows_mouse);
if has_valid_args {
let session_id = WindowsApi::process_id_to_session_id()?;
SESSION_ID.store(session_id, Ordering::SeqCst);
let mut system = sysinfo::System::new_all();
system.refresh_processes();
if system.process_by_name("komorebi.exe").len() > 1 {
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
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);
}
}
// File logging worker guard has to have an assignment in the main fn to work
@@ -293,6 +428,8 @@ fn main() -> Result<()> {
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
WindowsApi::set_process_dpi_awareness_context()?;
Border::create("komorebi-border-window")?;
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
crossbeam_channel::unbounded();
@@ -306,14 +443,21 @@ fn main() -> Result<()> {
wm.lock().init()?;
listen_for_commands(wm.clone());
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
if opts.await_configuration {
let backoff = Backoff::new();
while !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
backoff.snooze();
}
}
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
load_configuration()?;
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {
ctrlc_sender
@@ -328,6 +472,11 @@ fn main() -> Result<()> {
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

@@ -7,6 +7,7 @@ use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Serialize;
use komorebi_core::Rect;
@@ -15,7 +16,7 @@ use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
id: isize,
@@ -45,11 +46,11 @@ pub fn new(id: isize, size: Rect, work_area_size: Rect) -> Monitor {
}
impl Monitor {
pub fn load_focused_workspace(&mut self) -> Result<()> {
pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
let focused_idx = self.focused_workspace_idx();
for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
if i == focused_idx {
workspace.restore()?;
workspace.restore(mouse_follows_focus)?;
} else {
workspace.hide();
}
@@ -58,16 +59,39 @@ impl Monitor {
Ok(())
}
pub fn add_container(&mut self, container: Container) -> Result<()> {
let workspace = self
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?;
pub fn add_container(
&mut self,
container: Container,
workspace_idx: Option<usize>,
) -> Result<()> {
let workspace = if let Some(idx) = workspace_idx {
self.workspaces_mut()
.get_mut(idx)
.ok_or_else(|| anyhow!("there is no workspace at index {}", idx))?
} else {
self.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?
};
workspace.add_container(container);
Ok(())
}
pub fn remove_workspace_by_idx(&mut self, idx: usize) -> Option<Workspace> {
if idx < self.workspaces().len() {
return self.workspaces_mut().remove(idx);
}
if idx == 0 {
self.workspaces_mut().push_back(Workspace::default());
} else {
self.focus_workspace(idx - 1).ok()?;
};
None
}
pub fn ensure_workspace_count(&mut self, ensure_count: usize) {
if self.workspaces().len() < ensure_count {
self.workspaces_mut()

View File

@@ -7,29 +7,46 @@ use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use miow::pipe::connect;
use parking_lot::Mutex;
use schemars::schema_for;
use uds_windows::UnixStream;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::SocketMessage;
use komorebi_core::StateQuery;
use komorebi_core::WindowContainerBehaviour;
use crate::border::Border;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::window::Window;
use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::BORDER_ENABLED;
use crate::BORDER_HWND;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::CUSTOM_FFM;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::HOME_DIR;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::SUBSCRIPTION_PIPES;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_RULES;
@@ -42,7 +59,7 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
.try_clone()
.expect("could not clone unix listener");
thread::spawn(move || {
std::thread::spawn(move || {
tracing::info!("listening");
for client in listener.incoming() {
match client {
@@ -62,7 +79,31 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn process_command(&mut self, message: SocketMessage) -> Result<()> {
self.validate_virtual_desktop_id();
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
if let Some(id) = current_virtual_desktop() {
if id != *virtual_desktop_id {
tracing::info!(
"ignoring events and commands while not on virtual desktop {:?}",
virtual_desktop_id
);
return Ok(());
}
}
}
match message {
SocketMessage::CycleFocusMonitor(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusMonitorNumber(_)
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
| SocketMessage::FocusWorkspaceNumber(_) => {
if self.focused_workspace()?.visible_windows().is_empty() {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.hide()?;
}
}
_ => {}
};
match message {
SocketMessage::Promote => self.promote_container_to_front()?,
@@ -92,24 +133,71 @@ impl WindowManager {
SocketMessage::WorkspacePadding(monitor_idx, workspace_idx, size) => {
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
}
SocketMessage::WorkspaceRule(_, id, monitor_idx, workspace_idx) => {
SocketMessage::WorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
{
let mut workspace_rules = WORKSPACE_RULES.lock();
workspace_rules.insert(id, (monitor_idx, workspace_idx));
workspace_rules.insert(id.to_string(), (monitor_idx, workspace_idx));
}
self.enforce_workspace_rules()?;
}
SocketMessage::ManageRule(_, id) => {
SocketMessage::ManageRule(_, ref id) => {
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
if !manage_identifiers.contains(&id) {
manage_identifiers.push(id);
if !manage_identifiers.contains(id) {
manage_identifiers.push(id.to_string());
}
}
SocketMessage::FloatRule(_, id) => {
SocketMessage::FloatRule(identifier, ref id) => {
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
if !float_identifiers.contains(&id) {
float_identifiers.push(id);
if !float_identifiers.contains(id) {
float_identifiers.push(id.to_string());
}
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
let mut hwnds_to_purge = vec![];
for (i, monitor) in self.monitors().iter().enumerate() {
for container in monitor
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace"))?
.containers()
.iter()
{
for window in container.windows().iter() {
match identifier {
ApplicationIdentifier::Exe => {
if window.exe()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Class => {
if window.class()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Title => {
if window.title()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
}
}
}
}
for (monitor_idx, hwnd) in hwnds_to_purge {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor"))?;
monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no focused workspace"))?
.remove_window(hwnd)?;
monitor.update_focused_workspace(offset, &invisible_borders)?;
}
}
SocketMessage::AdjustContainerPadding(sizing, adjustment) => {
@@ -122,13 +210,19 @@ impl WindowManager {
self.move_container_to_workspace(workspace_idx, true)?;
}
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, true)?;
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false)?;
}
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, false)?;
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)?;
}
SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {
self.move_workspace_to_monitor(monitor_idx)?;
}
SocketMessage::TogglePause => {
if self.is_paused {
@@ -138,6 +232,7 @@ impl WindowManager {
}
self.is_paused = !self.is_paused;
self.retile_all(true)?;
}
SocketMessage::ToggleTiling => {
self.toggle_tiling()?;
@@ -150,18 +245,20 @@ impl WindowManager {
);
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(true)?;
self.update_focused_workspace(self.mouse_follows_focus)?;
}
SocketMessage::FocusMonitorNumber(monitor_idx) => {
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(true)?;
self.update_focused_workspace(self.mouse_follows_focus)?;
}
SocketMessage::Retile => self.retile_all()?,
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::ChangeLayoutCustom(path) => self.change_workspace_custom_layout(path)?,
SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, path) => {
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;
SocketMessage::ChangeLayoutCustom(ref path) => {
self.change_workspace_custom_layout(path.clone())?;
}
SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, ref path) => {
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path.clone())?;
}
SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => {
self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;
@@ -169,6 +266,35 @@ impl WindowManager {
SocketMessage::WorkspaceLayout(monitor_idx, workspace_idx, layout) => {
self.set_workspace_layout_default(monitor_idx, workspace_idx, layout)?;
}
SocketMessage::WorkspaceLayoutRule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
) => {
self.add_workspace_layout_default_rule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
)?;
}
SocketMessage::WorkspaceLayoutCustomRule(
monitor_idx,
workspace_idx,
at_container_count,
ref path,
) => {
self.add_workspace_layout_custom_rule(
monitor_idx,
workspace_idx,
at_container_count,
path.clone(),
)?;
}
SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {
self.clear_workspace_layout_rules(monitor_idx, workspace_idx)?;
}
SocketMessage::CycleFocusWorkspace(direction) => {
// 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
@@ -203,7 +329,10 @@ impl WindowManager {
})?;
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
}
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
}
SocketMessage::Stop => {
@@ -211,6 +340,11 @@ impl WindowManager {
"received stop command, restoring all hidden windows and terminating process"
);
self.restore_all_windows();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
std::process::exit(0)
}
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {
@@ -219,13 +353,17 @@ impl WindowManager {
SocketMessage::NewWorkspace => {
self.new_workspace()?;
}
SocketMessage::WorkspaceName(monitor_idx, workspace_idx, name) => {
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
SocketMessage::WorkspaceName(monitor_idx, workspace_idx, ref name) => {
self.set_workspace_name(monitor_idx, workspace_idx, name.to_string())?;
}
SocketMessage::State => {
let state = serde_json::to_string_pretty(&window_manager::State::from(&*self))?;
let mut socket =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let state = match serde_json::to_string_pretty(&window_manager::State::from(&*self))
{
Ok(state) => state,
Err(error) => error.to_string(),
};
let mut socket = HOME_DIR.clone();
socket.push("komorebic.sock");
let socket = socket.as_path();
@@ -248,16 +386,117 @@ impl WindowManager {
}
.to_string();
let mut socket =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut socket = HOME_DIR.clone();
socket.push("komorebic.sock");
let socket = socket.as_path();
let mut stream = UnixStream::connect(&socket)?;
stream.write_all(response.as_bytes())?;
}
SocketMessage::ResizeWindow(direction, sizing) => {
self.resize_window(direction, sizing, Option::from(50))?;
SocketMessage::ResizeWindowEdge(direction, sizing) => {
self.resize_window(direction, sizing, self.resize_delta, true)?;
}
SocketMessage::ResizeWindowAxis(axis, sizing) => {
// If the user has a custom layout, allow for the resizing of the primary column
// with this signal
let workspace = self.focused_workspace_mut()?;
let container_len = workspace.containers().len();
let no_layout_rules = workspace.layout_rules().is_empty();
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if matches!(axis, Axis::Horizontal) {
let percentage = custom
.primary_width_percentage()
.unwrap_or(100 / custom.len());
if no_layout_rules {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
}
}
} else {
for rule in workspace.layout_rules_mut() {
if container_len >= rule.0 {
if let Layout::Custom(ref mut custom) = rule.1 {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
}
}
}
}
}
}
}
// Otherwise proceed with the resizing logic for individual window containers in the
// assumed BSP layout
} else {
match axis {
Axis::Horizontal => {
self.resize_window(
OperationDirection::Left,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Right,
sizing,
self.resize_delta,
false,
)?;
}
Axis::Vertical => {
self.resize_window(
OperationDirection::Up,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Down,
sizing,
self.resize_delta,
false,
)?;
}
Axis::HorizontalAndVertical => {
self.resize_window(
OperationDirection::Left,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Right,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Up,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Down,
sizing,
self.resize_delta,
false,
)?;
}
}
}
self.update_focused_workspace(false)?;
}
SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
if !CUSTOM_FFM.load(Ordering::SeqCst) {
@@ -355,19 +594,37 @@ impl WindowManager {
SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
}
SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
self.update_focused_workspace(false)?;
}
}
SocketMessage::WatchConfiguration(enable) => {
self.watch_configuration(enable)?;
}
SocketMessage::IdentifyBorderOverflow(_, id) => {
SocketMessage::IdentifyBorderOverflowApplication(_, ref id) => {
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
if !identifiers.contains(&id) {
identifiers.push(id);
if !identifiers.contains(id) {
identifiers.push(id.to_string());
}
}
SocketMessage::IdentifyTrayApplication(_, id) => {
SocketMessage::IdentifyObjectNameChangeApplication(_, ref id) => {
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
}
}
SocketMessage::IdentifyTrayApplication(_, ref id) => {
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
if !identifiers.contains(&id) {
identifiers.push(id);
if !identifiers.contains(id) {
identifiers.push(id.to_string());
}
}
SocketMessage::IdentifyLayeredApplication(_, ref id) => {
let mut identifiers = LAYERED_WHITELIST.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
}
}
SocketMessage::ManageFocusedWindow => {
@@ -378,11 +635,11 @@ impl WindowManager {
}
SocketMessage::InvisibleBorders(rect) => {
self.invisible_borders = rect;
self.retile_all()?;
self.retile_all(false)?;
}
SocketMessage::WorkAreaOffset(rect) => {
self.work_area_offset = Option::from(rect);
self.retile_all()?;
self.retile_all(false)?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
@@ -417,7 +674,7 @@ impl WindowManager {
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::Save(path) => {
SocketMessage::Save(ref path) => {
let workspace = self.focused_workspace_mut()?;
let resize = workspace.resize_dimensions();
@@ -425,14 +682,14 @@ impl WindowManager {
.write(true)
.truncate(true)
.create(true)
.open(path)?;
.open(path.clone())?;
serde_json::to_writer_pretty(&file, &resize)?;
}
SocketMessage::Load(path) => {
SocketMessage::Load(ref path) => {
let workspace = self.focused_workspace_mut()?;
let file = File::open(&path)
let file = File::open(path)
.map_err(|_| anyhow!("no file found at {}", path.display().to_string()))?;
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
@@ -440,19 +697,133 @@ impl WindowManager {
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::AddSubscriber(subscriber) => {
SocketMessage::AddSubscriber(ref subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
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)
})?;
pipes.insert(subscriber, pipe);
pipes.insert(subscriber.clone(), pipe);
}
SocketMessage::RemoveSubscriber(subscriber) => {
SocketMessage::RemoveSubscriber(ref subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
pipes.remove(&subscriber);
pipes.remove(subscriber);
}
SocketMessage::MouseFollowsFocus(enable) => {
self.mouse_follows_focus = enable;
}
SocketMessage::ToggleMouseFollowsFocus => {
self.mouse_follows_focus = !self.mouse_follows_focus;
}
SocketMessage::ResizeDelta(delta) => {
self.resize_delta = delta;
}
SocketMessage::ToggleWindowContainerBehaviour => {
match self.window_container_behaviour {
WindowContainerBehaviour::Create => {
self.window_container_behaviour = WindowContainerBehaviour::Append;
}
WindowContainerBehaviour::Append => {
self.window_container_behaviour = WindowContainerBehaviour::Create;
}
}
}
SocketMessage::WindowHidingBehaviour(behaviour) => {
let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();
*hiding_behaviour = behaviour;
}
SocketMessage::ToggleCrossMonitorMoveBehaviour => {
match self.cross_monitor_move_behaviour {
MoveBehaviour::Swap => {
self.cross_monitor_move_behaviour = MoveBehaviour::Insert;
}
MoveBehaviour::Insert => {
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
}
}
}
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
self.cross_monitor_move_behaviour = behaviour;
}
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
self.unmanaged_window_operation_behaviour = behaviour;
}
SocketMessage::ActiveWindowBorder(enable) => {
if enable {
BORDER_ENABLED.store(true, Ordering::SeqCst);
self.show_border()?;
} else {
BORDER_ENABLED.store(false, Ordering::SeqCst);
self.hide_border()?;
}
}
SocketMessage::ActiveWindowBorderColour(r, g, b) => {
let hwnd = BORDER_HWND.load(Ordering::SeqCst);
WindowsApi::change_border_colour(hwnd, r, g, b)?;
}
SocketMessage::NotificationSchema => {
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
let mut socket = HOME_DIR.clone();
socket.push("komorebic.sock");
let socket = socket.as_path();
let mut stream = UnixStream::connect(&socket)?;
stream.write_all(schema.as_bytes())?;
}
};
match message {
SocketMessage::ChangeLayout(_)
| SocketMessage::ChangeLayoutCustom(_)
| SocketMessage::FlipLayout(_)
| SocketMessage::ManageFocusedWindow
| SocketMessage::MoveWorkspaceToMonitorNumber(_)
| SocketMessage::MoveContainerToMonitorNumber(_)
| SocketMessage::MoveContainerToWorkspaceNumber(_)
| SocketMessage::ResizeWindowEdge(_, _)
| SocketMessage::ResizeWindowAxis(_, _)
| SocketMessage::ToggleFloat
| SocketMessage::ToggleMonocle
| SocketMessage::ToggleMaximize
| SocketMessage::Promote
| SocketMessage::Retile
| SocketMessage::MoveWindow(_) => {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(foreground_window, &self.invisible_borders, false)?;
}
SocketMessage::TogglePause => {
let is_paused = self.is_paused;
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
if is_paused {
border.hide()?;
} else {
let focused = self.focused_window()?;
border.set_position(*focused, &self.invisible_borders, true)?;
focused.focus(false)?;
}
}
SocketMessage::ToggleTiling | SocketMessage::WorkspaceTiling(..) => {
let tiling_enabled = *self.focused_workspace_mut()?.tile();
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
if tiling_enabled {
let focused = self.focused_window()?;
border.set_position(*focused, &self.invisible_borders, true)?;
focused.focus(false)?;
} else {
border.hide()?;
}
}
_ => {}
};
tracing::info!("processed");
@@ -480,7 +851,7 @@ impl WindowManager {
self.process_command(message.clone())?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: (&*self).into(),
state: self.as_ref().into(),
})?)?;
}

View File

@@ -1,6 +1,6 @@
use std::fs::OpenOptions;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
@@ -10,13 +10,20 @@ use parking_lot::Mutex;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::WindowContainerBehaviour;
use crate::border::Border;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
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_ENABLED;
use crate::BORDER_HIDDEN;
use crate::BORDER_HWND;
use crate::DATA_DIR;
use crate::HIDDEN_HWNDS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
@@ -24,7 +31,7 @@ use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
let receiver = wm.lock().incoming_events.lock().clone();
thread::spawn(move || {
std::thread::spawn(move || {
tracing::info!("listening");
loop {
select! {
@@ -50,7 +57,17 @@ impl WindowManager {
return Ok(());
}
self.validate_virtual_desktop_id();
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
if let Some(id) = current_virtual_desktop() {
if id != *virtual_desktop_id {
tracing::info!(
"ignoring events and commands while not on virtual desktop {:?}",
virtual_desktop_id
);
return Ok(());
}
}
}
// Make sure we have the most recently focused monitor from any event
match event {
@@ -63,7 +80,23 @@ impl WindowManager {
let monitor_idx = self.monitor_idx_from_window(*window)
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
self.focus_monitor(monitor_idx)?;
// This is a hidden window apparently associated with COM support mechanisms (based
// on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)
//
// The hidden window, OLEChannelWnd, associated with this class (spawned by
// explorer.exe), after some debugging, is observed to always be tied to the primary
// display monitor, or (usually) monitor 0 in the WindowManager state.
//
// Due to this, at least one user in the Discord has witnessed behaviour where, when
// a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets
// set repeatedly to 0, regardless of where the current foreground window is actually
// located.
//
// This check ensures that we only update the focused monitor when the window
// triggering monitor reconciliation is known to not be tied to a specific monitor.
if window.class()? != "OleMainThreadWndClass" {
self.focus_monitor(monitor_idx)?;
}
}
_ => {}
}
@@ -102,13 +135,25 @@ impl WindowManager {
window.raise()?;
self.has_pending_raise_op = false;
}
WindowManagerEvent::Minimize(_, window)
| WindowManagerEvent::Destroy(_, window)
| WindowManagerEvent::Unmanage(window) => {
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
self.update_focused_workspace(false)?;
}
WindowManagerEvent::Minimize(_, window) => {
let mut hide = false;
{
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if !programmatically_hidden_hwnds.contains(&window.hwnd) {
hide = true;
}
}
if hide {
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
self.update_focused_workspace(false)?;
}
}
WindowManagerEvent::Hide(_, window) => {
let mut hide = false;
// Some major applications unfortunately send the HIDE signal when they are being
@@ -201,14 +246,54 @@ impl WindowManager {
}
}
let behaviour = self.window_container_behaviour;
let workspace = self.focused_workspace_mut()?;
if !workspace.contains_window(window.hwnd) {
workspace.new_container_for_window(*window);
self.update_focused_workspace(false)?;
match behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(*window);
self.update_focused_workspace(false)?;
}
WindowContainerBehaviour::Append => {
workspace
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no focused container"))?
.add_window(*window);
self.update_focused_workspace(true)?;
}
}
}
}
WindowManagerEvent::MoveResizeStart(_, _) => {
let monitor_idx = self.focused_monitor_idx();
let workspace_idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
.focused_workspace_idx();
let container_idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace with this idx"))?
.focused_container_idx();
self.pending_move_op = Option::from((monitor_idx, workspace_idx, container_idx));
}
WindowManagerEvent::MoveResizeEnd(_, window) => {
// We need this because if the event ends on a different monitor,
// that monitor will already have been focused and updated in the state
let pending = self.pending_move_op;
// Always consume the pending move op whenever this event is handled
self.pending_move_op = None;
let target_monitor_idx = self
.monitor_idx_from_current_pos()
.ok_or_else(|| anyhow!("cannot get monitor idx from current position"))?;
let new_window_behaviour = self.window_container_behaviour;
let invisible_borders = self.invisible_borders;
let workspace = self.focused_workspace_mut()?;
if workspace
.floating_windows()
@@ -218,12 +303,32 @@ impl WindowManager {
return Ok(());
}
let focused_idx = workspace.focused_container_idx();
let focused_container_idx = workspace.focused_container_idx();
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
let old_position = *workspace
.latest_layout()
.get(focused_idx)
.ok_or_else(|| anyhow!("there is no latest layout"))?;
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
.get(focused_container_idx)
// If the move was to another monitor with an empty workspace, the
// workspace here will refer to that empty workspace, which won't
// have any latest layout set. We fall back to a Default for Rect
// which allows us to make a reasonable guess that the drag has taken
// place across a monitor boundary to an empty workspace
.unwrap_or(&Rect::default());
// This will be true if we have moved to an empty workspace on another monitor
let mut moved_across_monitors = old_position == Rect::default();
if let Some((origin_monitor_idx, _, _)) = pending {
// If we didn't move to another monitor with an empty workspace, it is
// still possible that we moved to another monitor with a populated workspace
if !moved_across_monitors {
// So we'll check if the origin monitor index and the target monitor index
// are different, if they are, we can set the override
moved_across_monitors = origin_monitor_idx != target_monitor_idx;
}
}
// Adjust for the invisible borders
new_position.left += invisible_borders.left;
@@ -238,16 +343,93 @@ impl WindowManager {
bottom: new_position.bottom - old_position.bottom,
};
let is_move = resize.right == 0 && resize.bottom == 0;
// If we have moved across the monitors, use that override, otherwise determine
// if a move has taken place by ruling out a resize
let is_move = moved_across_monitors
|| resize.right == 0 && resize.bottom == 0
|| resize.right.abs() == invisible_borders.right
&& resize.bottom.abs() == invisible_borders.bottom;
if is_move {
tracing::info!("moving with mouse");
match workspace.container_idx_from_current_point() {
Some(target_idx) => {
workspace.swap_containers(focused_idx, target_idx);
if moved_across_monitors {
if let Some((
origin_monitor_idx,
origin_workspace_idx,
origin_container_idx,
)) = pending
{
let target_workspace_idx = self
.monitors()
.get(target_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
.focused_workspace_idx();
let target_container_idx = self
.monitors()
.get(target_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
.focused_workspace()
.ok_or_else(|| {
anyhow!("there is no focused workspace for this monitor")
})?
.container_idx_from_current_point()
// Default to 0 in the case of an empty workspace
.unwrap_or(0);
self.transfer_container(
(
origin_monitor_idx,
origin_workspace_idx,
origin_container_idx,
),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
// We want to make sure both the origin and target monitors are updated,
// so that we don't have ghost tiles until we force an interaction on
// the origin monitor's focused workspace
self.focus_monitor(origin_monitor_idx)?;
self.focus_workspace(origin_workspace_idx)?;
self.update_focused_workspace(false)?;
self.focus_monitor(target_monitor_idx)?;
self.focus_workspace(target_workspace_idx)?;
self.update_focused_workspace(false)?;
}
None => self.update_focused_workspace(true)?,
// Here we handle a simple move on the same monitor which is treated as
// a container swap
} else {
match new_window_behaviour {
WindowContainerBehaviour::Create => {
match workspace.container_idx_from_current_point() {
Some(target_idx) => {
workspace
.swap_containers(focused_container_idx, target_idx);
self.update_focused_workspace(false)?;
}
None => {
self.update_focused_workspace(self.mouse_follows_focus)?;
}
}
}
WindowContainerBehaviour::Append => {
match workspace.container_idx_from_current_point() {
Some(target_idx) => {
workspace.move_window_to_container(target_idx)?;
self.update_focused_workspace(false)?;
}
None => {
self.update_focused_workspace(self.mouse_follows_focus)?;
}
}
}
}
}
} else {
tracing::info!("resizing with mouse");
@@ -282,8 +464,8 @@ impl WindowManager {
ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));
}
for (edge, sizing, step) in ops {
self.resize_window(edge, sizing, Option::from(step))?;
for (edge, sizing, delta) in ops {
self.resize_window(edge, sizing, delta, true)?;
}
self.update_focused_workspace(false)?;
@@ -292,11 +474,46 @@ impl WindowManager {
WindowManagerEvent::MonitorPoll(..) | WindowManagerEvent::MouseCapture(..) => {}
};
if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) {
match event {
WindowManagerEvent::MoveResizeStart(_, _) => {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.hide()?;
BORDER_HIDDEN.store(true, Ordering::SeqCst);
}
WindowManagerEvent::MoveResizeEnd(_, _)
| WindowManagerEvent::Show(_, _)
| WindowManagerEvent::FocusChange(_, _) => {
let window = self.focused_window()?;
let mut rect = WindowsApi::window_rect(window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;
let activate = BORDER_HIDDEN.load(Ordering::SeqCst);
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(*window, &self.invisible_borders, activate)?;
if activate {
BORDER_HIDDEN.store(false, Ordering::SeqCst);
}
}
_ => {}
}
}
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
if let WindowManagerEvent::Unmanage(window) = event {
window.center(&self.focused_monitor_work_area()?, &invisible_borders)?;
}
// If there are no more windows on the workspace, we shouldn't show the border window
if self.focused_workspace()?.containers().is_empty() {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.hide()?;
BORDER_HIDDEN.store(true, Ordering::SeqCst);
}
tracing::trace!("updating list of known hwnds");
let mut known_hwnds = vec![];
for monitor in self.monitors() {
@@ -309,9 +526,7 @@ impl WindowManager {
}
}
let mut hwnd_json =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
hwnd_json.push("komorebi.hwnd.json");
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
@@ -321,7 +536,7 @@ impl WindowManager {
serde_json::to_writer_pretty(&file, &known_hwnds)?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::WindowManager(*event),
state: (&*self).into(),
state: self.as_ref().into(),
})?)?;
tracing::info!("processed: {}", event.window().to_string());

View File

@@ -17,7 +17,7 @@ pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
let receiver = message_loop::start().expect("could not start winput message loop");
loop {
let focus_follows_mouse = wm.lock().focus_follows_mouse.clone();
let focus_follows_mouse = wm.lock().focus_follows_mouse;
if let Some(FocusFollowsMouseImplementation::Komorebi) = focus_follows_mouse {
match receiver.next_event() {
// Don't want to send any raise events while we are dragging or resizing

View File

@@ -1,8 +1,9 @@
use std::collections::VecDeque;
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct Ring<T> {
elements: VecDeque<T>,
focused: usize,

View File

@@ -1,20 +1,19 @@
use bitflags::bitflags;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_DEFERERASE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_DRAWFRAME;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_HIDEWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOCOPYBITS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOREDRAW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOREPOSITION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOSENDCHANGING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
use windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;
use windows::Win32::UI::WindowsAndMessaging::SWP_DEFERERASE;
use windows::Win32::UI::WindowsAndMessaging::SWP_DRAWFRAME;
use windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED;
use windows::Win32::UI::WindowsAndMessaging::SWP_HIDEWINDOW;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOCOPYBITS;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOREDRAW;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOREPOSITION;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSENDCHANGING;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER;
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
bitflags! {
#[derive(Default)]

View File

@@ -1,63 +1,63 @@
use bitflags::bitflags;
use windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
use windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
use windows::Win32::UI::WindowsAndMessaging::WS_CHILDWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_CLIPCHILDREN;
use windows::Win32::UI::WindowsAndMessaging::WS_CLIPSIBLINGS;
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
use windows::Win32::UI::WindowsAndMessaging::WS_DLGFRAME;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_ACCEPTFILES;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_APPWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_CLIENTEDGE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_COMPOSITED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTEXTHELP;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTROLPARENT;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_DLGMODALFRAME;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYOUTRTL;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFT;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFTSCROLLBAR;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LTRREADING;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_MDICHILD;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOINHERITLAYOUT;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOPARENTNOTIFY;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_PALETTEWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHT;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHTSCROLLBAR;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_RTLREADING;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_STATICEDGE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TRANSPARENT;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_WINDOWEDGE;
use windows::Win32::UI::WindowsAndMessaging::WS_GROUP;
use windows::Win32::UI::WindowsAndMessaging::WS_HSCROLL;
use windows::Win32::UI::WindowsAndMessaging::WS_ICONIC;
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPED;
use windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUPWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_SIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use windows::Win32::UI::WindowsAndMessaging::WS_TABSTOP;
use windows::Win32::UI::WindowsAndMessaging::WS_THICKFRAME;
use windows::Win32::UI::WindowsAndMessaging::WS_TILED;
use windows::Win32::UI::WindowsAndMessaging::WS_TILEDWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
use windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CHILDWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CLIPCHILDREN;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CLIPSIBLINGS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_DLGFRAME;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_ACCEPTFILES;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_APPWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CLIENTEDGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_COMPOSITED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTEXTHELP;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTROLPARENT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_DLGMODALFRAME;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYOUTRTL;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFTSCROLLBAR;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LTRREADING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_MDICHILD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOINHERITLAYOUT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOPARENTNOTIFY;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_PALETTEWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHTSCROLLBAR;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RTLREADING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_STATICEDGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TRANSPARENT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_WINDOWEDGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_GROUP;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_HSCROLL;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_ICONIC;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_POPUPWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_SIZEBOX;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TABSTOP;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_THICKFRAME;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TILED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TILEDWINDOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
bitflags! {
#[derive(Default)]
pub struct GwlStyle: u32 {
pub struct WindowStyle: u32 {
const BORDER = WS_BORDER.0;
const CAPTION = WS_CAPTION.0;
const CHILD = WS_CHILD.0;
@@ -88,9 +88,10 @@ bitflags! {
}
}
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
bitflags! {
#[derive(Default)]
pub struct GwlExStyle: u32 {
pub struct ExtendedWindowStyle: u32 {
const ACCEPTFILES = WS_EX_ACCEPTFILES.0;
const APPWINDOW = WS_EX_APPWINDOW.0;
const CLIENTEDGE = WS_EX_CLIENTEDGE.0;

View File

@@ -1,29 +1,34 @@
use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::ser::Error;
use serde::ser::SerializeStruct;
use serde::Serialize;
use serde::Serializer;
use windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::Foundation::HWND;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
use komorebi_core::Rect;
use crate::styles::GwlExStyle;
use crate::styles::GwlStyle;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDDEN_HWNDS;
use crate::LAYERED_EXE_WHITELIST;
use crate::HIDING_BEHAVIOUR;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, JsonSchema)]
pub struct Window {
pub(crate) hwnd: isize,
}
@@ -33,18 +38,18 @@ impl Display for Window {
let mut display = format!("(hwnd: {}", self.hwnd);
if let Ok(title) = self.title() {
display.push_str(&format!(", title: {}", title));
write!(display, ", title: {}", title)?;
}
if let Ok(exe) = self.exe() {
display.push_str(&format!(", exe: {}", exe));
write!(display, ", exe: {}", exe)?;
}
if let Ok(class) = self.class() {
display.push_str(&format!(", class: {}", class));
write!(display, ", class: {}", class)?;
}
display.push(')');
write!(display, ")")?;
write!(f, "{}", display)
}
@@ -139,7 +144,11 @@ impl Window {
programmatically_hidden_hwnds.push(self.hwnd);
}
WindowsApi::hide_window(self.hwnd());
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
match *hiding_behaviour {
HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd()),
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd()),
}
}
pub fn restore(self) {
@@ -166,6 +175,18 @@ impl Window {
WindowsApi::maximize_window(self.hwnd());
}
pub fn unmaximize(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds
.iter()
.position(|&hwnd| hwnd == self.hwnd)
{
programmatically_hidden_hwnds.remove(idx);
}
WindowsApi::unmaximize_window(self.hwnd());
}
pub fn raise(self) -> Result<()> {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
@@ -187,11 +208,23 @@ impl Window {
WindowsApi::set_focus(self.hwnd())
}
pub fn focus(self) -> Result<()> {
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
let current_thread_id = WindowsApi::current_thread_id();
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
// hook has been installed
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not attach to window thread input processing mechanism, but continuing execution of focus(): {}",
error
);
}
};
// Raise Window to foreground
match WindowsApi::set_foreground_window(self.hwnd()) {
@@ -205,25 +238,37 @@ impl Window {
};
// Center cursor in Window
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
if mouse_follows_focus {
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
}
// This isn't really needed when the above command works as expected via AHK
WindowsApi::set_focus(self.hwnd())
match WindowsApi::set_focus(self.hwnd()) {
Ok(_) => {}
Err(error) => {
tracing::error!(
"could not set focus, but continuing execution of focus(): {}",
error
);
}
};
Ok(())
}
#[allow(dead_code)]
pub fn update_style(self, style: GwlStyle) -> Result<()> {
pub fn update_style(self, style: WindowStyle) -> Result<()> {
WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?)
}
pub fn style(self) -> Result<GwlStyle> {
pub fn style(self) -> Result<WindowStyle> {
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
GwlStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
WindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn ex_style(self) -> Result<GwlExStyle> {
pub fn ex_style(self) -> Result<ExtendedWindowStyle> {
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
GwlExStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
ExtendedWindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn title(self) -> Result<String> {
@@ -271,46 +316,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()) {
{
let float_identifiers = FLOAT_IDENTIFIERS.lock();
if float_identifiers.contains(&title)
|| float_identifiers.contains(&exe_name)
|| float_identifiers.contains(&class) {
return Ok(false);
}
}
let managed_override = {
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
manage_identifiers.contains(&exe_name) || manage_identifiers.contains(&class)
};
let allow_layered = {
let layered_exe_whitelist = LAYERED_EXE_WHITELIST.lock();
layered_exe_whitelist.contains(&exe_name)
};
let allow_wsl2_gui = {
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
wsl2_ui_processes.contains(&exe_name)
};
let style = self.style()?;
let ex_style = self.ex_style()?;
if (allow_wsl2_gui || style.contains(GwlStyle::CAPTION) && ex_style.contains(GwlExStyle::WINDOWEDGE))
&& !ex_style.contains(GwlExStyle::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
// allowing a specific layered window on the whitelist (like Steam), it should
// pass this check
&& (allow_layered || !ex_style.contains(GwlExStyle::LAYERED))
|| managed_override
{
return Ok(true);
} else if event.is_some() {
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
}
return Ok(window_is_eligible(&title, &exe_name, &class, self.style()?, self.ex_style()?, event));
}
}
_ => {}
@@ -319,3 +325,83 @@ impl Window {
Ok(false)
}
}
fn window_is_eligible(
title: &String,
exe_name: &String,
class: &String,
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 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),
},
)
};
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 allow_wsl2_gui = {
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
wsl2_ui_processes.contains(exe_name)
};
if (allow_wsl2_gui || 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
// allowing a specific layered window on the whitelist (like Steam), it should
// pass this check
&& (allow_layered || !ex_style.contains(ExtendedWindowStyle::LAYERED))
|| managed_override
{
return true;
} else if event.is_some() {
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
}
false
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,14 @@
use std::fmt::Display;
use std::fmt::Formatter;
use schemars::JsonSchema;
use serde::Serialize;
use crate::window::Window;
use crate::winevent::WinEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
#[derive(Debug, Copy, Clone, Serialize)]
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum WindowManagerEvent {
Destroy(WinEvent, Window),
@@ -15,6 +16,7 @@ pub enum WindowManagerEvent {
Hide(WinEvent, Window),
Minimize(WinEvent, Window),
Show(WinEvent, Window),
MoveResizeStart(WinEvent, Window),
MoveResizeEnd(WinEvent, Window),
MouseCapture(WinEvent, Window),
Manage(Window),
@@ -26,49 +28,56 @@ pub enum WindowManagerEvent {
impl Display for WindowManagerEvent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WindowManagerEvent::Manage(window) => {
Self::Manage(window) => {
write!(f, "Manage (Window: {})", window)
}
WindowManagerEvent::Unmanage(window) => {
Self::Unmanage(window) => {
write!(f, "Unmanage (Window: {})", window)
}
WindowManagerEvent::Destroy(winevent, window) => {
Self::Destroy(winevent, window) => {
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
}
WindowManagerEvent::FocusChange(winevent, window) => {
Self::FocusChange(winevent, window) => {
write!(
f,
"FocusChange (WinEvent: {}, Window: {})",
winevent, window
)
}
WindowManagerEvent::Hide(winevent, window) => {
Self::Hide(winevent, window) => {
write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window)
}
WindowManagerEvent::Minimize(winevent, window) => {
Self::Minimize(winevent, window) => {
write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window)
}
WindowManagerEvent::Show(winevent, window) => {
Self::Show(winevent, window) => {
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
}
WindowManagerEvent::MoveResizeEnd(winevent, window) => {
Self::MoveResizeStart(winevent, window) => {
write!(
f,
"MoveResizeStart (WinEvent: {}, Window: {})",
winevent, window
)
}
Self::MoveResizeEnd(winevent, window) => {
write!(
f,
"MoveResizeEnd (WinEvent: {}, Window: {})",
winevent, window
)
}
WindowManagerEvent::MouseCapture(winevent, window) => {
Self::MouseCapture(winevent, window) => {
write!(
f,
"MouseCapture (WinEvent: {}, Window: {})",
winevent, window
)
}
WindowManagerEvent::Raise(window) => {
Self::Raise(window) => {
write!(f, "Raise (Window: {})", window)
}
WindowManagerEvent::MonitorPoll(winevent, window) => {
Self::MonitorPoll(winevent, window) => {
write!(
f,
"MonitorPoll (WinEvent: {}, Window: {})",
@@ -82,17 +91,18 @@ impl Display for WindowManagerEvent {
impl WindowManagerEvent {
pub const fn window(self) -> Window {
match self {
WindowManagerEvent::Destroy(_, window)
| WindowManagerEvent::FocusChange(_, window)
| WindowManagerEvent::Hide(_, window)
| WindowManagerEvent::Minimize(_, window)
| WindowManagerEvent::Show(_, window)
| WindowManagerEvent::MoveResizeEnd(_, window)
| WindowManagerEvent::MouseCapture(_, window)
| WindowManagerEvent::MonitorPoll(_, window)
| WindowManagerEvent::Raise(window)
| WindowManagerEvent::Manage(window)
| WindowManagerEvent::Unmanage(window) => window,
Self::Destroy(_, window)
| Self::FocusChange(_, window)
| Self::Hide(_, window)
| Self::Minimize(_, window)
| Self::Show(_, window)
| Self::MoveResizeStart(_, window)
| Self::MoveResizeEnd(_, window)
| Self::MouseCapture(_, window)
| Self::MonitorPoll(_, window)
| Self::Raise(window)
| Self::Manage(window)
| Self::Unmanage(window) => window,
}
}
@@ -113,6 +123,7 @@ impl WindowManagerEvent {
WinEvent::ObjectFocus | WinEvent::SystemForeground => {
Option::from(Self::FocusChange(winevent, window))
}
WinEvent::SystemMoveSizeStart => Option::from(Self::MoveResizeStart(winevent, window)),
WinEvent::SystemMoveSizeEnd => Option::from(Self::MoveResizeEnd(winevent, window)),
WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => {
Option::from(Self::MouseCapture(winevent, window))
@@ -127,7 +138,10 @@ impl WindowManagerEvent {
let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
if object_name_change_on_launch.contains(&window.exe().ok()?) {
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()?)
{
Option::from(Self::Show(winevent, window))
} else {
None

View File

@@ -1,85 +1,110 @@
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::ffi::c_void;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::Error;
use color_eyre::Result;
use windows::core::Result as WindowsCrateResult;
use windows::core::PCSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::POINT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFO;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
use windows::Win32::System::Threading::AttachThreadInput;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::System::Threading::OpenProcess;
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
use windows::Win32::UI::Shell::Common::DEVICE_SCALE_FACTOR;
use windows::Win32::UI::Shell::GetScaleFactorForMonitor;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExA;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
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::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassA;
use windows::Win32::UI::WindowsAndMessaging::SetClassLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::GCLP_HBRBACKGROUND;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
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_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use bindings::Handle;
use bindings::Result as WindowsCrateResult;
use bindings::Windows::Win32::Foundation::BOOL;
use bindings::Windows::Win32::Foundation::HANDLE;
use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::Foundation::LPARAM;
use bindings::Windows::Win32::Foundation::POINT;
use bindings::Windows::Win32::Foundation::PWSTR;
use bindings::Windows::Win32::Foundation::RECT;
use bindings::Windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
use bindings::Windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use bindings::Windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use bindings::Windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromPoint;
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromWindow;
use bindings::Windows::Win32::Graphics::Gdi::HDC;
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
use bindings::Windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use bindings::Windows::Win32::Graphics::Gdi::MONITORINFO;
use bindings::Windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use bindings::Windows::Win32::System::Threading::AttachThreadInput;
use bindings::Windows::Win32::System::Threading::GetCurrentProcessId;
use bindings::Windows::Win32::System::Threading::GetCurrentThreadId;
use bindings::Windows::Win32::System::Threading::OpenProcess;
use bindings::Windows::Win32::System::Threading::QueryFullProcessImageNameW;
use bindings::Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use bindings::Windows::Win32::System::Threading::PROCESS_NAME_FORMAT;
use bindings::Windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::SetFocus;
use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsIconic;
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use bindings::Windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use komorebi_core::Rect;
use crate::container::Container;
@@ -109,7 +134,7 @@ macro_rules! impl_from_integer_for_windows_result {
};
}
impl_from_integer_for_windows_result!(isize, u32, i32);
impl_from_integer_for_windows_result!(usize, isize, u16, u32, i32);
impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
fn from(result: WindowsResult<T, E>) -> Self {
@@ -124,15 +149,16 @@ pub trait ProcessWindowsCrateResult<T> {
fn process(self) -> Result<T>;
}
macro_rules! impl_process_windows_crate_result {
macro_rules! impl_process_windows_crate_integer_wrapper_result {
( $($input:ty => $deref:ty),+ $(,)? ) => (
paste::paste! {
$(
impl ProcessWindowsCrateResult<$deref> for WindowsCrateResult<$input> {
impl ProcessWindowsCrateResult<$deref> for $input {
fn process(self) -> Result<$deref> {
match self {
Ok(value) => Ok(value.0),
Err(error) => Err(error.into()),
if self == $input(0) {
Err(std::io::Error::last_os_error().into())
} else {
Ok(self.0)
}
}
}
@@ -141,7 +167,7 @@ macro_rules! impl_process_windows_crate_result {
);
}
impl_process_windows_crate_result!(
impl_process_windows_crate_integer_wrapper_result!(
HWND => isize,
);
@@ -165,7 +191,7 @@ impl WindowsApi {
EnumDisplayMonitors(
HDC(0),
std::ptr::null_mut(),
Option::from(callback),
callback,
LPARAM(callback_data_address),
)
}
@@ -177,7 +203,7 @@ impl WindowsApi {
let mut monitors: Vec<isize> = vec![];
let monitors_ref: &mut Vec<isize> = monitors.as_mut();
Self::enum_display_monitors(
windows_callbacks::valid_display_monitors,
Option::Some(windows_callbacks::valid_display_monitors),
monitors_ref as *mut Vec<isize> as isize,
)?;
@@ -186,13 +212,13 @@ impl WindowsApi {
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
Self::enum_display_monitors(
windows_callbacks::enum_display_monitor,
Option::Some(windows_callbacks::enum_display_monitor),
monitors as *mut Ring<Monitor> as isize,
)
}
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
unsafe { EnumWindows(Option::from(callback), LPARAM(callback_data_address)) }
unsafe { EnumWindows(callback, LPARAM(callback_data_address)) }
.ok()
.process()
}
@@ -203,7 +229,7 @@ impl WindowsApi {
if let Some(workspace) = monitor.workspaces_mut().front_mut() {
// EnumWindows will enumerate through windows on all monitors
Self::enum_windows(
windows_callbacks::enum_window,
Option::Some(windows_callbacks::enum_window),
workspace.containers_mut() as *mut VecDeque<Container> as isize,
)?;
@@ -257,6 +283,24 @@ impl WindowsApi {
Self::set_window_pos(hwnd, layout, position, flags.bits())
}
pub fn position_border_window(hwnd: HWND, layout: &Rect, activate: bool) -> Result<()> {
let flags = if activate {
SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE
} else {
SetWindowPosition::NO_ACTIVATE
};
let position = HWND_BOTTOM;
Self::set_window_pos(hwnd, layout, position, flags.bits())
}
pub fn hide_border_window(hwnd: HWND) -> Result<()> {
let flags = SetWindowPosition::HIDE_WINDOW;
let position = HWND_BOTTOM;
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
}
pub fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
unsafe {
SetWindowPos(
@@ -279,12 +323,20 @@ impl WindowsApi {
unsafe { ShowWindow(hwnd, command) };
}
pub fn minimize_window(hwnd: HWND) {
Self::show_window(hwnd, SW_MINIMIZE);
}
pub fn hide_window(hwnd: HWND) {
Self::show_window(hwnd, SW_HIDE);
}
pub fn restore_window(hwnd: HWND) {
Self::show_window(hwnd, SW_RESTORE);
Self::show_window(hwnd, SW_SHOWNOACTIVATE);
}
pub fn unmaximize_window(hwnd: HWND) {
Self::show_window(hwnd, SW_NORMAL);
}
pub fn maximize_window(hwnd: HWND) {
@@ -292,7 +344,7 @@ impl WindowsApi {
}
pub fn foreground_window() -> Result<isize> {
unsafe { GetForegroundWindow() }.ok().process()
unsafe { GetForegroundWindow() }.process()
}
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
@@ -301,16 +353,16 @@ impl WindowsApi {
#[allow(dead_code)]
pub fn top_window() -> Result<isize> {
unsafe { GetTopWindow(HWND::default()) }.ok().process()
unsafe { GetTopWindow(HWND::default()) }.process()
}
pub fn desktop_window() -> Result<isize> {
unsafe { GetDesktopWindow() }.ok().process()
unsafe { GetDesktopWindow() }.process()
}
#[allow(dead_code)]
pub fn next_window(hwnd: HWND) -> Result<isize> {
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.ok().process()
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
}
#[allow(dead_code)]
@@ -348,7 +400,7 @@ impl WindowsApi {
}
pub fn window_from_point(point: POINT) -> Result<isize> {
unsafe { WindowFromPoint(point) }.ok().process()
unsafe { WindowFromPoint(point) }.process()
}
pub fn window_at_cursor_pos() -> Result<isize> {
@@ -377,6 +429,19 @@ impl WindowsApi {
unsafe { GetCurrentProcessId() }
}
pub fn process_id_to_session_id() -> Result<u32> {
let process_id = Self::current_process_id();
let mut session_id = 0;
unsafe {
if ProcessIdToSessionId(process_id, &mut session_id).as_bool() {
Ok(session_id)
} else {
Err(anyhow!("could not determine current session id"))
}
}
}
pub fn attach_thread_input(thread_id: u32, target_thread_id: u32, attach: bool) -> Result<()> {
unsafe { AttachThreadInput(thread_id, target_thread_id, attach) }
.ok()
@@ -384,7 +449,7 @@ impl WindowsApi {
}
pub fn set_focus(hwnd: HWND) -> Result<()> {
unsafe { SetFocus(hwnd) }.ok().map(|_| ()).process()
unsafe { SetFocus(hwnd) }.process().map(|_| ())
}
#[allow(dead_code)]
@@ -418,11 +483,14 @@ impl WindowsApi {
Self::set_window_long_ptr_w(hwnd, GWL_STYLE, new_value)
}
#[allow(dead_code)]
pub fn update_ex_style(hwnd: HWND, new_value: isize) -> Result<()> {
Self::set_window_long_ptr_w(hwnd, GWL_EXSTYLE, new_value)
}
pub fn window_text_w(hwnd: HWND) -> Result<String> {
let mut text: [u16; 512] = [0; 512];
match WindowsResult::from(unsafe {
GetWindowTextW(hwnd, PWSTR(text.as_mut_ptr()), text.len().try_into()?)
}) {
match WindowsResult::from(unsafe { GetWindowTextW(hwnd, &mut text) }) {
WindowsResult::Ok(len) => {
let length = usize::try_from(len)?;
Ok(String::from_utf16(&text[..length])?)
@@ -436,9 +504,7 @@ impl WindowsApi {
inherit_handle: bool,
process_id: u32,
) -> Result<HANDLE> {
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }
.ok()
.process()
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }.process()
}
pub fn process_handle(process_id: u32) -> Result<HANDLE> {
@@ -453,9 +519,9 @@ impl WindowsApi {
unsafe {
QueryFullProcessImageNameW(
handle,
PROCESS_NAME_FORMAT(0),
PROCESS_NAME_WIN32,
PWSTR(text_ptr),
&mut len as *mut u32,
std::ptr::addr_of_mut!(len),
)
}
.ok()
@@ -477,7 +543,7 @@ impl WindowsApi {
let mut class: [u16; BUF_SIZE] = [0; BUF_SIZE];
let len = Result::from(WindowsResult::from(unsafe {
RealGetWindowClassW(hwnd, PWSTR(class.as_mut_ptr()), u32::try_from(BUF_SIZE)?)
RealGetWindowClassW(hwnd, &mut class)
}))?;
Ok(String::from_utf16(&class[0..len as usize])?)
@@ -491,7 +557,7 @@ impl WindowsApi {
unsafe {
DwmGetWindowAttribute(
hwnd,
std::mem::transmute::<_, u32>(attribute),
attribute,
(value as *mut T).cast(),
u32::try_from(std::mem::size_of::<T>())?,
)?;
@@ -534,7 +600,7 @@ impl WindowsApi {
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
unsafe { GetMonitorInfoW(hmonitor, (&mut monitor_info as *mut MONITORINFO).cast()) }
unsafe { GetMonitorInfoW(hmonitor, std::ptr::addr_of_mut!(monitor_info).cast()) }
.ok()
.process()?;
@@ -551,6 +617,12 @@ impl WindowsApi {
))
}
pub fn set_process_dpi_awareness_context() -> Result<()> {
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }
.ok()
.process()
}
#[allow(dead_code)]
pub fn system_parameters_info_w(
action: SYSTEM_PARAMETERS_INFO_ACTION,
@@ -570,7 +642,7 @@ impl WindowsApi {
Self::system_parameters_info_w(
SPI_GETACTIVEWINDOWTRACKING,
0,
(&mut is_enabled as *mut BOOL).cast(),
std::ptr::addr_of_mut!(is_enabled).cast(),
SPIF_SENDCHANGE,
)?;
@@ -596,4 +668,71 @@ impl WindowsApi {
SPIF_SENDCHANGE,
)
}
pub fn module_handle_w() -> Result<HINSTANCE> {
unsafe { GetModuleHandleW(None) }.process()
}
pub fn create_solid_brush(r: u32, g: u32, b: u32) -> HBRUSH {
unsafe { CreateSolidBrush(r | (g << 8) | (b << 16)) }
}
pub fn register_class_a(window_class: &WNDCLASSA) -> Result<u16> {
Result::from(WindowsResult::from(unsafe { RegisterClassA(window_class) }))
}
pub fn scale_factor_for_monitor(hmonitor: isize) -> Result<DEVICE_SCALE_FACTOR> {
unsafe { GetScaleFactorForMonitor(HMONITOR(hmonitor)) }.process()
}
pub fn monitors_have_same_scale_factor(a: isize, b: isize) -> Result<bool> {
let a = Self::scale_factor_for_monitor(a)?;
let b = Self::scale_factor_for_monitor(b)?;
Ok(a == b)
}
pub fn change_border_colour(hwnd: isize, r: u32, g: u32, b: u32) -> Result<usize> {
Result::from(WindowsResult::from(unsafe {
SetClassLongPtrW(
HWND(hwnd),
GCLP_HBRBACKGROUND,
Self::create_solid_brush(r, g, b).0,
)
}))
}
pub fn round_corners(hwnd: isize) -> Result<()> {
let round = DWMWCP_ROUND;
unsafe {
DwmSetWindowAttribute(
HWND(hwnd),
DWMWA_WINDOW_CORNER_PREFERENCE,
std::ptr::addr_of!(round).cast(),
4,
)
}
.process()
}
pub fn create_border_window(name: PCSTR, instance: HINSTANCE) -> Result<isize> {
unsafe {
CreateWindowExA(
WS_EX_TOOLWINDOW,
name,
name,
WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
None,
instance,
std::ptr::null(),
)
}
.process()
}
}

View File

@@ -1,12 +1,19 @@
use std::collections::VecDeque;
use bindings::Windows::Win32::Foundation::BOOL;
use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::Foundation::LPARAM;
use bindings::Windows::Win32::Foundation::RECT;
use bindings::Windows::Win32::Graphics::Gdi::HDC;
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
use bindings::Windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use crate::container::Container;
use crate::monitor::Monitor;
@@ -103,3 +110,24 @@ pub extern "system" fn win_event_hook(
}
}
}
pub extern "system" fn border_window(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message as u32 {
WM_PAINT => {
InvalidateRect(window, std::ptr::null(), true);
LRESULT(0)
}
WM_DESTROY => {
PostQuitMessage(0);
LRESULT(0)
}
_ => DefWindowProcW(window, message, wparam, lparam),
}
}
}

View File

@@ -1,92 +1,94 @@
#![allow(clippy::use_self)]
use schemars::JsonSchema;
use serde::Serialize;
use strum::Display;
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_START;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_CARET;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END_APPLICATION;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_LAYOUT;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_START_APPLICATION;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_REGION;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SCROLL;
use windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SIMPLE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_ACCELERATORCHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CLOAKED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CONTENTSCROLLED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CREATE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DEFACTIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESCRIPTIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCANCEL;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCOMPLETE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGDROPPED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGENTER;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGLEAVE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_FOCUS;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HELPCHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HIDE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_CHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_HIDE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_SHOW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_INVOKED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LIVEREGIONCHANGED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_NAMECHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_PARENTCHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_REORDER;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTION;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONADD;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONREMOVE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONWITHIN;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SHOW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_STATECHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTSELECTIONCHANGED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_UNCLOAKED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_VALUECHANGE;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_START;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ALERT;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ARRANGMENTPREVIEW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTUREEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTURESTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DESKTOPSWITCH;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_FOREGROUND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_IME_KEY_NOTIFICATION;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZEEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZESTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZEEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZESTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SOUND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHEND;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPDROPPED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPGRABBED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPOVERTARGET;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_CANCELLED;
use windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHSTART;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_START;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_CARET;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END_APPLICATION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_LAYOUT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_START_APPLICATION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_REGION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SCROLL;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SIMPLE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_ACCELERATORCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CLOAKED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CONTENTSCROLLED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CREATE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DEFACTIONCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESCRIPTIONCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCANCEL;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCOMPLETE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGDROPPED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGENTER;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGLEAVE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_FOCUS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HELPCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HIDE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_CHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_HIDE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_SHOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_INVOKED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LIVEREGIONCHANGED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_NAMECHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_PARENTCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_REORDER;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONADD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONREMOVE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONWITHIN;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SHOW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_STATECHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTSELECTIONCHANGED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_UNCLOAKED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_VALUECHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_START;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ALERT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ARRANGMENTPREVIEW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTUREEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTURESTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DESKTOPSWITCH;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_FOREGROUND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_IME_KEY_NOTIFICATION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZEEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZESTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZEEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZESTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SOUND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHEND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPDROPPED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPGRABBED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPOVERTARGET;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_CANCELLED;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHSTART;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Display)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Display, JsonSchema)]
#[repr(u32)]
#[allow(dead_code)]
pub enum WinEvent {

View File

@@ -1,23 +1,21 @@
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::UI::Accessibility::SetWinEventHook;
use bindings::Windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::PeekMessageW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
use bindings::Windows::Win32::UI::WindowsAndMessaging::MSG;
use bindings::Windows::Win32::UI::WindowsAndMessaging::PM_REMOVE;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::Accessibility::SetWinEventHook;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::PeekMessageW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PM_REMOVE;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_callbacks;
@@ -45,10 +43,10 @@ impl WinEventListener {
let hook = self.hook.clone();
let outgoing = self.outgoing_events.lock().clone();
thread::spawn(move || unsafe {
std::thread::spawn(move || unsafe {
let hook_ref = SetWinEventHook(
EVENT_MIN as u32,
EVENT_MAX as u32,
EVENT_MIN,
EVENT_MAX,
None,
Some(windows_callbacks::win_event_hook),
0,
@@ -97,7 +95,7 @@ impl MessageLoop {
}
}
thread::sleep(Duration::from_millis(sleep));
std::thread::sleep(Duration::from_millis(sleep));
if !cb(value) {
break;

View File

@@ -1,5 +1,6 @@
use std::collections::VecDeque;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
@@ -7,11 +8,12 @@ use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Serialize;
use komorebi_core::Axis;
use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::Flip;
use komorebi_core::Layout;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
@@ -20,8 +22,9 @@ use crate::container::Container;
use crate::ring::Ring;
use crate::window::Window;
use crate::windows_api::WindowsApi;
use crate::INITIAL_CONFIGURATION_LOADED;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
pub struct Workspace {
#[getset(set = "pub")]
name: Option<String>,
@@ -38,10 +41,12 @@ pub struct Workspace {
maximized_window_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub")]
floating_windows: Vec<Window>,
#[getset(get = "pub", set = "pub")]
#[getset(get = "pub", get_mut = "pub", set = "pub")]
layout: Layout,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
layout_rules: Vec<(usize, Layout)>,
#[getset(get_copy = "pub", set = "pub")]
layout_flip: Option<Flip>,
layout_flip: Option<Axis>,
#[getset(get_copy = "pub", set = "pub")]
workspace_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
@@ -68,6 +73,7 @@ impl Default for Workspace {
monocle_container_restore_idx: None,
floating_windows: Vec::default(),
layout: Layout::Default(DefaultLayout::BSP),
layout_rules: vec![],
layout_flip: None,
workspace_padding: Option::from(10),
container_padding: Option::from(10),
@@ -101,7 +107,7 @@ impl Workspace {
}
}
pub fn restore(&mut self) -> Result<()> {
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
let idx = self.focused_container_idx();
let mut to_focus = None;
for (i, container) in self.containers_mut().iter_mut().enumerate() {
@@ -132,7 +138,7 @@ impl Workspace {
// Maximised windows should always be drawn at the top of the Z order
if let Some(window) = to_focus {
if self.maximized_window().is_none() {
window.focus()?;
window.focus(mouse_follows_focus)?;
}
}
@@ -145,6 +151,10 @@ impl Workspace {
offset: Option<Rect>,
invisible_borders: &Rect,
) -> Result<()> {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
return Ok(());
}
let container_padding = self.container_padding();
let mut adjusted_work_area = offset.map_or_else(
|| *work_area,
@@ -163,6 +173,24 @@ impl Workspace {
self.enforce_resize_constraints();
if !self.layout_rules().is_empty() {
let mut updated_layout = None;
for rule in self.layout_rules() {
if self.containers().len() >= rule.0 {
updated_layout = Option::from(rule.1.clone());
}
}
if let Some(updated_layout) = updated_layout {
if !matches!(updated_layout, Layout::Default(DefaultLayout::BSP)) {
self.set_layout_flip(None);
}
self.set_layout(updated_layout);
}
}
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
@@ -316,6 +344,45 @@ impl Workspace {
None
}
pub fn contains_managed_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {
return true;
}
}
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return true;
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return true;
}
}
false
}
pub fn is_focused_window_monocle_or_maximized(&self) -> Result<bool> {
let hwnd = WindowsApi::foreground_window()?;
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return Ok(true);
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return Ok(true);
}
}
Ok(false)
}
pub fn contains_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {
@@ -372,7 +439,11 @@ impl Workspace {
self.focus_last_container();
}
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
self.containers_mut().insert(idx, container);
}
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
if idx < self.resize_dimensions().len() {
self.resize_dimensions_mut().remove(idx);
}
@@ -422,6 +493,7 @@ impl Workspace {
if let Some(window) = self.maximized_window() {
if window.hwnd == hwnd {
window.unmaximize();
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
return Ok(());
@@ -456,9 +528,11 @@ impl Workspace {
if self.resize_dimensions().get(container_idx).is_some() {
self.resize_dimensions_mut().remove(container_idx);
}
}
self.focus_previous_container();
self.focus_previous_container();
} else {
container.load_focused_window();
}
Ok(())
}
@@ -471,6 +545,13 @@ impl Workspace {
container
}
pub fn remove_container(&mut self, idx: usize) -> Option<Container> {
let container = self.remove_container_by_idx(idx);
self.focus_previous_container();
container
}
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
let len = NonZeroUsize::new(self.containers().len())?;
@@ -594,22 +675,44 @@ impl Workspace {
}
pub fn new_floating_window(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let window = if let Some(maximized_window) = self.maximized_window() {
let window = *maximized_window;
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
window
} else if let Some(monocle_container) = self.monocle_container_mut() {
let window = monocle_container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
if monocle_container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
} else {
monocle_container.load_focused_window();
}
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
window
} else {
container.load_focused_window();
}
let focused_idx = self.focused_container_idx();
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
} else {
container.load_focused_window();
}
window
};
self.floating_windows_mut().push(window);
@@ -695,6 +798,53 @@ impl Workspace {
pub fn new_maximized_window(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let foreground_hwnd = WindowsApi::foreground_window()?;
let mut floating_window = None;
if !self.floating_windows().is_empty() {
let mut focused_floating_window_idx = None;
for (i, w) in self.floating_windows().iter().enumerate() {
if w.hwnd == foreground_hwnd {
focused_floating_window_idx = Option::from(i);
}
}
if let Some(idx) = focused_floating_window_idx {
floating_window = Option::from(self.floating_windows_mut().remove(idx));
}
}
if let Some(floating_window) = floating_window {
self.set_maximized_window(Option::from(floating_window));
self.set_maximized_window_restore_idx(Option::from(focused_idx));
if let Some(window) = self.maximized_window() {
window.maximize();
}
return Ok(());
}
let monocle_restore_idx = self.monocle_container_restore_idx();
if let Some(monocle_container) = self.monocle_container_mut() {
let window = monocle_container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if monocle_container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
} else {
monocle_container.load_focused_window();
}
self.set_maximized_window(Option::from(window));
self.set_maximized_window_restore_idx(monocle_restore_idx);
if let Some(window) = self.maximized_window() {
window.maximize();
}
return Ok(());
}
let container = self
.focused_container_mut()

View File

@@ -1,7 +1,7 @@
; Generated by komorebic.exe
Start(ffm) {
Run, komorebic.exe start --ffm %ffm%, , Hide
Start(ffm, await_configuration) {
Run, komorebic.exe start %ffm% --await_configuration %await_configuration%, , Hide
}
Stop() {
@@ -28,20 +28,20 @@ Log() {
Run, komorebic.exe log, , Hide
}
QuickSave() {
Run, komorebic.exe quick-save, , Hide
QuickSaveResize() {
Run, komorebic.exe quick-save-resize, , Hide
}
QuickLoad() {
Run, komorebic.exe quick-load, , Hide
QuickLoadResize() {
Run, komorebic.exe quick-load-resize, , Hide
}
Save(path) {
Run, komorebic.exe save %path%, , Hide
SaveResize(path) {
Run, komorebic.exe save-resize %path%, , Hide
}
Load(path) {
Run, komorebic.exe load %path%, , Hide
LoadResize(path) {
Run, komorebic.exe load-resize %path%, , Hide
}
Focus(operation_direction) {
@@ -68,6 +68,10 @@ Resize(edge, sizing) {
Run, komorebic.exe resize %edge% %sizing%, , Hide
}
ResizeAxis(axis, sizing) {
Run, komorebic.exe resize-axis %axis% %sizing%, , Hide
}
Unstack() {
Run, komorebic.exe unstack, , Hide
}
@@ -92,6 +96,10 @@ SendToWorkspace(target) {
Run, komorebic.exe send-to-workspace %target%, , Hide
}
SendToMonitorWorkspace(target_monitor, target_workspace) {
Run, komorebic.exe send-to-monitor-workspace %target_monitor% %target_workspace%, , Hide
}
FocusMonitor(target) {
Run, komorebic.exe focus-monitor %target%, , Hide
}
@@ -100,6 +108,10 @@ FocusWorkspace(target) {
Run, komorebic.exe focus-workspace %target%, , Hide
}
FocusMonitorWorkspace(target_monitor, target_workspace) {
Run, komorebic.exe focus-monitor-workspace %target_monitor% %target_workspace%, , Hide
}
CycleMonitor(cycle_direction) {
Run, komorebic.exe cycle-monitor %cycle_direction%, , Hide
}
@@ -108,10 +120,18 @@ CycleWorkspace(cycle_direction) {
Run, komorebic.exe cycle-workspace %cycle_direction%, , Hide
}
MoveWorkspaceToMonitor(target) {
Run, komorebic.exe move-workspace-to-monitor %target%, , Hide
}
NewWorkspace() {
Run, komorebic.exe new-workspace, , Hide
}
ResizeDelta(pixels) {
Run, komorebic.exe resize-delta %pixels%, , Hide
}
InvisibleBorders(left, top, right, bottom) {
Run, komorebic.exe invisible-borders %left% %top% %right% %bottom%, , Hide
}
@@ -136,8 +156,8 @@ LoadCustomLayout(path) {
Run, komorebic.exe load-custom-layout %path%, , Hide
}
FlipLayout(flip) {
Run, komorebic.exe flip-layout %flip%, , Hide
FlipLayout(axis) {
Run, komorebic.exe flip-layout %axis%, , Hide
}
Promote() {
@@ -168,6 +188,18 @@ WorkspaceCustomLayout(monitor, workspace, path) {
Run, komorebic.exe workspace-custom-layout %monitor% %workspace% %path%, , Hide
}
WorkspaceLayoutRule(monitor, workspace, at_container_count, layout) {
Run, komorebic.exe workspace-layout-rule %monitor% %workspace% %at_container_count% %layout%, , Hide
}
WorkspaceCustomLayoutRule(monitor, workspace, at_container_count, path) {
Run, komorebic.exe workspace-custom-layout-rule %monitor% %workspace% %at_container_count% %path%, , Hide
}
ClearWorkspaceLayoutRules(monitor, workspace) {
Run, komorebic.exe clear-workspace-layout-rules %monitor% %workspace%, , Hide
}
WorkspaceTiling(monitor, workspace, value) {
Run, komorebic.exe workspace-tiling %monitor% %workspace% %value%, , Hide
}
@@ -176,6 +208,10 @@ WorkspaceName(monitor, workspace, value) {
Run, komorebic.exe workspace-name %monitor% %workspace% %value%, , Hide
}
ToggleWindowContainerBehaviour() {
Run, komorebic.exe toggle-window-container-behaviour, , Hide
}
TogglePause() {
Run, komorebic.exe toggle-pause, , Hide
}
@@ -216,24 +252,60 @@ WatchConfiguration(boolean_state) {
Run, komorebic.exe watch-configuration %boolean_state%, , Hide
}
CompleteConfiguration() {
Run, komorebic.exe complete-configuration, , Hide
}
WindowHidingBehaviour(hiding_behaviour) {
Run, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide
}
CrossMonitorMoveBehaviour(move_behaviour) {
Run, komorebic.exe cross-monitor-move-behaviour %move_behaviour%, , Hide
}
ToggleCrossMonitorMoveBehaviour() {
Run, komorebic.exe toggle-cross-monitor-move-behaviour, , Hide
}
UnmanagedWindowOperationBehaviour(operation_behaviour) {
Run, komorebic.exe unmanaged-window-operation-behaviour %operation_behaviour%, , Hide
}
FloatRule(identifier, id) {
Run, komorebic.exe float-rule %identifier% %id%, , Hide
Run, komorebic.exe float-rule %identifier% "%id%", , Hide
}
ManageRule(identifier, id) {
Run, komorebic.exe manage-rule %identifier% %id%, , Hide
Run, komorebic.exe manage-rule %identifier% "%id%", , Hide
}
WorkspaceRule(identifier, id, monitor, workspace) {
Run, komorebic.exe workspace-rule %identifier% %id% %monitor% %workspace%, , Hide
Run, komorebic.exe workspace-rule %identifier% "%id%" %monitor% %workspace%, , Hide
}
IdentifyObjectNameChangeApplication(identifier, id) {
Run, komorebic.exe identify-object-name-change-application %identifier% "%id%", , Hide
}
IdentifyTrayApplication(identifier, id) {
Run, komorebic.exe identify-tray-application %identifier% %id%, , Hide
Run, komorebic.exe identify-tray-application %identifier% "%id%", , Hide
}
IdentifyBorderOverflow(identifier, id) {
Run, komorebic.exe identify-border-overflow %identifier% %id%, , Hide
IdentifyLayeredApplication(identifier, id) {
Run, komorebic.exe identify-layered-application %identifier% "%id%", , Hide
}
IdentifyBorderOverflowApplication(identifier, id) {
Run, komorebic.exe identify-border-overflow-application %identifier% "%id%", , Hide
}
ActiveWindowBorder(boolean_state) {
Run, komorebic.exe active-window-border %boolean_state%, , Hide
}
ActiveWindowBorderColour(r, g, b) {
Run, komorebic.exe active-window-border-colour %r% %g% %b%, , Hide
}
FocusFollowsMouse(boolean_state, implementation) {
@@ -244,6 +316,26 @@ ToggleFocusFollowsMouse(implementation) {
Run, komorebic.exe toggle-focus-follows-mouse --implementation %implementation%, , Hide
}
MouseFollowsFocus(boolean_state) {
Run, komorebic.exe mouse-follows-focus %boolean_state%, , Hide
}
ToggleMouseFollowsFocus() {
Run, komorebic.exe toggle-mouse-follows-focus, , Hide
}
AhkLibrary() {
Run, komorebic.exe ahk-library, , Hide
}
AhkAppSpecificConfiguration(path, override_path) {
Run, komorebic.exe ahk-app-specific-configuration %path% %override_path%, , Hide
}
FormatAppSpecificConfiguration(path) {
Run, komorebic.exe format-app-specific-configuration %path%, , Hide
}
NotificationSchema() {
Run, komorebic.exe notification-schema, , Hide
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.6"
version = "0.1.11"
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"]
@@ -11,17 +11,25 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bindings = { package = "bindings", path = "../bindings" }
derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" }
clap = "3.0.0-beta.4"
color-eyre = "0.5"
clap = { version = "3", features = ["derive", "wrap_help"] }
color-eyre = "0.6"
dirs = "4"
fs-tail = "0.1"
heck = "0.3"
heck = "0.4"
lazy_static = "1"
paste = "1"
powershell_script = "0.2"
powershell_script = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
uds_windows = "1"
[dependencies.windows]
version = "0.39"
features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging"
]

File diff suppressed because it is too large Load Diff

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "stable"