mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-19 22:13:41 +01:00
Compare commits
53 Commits
v0.1.7
...
feature/un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91c70e2e8 | ||
|
|
61760fc77d | ||
|
|
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 |
8
.github/workflows/windows.yaml
vendored
8
.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,7 +77,7 @@ 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: |
|
||||
|
||||
456
Cargo.lock
generated
456
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
290
README.md
290
README.md
@@ -18,7 +18,7 @@ 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.
|
||||
|
||||
@@ -27,6 +27,23 @@ Articles, blog posts, demos, and videos about _komorebi_ can be added to this li
|
||||
- [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
|
||||
@@ -88,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
|
||||
@@ -138,6 +157,68 @@ for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778d
|
||||
|
||||
### 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.
|
||||
|
||||
#### Floating Windows
|
||||
|
||||
Sometimes you will want a specific application to never be tiled, and instead float all the time. You add add rules to
|
||||
@@ -173,6 +254,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
|
||||
@@ -276,6 +375,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
|
||||
@@ -287,77 +411,87 @@ 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-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
|
||||
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-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
|
||||
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
|
||||
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
|
||||
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
|
||||
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`
|
||||
@@ -401,6 +535,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
|
||||
@@ -516,3 +651,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.
|
||||
|
||||
2
justfile
2
justfile
@@ -19,7 +19,7 @@ install:
|
||||
just install-komorebic
|
||||
just install-komorebi
|
||||
komorebic ahk-library
|
||||
cat '%USERPROFILE%\komorebic.lib.ahk' > komorebic.lib.sample.ahk
|
||||
cat '%USERPROFILE%\.config\komorebi\komorebic.lib.ahk' > komorebic.lib.sample.ahk
|
||||
|
||||
run:
|
||||
just install-komorebic
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3", features = ["derive"] }
|
||||
color-eyre = "0.5"
|
||||
color-eyre = "0.6"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.8"
|
||||
strum = { version = "0.23", features = ["derive"] }
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.30"
|
||||
version = "0.35"
|
||||
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;
|
||||
@@ -36,16 +37,16 @@ impl Arrangement for DefaultLayout {
|
||||
) -> 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 {
|
||||
@@ -87,7 +88,7 @@ impl Arrangement for DefaultLayout {
|
||||
|
||||
layouts
|
||||
}
|
||||
DefaultLayout::HorizontalStack => {
|
||||
Self::HorizontalStack => {
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
|
||||
let bottom = match len {
|
||||
@@ -129,7 +130,7 @@ impl Arrangement for DefaultLayout {
|
||||
|
||||
layouts
|
||||
}
|
||||
DefaultLayout::UltrawideVerticalStack => {
|
||||
Self::UltrawideVerticalStack => {
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
|
||||
let primary_right = match len {
|
||||
@@ -341,7 +342,7 @@ 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 Axis {
|
||||
Horizontal,
|
||||
@@ -474,6 +475,7 @@ 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,
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,13 @@ 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 {
|
||||
@@ -251,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>),
|
||||
@@ -259,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,7 +22,7 @@ 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,
|
||||
|
||||
@@ -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,11 +1,12 @@
|
||||
#![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;
|
||||
@@ -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
|
||||
@@ -47,6 +49,7 @@ pub enum SocketMessage {
|
||||
MoveContainerToWorkspaceNumber(usize),
|
||||
SendContainerToMonitorNumber(usize),
|
||||
SendContainerToWorkspaceNumber(usize),
|
||||
SendContainerToMonitorWorkspaceNumber(usize, usize),
|
||||
MoveWorkspaceToMonitorNumber(usize),
|
||||
Promote,
|
||||
ToggleFloat,
|
||||
@@ -54,6 +57,7 @@ pub enum SocketMessage {
|
||||
ToggleMaximize,
|
||||
ToggleWindowContainerBehaviour,
|
||||
WindowHidingBehaviour(HidingBehaviour),
|
||||
UnmanagedWindowOperationBehaviour(OperationBehaviour),
|
||||
// Current Workspace Commands
|
||||
ManageFocusedWindow,
|
||||
UnmanageFocusedWindow,
|
||||
@@ -84,6 +88,9 @@ pub enum SocketMessage {
|
||||
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),
|
||||
@@ -93,8 +100,10 @@ pub enum SocketMessage {
|
||||
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),
|
||||
@@ -103,6 +112,7 @@ pub enum SocketMessage {
|
||||
ToggleMouseFollowsFocus,
|
||||
AddSubscriber(String),
|
||||
RemoveSubscriber(String),
|
||||
NotificationSchema,
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
@@ -119,7 +129,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,
|
||||
@@ -128,36 +138,44 @@ 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, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[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)]
|
||||
#[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,
|
||||
@@ -168,8 +186,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,6 +1,7 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ArgEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -9,7 +10,7 @@ use strum::EnumString;
|
||||
use crate::direction::Direction;
|
||||
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,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use 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,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
@@ -15,7 +15,7 @@ komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
bitflags = "1"
|
||||
clap = { version = "3", features = ["derive"] }
|
||||
color-eyre = "0.5"
|
||||
color-eyre = "0.6"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
ctrlc = "3"
|
||||
@@ -24,12 +24,12 @@ 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.23", features = ["derive"] }
|
||||
sysinfo = "0.22"
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
sysinfo = "0.23"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
@@ -38,9 +38,10 @@ which = "4"
|
||||
winput = "0.2"
|
||||
miow = "0.4"
|
||||
winreg = "0.10"
|
||||
schemars = "0.8"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.30"
|
||||
version = "0.35"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dwm",
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
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;
|
||||
@@ -23,7 +24,10 @@ 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;
|
||||
@@ -64,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![
|
||||
@@ -98,6 +102,22 @@ lazy_static! {
|
||||
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")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
@@ -114,7 +134,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 home = HOME_DIR.clone();
|
||||
let appender = tracing_appender::rolling::never(home, "komorebi.log");
|
||||
let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
||||
@@ -168,7 +188,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -248,14 +268,14 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
current
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[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,
|
||||
@@ -341,9 +361,20 @@ fn main() -> Result<()> {
|
||||
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 shim_is_active = false;
|
||||
for proc in matched_procs {
|
||||
if proc.root().ends_with("shims") {
|
||||
shim_is_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !shim_is_active {
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
@@ -58,10 +59,19 @@ 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);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ 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;
|
||||
@@ -37,7 +38,10 @@ 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;
|
||||
@@ -187,13 +191,16 @@ 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)?;
|
||||
@@ -238,6 +245,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
|
||||
@@ -306,8 +342,7 @@ impl WindowManager {
|
||||
Err(error) => error.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();
|
||||
|
||||
@@ -330,8 +365,7 @@ 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();
|
||||
|
||||
@@ -517,18 +551,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()?;
|
||||
}
|
||||
@@ -635,6 +681,19 @@ impl WindowManager {
|
||||
let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
||||
*hiding_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");
|
||||
@@ -662,7 +721,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(),
|
||||
})?)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::windows_api::WindowsApi;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::HOME_DIR;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -75,7 +76,9 @@ 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)?;
|
||||
if window.class()? != "OleMainThreadWndClass" {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -466,8 +469,7 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
let mut hwnd_json =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let mut hwnd_json = HOME_DIR.clone();
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
@@ -478,7 +480,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,
|
||||
|
||||
@@ -18,20 +18,20 @@ use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct SetWindowPosition: u32 {
|
||||
const ASYNC_WINDOW_POS = SWP_ASYNCWINDOWPOS;
|
||||
const DEFER_ERASE = SWP_DEFERERASE;
|
||||
const DRAW_FRAME = SWP_DRAWFRAME;
|
||||
const FRAME_CHANGED = SWP_FRAMECHANGED;
|
||||
const HIDE_WINDOW = SWP_HIDEWINDOW;
|
||||
const NO_ACTIVATE = SWP_NOACTIVATE;
|
||||
const NO_COPY_BITS = SWP_NOCOPYBITS;
|
||||
const NO_MOVE = SWP_NOMOVE;
|
||||
const NO_OWNER_Z_ORDER = SWP_NOOWNERZORDER;
|
||||
const NO_REDRAW = SWP_NOREDRAW;
|
||||
const NO_REPOSITION = SWP_NOREPOSITION;
|
||||
const NO_SEND_CHANGING = SWP_NOSENDCHANGING;
|
||||
const NO_SIZE = SWP_NOSIZE;
|
||||
const NO_Z_ORDER = SWP_NOZORDER;
|
||||
const SHOW_WINDOW = SWP_SHOWWINDOW;
|
||||
const ASYNC_WINDOW_POS = SWP_ASYNCWINDOWPOS.0;
|
||||
const DEFER_ERASE = SWP_DEFERERASE.0;
|
||||
const DRAW_FRAME = SWP_DRAWFRAME.0;
|
||||
const FRAME_CHANGED = SWP_FRAMECHANGED.0;
|
||||
const HIDE_WINDOW = SWP_HIDEWINDOW.0;
|
||||
const NO_ACTIVATE = SWP_NOACTIVATE.0;
|
||||
const NO_COPY_BITS = SWP_NOCOPYBITS.0;
|
||||
const NO_MOVE = SWP_NOMOVE.0;
|
||||
const NO_OWNER_Z_ORDER = SWP_NOOWNERZORDER.0;
|
||||
const NO_REDRAW = SWP_NOREDRAW.0;
|
||||
const NO_REPOSITION = SWP_NOREPOSITION.0;
|
||||
const NO_SEND_CHANGING = SWP_NOSENDCHANGING.0;
|
||||
const NO_SIZE = SWP_NOSIZE.0;
|
||||
const NO_Z_ORDER = SWP_NOZORDER.0;
|
||||
const SHOW_WINDOW = SWP_SHOWWINDOW.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,33 +58,33 @@ use windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct WindowStyle: u32 {
|
||||
const BORDER = WS_BORDER;
|
||||
const CAPTION = WS_CAPTION;
|
||||
const CHILD = WS_CHILD;
|
||||
const CHILDWINDOW = WS_CHILDWINDOW;
|
||||
const CLIPCHILDREN = WS_CLIPCHILDREN;
|
||||
const CLIPSIBLINGS = WS_CLIPSIBLINGS;
|
||||
const DISABLED = WS_DISABLED;
|
||||
const DLGFRAME = WS_DLGFRAME;
|
||||
const GROUP = WS_GROUP;
|
||||
const HSCROLL = WS_HSCROLL;
|
||||
const ICONIC = WS_ICONIC;
|
||||
const MAXIMIZE = WS_MAXIMIZE;
|
||||
const MAXIMIZEBOX = WS_MAXIMIZEBOX;
|
||||
const MINIMIZE = WS_MINIMIZE;
|
||||
const MINIMIZEBOX = WS_MINIMIZEBOX;
|
||||
const OVERLAPPED = WS_OVERLAPPED;
|
||||
const OVERLAPPEDWINDOW = WS_OVERLAPPEDWINDOW;
|
||||
const POPUP = WS_POPUP;
|
||||
const POPUPWINDOW = WS_POPUPWINDOW;
|
||||
const SIZEBOX = WS_SIZEBOX;
|
||||
const SYSMENU = WS_SYSMENU;
|
||||
const TABSTOP = WS_TABSTOP;
|
||||
const THICKFRAME = WS_THICKFRAME;
|
||||
const TILED = WS_TILED;
|
||||
const TILEDWINDOW = WS_TILEDWINDOW;
|
||||
const VISIBLE = WS_VISIBLE;
|
||||
const VSCROLL = WS_VSCROLL;
|
||||
const BORDER = WS_BORDER.0;
|
||||
const CAPTION = WS_CAPTION.0;
|
||||
const CHILD = WS_CHILD.0;
|
||||
const CHILDWINDOW = WS_CHILDWINDOW.0;
|
||||
const CLIPCHILDREN = WS_CLIPCHILDREN.0;
|
||||
const CLIPSIBLINGS = WS_CLIPSIBLINGS.0;
|
||||
const DISABLED = WS_DISABLED.0;
|
||||
const DLGFRAME = WS_DLGFRAME.0;
|
||||
const GROUP = WS_GROUP.0;
|
||||
const HSCROLL = WS_HSCROLL.0;
|
||||
const ICONIC = WS_ICONIC.0;
|
||||
const MAXIMIZE = WS_MAXIMIZE.0;
|
||||
const MAXIMIZEBOX = WS_MAXIMIZEBOX.0;
|
||||
const MINIMIZE = WS_MINIMIZE.0;
|
||||
const MINIMIZEBOX = WS_MINIMIZEBOX.0;
|
||||
const OVERLAPPED = WS_OVERLAPPED.0;
|
||||
const OVERLAPPEDWINDOW = WS_OVERLAPPEDWINDOW.0;
|
||||
const POPUP = WS_POPUP.0;
|
||||
const POPUPWINDOW = WS_POPUPWINDOW.0;
|
||||
const SIZEBOX = WS_SIZEBOX.0;
|
||||
const SYSMENU = WS_SYSMENU.0;
|
||||
const TABSTOP = WS_TABSTOP.0;
|
||||
const THICKFRAME = WS_THICKFRAME.0;
|
||||
const TILED = WS_TILED.0;
|
||||
const TILEDWINDOW = WS_TILEDWINDOW.0;
|
||||
const VISIBLE = WS_VISIBLE.0;
|
||||
const VSCROLL = WS_VSCROLL.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,32 +92,32 @@ bitflags! {
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct ExtendedWindowStyle: u32 {
|
||||
const ACCEPTFILES = WS_EX_ACCEPTFILES;
|
||||
const APPWINDOW = WS_EX_APPWINDOW;
|
||||
const CLIENTEDGE = WS_EX_CLIENTEDGE;
|
||||
const COMPOSITED = WS_EX_COMPOSITED;
|
||||
const CONTEXTHELP = WS_EX_CONTEXTHELP;
|
||||
const CONTROLPARENT = WS_EX_CONTROLPARENT;
|
||||
const DLGMODALFRAME = WS_EX_DLGMODALFRAME;
|
||||
const LAYERED = WS_EX_LAYERED;
|
||||
const LAYOUTRTL = WS_EX_LAYOUTRTL;
|
||||
const LEFT = WS_EX_LEFT;
|
||||
const LEFTSCROLLBAR = WS_EX_LEFTSCROLLBAR;
|
||||
const LTRREADING = WS_EX_LTRREADING;
|
||||
const MDICHILD = WS_EX_MDICHILD;
|
||||
const NOACTIVATE = WS_EX_NOACTIVATE;
|
||||
const NOINHERITLAYOUT = WS_EX_NOINHERITLAYOUT;
|
||||
const NOPARENTNOTIFY = WS_EX_NOPARENTNOTIFY;
|
||||
const NOREDIRECTIONBITMAP = WS_EX_NOREDIRECTIONBITMAP;
|
||||
const OVERLAPPEDWINDOW = WS_EX_OVERLAPPEDWINDOW;
|
||||
const PALETTEWINDOW = WS_EX_PALETTEWINDOW;
|
||||
const RIGHT = WS_EX_RIGHT;
|
||||
const RIGHTSCROLLBAR = WS_EX_RIGHTSCROLLBAR;
|
||||
const RTLREADING = WS_EX_RTLREADING;
|
||||
const STATICEDGE = WS_EX_STATICEDGE;
|
||||
const TOOLWINDOW = WS_EX_TOOLWINDOW;
|
||||
const TOPMOST = WS_EX_TOPMOST;
|
||||
const TRANSPARENT = WS_EX_TRANSPARENT;
|
||||
const WINDOWEDGE = WS_EX_WINDOWEDGE;
|
||||
const ACCEPTFILES = WS_EX_ACCEPTFILES.0;
|
||||
const APPWINDOW = WS_EX_APPWINDOW.0;
|
||||
const CLIENTEDGE = WS_EX_CLIENTEDGE.0;
|
||||
const COMPOSITED = WS_EX_COMPOSITED.0;
|
||||
const CONTEXTHELP = WS_EX_CONTEXTHELP.0;
|
||||
const CONTROLPARENT = WS_EX_CONTROLPARENT.0;
|
||||
const DLGMODALFRAME = WS_EX_DLGMODALFRAME.0;
|
||||
const LAYERED = WS_EX_LAYERED.0;
|
||||
const LAYOUTRTL = WS_EX_LAYOUTRTL.0;
|
||||
const LEFT = WS_EX_LEFT.0;
|
||||
const LEFTSCROLLBAR = WS_EX_LEFTSCROLLBAR.0;
|
||||
const LTRREADING = WS_EX_LTRREADING.0;
|
||||
const MDICHILD = WS_EX_MDICHILD.0;
|
||||
const NOACTIVATE = WS_EX_NOACTIVATE.0;
|
||||
const NOINHERITLAYOUT = WS_EX_NOINHERITLAYOUT.0;
|
||||
const NOPARENTNOTIFY = WS_EX_NOPARENTNOTIFY.0;
|
||||
const NOREDIRECTIONBITMAP = WS_EX_NOREDIRECTIONBITMAP.0;
|
||||
const OVERLAPPEDWINDOW = WS_EX_OVERLAPPEDWINDOW.0;
|
||||
const PALETTEWINDOW = WS_EX_PALETTEWINDOW.0;
|
||||
const RIGHT = WS_EX_RIGHT.0;
|
||||
const RIGHTSCROLLBAR = WS_EX_RIGHTSCROLLBAR.0;
|
||||
const RTLREADING = WS_EX_RTLREADING.0;
|
||||
const STATICEDGE = WS_EX_STATICEDGE.0;
|
||||
const TOOLWINDOW = WS_EX_TOOLWINDOW.0;
|
||||
const TOPMOST = WS_EX_TOPMOST.0;
|
||||
const TRANSPARENT = WS_EX_TRANSPARENT.0;
|
||||
const WINDOWEDGE = WS_EX_WINDOWEDGE.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ use std::fmt::Formatter;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use serde::ser::Error;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
@@ -21,11 +22,11 @@ use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
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,
|
||||
}
|
||||
@@ -63,24 +64,23 @@ impl Serialize for Window {
|
||||
"title",
|
||||
&self
|
||||
.title()
|
||||
.map_err(|_| S::Error::custom("could not get window title"))?,
|
||||
.unwrap_or_else(|_| "could not get window title".to_string()),
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"exe",
|
||||
&self
|
||||
.exe()
|
||||
.map_err(|_| S::Error::custom("could not get window exe"))?,
|
||||
.unwrap_or_else(|_| "could not get window exe".to_string()),
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"class",
|
||||
&self
|
||||
.class()
|
||||
.map_err(|_| S::Error::custom("could not get window class"))?,
|
||||
.unwrap_or_else(|_| "could not get window class".to_string()),
|
||||
)?;
|
||||
state.serialize_field(
|
||||
"rect",
|
||||
&WindowsApi::window_rect(self.hwnd())
|
||||
.map_err(|_| S::Error::custom("could not get window rect"))?,
|
||||
&WindowsApi::window_rect(self.hwnd()).unwrap_or_default(),
|
||||
)?;
|
||||
state.end()
|
||||
}
|
||||
@@ -294,8 +294,8 @@ impl Window {
|
||||
};
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
let allow_wsl2_gui = {
|
||||
|
||||
@@ -11,6 +11,7 @@ use crossbeam_channel::Receiver;
|
||||
use hotwatch::notify::DebouncedEvent;
|
||||
use hotwatch::Hotwatch;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixListener;
|
||||
|
||||
@@ -21,6 +22,7 @@ use komorebi_core::CycleDirection;
|
||||
use komorebi_core::DefaultLayout;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::OperationBehaviour;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
@@ -38,8 +40,10 @@ use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
use crate::HOME_DIR;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WORKSPACE_RULES;
|
||||
|
||||
@@ -53,6 +57,7 @@ pub struct WindowManager {
|
||||
pub work_area_offset: Option<Rect>,
|
||||
pub resize_delta: i32,
|
||||
pub window_container_behaviour: WindowContainerBehaviour,
|
||||
pub unmanaged_window_operation_behaviour: OperationBehaviour,
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
||||
pub mouse_follows_focus: bool,
|
||||
pub hotwatch: Hotwatch,
|
||||
@@ -61,7 +66,7 @@ pub struct WindowManager {
|
||||
pub pending_move_op: Option<(usize, usize, usize)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct State {
|
||||
pub monitors: Ring<Monitor>,
|
||||
pub is_paused: bool,
|
||||
@@ -74,9 +79,16 @@ pub struct State {
|
||||
pub has_pending_raise_op: bool,
|
||||
pub float_identifiers: Vec<String>,
|
||||
pub manage_identifiers: Vec<String>,
|
||||
pub layered_exe_whitelist: Vec<String>,
|
||||
pub layered_whitelist: Vec<String>,
|
||||
pub tray_and_multi_window_identifiers: Vec<String>,
|
||||
pub border_overflow_identifiers: Vec<String>,
|
||||
pub name_change_on_launch_identifiers: Vec<String>,
|
||||
}
|
||||
|
||||
impl AsRef<Self> for WindowManager {
|
||||
fn as_ref(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WindowManager> for State {
|
||||
@@ -93,9 +105,10 @@ impl From<&WindowManager> for State {
|
||||
has_pending_raise_op: wm.has_pending_raise_op,
|
||||
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
|
||||
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
|
||||
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(),
|
||||
layered_whitelist: LAYERED_WHITELIST.lock().clone(),
|
||||
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
|
||||
border_overflow_identifiers: BORDER_OVERFLOW_IDENTIFIERS.lock().clone(),
|
||||
name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +142,7 @@ impl EnforceWorkspaceRuleOp {
|
||||
impl WindowManager {
|
||||
#[tracing::instrument]
|
||||
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let home = HOME_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
@@ -161,6 +174,7 @@ impl WindowManager {
|
||||
virtual_desktop_id: current_virtual_desktop(),
|
||||
work_area_offset: None,
|
||||
window_container_behaviour: WindowContainerBehaviour::Create,
|
||||
unmanaged_window_operation_behaviour: OperationBehaviour::Op,
|
||||
resize_delta: 50,
|
||||
focus_follows_mouse: None,
|
||||
mouse_follows_focus: true,
|
||||
@@ -186,7 +200,7 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn watch_configuration(&mut self, enable: bool) -> 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");
|
||||
@@ -302,6 +316,7 @@ impl WindowManager {
|
||||
|
||||
should_update = true;
|
||||
}
|
||||
|
||||
if reference.size() != monitor.size() {
|
||||
monitor.set_size(Rect {
|
||||
left: reference.size().left,
|
||||
@@ -774,7 +789,29 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_to_monitor(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
fn handle_unmanaged_window_behaviour(&self) -> Result<()> {
|
||||
if let OperationBehaviour::NoOp = self.unmanaged_window_operation_behaviour {
|
||||
let workspace = self.focused_workspace()?;
|
||||
let focused_hwnd = WindowsApi::foreground_window()?;
|
||||
if !workspace.contains_managed_window(focused_hwnd) {
|
||||
return Err(anyhow!(
|
||||
"ignoring commands while active window is not managed by komorebi"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_to_monitor(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: Option<usize>,
|
||||
follow: bool,
|
||||
) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("moving container");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
@@ -798,17 +835,19 @@ impl WindowManager {
|
||||
.remove_focused_container()
|
||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||
|
||||
monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||
|
||||
let target_monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(idx)
|
||||
.get_mut(monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
target_monitor.add_container(container)?;
|
||||
target_monitor.add_container(container, workspace_idx)?;
|
||||
target_monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
target_monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||
|
||||
if follow {
|
||||
self.focus_monitor(idx)?;
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
|
||||
self.update_focused_workspace(self.mouse_follows_focus)
|
||||
@@ -816,6 +855,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("moving container");
|
||||
|
||||
let mouse_follows_focus = self.mouse_follows_focus;
|
||||
@@ -828,6 +869,7 @@ impl WindowManager {
|
||||
|
||||
self.update_focused_workspace(mouse_follows_focus)
|
||||
}
|
||||
|
||||
pub fn remove_focused_workspace(&mut self) -> Option<Workspace> {
|
||||
let focused_monitor: &mut Monitor = self.focused_monitor_mut()?;
|
||||
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
|
||||
@@ -859,7 +901,10 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("focusing container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let new_idx = workspace
|
||||
@@ -874,6 +919,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("moving container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -890,7 +937,22 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("focusing container");
|
||||
let mut maximize_next = false;
|
||||
let mut monocle_next = false;
|
||||
|
||||
if self.focused_workspace_mut()?.maximized_window().is_some() {
|
||||
maximize_next = true;
|
||||
self.unmaximize_window()?;
|
||||
}
|
||||
|
||||
if self.focused_workspace_mut()?.monocle_container().is_some() {
|
||||
monocle_next = true;
|
||||
self.monocle_off()?;
|
||||
}
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let new_idx = workspace
|
||||
@@ -898,13 +960,22 @@ impl WindowManager {
|
||||
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
||||
|
||||
workspace.focus_container(new_idx);
|
||||
self.focused_window_mut()?.focus(self.mouse_follows_focus)?;
|
||||
|
||||
if maximize_next {
|
||||
self.toggle_maximize()?;
|
||||
} else if monocle_next {
|
||||
self.toggle_monocle()?;
|
||||
} else {
|
||||
self.focused_window_mut()?.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("moving container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -921,6 +992,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("cycling container windows");
|
||||
|
||||
let container = self.focused_container_mut()?;
|
||||
@@ -943,6 +1016,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn add_window_to_container(&mut self, direction: OperationDirection) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("adding window to container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -979,6 +1054,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn promote_container_to_front(&mut self) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("promoting container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
@@ -988,6 +1065,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn remove_window_from_container(&mut self) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
tracing::info!("removing window");
|
||||
|
||||
if self.focused_container()?.windows().len() == 1 {
|
||||
@@ -1060,6 +1139,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn toggle_monocle(&mut self) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
match workspace.monocle_container() {
|
||||
@@ -1067,7 +1148,7 @@ impl WindowManager {
|
||||
Some(_) => self.monocle_off()?,
|
||||
}
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -1088,6 +1169,8 @@ impl WindowManager {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn toggle_maximize(&mut self) -> Result<()> {
|
||||
self.handle_unmanaged_window_behaviour()?;
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
match workspace.maximized_window() {
|
||||
@@ -1095,7 +1178,7 @@ impl WindowManager {
|
||||
Some(_) => self.unmaximize_window()?,
|
||||
}
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -1260,6 +1343,127 @@ impl WindowManager {
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn add_workspace_layout_default_rule(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
at_container_count: usize,
|
||||
layout: DefaultLayout,
|
||||
) -> Result<()> {
|
||||
tracing::info!("setting workspace layout");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
let offset = self.work_area_offset;
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let work_area = *monitor.work_area_size();
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let rules: &mut Vec<(usize, Layout)> = workspace.layout_rules_mut();
|
||||
rules.retain(|pair| pair.0 != at_container_count);
|
||||
rules.push((at_container_count, Layout::Default(layout)));
|
||||
rules.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
// If this is the focused workspace on a non-focused screen, let's update it
|
||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(self.update_focused_workspace(false)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn add_workspace_layout_custom_rule(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
at_container_count: usize,
|
||||
path: PathBuf,
|
||||
) -> Result<()> {
|
||||
tracing::info!("setting workspace layout");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
let offset = self.work_area_offset;
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let work_area = *monitor.work_area_size();
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let layout = CustomLayout::from_path_buf(path)?;
|
||||
|
||||
let rules: &mut Vec<(usize, Layout)> = workspace.layout_rules_mut();
|
||||
rules.retain(|pair| pair.0 != at_container_count);
|
||||
rules.push((at_container_count, Layout::Custom(layout)));
|
||||
rules.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
// If this is the focused workspace on a non-focused screen, let's update it
|
||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(self.update_focused_workspace(false)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn clear_workspace_layout_rules(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
) -> Result<()> {
|
||||
tracing::info!("setting workspace layout");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
let offset = self.work_area_offset;
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let work_area = *monitor.work_area_size();
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let rules: &mut Vec<(usize, Layout)> = workspace.layout_rules_mut();
|
||||
rules.clear();
|
||||
|
||||
// If this is the focused workspace on a non-focused screen, let's update it
|
||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(self.update_focused_workspace(false)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn set_workspace_layout_default(
|
||||
&mut self,
|
||||
|
||||
@@ -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),
|
||||
@@ -137,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,18 +1,17 @@
|
||||
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::PWSTR;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||
@@ -37,6 +36,7 @@ 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;
|
||||
@@ -66,6 +66,7 @@ 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;
|
||||
@@ -123,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 == HWND(0) {
|
||||
Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +142,7 @@ macro_rules! impl_process_windows_crate_result {
|
||||
);
|
||||
}
|
||||
|
||||
impl_process_windows_crate_result!(
|
||||
impl_process_windows_crate_integer_wrapper_result!(
|
||||
HWND => isize,
|
||||
);
|
||||
|
||||
@@ -265,7 +267,7 @@ impl WindowsApi {
|
||||
layout.top,
|
||||
layout.right,
|
||||
layout.bottom,
|
||||
flags,
|
||||
SET_WINDOW_POS_FLAGS(flags),
|
||||
)
|
||||
}
|
||||
.ok()
|
||||
@@ -295,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<()> {
|
||||
@@ -304,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)]
|
||||
@@ -351,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> {
|
||||
@@ -400,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)]
|
||||
@@ -436,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])?)
|
||||
@@ -452,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> {
|
||||
@@ -467,7 +465,12 @@ impl WindowsApi {
|
||||
let text_ptr = path.as_mut_ptr();
|
||||
|
||||
unsafe {
|
||||
QueryFullProcessImageNameW(handle, 0, PWSTR(text_ptr), std::ptr::addr_of_mut!(len))
|
||||
QueryFullProcessImageNameW(
|
||||
handle,
|
||||
PROCESS_NAME_WIN32,
|
||||
PWSTR(text_ptr),
|
||||
std::ptr::addr_of_mut!(len),
|
||||
)
|
||||
}
|
||||
.ok()
|
||||
.process()?;
|
||||
@@ -488,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])?)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||
@@ -85,7 +88,7 @@ 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;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Display)]
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Display, JsonSchema)]
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
pub enum WinEvent {
|
||||
|
||||
@@ -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::Axis;
|
||||
@@ -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>,
|
||||
@@ -40,6 +41,8 @@ pub struct Workspace {
|
||||
floating_windows: Vec<Window>,
|
||||
#[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<Axis>,
|
||||
#[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),
|
||||
@@ -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) {
|
||||
|
||||
@@ -96,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
|
||||
}
|
||||
@@ -184,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
|
||||
}
|
||||
@@ -240,24 +256,36 @@ WindowHidingBehaviour(hiding_behaviour) {
|
||||
Run, komorebic.exe window-hiding-behaviour %hiding_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) {
|
||||
@@ -278,4 +306,16 @@ ToggleMouseFollowsFocus() {
|
||||
|
||||
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.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
||||
categories = ["cli", "tiling-window-manager", "windows"]
|
||||
@@ -15,18 +15,20 @@ derive-ahk = { path = "../derive-ahk" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
clap = { version = "3", features = ["derive", "wrap_help"] }
|
||||
color-eyre = "0.5"
|
||||
color-eyre = "0.6"
|
||||
dirs = "4"
|
||||
fs-tail = "0.1"
|
||||
heck = "0.4"
|
||||
lazy_static = "1"
|
||||
paste = "1"
|
||||
powershell_script = "0.2"
|
||||
powershell_script = "0.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.8"
|
||||
uds_windows = "1"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.30"
|
||||
version = "0.35"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufRead;
|
||||
@@ -17,6 +18,7 @@ use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use fs_tail::TailedFile;
|
||||
use heck::ToKebabCase;
|
||||
use lazy_static::lazy_static;
|
||||
use paste::paste;
|
||||
use uds_windows::UnixListener;
|
||||
use uds_windows::UnixStream;
|
||||
@@ -27,18 +29,39 @@ use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||
|
||||
use derive_ahk::AhkFunction;
|
||||
use derive_ahk::AhkLibrary;
|
||||
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::Axis;
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::DefaultLayout;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::HidingBehaviour;
|
||||
use komorebi_core::OperationBehaviour;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
use komorebi_core::SocketMessage;
|
||||
use komorebi_core::StateQuery;
|
||||
|
||||
lazy_static! {
|
||||
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")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trait AhkLibrary {
|
||||
fn generate_ahk_library() -> String;
|
||||
}
|
||||
@@ -92,6 +115,7 @@ gen_enum_subcommand_args! {
|
||||
MouseFollowsFocus: BooleanState,
|
||||
Query: StateQuery,
|
||||
WindowHidingBehaviour: HidingBehaviour,
|
||||
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
||||
}
|
||||
|
||||
macro_rules! gen_target_subcommand_args {
|
||||
@@ -151,6 +175,15 @@ gen_workspace_subcommand_args! {
|
||||
Tiling: #[enum] BooleanState,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
pub struct ClearWorkspaceLayoutRules {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
|
||||
/// Workspace index on the specified monitor (zero-indexed)
|
||||
workspace: usize,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
pub struct WorkspaceCustomLayout {
|
||||
/// Monitor index (zero-indexed)
|
||||
@@ -163,6 +196,35 @@ pub struct WorkspaceCustomLayout {
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
pub struct WorkspaceLayoutRule {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
|
||||
/// Workspace index on the specified monitor (zero-indexed)
|
||||
workspace: usize,
|
||||
|
||||
/// The number of window containers on-screen required to trigger this layout rule
|
||||
at_container_count: usize,
|
||||
|
||||
layout: DefaultLayout,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
pub struct WorkspaceCustomLayoutRule {
|
||||
/// Monitor index (zero-indexed)
|
||||
monitor: usize,
|
||||
|
||||
/// Workspace index on the specified monitor (zero-indexed)
|
||||
workspace: usize,
|
||||
|
||||
/// The number of window containers on-screen required to trigger this layout rule
|
||||
at_container_count: usize,
|
||||
|
||||
/// JSON or YAML file from which the custom layout definition should be loaded
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct Resize {
|
||||
#[clap(arg_enum)]
|
||||
@@ -225,6 +287,14 @@ struct FocusMonitorWorkspace {
|
||||
target_workspace: usize,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
pub struct SendToMonitorWorkspace {
|
||||
/// Target monitor index (zero-indexed)
|
||||
target_monitor: usize,
|
||||
/// Workspace index on the target monitor (zero-indexed)
|
||||
target_workspace: usize,
|
||||
}
|
||||
|
||||
macro_rules! gen_padding_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
@@ -286,7 +356,9 @@ gen_application_target_subcommand_args! {
|
||||
FloatRule,
|
||||
ManageRule,
|
||||
IdentifyTrayApplication,
|
||||
IdentifyBorderOverflow,
|
||||
IdentifyLayeredApplication,
|
||||
IdentifyObjectNameChangeApplication,
|
||||
IdentifyBorderOverflowApplication,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
@@ -352,6 +424,20 @@ struct Unsubscribe {
|
||||
named_pipe: String,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct AhkAppSpecificConfiguration {
|
||||
/// YAML file from which the application-specific configurations should be loaded
|
||||
path: String,
|
||||
/// Optional YAML file of overrides to apply over the first file
|
||||
override_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct FormatAppSpecificConfiguration {
|
||||
/// YAML file from which the application-specific configurations should be loaded
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)]
|
||||
struct Opts {
|
||||
@@ -368,13 +454,13 @@ enum SubCommand {
|
||||
/// Show a JSON representation of the current window manager state
|
||||
State,
|
||||
/// Query the current window manager state
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Query(Query),
|
||||
/// Subscribe to komorebi events
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Subscribe(Subscribe),
|
||||
/// Unsubscribe from komorebi events
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Unsubscribe(Unsubscribe),
|
||||
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||
Log,
|
||||
@@ -385,120 +471,132 @@ enum SubCommand {
|
||||
#[clap(alias = "quick-load")]
|
||||
QuickLoadResize,
|
||||
/// Save the current resize layout dimensions to a file
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "save")]
|
||||
SaveResize(SaveResize),
|
||||
/// Load the resize layout dimensions from a file
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "load")]
|
||||
LoadResize(LoadResize),
|
||||
/// Change focus to the window in the specified direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Focus(Focus),
|
||||
/// Move the focused window in the specified direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Move(Move),
|
||||
/// Change focus to the window in the specified cycle direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleFocus(CycleFocus),
|
||||
/// Move the focused window in the specified cycle direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleMove(CycleMove),
|
||||
/// Stack the focused window in the specified direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Stack(Stack),
|
||||
/// Resize the focused window in the specified direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "resize")]
|
||||
ResizeEdge(Resize),
|
||||
/// Resize the focused window or primary column along the specified axis
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ResizeAxis(ResizeAxis),
|
||||
/// Unstack the focused window
|
||||
Unstack,
|
||||
/// Cycle the focused stack in the specified cycle direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleStack(CycleStack),
|
||||
/// Move the focused window to the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MoveToMonitor(MoveToMonitor),
|
||||
/// Move the focused window to the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MoveToWorkspace(MoveToWorkspace),
|
||||
/// Send the focused window to the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
SendToMonitor(SendToMonitor),
|
||||
/// Send the focused window to the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
SendToWorkspace(SendToWorkspace),
|
||||
/// Send the focused window to the specified monitor workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
SendToMonitorWorkspace(SendToMonitorWorkspace),
|
||||
/// Focus the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusMonitor(FocusMonitor),
|
||||
/// Focus the specified workspace on the focused monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusWorkspace(FocusWorkspace),
|
||||
/// Focus the specified workspace on the target monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusMonitorWorkspace(FocusMonitorWorkspace),
|
||||
/// Focus the monitor in the given cycle direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleMonitor(CycleMonitor),
|
||||
/// Focus the workspace in the given cycle direction
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleWorkspace(CycleWorkspace),
|
||||
/// Move the focused workspace to the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
||||
/// Create and append a new workspace on the focused monitor
|
||||
NewWorkspace,
|
||||
/// Set the resize delta (used by resize-edge and resize-axis)
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ResizeDelta(ResizeDelta),
|
||||
/// Set the invisible border dimensions around each window
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
InvisibleBorders(InvisibleBorders),
|
||||
/// Set offsets to exclude parts of the work area from tiling
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkAreaOffset(WorkAreaOffset),
|
||||
/// Adjust container padding on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
AdjustContainerPadding(AdjustContainerPadding),
|
||||
/// Adjust workspace padding on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
AdjustWorkspacePadding(AdjustWorkspacePadding),
|
||||
/// Set the layout on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ChangeLayout(ChangeLayout),
|
||||
/// Load a custom layout from file for the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
LoadCustomLayout(LoadCustomLayout),
|
||||
/// Flip the layout on the focused workspace (BSP only)
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FlipLayout(FlipLayout),
|
||||
/// Promote the focused window to the top of the tree
|
||||
Promote,
|
||||
/// Force the retiling of all managed windows
|
||||
Retile,
|
||||
/// Create at least this many workspaces for the specified monitor
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
EnsureWorkspaces(EnsureWorkspaces),
|
||||
/// Set the container padding for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ContainerPadding(ContainerPadding),
|
||||
/// Set the workspace padding for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspacePadding(WorkspacePadding),
|
||||
/// Set the layout for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceLayout(WorkspaceLayout),
|
||||
/// Set a custom layout for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceCustomLayout(WorkspaceCustomLayout),
|
||||
/// Add a dynamic layout rule for the specified workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceLayoutRule(WorkspaceLayoutRule),
|
||||
/// Add a dynamic custom layout for the specified workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceCustomLayoutRule(WorkspaceCustomLayoutRule),
|
||||
/// Clear all dynamic layout rules for the specified workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ClearWorkspaceLayoutRules(ClearWorkspaceLayoutRules),
|
||||
/// Enable or disable window tiling for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceTiling(WorkspaceTiling),
|
||||
/// Set the workspace name for the specified workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceName(WorkspaceName),
|
||||
/// Toggle the behaviour for new windows (stacking or dynamic tiling)
|
||||
ToggleWindowContainerBehaviour,
|
||||
@@ -521,43 +619,63 @@ enum SubCommand {
|
||||
/// Reload ~/komorebi.ahk (if it exists)
|
||||
ReloadConfiguration,
|
||||
/// Enable or disable watching of ~/komorebi.ahk (if it exists)
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WatchConfiguration(WatchConfiguration),
|
||||
/// Set the window behaviour when switching workspaces / cycling stacks
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WindowHidingBehaviour(WindowHidingBehaviour),
|
||||
/// Set the operation behaviour when the focused window is not managed
|
||||
#[clap(arg_required_else_help = true)]
|
||||
UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour),
|
||||
/// Add a rule to always float the specified application
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FloatRule(FloatRule),
|
||||
/// Add a rule to always manage the specified application
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ManageRule(ManageRule),
|
||||
/// Add a rule to associate an application with a workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
WorkspaceRule(WorkspaceRule),
|
||||
/// Identify an application that sends EVENT_OBJECT_NAMECHANGE on launch
|
||||
#[clap(arg_required_else_help = true)]
|
||||
IdentifyObjectNameChangeApplication(IdentifyObjectNameChangeApplication),
|
||||
/// Identify an application that closes to the system tray
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
IdentifyTrayApplication(IdentifyTrayApplication),
|
||||
/// Identify an application that has WS_EX_LAYERED, but should still be managed
|
||||
#[clap(arg_required_else_help = true)]
|
||||
IdentifyLayeredApplication(IdentifyLayeredApplication),
|
||||
/// Identify an application that has overflowing borders
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
IdentifyBorderOverflow(IdentifyBorderOverflow),
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "identify-border-overflow")]
|
||||
IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),
|
||||
/// Enable or disable focus follows mouse for the operating system
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusFollowsMouse(FocusFollowsMouse),
|
||||
/// Toggle focus follows mouse for the operating system
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
ToggleFocusFollowsMouse(ToggleFocusFollowsMouse),
|
||||
/// Enable or disable mouse follows focus on all workspaces
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MouseFollowsFocus(MouseFollowsFocus),
|
||||
/// Toggle mouse follows focus on all workspaces
|
||||
ToggleMouseFollowsFocus,
|
||||
/// Generate a library of AutoHotKey helper functions
|
||||
AhkLibrary,
|
||||
/// Generate common app-specific configurations and fixes to use in komorebi.ahk
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "ahk-asc")]
|
||||
AhkAppSpecificConfiguration(AhkAppSpecificConfiguration),
|
||||
/// Format a YAML file for use with the 'ahk-app-specific-configuration' command
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "fmt-asc")]
|
||||
FormatAppSpecificConfiguration(FormatAppSpecificConfiguration),
|
||||
/// Generate a JSON Schema of subscription notifications
|
||||
NotificationSchema,
|
||||
}
|
||||
|
||||
pub fn send_message(bytes: &[u8]) -> Result<()> {
|
||||
let mut socket = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let mut socket = HOME_DIR.clone();
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
@@ -571,8 +689,7 @@ fn main() -> Result<()> {
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::AhkLibrary => {
|
||||
let mut library =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let mut library = HOME_DIR.clone();
|
||||
library.push("komorebic.lib.ahk");
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
@@ -580,7 +697,10 @@ fn main() -> Result<()> {
|
||||
.truncate(true)
|
||||
.open(library.clone())?;
|
||||
|
||||
file.write_all(SubCommand::generate_ahk_library().as_bytes())?;
|
||||
let output: String = SubCommand::generate_ahk_library();
|
||||
let fixed_output = output.replace("%id%", "\"%id%\"");
|
||||
|
||||
file.write_all(fixed_output.as_bytes())?;
|
||||
|
||||
println!(
|
||||
"\nAHK helper library for komorebic written to {}",
|
||||
@@ -637,6 +757,15 @@ fn main() -> Result<()> {
|
||||
SubCommand::SendToWorkspace(arg) => {
|
||||
send_message(&*SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::SendToMonitorWorkspace(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::SendContainerToMonitorWorkspaceNumber(
|
||||
arg.target_monitor,
|
||||
arg.target_workspace,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::MoveWorkspaceToMonitor(arg) => {
|
||||
send_message(&*SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
|
||||
}
|
||||
@@ -715,6 +844,34 @@ fn main() -> Result<()> {
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::WorkspaceLayoutRule(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceLayoutRule(
|
||||
arg.monitor,
|
||||
arg.workspace,
|
||||
arg.at_container_count,
|
||||
arg.layout,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::WorkspaceCustomLayoutRule(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceLayoutCustomRule(
|
||||
arg.monitor,
|
||||
arg.workspace,
|
||||
arg.at_container_count,
|
||||
resolve_windows_path(&arg.path)?,
|
||||
)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::ClearWorkspaceLayoutRules(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::ClearWorkspaceLayoutRules(arg.monitor, arg.workspace)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::WorkspaceTiling(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceTiling(arg.monitor, arg.workspace, arg.value.into())
|
||||
@@ -846,7 +1003,7 @@ fn main() -> Result<()> {
|
||||
)?;
|
||||
}
|
||||
SubCommand::State => {
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let home = HOME_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
@@ -880,7 +1037,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
SubCommand::Query(arg) => {
|
||||
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let home = HOME_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
@@ -914,8 +1071,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
SubCommand::RestoreWindows => {
|
||||
let mut hwnd_json =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
||||
let mut hwnd_json = HOME_DIR.clone();
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
|
||||
let file = File::open(hwnd_json)?;
|
||||
@@ -948,15 +1104,28 @@ fn main() -> Result<()> {
|
||||
&*SocketMessage::WatchConfiguration(arg.boolean_state.into()).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::IdentifyObjectNameChangeApplication(target) => {
|
||||
send_message(
|
||||
&*SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::IdentifyTrayApplication(target) => {
|
||||
send_message(
|
||||
&*SocketMessage::IdentifyTrayApplication(target.identifier, target.id)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::IdentifyBorderOverflow(target) => {
|
||||
SubCommand::IdentifyLayeredApplication(target) => {
|
||||
send_message(
|
||||
&*SocketMessage::IdentifyBorderOverflow(target.identifier, target.id).as_bytes()?,
|
||||
&*SocketMessage::IdentifyLayeredApplication(target.identifier, target.id)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::IdentifyBorderOverflowApplication(target) => {
|
||||
send_message(
|
||||
&*SocketMessage::IdentifyBorderOverflowApplication(target.identifier, target.id)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::Manage => {
|
||||
@@ -998,6 +1167,97 @@ fn main() -> Result<()> {
|
||||
SubCommand::WindowHidingBehaviour(arg) => {
|
||||
send_message(&*SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::UnmanagedWindowOperationBehaviour(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::AhkAppSpecificConfiguration(arg) => {
|
||||
let content = fs::read_to_string(resolve_windows_path(&arg.path)?)?;
|
||||
let lines = if let Some(override_path) = arg.override_path {
|
||||
let override_content = fs::read_to_string(resolve_windows_path(&override_path)?)?;
|
||||
|
||||
ApplicationConfigurationGenerator::generate_ahk(
|
||||
&content,
|
||||
Option::from(override_content.as_str()),
|
||||
)?
|
||||
} else {
|
||||
ApplicationConfigurationGenerator::generate_ahk(&content, None)?
|
||||
};
|
||||
|
||||
let mut generated_config = HOME_DIR.clone();
|
||||
generated_config.push("komorebi.generated.ahk");
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(generated_config.clone())?;
|
||||
|
||||
file.write_all(lines.join("\n").as_bytes())?;
|
||||
|
||||
println!(
|
||||
"\nApplication-specific generated configuration written to {}",
|
||||
generated_config.to_str().ok_or_else(|| anyhow!(
|
||||
"could not find the path to the generated configuration file"
|
||||
))?
|
||||
);
|
||||
|
||||
println!(
|
||||
"\nYou can include the generated configuration at the top of your komorebi.ahk config with this line:"
|
||||
);
|
||||
|
||||
println!("\n#Include %A_ScriptDir%\\komorebi.generated.ahk");
|
||||
}
|
||||
SubCommand::FormatAppSpecificConfiguration(arg) => {
|
||||
let file_path = resolve_windows_path(&arg.path)?;
|
||||
let content = fs::read_to_string(&file_path)?;
|
||||
let formatted_content = ApplicationConfigurationGenerator::format(&content)?;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(file_path)?;
|
||||
|
||||
file.write_all(formatted_content.as_bytes())?;
|
||||
|
||||
println!("File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration");
|
||||
}
|
||||
SubCommand::NotificationSchema => {
|
||||
let home = HOME_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&*SocketMessage::NotificationSchema.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user