mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-10 23:37:41 +01:00
Compare commits
22 Commits
v0.1.3
...
feature/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
355bcb6877 | ||
|
|
b8a27a93fe | ||
|
|
28a641609c | ||
|
|
f1ee5ea194 | ||
|
|
ff53533da0 | ||
|
|
c4c8bd7d4b | ||
|
|
e1bd0e9fcb | ||
|
|
368d41e3e0 | ||
|
|
2b7c51b87b | ||
|
|
ce3c742e09 | ||
|
|
2a4e6fa6da | ||
|
|
2d19109fb6 | ||
|
|
a9a0ecd49d | ||
|
|
5ec2b80c3a | ||
|
|
4cc059ff1d | ||
|
|
d4d7e2b694 | ||
|
|
84752c4338 | ||
|
|
ffa0786178 | ||
|
|
752dde2288 | ||
|
|
dc771a104d | ||
|
|
e5a7c140ff | ||
|
|
61b231be28 |
1
.github/workflows/windows.yaml
vendored
1
.github/workflows/windows.yaml
vendored
@@ -66,7 +66,6 @@ jobs:
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe
|
||||
.\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal
|
||||
del rustup-init.exe
|
||||
shell: powershell
|
||||
- name: Ensure stable toolchain is up to date
|
||||
run: rustup update stable
|
||||
|
||||
97
Cargo.lock
generated
97
Cargo.lock
generated
@@ -73,9 +73,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -310,7 +310,7 @@ checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.2.10",
|
||||
"redox_syscall",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -509,6 +509,7 @@ version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"bitflags",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils",
|
||||
@@ -530,6 +531,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"uds_windows",
|
||||
"which",
|
||||
"winput",
|
||||
"winvd",
|
||||
]
|
||||
|
||||
@@ -578,15 +580,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.100"
|
||||
version = "0.2.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
||||
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
@@ -768,9 +770,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.26.1"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2766204889d09937d00bfbb7fec56bb2a199e2ade963cab19185d8a6104c7c"
|
||||
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -795,9 +797,9 @@ checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
@@ -806,16 +808,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"petgraph",
|
||||
"redox_syscall 0.2.10",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"thread-id",
|
||||
"winapi 0.3.9",
|
||||
@@ -881,9 +883,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.28"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
|
||||
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
@@ -999,12 +1001,6 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
@@ -1021,7 +1017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall 0.2.10",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1059,9 +1055,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
@@ -1086,18 +1082,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.128"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.128"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1106,9 +1102,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.66"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
|
||||
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -1165,9 +1161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.75"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7"
|
||||
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1176,9 +1172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4786f320388e6031aa78c68455553f4e3425deeeb40565fecba3d101c1faf21f"
|
||||
checksum = "92d77883450d697c0010e60db3d940ed130b0ed81d27485edee981621b434e52"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"core-foundation-sys",
|
||||
@@ -1219,12 +1215,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "3.3.0"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
|
||||
checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -1249,9 +1245,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
|
||||
checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"pin-project-lite",
|
||||
@@ -1272,9 +1268,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
|
||||
checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1283,9 +1279,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.19"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8"
|
||||
checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
@@ -1323,9 +1319,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.20"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cbe87a2fa7e35900ce5de20220a582a9483a7063811defce79d7cbd59d4cfe"
|
||||
checksum = "62af966210b88ad5776ee3ba12d5f35b8d6a2b2a12168f3080cf02b814d7376b"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"chrono",
|
||||
@@ -1499,6 +1495,15 @@ version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab"
|
||||
|
||||
[[package]]
|
||||
name = "winput"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd4bec39938e0ae68b300e2a4197b6437f13d53d1c146c6e297e346a71d5dde9"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winvd"
|
||||
version = "0.0.20"
|
||||
|
||||
36
README.md
36
README.md
@@ -14,6 +14,10 @@ _komorebi_ allows you to control application windows, virtual workspaces and dis
|
||||
used with third-party software such as [AutoHotKey](https://github.com/Lexikos/AutoHotkey_L) to set user-defined
|
||||
keyboard shortcuts.
|
||||
|
||||
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))
|
||||
|
||||
## Description
|
||||
|
||||
_komorebi_ only responds to [WinEvents](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) and the
|
||||
@@ -160,6 +164,26 @@ komorebic.exe identify-tray-application exe Discord.exe
|
||||
# komorebic.exe identify-tray-application title [TITLE]
|
||||
```
|
||||
|
||||
#### Focus Follows Mouse
|
||||
|
||||
`komorebi` supports two focus-follows-mouse implementations; the native Windows Xmouse implementation, which treats the
|
||||
desktop, the task bar, and the system tray as windows and switches focus to them eagerly, and a custom `komorebi`
|
||||
implementation, which only considers windows managed by `komorebi` as valid targets to switch focus to when moving the
|
||||
mouse.
|
||||
|
||||
To enable the `komorebi` implementation you must start the process with the `--ffm` flag to explicitly enable the feature.
|
||||
This is because the mouse tracking required for this feature significantly increases the CPU usage of the process (on my
|
||||
machine, it jumps from <1% to ~4~), and this CPU increase persists regardless of whether focus-follows-mouse is enabled
|
||||
or disabled at any given time via `komorebic`'s configuration commands.
|
||||
|
||||
When calling any of the `komorebic` commands related to focus-follows-mouse functionality, the `windows`
|
||||
implementation will be chosen as the default implementation. You can optionally specify the `komorebi` implementation by
|
||||
passing it as an argument to the `--implementation` flag:
|
||||
|
||||
```powershell
|
||||
komorebic.exe toggle-focus-follows-mouse --implementation komorebi
|
||||
```
|
||||
|
||||
## Configuration with `komorebic`
|
||||
|
||||
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
|
||||
@@ -174,6 +198,7 @@ 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
|
||||
log Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||
focus Change focus to the window in the specified direction
|
||||
move Move the focused window in the specified direction
|
||||
@@ -188,6 +213,7 @@ 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
|
||||
new-workspace Create and append a new workspace on the focused monitor
|
||||
invisible-borders Set the invisible border dimensions around each window
|
||||
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
|
||||
@@ -214,6 +240,7 @@ float-rule Add a rule to always float the specified applicati
|
||||
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
|
||||
@@ -254,11 +281,14 @@ used [is available here](komorebi.sample.with.lib.ahk).
|
||||
- [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 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 focus follows mouse
|
||||
- [x] Toggle Xmouse/Windows focus follows mouse implementation
|
||||
- [x] Toggle Komorebi focus follows mouse implementation (desktop and system tray-aware)
|
||||
- [x] Toggle automatic tiling
|
||||
- [x] Pause all window management
|
||||
- [x] Load configuration on startup
|
||||
@@ -266,6 +296,7 @@ used [is available here](komorebi.sample.with.lib.ahk).
|
||||
- [x] Watch configuration for changes
|
||||
- [x] Helper library for AutoHotKey
|
||||
- [x] View window manager state
|
||||
- [x] Query window manager state
|
||||
|
||||
## Development
|
||||
|
||||
@@ -314,8 +345,7 @@ process and the log will appear frozen.
|
||||
|
||||
If you believe you have encountered a deadlock, you can compile `komorebi` with `--features deadlock_detection` and try
|
||||
reproducing the deadlock again. This will check for deadlocks every 5 seconds in the background, and if a deadlock is
|
||||
found, information about it will appear in the log which can be shared when opening an issu which can be shared when
|
||||
opening an issue.
|
||||
found, information about it will appear in the log which can be shared when opening an issue.
|
||||
|
||||
## Window Manager State and Integrations
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
fn main() {
|
||||
windows::build!(
|
||||
Windows::Win32::Devices::HumanInterfaceDevice::HID_USAGE_PAGE_GENERIC,
|
||||
Windows::Win32::Devices::HumanInterfaceDevice::HID_USAGE_GENERIC_MOUSE,
|
||||
Windows::Win32::Foundation::RECT,
|
||||
Windows::Win32::Foundation::POINT,
|
||||
Windows::Win32::Foundation::BOOL,
|
||||
@@ -10,6 +12,7 @@ fn main() {
|
||||
Windows::Win32::Graphics::Dwm::*,
|
||||
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
|
||||
Windows::Win32::Graphics::Gdi::*,
|
||||
Windows::Win32::System::LibraryLoader::GetModuleHandleW,
|
||||
Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS,
|
||||
Windows::Win32::System::Threading::PROCESS_NAME_FORMAT,
|
||||
Windows::Win32::System::Threading::OpenProcess,
|
||||
@@ -17,7 +20,8 @@ fn main() {
|
||||
Windows::Win32::System::Threading::GetCurrentThreadId,
|
||||
Windows::Win32::System::Threading::AttachThreadInput,
|
||||
Windows::Win32::System::Threading::GetCurrentProcessId,
|
||||
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
|
||||
// error: `Windows.Win32.UI.KeyboardAndMouseInput.RIM_TYPEMOUSE` not found in metadata
|
||||
Windows::Win32::UI::KeyboardAndMouseInput::*,
|
||||
Windows::Win32::UI::Accessibility::SetWinEventHook,
|
||||
Windows::Win32::UI::Accessibility::HWINEVENTHOOK,
|
||||
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
|
||||
|
||||
@@ -8,11 +8,11 @@ use ::std::convert::Into;
|
||||
use ::std::iter::Extend;
|
||||
use ::std::iter::Iterator;
|
||||
use ::std::matches;
|
||||
use ::std::option::Option::Some;
|
||||
use ::std::string::ToString;
|
||||
use ::std::unreachable;
|
||||
|
||||
use ::quote::quote;
|
||||
use ::std::option::Option::Some;
|
||||
use ::syn::parse_macro_input;
|
||||
use ::syn::Data;
|
||||
use ::syn::DataEnum;
|
||||
@@ -20,7 +20,10 @@ use ::syn::DeriveInput;
|
||||
use ::syn::Fields;
|
||||
use ::syn::FieldsNamed;
|
||||
use ::syn::FieldsUnnamed;
|
||||
use ::syn::Meta;
|
||||
use ::syn::NestedMeta;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[proc_macro_derive(AhkFunction)]
|
||||
pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
@@ -29,29 +32,118 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
|
||||
match input.data {
|
||||
Data::Struct(s) => match s.fields {
|
||||
Fields::Named(FieldsNamed { named, .. }) => {
|
||||
let idents = named.iter().map(|f| &f.ident);
|
||||
let arguments = quote! {#(#idents), *}.to_string();
|
||||
let argument_idents = named
|
||||
.iter()
|
||||
// Filter out the flags
|
||||
.filter(|&f| {
|
||||
let mut include = true;
|
||||
for attribute in &f.attrs {
|
||||
if let ::std::result::Result::Ok(Meta::List(list)) =
|
||||
attribute.parse_meta()
|
||||
{
|
||||
for nested in list.nested {
|
||||
if let NestedMeta::Meta(Meta::Path(path)) = nested {
|
||||
if path.is_ident("long") {
|
||||
include = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let idents = named.iter().map(|f| &f.ident);
|
||||
let called_arguments = quote! {#(%#idents%) *}
|
||||
include
|
||||
})
|
||||
.map(|f| &f.ident);
|
||||
|
||||
let argument_idents_clone = argument_idents.clone();
|
||||
|
||||
let called_arguments = quote! {#(%#argument_idents_clone%) *}
|
||||
.to_string()
|
||||
.replace(" %", "%")
|
||||
.replace("% ", "%")
|
||||
.replace("%%", "% %");
|
||||
|
||||
quote! {
|
||||
impl AhkFunction for #name {
|
||||
fn generate_ahk_function() -> String {
|
||||
::std::format!(r#"
|
||||
let flag_idents = named
|
||||
.iter()
|
||||
// Filter only the flags
|
||||
.filter(|f| {
|
||||
let mut include = false;
|
||||
|
||||
for attribute in &f.attrs {
|
||||
if let ::std::result::Result::Ok(Meta::List(list)) =
|
||||
attribute.parse_meta()
|
||||
{
|
||||
for nested in list.nested {
|
||||
if let NestedMeta::Meta(Meta::Path(path)) = nested {
|
||||
// Identify them using the --long flag name
|
||||
if path.is_ident("long") {
|
||||
include = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include
|
||||
})
|
||||
.map(|f| &f.ident);
|
||||
|
||||
let has_flags = flag_idents.clone().count() != 0;
|
||||
|
||||
if has_flags {
|
||||
let flag_idents_concat = flag_idents.clone();
|
||||
let argument_idents_concat = argument_idents.clone();
|
||||
|
||||
// Concat the args and flag args if there are flags
|
||||
let all_arguments =
|
||||
quote! {#(#argument_idents_concat,) * #(#flag_idents_concat), *}
|
||||
.to_string();
|
||||
|
||||
let flag_idents_clone = flag_idents.clone();
|
||||
let flags = quote! {#(--#flag_idents_clone) *}
|
||||
.to_string()
|
||||
.replace("- - ", "--");
|
||||
|
||||
let called_flag_arguments = quote! {#(%#flag_idents%) *}
|
||||
.to_string()
|
||||
.replace(" %", "%")
|
||||
.replace("% ", "%")
|
||||
.replace("%%", "% %");
|
||||
|
||||
quote! {
|
||||
impl AhkFunction for #name {
|
||||
fn generate_ahk_function() -> String {
|
||||
::std::format!(r#"
|
||||
{}({}) {{
|
||||
Run, komorebic.exe {} {} {} {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
#all_arguments,
|
||||
::std::stringify!(#name).to_kebab_case(),
|
||||
#called_arguments,
|
||||
#flags,
|
||||
#called_flag_arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let arguments = quote! {#(#argument_idents), *}.to_string();
|
||||
|
||||
quote! {
|
||||
impl AhkFunction for #name {
|
||||
fn generate_ahk_function() -> String {
|
||||
::std::format!(r#"
|
||||
{}({}) {{
|
||||
Run, komorebic.exe {} {}, , Hide
|
||||
}}"#,
|
||||
::std::stringify!(#name),
|
||||
#arguments,
|
||||
stringify!(#name).to_kebab_case(),
|
||||
#called_arguments
|
||||
)
|
||||
}
|
||||
::std::stringify!(#name),
|
||||
#arguments,
|
||||
::std::stringify!(#name).to_kebab_case(),
|
||||
#called_arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,16 @@ pub enum SocketMessage {
|
||||
// Configuration
|
||||
ReloadConfiguration,
|
||||
WatchConfiguration(bool),
|
||||
InvisibleBorders(Rect),
|
||||
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
|
||||
FloatRule(ApplicationIdentifier, String),
|
||||
ManageRule(ApplicationIdentifier, String),
|
||||
IdentifyTrayApplication(ApplicationIdentifier, String),
|
||||
IdentifyBorderOverflow(ApplicationIdentifier, String),
|
||||
State,
|
||||
FocusFollowsMouse(bool),
|
||||
ToggleFocusFollowsMouse,
|
||||
Query(StateQuery),
|
||||
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
|
||||
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
@@ -89,6 +92,15 @@ impl FromStr for SocketMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum StateQuery {
|
||||
FocusedMonitorIndex,
|
||||
FocusedWorkspaceIndex,
|
||||
FocusedContainerIndex,
|
||||
FocusedWindowIndex,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ApplicationIdentifier {
|
||||
@@ -97,6 +109,13 @@ pub enum ApplicationIdentifier {
|
||||
Title,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Sizing {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Rect {
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
@@ -10,17 +11,6 @@ pub struct Rect {
|
||||
pub bottom: i32,
|
||||
}
|
||||
|
||||
impl Default for Rect {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RECT> for Rect {
|
||||
fn from(rect: RECT) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
; Enable hot reloading of changes to this file
|
||||
Run, komorebic.exe watch-configuration enable, , Hide
|
||||
|
||||
; Configure the invisible border dimensions
|
||||
Run, komorebic.exe invisible-borders 7 0 14 7, , Hide
|
||||
|
||||
; Enable focus follows mouse
|
||||
Run, komorebic.exe focus-follows-mouse enable, , Hide
|
||||
|
||||
@@ -52,6 +55,9 @@ 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::
|
||||
Run, komorebic.exe focus left, , Hide
|
||||
|
||||
@@ -7,6 +7,9 @@ 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")
|
||||
|
||||
@@ -40,6 +43,12 @@ FloatRule("exe", "Wox.exe")
|
||||
|
||||
; Identify Minimize-to-Tray Applications
|
||||
IdentifyTrayApplication("exe", "Discord.exe")
|
||||
IdentifyTrayApplication("exe", "Spotify.exe")
|
||||
|
||||
; Identify Electron applications with overflowing borders
|
||||
IdentifyBorderOverflow("exe", "Discord.exe")
|
||||
IdentifyBorderOverflow("exe", "Spotify.exe")
|
||||
IdentifyBorderOverflow("class", "ZPFTEWndClass")
|
||||
|
||||
; Change the focused window, Alt + Vim direction keys
|
||||
!h::
|
||||
@@ -170,9 +179,9 @@ return
|
||||
TogglePause()
|
||||
return
|
||||
|
||||
; Toggle focus follows mouse
|
||||
; Enable focus follows mouse
|
||||
!0::
|
||||
ToggleFocusFollowsMouse()
|
||||
ToggleFocusFollowsMouse("komorebi")
|
||||
return
|
||||
|
||||
; Switch to workspace
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.3"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -10,6 +15,7 @@ bindings = { package = "bindings", path = "../bindings" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
bitflags = "1"
|
||||
clap = "3.0.0-beta.4"
|
||||
color-eyre = "0.5"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
@@ -30,6 +36,7 @@ tracing-appender = "0.1"
|
||||
tracing-subscriber = "0.2"
|
||||
uds_windows = "1"
|
||||
which = "4"
|
||||
winput = "0.2"
|
||||
winvd = "0.0.20"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -44,6 +44,18 @@ impl Container {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {
|
||||
for window in self.windows() {
|
||||
if let Ok(window_exe) = window.exe() {
|
||||
if exe == window_exe {
|
||||
return Option::from(window.hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for window in self.windows() {
|
||||
if window.hwnd == hwnd {
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::thread;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Clap;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
@@ -25,6 +28,7 @@ use which::which;
|
||||
|
||||
use crate::process_command::listen_for_commands;
|
||||
use crate::process_event::listen_for_events;
|
||||
use crate::process_movement::listen_for_movements;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
@@ -36,6 +40,7 @@ mod container;
|
||||
mod monitor;
|
||||
mod process_command;
|
||||
mod process_event;
|
||||
mod process_movement;
|
||||
mod set_window_position;
|
||||
mod styles;
|
||||
mod window;
|
||||
@@ -51,16 +56,15 @@ lazy_static! {
|
||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
|
||||
static ref TRAY_AND_MULTI_WINDOW_CLASSES: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec![]));
|
||||
static ref TRAY_AND_MULTI_WINDOW_EXES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"explorer.exe".to_string(),
|
||||
"firefox.exe".to_string(),
|
||||
"chrome.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
"ApplicationFrameHost.exe".to_string(),
|
||||
"steam.exe".to_string(),
|
||||
]));
|
||||
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec![
|
||||
"explorer.exe".to_string(),
|
||||
"firefox.exe".to_string(),
|
||||
"chrome.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
"ApplicationFrameHost.exe".to_string(),
|
||||
"steam.exe".to_string(),
|
||||
]));
|
||||
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||
"firefox.exe".to_string(),
|
||||
"idea64.exe".to_string(),
|
||||
@@ -69,8 +73,11 @@ lazy_static! {
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
}
|
||||
|
||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
@@ -193,61 +200,76 @@ fn detect_deadlocks() {
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[clap(author, about, version)]
|
||||
struct Opts {
|
||||
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
||||
#[clap(long = "ffm")]
|
||||
focus_follows_mouse: bool,
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn main() -> Result<()> {
|
||||
match std::env::args().count() {
|
||||
1 => {
|
||||
let mut system = sysinfo::System::new_all();
|
||||
system.refresh_processes();
|
||||
let opts: Opts = Opts::parse();
|
||||
CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
|
||||
|
||||
if system.process_by_name("komorebi.exe").len() > 1 {
|
||||
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let arg_count = std::env::args().count();
|
||||
let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst));
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
if has_valid_args {
|
||||
let mut system = sysinfo::System::new_all();
|
||||
system.refresh_processes();
|
||||
|
||||
#[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();
|
||||
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
incoming,
|
||||
)))?));
|
||||
|
||||
wm.lock().init()?;
|
||||
listen_for_commands(wm.clone());
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
load_configuration()?;
|
||||
|
||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||
ctrlc::set_handler(move || {
|
||||
ctrlc_sender
|
||||
.send(())
|
||||
.expect("could not send signal on ctrl-c channel");
|
||||
})?;
|
||||
|
||||
ctrlc_receiver
|
||||
.recv()
|
||||
.expect("could not receive signal on ctrl-c channel");
|
||||
|
||||
tracing::error!(
|
||||
"received ctrl-c, restoring all hidden windows and terminating process"
|
||||
);
|
||||
|
||||
wm.lock().restore_all_windows();
|
||||
std::process::exit(130);
|
||||
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);
|
||||
}
|
||||
_ => Ok(()),
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
|
||||
#[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();
|
||||
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
incoming,
|
||||
)))?));
|
||||
|
||||
wm.lock().init()?;
|
||||
listen_for_commands(wm.clone());
|
||||
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
|
||||
.send(())
|
||||
.expect("could not send signal on ctrl-c channel");
|
||||
})?;
|
||||
|
||||
ctrlc_receiver
|
||||
.recv()
|
||||
.expect("could not receive signal on ctrl-c channel");
|
||||
|
||||
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
|
||||
|
||||
wm.lock().restore_all_windows();
|
||||
std::process::exit(130);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Rect;
|
||||
@@ -14,9 +15,9 @@ use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
|
||||
pub struct Monitor {
|
||||
#[getset(get_copy = "pub")]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
id: isize,
|
||||
monitor_size: Rect,
|
||||
#[getset(get = "pub")]
|
||||
@@ -30,11 +31,14 @@ pub struct Monitor {
|
||||
impl_ring_elements!(Monitor, Workspace);
|
||||
|
||||
pub fn new(id: isize, monitor_size: Rect, work_area_size: Rect) -> Monitor {
|
||||
let mut workspaces = Ring::default();
|
||||
workspaces.elements_mut().push_back(Workspace::default());
|
||||
|
||||
Monitor {
|
||||
id,
|
||||
monitor_size,
|
||||
work_area_size,
|
||||
workspaces: Ring::default(),
|
||||
workspaces,
|
||||
workspace_names: HashMap::default(),
|
||||
}
|
||||
}
|
||||
@@ -141,12 +145,12 @@ impl Monitor {
|
||||
self.workspaces().len()
|
||||
}
|
||||
|
||||
pub fn update_focused_workspace(&mut self) -> Result<()> {
|
||||
pub fn update_focused_workspace(&mut self, invisible_borders: &Rect) -> Result<()> {
|
||||
let work_area = *self.work_area_size();
|
||||
|
||||
self.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||
.update(&work_area)?;
|
||||
.update(&work_area, invisible_borders)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
@@ -10,16 +11,18 @@ use color_eyre::Result;
|
||||
use parking_lot::Mutex;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::SocketMessage;
|
||||
use komorebi_core::StateQuery;
|
||||
|
||||
use crate::window_manager;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::CUSTOM_FFM;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WORKSPACE_RULES;
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -113,7 +116,12 @@ impl WindowManager {
|
||||
self.move_container_to_monitor(monitor_idx, false)?;
|
||||
}
|
||||
SocketMessage::TogglePause => {
|
||||
tracing::info!("pausing");
|
||||
if self.is_paused {
|
||||
tracing::info!("resuming");
|
||||
} else {
|
||||
tracing::info!("pausing");
|
||||
}
|
||||
|
||||
self.is_paused = !self.is_paused;
|
||||
}
|
||||
SocketMessage::ToggleTiling => {
|
||||
@@ -123,21 +131,7 @@ impl WindowManager {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
self.update_focused_workspace(true)?;
|
||||
}
|
||||
SocketMessage::Retile => {
|
||||
for monitor in self.monitors_mut() {
|
||||
let work_area = *monitor.work_area_size();
|
||||
let workspace = monitor
|
||||
.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
// Reset any resize adjustments if we want to force a retile
|
||||
for resize in workspace.resize_dimensions_mut() {
|
||||
*resize = None;
|
||||
}
|
||||
|
||||
workspace.update(&work_area)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::Retile => self.retile_all()?,
|
||||
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
|
||||
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout(layout)?,
|
||||
SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => {
|
||||
@@ -147,6 +141,15 @@ impl WindowManager {
|
||||
self.set_workspace_layout(monitor_idx, workspace_idx, layout)?;
|
||||
}
|
||||
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
|
||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||
// secondary monitor where the cursor is focused will be used as the target for
|
||||
// the workspace switch op
|
||||
let monitor_idx = self.monitor_idx_from_current_pos().ok_or_else(|| {
|
||||
anyhow!("there is no monitor associated with the current cursor position")
|
||||
})?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
SocketMessage::Stop => {
|
||||
@@ -175,21 +178,124 @@ impl WindowManager {
|
||||
let mut stream = UnixStream::connect(&socket)?;
|
||||
stream.write_all(state.as_bytes())?;
|
||||
}
|
||||
SocketMessage::Query(query) => {
|
||||
let response = match query {
|
||||
StateQuery::FocusedMonitorIndex => self.focused_monitor_idx(),
|
||||
StateQuery::FocusedWorkspaceIndex => self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx(),
|
||||
StateQuery::FocusedContainerIndex => {
|
||||
self.focused_workspace()?.focused_container_idx()
|
||||
}
|
||||
StateQuery::FocusedWindowIndex => {
|
||||
self.focused_container()?.focused_window_idx()
|
||||
}
|
||||
}
|
||||
.to_string();
|
||||
|
||||
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::ResizeWindow(direction, sizing) => {
|
||||
self.resize_window(direction, sizing, Option::from(50))?;
|
||||
}
|
||||
SocketMessage::FocusFollowsMouse(enable) => {
|
||||
if enable {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
} else {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
|
||||
if !CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||
tracing::warn!(
|
||||
"komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be enabled; defaulting to windows implementation"
|
||||
);
|
||||
implementation = FocusFollowsMouseImplementation::Windows;
|
||||
}
|
||||
|
||||
match implementation {
|
||||
FocusFollowsMouseImplementation::Komorebi => {
|
||||
if WindowsApi::focus_follows_mouse()? {
|
||||
tracing::warn!(
|
||||
"the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled"
|
||||
);
|
||||
} else if enable {
|
||||
self.focus_follows_mouse = Option::from(implementation);
|
||||
} else {
|
||||
self.focus_follows_mouse = None;
|
||||
self.has_pending_raise_op = false;
|
||||
}
|
||||
}
|
||||
FocusFollowsMouseImplementation::Windows => {
|
||||
if let Some(FocusFollowsMouseImplementation::Komorebi) =
|
||||
self.focus_follows_mouse
|
||||
{
|
||||
tracing::warn!(
|
||||
"the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled"
|
||||
);
|
||||
} else if enable {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
self.focus_follows_mouse =
|
||||
Option::from(FocusFollowsMouseImplementation::Windows);
|
||||
} else {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
self.focus_follows_mouse = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::ToggleFocusFollowsMouse => {
|
||||
if WindowsApi::focus_follows_mouse()? {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
} else {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
SocketMessage::ToggleFocusFollowsMouse(mut implementation) => {
|
||||
if !CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||
tracing::warn!(
|
||||
"komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be toggled; defaulting to windows implementation"
|
||||
);
|
||||
implementation = FocusFollowsMouseImplementation::Windows;
|
||||
}
|
||||
|
||||
match implementation {
|
||||
FocusFollowsMouseImplementation::Komorebi => {
|
||||
if WindowsApi::focus_follows_mouse()? {
|
||||
tracing::warn!(
|
||||
"the komorebi implementation of focus follows mouse cannot be toggled while the windows implementation is enabled"
|
||||
);
|
||||
} else {
|
||||
match self.focus_follows_mouse {
|
||||
None => {
|
||||
self.focus_follows_mouse = Option::from(implementation);
|
||||
self.has_pending_raise_op = false;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Komorebi) => {
|
||||
self.focus_follows_mouse = None;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Windows) => {
|
||||
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FocusFollowsMouseImplementation::Windows => {
|
||||
if let Some(FocusFollowsMouseImplementation::Komorebi) =
|
||||
self.focus_follows_mouse
|
||||
{
|
||||
tracing::warn!(
|
||||
"the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled"
|
||||
);
|
||||
} else {
|
||||
match self.focus_follows_mouse {
|
||||
None => {
|
||||
WindowsApi::enable_focus_follows_mouse()?;
|
||||
self.focus_follows_mouse = Option::from(implementation);
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Windows) => {
|
||||
WindowsApi::disable_focus_follows_mouse()?;
|
||||
self.focus_follows_mouse = None;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Komorebi) => {
|
||||
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::ReloadConfiguration => {
|
||||
@@ -198,28 +304,29 @@ impl WindowManager {
|
||||
SocketMessage::WatchConfiguration(enable) => {
|
||||
self.watch_configuration(enable)?;
|
||||
}
|
||||
SocketMessage::IdentifyTrayApplication(identifier, id) => match identifier {
|
||||
ApplicationIdentifier::Exe => {
|
||||
let mut exes = TRAY_AND_MULTI_WINDOW_EXES.lock();
|
||||
if !exes.contains(&id) {
|
||||
exes.push(id);
|
||||
}
|
||||
SocketMessage::IdentifyBorderOverflow(_, id) => {
|
||||
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||
if !identifiers.contains(&id) {
|
||||
identifiers.push(id);
|
||||
}
|
||||
ApplicationIdentifier::Class => {
|
||||
let mut classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock();
|
||||
if !classes.contains(&id) {
|
||||
classes.push(id);
|
||||
}
|
||||
}
|
||||
SocketMessage::IdentifyTrayApplication(_, id) => {
|
||||
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
||||
if !identifiers.contains(&id) {
|
||||
identifiers.push(id);
|
||||
}
|
||||
ApplicationIdentifier::Title => {}
|
||||
},
|
||||
}
|
||||
SocketMessage::ManageFocusedWindow => {
|
||||
self.manage_focused_window()?;
|
||||
}
|
||||
SocketMessage::UnmanageFocusedWindow => {
|
||||
self.unmanage_focused_window()?;
|
||||
}
|
||||
}
|
||||
SocketMessage::InvisibleBorders(rect) => {
|
||||
self.invisible_borders = rect;
|
||||
self.retile_all()?;
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("processed");
|
||||
Ok(())
|
||||
@@ -232,14 +339,15 @@ impl WindowManager {
|
||||
let message = SocketMessage::from_str(&line?)?;
|
||||
|
||||
if self.is_paused {
|
||||
if let SocketMessage::TogglePause = message {
|
||||
tracing::info!("resuming");
|
||||
self.is_paused = !self.is_paused;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::trace!("ignoring while paused");
|
||||
return Ok(());
|
||||
return match message {
|
||||
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
|
||||
Ok(self.process_command(message)?)
|
||||
}
|
||||
_ => {
|
||||
tracing::trace!("ignoring while paused");
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
self.process_command(message)?;
|
||||
|
||||
@@ -15,8 +15,7 @@ use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||
@@ -55,8 +54,9 @@ impl WindowManager {
|
||||
WindowManagerEvent::FocusChange(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||
let monitor_idx = self
|
||||
.monitor_idx_from_window(*window)
|
||||
self.reconcile_monitors()?;
|
||||
|
||||
let monitor_idx = self.monitor_idx_from_window(*window)
|
||||
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
@@ -64,12 +64,14 @@ impl WindowManager {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
|
||||
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||
let work_area = *monitor.work_area_size();
|
||||
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||
let reaped_orphans = workspace.reap_orphans()?;
|
||||
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
||||
workspace.update(&work_area)?;
|
||||
workspace.update(&work_area, &invisible_borders)?;
|
||||
tracing::info!(
|
||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||
reaped_orphans.0,
|
||||
@@ -91,6 +93,10 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
match event {
|
||||
WindowManagerEvent::Raise(window) => {
|
||||
window.raise()?;
|
||||
self.has_pending_raise_op = false;
|
||||
}
|
||||
WindowManagerEvent::Minimize(_, window)
|
||||
| WindowManagerEvent::Destroy(_, window)
|
||||
| WindowManagerEvent::Unmanage(window) => {
|
||||
@@ -105,15 +111,16 @@ impl WindowManager {
|
||||
// and will have is_window() return true, as the process is still running even if
|
||||
// the window is not visible.
|
||||
{
|
||||
let tray_and_multi_window_exes = TRAY_AND_MULTI_WINDOW_EXES.lock();
|
||||
let tray_and_multi_window_classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock();
|
||||
let tray_and_multi_window_identifiers =
|
||||
TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
||||
|
||||
// We don't want to purge windows that have been deliberately hidden by us, eg. when
|
||||
// they are not on the top of a container stack.
|
||||
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
|
||||
if (!window.is_window() || tray_and_multi_window_exes.contains(&window.exe()?))
|
||||
|| tray_and_multi_window_classes.contains(&window.class()?)
|
||||
if (!window.is_window()
|
||||
|| tray_and_multi_window_identifiers.contains(&window.exe()?))
|
||||
|| tray_and_multi_window_identifiers.contains(&window.class()?)
|
||||
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
|
||||
{
|
||||
hide = true;
|
||||
@@ -213,19 +220,11 @@ impl WindowManager {
|
||||
.ok_or_else(|| anyhow!("there is no latest layout"))?;
|
||||
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||
|
||||
// See Window.set_position() in window.rs for comments
|
||||
let border = Rect {
|
||||
left: 12,
|
||||
top: 0,
|
||||
right: 24,
|
||||
bottom: 12,
|
||||
};
|
||||
|
||||
// Adjust for the invisible border
|
||||
new_position.left += border.left;
|
||||
new_position.top += border.top;
|
||||
new_position.right -= border.right;
|
||||
new_position.bottom -= border.bottom;
|
||||
// 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,
|
||||
@@ -290,7 +289,7 @@ impl WindowManager {
|
||||
|
||||
// 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()?)?;
|
||||
window.center(&self.focused_monitor_work_area()?, &invisible_borders)?;
|
||||
}
|
||||
|
||||
tracing::trace!("updating list of known hwnds");
|
||||
@@ -315,6 +314,7 @@ impl WindowManager {
|
||||
.open(hwnd_json)?;
|
||||
|
||||
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
||||
|
||||
tracing::info!("processed: {}", event.window().to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
41
komorebi/src/process_movement.rs
Normal file
41
komorebi/src/process_movement.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use winput::message_loop;
|
||||
use winput::message_loop::Event;
|
||||
use winput::Action;
|
||||
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
|
||||
use crate::window_manager::WindowManager;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || {
|
||||
let mut ignore_movement = false;
|
||||
|
||||
let receiver = message_loop::start().expect("could not start winput message loop");
|
||||
|
||||
loop {
|
||||
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
|
||||
Event::MouseButton { action, .. } => match action {
|
||||
Action::Press => ignore_movement = true,
|
||||
Action::Release => ignore_movement = false,
|
||||
},
|
||||
Event::MouseMoveRelative { .. } => {
|
||||
if !ignore_movement {
|
||||
match wm.lock().raise_window_at_cursor_pos() {
|
||||
Ok(_) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -15,6 +15,7 @@ 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::LAYERED_EXE_WHITELIST;
|
||||
@@ -70,7 +71,7 @@ impl Window {
|
||||
HWND(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
|
||||
pub fn center(&mut self, work_area: &Rect, invisible_borders: &Rect) -> Result<()> {
|
||||
let half_width = work_area.right / 2;
|
||||
let half_weight = work_area.bottom / 2;
|
||||
|
||||
@@ -81,44 +82,35 @@ impl Window {
|
||||
right: half_width,
|
||||
bottom: half_weight,
|
||||
},
|
||||
invisible_borders,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
|
||||
// NOTE: This is how the border variable below was calculated; every time this code was
|
||||
// run on any window in any position, the generated border was always the same, so I am
|
||||
// hard coding the border Rect to avoid two calls to set_window_pos and making the screen
|
||||
// flicker on container/window movement. Still not 100% sure if this is DPI-aware.
|
||||
|
||||
// Set the new position first to be able to get the extended frame bounds
|
||||
// WindowsApi::set_window_pos(self.hwnd(), layout, false, false)?;
|
||||
// let mut rect = WindowsApi::window_rect(self.hwnd())?;
|
||||
|
||||
// Get the extended frame bounds of the new position
|
||||
// let frame = WindowsApi::window_rect_with_extended_frame_bounds(self.hwnd())?;
|
||||
|
||||
// Calculate the invisible border diff
|
||||
// let border = Rect {
|
||||
// left: frame.left - rect.left,
|
||||
// top: frame.top - rect.top,
|
||||
// right: rect.right - frame.right,
|
||||
// bottom: rect.bottom - frame.bottom,
|
||||
// };
|
||||
|
||||
pub fn set_position(
|
||||
&mut self,
|
||||
layout: &Rect,
|
||||
invisible_borders: &Rect,
|
||||
top: bool,
|
||||
) -> Result<()> {
|
||||
let mut rect = *layout;
|
||||
let border = Rect {
|
||||
left: 12,
|
||||
top: 0,
|
||||
right: 24,
|
||||
bottom: 12,
|
||||
};
|
||||
let mut should_remove_border = true;
|
||||
|
||||
// Remove the invisible border
|
||||
rect.left -= border.left;
|
||||
rect.top -= border.top;
|
||||
rect.right += border.right;
|
||||
rect.bottom += border.bottom;
|
||||
let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||
if border_overflows.contains(&self.title()?)
|
||||
|| border_overflows.contains(&self.exe()?)
|
||||
|| border_overflows.contains(&self.class()?)
|
||||
{
|
||||
should_remove_border = false;
|
||||
}
|
||||
|
||||
if should_remove_border {
|
||||
// Remove the invisible borders
|
||||
rect.left -= invisible_borders.left;
|
||||
rect.top -= invisible_borders.top;
|
||||
rect.right += invisible_borders.right;
|
||||
rect.bottom += invisible_borders.bottom;
|
||||
}
|
||||
|
||||
WindowsApi::position_window(self.hwnd(), &rect, top)
|
||||
}
|
||||
@@ -156,6 +148,27 @@ impl Window {
|
||||
WindowsApi::maximize_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn raise(self) -> Result<()> {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
let current_thread_id = WindowsApi::current_thread_id();
|
||||
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 focus(): {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
WindowsApi::set_focus(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn focus(self) -> Result<()> {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
|
||||
@@ -17,6 +17,7 @@ use uds_windows::UnixListener;
|
||||
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::Flip;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
@@ -31,11 +32,11 @@ use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||
use crate::FLOAT_IDENTIFIERS;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WORKSPACE_RULES;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -44,19 +45,25 @@ pub struct WindowManager {
|
||||
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||
pub command_listener: UnixListener,
|
||||
pub is_paused: bool,
|
||||
pub invisible_borders: Rect,
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
||||
pub hotwatch: Hotwatch,
|
||||
pub virtual_desktop_id: Option<usize>,
|
||||
pub has_pending_raise_op: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct State {
|
||||
pub monitors: Ring<Monitor>,
|
||||
pub is_paused: bool,
|
||||
pub invisible_borders: Rect,
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
||||
pub has_pending_raise_op: bool,
|
||||
pub float_identifiers: Vec<String>,
|
||||
pub manage_identifiers: Vec<String>,
|
||||
pub layered_exe_whitelist: Vec<String>,
|
||||
pub tray_and_multi_window_exes: Vec<String>,
|
||||
pub tray_and_multi_window_classes: Vec<String>,
|
||||
pub tray_and_multi_window_identifiers: Vec<String>,
|
||||
pub border_overflow_identifiers: Vec<String>,
|
||||
}
|
||||
|
||||
#[allow(clippy::fallible_impl_from)]
|
||||
@@ -65,11 +72,14 @@ impl From<&mut WindowManager> for State {
|
||||
Self {
|
||||
monitors: wm.monitors.clone(),
|
||||
is_paused: wm.is_paused,
|
||||
invisible_borders: wm.invisible_borders,
|
||||
focus_follows_mouse: wm.focus_follows_mouse.clone(),
|
||||
has_pending_raise_op: wm.has_pending_raise_op,
|
||||
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
|
||||
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
|
||||
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(),
|
||||
tray_and_multi_window_exes: TRAY_AND_MULTI_WINDOW_EXES.lock().clone(),
|
||||
tray_and_multi_window_classes: TRAY_AND_MULTI_WINDOW_CLASSES.lock().clone(),
|
||||
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
|
||||
border_overflow_identifiers: BORDER_OVERFLOW_IDENTIFIERS.lock().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,8 +138,16 @@ impl WindowManager {
|
||||
incoming_events: incoming,
|
||||
command_listener: listener,
|
||||
is_paused: false,
|
||||
invisible_borders: Rect {
|
||||
left: 7,
|
||||
top: 0,
|
||||
right: 14,
|
||||
bottom: 7,
|
||||
},
|
||||
focus_follows_mouse: None,
|
||||
hotwatch: Hotwatch::new()?,
|
||||
virtual_desktop_id,
|
||||
has_pending_raise_op: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -215,6 +233,44 @@ impl WindowManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn reconcile_monitors(&mut self) -> Result<()> {
|
||||
let valid_hmonitors = WindowsApi::valid_hmonitors()?;
|
||||
let mut invalid = vec![];
|
||||
|
||||
for monitor in self.monitors_mut() {
|
||||
if !valid_hmonitors.contains(&monitor.id()) {
|
||||
let mut mark_as_invalid = true;
|
||||
|
||||
// If an invalid hmonitor has at least one window in the window manager state,
|
||||
// we can attempt to update its hmonitor id in-place so that it doesn't get reaped
|
||||
if let Some(workspace) = monitor.focused_workspace() {
|
||||
if let Some(container) = workspace.focused_container() {
|
||||
if let Some(window) = container.focused_window() {
|
||||
let actual_hmonitor = WindowsApi::monitor_from_window(window.hwnd());
|
||||
if actual_hmonitor != monitor.id() {
|
||||
monitor.set_id(actual_hmonitor);
|
||||
mark_as_invalid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mark_as_invalid {
|
||||
invalid.push(monitor.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any invalid monitors from our state
|
||||
self.monitors_mut().retain(|m| !invalid.contains(&m.id()));
|
||||
|
||||
// Check for and add any new monitors that may have been plugged in
|
||||
WindowsApi::load_monitor_information(&mut self.monitors)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn enforce_workspace_rules(&mut self) -> Result<()> {
|
||||
let mut to_move = vec![];
|
||||
@@ -332,6 +388,26 @@ impl WindowManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn retile_all(&mut self) -> Result<()> {
|
||||
let invisible_borders = self.invisible_borders;
|
||||
for monitor in self.monitors_mut() {
|
||||
let work_area = *monitor.work_area_size();
|
||||
let workspace = monitor
|
||||
.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
||||
|
||||
// Reset any resize adjustments if we want to force a retile
|
||||
for resize in workspace.resize_dimensions_mut() {
|
||||
*resize = None;
|
||||
}
|
||||
|
||||
workspace.update(&work_area, &invisible_borders)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn validate_virtual_desktop_id(&self) {
|
||||
let virtual_desktop_id = winvd::helpers::get_current_desktop_number().ok();
|
||||
@@ -360,13 +436,76 @@ impl WindowManager {
|
||||
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn raise_window_at_cursor_pos(&mut self) -> Result<()> {
|
||||
let mut hwnd = WindowsApi::window_at_cursor_pos()?;
|
||||
|
||||
if self.has_pending_raise_op
|
||||
|| self.focused_window()?.hwnd == hwnd
|
||||
// Sometimes we need this check, because the focus may have been given by a click
|
||||
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
|
||||
// the focused window of the workspace is not actually focused by the OS at that point
|
||||
|| WindowsApi::foreground_window()? == hwnd
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
let mut known_hwnd = false;
|
||||
for monitor in self.monitors() {
|
||||
for workspace in monitor.workspaces() {
|
||||
if workspace.contains_window(hwnd) {
|
||||
known_hwnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not sure if this needs to be made configurable just yet...
|
||||
let overlay_classes = [
|
||||
// Chromium/Electron
|
||||
"Chrome_RenderWidgetHostHWND".to_string(),
|
||||
// Explorer
|
||||
"DirectUIHWND".to_string(),
|
||||
"SysTreeView32".to_string(),
|
||||
"ToolbarWindow32".to_string(),
|
||||
"NetUIHWND".to_string(),
|
||||
];
|
||||
|
||||
if !known_hwnd {
|
||||
let class = Window { hwnd }.class()?;
|
||||
// Some applications (Electron/Chromium-based, explorer) have (invisible?) overlays
|
||||
// windows that we need to look beyond to find the actual window to raise
|
||||
if overlay_classes.contains(&class) {
|
||||
for monitor in self.monitors() {
|
||||
for workspace in monitor.workspaces() {
|
||||
if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?)
|
||||
{
|
||||
hwnd = exe_hwnd;
|
||||
known_hwnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if known_hwnd {
|
||||
let event = WindowManagerEvent::Raise(Window { hwnd });
|
||||
self.has_pending_raise_op = true;
|
||||
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||
} else {
|
||||
tracing::debug!("not raising unknown window: {}", Window { hwnd });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||
tracing::info!("updating");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
|
||||
self.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.update_focused_workspace()?;
|
||||
.update_focused_workspace(&invisible_borders)?;
|
||||
|
||||
if mouse_follows_focus {
|
||||
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
||||
@@ -488,6 +627,8 @@ impl WindowManager {
|
||||
pub fn move_container_to_monitor(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
tracing::info!("moving container");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
@@ -512,6 +653,7 @@ impl WindowManager {
|
||||
|
||||
target_monitor.add_container(container)?;
|
||||
target_monitor.load_focused_workspace()?;
|
||||
target_monitor.update_focused_workspace(&invisible_borders)?;
|
||||
|
||||
if follow {
|
||||
self.focus_monitor(idx)?;
|
||||
@@ -673,6 +815,7 @@ impl WindowManager {
|
||||
tracing::info!("floating window");
|
||||
|
||||
let work_area = self.focused_monitor_work_area()?;
|
||||
let invisible_borders = self.invisible_borders;
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.new_floating_window()?;
|
||||
@@ -682,7 +825,7 @@ impl WindowManager {
|
||||
.last_mut()
|
||||
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
||||
|
||||
window.center(&work_area)?;
|
||||
window.center(&work_area, &invisible_borders)?;
|
||||
window.focus()?;
|
||||
|
||||
Ok(())
|
||||
@@ -865,6 +1008,7 @@ impl WindowManager {
|
||||
) -> Result<()> {
|
||||
tracing::info!("setting workspace layout");
|
||||
|
||||
let invisible_borders = self.invisible_borders;
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let monitor = self
|
||||
@@ -884,7 +1028,7 @@ impl WindowManager {
|
||||
|
||||
// If this is the focused workspace on a non-focused screen, let's update it
|
||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||
workspace.update(&work_area)?;
|
||||
workspace.update(&work_area, &invisible_borders)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(self.update_focused_workspace(false)?)
|
||||
@@ -1014,6 +1158,18 @@ impl WindowManager {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
|
||||
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
|
||||
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
if monitor.id() == hmonitor {
|
||||
return Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn focused_workspace(&self) -> Result<&Workspace> {
|
||||
self.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
@@ -1068,6 +1224,12 @@ impl WindowManager {
|
||||
.ok_or_else(|| anyhow!("there is no container"))
|
||||
}
|
||||
|
||||
pub fn focused_window(&self) -> Result<&Window> {
|
||||
self.focused_container()?
|
||||
.focused_window()
|
||||
.ok_or_else(|| anyhow!("there is no window"))
|
||||
}
|
||||
|
||||
fn focused_window_mut(&mut self) -> Result<&mut Window> {
|
||||
self.focused_container_mut()?
|
||||
.focused_window_mut()
|
||||
|
||||
@@ -16,6 +16,7 @@ pub enum WindowManagerEvent {
|
||||
MouseCapture(WinEvent, Window),
|
||||
Manage(Window),
|
||||
Unmanage(Window),
|
||||
Raise(Window),
|
||||
}
|
||||
|
||||
impl Display for WindowManagerEvent {
|
||||
@@ -60,6 +61,9 @@ impl Display for WindowManagerEvent {
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::Raise(window) => {
|
||||
write!(f, "Raise (Window: {})", window)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +78,7 @@ impl WindowManagerEvent {
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window)
|
||||
| WindowManagerEvent::MouseCapture(_, window)
|
||||
| WindowManagerEvent::Raise(window)
|
||||
| WindowManagerEvent::Manage(window)
|
||||
| WindowManagerEvent::Unmanage(window) => window,
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
|
||||
use bindings::Windows::Win32::Devices::HumanInterfaceDevice::HID_USAGE_GENERIC_MOUSE;
|
||||
use bindings::Windows::Win32::Devices::HumanInterfaceDevice::HID_USAGE_PAGE_GENERIC;
|
||||
use bindings::Windows::Win32::Foundation::BOOL;
|
||||
use bindings::Windows::Win32::Foundation::HANDLE;
|
||||
use bindings::Windows::Win32::Foundation::HINSTANCE;
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::Foundation::LPARAM;
|
||||
use bindings::Windows::Win32::Foundation::POINT;
|
||||
@@ -23,12 +26,15 @@ 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::HBRUSH;
|
||||
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::LibraryLoader::GetModuleHandleW;
|
||||
use bindings::Windows::Win32::System::Threading::AttachThreadInput;
|
||||
use bindings::Windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use bindings::Windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
@@ -37,9 +43,22 @@ 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::GetRawInputBuffer;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::GetRawInputData;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RegisterRawInputDevices;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::SetFocus;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::HRAWINPUT;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RAWINPUT;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RAWINPUTDEVICE;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RAWINPUTHEADER;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RIDEV_INPUTSINK;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RIDEV_NOLEGACY;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::RID_INPUT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::DestroyWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::FindWindowExW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||
@@ -53,15 +72,23 @@ 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::RegisterClassExW;
|
||||
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::UnregisterClassW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
||||
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::HCURSOR;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::HICON;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::HMENU;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_MESSAGE;
|
||||
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;
|
||||
@@ -74,8 +101,13 @@ 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_EX_STYLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_STYLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDCLASSEXW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDCLASS_STYLES;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDPROC;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
@@ -84,7 +116,21 @@ use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::set_window_position::SetWindowPosition;
|
||||
use crate::windows_callbacks;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
pub trait IntoPWSTR {
|
||||
fn into_pwstr(self) -> PWSTR;
|
||||
}
|
||||
|
||||
impl IntoPWSTR for &str {
|
||||
fn into_pwstr(self) -> PWSTR {
|
||||
PWSTR(
|
||||
self.encode_utf16()
|
||||
.chain([0_u16])
|
||||
.collect::<Vec<u16>>()
|
||||
.as_mut_ptr(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WindowsResult<T, E> {
|
||||
Err(E),
|
||||
@@ -101,6 +147,16 @@ impl From<BOOL> for WindowsResult<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HINSTANCE> for WindowsResult<HINSTANCE, Error> {
|
||||
fn from(return_value: HINSTANCE) -> Self {
|
||||
if return_value.is_null() {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Self::Ok(return_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HWND> for WindowsResult<isize, Error> {
|
||||
fn from(return_value: HWND) -> Self {
|
||||
if return_value.is_null() {
|
||||
@@ -136,7 +192,7 @@ macro_rules! impl_from_integer_for_windows_result {
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_integer_for_windows_result!(isize, u32, i32);
|
||||
impl_from_integer_for_windows_result!(isize, u16, u32, i32);
|
||||
|
||||
impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
|
||||
fn from(result: WindowsResult<T, E>) -> Self {
|
||||
@@ -164,6 +220,57 @@ impl WindowsApi {
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn valid_hwnds() -> Result<Vec<isize>> {
|
||||
let mut hwnds: Vec<isize> = vec![];
|
||||
let hwnds_ref: &mut Vec<isize> = hwnds.as_mut();
|
||||
Self::enum_windows(
|
||||
windows_callbacks::valid_hwnds,
|
||||
hwnds_ref as *mut Vec<isize> as isize,
|
||||
)?;
|
||||
|
||||
Ok(hwnds)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn hwnd_by_class(class: &str) -> Option<isize> {
|
||||
let hwnds = Self::valid_hwnds().ok()?;
|
||||
for hwnd in hwnds {
|
||||
if let Ok(hwnd_class) = Self::real_window_class_w(HWND(hwnd)) {
|
||||
if hwnd_class == class {
|
||||
return Option::from(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn hwnd_by_title(class: &str) -> Option<isize> {
|
||||
let hwnds = Self::valid_hwnds().ok()?;
|
||||
for hwnd in hwnds {
|
||||
if let Ok(hwnd_title) = Self::window_text_w(HWND(hwnd)) {
|
||||
if hwnd_title == class {
|
||||
return Option::from(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn valid_hmonitors() -> Result<Vec<isize>> {
|
||||
let mut monitors: Vec<isize> = vec![];
|
||||
let monitors_ref: &mut Vec<isize> = monitors.as_mut();
|
||||
Self::enum_display_monitors(
|
||||
windows_callbacks::valid_display_monitors,
|
||||
monitors_ref as *mut Vec<isize> as isize,
|
||||
)?;
|
||||
|
||||
Ok(monitors)
|
||||
}
|
||||
|
||||
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||
Self::enum_display_monitors(
|
||||
windows_callbacks::enum_display_monitor,
|
||||
@@ -179,9 +286,8 @@ impl WindowsApi {
|
||||
|
||||
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||
for monitor in monitors.elements_mut() {
|
||||
if monitor.workspaces().is_empty() {
|
||||
let mut workspace = Workspace::default();
|
||||
|
||||
let monitor_id = monitor.id();
|
||||
if let Some(workspace) = monitor.workspaces_mut().front_mut() {
|
||||
// EnumWindows will enumerate through windows on all monitors
|
||||
Self::enum_windows(
|
||||
windows_callbacks::enum_window,
|
||||
@@ -198,7 +304,7 @@ impl WindowsApi {
|
||||
|
||||
for container in workspace.containers_mut() {
|
||||
for window in container.windows() {
|
||||
if Self::monitor_from_window(window.hwnd()) != monitor.id() {
|
||||
if Self::monitor_from_window(window.hwnd()) != monitor_id {
|
||||
windows_on_other_monitors.push(window.hwnd().0);
|
||||
}
|
||||
}
|
||||
@@ -207,8 +313,6 @@ impl WindowsApi {
|
||||
for hwnd in windows_on_other_monitors {
|
||||
workspace.remove_window(hwnd)?;
|
||||
}
|
||||
|
||||
monitor.workspaces_mut().push_back(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +331,12 @@ impl WindowsApi {
|
||||
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
|
||||
}
|
||||
|
||||
pub fn monitor_from_point(point: POINT) -> isize {
|
||||
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }.0
|
||||
}
|
||||
|
||||
pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
|
||||
let flags = SetWindowPosition::NO_ACTIVATE;
|
||||
|
||||
@@ -342,6 +452,14 @@ impl WindowsApi {
|
||||
Ok(cursor_pos)
|
||||
}
|
||||
|
||||
pub fn window_from_point(point: POINT) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { WindowFromPoint(point) }))
|
||||
}
|
||||
|
||||
pub fn window_at_cursor_pos() -> Result<isize> {
|
||||
Self::window_from_point(Self::cursor_pos()?)
|
||||
}
|
||||
|
||||
pub fn center_cursor_in_rect(rect: &Rect) -> Result<()> {
|
||||
Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))
|
||||
}
|
||||
@@ -547,6 +665,7 @@ impl WindowsApi {
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn system_parameters_info_w(
|
||||
action: SYSTEM_PARAMETERS_INFO_ACTION,
|
||||
ui_param: u32,
|
||||
@@ -558,6 +677,7 @@ impl WindowsApi {
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn focus_follows_mouse() -> Result<bool> {
|
||||
let mut is_enabled: BOOL = unsafe { std::mem::zeroed() };
|
||||
|
||||
@@ -571,6 +691,7 @@ impl WindowsApi {
|
||||
Ok(is_enabled.into())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn enable_focus_follows_mouse() -> Result<()> {
|
||||
Self::system_parameters_info_w(
|
||||
SPI_SETACTIVEWINDOWTRACKING,
|
||||
@@ -580,6 +701,7 @@ impl WindowsApi {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn disable_focus_follows_mouse() -> Result<()> {
|
||||
Self::system_parameters_info_w(
|
||||
SPI_SETACTIVEWINDOWTRACKING,
|
||||
@@ -588,4 +710,192 @@ impl WindowsApi {
|
||||
SPIF_SENDCHANGE,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn module_handle_w() -> Result<HINSTANCE> {
|
||||
Result::from(WindowsResult::from(unsafe { GetModuleHandleW(None) }))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn register_class_ex_w(class: &WNDCLASSEXW) -> Result<u16> {
|
||||
Result::from(WindowsResult::from(unsafe { RegisterClassExW(class) }))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, dead_code)]
|
||||
fn create_window_ex_w(
|
||||
window_ex_style: WINDOW_EX_STYLE,
|
||||
class_name: PWSTR,
|
||||
window_name: PWSTR,
|
||||
window_style: WINDOW_STYLE,
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
hwnd_parent: HWND,
|
||||
hmenu: HMENU,
|
||||
hinstance: HINSTANCE,
|
||||
lp_param: *mut c_void,
|
||||
) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
CreateWindowExW(
|
||||
window_ex_style,
|
||||
class_name,
|
||||
window_name,
|
||||
window_style,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
hwnd_parent,
|
||||
hmenu,
|
||||
hinstance,
|
||||
lp_param,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn hidden_message_window(name: &str, wnd_proc: Option<WNDPROC>) -> Result<isize> {
|
||||
let hinstance = Self::module_handle_w()?;
|
||||
|
||||
let window_class = WNDCLASSEXW {
|
||||
cbSize: u32::try_from(std::mem::size_of::<WNDCLASSEXW>())?,
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hbrBackground: HBRUSH::NULL,
|
||||
hCursor: HCURSOR::NULL,
|
||||
hIcon: HICON::NULL,
|
||||
hIconSm: HICON::NULL,
|
||||
hInstance: hinstance,
|
||||
lpfnWndProc: wnd_proc,
|
||||
lpszClassName: name.into_pwstr(),
|
||||
lpszMenuName: PWSTR::NULL,
|
||||
style: WNDCLASS_STYLES::from(0),
|
||||
};
|
||||
|
||||
Self::register_class_ex_w(&window_class)?;
|
||||
|
||||
Self::create_window_ex_w(
|
||||
WINDOW_EX_STYLE::from(0),
|
||||
name.into_pwstr(),
|
||||
name.into_pwstr(),
|
||||
WINDOW_STYLE::from(0),
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
HWND_MESSAGE,
|
||||
HMENU::NULL,
|
||||
hinstance,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn destroy_window(hwnd: isize) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe { DestroyWindow(HWND(hwnd)) }))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn unregister_class_w(name: &str) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
UnregisterClassW(name.into_pwstr(), Self::module_handle_w()?)
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn register_raw_input_devices(devices: &mut [RAWINPUTDEVICE]) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
RegisterRawInputDevices(
|
||||
devices.as_mut_ptr(),
|
||||
u32::try_from(devices.len())?,
|
||||
u32::try_from(std::mem::size_of::<RAWINPUTDEVICE>())?,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn register_mice_for_hwnd(hwnd: isize) -> Result<()> {
|
||||
Self::register_raw_input_devices(&mut [RAWINPUTDEVICE {
|
||||
dwFlags: RIDEV_NOLEGACY | RIDEV_INPUTSINK,
|
||||
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
||||
usUsage: HID_USAGE_GENERIC_MOUSE,
|
||||
hwndTarget: HWND(hwnd),
|
||||
}])
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn raw_input_buffer_null(buffer_size: *mut u32, header_size: u32) -> Result<()> {
|
||||
Result::from(unsafe {
|
||||
match GetRawInputBuffer(std::ptr::null_mut(), buffer_size, header_size) {
|
||||
0 => WindowsResult::Ok(()),
|
||||
_ => WindowsResult::Err(std::io::Error::last_os_error().into()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn raw_input_buffer(
|
||||
raw_input_pointer: *mut RAWINPUT,
|
||||
buffer_size: *mut u32,
|
||||
header_size: u32,
|
||||
) -> Result<u32> {
|
||||
Result::from(unsafe {
|
||||
WindowsResult::Ok(GetRawInputBuffer(
|
||||
raw_input_pointer,
|
||||
buffer_size,
|
||||
header_size,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn raw_input_data_null(raw_input_handle: HRAWINPUT, buffer_size: &mut u32) -> Result<()> {
|
||||
Result::from(unsafe {
|
||||
match GetRawInputData(
|
||||
raw_input_handle,
|
||||
RID_INPUT,
|
||||
std::ptr::null_mut(),
|
||||
buffer_size,
|
||||
u32::try_from(std::mem::size_of::<RAWINPUTHEADER>())?,
|
||||
) {
|
||||
0 => WindowsResult::Ok(()),
|
||||
_ => WindowsResult::Err(std::io::Error::last_os_error().into()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn raw_input_data(
|
||||
raw_input_handle: HRAWINPUT,
|
||||
buffer: *mut c_void,
|
||||
buffer_size: *mut u32,
|
||||
) -> Result<u32> {
|
||||
Result::from(unsafe {
|
||||
match GetRawInputData(
|
||||
raw_input_handle,
|
||||
RID_INPUT,
|
||||
buffer,
|
||||
buffer_size,
|
||||
u32::try_from(std::mem::size_of::<RAWINPUTHEADER>())?,
|
||||
) {
|
||||
0 => WindowsResult::Err(std::io::Error::last_os_error().into()),
|
||||
n => WindowsResult::Ok(n),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn find_window_ex_w(parent: HWND, class: &str, title: &str) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
let hwnd = FindWindowExW(parent, HWND::NULL, class.into_pwstr(), title.into_pwstr());
|
||||
dbg!(hwnd);
|
||||
hwnd
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn find_message_window(class: &str, title: &str) -> Result<isize> {
|
||||
Self::find_window_ex_w(HWND_MESSAGE, class, title)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,17 @@ use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
|
||||
pub extern "system" fn valid_display_monitors(
|
||||
hmonitor: HMONITOR,
|
||||
_: HDC,
|
||||
_: *mut RECT,
|
||||
lparam: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
||||
monitors.push(hmonitor.0);
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub extern "system" fn enum_display_monitor(
|
||||
hmonitor: HMONITOR,
|
||||
_: HDC,
|
||||
@@ -23,6 +34,14 @@ pub extern "system" fn enum_display_monitor(
|
||||
lparam: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = unsafe { &mut *(lparam.0 as *mut Ring<Monitor>) };
|
||||
|
||||
// Don't duplicate a monitor that is already being managed
|
||||
for monitor in monitors.elements() {
|
||||
if monitor.id() == hmonitor.0 {
|
||||
return true.into();
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(m) = WindowsApi::monitor(hmonitor) {
|
||||
monitors.elements_mut().push_back(m);
|
||||
}
|
||||
@@ -30,6 +49,13 @@ pub extern "system" fn enum_display_monitor(
|
||||
true.into()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub extern "system" fn valid_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
||||
hwnds.push(hwnd.0);
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ impl MessageLoop {
|
||||
loop {
|
||||
let mut value: Option<MSG> = None;
|
||||
unsafe {
|
||||
if !bool::from(!PeekMessageW(&mut msg, HWND(0), 0, 0, PM_REMOVE)) {
|
||||
if bool::from(PeekMessageW(&mut msg, HWND(0), 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ impl Workspace {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, work_area: &Rect) -> Result<()> {
|
||||
pub fn update(&mut self, work_area: &Rect, invisible_borders: &Rect) -> Result<()> {
|
||||
let mut adjusted_work_area = *work_area;
|
||||
adjusted_work_area.add_padding(self.workspace_padding());
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Workspace {
|
||||
if *self.tile() {
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window) = container.focused_window_mut() {
|
||||
window.set_position(&adjusted_work_area, true)?;
|
||||
window.set_position(&adjusted_work_area, invisible_borders, true)?;
|
||||
};
|
||||
} else if let Some(window) = self.maximized_window_mut() {
|
||||
window.maximize();
|
||||
@@ -166,7 +166,7 @@ impl Workspace {
|
||||
let windows = self.visible_windows_mut();
|
||||
for (i, window) in windows.into_iter().enumerate() {
|
||||
if let (Some(window), Some(layout)) = (window, layouts.get(i)) {
|
||||
window.set_position(layout, false)?;
|
||||
window.set_position(layout, invisible_borders, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +263,38 @@ impl Workspace {
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {
|
||||
for container in self.containers() {
|
||||
if let Some(hwnd) = container.hwnd_from_exe(exe) {
|
||||
return Option::from(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
if let Ok(window_exe) = window.exe() {
|
||||
if exe == window_exe {
|
||||
return Option::from(window.hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container() {
|
||||
if let Some(hwnd) = container.hwnd_from_exe(exe) {
|
||||
return Option::from(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
if let Ok(window_exe) = window.exe() {
|
||||
if exe == window_exe {
|
||||
return Option::from(window.hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for container in self.containers() {
|
||||
if container.contains_window(hwnd) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
; Generated by komorebic.exe
|
||||
|
||||
Start() {
|
||||
Run, komorebic.exe start, , Hide
|
||||
Start(ffm) {
|
||||
Run, komorebic.exe start --ffm %ffm%, , Hide
|
||||
}
|
||||
|
||||
Stop() {
|
||||
@@ -12,6 +12,10 @@ State() {
|
||||
Run, komorebic.exe state, , Hide
|
||||
}
|
||||
|
||||
Query(state_query) {
|
||||
Run, komorebic.exe query %state_query%, , Hide
|
||||
}
|
||||
|
||||
Log() {
|
||||
Run, komorebic.exe log, , Hide
|
||||
}
|
||||
@@ -68,6 +72,10 @@ NewWorkspace() {
|
||||
Run, komorebic.exe new-workspace, , Hide
|
||||
}
|
||||
|
||||
InvisibleBorders(left, top, right, bottom) {
|
||||
Run, komorebic.exe invisible-borders %left% %top% %right% %bottom%, , Hide
|
||||
}
|
||||
|
||||
AdjustContainerPadding(sizing, adjustment) {
|
||||
Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide
|
||||
}
|
||||
@@ -172,12 +180,16 @@ IdentifyTrayApplication(identifier, id) {
|
||||
Run, komorebic.exe identify-tray-application %identifier% %id%, , Hide
|
||||
}
|
||||
|
||||
FocusFollowsMouse(boolean_state) {
|
||||
Run, komorebic.exe focus-follows-mouse %boolean_state%, , Hide
|
||||
IdentifyBorderOverflow(identifier, id) {
|
||||
Run, komorebic.exe identify-border-overflow %identifier% %id%, , Hide
|
||||
}
|
||||
|
||||
ToggleFocusFollowsMouse() {
|
||||
Run, komorebic.exe toggle-focus-follows-mouse, , Hide
|
||||
FocusFollowsMouse(boolean_state, implementation) {
|
||||
Run, komorebic.exe focus-follows-mouse %boolean_state% --implementation %implementation%, , Hide
|
||||
}
|
||||
|
||||
ToggleFocusFollowsMouse(implementation) {
|
||||
Run, komorebic.exe toggle-focus-follows-mouse --implementation %implementation%, , Hide
|
||||
}
|
||||
|
||||
AhkLibrary() {
|
||||
|
||||
@@ -9,7 +9,6 @@ use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::stringify;
|
||||
|
||||
use clap::AppSettings;
|
||||
use clap::ArgEnum;
|
||||
@@ -31,10 +30,13 @@ use derive_ahk::AhkLibrary;
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::Flip;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
use komorebi_core::SocketMessage;
|
||||
use komorebi_core::StateQuery;
|
||||
|
||||
trait AhkLibrary {
|
||||
fn generate_ahk_library() -> String;
|
||||
@@ -61,7 +63,7 @@ impl From<BooleanState> for bool {
|
||||
|
||||
macro_rules! gen_enum_subcommand_args {
|
||||
// SubCommand Pattern: Enum Type
|
||||
( $( $name:ident: $element:ty ),+ ) => {
|
||||
( $( $name:ident: $element:ty ),+ $(,)? ) => {
|
||||
$(
|
||||
paste! {
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
@@ -82,12 +84,12 @@ gen_enum_subcommand_args! {
|
||||
FlipLayout: Flip,
|
||||
ChangeLayout: Layout,
|
||||
WatchConfiguration: BooleanState,
|
||||
FocusFollowsMouse: BooleanState
|
||||
Query: StateQuery,
|
||||
}
|
||||
|
||||
macro_rules! gen_target_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
@@ -104,7 +106,7 @@ gen_target_subcommand_args! {
|
||||
SendToMonitor,
|
||||
SendToWorkspace,
|
||||
FocusMonitor,
|
||||
FocusWorkspace
|
||||
FocusWorkspace,
|
||||
}
|
||||
|
||||
// Thanks to @danielhenrymantilla for showing me how to use cfg_attr with an optional argument like
|
||||
@@ -112,7 +114,7 @@ gen_target_subcommand_args! {
|
||||
macro_rules! gen_workspace_subcommand_args {
|
||||
// Workspace Property: #[enum] Value Enum (if the value is an Enum)
|
||||
// Workspace Property: Value Type (if the value is anything else)
|
||||
( $( $name:ident: $(#[enum] $(@$arg_enum:tt)?)? $value:ty ),+ ) => (
|
||||
( $( $name:ident: $(#[enum] $(@$arg_enum:tt)?)? $value:ty ),+ $(,)? ) => (
|
||||
paste! {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
@@ -138,7 +140,7 @@ macro_rules! gen_workspace_subcommand_args {
|
||||
gen_workspace_subcommand_args! {
|
||||
Name: String,
|
||||
Layout: #[enum] Layout,
|
||||
Tiling: #[enum] BooleanState
|
||||
Tiling: #[enum] BooleanState,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
@@ -149,6 +151,18 @@ struct Resize {
|
||||
sizing: Sizing,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct InvisibleBorders {
|
||||
/// Size of the left invisible border
|
||||
left: i32,
|
||||
/// Size of the top invisible border (usually 0)
|
||||
top: i32,
|
||||
/// Size of the right invisible border (usually left * 2)
|
||||
right: i32,
|
||||
/// Size of the bottom invisible border (usually the same as left)
|
||||
bottom: i32,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct EnsureWorkspaces {
|
||||
/// Monitor index (zero-indexed)
|
||||
@@ -159,7 +173,7 @@ struct EnsureWorkspaces {
|
||||
|
||||
macro_rules! gen_padding_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
@@ -176,12 +190,12 @@ macro_rules! gen_padding_subcommand_args {
|
||||
|
||||
gen_padding_subcommand_args! {
|
||||
ContainerPadding,
|
||||
WorkspacePadding
|
||||
WorkspacePadding,
|
||||
}
|
||||
|
||||
macro_rules! gen_padding_adjustment_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
@@ -196,12 +210,12 @@ macro_rules! gen_padding_adjustment_subcommand_args {
|
||||
|
||||
gen_padding_adjustment_subcommand_args! {
|
||||
AdjustContainerPadding,
|
||||
AdjustWorkspacePadding
|
||||
AdjustWorkspacePadding,
|
||||
}
|
||||
|
||||
macro_rules! gen_application_target_subcommand_args {
|
||||
// SubCommand Pattern
|
||||
( $( $name:ident ),+ ) => {
|
||||
( $( $name:ident ),+ $(,)? ) => {
|
||||
$(
|
||||
#[derive(clap::Clap, derive_ahk::AhkFunction)]
|
||||
pub struct $name {
|
||||
@@ -217,7 +231,8 @@ macro_rules! gen_application_target_subcommand_args {
|
||||
gen_application_target_subcommand_args! {
|
||||
FloatRule,
|
||||
ManageRule,
|
||||
IdentifyTrayApplication
|
||||
IdentifyTrayApplication,
|
||||
IdentifyBorderOverflow,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
@@ -232,6 +247,27 @@ struct WorkspaceRule {
|
||||
workspace: usize,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct ToggleFocusFollowsMouse {
|
||||
#[clap(arg_enum, short, long, default_value = "windows")]
|
||||
implementation: FocusFollowsMouseImplementation,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct FocusFollowsMouse {
|
||||
#[clap(arg_enum, short, long, default_value = "windows")]
|
||||
implementation: FocusFollowsMouseImplementation,
|
||||
#[clap(arg_enum)]
|
||||
boolean_state: BooleanState,
|
||||
}
|
||||
|
||||
#[derive(Clap, AhkFunction)]
|
||||
struct Start {
|
||||
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
||||
#[clap(long)]
|
||||
ffm: bool,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
#[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)]
|
||||
struct Opts {
|
||||
@@ -242,11 +278,14 @@ struct Opts {
|
||||
#[derive(Clap, AhkLibrary)]
|
||||
enum SubCommand {
|
||||
/// Start komorebi.exe as a background process
|
||||
Start,
|
||||
Start(Start),
|
||||
/// Stop the komorebi.exe process and restore all hidden windows
|
||||
Stop,
|
||||
/// Show a JSON representation of the current window manager state
|
||||
State,
|
||||
/// Query the current window manager state
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
Query(Query),
|
||||
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||
Log,
|
||||
/// Change focus to the window in the specified direction
|
||||
@@ -286,6 +325,9 @@ enum SubCommand {
|
||||
FocusWorkspace(FocusWorkspace),
|
||||
/// Create and append a new workspace on the focused monitor
|
||||
NewWorkspace,
|
||||
/// Set the invisible border dimensions around each window
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
InvisibleBorders(InvisibleBorders),
|
||||
/// Adjust container padding on the focused workspace
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
AdjustContainerPadding(AdjustContainerPadding),
|
||||
@@ -353,11 +395,15 @@ enum SubCommand {
|
||||
/// Identify an application that closes to the system tray
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
IdentifyTrayApplication(IdentifyTrayApplication),
|
||||
/// Identify an application that has overflowing borders
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
IdentifyBorderOverflow(IdentifyBorderOverflow),
|
||||
/// Enable or disable focus follows mouse for the operating system
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
FocusFollowsMouse(FocusFollowsMouse),
|
||||
/// Toggle focus follows mouse for the operating system
|
||||
ToggleFocusFollowsMouse,
|
||||
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
|
||||
ToggleFocusFollowsMouse(ToggleFocusFollowsMouse),
|
||||
/// Generate a library of AutoHotKey helper functions
|
||||
AhkLibrary,
|
||||
}
|
||||
@@ -436,6 +482,17 @@ fn main() -> Result<()> {
|
||||
SubCommand::SendToWorkspace(arg) => {
|
||||
send_message(&*SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::InvisibleBorders(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::InvisibleBorders(Rect {
|
||||
left: arg.left,
|
||||
top: arg.top,
|
||||
right: arg.right,
|
||||
bottom: arg.bottom,
|
||||
})
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::ContainerPadding(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)
|
||||
@@ -458,8 +515,8 @@ fn main() -> Result<()> {
|
||||
&*SocketMessage::AdjustContainerPadding(arg.sizing, arg.adjustment).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::ToggleFocusFollowsMouse => {
|
||||
send_message(&*SocketMessage::ToggleFocusFollowsMouse.as_bytes()?)?;
|
||||
SubCommand::ToggleFocusFollowsMouse(arg) => {
|
||||
send_message(&*SocketMessage::ToggleFocusFollowsMouse(arg.implementation).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ToggleTiling => {
|
||||
send_message(&*SocketMessage::ToggleTiling.as_bytes()?)?;
|
||||
@@ -485,7 +542,7 @@ fn main() -> Result<()> {
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::Start => {
|
||||
SubCommand::Start(arg) => {
|
||||
let mut buf: PathBuf;
|
||||
|
||||
// The komorebi.ps1 shim will only exist in the Path if installed by Scoop
|
||||
@@ -508,11 +565,27 @@ fn main() -> Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
let script = if let Some(exec) = exec {
|
||||
format!("Start-Process '{}' -WindowStyle hidden", exec)
|
||||
} else {
|
||||
String::from("Start-Process komorebi -WindowStyle hidden")
|
||||
};
|
||||
let script = exec.map_or_else(
|
||||
|| {
|
||||
if arg.ffm {
|
||||
String::from(
|
||||
"Start-Process komorebi.exe -ArgumentList '--ffm' -WindowStyle hidden",
|
||||
)
|
||||
} else {
|
||||
String::from("Start-Process komorebi.exe -WindowStyle hidden")
|
||||
}
|
||||
},
|
||||
|exec| {
|
||||
if arg.ffm {
|
||||
format!(
|
||||
"Start-Process '{}' -ArgumentList '--ffm' -WindowStyle hidden",
|
||||
exec
|
||||
)
|
||||
} else {
|
||||
format!("Start-Process '{}' -WindowStyle hidden", exec)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
match powershell_script::run(&script, true) {
|
||||
Ok(output) => {
|
||||
@@ -608,6 +681,40 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
SubCommand::Query(arg) => {
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let mut socket = home;
|
||||
socket.push("komorebic.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&*SocketMessage::Query(arg.state_query).as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
match listener.accept() {
|
||||
Ok(incoming) => {
|
||||
let stream = BufReader::new(incoming.0);
|
||||
for line in stream.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
SubCommand::RestoreWindows => {
|
||||
let mut hwnd_json = dirs::home_dir().context("there is no home directory")?;
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
@@ -629,7 +736,9 @@ fn main() -> Result<()> {
|
||||
BooleanState::Disable => false,
|
||||
};
|
||||
|
||||
send_message(&*SocketMessage::FocusFollowsMouse(enable).as_bytes()?)?;
|
||||
send_message(
|
||||
&*SocketMessage::FocusFollowsMouse(arg.implementation, enable).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::ReloadConfiguration => {
|
||||
send_message(&*SocketMessage::ReloadConfiguration.as_bytes()?)?;
|
||||
@@ -647,6 +756,11 @@ fn main() -> Result<()> {
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::IdentifyBorderOverflow(target) => {
|
||||
send_message(
|
||||
&*SocketMessage::IdentifyBorderOverflow(target.identifier, target.id).as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
SubCommand::Manage => {
|
||||
send_message(&*SocketMessage::ManageFocusedWindow.as_bytes()?)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user