mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-20 06:18:04 +01:00
Compare commits
146 Commits
feature/re
...
feature/ko
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580f70a5a1 | ||
|
|
1c7a5ccb42 | ||
|
|
876439b96b | ||
|
|
d1b1a9e006 | ||
|
|
04791f427b | ||
|
|
e9bccd0316 | ||
|
|
29201b6b94 | ||
|
|
8efce49f2c | ||
|
|
6c022f8d69 | ||
|
|
748659db35 | ||
|
|
91c7f0588c | ||
|
|
60ec439d06 | ||
|
|
ead175ddbc | ||
|
|
5dd3e76602 | ||
|
|
ebcd7ce224 | ||
|
|
b982021573 | ||
|
|
3c84bfd27e | ||
|
|
c874bfc7bf | ||
|
|
85f9c381e5 | ||
|
|
336a4e358f | ||
|
|
4576078b96 | ||
|
|
39971774ea | ||
|
|
7da431081e | ||
|
|
7cc69a4a40 | ||
|
|
5f325a7458 | ||
|
|
34a7b2eb0c | ||
|
|
b08eb0d50c | ||
|
|
005a95b1e6 | ||
|
|
092e36b8b3 | ||
|
|
70be6f4ea4 | ||
|
|
e09d55e71a | ||
|
|
7cef7b53b5 | ||
|
|
8594e72d31 | ||
|
|
bc22ab699f | ||
|
|
b3844af1f3 | ||
|
|
b43f03ce83 | ||
|
|
5c1cfe7b2e | ||
|
|
6269e5972c | ||
|
|
ffa0b0b55e | ||
|
|
66199c5b15 | ||
|
|
aa42a64a48 | ||
|
|
17f1923423 | ||
|
|
5cfc3e831b | ||
|
|
aaf0434053 | ||
|
|
2224479c30 | ||
|
|
957588f60d | ||
|
|
d111d68c0b | ||
|
|
a10b13c799 | ||
|
|
1e69c65c25 | ||
|
|
711ab8d59b | ||
|
|
e1c36c9190 | ||
|
|
686d013734 | ||
|
|
fad4cbf019 | ||
|
|
839f8c9bf7 | ||
|
|
5d468ae70a | ||
|
|
93edcfaa2f | ||
|
|
02a3220cbd | ||
|
|
4b6a7c05e0 | ||
|
|
304158cb1f | ||
|
|
c426c06c01 | ||
|
|
c2cc21d09d | ||
|
|
09a24b89e5 | ||
|
|
4686d5e346 | ||
|
|
532adc9c6c | ||
|
|
a4e8286327 | ||
|
|
75234caa98 | ||
|
|
31b8be1481 | ||
|
|
634bc04d76 | ||
|
|
3b30c10ebb | ||
|
|
3eade94032 | ||
|
|
e46f1f4f6d | ||
|
|
45ea630e6a | ||
|
|
ed01bb674f | ||
|
|
a9534fa49c | ||
|
|
d4c0c35f3a | ||
|
|
f6e0f5ab81 | ||
|
|
51139b9e0c | ||
|
|
d7f1190152 | ||
|
|
b62d77501a | ||
|
|
7cb60ca7c5 | ||
|
|
43edf13bb2 | ||
|
|
cd894655db | ||
|
|
02c54734fb | ||
|
|
4a3f7ee34e | ||
|
|
2db0d888c1 | ||
|
|
cf5a41b5eb | ||
|
|
e4ee298606 | ||
|
|
38c0b25a1c | ||
|
|
d1b6a63af5 | ||
|
|
c246b209c4 | ||
|
|
a2e1b8c967 | ||
|
|
cb387025d2 | ||
|
|
6655d290f2 | ||
|
|
999f2ae2d4 | ||
|
|
cddc69d2bf | ||
|
|
43b2366378 | ||
|
|
e67425f841 | ||
|
|
b2a34204c6 | ||
|
|
d18283969a | ||
|
|
0138a313c0 | ||
|
|
c62ddb3c42 | ||
|
|
87e8eb48a6 | ||
|
|
5f1356b3e2 | ||
|
|
00df672352 | ||
|
|
2b83ff8148 | ||
|
|
749e247d85 | ||
|
|
e70086b681 | ||
|
|
39685dd615 | ||
|
|
228ef78d7f | ||
|
|
e2ae9b1207 | ||
|
|
5e3f1cbb44 | ||
|
|
9fd4dbf044 | ||
|
|
9be248bc03 | ||
|
|
85fe20ebba | ||
|
|
409d374b72 | ||
|
|
1fb0a7cd6e | ||
|
|
d0e46515c5 | ||
|
|
e01bbd9f74 | ||
|
|
be53ea2c24 | ||
|
|
d49279e888 | ||
|
|
daa2912945 | ||
|
|
2c515d54f7 | ||
|
|
f9785bef55 | ||
|
|
0519ebddbf | ||
|
|
b1ca0a3e3c | ||
|
|
84ccfedad4 | ||
|
|
adcb38fed9 | ||
|
|
4a19edaab2 | ||
|
|
676b643faf | ||
|
|
7f74640dbd | ||
|
|
c247426b8e | ||
|
|
4d7ccc5519 | ||
|
|
71e28b33e3 | ||
|
|
40226a2bbd | ||
|
|
2814349228 | ||
|
|
d627a1a771 | ||
|
|
78683ce7b3 | ||
|
|
a1ca4f03c3 | ||
|
|
147a56c274 | ||
|
|
127254b7ac | ||
|
|
4e6e2b3aa8 | ||
|
|
a55069df48 | ||
|
|
7fd545ca35 | ||
|
|
14e63292e1 | ||
|
|
18f34babfa | ||
|
|
2f7ae6f15f |
12
.github/workflows/windows.yaml
vendored
12
.github/workflows/windows.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
906
Cargo.lock
generated
906
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"bindings",
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"komorebic"
|
||||
"komorebic",
|
||||
"komokana"
|
||||
]
|
||||
|
||||
358
README.md
358
README.md
@@ -18,10 +18,32 @@ 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)
|
||||
|
||||
## 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 +105,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 +155,99 @@ 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.
|
||||
|
||||
#### 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
|
||||
```
|
||||
|
||||
#### 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 +283,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 +321,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,
|
||||
@@ -261,6 +404,31 @@ YAML
|
||||
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 +440,89 @@ 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)
|
||||
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
|
||||
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 +542,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 +568,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 +579,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 +617,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 +665,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 +684,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.
|
||||
|
||||
@@ -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"
|
||||
@@ -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::*,
|
||||
);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub use windows::Handle;
|
||||
pub use windows::Result;
|
||||
|
||||
::windows::include_bindings!();
|
||||
46
justfile
Normal file
46
justfile
Normal file
@@ -0,0 +1,46 @@
|
||||
set shell := ["cmd.exe", "/C"]
|
||||
export RUST_BACKTRACE := "full"
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
fmt:
|
||||
cargo +nightly fmt
|
||||
cargo +nightly clippy
|
||||
prettier --write README.md
|
||||
|
||||
install-komorebic:
|
||||
cargo +stable install --path komorebic --locked
|
||||
|
||||
install-komorebi:
|
||||
cargo +stable install --path komorebi --locked
|
||||
|
||||
install-komokana:
|
||||
cargo +stable install --path komokana --locked
|
||||
|
||||
install:
|
||||
just install-komorebic
|
||||
just install-komorebi
|
||||
just install-komokana
|
||||
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
|
||||
25
komokana/Cargo.toml
Normal file
25
komokana/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "komokana"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3", features = ["derive", "wrap_help"] }
|
||||
color-eyre = "0.6"
|
||||
dirs = "4"
|
||||
env_logger = "0.9"
|
||||
json_dotpath = "1"
|
||||
log = "0.4"
|
||||
miow = "0.4"
|
||||
parking_lot = "0.12"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.8"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
]
|
||||
37
komokana/src/configuration.rs
Normal file
37
komokana/src/configuration.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub type Configuration = Vec<Entry>;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Entry {
|
||||
pub exe: String,
|
||||
pub target_layer: String,
|
||||
pub title_overrides: Option<Vec<TitleOverride>>,
|
||||
pub virtual_key_overrides: Option<Vec<VirtualKeyOverride>>,
|
||||
pub virtual_key_ignores: Option<Vec<i32>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TitleOverride {
|
||||
pub title: String,
|
||||
pub strategy: Strategy,
|
||||
pub target_layer: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct VirtualKeyOverride {
|
||||
pub virtual_key_code: i32,
|
||||
pub targer_layer: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Strategy {
|
||||
StartsWith,
|
||||
EndsWith,
|
||||
Contains,
|
||||
Equals,
|
||||
}
|
||||
376
komokana/src/main.rs
Normal file
376
komokana/src/main.rs
Normal file
@@ -0,0 +1,376 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use clap::Parser;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::net::TcpStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Report;
|
||||
use color_eyre::Result;
|
||||
use dirs::home_dir;
|
||||
use json_dotpath::DotPaths;
|
||||
use miow::pipe::NamedPipe;
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::json;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::configuration::Strategy;
|
||||
|
||||
mod configuration;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[clap(short = 'p', long)]
|
||||
kanata_port: i32,
|
||||
#[clap(short, long, default_value = "~/komokana.yaml")]
|
||||
configuration: String,
|
||||
#[clap(short, long)]
|
||||
default_layer: String,
|
||||
#[clap(short, long, action)]
|
||||
tmpfile: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli: Cli = Cli::parse();
|
||||
let configuration = resolve_windows_path(&cli.configuration)?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
env_logger::builder().format_timestamp(None).init();
|
||||
|
||||
let mut komokana = Komokana::init(
|
||||
configuration,
|
||||
cli.kanata_port,
|
||||
cli.default_layer,
|
||||
cli.tmpfile,
|
||||
)?;
|
||||
|
||||
komokana.listen();
|
||||
|
||||
loop {
|
||||
sleep(Duration::from_secs(60));
|
||||
}
|
||||
}
|
||||
|
||||
struct Komokana {
|
||||
komorebi: Arc<Mutex<NamedPipe>>,
|
||||
kanata: Arc<Mutex<TcpStream>>,
|
||||
configuration: Configuration,
|
||||
default_layer: String,
|
||||
tmpfile: bool,
|
||||
}
|
||||
|
||||
const PIPE: &str = r#"\\.\pipe\"#;
|
||||
|
||||
impl Komokana {
|
||||
pub fn init(
|
||||
configuration: PathBuf,
|
||||
kanata_port: i32,
|
||||
default_layer: String,
|
||||
tmpfile: bool,
|
||||
) -> Result<Self> {
|
||||
let name = "komokana";
|
||||
let pipe = format!("{}\\{}", PIPE, name);
|
||||
let mut cfg = home_dir().expect("could not look up home dir");
|
||||
cfg.push("komokana.yaml");
|
||||
|
||||
let configuration: Configuration =
|
||||
serde_yaml::from_str(&std::fs::read_to_string(configuration)?)?;
|
||||
|
||||
let named_pipe = NamedPipe::new(pipe)?;
|
||||
|
||||
let mut output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", name])
|
||||
.output()?;
|
||||
|
||||
while !output.status.success() {
|
||||
log::warn!(
|
||||
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
|
||||
output.status.code()
|
||||
);
|
||||
|
||||
sleep(Duration::from_secs(5));
|
||||
|
||||
output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", name])
|
||||
.output()?;
|
||||
}
|
||||
|
||||
named_pipe.connect()?;
|
||||
log::debug!("connected to komorebi");
|
||||
|
||||
let stream = TcpStream::connect(format!("localhost:{kanata_port}"))?;
|
||||
log::debug!("connected to kanata");
|
||||
|
||||
Ok(Self {
|
||||
komorebi: Arc::new(Mutex::new(named_pipe)),
|
||||
kanata: Arc::new(Mutex::new(stream)),
|
||||
configuration,
|
||||
default_layer,
|
||||
tmpfile,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listen(&mut self) {
|
||||
let pipe = self.komorebi.clone();
|
||||
let stream = self.kanata.clone();
|
||||
let stream_read = self.kanata.clone();
|
||||
let tmpfile = self.tmpfile;
|
||||
log::info!("listening");
|
||||
|
||||
thread::spawn(move || -> Result<()> {
|
||||
let mut read_stream = stream_read.lock().try_clone()?;
|
||||
drop(stream_read);
|
||||
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
if let Ok(bytes_read) = read_stream.read(&mut buf) {
|
||||
let data = String::from_utf8(buf[0..bytes_read].to_vec())?;
|
||||
if data == "\n" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let notification: serde_json::Value = serde_json::from_str(&data)?;
|
||||
|
||||
if notification.dot_has("LayerChange.new") {
|
||||
if let Some(new) = notification.dot_get::<String>("LayerChange.new")? {
|
||||
log::info!("current layer: {new}");
|
||||
if tmpfile {
|
||||
let mut tmp = std::env::temp_dir();
|
||||
tmp.push("kanata_layer");
|
||||
std::fs::write(tmp, new)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let config = self.configuration.clone();
|
||||
let default_layer = self.default_layer.clone();
|
||||
thread::spawn(move || -> Result<()> {
|
||||
let mut buf = vec![0; 4096];
|
||||
loop {
|
||||
let mut named_pipe = pipe.lock();
|
||||
match (*named_pipe).read(&mut buf) {
|
||||
Ok(bytes_read) => {
|
||||
let data = String::from_utf8(buf[0..bytes_read].to_vec())?;
|
||||
if data == "\n" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let notification: serde_json::Value = serde_json::from_str(&data)?;
|
||||
if notification.dot_has("event.content.1.exe") {
|
||||
if let (Some(exe), Some(title), Some(kind)) = (
|
||||
notification.dot_get::<String>("event.content.1.exe")?,
|
||||
notification.dot_get::<String>("event.content.1.title")?,
|
||||
notification.dot_get::<String>("event.type")?,
|
||||
) {
|
||||
match kind.as_str() {
|
||||
"Show" => handle_event(
|
||||
&config,
|
||||
&stream,
|
||||
&default_layer,
|
||||
Event::Show,
|
||||
&exe,
|
||||
&title,
|
||||
)?,
|
||||
"FocusChange" => handle_event(
|
||||
&config,
|
||||
&stream,
|
||||
&default_layer,
|
||||
Event::FocusChange,
|
||||
&exe,
|
||||
&title,
|
||||
)?,
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
// Broken pipe
|
||||
if error.raw_os_error().expect("could not get raw os error") == 109 {
|
||||
named_pipe.disconnect()?;
|
||||
|
||||
let mut output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", "bar"])
|
||||
.output()?;
|
||||
|
||||
while !output.status.success() {
|
||||
log::warn!(
|
||||
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
|
||||
output.status.code()
|
||||
);
|
||||
|
||||
sleep(Duration::from_secs(5));
|
||||
|
||||
output = Command::new("cmd.exe")
|
||||
.args(["/C", "komorebic.exe", "subscribe", "bar"])
|
||||
.output()?;
|
||||
}
|
||||
|
||||
named_pipe.connect()?;
|
||||
} else {
|
||||
return Err(Report::from(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
configuration: &Configuration,
|
||||
stream: &Arc<Mutex<TcpStream>>,
|
||||
default_layer: &str,
|
||||
event: Event,
|
||||
exe: &str,
|
||||
title: &str,
|
||||
) -> Result<()> {
|
||||
let target = calculate_target(
|
||||
configuration,
|
||||
event,
|
||||
exe,
|
||||
title,
|
||||
if matches!(event, Event::FocusChange) {
|
||||
Option::from(default_layer)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(target) = target {
|
||||
let mut stream = stream.lock();
|
||||
let request = json!({
|
||||
"ChangeLayer": {
|
||||
"new": target,
|
||||
}
|
||||
});
|
||||
|
||||
stream.write_all(request.to_string().as_bytes())?;
|
||||
log::debug!("request sent: {request}");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Event {
|
||||
Show,
|
||||
FocusChange,
|
||||
}
|
||||
|
||||
fn calculate_target(
|
||||
configuration: &Configuration,
|
||||
event: Event,
|
||||
exe: &str,
|
||||
title: &str,
|
||||
default: Option<&str>,
|
||||
) -> Option<String> {
|
||||
let mut new_layer = default;
|
||||
for entry in configuration {
|
||||
if entry.exe == exe {
|
||||
if matches!(event, Event::FocusChange) {
|
||||
new_layer = Option::from(entry.target_layer.as_str());
|
||||
}
|
||||
|
||||
if let Some(title_overrides) = &entry.title_overrides {
|
||||
for title_override in title_overrides {
|
||||
match title_override.strategy {
|
||||
Strategy::StartsWith => {
|
||||
if title.starts_with(&title_override.title) {
|
||||
new_layer = Option::from(title_override.target_layer.as_str());
|
||||
}
|
||||
}
|
||||
Strategy::EndsWith => {
|
||||
if title.ends_with(&title_override.title) {
|
||||
new_layer = Option::from(title_override.target_layer.as_str());
|
||||
}
|
||||
}
|
||||
Strategy::Contains => {
|
||||
if title.contains(&title_override.title) {
|
||||
new_layer = Option::from(title_override.target_layer.as_str());
|
||||
}
|
||||
}
|
||||
Strategy::Equals => {
|
||||
if title.eq(&title_override.title) {
|
||||
new_layer = Option::from(title_override.target_layer.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This acts like a default target layer within the application
|
||||
// which defaults back to the entry's main target layer
|
||||
if new_layer.is_none() {
|
||||
new_layer = Option::from(entry.target_layer.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(event, Event::FocusChange) {
|
||||
if let Some(virtual_key_overrides) = &entry.virtual_key_overrides {
|
||||
for virtual_key_override in virtual_key_overrides {
|
||||
if unsafe { GetKeyState(virtual_key_override.virtual_key_code) } < 0 {
|
||||
new_layer = Option::from(virtual_key_override.targer_layer.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(virtual_key_ignores) = &entry.virtual_key_ignores {
|
||||
for virtual_key in virtual_key_ignores {
|
||||
if unsafe { GetKeyState(*virtual_key) } < 0 {
|
||||
new_layer = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_layer.and_then(|new_layer| Option::from(new_layer.to_string()))
|
||||
}
|
||||
|
||||
fn resolve_windows_path(raw_path: &str) -> Result<PathBuf> {
|
||||
let path = if raw_path.starts_with('~') {
|
||||
raw_path.replacen(
|
||||
'~',
|
||||
&dirs::home_dir()
|
||||
.ok_or_else(|| anyhow!("there is no home directory"))?
|
||||
.display()
|
||||
.to_string(),
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
raw_path.to_string()
|
||||
};
|
||||
|
||||
let full_path = PathBuf::from(path);
|
||||
|
||||
let parent = full_path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("cannot parse directory"))?;
|
||||
|
||||
let file = full_path
|
||||
.components()
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("cannot parse filename"))?;
|
||||
|
||||
let mut canonicalized = std::fs::canonicalize(parent)?;
|
||||
canonicalized.push(file);
|
||||
|
||||
Ok(canonicalized)
|
||||
}
|
||||
@@ -1,16 +1,21 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.6"
|
||||
version = "0.1.10"
|
||||
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"] }
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
]
|
||||
|
||||
@@ -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);
|
||||
|
||||
176
komorebi-core/src/config_generation.rs
Normal file
176
komorebi-core/src/config_generation.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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!(),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,38 @@ 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),
|
||||
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 +131,7 @@ impl FromStr for SocketMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum StateQuery {
|
||||
FocusedMonitorIndex,
|
||||
@@ -120,22 +140,51 @@ pub enum StateQuery {
|
||||
FocusedWindowIndex,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[derive(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(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(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 +195,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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.6"
|
||||
version = "0.1.10"
|
||||
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"
|
||||
@@ -25,20 +24,34 @@ getset = "0.1"
|
||||
hotwatch = "0.4"
|
||||
lazy_static = "1"
|
||||
nanoid = "0.4"
|
||||
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
|
||||
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
|
||||
paste = "1"
|
||||
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.24"
|
||||
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"
|
||||
miow = "0.4"
|
||||
winreg = "0.10"
|
||||
schemars = "0.8"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
]
|
||||
|
||||
[features]
|
||||
deadlock_detection = []
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#![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::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
@@ -13,7 +15,7 @@ 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;
|
||||
@@ -22,13 +24,19 @@ use lazy_static::lazy_static;
|
||||
#[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::process_command::listen_for_commands;
|
||||
@@ -60,7 +68,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 +86,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 +100,51 @@ 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
|
||||
};
|
||||
}
|
||||
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
@@ -102,8 +157,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 +187,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 +218,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 +230,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
|
||||
@@ -190,14 +247,57 @@ pub fn load_configuration() -> Result<()> {
|
||||
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,
|
||||
@@ -260,7 +360,7 @@ fn detect_deadlocks() {
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Parser)]
|
||||
#[clap(author, about, version)]
|
||||
struct Opts {
|
||||
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
||||
@@ -277,12 +377,26 @@ fn main() -> Result<()> {
|
||||
let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst));
|
||||
|
||||
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
|
||||
@@ -328,6 +442,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -13,13 +13,22 @@ 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::current_virtual_desktop;
|
||||
use crate::notify_subscribers;
|
||||
use crate::window_manager;
|
||||
use crate::window_manager::WindowManager;
|
||||
@@ -29,7 +38,11 @@ use crate::NotificationEvent;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::CUSTOM_FFM;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::HOME_DIR;
|
||||
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;
|
||||
@@ -62,7 +75,17 @@ 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::Promote => self.promote_container_to_front()?,
|
||||
@@ -106,10 +129,57 @@ impl WindowManager {
|
||||
manage_identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::FloatRule(_, id) => {
|
||||
SocketMessage::FloatRule(identifier, id) => {
|
||||
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||
if !float_identifiers.contains(&id) {
|
||||
float_identifiers.push(id);
|
||||
float_identifiers.push(id.clone());
|
||||
}
|
||||
|
||||
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 +192,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 +214,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
self.is_paused = !self.is_paused;
|
||||
self.retile_all(true)?;
|
||||
}
|
||||
SocketMessage::ToggleTiling => {
|
||||
self.toggle_tiling()?;
|
||||
@@ -150,13 +227,13 @@ 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)?,
|
||||
@@ -169,6 +246,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,
|
||||
path,
|
||||
) => {
|
||||
self.add_workspace_layout_custom_rule(
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
at_container_count,
|
||||
path,
|
||||
)?;
|
||||
}
|
||||
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 +309,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 +320,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) => {
|
||||
@@ -223,9 +337,13 @@ impl WindowManager {
|
||||
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
|
||||
}
|
||||
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 +366,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) {
|
||||
@@ -358,18 +577,30 @@ impl WindowManager {
|
||||
SocketMessage::WatchConfiguration(enable) => {
|
||||
self.watch_configuration(enable)?;
|
||||
}
|
||||
SocketMessage::IdentifyBorderOverflow(_, id) => {
|
||||
SocketMessage::IdentifyBorderOverflowApplication(_, id) => {
|
||||
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||
if !identifiers.contains(&id) {
|
||||
identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::IdentifyObjectNameChangeApplication(_, id) => {
|
||||
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
||||
if !identifiers.contains(&id) {
|
||||
identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::IdentifyTrayApplication(_, id) => {
|
||||
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
||||
if !identifiers.contains(&id) {
|
||||
identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::IdentifyLayeredApplication(_, id) => {
|
||||
let mut identifiers = LAYERED_WHITELIST.lock();
|
||||
if !identifiers.contains(&id) {
|
||||
identifiers.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::ManageFocusedWindow => {
|
||||
self.manage_focused_window()?;
|
||||
}
|
||||
@@ -378,11 +609,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()?;
|
||||
@@ -453,6 +684,55 @@ impl WindowManager {
|
||||
let mut pipes = SUBSCRIPTION_PIPES.lock();
|
||||
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::NotificationSchema => {
|
||||
let notification = schema_for!(Notification);
|
||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||
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())?;
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("processed");
|
||||
@@ -480,7 +760,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(),
|
||||
})?)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,16 @@ use parking_lot::Mutex;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
use komorebi_core::WindowContainerBehaviour;
|
||||
|
||||
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::DATA_DIR;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
|
||||
@@ -50,7 +53,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 +76,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 +131,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 +242,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 +299,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 +339,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 +460,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)?;
|
||||
@@ -309,9 +487,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 +497,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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
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::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 +37,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 +143,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) {
|
||||
@@ -187,11 +195,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 +225,27 @@ 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())
|
||||
}
|
||||
|
||||
#[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,23 +293,35 @@ 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 mut should_float = false;
|
||||
|
||||
{
|
||||
let float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||
if float_identifiers.contains(&title)
|
||||
|| float_identifiers.contains(&exe_name)
|
||||
|| float_identifiers.contains(&class) {
|
||||
return Ok(false);
|
||||
for identifier in float_identifiers.iter() {
|
||||
if title.starts_with(identifier) || title.ends_with(identifier) ||
|
||||
class.starts_with(identifier) || class.ends_with(identifier) ||
|
||||
identifier == &exe_name {
|
||||
should_float = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let managed_override = {
|
||||
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
manage_identifiers.contains(&exe_name) || manage_identifiers.contains(&class)
|
||||
manage_identifiers.contains(&exe_name)
|
||||
|| manage_identifiers.contains(&class)
|
||||
|| manage_identifiers.contains(&title)
|
||||
};
|
||||
|
||||
if should_float && !managed_override {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let allow_layered = {
|
||||
let layered_exe_whitelist = LAYERED_EXE_WHITELIST.lock();
|
||||
layered_exe_whitelist.contains(&exe_name)
|
||||
let layered_whitelist = LAYERED_WHITELIST.lock();
|
||||
layered_whitelist.contains(&exe_name)
|
||||
|| layered_whitelist.contains(&class)
|
||||
|| layered_whitelist.contains(&title)
|
||||
};
|
||||
|
||||
let allow_wsl2_gui = {
|
||||
@@ -298,13 +332,13 @@ impl Window {
|
||||
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)
|
||||
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(GwlExStyle::LAYERED))
|
||||
&& (allow_layered || !ex_style.contains(ExtendedWindowStyle::LAYERED))
|
||||
|| managed_override
|
||||
{
|
||||
return Ok(true);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
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::PWSTR;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::HANDLE;
|
||||
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::DWMWA_CLOAKED;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
|
||||
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::EnumDisplayMonitors;
|
||||
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
||||
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
||||
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||
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::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::Input::KeyboardAndMouse::SetFocus;
|
||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
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::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::GWL_EXSTYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||
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_RESTORE;
|
||||
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::WNDENUMPROC;
|
||||
|
||||
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;
|
||||
@@ -124,15 +124,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 +142,7 @@ macro_rules! impl_process_windows_crate_result {
|
||||
);
|
||||
}
|
||||
|
||||
impl_process_windows_crate_result!(
|
||||
impl_process_windows_crate_integer_wrapper_result!(
|
||||
HWND => isize,
|
||||
);
|
||||
|
||||
@@ -165,7 +166,7 @@ impl WindowsApi {
|
||||
EnumDisplayMonitors(
|
||||
HDC(0),
|
||||
std::ptr::null_mut(),
|
||||
Option::from(callback),
|
||||
callback,
|
||||
LPARAM(callback_data_address),
|
||||
)
|
||||
}
|
||||
@@ -177,7 +178,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 +187,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 +204,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,
|
||||
)?;
|
||||
|
||||
@@ -279,6 +280,10 @@ 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);
|
||||
}
|
||||
@@ -292,7 +297,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 +306,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 +353,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 +382,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 +402,7 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
pub fn set_focus(hwnd: HWND) -> Result<()> {
|
||||
unsafe { SetFocus(hwnd) }.ok().map(|_| ()).process()
|
||||
unsafe { SetFocus(hwnd) }.process().map(|_| ())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -420,9 +438,7 @@ impl WindowsApi {
|
||||
|
||||
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 +452,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 +467,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 +491,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 +505,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 +548,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()?;
|
||||
|
||||
@@ -570,7 +584,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,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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::RECT;
|
||||
use windows::Win32::Graphics::Gdi::HDC;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::monitor::Monitor;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,16 +8,15 @@ 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;
|
||||
|
||||
@@ -7,11 +7,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;
|
||||
@@ -21,7 +22,7 @@ use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
#[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 +39,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 +71,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 +105,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 +136,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)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +167,20 @@ 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 {
|
||||
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 +334,28 @@ 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 contains_window(&self, hwnd: isize) -> bool {
|
||||
for container in self.containers() {
|
||||
if container.contains_window(hwnd) {
|
||||
@@ -372,7 +412,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);
|
||||
}
|
||||
@@ -456,9 +500,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 +517,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())?;
|
||||
|
||||
|
||||
@@ -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,48 @@ WatchConfiguration(boolean_state) {
|
||||
Run, komorebic.exe watch-configuration %boolean_state%, , 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
|
||||
}
|
||||
|
||||
FocusFollowsMouse(boolean_state, implementation) {
|
||||
@@ -244,6 +304,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
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.6"
|
||||
version = "0.1.10"
|
||||
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.8"
|
||||
uds_windows = "1"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user