Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
987dc2b8dd feat(wm): add cmds for titlebar removal
This commit introduces some basic commands to read from/add to a
whitelist of applications which that user has determined that are safe
to remove the titlebar from.

Titlebars and removed and added by removing and adding the WS_CAPTION
and WS_THICKFRAME styles (this is the approach that Workspacer also
takes) from the windows.

Wherever possible, the user should prefer native preferences and
settings that remove the titlebar in popular apps (Windows Terminal,
Firefox etc).

re #57
2021-10-27 17:37:57 -07:00
55 changed files with 2177 additions and 5595 deletions

1
.github/FUNDING.yml vendored
View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
assignees:
- "LGUG2Z"
commit-message:

View File

@@ -28,7 +28,7 @@ jobs:
target:
- x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
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@v3
uses: actions/cache@v2
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@v3
uses: actions/cache@v2
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') }}
@@ -76,20 +76,13 @@ jobs:
- name: Run a full build
run: |
cargo build --locked --release --target ${{ matrix.target }}
- name: Create MSI installer
run: |
cargo install cargo-wix
cargo wix -p komorebi --nocapture -I .\wix\main.wxs --target x86_64-pc-windows-msvc
- name: Upload the built artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
name: komorebi-${{ matrix.target }}
path: |
target/${{ matrix.target }}/release/komorebi.exe
target/${{ matrix.target }}/release/komorebic.exe
target/${{ matrix.target }}/release/komorebi.pdb
target/${{ matrix.target }}/release/komorebic.pdb
target/wix/komorebi-*.msi
retention-days: 7
- name: Generate changelog
if: startsWith(github.ref, 'refs/tags/')
@@ -98,7 +91,7 @@ jobs:
if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi
kokai release --no-emoji --add-links github:commits,issues --ref "$(git tag --points-at HEAD)" >"CHANGELOG.md"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
uses: goreleaser/goreleaser-action@v2
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest
@@ -106,8 +99,3 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
- name: Add MSI to release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: "target/wix/komorebi-*.msi"

View File

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

877
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

381
README.md
View File

@@ -2,14 +2,7 @@
Tiling Window Management for Windows.
![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/LGUG2Z/komorebi/Windows/master)
![GitHub](https://img.shields.io/github/license/LGUG2Z/komorebi)
![GitHub all releases](https://img.shields.io/github/downloads/LGUG2Z/komorebi/total)
![GitHub commits since latest release (by date) for a branch](https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest/master)
![Discord](https://img.shields.io/discord/898554690126630914?label=discord)
![GitHub Sponsors](https://img.shields.io/github/sponsors/LGUG2Z)
![screenshot](https://user-images.githubusercontent.com/13164844/184027064-f5a6cec2-2865-4d65-a549-a1f1da589abf.png)
![screenshot](https://i.ibb.co/BTqNS45/komorebi.png)
## About
@@ -25,53 +18,10 @@ 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/mGkn66PHkx) available for _komorebi_-related discussion, help,
There is a [Discord server](https://discord.gg/vzBmPm6RkQ) available for _komorebi_-related discussion, help,
troubleshooting etc. If you have any specific feature requests or bugs to report, please create an issue in this
repository.
Articles, blog posts, demos, and videos about _komorebi_ can be added to this list by PR:
- [Moving to Windows from Linux Pt 1](https://kvwu.io/posts/moving-to-windows/)
- [Windows 下的现代化平铺窗口管理器 komorebi](https://zhuanlan.zhihu.com/p/455064481)
## GitHub Sponsors Early Access
[GitHub Sponsors is enabled for this project](https://github.com/sponsors/LGUG2Z). Users who sponsor my work
on `komorebi` at any of the predefined monthly tiers will be given access to a private fork of this repository where I
push features-in-progress that are not yet quite ready to be pushed on the main repository.
There will never be any feature of `komorebi` that is gated behind sponsorship; every new feature will always be
available for free in the public repository once it meets the requisite level of code quality and completion.
Features-in-progress that are available in early access will be tagged in the issues with
an ["early access" label](https://github.com/LGUG2Z/komorebi/issues?q=is%3Aopen+is%3Aissue+label%3A%22early+access%22).
## Charitable Donations
`komorebi`, like `vim`, is a free and open-source project, and one that encourages you to make charitable donations if
you find the software to be useful and have the financial means.
I encourage you to make a charitable donation
to [Fresh Start Refugee](https://www.freshstartrefugee.org/donate) before
you consider sponsoring me on GitHub.
## 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
@@ -133,19 +83,13 @@ 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:
```powershell
scoop bucket add extras
```
scoop bucket add komorebi https://github.com/LGUG2Z/komorebi-bucket
scoop install komorebi
# To download the example configuration
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.sample.ahk -OutFile $Env:USERPROFILE\komorebi.ahk
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebic.lib.ahk -OutFile $Env:USERPROFILE\komorebic.lib.ahk
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/master/komorebi.generated.ahk -OutFile $Env:USERPROFILE\komorebi.generated.ahk
```
If you install _komorebi_ using Scoop, the binaries will automatically be added to your `Path`.
Thanks to [@sitiom](https://github.com/sitiom) for getting _komorebi_ added to the popular Scoop Extras bucket.
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.
### Building from Source
@@ -165,7 +109,7 @@ cargo install --path komorebic --locked
Once you have either the prebuilt binaries in your `Path`, or have compiled the binaries from source (these will already
be in your `Path` if you installed Rust with [rustup](https://rustup.rs), which you absolutely should), you can
run `komorebic start --await-configuration` at a Powershell prompt, and you will see the following output:
run `komorebic start` at a Powershell prompt, and you will see the following output:
```
Start-Process komorebi -WindowStyle hidden
@@ -187,126 +131,8 @@ the `AutoHotKey64.exe` executable for AutoHotKey v2 is in your `Path`. If both `
exist in your home directory, only `komorebi.ahk` will be loaded. An example of an AutoHotKey v2 configuration file
for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778dabf693ce9236c57b201cd).
#### Using Different AHK Executables
The preferred way to install AutoHotKey for use with `komorebi` is to install it via `scoop`:
```powershell
scoop install autohotkey
```
If you install AutoHotKey using a different method, the name of the executable file may differ from the name given by
`scoop`, and thus what is expected by default in `komorebi`.
You may override the executables that `komorebi` looks for to launch and reload `komorebi.ahk` configuration files using
by setting one of the following two environment variables depending on which version of AutoHotKey you wish to use:
- `$Env:KOMOREBI_AHK_V1_EXE`
- `$Env:KOMOREBI_AHK_V2_EXE`
Please keep in mind that even when setting a custom executable name using these environment variables, the executables
are still required to be in your `Path`.
### Common First-Time Tips
#### Generating Common Application-Specific Configurations
A curated selection of application-specific configurations can be generated to
help ease the setup for first-time users.
[`komorebi-application-specific-configuration`](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
contains YAML definitions of settings that are known to make tricky
applications behave as expected. These YAML definitions can be used to generate
an AHK file which you can import at the start of your own `komorebi.ahk` file,
leaving you to focus primarily on your desired keybindings and workspace
configurations.
If you have settings for an application that you think should be part of this
curated selection, please open a PR on the configuration repository.
In the event that your PR is not accepted, or if you find there are any
settings that you wish to override, this can easily be done using an override
file.
```powershell
# Clone and enter the repository
git clone https://github.com/LGUG2Z/komorebi-application-specific-configuration.git
cd komorebi-application-specific-configuration
# Use komorebic to generate an AHK file
komorebic.exe ahk-app-specific-configuration applications.yaml
# Application-specific generated configuration written to C:\Users\LGUG2Z\.config\komorebi\komorebi.generated.ahk
#
# You can include the generated configuration at the top of your komorebi.ahk config with this line:
#
# #Include %A_ScriptDir%\komorebi.generated.ahk
# Optionally, provide an override file that follows the same schema as the second argument
komorebic.exe ahk-app-specific-configuration applications.yaml overrides.yaml
```
#### Setting a Custom KOMOREBI_CONFIG_HOME Directory
If you do not want to keep _komorebi_-related files in your `$Env:UserProfile` directory, you can specify a custom directory
by setting the `$Env:KOMOREBI_CONFIG_HOME` environment variable.
For example, to use the `~/.config/komorebi` directory:
```powershell
# Run this command to make sure that the directory has been created
mkdir -p ~/.config/komorebi
# Run this command to open up your PowerShell profile configuration in Notepad
notepad $PROFILE
# Add this line (with your login user!) to the bottom of your PowerShell profile configuration
$Env:KOMOREBI_CONFIG_HOME = 'C:\Users\LGUG2Z\.config\komorebi'
# Save the changes and then reload the PowerShell profile
. $PROFILE
```
If you already have configuration files that you wish to keep, move them to the `~/.config/komorebi` directory.
The next time you run `komorebic start`, any files created by or loaded by _komorebi_ will be placed or expected to
exist in this folder.
#### Adding an Active Window Border
If you would like to add a visual border around the currently focused window, two commands are available:
```powershell
komorebic.exe active-window-border [enable|disable]
komorebic.exe active-window-border-colour [R G B] --window-kind single
# optionally, if you want a different colour for stacks of windows
komorebic.exe active-window-border-colour [R G B] --window-kind stack
```
It is important to note that the active window border will only apply to windows managed by `komorebi`.
#### Removing Gaps
If you would like to remove all gaps from a given workspace, both between windows themselves, and between the monitor edges and the windows, you can set the following two configuration options to `0` for the desired monitors and workspaces:
```powershell
komorebic.exe container-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
komorebic.exe workspace padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
```
#### Multiple Layout Changes on Startup
Depending on what is in your configuration, when `komorebi` is started, you may experience the layout rapidly being adjusted
with many retile events.
If you would like to avoid this, you can start `komorebi` with a flag which tells `komorebi` to wait until all configuration
has been loaded before listening to and responding to window manager events: `komorebic start --await-configuration`.
If you start `komorebi` with the `--await-configuration` flag, you _must_ send the `komorebic complete-configuration`
command at the end of the configuration section of your `komorebi.ahk` config (before you start defining the key
bindings). The layout will not be updated and `komorebi` will not respond to `komorebic` commands until this command has
been received.
#### Floating Windows
Sometimes you will want a specific application to never be tiled, and instead float all the time. You add add rules to
@@ -342,24 +168,6 @@ 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
@@ -380,16 +188,6 @@ passing it as an argument to the `--implementation` flag:
komorebic.exe toggle-focus-follows-mouse --implementation komorebi
```
#### Mouse Follows Focus
By default, the mouse will move to the center of the window when the focus is changed in a given direction. This
behaviour is know is 'mouse follows focus'. To disable this behaviour across all workspaces, add the following command
to your configuration file:
```ahk
Run, komorebic.exe toggle-mouse-follows-focus, , Hide
```
#### Saving and Loading Resized Layouts
If you create a BSP layout through various resize adjustments that you want to be able to restore easily in the future,
@@ -454,38 +252,15 @@ YAML
```yaml
- column: Secondary
configuration: !Horizontal 2 # max number of rows
configuration:
Horizontal: 2 # max number of rows,
- column: Primary
configuration: !WidthPercentage 50 # percentage of screen
configuration:
WidthPercentage: 45 # percentage of screen
- column: Tertiary
configuration: Horizontal
```
#### Dynamically Changing Layouts Based on Number of Visible Window Containers
With `komorebi` it is possible to define rules to automatically change the layout on a specified workspace when a
threshold of window containers is met.
```powershell
# On the first workspace of the first monitor (0 0)
# When there are one or more window containers visible on the screen (1)
# Use the bsp layout (bsp)
komorebic workspace-layout-rule 0 0 1 bsp
# On the first workspace of the first monitor (0 0)
# When there are five or more window containers visible on the screen (five)
# Use the custom layout stored in the home directory (~/custom.yaml)
komorebic workspace-custom-layout-rule 0 0 5 ~/custom.yaml
```
However, if you add workspace layout rules, you will not be able to manually change the layout of a workspace until all
layout rules for that workspace have been cleared.
```powershell
# If you decide that workspace layout rules are not for you, you can remove them from that same workspace like this
komorebic clear-workspace-layout-rules 0 0
```
## Configuration with `komorebic`
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
@@ -496,6 +271,72 @@ You can run `komorebic.exe` to get a full list of the commands that you can use
keybindings with. You can run `komorebic.exe <COMMAND> --help` to get a full explanation of the arguments required for
each command.
```
start Start komorebi.exe as a background process
stop Stop the komorebi.exe process and restore all hidden windows
state Show a JSON representation of the current window manager state
query Query the current window manager state
subscribe Subscribe to komorebi events
unsubscribe Unsubscribe from komorebi events
log Tail komorebi.exe's process logs (cancel with Ctrl-C)
quick-save Quicksave the current resize layout dimensions
quick-load Load the last quicksaved resize layout dimensions
save Save the current resize layout dimensions to a file
load Load the resize layout dimensions from a file
focus Change focus to the window in the specified direction
move Move the focused window in the specified direction
cycle-focus Change focus to the window in the specified cycle direction
cycle-move Move the focused window in the specified cycle direction
stack Stack the focused window in the specified direction
resize Resize the focused window in the specified direction
unstack Unstack the focused window
cycle-stack Cycle the focused stack in the specified cycle direction
move-to-monitor Move the focused window to the specified monitor
move-to-workspace Move the focused window to the specified workspace
send-to-monitor Send the focused window to the specified monitor
send-to-workspace Send the focused window to the specified workspace
focus-monitor Focus the specified monitor
focus-workspace Focus the specified workspace on the focused monitor
cycle-monitor Focus the monitor in the given cycle direction
cycle-workspace Focus the workspace in the given cycle direction
new-workspace Create and append a new workspace on the focused monitor
invisible-borders Set the invisible border dimensions around each window
work-area-offset Set offsets to exclude parts of the work area from tiling
adjust-container-padding Adjust container padding on the focused workspace
adjust-workspace-padding Adjust workspace padding on the focused workspace
change-layout Set the layout on the focused workspace
load-custom-layout Load a custom layout from file for the focused workspace
flip-layout Flip the layout on the focused workspace (BSP only)
promote Promote the focused window to the top of the tree
retile Force the retiling of all managed windows
ensure-workspaces Create at least this many workspaces for the specified monitor
container-padding Set the container padding for the specified workspace
workspace-padding Set the workspace padding for the specified workspace
workspace-layout Set the layout for the specified workspace
workspace-custom-layout Set a custom layout for the specified workspace
workspace-tiling Enable or disable window tiling for the specified workspace
workspace-name Set the workspace name for the specified workspace
toggle-pause Toggle the window manager on and off across all monitors
toggle-tiling Toggle window tiling on the focused workspace
toggle-float Toggle floating mode for the focused window
toggle-monocle Toggle monocle mode for the focused container
toggle-maximize Toggle native maximization for the focused window
restore-windows Restore all hidden windows (debugging command)
manage Force komorebi to manage the focused window
unmanage Unmanage a window that was forcibly managed
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
float-rule Add a rule to always float the specified application
manage-rule Add a rule to always manage the specified application
workspace-rule Add a rule to associate an application with a workspace
identify-tray-application Identify an application that closes to the system tray
identify-border-overflow Identify an application that has overflowing borders
focus-follows-mouse Enable or disable focus follows mouse for the operating system
toggle-focus-follows-mouse Toggle focus follows mouse for the operating system
ahk-library Generate a library of AutoHotKey helper functions
help Print this message or the help of the given subcommand(s)
```
### AutoHotKey Helper Library for `komorebic`
Additionally, you may run `komorebic.exe ahk-library` to
@@ -513,19 +354,14 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Window stacks
- [x] Cycle through stacked windows
- [x] Change focused window by direction
- [x] Change focused window by direction across monitor boundary
- [x] Move focused window container in direction
- [x] Move focused window container in direction across monitor boundary
- [x] Move focused window container to monitor and follow
- [x] Move focused window container to workspace follow
- [x] Send focused window container to monitor
- [x] Send focused window container to workspace
- [x] Move focused workspace to monitor
- [x] Mouse follows focused container
- [x] Resize window container in direction
- [x] Resize window container on axis
- [x] Set custom resize delta
- [x] Active window border
- [ ] Resize child window containers by split ratio
- [x] Quicksave and quickload layouts with resize dimensions
- [x] Save and load layouts with resize dimensions to/from specific files
- [x] Mouse drag to swap window container position
@@ -539,18 +375,16 @@ 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
- [x] Identify applications which overflow their borders by exe name and class
- [x] Identify 'close/minimize to tray' applications by exe name and class
- [x] Configure work area offsets to preserve space for custom taskbars
- [x] Configure and compensate for the size of Windows invisible borders
- [x] Configure and compensate for the size of Windows 10's invisible borders
- [x] Toggle floating windows
- [x] Toggle monocle window
- [x] Toggle native maximization
- [x] Toggle mouse follows focus
- [x] Toggle Xmouse/Windows focus follows mouse implementation
- [x] Toggle Komorebi focus follows mouse implementation (desktop and system tray-aware)
- [x] Toggle automatic tiling
@@ -588,14 +422,14 @@ the IDE for completions and navigation:
## Logs and Debugging
Logs from `komorebi` will be appended to `%LOCALAPPDATA%/komorebi/komorebi.log`; this file is never rotated or overwritten, so it will keep
Logs from `komorebi` will be appended to `~/komorebi.log`; this file is never rotated or overwritten, so it will keep
growing until it is deleted by the user.
Whenever running the `komorebic stop` command or sending a Ctrl-C signal to `komorebi` directly, the `komorebi` process
ensures that all hidden windows are restored before termination.
If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known
to `komorebi` are stored and continuously updated in `%LOCALAPPDATA%/komorebi//komorebi.hwnd.json`.
to `komorebi` are stored and continuously updated in `~/komorebi.hwnd.json`.
### Restoring Windows
@@ -636,16 +470,16 @@ Note that you do not have to include the full path of the named pipe, just the n
If the named pipe exists, `komorebi` will start pushing JSON data of successfully handled events and messages:
```json lines
{"event":{"type":"AddSubscriber","content":"yasb"},"state":{}}
{"event":{"type":"FocusWindow","content":"Left"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{}}
{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{}}
{"event":{"type":"FocusWindow","content":"Right"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}}
{"event":{"type":"FocusWindow","content":"Down"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{}}
{"event":{"type":"FocusWindow","content":"Up"},"state":{}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}}
{"event":{"type":"AddSubscriber","content":"yasb"},"state":{...}}
{"event":{"type":"FocusWindow","content":"Left"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{...}}
{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{...}}
{"event":{"type":"FocusWindow","content":"Right"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}...}
{"event":{"type":"FocusWindow","content":"Down"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{...}}
{"event":{"type":"FocusWindow","content":"Up"},"state":{...}}
{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{...}}
```
You may then filter on the `type` key to listen to the events that you are interested in. For a full list of possible
@@ -655,36 +489,3 @@ 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).
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Rust can also be found
in the [`komokana`](https://github.com/LGUG2Z/komokana) repository.
### 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.
### Communication over TCP
A TCP listener can optionally be exposed on a port of your choosing with the `--tcp-port=N` flag. If this flag is not
provided to `komorebi` or `komorebic start`, no TCP listener will be created.
Once created, your client may send
any [SocketMessage](https://github.com/LGUG2Z/komorebi/blob/master/komorebi-core/src/lib.rs#L37) to `komorebi` in the
same way that `komorebic` would.
This can be used if you would like to create your own alternative to `komorebic` which incorporates scripting and
various middleware layers, and similarly it can be used if you would like to integrate `komorebi` with
a [custom input handler](https://github.com/LGUG2Z/komorebi/issues/176#issue-1302643961).
If a client sends an unrecognized message, it will be disconnected and have to reconnect before trying to communicate
again.
### Socket Message Schema
A [JSON Schema](https://json-schema.org/) of socket messages used to send instructions to `komorebi` can be generated
with the `komorebic socket-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.

13
bindings/Cargo.toml Normal file
View File

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

26
bindings/build.rs Normal file
View File

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

4
bindings/src/lib.rs Normal file
View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "derive-ahk"
version = "0.1.1"
version = "0.1.0"
edition = "2021"
[lib]

View File

@@ -5,15 +5,12 @@
use ::std::clone::Clone;
use ::std::convert::From;
use ::std::convert::Into;
use ::std::format;
use ::std::iter::Extend;
use ::std::iter::Iterator;
use ::std::matches;
use ::std::option::Option::Some;
use ::std::string::String;
use ::std::string::ToString;
use ::std::unreachable;
use ::std::vec::Vec;
use ::quote::quote;
use ::syn::parse_macro_input;
@@ -105,8 +102,7 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
let flag_idents_clone = flag_idents.clone();
let flags = quote! {#(--#flag_idents_clone) *}
.to_string()
.replace("- - ", "--")
.replace('_', "-");
.replace("- - ", "--");
let called_flag_arguments = quote! {#(%#flag_idents%) *}
.to_string()
@@ -114,28 +110,19 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
.replace("% ", "%")
.replace("%%", "% %");
let flags_split: Vec<_> = flags.split(' ').collect();
let flag_args_split: Vec<_> = called_flag_arguments.split(' ').collect();
let mut consolidated_flags: Vec<String> = Vec::new();
for (idx, flag) in flags_split.iter().enumerate() {
consolidated_flags.push(format!("{} {}", flag, flag_args_split[idx]));
}
let all_flags = consolidated_flags.join(" ");
quote! {
impl AhkFunction for #name {
fn generate_ahk_function() -> String {
::std::format!(r#"
{}({}) {{
Run, komorebic.exe {} {} {}, , Hide
Run, komorebic.exe {} {} {} {}, , Hide
}}"#,
::std::stringify!(#name),
#all_arguments,
::std::stringify!(#name).to_kebab_case(),
#called_arguments,
#all_flags,
#flags,
#called_flag_arguments
)
}
}

View File

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

View File

@@ -1,21 +1,16 @@
[package]
name = "komorebi-core"
version = "0.1.13"
version = "0.1.6"
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.6"
bindings = { package = "bindings", path = "../bindings" }
clap = "3.0.0-beta.4"
color-eyre = "0.5"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
strum = { version = "0.24", features = ["derive"] }
schemars = "0.8"
[dependencies.windows]
version = "0.39"
features = [
"Win32_Foundation",
]
serde_yaml = "0.8"
strum = { version = "0.21", features = ["derive"] }

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,12 @@
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, JsonSchema)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[strum(serialize_all = "snake_case")]
pub enum CycleDirection {
Previous,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,348 +0,0 @@
; Generated by komorebic.exe
; To use this file, add the line below to the top of your komorebi.ahk configuration file
; #Include %A_ScriptDir%\komorebi.generated.ahk
; 1Password
Run, komorebic.exe float-rule exe "1Password.exe", , Hide
; Adobe Creative Cloud
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application class "CreativeCloudDesktopWindowClass", , Hide
; AutoHotkey
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "AutoHotkeyU64.exe", , Hide
Run, komorebic.exe float-rule title "Window Spy", , Hide
; Beeper
Run, komorebic.exe identify-border-overflow-application exe "Beeper.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Beeper.exe", , Hide
; Bitwarden
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Bitwarden.exe", , Hide
; Calculator
Run, komorebic.exe float-rule title "Calculator", , Hide
; Credential Manager UI Host
; Targets the Windows popup prompting you for a PIN instead of a password on 1Password etc.
Run, komorebic.exe float-rule exe "CredentialUIBroker.exe", , Hide
; Cron
Run, komorebic.exe identify-border-overflow-application exe "Cron.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Cron.exe", , Hide
; Discord
Run, komorebic.exe identify-border-overflow-application exe "Discord.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Discord.exe", , Hide
; DiscordCanary
Run, komorebic.exe identify-border-overflow-application exe "DiscordCanary.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "DiscordCanary.exe", , Hide
; DiscordDevelopment
Run, komorebic.exe identify-border-overflow-application exe "DiscordDeveloper.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "DiscordDeveloper.exe", , Hide
; DiscordPTB
Run, komorebic.exe identify-border-overflow-application exe "DiscordPTB.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "DiscordPTB.exe", , Hide
; ElectronMail
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ElectronMail.exe", , Hide
; Element
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Element.exe", , Hide
; ElevenClock
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ElevenClock.exe", , Hide
; Epic Games Launcher
Run, komorebic.exe identify-border-overflow-application exe "EpicGamesLauncher.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "EpicGamesLauncher.exe", , Hide
; Flow Launcher
Run, komorebic.exe identify-border-overflow-application exe "Flow.Launcher.exe", , Hide
; GOG Galaxy
Run, komorebic.exe identify-border-overflow-application exe "GalaxyClient.exe", , Hide
Run, komorebic.exe manage-rule exe "GalaxyClient.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "GalaxyClient.exe", , Hide
; Targets a hidden window spawned by GOG Galaxy
Run, komorebic.exe float-rule class "Chrome_RenderWidgetHostHWND", , Hide
; GoPro Webcam
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application class "GoPro Webcam", , Hide
; Google Chrome
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "chrome.exe", , Hide
; Google Drive
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "GoogleDriveFS.exe", , Hide
; Inno Setup
; Target hidden window spawned by Inno Setup applications
Run, komorebic.exe float-rule class "TApplication", , Hide
; Target Inno Setup installers
Run, komorebic.exe float-rule class "TWizardForm", , Hide
; IntelliJ IDEA
Run, komorebic.exe identify-object-name-change-application exe "idea64.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "idea64.exe", , Hide
; Targets JetBrains IDE popups and floating windows
Run, komorebic.exe float-rule class "SunAwtDialog", , Hide
; Kleopatra
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "kleopatra.exe", , Hide
; Kotatogram
Run, komorebic.exe identify-border-overflow-application exe "Kotatogram.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Kotatogram.exe", , Hide
; Logi Bolt
Run, komorebic.exe float-rule exe "LogiBolt.exe", , Hide
; LogiTune
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "LogiTune.exe", , Hide
Run, komorebic.exe float-rule exe "LogiTune.exe", , Hide
; Logitech G HUB
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "lghub.exe", , Hide
Run, komorebic.exe identify-border-overflow-application exe "lghub.exe", , Hide
; Logitech Options
Run, komorebic.exe float-rule exe "LogiOptionsUI.exe", , Hide
; Mailspring
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "mailspring.exe", , Hide
; ManyCam
Run, komorebic.exe identify-border-overflow-application exe "ManyCam.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ManyCam.exe", , Hide
; Mica For Everyone
; Microsoft Excel
Run, komorebic.exe identify-border-overflow-application exe "EXCEL.EXE", , Hide
Run, komorebic.exe identify-layered-application exe "EXCEL.EXE", , Hide
; Targets a hidden window spawned by Microsoft Office applications
Run, komorebic.exe float-rule class "_WwB", , Hide
; Microsoft Outlook
Run, komorebic.exe identify-border-overflow-application exe "OUTLOOK.EXE", , Hide
Run, komorebic.exe identify-layered-application exe "OUTLOOK.EXE", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "OUTLOOK.EXE", , Hide
; Microsoft PowerPoint
Run, komorebic.exe identify-border-overflow-application exe "POWERPNT.EXE", , Hide
Run, komorebic.exe identify-layered-application exe "POWERPNT.EXE", , Hide
; Microsoft Teams
; Target Teams pop-up notification windows
Run, komorebic.exe float-rule title "Microsoft Teams Notifications", , Hide
; Microsoft Word
Run, komorebic.exe identify-border-overflow-application exe "WINWORD.EXE", , Hide
Run, komorebic.exe identify-layered-application exe "WINWORD.EXE", , Hide
; Modern Flyouts
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ModernFlyoutsHost.exe", , Hide
; Mozilla Firefox
Run, komorebic.exe identify-object-name-change-application exe "firefox.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "firefox.exe", , Hide
; Targets invisible windows spawned by Firefox to show tab previews in the taskbar
Run, komorebic.exe float-rule class "MozillaTaskbarPreviewClass", , Hide
; NVIDIA GeForce Experience
Run, komorebic.exe identify-border-overflow-application exe "NVIDIA GeForce Experience.exe", , Hide
; NiceHash Miner
Run, komorebic.exe identify-border-overflow-application exe "nhm_app.exe", , Hide
Run, komorebic.exe manage-rule exe "nhm_app.exe", , Hide
; NohBoard
Run, komorebic.exe float-rule exe "NohBoard.exe", , Hide
; Notion Enhanced
Run, komorebic.exe identify-border-overflow-application exe "Notion Enhanced.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Notion Enhanced.exe", , Hide
; OBS Studio (32-bit)
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "obs32.exe", , Hide
; OBS Studio (64-bit)
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "obs64.exe", , Hide
; ONLYOFFICE Editors
Run, komorebic.exe identify-border-overflow-application class "DocEditorsWindowClass", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application class "DocEditorsWindowClass", , Hide
; Obsidian
Run, komorebic.exe identify-border-overflow-application exe "Obsidian.exe", , Hide
Run, komorebic.exe manage-rule exe "Obsidian.exe", , Hide
; OpenRGB
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "OpenRGB.exe", , Hide
; Paradox Launcher
Run, komorebic.exe float-rule exe "Paradox Launcher.exe", , Hide
; PowerToys
; Target color picker dialog
Run, komorebic.exe float-rule exe "PowerToys.ColorPickerUI.exe", , Hide
; Target image resizer dialog
Run, komorebic.exe float-rule exe "PowerToys.ImageResizer.exe", , Hide
; Process Hacker
Run, komorebic.exe identify-border-overflow-application exe "ProcessHacker.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ProcessHacker.exe", , Hide
Run, komorebic.exe float-rule exe "ProcessHacker.exe", , Hide
; ProtonVPN
Run, komorebic.exe identify-border-overflow-application exe "ProtonVPN.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ProtonVPN.exe", , Hide
; PyCharm
Run, komorebic.exe identify-object-name-change-application exe "pycharm64.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "pycharm64.exe", , Hide
; QuickLook
Run, komorebic.exe float-rule exe "QuickLook.exe", , Hide
; RepoZ
Run, komorebic.exe float-rule exe "RepoZ.exe", , Hide
; Roblox FPS Unlocker
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "rbxfpsunlocker.exe", , Hide
; RoundedTB
Run, komorebic.exe float-rule exe "RoundedTB.exe", , Hide
; RoundedTB
Run, komorebic.exe identify-border-overflow-application exe "RoundedTB.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "RoundedTB.exe", , Hide
; ShareX
Run, komorebic.exe identify-border-overflow-application exe "ShareX.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ShareX.exe", , Hide
; Signal
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "signal.exe", , Hide
; SiriKali
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "sirikali.exe", , Hide
; Slack
Run, komorebic.exe identify-border-overflow-application exe "Slack.exe", , Hide
Run, komorebic.exe manage-rule exe "Slack.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Slack.exe", , Hide
; Slack
Run, komorebic.exe identify-border-overflow-application exe "slack.exe", , Hide
Run, komorebic.exe manage-rule exe "slack.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "slack.exe", , Hide
; Spotify
Run, komorebic.exe identify-border-overflow-application exe "Spotify.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Spotify.exe", , Hide
; Steam
Run, komorebic.exe identify-border-overflow-application class "vguiPopupWindow", , Hide
; Task Manager
Run, komorebic.exe float-rule class "TaskManagerWindow", , Hide
; Telegram
Run, komorebic.exe identify-border-overflow-application exe "Telegram.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "Telegram.exe", , Hide
; TouchCursor
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "tcconfig.exe", , Hide
Run, komorebic.exe float-rule exe "tcconfig.exe", , Hide
; TranslucentTB
Run, komorebic.exe float-rule exe "TranslucentTB.exe", , Hide
; TranslucentTB
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "TranslucentTB.exe", , Hide
; Unreal Editor
Run, komorebic.exe identify-border-overflow-application exe "UnrealEditor.exe", , Hide
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "UnrealEditor.exe", , Hide
; Visual Studio Code
Run, komorebic.exe identify-border-overflow-application exe "Code.exe", , Hide
; Windows Console (conhost.exe)
Run, komorebic.exe manage-rule class "ConsoleWindowClass", , Hide
; Windows Explorer
; Targets copy/move operation windows
Run, komorebic.exe float-rule class "OperationStatusWindow", , Hide
Run, komorebic.exe float-rule title "Control Panel", , Hide
; Windows Installer
; Targets MSI Installers
Run, komorebic.exe float-rule class "MsiDialogCloseClass", , Hide
; Wox
; Targets a hidden window spawned by Wox
Run, komorebic.exe float-rule title "Hotkey sink", , Hide
; Zoom
Run, komorebic.exe float-rule exe "Zoom.exe", , Hide
; qBittorrent
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "qbittorrent.exe", , Hide
; ueli
; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line
Run, komorebic.exe identify-tray-application exe "ueli.exe", , Hide
Run, komorebic.exe float-rule exe "ueli.exe", , Hide

15
komorebi.iml Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RUST_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/bindings/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/komorebi-core/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/komorebi/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/komorebic/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,93 +1,225 @@
#SingleInstance Force
; You can generate a fresh version of this file with "komorebic ahk-library"
#Include %A_ScriptDir%\komorebic.lib.ahk
; https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations
#Include %A_ScriptDir%\komorebi.generated.ahk
; Default to minimizing windows when switching workspaces
WindowHidingBehaviour("minimize")
; Set cross-monitor move behaviour to insert instead of swap
CrossMonitorMoveBehaviour("insert")
; Enable hot reloading of changes to this file
WatchConfiguration("enable")
; Ensure there is 1 workspace created on monitor 0
EnsureWorkspaces(0, 1)
Run, komorebic.exe watch-configuration enable, , Hide
; Configure the invisible border dimensions
InvisibleBorders(7, 0, 14, 7)
Run, komorebic.exe invisible-borders 7 0 14 7, , Hide
; Configure the 1st workspace
WorkspaceName(0, 0, "I")
; Enable focus follows mouse
Run, komorebic.exe focus-follows-mouse enable, , Hide
; Uncomment the next two lines if you want a visual border drawn around the focused window
; ActiveWindowBorderColour(66, 165, 245) ; this is a nice blue colour
; ActiveWindowBorder("enable")
; Ensure there are 3 workspaces created on monitor 0
Run, komorebic.exe ensure-workspaces 0 5, , Hide
; Allow komorebi to start managing windows
CompleteConfiguration()
; Give the workspaces some optional names
Run, komorebic.exe workspace-name 0 0 bsp, , Hide
Run, komorebic.exe workspace-name 0 1 columns, , Hide
Run, komorebic.exe workspace-name 0 2 thicc, , Hide
Run, komorebic.exe workspace-name 0 3 matrix, , Hide
Run, komorebic.exe workspace-name 0 4 floaty, , Hide
; Change the focused window, Alt + Vim direction keys (HJKL)
; Set the padding of the different workspaces
Run, komorebic.exe workspace-padding 0 1 30, , Hide
Run, komorebic.exe container-padding 0 1 30, , Hide
Run, komorebic.exe workspace-padding 0 2 200, , Hide
Run, komorebic.exe workspace-padding 0 3 0, , Hide
Run, komorebic.exe container-padding 0 3 0, , Hide
; Set the layouts of different workspaces
Run, komorebic.exe workspace-layout 0 1 columns, , Hide
; Set the floaty layout to not tile any windows
Run, komorebic.exe workspace-tiling 0 4 disable, , Hide
; Always show chat apps on the second workspace
Run, komorebic.exe workspace-rule exe slack.exe 0 1, , Hide
Run, komorebic.exe workspace-rule exe Discord.exe 0 1, , Hide
; Always float IntelliJ popups, matching on class
Run, komorebic.exe float-rule class SunAwtDialog, , Hide
; Always float Control Panel, matching on title
Run, komorebic.exe float-rule title "Control Panel", , Hide
; Always float Task Manager, matching on class
Run, komorebic.exe float-rule class TaskManagerWindow, , Hide
; Always float Wally, matching on executable name
Run, komorebic.exe float-rule exe Wally.exe, , Hide
Run, komorebic.exe float-rule exe wincompose.exe, , Hide
; Always float Calculator app, matching on window title
Run, komorebic.exe float-rule title Calculator, , Hide
Run, komorebic.exe float-rule exe 1Password.exe, , Hide
; Always manage forcibly these applications that don't automatically get picked up by komorebi
Run, komorebic.exe manage-rule exe TIM.exe, , Hide
; Identify applications that close to the tray
Run, komorebic.exe identify-tray-application exe Discord.exe, , Hide
; Identify applications that have overflowing borders
Run, komorebic.exe identify-border-overflow exe Discord.exe, , Hide
; Change the focused window, Alt + Vim direction keys
!h::
Focus("left")
Run, komorebic.exe focus left, , Hide
return
!j::
Focus("down")
Run, komorebic.exe focus down, , Hide
return
!k::
Focus("up")
Run, komorebic.exe focus up, , Hide
return
!l::
Focus("right")
Run, komorebic.exe focus right, , Hide
return
; Move the focused window in a given direction, Alt + Shift + Vim direction keys (HJKL)
; Move the focused window in a given direction, Alt + Shift + Vim direction keys
!+h::
Move("left")
Run, komorebic.exe move left, , Hide
return
!+j::
Move("down")
Run, komorebic.exe move down, , Hide
return
!+k::
Move("up")
Run, komorebic.exe move up, , Hide
return
!+l::
Move("right")
Run, komorebic.exe move right, , Hide
return
; There are many more commands that you can bind to whatever keys combinations you want!
;
; Have a look at the komorebic.lib.ahk file to see which arguments are required by different commands
;
; If you want more information about a command, you can run every komorebic command with "--help"
;
; For example, if you see this in komorebic.lib.ahk
;
; WorkspaceLayout(monitor, workspace, value) {
; Run, komorebic.exe workspace-layout %monitor% %workspace% %value%, , Hide
; }
;
; Just run "komorebic.exe workspace-layout --help" and you'll get all the information you need to use the command
;
; komorebic.exe-workspace-layout
; Set the layout for the specified workspace
;
; USAGE:
; komorebic.exe workspace-layout <MONITOR> <WORKSPACE> <VALUE>
;
; ARGS:
; <MONITOR> Monitor index (zero-indexed)
; <WORKSPACE> Workspace index on the specified monitor (zero-indexed)
; <VALUE> [possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
;
; OPTIONS:
; -h, --help Print help information
; Stack the focused window in a given direction, Alt + Shift + direction keys
!+Left::
Run, komorebic.exe stack left, , Hide
return
!+Down::
Run, komorebic.exe stack down, , Hide
return
!+Up::
Run, komorebic.exe stack up, , Hide
return
!+Right::
Run, komorebic.exe stack right, , Hide
return
!]::
Run, komorebic.exe cycle-stack next, , Hide
return
![::
Run, komorebic.exe cycle-stack previous, , Hide
return
; Unstack the focused window, Alt + Shift + D
!+d::
Run, komorebic.exe unstack, , Hide
return
; Promote the focused window to the top of the tree, Alt + Shift + Enter
!+Enter::
Run, komorebic.exe promote, , Hide
return
; Switch to an equal-width, max-height column layout on the main workspace, Alt + Shift + C
!+c::
Run, komorebic.exe workspace-layout 0 0 columns, , Hide
return
; Switch to the default bsp tiling layout on the main workspace, Alt + Shift + T
!+t::
Run, komorebic.exe workspace-layout 0 0 bsp, , Hide
return
; Toggle the Monocle layout for the focused window, Alt + Shift + F
!+f::
Run, komorebic.exe toggle-monocle, , Hide
return
; Toggle native maximize for the focused window, Alt + Shift + =
!+=::
Run, komorebic.exe toggle-maximize, , Hide
return
; Flip horizontally, Alt + X
!x::
Run, komorebic.exe flip-layout horizontal, , Hide
return
; Flip vertically, Alt + Y
!y::
Run, komorebic.exe flip-layout vertical, , Hide
return
; Force a retile if things get janky, Alt + Shift + R
!+r::
Run, komorebic.exe retile, , Hide
return
; Float the focused window, Alt + T
!t::
Run, komorebic.exe toggle-float, , Hide
return
; Reload ~/komorebi.ahk, Alt + O
!o::
Run, komorebic.exe reload-configuration, , Hide
return
; Pause responding to any window events or komorebic commands, Alt + P
!p::
Run, komorebic.exe toggle-pause, , Hide
return
; Switch to workspace
!1::
Send !
Run, komorebic.exe focus-workspace 0, , Hide
return
!2::
Send !
Run, komorebic.exe focus-workspace 1, , Hide
return
!3::
Send !
Run, komorebic.exe focus-workspace 2, , Hide
return
!4::
Send !
Run, komorebic.exe focus-workspace 3, , Hide
return
!5::
Send !
Run, komorebic.exe focus-workspace 4, , Hide
return
; Move window to workspace
!+1::
Run, komorebic.exe move-to-workspace 0, , Hide
return
!+2::
Run, komorebic.exe move-to-workspace 1, , Hide
return
!+3::
Run, komorebic.exe move-to-workspace 2, , Hide
return
!+4::
Run, komorebic.exe move-to-workspace 3, , Hide
return
!+5::
Run, komorebic.exe move-to-workspace 4, , Hide
return

View File

@@ -0,0 +1,240 @@
#SingleInstance Force
#Include %A_ScriptDir%\komorebic.lib.ahk
; Enable hot reloading of changes to this file
WatchConfiguration("enable")
; Ensure there are 5 workspaces created on monitor 0
EnsureWorkspaces(0, 5)
; Configure the invisible border dimensions
InvisibleBorders(7, 0, 14, 7)
; Configure the 1st workspace
WorkspaceName(0, 0, "bsp")
; Configure the 2nd workspace
WorkspaceName(0, 1, "columns") ; Optionally set the name of the workspace
WorkspacePadding(0, 1, 30) ; Set the padding around the edge of the screen
ContainerPadding(0, 1, 30) ; Set the padding between the containers on the screen
WorkspaceRule("exe", "slack.exe", 0, 1) ; Always show chat apps on this workspace
; Configure the 3rd workspace
WorkspaceName(0, 2, "thicc")
WorkspacePadding(0, 2, 200) ; Set some super thicc padding
; Configure the 4th workspace
WorkspaceName(0, 3, "matrix")
WorkspacePadding(0, 3, 0) ; No padding at all
ContainerPadding(0, 3, 0) ; Matrix-y hacker vibes
; Configure the 5th workspace
WorkspaceName(0, 4, "floaty")
WorkspaceTiling(0, 4, "disable") ; Everything floats here
; Configure floating rules
FloatRule("class", "SunAwtDialog") ; All the IntelliJ popups
FloatRule("title", "Control Panek")
FloatRule("class", "TaskManagerWindow")
FloatRule("exe", "Wally.exe")
FloatRule("exe", "wincompose.exe")
FloatRule("exe", "1Password.exe")
FloatRule("exe", "Wox.exe")
FloatRule("exe", "ddm.exe")
FloatRule("class", "Chrome_RenderWidgetHostHWND") ; GOG Electron invisible overlay
FloatRule("class", "CEFCLIENT")
; Identify Minimize-to-Tray Applications
IdentifyTrayApplication("exe", "Discord.exe")
IdentifyTrayApplication("exe", "Spotify.exe")
IdentifyTrayApplication("exe", "GalaxyClient.exe")
; Identify Electron applications with overflowing borders
IdentifyBorderOverflow("exe", "Discord.exe")
IdentifyBorderOverflow("exe", "Spotify.exe")
IdentifyBorderOverflow("exe", "GalaxyClient.exe")
IdentifyBorderOverflow("class", "ZPFTEWndClass")
; Identify applications to be forcibly managed
ManageRule("exe", "GalaxyClient.exe")
; Change the focused window, Alt + Vim direction keys
!h::
Focus("left")
return
!j::
Focus("down")
return
!k::
Focus("up")
return
!l::
Focus("right")
return
; Move the focused window in a given direction, Alt + Shift + Vim direction keys
!+h::
Move("left")
return
!+j::
Move("down")
return
!+k::
Move("up")
return
!+l::
Move("right")
return
; Stack the focused window in a given direction, Alt + Shift + direction keys
!+Left::
Stack("left")
return
!+Down::
Stack("down")
return
!+Up::
Stack("up")
return
!+Right::
Stack("right")
return
!]::
CycleStack("next")
return
![::
CycleStack("previous")
return
; Unstack the focused window, Alt + Shift + D
!+d::
Unstack()
return
; Promote the focused window to the top of the tree, Alt + Shift + Enter
!+Enter::
Promote()
return
; Manage the focused window
!=::
Manage()
return
; Unmanage the focused window
!-::
Unmanage()
return
; Switch to an equal-width, max-height column layout on the main workspace, Alt + Shift + C
!+c::
ChangeLayout("columns")
return
; Switch to the default bsp tiling layout on the main workspace, Alt + Shift + T
!+t::
ChangeLayout("bsp")
return
; Toggle the Monocle layout for the focused window, Alt + Shift + F
!+f::
ToggleMonocle()
return
; Toggle native maximize for the focused window, Alt + Shift + =
!+=::
ToggleMaximize()
return
; Flip horizontally, Alt + X
!x::
FlipLayout("horizontal")
return
; Flip vertically, Alt + Y
!y::
FlipLayout("vertical")
return
; Force a retile if things get janky, Alt + Shift + R
!+r::
Retile()
return
; Float the focused window, Alt + T
!t::
ToggleFloat()
return
; Reload ~/komorebi.ahk, Alt + O
!o::
ReloadConfiguration()
return
; Pause responding to any window events or komorebic commands, Alt + P
!p::
TogglePause()
return
; Enable focus follows mouse
!0::
ToggleFocusFollowsMouse("komorebi")
return
; Switch to workspace
!1::
Send !
FocusWorkspace(0)
return
!2::
Send !
FocusWorkspace(1)
return
!3::
Send !
FocusWorkspace(2)
return
!4::
Send !
FocusWorkspace(3)
return
!5::
Send !
FocusWorkspace(4)
return
; Move window to workspace
!+1::
MoveToWorkspace(0)
return
!+2::
MoveToWorkspace(1)
return
!+3::
MoveToWorkspace(2)
return
!+4::
MoveToWorkspace(3)
return
!+5::
MoveToWorkspace(4)
return

View File

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

View File

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

View File

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

View File

@@ -1,49 +1,37 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::redundant_pub_crate)]
#![allow(clippy::missing_errors_doc)]
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::net::TcpStream;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[cfg(feature = "deadlock_detection")]
use std::thread;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use clap::Parser;
use clap::Clap;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::Backoff;
use lazy_static::lazy_static;
use os_info::Version;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Serialize;
use sysinfo::Process;
use sysinfo::ProcessExt;
use sysinfo::SystemExt;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use which::which;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use komorebi_core::HidingBehaviour;
use komorebi_core::Rect;
use komorebi_core::SocketMessage;
use crate::process_command::listen_for_commands;
use crate::process_command::listen_for_commands_tcp;
use crate::process_event::listen_for_events;
use crate::process_movement::listen_for_movements;
use crate::window_manager::State;
@@ -54,7 +42,6 @@ use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;
mod border;
mod container;
mod monitor;
mod process_command;
@@ -73,7 +60,7 @@ mod workspace;
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
static ref LAYERED_EXE_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![
@@ -91,85 +78,22 @@ lazy_static! {
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
// mstsc.exe creates these on Windows 11 when a WSL process is launched
// https://github.com/LGUG2Z/komorebi/issues/74
"OPContainerClass".to_string(),
"IHWindowClass".to_string()
]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"X410.exe".to_string(),
"mstsc.exe".to_string(),
"vcxsrv.exe".to_string(),
]));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
Arc::new(Mutex::new(HidingBehaviour::Minimize));
static ref HOME_DIR: PathBuf = {
if let Ok(home_path) = std::env::var("KOMOREBI_CONFIG_HOME") {
let home = PathBuf::from(&home_path);
if home.as_path().is_dir() {
home
} else {
panic!(
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
}
} else {
dirs::home_dir().expect("there is no home directory")
}
};
static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
static ref AHK_V1_EXE: String = {
let mut ahk_v1: String = String::from("autohotkey.exe");
if let Ok(komorebi_ahk_v1_exe) = std::env::var("KOMOREBI_AHK_V1_EXE") {
if which(&komorebi_ahk_v1_exe).is_ok() {
ahk_v1 = komorebi_ahk_v1_exe;
}
}
ahk_v1
};
static ref AHK_V2_EXE: String = {
let mut ahk_v2: String = String::from("AutoHotkey64.exe");
if let Ok(komorebi_ahk_v2_exe) = std::env::var("KOMOREBI_AHK_V2_EXE") {
if which(&komorebi_ahk_v2_exe).is_ok() {
ahk_v2 = komorebi_ahk_v2_exe;
}
}
ahk_v2
};
static ref WINDOWS_11: bool = {
matches!(
os_info::get().version(),
Version::Semantic(_, _, x) if x >= &22000
)
};
static ref BORDER_RECT: Arc<Mutex<Rect>> =
Arc::new(Mutex::new(Rect::default()));
// Use app-specific titlebar removal options where possible
// eg. Windows Terminal, IntelliJ IDEA, Firefox
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
}
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false);
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);
pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0);
// 0 0 0 aka pure black, I doubt anyone will want this as a border colour
pub const TRANSPARENCY_COLOUR: u32 = 0;
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
@@ -182,7 +106,8 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
std::env::set_var("RUST_LOG", "info");
}
let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "komorebi.log");
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
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);
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
@@ -212,30 +137,27 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
// occurred to be recorded.
std::panic::set_hook(Box::new(|panic| {
// If the panic has a source location, record it as structured fields.
panic.location().map_or_else(
|| {
tracing::error!(message = %panic);
},
|location| {
// On nightly Rust, where the `PanicInfo` type also exposes a
// `message()` method returning just the message, we could record
// just the message instead of the entire `fmt::Display`
// implementation, avoiding the duplciated location
tracing::error!(
message = %panic,
panic.file = location.file(),
panic.line = location.line(),
panic.column = location.column(),
);
},
);
if let Some(location) = panic.location() {
// On nightly Rust, where the `PanicInfo` type also exposes a
// `message()` method returning just the message, we could record
// just the message instead of the entire `fmt::Display`
// implementation, avoiding the duplciated location
tracing::error!(
message = %panic,
panic.file = location.file(),
panic.line = location.line(),
panic.column = location.column(),
);
} else {
tracing::error!(message = %panic);
}
}));
Ok((guard, color_guard))
}
pub fn load_configuration() -> Result<()> {
let home = HOME_DIR.clone();
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut config_v1 = home.clone();
config_v1.push("komorebi.ahk");
@@ -243,7 +165,7 @@ pub fn load_configuration() -> Result<()> {
let mut config_v2 = home;
config_v2.push("komorebi.ahk2");
if config_v1.exists() && which(&*AHK_V1_EXE).is_ok() {
if config_v1.exists() && which("autohotkey.exe").is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v1
@@ -255,7 +177,7 @@ pub fn load_configuration() -> Result<()> {
Command::new("autohotkey.exe")
.arg(config_v1.as_os_str())
.output()?;
} else if config_v2.exists() && which(&*AHK_V2_EXE).is_ok() {
} else if config_v2.exists() && which("AutoHotkey64.exe").is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v2
@@ -267,62 +189,19 @@ pub fn load_configuration() -> Result<()> {
Command::new("AutoHotkey64.exe")
.arg(config_v2.as_os_str())
.output()?;
}
};
Ok(())
}
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
// This is the path on Windows 10
let mut current = hkcu
.open_subkey(format!(
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
SESSION_ID.load(Ordering::SeqCst)
))
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
// This is the path on Windows 11
if current.is_none() {
current = hkcu
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
}
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
// exist until one has been created in the task view
// The registry value will also not exist on user login if virtual desktops have been created
// but the task view has not been initiated
// In both of these cases, we return None, and the virtual desktop validation will never run. In
// the latter case, if the user desires this validation after initiating the task view, komorebi
// should be restarted, and then when this // fn runs again for the first time, it will pick up
// the value of CurrentVirtualDesktop and validate against it accordingly
current
}
#[derive(Debug, Serialize, JsonSchema)]
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
Socket(SocketMessage),
}
#[derive(Debug, Serialize, JsonSchema)]
#[derive(Debug, Serialize)]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,
@@ -366,9 +245,9 @@ pub fn notify_subscribers(notification: &str) -> Result<()> {
#[tracing::instrument]
fn detect_deadlocks() {
// Create a background thread which checks for deadlocks every 10s
std::thread::spawn(move || loop {
thread::spawn(move || loop {
tracing::info!("running deadlock detector");
std::thread::sleep(Duration::from_secs(5));
thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
@@ -385,62 +264,29 @@ fn detect_deadlocks() {
});
}
#[derive(Parser)]
#[derive(Clap)]
#[clap(author, about, version)]
struct Opts {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(action, short, long = "ffm")]
#[clap(long = "ffm")]
focus_follows_mouse: bool,
/// Wait for 'komorebic complete-configuration' to be sent before processing events
#[clap(action, short, long)]
await_configuration: bool,
/// Start a TCP server on the given port to allow the direct sending of SocketMessages
#[clap(action, short, long)]
tcp_port: Option<usize>,
}
#[tracing::instrument]
#[allow(clippy::nonminimal_bool)]
fn main() -> Result<()> {
let opts: Opts = Opts::parse();
CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
let arg_count = std::env::args().count();
let has_valid_args = arg_count == 1
|| (arg_count == 2
&& (opts.await_configuration || opts.focus_follows_mouse || opts.tcp_port.is_some()))
|| (arg_count == 3 && opts.await_configuration && opts.focus_follows_mouse)
|| (arg_count == 3 && opts.tcp_port.is_some() && opts.focus_follows_mouse)
|| (arg_count == 3 && opts.tcp_port.is_some() && opts.await_configuration)
|| (arg_count == 4
&& (opts.focus_follows_mouse && opts.await_configuration && opts.tcp_port.is_some()));
let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst));
if has_valid_args {
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
WindowsApi::set_process_dpi_awareness_context()?;
let session_id = WindowsApi::process_id_to_session_id()?;
SESSION_ID.store(session_id, Ordering::SeqCst);
let mut system = sysinfo::System::new_all();
system.refresh_processes();
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe").collect();
if matched_procs.len() > 1 {
let mut len = matched_procs.len();
for proc in matched_procs {
if proc.root().ends_with("shims") {
len -= 1;
}
}
if len > 1 {
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
}
if 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);
}
// File logging worker guard has to have an assignment in the main fn to work
@@ -449,6 +295,9 @@ fn main() -> Result<()> {
#[cfg(feature = "deadlock_detection")]
detect_deadlocks();
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
crossbeam_channel::unbounded();
@@ -461,32 +310,14 @@ fn main() -> Result<()> {
wm.lock().init()?;
listen_for_commands(wm.clone());
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
};
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
std::thread::spawn(|| {
load_configuration().expect("could not load configuration");
});
if opts.await_configuration {
let backoff = Backoff::new();
while !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
backoff.snooze();
}
}
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
load_configuration()?;
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {
ctrlc_sender
@@ -500,12 +331,7 @@ fn main() -> Result<()> {
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
wm.lock().restore_all_windows();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
wm.lock().restore_all_windows()?;
std::process::exit(130);
}

View File

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

View File

@@ -2,62 +2,37 @@ use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use std::thread;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use schemars::schema_for;
use uds_windows::UnixStream;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::SocketMessage;
use komorebi_core::StateQuery;
use komorebi_core::WindowContainerBehaviour;
use komorebi_core::WindowKind;
use crate::border::Border;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::window::Window;
use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_SINGLE;
use crate::BORDER_COLOUR_STACK;
use crate::BORDER_ENABLED;
use crate::BORDER_HWND;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::CUSTOM_FFM;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::HOME_DIR;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::NO_TITLEBAR;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_RULES;
@@ -69,11 +44,11 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
.try_clone()
.expect("could not clone unix listener");
std::thread::spawn(move || {
tracing::info!("listening on komorebi.sock");
thread::spawn(move || {
tracing::info!("listening");
for client in listener.incoming() {
match client {
Ok(stream) => match read_commands_uds(&wm, stream) {
Ok(stream) => match wm.lock().read_commands(stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
},
@@ -86,80 +61,13 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
});
}
#[tracing::instrument]
pub fn listen_for_commands_tcp(wm: Arc<Mutex<WindowManager>>, port: usize) {
let listener =
TcpListener::bind(format!("0.0.0.0:{}", port)).expect("could not start tcp server");
std::thread::spawn(move || {
tracing::info!("listening on 0.0.0.0:43663");
for client in listener.incoming() {
match client {
Ok(mut stream) => {
stream
.set_keepalive(Some(Duration::from_secs(30)))
.expect("TCP keepalive should be set");
let addr = stream
.peer_addr()
.expect("incoming connection should have an address")
.to_string();
let mut connections = TCP_CONNECTIONS.lock();
connections.insert(
addr.clone(),
stream.try_clone().expect("stream should be cloneable"),
);
tracing::info!("listening for incoming tcp messages from {}", &addr);
match read_commands_tcp(&wm, &mut stream, &addr) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
}
Err(error) => {
tracing::error!("{}", error);
break;
}
}
}
});
}
impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn process_command(&mut self, message: SocketMessage) -> Result<()> {
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
if let Some(id) = current_virtual_desktop() {
if id != *virtual_desktop_id {
tracing::info!(
"ignoring events and commands while not on virtual desktop {:?}",
virtual_desktop_id
);
return Ok(());
}
}
}
match message {
SocketMessage::CycleFocusMonitor(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusMonitorNumber(_)
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
| SocketMessage::FocusWorkspaceNumber(_) => {
if self.focused_workspace()?.visible_windows().is_empty() {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.hide()?;
}
}
_ => {}
};
self.validate_virtual_desktop_id();
match message {
SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
SocketMessage::FocusWindow(direction) => {
self.focus_container_in_direction(direction)?;
}
@@ -186,71 +94,24 @@ impl WindowManager {
SocketMessage::WorkspacePadding(monitor_idx, workspace_idx, size) => {
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
}
SocketMessage::WorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
SocketMessage::WorkspaceRule(_, id, monitor_idx, workspace_idx) => {
{
let mut workspace_rules = WORKSPACE_RULES.lock();
workspace_rules.insert(id.to_string(), (monitor_idx, workspace_idx));
workspace_rules.insert(id, (monitor_idx, workspace_idx));
}
self.enforce_workspace_rules()?;
}
SocketMessage::ManageRule(_, ref id) => {
SocketMessage::ManageRule(_, id) => {
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
if !manage_identifiers.contains(id) {
manage_identifiers.push(id.to_string());
if !manage_identifiers.contains(&id) {
manage_identifiers.push(id);
}
}
SocketMessage::FloatRule(identifier, ref id) => {
SocketMessage::FloatRule(_, id) => {
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
if !float_identifiers.contains(id) {
float_identifiers.push(id.to_string());
}
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
let mut hwnds_to_purge = vec![];
for (i, monitor) in self.monitors().iter().enumerate() {
for container in monitor
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace"))?
.containers()
.iter()
{
for window in container.windows().iter() {
match identifier {
ApplicationIdentifier::Exe => {
if window.exe()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Class => {
if window.class()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Title => {
if window.title()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
}
}
}
}
for (monitor_idx, hwnd) in hwnds_to_purge {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor"))?;
monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no focused workspace"))?
.remove_window(hwnd)?;
monitor.update_focused_workspace(offset, &invisible_borders)?;
if !float_identifiers.contains(&id) {
float_identifiers.push(id);
}
}
SocketMessage::AdjustContainerPadding(sizing, adjustment) => {
@@ -263,19 +124,13 @@ impl WindowManager {
self.move_container_to_workspace(workspace_idx, true)?;
}
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, true)?;
self.move_container_to_monitor(monitor_idx, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false)?;
}
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
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)?;
self.move_container_to_monitor(monitor_idx, false)?;
}
SocketMessage::TogglePause => {
if self.is_paused {
@@ -285,7 +140,6 @@ impl WindowManager {
}
self.is_paused = !self.is_paused;
self.retile_all(true)?;
}
SocketMessage::ToggleTiling => {
self.toggle_tiling()?;
@@ -298,20 +152,18 @@ impl WindowManager {
);
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(self.mouse_follows_focus)?;
self.update_focused_workspace(true)?;
}
SocketMessage::FocusMonitorNumber(monitor_idx) => {
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(self.mouse_follows_focus)?;
self.update_focused_workspace(true)?;
}
SocketMessage::Retile => self.retile_all(false)?,
SocketMessage::Retile => self.retile_all()?,
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
SocketMessage::ChangeLayoutCustom(ref path) => {
self.change_workspace_custom_layout(path.clone())?;
}
SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, ref path) => {
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path.clone())?;
SocketMessage::ChangeLayoutCustom(path) => self.change_workspace_custom_layout(path)?,
SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, path) => {
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;
}
SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => {
self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;
@@ -319,35 +171,6 @@ impl WindowManager {
SocketMessage::WorkspaceLayout(monitor_idx, workspace_idx, layout) => {
self.set_workspace_layout_default(monitor_idx, workspace_idx, layout)?;
}
SocketMessage::WorkspaceLayoutRule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
) => {
self.add_workspace_layout_default_rule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
)?;
}
SocketMessage::WorkspaceLayoutCustomRule(
monitor_idx,
workspace_idx,
at_container_count,
ref path,
) => {
self.add_workspace_layout_custom_rule(
monitor_idx,
workspace_idx,
at_container_count,
path.clone(),
)?;
}
SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {
self.clear_workspace_layout_rules(monitor_idx, workspace_idx)?;
}
SocketMessage::CycleFocusWorkspace(direction) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
@@ -382,22 +205,14 @@ impl WindowManager {
})?;
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
}
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
}
SocketMessage::Stop => {
tracing::info!(
"received stop command, restoring all hidden windows and terminating process"
);
self.restore_all_windows();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
self.restore_all_windows()?;
std::process::exit(0)
}
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {
@@ -406,17 +221,13 @@ impl WindowManager {
SocketMessage::NewWorkspace => {
self.new_workspace()?;
}
SocketMessage::WorkspaceName(monitor_idx, workspace_idx, ref name) => {
self.set_workspace_name(monitor_idx, workspace_idx, name.to_string())?;
SocketMessage::WorkspaceName(monitor_idx, workspace_idx, name) => {
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
}
SocketMessage::State => {
let state = match serde_json::to_string_pretty(&window_manager::State::from(&*self))
{
Ok(state) => state,
Err(error) => error.to_string(),
};
let mut socket = HOME_DIR.clone();
let state = serde_json::to_string_pretty(&window_manager::State::from(&*self))?;
let mut socket =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
socket.push("komorebic.sock");
let socket = socket.as_path();
@@ -439,117 +250,16 @@ impl WindowManager {
}
.to_string();
let mut socket = HOME_DIR.clone();
let mut socket =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
socket.push("komorebic.sock");
let socket = socket.as_path();
let mut stream = UnixStream::connect(&socket)?;
stream.write_all(response.as_bytes())?;
}
SocketMessage::ResizeWindowEdge(direction, sizing) => {
self.resize_window(direction, sizing, self.resize_delta, true)?;
}
SocketMessage::ResizeWindowAxis(axis, sizing) => {
// If the user has a custom layout, allow for the resizing of the primary column
// with this signal
let workspace = self.focused_workspace_mut()?;
let container_len = workspace.containers().len();
let no_layout_rules = workspace.layout_rules().is_empty();
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if matches!(axis, Axis::Horizontal) {
let percentage = custom
.primary_width_percentage()
.unwrap_or(100 / custom.len());
if no_layout_rules {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
}
}
} else {
for rule in workspace.layout_rules_mut() {
if container_len >= rule.0 {
if let Layout::Custom(ref mut custom) = rule.1 {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
}
}
}
}
}
}
}
// Otherwise proceed with the resizing logic for individual window containers in the
// assumed BSP layout
} else {
match axis {
Axis::Horizontal => {
self.resize_window(
OperationDirection::Left,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Right,
sizing,
self.resize_delta,
false,
)?;
}
Axis::Vertical => {
self.resize_window(
OperationDirection::Up,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Down,
sizing,
self.resize_delta,
false,
)?;
}
Axis::HorizontalAndVertical => {
self.resize_window(
OperationDirection::Left,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Right,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Up,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Down,
sizing,
self.resize_delta,
false,
)?;
}
}
}
self.update_focused_workspace(false)?;
SocketMessage::ResizeWindow(direction, sizing) => {
self.resize_window(direction, sizing, Option::from(50))?;
}
SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
if !CUSTOM_FFM.load(Ordering::SeqCst) {
@@ -647,37 +357,19 @@ impl WindowManager {
SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
}
SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
self.update_focused_workspace(false)?;
}
}
SocketMessage::WatchConfiguration(enable) => {
self.watch_configuration(enable)?;
}
SocketMessage::IdentifyBorderOverflowApplication(_, ref id) => {
SocketMessage::IdentifyBorderOverflow(_, id) => {
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
if !identifiers.contains(&id) {
identifiers.push(id);
}
}
SocketMessage::IdentifyObjectNameChangeApplication(_, ref id) => {
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
}
}
SocketMessage::IdentifyTrayApplication(_, ref id) => {
SocketMessage::IdentifyTrayApplication(_, id) => {
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
}
}
SocketMessage::IdentifyLayeredApplication(_, ref id) => {
let mut identifiers = LAYERED_WHITELIST.lock();
if !identifiers.contains(id) {
identifiers.push(id.to_string());
if !identifiers.contains(&id) {
identifiers.push(id);
}
}
SocketMessage::ManageFocusedWindow => {
@@ -688,11 +380,11 @@ impl WindowManager {
}
SocketMessage::InvisibleBorders(rect) => {
self.invisible_borders = rect;
self.retile_all(false)?;
self.retile_all()?;
}
SocketMessage::WorkAreaOffset(rect) => {
self.work_area_offset = Option::from(rect);
self.retile_all(false)?;
self.retile_all()?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
@@ -727,7 +419,7 @@ impl WindowManager {
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::Save(ref path) => {
SocketMessage::Save(path) => {
let workspace = self.focused_workspace_mut()?;
let resize = workspace.resize_dimensions();
@@ -735,14 +427,14 @@ impl WindowManager {
.write(true)
.truncate(true)
.create(true)
.open(path.clone())?;
.open(path)?;
serde_json::to_writer_pretty(&file, &resize)?;
}
SocketMessage::Load(ref path) => {
SocketMessage::Load(path) => {
let workspace = self.focused_workspace_mut()?;
let file = File::open(path)
let file = File::open(&path)
.map_err(|_| anyhow!("no file found at {}", path.display().to_string()))?;
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
@@ -750,245 +442,61 @@ impl WindowManager {
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::AddSubscriber(ref subscriber) => {
SocketMessage::AddSubscriber(subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
let pipe_path = format!(r"\\.\pipe\{}", subscriber);
let pipe = connect(&pipe_path).map_err(|_| {
anyhow!("the named pipe '{}' has not yet been created; please create it before running this command", pipe_path)
})?;
pipes.insert(subscriber.clone(), pipe);
pipes.insert(subscriber, pipe);
}
SocketMessage::RemoveSubscriber(ref subscriber) => {
SocketMessage::RemoveSubscriber(subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
pipes.remove(subscriber);
pipes.remove(&subscriber);
}
SocketMessage::MouseFollowsFocus(enable) => {
self.mouse_follows_focus = enable;
}
SocketMessage::ToggleMouseFollowsFocus => {
self.mouse_follows_focus = !self.mouse_follows_focus;
}
SocketMessage::ResizeDelta(delta) => {
self.resize_delta = delta;
}
SocketMessage::ToggleWindowContainerBehaviour => {
match self.window_container_behaviour {
WindowContainerBehaviour::Create => {
self.window_container_behaviour = WindowContainerBehaviour::Append;
}
WindowContainerBehaviour::Append => {
self.window_container_behaviour = WindowContainerBehaviour::Create;
}
SocketMessage::RemoveTitleBar(_, id) => {
let mut identifiers = NO_TITLEBAR.lock();
if !identifiers.contains(&id) {
identifiers.push(id);
}
}
SocketMessage::WindowHidingBehaviour(behaviour) => {
let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();
*hiding_behaviour = behaviour;
SocketMessage::ToggleTitleBars => {
let current = REMOVE_TITLEBARS.load(Ordering::SeqCst);
REMOVE_TITLEBARS.store(!current, Ordering::SeqCst);
self.update_focused_workspace(false)?;
}
SocketMessage::ToggleCrossMonitorMoveBehaviour => {
match self.cross_monitor_move_behaviour {
MoveBehaviour::Swap => {
self.cross_monitor_move_behaviour = MoveBehaviour::Insert;
}
MoveBehaviour::Insert => {
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
}
}
}
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
self.cross_monitor_move_behaviour = behaviour;
}
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
self.unmanaged_window_operation_behaviour = behaviour;
}
SocketMessage::ActiveWindowBorder(enable) => {
if enable {
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
Border::create("komorebi-border-window")?;
}
BORDER_ENABLED.store(true, Ordering::SeqCst);
self.show_border()?;
} else {
BORDER_ENABLED.store(false, Ordering::SeqCst);
self.hide_border()?;
}
}
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
match kind {
WindowKind::Single => {
BORDER_COLOUR_SINGLE.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
BORDER_COLOUR_CURRENT.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
}
WindowKind::Stack => {
BORDER_COLOUR_STACK.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
}
}
WindowsApi::invalidate_border_rect()?;
}
SocketMessage::NotificationSchema => {
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
let mut socket = HOME_DIR.clone();
socket.push("komorebic.sock");
let socket = socket.as_path();
let mut stream = UnixStream::connect(&socket)?;
stream.write_all(schema.as_bytes())?;
}
SocketMessage::SocketSchema => {
let socket_message = schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
let mut socket = HOME_DIR.clone();
socket.push("komorebic.sock");
let socket = socket.as_path();
let mut stream = UnixStream::connect(&socket)?;
stream.write_all(schema.as_bytes())?;
}
};
match message {
SocketMessage::ChangeLayout(_)
| SocketMessage::ChangeLayoutCustom(_)
| SocketMessage::FlipLayout(_)
| SocketMessage::ManageFocusedWindow
| SocketMessage::MoveWorkspaceToMonitorNumber(_)
| SocketMessage::MoveContainerToMonitorNumber(_)
| SocketMessage::MoveContainerToWorkspaceNumber(_)
| SocketMessage::ResizeWindowEdge(_, _)
| SocketMessage::ResizeWindowAxis(_, _)
| SocketMessage::ToggleFloat
| SocketMessage::ToggleMonocle
| SocketMessage::ToggleMaximize
| SocketMessage::Promote
| SocketMessage::PromoteFocus
| SocketMessage::Retile
| SocketMessage::InvisibleBorders(_)
| SocketMessage::WorkAreaOffset(_)
| SocketMessage::MoveWindow(_) => {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
rect.top -= self.invisible_borders.bottom;
rect.bottom += self.invisible_borders.bottom;
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(foreground_window, &self.invisible_borders, false)?;
}
SocketMessage::TogglePause => {
let is_paused = self.is_paused;
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
if is_paused {
border.hide()?;
} else {
let focused = self.focused_window()?;
border.set_position(*focused, &self.invisible_borders, true)?;
focused.focus(false)?;
}
}
SocketMessage::ToggleTiling | SocketMessage::WorkspaceTiling(..) => {
let tiling_enabled = *self.focused_workspace_mut()?.tile();
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
if tiling_enabled {
let focused = self.focused_window()?;
border.set_position(*focused, &self.invisible_borders, true)?;
focused.focus(false)?;
} else {
border.hide()?;
}
}
_ => {}
};
tracing::info!("processed");
Ok(())
}
}
pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, stream: UnixStream) -> Result<()> {
let stream = BufReader::new(stream);
for line in stream.lines() {
let message = SocketMessage::from_str(&line?)?;
#[tracing::instrument(skip(self, stream))]
pub fn read_commands(&mut self, stream: UnixStream) -> Result<()> {
let stream = BufReader::new(stream);
for line in stream.lines() {
let message = SocketMessage::from_str(&line?)?;
let mut wm = wm.lock();
if wm.is_paused {
return match message {
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
Ok(wm.process_command(message)?)
}
_ => {
tracing::trace!("ignoring while paused");
Ok(())
}
};
}
wm.process_command(message.clone())?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: wm.as_ref().into(),
})?)?;
}
Ok(())
}
pub fn read_commands_tcp(
wm: &Arc<Mutex<WindowManager>>,
stream: &mut TcpStream,
addr: &str,
) -> Result<()> {
let mut stream = BufReader::new(stream);
loop {
let mut buf = vec![0; 1024];
match stream.read(&mut buf) {
Err(..) => {
tracing::warn!("removing disconnected tcp client: {addr}");
let mut connections = TCP_CONNECTIONS.lock();
connections.remove(addr);
break;
}
Ok(size) => {
let message = if let Ok(message) =
SocketMessage::from_str(&String::from_utf8_lossy(&buf[..size]))
{
message
} else {
tracing::warn!("client sent an invalid message, disconnecting: {addr}");
let mut connections = TCP_CONNECTIONS.lock();
connections.remove(addr);
break;
if self.is_paused {
return match message {
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
Ok(self.process_command(message)?)
}
_ => {
tracing::trace!("ignoring while paused");
Ok(())
}
};
let mut wm = wm.lock();
if wm.is_paused {
return match message {
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
Ok(wm.process_command(message)?)
}
_ => {
tracing::trace!("ignoring while paused");
Ok(())
}
};
}
wm.process_command(message.clone())?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: wm.as_ref().into(),
})?)?;
}
}
}
Ok(())
self.process_command(message.clone())?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: (&*self).into(),
})?)?;
}
Ok(())
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,34 +1,30 @@
use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::ser::Error;
use serde::ser::SerializeStruct;
use serde::Serialize;
use serde::Serializer;
use windows::Win32::Foundation::HWND;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
use bindings::Windows::Win32::Foundation::HWND;
use komorebi_core::Rect;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::styles::GwlExStyle;
use crate::styles::GwlStyle;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::LAYERED_WHITELIST;
use crate::LAYERED_EXE_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy, JsonSchema)]
#[derive(Debug, Clone, Copy)]
pub struct Window {
pub(crate) hwnd: isize,
}
@@ -38,18 +34,18 @@ impl Display for Window {
let mut display = format!("(hwnd: {}", self.hwnd);
if let Ok(title) = self.title() {
write!(display, ", title: {}", title)?;
display.push_str(&format!(", title: {}", title));
}
if let Ok(exe) = self.exe() {
write!(display, ", exe: {}", exe)?;
display.push_str(&format!(", exe: {}", exe));
}
if let Ok(class) = self.class() {
write!(display, ", class: {}", class)?;
display.push_str(&format!(", class: {}", class));
}
write!(display, ")")?;
display.push(')');
write!(f, "{}", display)
}
@@ -144,11 +140,7 @@ impl Window {
programmatically_hidden_hwnds.push(self.hwnd);
}
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
match *hiding_behaviour {
HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd()),
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd()),
}
WindowsApi::hide_window(self.hwnd());
}
pub fn restore(self) {
@@ -175,86 +167,32 @@ impl Window {
WindowsApi::maximize_window(self.hwnd());
}
pub fn unmaximize(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds
.iter()
.position(|&hwnd| hwnd == self.hwnd)
{
programmatically_hidden_hwnds.remove(idx);
}
WindowsApi::unmaximize_window(self.hwnd());
}
pub fn raise(self) {
pub fn raise(self) -> Result<()> {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
let current_thread_id = WindowsApi::current_thread_id();
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
// hook has been installed
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not attach to window thread input processing mechanism, but continuing execution of raise(): {}",
error
);
}
};
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
// Raise Window to foreground
match WindowsApi::set_foreground_window(self.hwnd()) {
Ok(_) => {}
Err(error) => {
tracing::error!(
"could not set as foreground window, but continuing execution of raise(): {}",
"could not set as foreground window, but continuing execution of focus(): {}",
error
);
}
};
// This isn't really needed when the above command works as expected via AHK
match WindowsApi::set_focus(self.hwnd()) {
Ok(_) => {}
Err(error) => {
tracing::error!(
"could not set focus, but continuing execution of raise(): {}",
error
);
}
};
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, false) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not detach from window thread input processing mechanism, but continuing execution of raise(): {}",
error
);
}
};
WindowsApi::set_focus(self.hwnd())
}
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
pub fn focus(self) -> Result<()> {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
let current_thread_id = WindowsApi::current_thread_id();
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
// hook has been installed
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not attach to window thread input processing mechanism, but continuing execution of focus(): {}",
error
);
}
};
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
// Raise Window to foreground
match WindowsApi::set_foreground_window(self.hwnd()) {
@@ -268,47 +206,39 @@ impl Window {
};
// Center cursor in Window
if mouse_follows_focus {
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
}
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
// This isn't really needed when the above command works as expected via AHK
match WindowsApi::set_focus(self.hwnd()) {
Ok(_) => {}
Err(error) => {
tracing::error!(
"could not set focus, but continuing execution of focus(): {}",
error
);
}
};
WindowsApi::set_focus(self.hwnd())
}
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, false) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not detach from window thread input processing mechanism, but continuing execution of focus(): {}",
error
);
}
};
pub fn remove_title_bar(self) -> Result<()> {
let mut style = self.style()?;
style.remove(GwlStyle::CAPTION);
style.remove(GwlStyle::THICKFRAME);
self.update_style(style)
}
Ok(())
pub fn add_title_bar(self) -> Result<()> {
let mut style = self.style()?;
style.insert(GwlStyle::CAPTION);
style.insert(GwlStyle::THICKFRAME);
self.update_style(style)
}
#[allow(dead_code)]
pub fn update_style(self, style: WindowStyle) -> Result<()> {
pub fn update_style(self, style: GwlStyle) -> Result<()> {
WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?)
}
pub fn style(self) -> Result<WindowStyle> {
pub fn style(self) -> Result<GwlStyle> {
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
WindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
GwlStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn ex_style(self) -> Result<ExtendedWindowStyle> {
pub fn ex_style(self) -> Result<GwlExStyle> {
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
ExtendedWindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
GwlExStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn title(self) -> Result<String> {
@@ -356,7 +286,55 @@ impl Window {
// If not allowing cloaked windows, we need to ensure the window is not cloaked
(false, false) => {
if let (Ok(title), Ok(exe_name), Ok(class)) = (self.title(), self.exe(), self.class()) {
return Ok(window_is_eligible(&title, &exe_name, &class, self.style()?, self.ex_style()?, event));
{
let float_identifiers = FLOAT_IDENTIFIERS.lock();
if float_identifiers.contains(&title)
|| float_identifiers.contains(&exe_name)
|| float_identifiers.contains(&class) {
return Ok(false);
}
}
let managed_override = {
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
manage_identifiers.contains(&exe_name) || manage_identifiers.contains(&class)
};
let allow_layered = {
let layered_exe_whitelist = LAYERED_EXE_WHITELIST.lock();
layered_exe_whitelist.contains(&exe_name)
};
let allow_wsl2_gui = {
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
wsl2_ui_processes.contains(&exe_name)
};
let allow_titlebar_removed = {
let titlebars_removed = NO_TITLEBAR.lock();
titlebars_removed.contains(&exe_name)
};
let style = self.style()?;
let ex_style = self.ex_style()?;
if (
allow_wsl2_gui
|| allow_titlebar_removed
|| style.contains(GwlStyle::CAPTION) && ex_style.contains(GwlExStyle::WINDOWEDGE)
)
&& !ex_style.contains(GwlExStyle::DLGMODALFRAME)
// Get a lot of dupe events coming through that make the redrawing go crazy
// on FocusChange events if I don't filter out this one. But, if we are
// allowing a specific layered window on the whitelist (like Steam), it should
// pass this check
&& (allow_layered || !ex_style.contains(GwlExStyle::LAYERED))
|| managed_override
{
return Ok(true);
} else if event.is_some() {
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
}
}
}
_ => {}
@@ -365,83 +343,3 @@ impl Window {
Ok(false)
}
}
fn window_is_eligible(
title: &String,
exe_name: &String,
class: &String,
style: WindowStyle,
ex_style: ExtendedWindowStyle,
event: Option<WindowManagerEvent>,
) -> bool {
let mut should_float = false;
let mut matched_identifier = None;
{
let float_identifiers = FLOAT_IDENTIFIERS.lock();
for identifier in float_identifiers.iter() {
if title.starts_with(identifier) || title.ends_with(identifier) {
should_float = true;
matched_identifier = Option::from(ApplicationIdentifier::Title);
}
if class.starts_with(identifier) || class.ends_with(identifier) {
should_float = true;
matched_identifier = Option::from(ApplicationIdentifier::Class);
}
if identifier == exe_name {
should_float = true;
matched_identifier = Option::from(ApplicationIdentifier::Exe);
}
}
};
let managed_override = {
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
matched_identifier.map_or_else(
|| {
manage_identifiers.contains(exe_name)
|| manage_identifiers.contains(class)
|| manage_identifiers.contains(title)
},
|matched_identifier| match matched_identifier {
ApplicationIdentifier::Exe => manage_identifiers.contains(exe_name),
ApplicationIdentifier::Class => manage_identifiers.contains(class),
ApplicationIdentifier::Title => manage_identifiers.contains(title),
},
)
};
if should_float && !managed_override {
return false;
}
let allow_layered = {
let layered_whitelist = LAYERED_WHITELIST.lock();
layered_whitelist.contains(exe_name)
|| layered_whitelist.contains(class)
|| layered_whitelist.contains(title)
};
let allow_wsl2_gui = {
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
wsl2_ui_processes.contains(exe_name)
};
if (allow_wsl2_gui || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
// Get a lot of dupe events coming through that make the redrawing go crazy
// on FocusChange events if I don't filter out this one. But, if we are
// allowing a specific layered window on the whitelist (like Steam), it should
// pass this check
&& (allow_layered || !ex_style.contains(ExtendedWindowStyle::LAYERED))
|| managed_override
{
return true;
} else if event.is_some() {
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
}
false
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,114 +1,85 @@
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::ffi::c_void;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::Error;
use color_eyre::Result;
use windows::core::Result as WindowsCrateResult;
use windows::core::PCSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::POINT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFO;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
use windows::Win32::System::Threading::AttachThreadInput;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::System::Threading::OpenProcess;
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
use windows::Win32::UI::Shell::Common::DEVICE_SCALE_FACTOR;
use windows::Win32::UI::Shell::GetScaleFactorForMonitor;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExA;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassA;
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use bindings::Handle;
use bindings::Result as WindowsCrateResult;
use bindings::Windows::Win32::Foundation::BOOL;
use bindings::Windows::Win32::Foundation::HANDLE;
use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::Foundation::LPARAM;
use bindings::Windows::Win32::Foundation::POINT;
use bindings::Windows::Win32::Foundation::PWSTR;
use bindings::Windows::Win32::Foundation::RECT;
use bindings::Windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
use bindings::Windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use bindings::Windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use bindings::Windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromPoint;
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromWindow;
use bindings::Windows::Win32::Graphics::Gdi::HDC;
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
use bindings::Windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use bindings::Windows::Win32::Graphics::Gdi::MONITORINFO;
use bindings::Windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use bindings::Windows::Win32::System::Threading::AttachThreadInput;
use bindings::Windows::Win32::System::Threading::GetCurrentProcessId;
use bindings::Windows::Win32::System::Threading::GetCurrentThreadId;
use bindings::Windows::Win32::System::Threading::OpenProcess;
use bindings::Windows::Win32::System::Threading::QueryFullProcessImageNameW;
use bindings::Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use bindings::Windows::Win32::System::Threading::PROCESS_NAME_FORMAT;
use bindings::Windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::SetFocus;
use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsIconic;
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use bindings::Windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use komorebi_core::Rect;
use crate::container::Container;
@@ -117,8 +88,6 @@ use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::set_window_position::SetWindowPosition;
use crate::windows_callbacks;
use crate::BORDER_HWND;
use crate::TRANSPARENCY_COLOUR;
pub enum WindowsResult<T, E> {
Err(E),
@@ -140,7 +109,7 @@ macro_rules! impl_from_integer_for_windows_result {
};
}
impl_from_integer_for_windows_result!(usize, isize, u16, u32, i32);
impl_from_integer_for_windows_result!(isize, u32, i32);
impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
fn from(result: WindowsResult<T, E>) -> Self {
@@ -155,16 +124,15 @@ pub trait ProcessWindowsCrateResult<T> {
fn process(self) -> Result<T>;
}
macro_rules! impl_process_windows_crate_integer_wrapper_result {
macro_rules! impl_process_windows_crate_result {
( $($input:ty => $deref:ty),+ $(,)? ) => (
paste::paste! {
$(
impl ProcessWindowsCrateResult<$deref> for $input {
impl ProcessWindowsCrateResult<$deref> for WindowsCrateResult<$input> {
fn process(self) -> Result<$deref> {
if self == $input(0) {
Err(std::io::Error::last_os_error().into())
} else {
Ok(self.0)
match self {
Ok(value) => Ok(value.0),
Err(error) => Err(error.into()),
}
}
}
@@ -173,7 +141,7 @@ macro_rules! impl_process_windows_crate_integer_wrapper_result {
);
}
impl_process_windows_crate_integer_wrapper_result!(
impl_process_windows_crate_result!(
HWND => isize,
);
@@ -197,7 +165,7 @@ impl WindowsApi {
EnumDisplayMonitors(
HDC(0),
std::ptr::null_mut(),
callback,
Option::from(callback),
LPARAM(callback_data_address),
)
}
@@ -209,7 +177,7 @@ impl WindowsApi {
let mut monitors: Vec<isize> = vec![];
let monitors_ref: &mut Vec<isize> = monitors.as_mut();
Self::enum_display_monitors(
Option::Some(windows_callbacks::valid_display_monitors),
windows_callbacks::valid_display_monitors,
monitors_ref as *mut Vec<isize> as isize,
)?;
@@ -218,13 +186,13 @@ impl WindowsApi {
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
Self::enum_display_monitors(
Option::Some(windows_callbacks::enum_display_monitor),
windows_callbacks::enum_display_monitor,
monitors as *mut Ring<Monitor> as isize,
)
}
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
unsafe { EnumWindows(callback, LPARAM(callback_data_address)) }
unsafe { EnumWindows(Option::from(callback), LPARAM(callback_data_address)) }
.ok()
.process()
}
@@ -235,7 +203,7 @@ impl WindowsApi {
if let Some(workspace) = monitor.workspaces_mut().front_mut() {
// EnumWindows will enumerate through windows on all monitors
Self::enum_windows(
Option::Some(windows_callbacks::enum_window),
windows_callbacks::enum_window,
workspace.containers_mut() as *mut VecDeque<Container> as isize,
)?;
@@ -285,39 +253,10 @@ impl WindowsApi {
pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
let flags = SetWindowPosition::NO_ACTIVATE;
let position = if top { HWND_TOPMOST } else { HWND_BOTTOM };
let position = if top { HWND_TOPMOST } else { HWND_NOTOPMOST };
Self::set_window_pos(hwnd, layout, position, flags.bits())
}
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
unsafe { BringWindowToTop(hwnd) }.ok().process()
}
pub fn raise_window(hwnd: HWND) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE;
let position = HWND_TOPMOST;
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
}
pub fn position_border_window(hwnd: HWND, layout: &Rect, activate: bool) -> Result<()> {
let flags = if activate {
SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE
} else {
SetWindowPosition::NO_ACTIVATE
};
let position = HWND_NOTOPMOST;
Self::set_window_pos(hwnd, layout, position, flags.bits())
}
pub fn hide_border_window(hwnd: HWND) -> Result<()> {
let flags = SetWindowPosition::HIDE_WINDOW;
let position = HWND_BOTTOM;
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
}
pub fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
unsafe {
SetWindowPos(
@@ -340,20 +279,12 @@ impl WindowsApi {
unsafe { ShowWindow(hwnd, command) };
}
pub fn minimize_window(hwnd: HWND) {
Self::show_window(hwnd, SW_MINIMIZE);
}
pub fn hide_window(hwnd: HWND) {
Self::show_window(hwnd, SW_HIDE);
}
pub fn restore_window(hwnd: HWND) {
Self::show_window(hwnd, SW_SHOWNOACTIVATE);
}
pub fn unmaximize_window(hwnd: HWND) {
Self::show_window(hwnd, SW_NORMAL);
Self::show_window(hwnd, SW_RESTORE);
}
pub fn maximize_window(hwnd: HWND) {
@@ -361,7 +292,7 @@ impl WindowsApi {
}
pub fn foreground_window() -> Result<isize> {
unsafe { GetForegroundWindow() }.process()
unsafe { GetForegroundWindow() }.ok().process()
}
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
@@ -370,16 +301,16 @@ impl WindowsApi {
#[allow(dead_code)]
pub fn top_window() -> Result<isize> {
unsafe { GetTopWindow(HWND::default()) }.process()
unsafe { GetTopWindow(HWND::default()) }.ok().process()
}
pub fn desktop_window() -> Result<isize> {
unsafe { GetDesktopWindow() }.process()
unsafe { GetDesktopWindow() }.ok().process()
}
#[allow(dead_code)]
pub fn next_window(hwnd: HWND) -> Result<isize> {
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.ok().process()
}
#[allow(dead_code)]
@@ -417,7 +348,7 @@ impl WindowsApi {
}
pub fn window_from_point(point: POINT) -> Result<isize> {
unsafe { WindowFromPoint(point) }.process()
unsafe { WindowFromPoint(point) }.ok().process()
}
pub fn window_at_cursor_pos() -> Result<isize> {
@@ -446,19 +377,6 @@ impl WindowsApi {
unsafe { GetCurrentProcessId() }
}
pub fn process_id_to_session_id() -> Result<u32> {
let process_id = Self::current_process_id();
let mut session_id = 0;
unsafe {
if ProcessIdToSessionId(process_id, &mut session_id).as_bool() {
Ok(session_id)
} else {
Err(anyhow!("could not determine current session id"))
}
}
}
pub fn attach_thread_input(thread_id: u32, target_thread_id: u32, attach: bool) -> Result<()> {
unsafe { AttachThreadInput(thread_id, target_thread_id, attach) }
.ok()
@@ -466,7 +384,7 @@ impl WindowsApi {
}
pub fn set_focus(hwnd: HWND) -> Result<()> {
unsafe { SetFocus(hwnd) }.process().map(|_| ())
unsafe { SetFocus(hwnd) }.ok().map(|_| ()).process()
}
#[allow(dead_code)]
@@ -490,9 +408,9 @@ impl WindowsApi {
}
fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result<isize> {
Result::from(WindowsResult::from(unsafe {
GetWindowLongPtrW(hwnd, index)
}))
// Can return 0, which does not always mean that an error has occurred
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
Result::from(unsafe { WindowsResult::Ok(GetWindowLongPtrW(hwnd, index)) })
}
#[allow(dead_code)]
@@ -500,14 +418,11 @@ impl WindowsApi {
Self::set_window_long_ptr_w(hwnd, GWL_STYLE, new_value)
}
#[allow(dead_code)]
pub fn update_ex_style(hwnd: HWND, new_value: isize) -> Result<()> {
Self::set_window_long_ptr_w(hwnd, GWL_EXSTYLE, new_value)
}
pub fn window_text_w(hwnd: HWND) -> Result<String> {
let mut text: [u16; 512] = [0; 512];
match WindowsResult::from(unsafe { GetWindowTextW(hwnd, &mut text) }) {
match WindowsResult::from(unsafe {
GetWindowTextW(hwnd, PWSTR(text.as_mut_ptr()), text.len().try_into()?)
}) {
WindowsResult::Ok(len) => {
let length = usize::try_from(len)?;
Ok(String::from_utf16(&text[..length])?)
@@ -521,7 +436,9 @@ impl WindowsApi {
inherit_handle: bool,
process_id: u32,
) -> Result<HANDLE> {
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }.process()
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }
.ok()
.process()
}
pub fn process_handle(process_id: u32) -> Result<HANDLE> {
@@ -536,9 +453,9 @@ impl WindowsApi {
unsafe {
QueryFullProcessImageNameW(
handle,
PROCESS_NAME_WIN32,
PROCESS_NAME_FORMAT(0),
PWSTR(text_ptr),
std::ptr::addr_of_mut!(len),
&mut len as *mut u32,
)
}
.ok()
@@ -560,7 +477,7 @@ impl WindowsApi {
let mut class: [u16; BUF_SIZE] = [0; BUF_SIZE];
let len = Result::from(WindowsResult::from(unsafe {
RealGetWindowClassW(hwnd, &mut class)
RealGetWindowClassW(hwnd, PWSTR(class.as_mut_ptr()), u32::try_from(BUF_SIZE)?)
}))?;
Ok(String::from_utf16(&class[0..len as usize])?)
@@ -574,7 +491,7 @@ impl WindowsApi {
unsafe {
DwmGetWindowAttribute(
hwnd,
attribute,
std::mem::transmute::<_, u32>(attribute),
(value as *mut T).cast(),
u32::try_from(std::mem::size_of::<T>())?,
)?;
@@ -617,7 +534,7 @@ impl WindowsApi {
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
unsafe { GetMonitorInfoW(hmonitor, std::ptr::addr_of_mut!(monitor_info).cast()) }
unsafe { GetMonitorInfoW(hmonitor, (&mut monitor_info as *mut MONITORINFO).cast()) }
.ok()
.process()?;
@@ -634,12 +551,6 @@ impl WindowsApi {
))
}
pub fn set_process_dpi_awareness_context() -> Result<()> {
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }
.ok()
.process()
}
#[allow(dead_code)]
pub fn system_parameters_info_w(
action: SYSTEM_PARAMETERS_INFO_ACTION,
@@ -659,7 +570,7 @@ impl WindowsApi {
Self::system_parameters_info_w(
SPI_GETACTIVEWINDOWTRACKING,
0,
std::ptr::addr_of_mut!(is_enabled).cast(),
(&mut is_enabled as *mut BOOL).cast(),
SPIF_SENDCHANGE,
)?;
@@ -685,77 +596,4 @@ impl WindowsApi {
SPIF_SENDCHANGE,
)
}
pub fn module_handle_w() -> Result<HINSTANCE> {
unsafe { GetModuleHandleW(None) }.process()
}
pub fn create_solid_brush(colour: u32) -> HBRUSH {
unsafe { CreateSolidBrush(colour) }
}
pub fn register_class_a(window_class: &WNDCLASSA) -> Result<u16> {
Result::from(WindowsResult::from(unsafe { RegisterClassA(window_class) }))
}
pub fn scale_factor_for_monitor(hmonitor: isize) -> Result<DEVICE_SCALE_FACTOR> {
unsafe { GetScaleFactorForMonitor(HMONITOR(hmonitor)) }.process()
}
pub fn monitors_have_same_scale_factor(a: isize, b: isize) -> Result<bool> {
let a = Self::scale_factor_for_monitor(a)?;
let b = Self::scale_factor_for_monitor(b)?;
Ok(a == b)
}
pub fn round_corners(hwnd: isize) -> Result<()> {
let round = DWMWCP_ROUND;
unsafe {
DwmSetWindowAttribute(
HWND(hwnd),
DWMWA_WINDOW_CORNER_PREFERENCE,
std::ptr::addr_of!(round).cast(),
4,
)
}
.process()
}
pub fn create_border_window(name: PCSTR, instance: HINSTANCE) -> Result<isize> {
unsafe {
let hwnd = CreateWindowExA(
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
name,
name,
WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
None,
instance,
std::ptr::null(),
);
SetLayeredWindowAttributes(hwnd, TRANSPARENCY_COLOUR, 0, LWA_COLORKEY);
hwnd
}
.process()
}
pub fn invalidate_border_rect() -> Result<()> {
unsafe {
InvalidateRect(
HWND(BORDER_HWND.load(Ordering::SeqCst)),
std::ptr::null(),
false,
)
}
.ok()
.process()
}
}

View File

@@ -1,27 +1,12 @@
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::BeginPaint;
use windows::Win32::Graphics::Gdi::CreatePen;
use windows::Win32::Graphics::Gdi::EndPaint;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::SelectObject;
use windows::Win32::Graphics::Gdi::ValidateRect;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use bindings::Windows::Win32::Foundation::BOOL;
use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::Foundation::LPARAM;
use bindings::Windows::Win32::Foundation::RECT;
use bindings::Windows::Win32::Graphics::Gdi::HDC;
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
use bindings::Windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use crate::container::Container;
use crate::monitor::Monitor;
@@ -30,9 +15,6 @@ use crate::window::Window;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_RECT;
use crate::TRANSPARENCY_COLOUR;
pub extern "system" fn valid_display_monitors(
hmonitor: HMONITOR,
@@ -121,35 +103,3 @@ pub extern "system" fn win_event_hook(
}
}
}
pub extern "system" fn border_window(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message as u32 {
WM_PAINT => {
let border_rect = *BORDER_RECT.lock();
let mut ps = PAINTSTRUCT::default();
let hdc = BeginPaint(window, std::ptr::addr_of_mut!(ps).cast());
let hpen = CreatePen(PS_SOLID, 20, BORDER_COLOUR_CURRENT.load(Ordering::SeqCst));
let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
SelectObject(hdc, hpen);
SelectObject(hdc, hbrush);
Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom);
EndPaint(window, &ps);
ValidateRect(window, std::ptr::null());
LRESULT(0)
}
WM_DESTROY => {
PostQuitMessage(0);
LRESULT(0)
}
_ => DefWindowProcW(window, message, wparam, lparam),
}
}
}

View File

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

View File

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

View File

@@ -8,12 +8,11 @@ use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Serialize;
use komorebi_core::Axis;
use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::Flip;
use komorebi_core::Layout;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
@@ -22,9 +21,10 @@ use crate::container::Container;
use crate::ring::Ring;
use crate::window::Window;
use crate::windows_api::WindowsApi;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REMOVE_TITLEBARS;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
pub struct Workspace {
#[getset(set = "pub")]
name: Option<String>,
@@ -41,12 +41,10 @@ pub struct Workspace {
maximized_window_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub")]
floating_windows: Vec<Window>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
#[getset(get = "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>,
layout_flip: Option<Flip>,
#[getset(get_copy = "pub", set = "pub")]
workspace_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
@@ -73,7 +71,6 @@ 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),
@@ -107,7 +104,7 @@ impl Workspace {
}
}
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
pub fn restore(&mut self) -> Result<()> {
let idx = self.focused_container_idx();
let mut to_focus = None;
for (i, container) in self.containers_mut().iter_mut().enumerate() {
@@ -138,7 +135,7 @@ impl Workspace {
// Maximised windows should always be drawn at the top of the Z order
if let Some(window) = to_focus {
if self.maximized_window().is_none() {
window.focus(mouse_follows_focus)?;
window.focus()?;
}
}
@@ -151,10 +148,6 @@ impl Workspace {
offset: Option<Rect>,
invisible_borders: &Rect,
) -> Result<()> {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
return Ok(());
}
let container_padding = self.container_padding();
let mut adjusted_work_area = offset.map_or_else(
|| *work_area,
@@ -173,24 +166,6 @@ impl Workspace {
self.enforce_resize_constraints();
if !self.layout_rules().is_empty() {
let mut updated_layout = None;
for rule in self.layout_rules() {
if self.containers().len() >= rule.0 {
updated_layout = Option::from(rule.1.clone());
}
}
if let Some(updated_layout) = updated_layout {
if !matches!(updated_layout, Layout::Default(DefaultLayout::BSP)) {
self.set_layout_flip(None);
}
self.set_layout(updated_layout);
}
}
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
@@ -212,9 +187,18 @@ impl Workspace {
self.resize_dimensions(),
);
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
let no_titlebar = { NO_TITLEBAR.lock().clone() };
let windows = self.visible_windows_mut();
for (i, window) in windows.into_iter().enumerate() {
if let (Some(window), Some(layout)) = (window, layouts.get(i)) {
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
window.remove_title_bar()?;
} else if no_titlebar.contains(&window.exe()?) {
window.add_title_bar()?;
}
window.set_position(layout, invisible_borders, false)?;
}
}
@@ -344,45 +328,6 @@ impl Workspace {
None
}
pub fn contains_managed_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {
return true;
}
}
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return true;
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return true;
}
}
false
}
pub fn is_focused_window_monocle_or_maximized(&self) -> Result<bool> {
let hwnd = WindowsApi::foreground_window()?;
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return Ok(true);
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return Ok(true);
}
}
Ok(false)
}
pub fn contains_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {
@@ -439,11 +384,7 @@ impl Workspace {
self.focus_last_container();
}
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
self.containers_mut().insert(idx, container);
}
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
if idx < self.resize_dimensions().len() {
self.resize_dimensions_mut().remove(idx);
}
@@ -493,7 +434,6 @@ impl Workspace {
if let Some(window) = self.maximized_window() {
if window.hwnd == hwnd {
window.unmaximize();
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
return Ok(());
@@ -528,12 +468,10 @@ impl Workspace {
if self.resize_dimensions().get(container_idx).is_some() {
self.resize_dimensions_mut().remove(container_idx);
}
self.focus_previous_container();
} else {
container.load_focused_window();
}
self.focus_previous_container();
Ok(())
}
@@ -545,13 +483,6 @@ impl Workspace {
container
}
pub fn remove_container(&mut self, idx: usize) -> Option<Container> {
let container = self.remove_container_by_idx(idx);
self.focus_previous_container();
container
}
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
let len = NonZeroUsize::new(self.containers().len())?;
@@ -675,44 +606,22 @@ impl Workspace {
}
pub fn new_floating_window(&mut self) -> Result<()> {
let window = if let Some(maximized_window) = self.maximized_window() {
let window = *maximized_window;
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
window
} else if let Some(monocle_container) = self.monocle_container_mut() {
let window = monocle_container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
let focused_idx = self.focused_container_idx();
if monocle_container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
} else {
monocle_container.load_focused_window();
}
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
window
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
} else {
let focused_idx = self.focused_container_idx();
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
} else {
container.load_focused_window();
}
window
};
container.load_focused_window();
}
self.floating_windows_mut().push(window);
@@ -798,53 +707,6 @@ impl Workspace {
pub fn new_maximized_window(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let foreground_hwnd = WindowsApi::foreground_window()?;
let mut floating_window = None;
if !self.floating_windows().is_empty() {
let mut focused_floating_window_idx = None;
for (i, w) in self.floating_windows().iter().enumerate() {
if w.hwnd == foreground_hwnd {
focused_floating_window_idx = Option::from(i);
}
}
if let Some(idx) = focused_floating_window_idx {
floating_window = Option::from(self.floating_windows_mut().remove(idx));
}
}
if let Some(floating_window) = floating_window {
self.set_maximized_window(Option::from(floating_window));
self.set_maximized_window_restore_idx(Option::from(focused_idx));
if let Some(window) = self.maximized_window() {
window.maximize();
}
return Ok(());
}
let monocle_restore_idx = self.monocle_container_restore_idx();
if let Some(monocle_container) = self.monocle_container_mut() {
let window = monocle_container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if monocle_container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
} else {
monocle_container.load_focused_window();
}
self.set_maximized_window(Option::from(window));
self.set_maximized_window_restore_idx(monocle_restore_idx);
if let Some(window) = self.maximized_window() {
window.maximize();
}
return Ok(());
}
let container = self
.focused_container_mut()

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.13"
version = "0.1.6"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
@@ -11,26 +11,17 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bindings = { package = "bindings", path = "../bindings" }
derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" }
clap = { version = "3", features = ["derive", "wrap_help"] }
color-eyre = "0.6"
clap = "3.0.0-beta.4"
color-eyre = "0.5"
dirs = "4"
fs-tail = "0.1"
heck = "0.4"
lazy_static = "1"
heck = "0.3"
paste = "1"
powershell_script = "1.0"
powershell_script = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
sysinfo = "0.25"
uds_windows = "1"
[dependencies.windows]
version = "0.39"
features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging"
]

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

View File

@@ -1,174 +0,0 @@
<?xml version='1.0' encoding='windows-1252'?>
<!--
Copyright (C) 2017 Christopher R. Field.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
The "cargo wix" subcommand provides a variety of predefined variables available
for customization of this template. The values for each variable are set at
installer creation time. The following variables are available:
TargetTriple = The rustc target triple name.
TargetEnv = The rustc target environment. This is typically either
"msvc" or "gnu" depending on the toolchain downloaded and
installed.
TargetVendor = The rustc target vendor. This is typically "pc", but Rust
does support other vendors, like "uwp".
CargoTargetBinDir = The complete path to the binary (exe). The default would
be "target\release\<BINARY_NAME>.exe" where
"<BINARY_NAME>" is replaced with the name of each binary
target defined in the package's manifest (Cargo.toml). If
a different rustc target triple is used than the host,
i.e. cross-compiling, then the default path would be
"target\<CARGO_TARGET>\<CARGO_PROFILE>\<BINARY_NAME>.exe",
where "<CARGO_TARGET>" is replaced with the "CargoTarget"
variable value and "<CARGO_PROFILE>" is replaced with the
value from the `CargoProfile` variable.
CargoTargetDir = The path to the directory for the build artifacts, i.e.
"target".
CargoProfile = Either "debug" or `release` depending on the build
profile. The default is "release".
Version = The version for the installer. The default is the
"Major.Minor.Fix" semantic versioning number of the Rust
package.
-->
<!--
Please do not remove these pre-processor If-Else blocks. These are used with
the `cargo wix` subcommand to automatically determine the installation
destination for 32-bit versus 64-bit installers. Removal of these lines will
cause installation errors.
-->
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder"?>
<?else ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder"?>
<?endif ?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product Id='*' Name='komorebi' UpgradeCode='F8B967B5-7E7B-4E3A-895B-B789EC898B54' Manufacturer='LGUG2Z' Language='1033' Codepage='1252' Version='$(var.Version)'>
<Package Id='*' Keywords='Installer' Description='A tiling window manager for Windows' Manufacturer='LGUG2Z' InstallerVersion='450' Languages='1033' Compressed='yes' InstallScope='perMachine' SummaryCodepage='1252' />
<MajorUpgrade Schedule='afterInstallInitialize' DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.' />
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1' />
<Property Id='DiskPrompt' Value='komorebi Installation' />
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
<Directory Id='APPLICATIONFOLDER' Name='komorebi'>
<!--
Disabling the license sidecar file in the installer is a two step process:
1. Comment out or remove the `Component` tag along with its contents.
2. Comment out or remove the `ComponentRef` tag with the "License" Id
attribute value further down in this file.
-->
<Component Id='License' Guid='*'>
<File Id='LicenseFile' Name='License.rtf' DiskId='1' Source='wix\License.rtf' KeyPath='yes' />
</Component>
<Directory Id='Bin' Name='bin'>
<Component Id='Path' Guid='6C6DF276-06C4-4675-BDED-48C5C2BC9BC5' KeyPath='yes'>
<Environment Id='PATH' Name='PATH' Value='[Bin]' Permanent='no' Part='last' Action='set' System='yes' />
</Component>
<Component Id='binary0' Guid='*'>
<File Id='exe0' Name='komorebi.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi.exe' KeyPath='yes' />
</Component>
<Component Id='binary1' Guid='*'>
<File Id='exe1' Name='komorebic.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic.exe' KeyPath='yes' />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id='Binaries' Title='Application' Description='Installs all binaries and the license.' Level='1' ConfigurableDirectory='APPLICATIONFOLDER' AllowAdvertise='no' Display='expand' Absent='disallow'>
<!--
Comment out or remove the following `ComponentRef` tag to remove
the license sidecar file from the installer.
-->
<ComponentRef Id='License' />
<ComponentRef Id='binary0' />
<ComponentRef Id='binary1' />
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
<ComponentRef Id='Path' />
</Feature>
</Feature>
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize' />
<!--
Uncomment the following `Icon` and `Property` tags to change the product icon.
The product icon is the graphic that appears in the Add/Remove
Programs control panel for the application.
-->
<!--<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>-->
<!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->
<Property Id='ARPHELPLINK' Value='https://github.com/LGUG2Z/komorebi' />
<UI>
<UIRef Id='WixUI_FeatureTree' />
<!--
Disabling the EULA dialog in the installer is a two step process:
1. Uncomment the following two `Publish` tags
2. Comment out or remove the `<WiXVariable Id='WixUILicenseRtf'...` tag further down
-->
<!--<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>-->
<!--<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>-->
</UI>
<!--
Disabling the EULA dialog in the installer requires commenting out
or removing the following `WixVariable` tag
-->
<WixVariable Id='WixUILicenseRtf' Value='wix\License.rtf' />
<!--
Uncomment the next `WixVaraible` tag to customize the installer's
Graphical User Interface (GUI) and add a custom banner image across
the top of each screen. See the WiX Toolset documentation for details
about customization.
The banner BMP dimensions are 493 x 58 pixels.
-->
<!--<WixVariable Id='WixUIBannerBmp' Value='wix\Banner.bmp'/>-->
<!--
Uncomment the next `WixVariable` tag to customize the installer's
Graphical User Interface (GUI) and add a custom image to the first
dialog, or screen. See the WiX Toolset documentation for details about
customization.
The dialog BMP dimensions are 493 x 312 pixels.
-->
<!--<WixVariable Id='WixUIDialogBmp' Value='wix\Dialog.bmp'/>-->
</Product>
</Wix>