Compare commits

..

10 Commits

Author SHA1 Message Date
LGUG2Z
cab8b4ad52 chore(release): v0.1.3 2021-08-24 08:28:44 -07:00
LGUG2Z
05777c34b9 fix(wm): ensure removal of max + monocle windows
Previously, the implementation of maximized and monocle windows assumed
that the only valid state for them to transition to would be to restore
them to the index that they were maximized/monocle-d from in their host
workspace.

This is not exclusively the case as it is also possible for them to be
closed when they are in a maximized or monocle state.

This commit updates the Workspace.remove_window() fn to also look for
the hwnd to be removed in the monocle container and maximized window, if
they exist.

fix #19
2021-08-24 07:21:13 -07:00
LGUG2Z
5094001862 feat(wm): add send-to-workspace/monitor cmds
This commit adds two commands to allow the user to send the currently
focused container to a different workspace or monitor as a background
operation, without following the moved container to the destination
workspace or monitor.

resolve #20
2021-08-24 06:52:56 -07:00
LGUG2Z
bc08e177a1 fix(komorebic): add missing help annotations 2021-08-23 15:14:16 -07:00
LGUG2Z
87fe718754 feat(wm): add toggle-focus-follows-mouse cmd
Decided there should be a quick way to toggle the native ffm
functionality, it gets especially annoying when trying to click drop
downs from the system tray etc.

re #7
2021-08-23 14:08:40 -07:00
LGUG2Z
fb4fe4d9c3 refactor(derive-ahk): enforce no_implicit_prelude
Starting to implement the feedback I got from this post on Reddit
https://old.reddit.com/r/rust/comments/pa2997/code_review_request_first_derive_macro/.
2021-08-23 11:16:58 -07:00
LGUG2Z
b61b03b1c9 refactor(eyre): handle options with combinators
This commit removes the unnecessary eyre dependency and instead uses the
relevant imports from color-eyre.

Additionally, after reading the eyre readme a little more closely, I
have switched out .compat() for the ok_or() combinator function as
suggested here: https://github.com/yaahc/eyre#compatibility-with-anyhow.
2021-08-23 09:52:13 -07:00
LGUG2Z
a02cd699a0 refactor(derive-ahk): push up generation logic
This commit pushes as much of the generation logic as possible to the
derive-ahk crate, so that when it is used in komorebic, we only need to
do an as_bytes() call to prepare it for being written to a file.

Besides that, this commit changes the generation command name to
'ahk-library' for clarity, and adds both additional samples and
instructions in the readme file and Scoop post-install hook.
2021-08-23 07:49:37 -07:00
LGUG2Z
2c876701d8 feat(ahk): add cmd to generate helper lib
Woke up today and thought this would be a cool way to learn more about
deriving functionality with proc macros.

Hopefully having this wrapper/helper library will make first time
configuration for new users easier.
2021-08-22 18:54:44 -07:00
LGUG2Z
c42739591f build(windows-rs): upgrade to 0.19.0 2021-08-22 07:19:34 -07:00
22 changed files with 971 additions and 216 deletions

View File

@@ -55,5 +55,6 @@ scoop:
pre_install:
- if (Get-Process -Name komorebi -ErrorAction SilentlyContinue) { komorebic stop }
post_install:
- Write-Host "Run 'cp $original_dir\komorebi.sample.ahk $Env:UserProfile\komorebi.ahk' to get started with the sample configuration"
- Write-Host "Once you have a configuration file in place, you can run 'komorebic start' to start the window manager"
- Write-Host "`nRun 'cp $original_dir\komorebi.sample.ahk $Env:UserProfile\komorebi.ahk' to get started with the sample configuration"
- Write-Host "`nRun 'komorebic ahk-library' if you would like to generate an AHK helper library to use in your configuration"
- Write-Host "`nOnce you have a configuration file in place, you can run 'komorebic start' to start the window manager"

68
Cargo.lock generated
View File

@@ -257,6 +257,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "derive-ahk"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dirs"
version = "3.0.2"
@@ -480,9 +489,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.7"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "kernel32-sys"
@@ -496,7 +505,7 @@ dependencies = [
[[package]]
name = "komorebi"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"bindings",
"bitflags",
@@ -505,7 +514,6 @@ dependencies = [
"crossbeam-utils",
"ctrlc",
"dirs",
"eyre",
"getset",
"hotwatch",
"komorebi-core",
@@ -527,7 +535,7 @@ dependencies = [
[[package]]
name = "komorebi-core"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"bindings",
"clap",
@@ -539,13 +547,15 @@ dependencies = [
[[package]]
name = "komorebic"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"bindings",
"clap",
"color-eyre",
"derive-ahk",
"dirs",
"fs-tail",
"heck",
"komorebi-core",
"paste",
"powershell_script",
@@ -568,9 +578,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.99"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
[[package]]
name = "lock_api"
@@ -1076,18 +1086,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.127"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.127"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4"
dependencies = [
"proc-macro2",
"quote",
@@ -1166,9 +1176,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.20.0"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0af066e6272f2175c1783cfc2ebf3e2d8dfe2c182b00677fdeccbf8291af83fb"
checksum = "4786f320388e6031aa78c68455553f4e3425deeeb40565fecba3d101c1faf21f"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
@@ -1446,9 +1456,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.18.0"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68088239696c06152844eadc03d262f088932cce50c67e4ace86e19d95e976fe"
checksum = "ef84dd25f4c69a271b1bba394532bf400523b43169de21dfc715e8f8e491053d"
dependencies = [
"const-sha1",
"windows_gen",
@@ -1457,20 +1467,38 @@ dependencies = [
[[package]]
name = "windows_gen"
version = "0.18.0"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf583322dc423ee021035b358e535015f7fd163058a31e2d37b99a939141121d"
checksum = "ac7bb21b8ff5e801232b72a6ff554b4cc0cef9ed9238188c3ca78fe3968a7e5d"
dependencies = [
"windows_quote",
"windows_reader",
]
[[package]]
name = "windows_macros"
version = "0.18.0"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58acfb8832e9f707f8997bd161e537a1c1f603e60a5bd9c3cf53484fdcc998f3"
checksum = "5566b8c51118769e4a9094a688bf1233a3f36aacbfc78f3b15817fe0b6e0442f"
dependencies = [
"syn",
"windows_gen",
"windows_quote",
"windows_reader",
]
[[package]]
name = "windows_quote"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4af8236a9493c38855f95cdd11b38b342512a5df4ee7473cffa828b5ebb0e39c"
[[package]]
name = "windows_reader"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab"
[[package]]
name = "winvd"
version = "0.0.20"

View File

@@ -2,6 +2,7 @@
members = [
"bindings",
"derive-ahk",
"komorebi",
"komorebi-core",
"komorebic"

107
README.md
View File

@@ -171,51 +171,65 @@ keybindings with. You can run `komorebic.exe <COMMAND> --help` to get a full exp
each command.
```
start Start komorebi.exe as a background process
stop Stop the komorebi.exe process and restore all hidden windows
state Show a JSON representation of the current window manager state
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
stack Stack the focused window in the specified direction
resize Resize the focused window in the specified direction
unstack Unstack the focused window
cycle-stack Cycle the focused stack in the specified cycle direction
move-to-monitor Move the focused window to the specified monitor
move-to-workspace Move the focused window to the specified workspace
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
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
flip-layout Flip the layout on the focused workspace (BSP only)
promote Promote the focused window to the top of the tree
retile Force the retiling of all managed windows
ensure-workspaces Create at least this many workspaces for the specified monitor
container-padding Set the container padding for the specified workspace
workspace-padding Set the workspace padding for the specified workspace
workspace-layout Set the layout for the specified workspace
workspace-tiling Enable or disable window tiling for the specified workspace
workspace-name Set the workspace name for the specified workspace
toggle-pause Toggle the window manager on and off across all monitors
toggle-tiling Toggle window tiling on the focused workspace
toggle-float Toggle floating mode for the focused window
toggle-monocle Toggle monocle mode for the focused container
toggle-maximize Toggle native maximization for the focused window
restore-windows Restore all hidden windows (debugging command)
manage Force komorebi to manage the focused window
unmanage Unmanage a window that was forcibly managed
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Toggle the automatic reloading of ~/komorebi.ahk (if it exists)
float-rule Add a rule to always float the specified application
manage-rule Add a rule to always manage the specified application
workspace-rule Add a rule to associate an application with a workspace
identify-tray-application Identify an application that closes to the system tray
focus-follows-mouse Enable or disable focus follows mouse for the operating system
help Print this message or the help of the given subcommand(s)
start Start komorebi.exe as a background process
stop Stop the komorebi.exe process and restore all hidden windows
state Show a JSON representation of the current window manager state
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
stack Stack the focused window in the specified direction
resize Resize the focused window in the specified direction
unstack Unstack the focused window
cycle-stack Cycle the focused stack in the specified cycle direction
move-to-monitor Move the focused window to the specified monitor
move-to-workspace Move the focused window to the specified workspace
send-to-monitor Send the focused window to the specified monitor
send-to-workspace Send the focused window to the specified workspace
focus-monitor Focus the specified monitor
focus-workspace Focus the specified workspace on the focused monitor
new-workspace Create and append a new workspace on the focused monitor
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
flip-layout Flip the layout on the focused workspace (BSP only)
promote Promote the focused window to the top of the tree
retile Force the retiling of all managed windows
ensure-workspaces Create at least this many workspaces for the specified monitor
container-padding Set the container padding for the specified workspace
workspace-padding Set the workspace padding for the specified workspace
workspace-layout Set the layout for the specified workspace
workspace-tiling Enable or disable window tiling for the specified workspace
workspace-name Set the workspace name for the specified workspace
toggle-pause Toggle the window manager on and off across all monitors
toggle-tiling Toggle window tiling on the focused workspace
toggle-float Toggle floating mode for the focused window
toggle-monocle Toggle monocle mode for the focused container
toggle-maximize Toggle native maximization for the focused window
restore-windows Restore all hidden windows (debugging command)
manage Force komorebi to manage the focused window
unmanage Unmanage a window that was forcibly managed
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
float-rule Add a rule to always float the specified application
manage-rule Add a rule to always manage the specified application
workspace-rule Add a rule to associate an application with a workspace
identify-tray-application Identify an application that closes to the system tray
focus-follows-mouse Enable or disable focus follows mouse for the operating system
toggle-focus-follows-mouse Toggle focus follows mouse for the operating system
ahk-library Generate a library of AutoHotKey helper functions
help Print this message or the help of the given subcommand(s)
```
### AutoHotKey Helper Library for `komorebic`
Additionally, you may run `komorebic.exe ahk-library` to
generate [a helper library for AutoHotKey](komorebic.lib.sample.ahk) which wraps every `komorebic` command in a native
AHK function.
If you include the generated library at the top of your `~/komorebi.ahk` configuration file, you will be able to call
any of the functions that it contains. A sample AHK script that shows how this library can be
used [is available here](komorebi.sample.with.lib.ahk).
## Features
- [x] Multi-monitor
@@ -224,8 +238,10 @@ help Print this message or the help of the given subcomm
- [x] Cycle through stacked windows
- [x] Change focused window by direction
- [x] Move focused window container in direction
- [x] Move focused window container to monitor
- [x] Move focused window container to workspace
- [x] Move focused window container to monitor and follow
- [x] Move focused window container to workspace follow
- [x] Send focused window container to monitor
- [x] Send focused window container to workspace
- [x] Mouse follows focused container
- [x] Resize window container in direction
- [ ] Resize child window containers by split ratio
@@ -248,6 +264,7 @@ help Print this message or the help of the given subcomm
- [x] Load configuration on startup
- [x] Manually reload configuration
- [x] Watch configuration for changes
- [x] Helper library for AutoHotKey
- [x] View window manager state
## Development

View File

@@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
windows = "0.18"
windows = "0.19"
[build-dependencies]
windows = "0.18"
windows = "0.19"

14
derive-ahk/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "derive-ahk"
version = "0.1.0"
edition = "2018"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"

120
derive-ahk/src/lib.rs Normal file
View File

@@ -0,0 +1,120 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![no_implicit_prelude]
use ::std::clone::Clone;
use ::std::convert::From;
use ::std::convert::Into;
use ::std::iter::Extend;
use ::std::iter::Iterator;
use ::std::matches;
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;
use ::syn::DeriveInput;
use ::syn::Fields;
use ::syn::FieldsNamed;
use ::syn::FieldsUnnamed;
#[proc_macro_derive(AhkFunction)]
pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
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 idents = named.iter().map(|f| &f.ident);
let called_arguments = quote! {#(%#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),
#arguments,
stringify!(#name).to_kebab_case(),
#called_arguments
)
}
}
}
}
_ => unreachable!("only to be used on structs with named fields"),
},
_ => unreachable!("only to be used on structs"),
}
.into()
}
#[proc_macro_derive(AhkLibrary)]
pub fn ahk_library(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
match input.data {
Data::Enum(DataEnum { variants, .. }) => {
let enums = variants.iter().filter(|&v| {
matches!(v.fields, Fields::Unit) || matches!(v.fields, Fields::Unnamed(..))
});
let mut stream = ::proc_macro2::TokenStream::new();
for variant in enums.clone() {
match &variant.fields {
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
for field in unnamed {
stream.extend(quote! {
v.push(#field::generate_ahk_function());
});
}
}
Fields::Unit => {
let name = &variant.ident;
stream.extend(quote! {
v.push(::std::format!(r#"
{}() {{
Run, komorebic.exe {}, , Hide
}}"#,
::std::stringify!(#name),
::std::stringify!(#name).to_kebab_case()
));
});
}
Fields::Named(_) => {
unreachable!("only to be used with unnamed and unit fields");
}
}
}
quote! {
impl #name {
fn generate_ahk_library() -> String {
let mut v: Vec<String> = vec![String::from("; Generated by komorebic.exe")];
#stream
v.join("\n")
}
}
}
}
_ => unreachable!("only to be used on enums"),
}
.into()
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-core"
version = "0.1.2"
version = "0.1.3"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -32,6 +32,8 @@ pub enum SocketMessage {
CycleStack(CycleDirection),
MoveContainerToMonitorNumber(usize),
MoveContainerToWorkspaceNumber(usize),
SendContainerToMonitorNumber(usize),
SendContainerToWorkspaceNumber(usize),
Promote,
ToggleFloat,
ToggleMonocle,
@@ -66,6 +68,7 @@ pub enum SocketMessage {
IdentifyTrayApplication(ApplicationIdentifier, String),
State,
FocusFollowsMouse(bool),
ToggleFocusFollowsMouse,
}
impl SocketMessage {

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.2"
version = "0.1.3"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -15,7 +15,6 @@ crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = "3"
dirs = "3"
eyre = "0.6"
getset = "0.1"
hotwatch = "0.4"
lazy_static = "1"

View File

@@ -9,7 +9,7 @@ use std::thread;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
@@ -82,7 +82,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
std::env::set_var("RUST_LOG", "info");
}
let home = dirs::home_dir().context("there is no home directory")?;
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let appender = tracing_appender::rolling::never(home, "komorebi.log");
let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log");
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
@@ -133,7 +133,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
}
pub fn load_configuration() -> Result<()> {
let home = dirs::home_dir().context("there is no home directory")?;
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut config_v1 = home.clone();
config_v1.push("komorebi.ahk");
@@ -147,7 +147,7 @@ pub fn load_configuration() -> Result<()> {
config_v1
.as_os_str()
.to_str()
.context("cannot convert path to string")?
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
Command::new("autohotkey.exe")
@@ -159,7 +159,7 @@ pub fn load_configuration() -> Result<()> {
config_v2
.as_os_str()
.to_str()
.context("cannot convert path to string")?
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
Command::new("AutoHotkey64.exe")

View File

@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::collections::VecDeque;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
@@ -56,7 +56,7 @@ impl Monitor {
pub fn add_container(&mut self, container: Container) -> Result<()> {
let workspace = self
.focused_workspace_mut()
.context("there is no workspace")?;
.ok_or_else(|| anyhow!("there is no workspace"))?;
workspace.add_container(container);
@@ -78,17 +78,17 @@ impl Monitor {
) -> Result<()> {
let workspace = self
.focused_workspace_mut()
.context("there is no workspace")?;
.ok_or_else(|| anyhow!("there is no workspace"))?;
if workspace.maximized_window().is_some() {
return Err(eyre::anyhow!(
return Err(anyhow!(
"cannot move native maximized window to another monitor or workspace"
));
}
let container = workspace
.remove_focused_container()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let workspaces = self.workspaces_mut();
@@ -129,7 +129,7 @@ impl Monitor {
if name.is_some() {
self.workspaces_mut()
.get_mut(idx)
.context("there is no workspace")?
.ok_or_else(|| anyhow!("there is no workspace"))?
.set_name(name);
}
}
@@ -145,7 +145,7 @@ impl Monitor {
let work_area = *self.work_area_size();
self.focused_workspace_mut()
.context("there is no workspace")?
.ok_or_else(|| anyhow!("there is no workspace"))?
.update(&work_area)?;
Ok(())

View File

@@ -5,7 +5,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::thread;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use parking_lot::Mutex;
use uds_windows::UnixStream;
@@ -106,6 +106,12 @@ impl WindowManager {
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false)?;
}
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, false)?;
}
SocketMessage::TogglePause => {
tracing::info!("pausing");
self.is_paused = !self.is_paused;
@@ -122,7 +128,7 @@ impl WindowManager {
let work_area = *monitor.work_area_size();
let workspace = monitor
.focused_workspace_mut()
.context("there is no workspace")?;
.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() {
@@ -161,7 +167,8 @@ impl WindowManager {
}
SocketMessage::State => {
let state = serde_json::to_string_pretty(&window_manager::State::from(self))?;
let mut socket = dirs::home_dir().context("there is no home directory")?;
let mut socket =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
socket.push("komorebic.sock");
let socket = socket.as_path();
@@ -178,6 +185,13 @@ impl WindowManager {
WindowsApi::disable_focus_follows_mouse()?;
}
}
SocketMessage::ToggleFocusFollowsMouse => {
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
} else {
WindowsApi::enable_focus_follows_mouse()?;
}
}
SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
}

View File

@@ -2,7 +2,7 @@ use std::fs::OpenOptions;
use std::sync::Arc;
use std::thread;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_channel::select;
use parking_lot::Mutex;
@@ -57,7 +57,7 @@ impl WindowManager {
| WindowManagerEvent::MoveResizeEnd(_, window) => {
let monitor_idx = self
.monitor_idx_from_window(*window)
.context("there is no monitor associated with this window, it may have already been destroyed")?;
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
self.focus_monitor(monitor_idx)?;
}
@@ -158,7 +158,7 @@ impl WindowManager {
if self.focused_monitor_idx() != known_monitor_idx
|| self
.focused_monitor()
.context("there is no monitor")?
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_idx()
!= known_workspace_idx
{
@@ -210,7 +210,7 @@ impl WindowManager {
let old_position = *workspace
.latest_layout()
.get(focused_idx)
.context("there is no latest layout")?;
.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
@@ -305,7 +305,8 @@ impl WindowManager {
}
}
let mut hwnd_json = dirs::home_dir().context("there is no home directory")?;
let mut hwnd_json =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
hwnd_json.push("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)

View File

@@ -2,7 +2,7 @@ use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use serde::ser::SerializeStruct;
use serde::Serialize;
@@ -187,12 +187,12 @@ impl Window {
pub fn style(self) -> Result<GwlStyle> {
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
GwlStyle::from_bits(bits).context("there is no gwl style")
GwlStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn ex_style(self) -> Result<GwlExStyle> {
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
GwlExStyle::from_bits(bits).context("there is no gwl style")
GwlExStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn title(self) -> Result<String> {

View File

@@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::thread;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::ContextCompat;
use color_eyre::Result;
use crossbeam_channel::Receiver;
@@ -102,7 +103,7 @@ impl EnforceWorkspaceRuleOp {
impl WindowManager {
#[tracing::instrument]
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
let home = dirs::home_dir().context("there is no home directory")?;
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut socket = home;
socket.push("komorebi.sock");
let socket = socket.as_path();
@@ -148,7 +149,7 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn watch_configuration(&mut self, enable: bool) -> Result<()> {
let home = dirs::home_dir().context("there is no home directory")?;
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut config_v1 = home.clone();
config_v1.push("komorebi.ahk");
@@ -173,7 +174,7 @@ impl WindowManager {
config
.as_os_str()
.to_str()
.context("cannot convert path to string")?
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
// Always make absolutely sure that there isn't an already existing watch, because
// hotwatch allows multiple watches to be registered for the same path
@@ -204,7 +205,7 @@ impl WindowManager {
config
.as_os_str()
.to_str()
.context("cannot convert path to string")?
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
self.hotwatch.unwatch(config)?;
@@ -222,7 +223,7 @@ impl WindowManager {
let focused_workspace_idx = self
.monitors()
.get(focused_monitor_idx)
.context("there is no monitor with that index")?
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
.focused_workspace_idx();
let workspace_rules = WORKSPACE_RULES.lock();
@@ -283,10 +284,10 @@ impl WindowManager {
let origin_workspace = self
.monitors_mut()
.get_mut(op.origin_monitor_idx)
.context("there is no monitor with that index")?
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
.workspaces_mut()
.get_mut(op.origin_workspace_idx)
.context("there is no workspace with that index")?;
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
// Hide the window we are about to remove if it is on the currently focused workspace
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
@@ -303,7 +304,7 @@ impl WindowManager {
let target_monitor = self
.monitors_mut()
.get_mut(op.target_monitor_idx)
.context("there is no monitor with that index")?;
.ok_or_else(|| anyhow!("there is no monitor with that index"))?;
// The very first time this fn is called, the workspace might not even exist yet
if target_monitor
@@ -318,7 +319,7 @@ impl WindowManager {
let target_workspace = target_monitor
.workspaces_mut()
.get_mut(op.target_workspace_idx)
.context("there is no workspace with that index")?;
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
target_workspace.new_container_for_window(Window { hwnd: op.hwnd });
}
@@ -364,7 +365,7 @@ impl WindowManager {
tracing::info!("updating");
self.focused_monitor_mut()
.context("there is no monitor")?
.ok_or_else(|| anyhow!("there is no monitor"))?
.update_focused_workspace()?;
if mouse_follows_focus {
@@ -385,7 +386,7 @@ impl WindowManager {
// attach to the thread of the desktop window always seems to result in "Access is
// denied (os error 5)"
WindowsApi::set_foreground_window(desktop_window.hwnd())
.map_err(|error| eyre::anyhow!("{} {}:{}", error, file!(), line!()))?;
.map_err(|error| anyhow!("{} {}:{}", error, file!(), line!()))?;
}
}
@@ -408,7 +409,7 @@ impl WindowManager {
let focused_idx_resize = workspace
.resize_dimensions()
.get(focused_idx)
.context("there is no resize adjustment for this container")?;
.ok_or_else(|| anyhow!("there is no resize adjustment for this container"))?;
if direction.is_valid(
workspace.layout(),
@@ -453,7 +454,7 @@ impl WindowManager {
let resize = workspace.layout().resize(
unaltered
.get(focused_idx)
.context("there is no last layout")?,
.ok_or_else(|| anyhow!("there is no last layout"))?,
focused_idx_resize,
direction,
sizing,
@@ -487,25 +488,27 @@ impl WindowManager {
pub fn move_container_to_monitor(&mut self, idx: usize, follow: bool) -> Result<()> {
tracing::info!("moving container");
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
let monitor = self
.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let workspace = monitor
.focused_workspace_mut()
.context("there is no workspace")?;
.ok_or_else(|| anyhow!("there is no workspace"))?;
if workspace.maximized_window().is_some() {
return Err(eyre::anyhow!(
return Err(anyhow!(
"cannot move native maximized window to another monitor or workspace"
));
}
let container = workspace
.remove_focused_container()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let target_monitor = self
.monitors_mut()
.get_mut(idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
target_monitor.add_container(container)?;
target_monitor.load_focused_workspace()?;
@@ -521,9 +524,13 @@ impl WindowManager {
pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> {
tracing::info!("moving container");
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
let monitor = self
.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?;
monitor.move_container_to_workspace(idx, follow)?;
monitor.load_focused_workspace()?;
self.update_focused_workspace(true)
}
@@ -534,7 +541,7 @@ impl WindowManager {
let new_idx = workspace
.new_idx_for_direction(direction)
.context("this is not a valid direction from the current position")?;
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
workspace.focus_container(new_idx);
self.focused_window_mut()?.focus()?;
@@ -551,7 +558,7 @@ impl WindowManager {
let current_idx = workspace.focused_container_idx();
let new_idx = workspace
.new_idx_for_direction(direction)
.context("this is not a valid direction from the current position")?;
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
workspace.swap_containers(current_idx, new_idx);
workspace.focus_container(new_idx);
@@ -565,7 +572,7 @@ impl WindowManager {
let container = self.focused_container_mut()?;
if container.windows().len() == 1 {
return Err(eyre::anyhow!("there is only one window in this container"));
return Err(anyhow!("there is only one window in this container"));
}
let current_idx = container.focused_window_idx();
@@ -592,9 +599,9 @@ impl WindowManager {
);
if is_valid {
let new_idx = workspace
.new_idx_for_direction(direction)
.context("this is not a valid direction from the current position")?;
let new_idx = workspace.new_idx_for_direction(direction).ok_or_else(|| {
anyhow!("this is not a valid direction from the current position")
})?;
let adjusted_new_index = if new_idx > current_container_idx {
new_idx - 1
@@ -623,7 +630,7 @@ impl WindowManager {
tracing::info!("removing window");
if self.focused_container()?.windows().len() == 1 {
return Err(eyre::anyhow!("a container must have at least one window"));
return Err(anyhow!("a container must have at least one window"));
}
let workspace = self.focused_workspace_mut()?;
@@ -673,7 +680,7 @@ impl WindowManager {
let window = workspace
.floating_windows_mut()
.last_mut()
.context("there is no floating window")?;
.ok_or_else(|| anyhow!("there is no floating window"))?;
window.center(&work_area)?;
window.focus()?;
@@ -805,7 +812,7 @@ impl WindowManager {
let padding = workspace
.workspace_padding()
.context("there is no workspace padding")?;
.ok_or_else(|| anyhow!("there is no workspace padding"))?;
workspace.set_workspace_padding(Option::from(sizing.adjust_by(padding, adjustment)));
@@ -820,7 +827,7 @@ impl WindowManager {
let padding = workspace
.container_padding()
.context("there is no container padding")?;
.ok_or_else(|| anyhow!("there is no container padding"))?;
workspace.set_container_padding(Option::from(sizing.adjust_by(padding, adjustment)));
@@ -837,12 +844,12 @@ impl WindowManager {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
workspace.set_tile(tile);
@@ -863,7 +870,7 @@ impl WindowManager {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
let work_area = *monitor.work_area_size();
let focused_workspace_idx = monitor.focused_workspace_idx();
@@ -871,7 +878,7 @@ impl WindowManager {
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
workspace.set_layout(layout);
@@ -895,7 +902,7 @@ impl WindowManager {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
monitor.ensure_workspace_count(workspace_count);
@@ -914,12 +921,12 @@ impl WindowManager {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
workspace.set_workspace_padding(Option::from(size));
@@ -938,12 +945,12 @@ impl WindowManager {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
workspace.set_name(Option::from(name.clone()));
monitor.workspace_names_mut().insert(workspace_idx, name);
@@ -963,12 +970,12 @@ impl WindowManager {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.context("there is no monitor")?;
.ok_or_else(|| anyhow!("there is no monitor"))?;
workspace.set_container_padding(Option::from(size));
@@ -978,7 +985,7 @@ impl WindowManager {
pub fn focused_monitor_work_area(&self) -> Result<Rect> {
Ok(*self
.focused_monitor()
.context("there is no monitor")?
.ok_or_else(|| anyhow!("there is no monitor"))?
.work_area_size())
}
@@ -989,7 +996,7 @@ impl WindowManager {
if self.monitors().get(idx).is_some() {
self.monitors.focus(idx);
} else {
return Err(eyre::anyhow!("this is not a valid monitor index"));
return Err(anyhow!("this is not a valid monitor index"));
}
Ok(())
@@ -1009,16 +1016,16 @@ impl WindowManager {
pub fn focused_workspace(&self) -> Result<&Workspace> {
self.focused_monitor()
.context("there is no monitor")?
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace()
.context("there is no workspace")
.ok_or_else(|| anyhow!("there is no workspace"))
}
pub fn focused_workspace_mut(&mut self) -> Result<&mut Workspace> {
self.focused_monitor_mut()
.context("there is no monitor")?
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_mut()
.context("there is no workspace")
.ok_or_else(|| anyhow!("there is no workspace"))
}
#[tracing::instrument(skip(self))]
@@ -1027,7 +1034,7 @@ impl WindowManager {
let monitor = self
.focused_monitor_mut()
.context("there is no workspace")?;
.ok_or_else(|| anyhow!("there is no workspace"))?;
monitor.focus_workspace(idx)?;
monitor.load_focused_workspace()?;
@@ -1041,7 +1048,7 @@ impl WindowManager {
let monitor = self
.focused_monitor_mut()
.context("there is no workspace")?;
.ok_or_else(|| anyhow!("there is no workspace"))?;
monitor.focus_workspace(monitor.new_workspace_idx())?;
monitor.load_focused_workspace()?;
@@ -1052,18 +1059,18 @@ impl WindowManager {
pub fn focused_container(&self) -> Result<&Container> {
self.focused_workspace()?
.focused_container()
.context("there is no container")
.ok_or_else(|| anyhow!("there is no container"))
}
pub fn focused_container_mut(&mut self) -> Result<&mut Container> {
self.focused_workspace_mut()?
.focused_container_mut()
.context("there is no container")
.ok_or_else(|| anyhow!("there is no container"))
}
fn focused_window_mut(&mut self) -> Result<&mut Window> {
self.focused_container_mut()?
.focused_window_mut()
.context("there is no window")
.ok_or_else(|| anyhow!("there is no window"))
}
}

View File

@@ -3,9 +3,9 @@ use std::convert::TryFrom;
use std::convert::TryInto;
use std::ffi::c_void;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::Error;
use color_eyre::Result;
use eyre::Error;
use bindings::Windows::Win32::Foundation::BOOL;
use bindings::Windows::Win32::Foundation::HANDLE;
@@ -67,6 +67,7 @@ use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
@@ -314,7 +315,7 @@ impl WindowsApi {
next_hwnd = Self::next_window(HWND(next_hwnd))?;
}
Err(eyre::anyhow!("could not find next window"))
Err(anyhow!("could not find next window"))
}
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
@@ -463,7 +464,7 @@ impl WindowsApi {
Ok(Self::exe_path(handle)?
.split('\\')
.last()
.context("there is no last element")?
.ok_or_else(|| anyhow!("there is no last element"))?
.to_string())
}
@@ -557,6 +558,19 @@ impl WindowsApi {
}))
}
pub fn focus_follows_mouse() -> Result<bool> {
let mut is_enabled: BOOL = unsafe { std::mem::zeroed() };
Self::system_parameters_info_w(
SPI_GETACTIVEWINDOWTRACKING,
0,
(&mut is_enabled as *mut BOOL).cast(),
SPIF_SENDCHANGE,
)?;
Ok(is_enabled.into())
}
pub fn enable_focus_follows_mouse() -> Result<()> {
Self::system_parameters_info_w(
SPI_SETACTIVEWINDOWTRACKING,

View File

@@ -1,6 +1,7 @@
use std::collections::VecDeque;
use std::num::NonZeroUsize;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::ContextCompat;
use color_eyre::Result;
use getset::CopyGetters;
@@ -229,16 +230,16 @@ impl Workspace {
pub fn focus_container_by_window(&mut self, hwnd: isize) -> Result<()> {
let container_idx = self
.container_idx_for_window(hwnd)
.context("there is no container/window")?;
.ok_or_else(|| anyhow!("there is no container/window"))?;
let container = self
.containers_mut()
.get_mut(container_idx)
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let window_idx = container
.idx_for_window(hwnd)
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
container.focus_window(window_idx);
self.focus_container(container_idx);
@@ -293,7 +294,7 @@ impl Workspace {
pub fn promote_container(&mut self) -> Result<()> {
let container = self
.remove_focused_container()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
self.containers_mut().push_front(container);
self.resize_dimensions_mut().insert(0, None);
self.focus_container(0);
@@ -328,29 +329,56 @@ impl Workspace {
return Ok(());
}
if let Some(container) = self.monocle_container_mut() {
if let Some(window_idx) = container
.windows()
.iter()
.position(|window| window.hwnd == hwnd)
{
container
.remove_window_by_idx(window_idx)
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
}
return Ok(());
}
}
if let Some(window) = self.maximized_window() {
if window.hwnd == hwnd {
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
return Ok(());
}
}
let container_idx = self
.container_idx_for_window(hwnd)
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
let container = self
.containers_mut()
.get_mut(container_idx)
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let window_idx = container
.windows()
.iter()
.position(|window| window.hwnd == hwnd)
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
container
.remove_window_by_idx(window_idx)
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut()
.remove(container_idx)
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
// Whenever a container is empty, we need to remove any resize dimensions for it too
if self.resize_dimensions().get(container_idx).is_some() {
@@ -393,11 +421,11 @@ impl Workspace {
let container = self
.focused_container_mut()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
// This is a little messy
let adjusted_target_container_index = if container.windows().is_empty() {
@@ -417,13 +445,13 @@ impl Workspace {
let target_container = self
.containers_mut()
.get_mut(adjusted_target_container_index)
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
target_container.add_window(window);
self.focus_container(adjusted_target_container_index);
self.focused_container_mut()
.context("there is no container")?
.ok_or_else(|| anyhow!("there is no container"))?
.load_focused_window();
Ok(())
@@ -434,11 +462,11 @@ impl Workspace {
let container = self
.focused_container_mut()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_container_idx);
@@ -458,7 +486,7 @@ impl Workspace {
let focused_idx = self.focused_container_idx();
let window = self
.remove_focused_floating_window()
.context("there is no floating window")?;
.ok_or_else(|| anyhow!("there is no floating window"))?;
let mut container = Container::default();
container.add_window(window);
@@ -498,11 +526,11 @@ impl Workspace {
let container = self
.focused_container_mut()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
@@ -547,7 +575,7 @@ impl Workspace {
let container = self
.containers_mut()
.remove(focused_idx)
.context("there is not container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
// We don't remove any resize adjustments for a monocle, because when this container is
// inevitably reintegrated, it would be weird if it doesn't go back to the dimensions
@@ -559,7 +587,7 @@ impl Workspace {
self.monocle_container_mut()
.as_mut()
.context("there is no monocle container")?
.ok_or_else(|| anyhow!("there is no monocle container"))?
.load_focused_window();
Ok(())
@@ -568,12 +596,12 @@ impl Workspace {
pub fn reintegrate_monocle_container(&mut self) -> Result<()> {
let restore_idx = self
.monocle_container_restore_idx()
.context("there is no monocle restore index")?;
.ok_or_else(|| anyhow!("there is no monocle restore index"))?;
let container = self
.monocle_container_mut()
.as_ref()
.context("there is no monocle container")?;
.ok_or_else(|| anyhow!("there is no monocle container"))?;
let container = container.clone();
if restore_idx > self.containers().len() - 1 {
@@ -584,7 +612,7 @@ impl Workspace {
self.containers_mut().insert(restore_idx, container);
self.focus_container(restore_idx);
self.focused_container_mut()
.context("there is no container")?
.ok_or_else(|| anyhow!("there is no container"))?
.load_focused_window();
self.set_monocle_container(None);
@@ -598,11 +626,11 @@ impl Workspace {
let container = self
.focused_container_mut()
.context("there is no container")?;
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.context("there is no window")?;
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
@@ -626,12 +654,12 @@ impl Workspace {
pub fn reintegrate_maximized_window(&mut self) -> Result<()> {
let restore_idx = self
.maximized_window_restore_idx()
.context("there is no monocle restore index")?;
.ok_or_else(|| anyhow!("there is no monocle restore index"))?;
let window = self
.maximized_window()
.as_ref()
.context("there is no monocle container")?;
.ok_or_else(|| anyhow!("there is no monocle container"))?;
let window = *window;
if !self.containers().is_empty() && restore_idx > self.containers().len() - 1 {
@@ -646,7 +674,7 @@ impl Workspace {
self.focus_container(restore_idx);
self.focused_container_mut()
.context("there is no container")?
.ok_or_else(|| anyhow!("there is no container"))?
.load_focused_window();
self.set_maximized_window(None);

185
komorebic.lib.sample.ahk Normal file
View File

@@ -0,0 +1,185 @@
; Generated by komorebic.exe
Start() {
Run, komorebic.exe start, , Hide
}
Stop() {
Run, komorebic.exe stop, , Hide
}
State() {
Run, komorebic.exe state, , Hide
}
Log() {
Run, komorebic.exe log, , Hide
}
Focus(operation_direction) {
Run, komorebic.exe focus %operation_direction%, , Hide
}
Move(operation_direction) {
Run, komorebic.exe move %operation_direction%, , Hide
}
Stack(operation_direction) {
Run, komorebic.exe stack %operation_direction%, , Hide
}
Resize(edge, sizing) {
Run, komorebic.exe resize %edge% %sizing%, , Hide
}
Unstack() {
Run, komorebic.exe unstack, , Hide
}
CycleStack(cycle_direction) {
Run, komorebic.exe cycle-stack %cycle_direction%, , Hide
}
MoveToMonitor(target) {
Run, komorebic.exe move-to-monitor %target%, , Hide
}
MoveToWorkspace(target) {
Run, komorebic.exe move-to-workspace %target%, , Hide
}
SendToMonitor(target) {
Run, komorebic.exe send-to-monitor %target%, , Hide
}
SendToWorkspace(target) {
Run, komorebic.exe send-to-workspace %target%, , Hide
}
FocusMonitor(target) {
Run, komorebic.exe focus-monitor %target%, , Hide
}
FocusWorkspace(target) {
Run, komorebic.exe focus-workspace %target%, , Hide
}
NewWorkspace() {
Run, komorebic.exe new-workspace, , Hide
}
AdjustContainerPadding(sizing, adjustment) {
Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide
}
AdjustWorkspacePadding(sizing, adjustment) {
Run, komorebic.exe adjust-workspace-padding %sizing% %adjustment%, , Hide
}
ChangeLayout(layout) {
Run, komorebic.exe change-layout %layout%, , Hide
}
FlipLayout(flip) {
Run, komorebic.exe flip-layout %flip%, , Hide
}
Promote() {
Run, komorebic.exe promote, , Hide
}
Retile() {
Run, komorebic.exe retile, , Hide
}
EnsureWorkspaces(monitor, workspace_count) {
Run, komorebic.exe ensure-workspaces %monitor% %workspace_count%, , Hide
}
ContainerPadding(monitor, workspace, size) {
Run, komorebic.exe container-padding %monitor% %workspace% %size%, , Hide
}
WorkspacePadding(monitor, workspace, size) {
Run, komorebic.exe workspace-padding %monitor% %workspace% %size%, , Hide
}
WorkspaceLayout(monitor, workspace, value) {
Run, komorebic.exe workspace-layout %monitor% %workspace% %value%, , Hide
}
WorkspaceTiling(monitor, workspace, value) {
Run, komorebic.exe workspace-tiling %monitor% %workspace% %value%, , Hide
}
WorkspaceName(monitor, workspace, value) {
Run, komorebic.exe workspace-name %monitor% %workspace% %value%, , Hide
}
TogglePause() {
Run, komorebic.exe toggle-pause, , Hide
}
ToggleTiling() {
Run, komorebic.exe toggle-tiling, , Hide
}
ToggleFloat() {
Run, komorebic.exe toggle-float, , Hide
}
ToggleMonocle() {
Run, komorebic.exe toggle-monocle, , Hide
}
ToggleMaximize() {
Run, komorebic.exe toggle-maximize, , Hide
}
RestoreWindows() {
Run, komorebic.exe restore-windows, , Hide
}
Manage() {
Run, komorebic.exe manage, , Hide
}
Unmanage() {
Run, komorebic.exe unmanage, , Hide
}
ReloadConfiguration() {
Run, komorebic.exe reload-configuration, , Hide
}
WatchConfiguration(boolean_state) {
Run, komorebic.exe watch-configuration %boolean_state%, , Hide
}
FloatRule(identifier, id) {
Run, komorebic.exe float-rule %identifier% %id%, , Hide
}
ManageRule(identifier, id) {
Run, komorebic.exe manage-rule %identifier% %id%, , Hide
}
WorkspaceRule(identifier, id, monitor, workspace) {
Run, komorebic.exe workspace-rule %identifier% %id% %monitor% %workspace%, , Hide
}
IdentifyTrayApplication(identifier, id) {
Run, komorebic.exe identify-tray-application %identifier% %id%, , Hide
}
FocusFollowsMouse(boolean_state) {
Run, komorebic.exe focus-follows-mouse %boolean_state%, , Hide
}
ToggleFocusFollowsMouse() {
Run, komorebic.exe toggle-focus-follows-mouse, , Hide
}
AhkLibrary() {
Run, komorebic.exe ahk-library, , Hide
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.2"
version = "0.1.3"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
@@ -12,12 +12,14 @@ edition = "2018"
[dependencies]
bindings = { package = "bindings", path = "../bindings" }
derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" }
clap = "3.0.0-beta.4"
color-eyre = "0.5"
dirs = "3"
fs-tail = "0.1"
heck = "0.3"
paste = "1"
powershell_script = "0.2"
serde = { version = "1", features = ["derive"] }

View File

@@ -2,12 +2,14 @@
#![allow(clippy::missing_errors_doc)]
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
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;
@@ -15,6 +17,7 @@ use clap::Clap;
use color_eyre::eyre::ContextCompat;
use color_eyre::Result;
use fs_tail::TailedFile;
use heck::KebabCase;
use paste::paste;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
@@ -23,6 +26,8 @@ use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use derive_ahk::AhkFunction;
use derive_ahk::AhkLibrary;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::CycleDirection;
use komorebi_core::Flip;
@@ -31,6 +36,14 @@ use komorebi_core::OperationDirection;
use komorebi_core::Sizing;
use komorebi_core::SocketMessage;
trait AhkLibrary {
fn generate_ahk_library() -> String;
}
trait AhkFunction {
fn generate_ahk_function() -> String;
}
#[derive(ArgEnum)]
enum BooleanState {
Enable,
@@ -51,7 +64,7 @@ macro_rules! gen_enum_subcommand_args {
( $( $name:ident: $element:ty ),+ ) => {
$(
paste! {
#[derive(clap::Clap)]
#[derive(clap::Clap, derive_ahk::AhkFunction)]
pub struct $name {
#[clap(arg_enum)]
[<$element:snake>]: $element
@@ -67,7 +80,7 @@ gen_enum_subcommand_args! {
Stack: OperationDirection,
CycleStack: CycleDirection,
FlipLayout: Flip,
SetLayout: Layout,
ChangeLayout: Layout,
WatchConfiguration: BooleanState,
FocusFollowsMouse: BooleanState
}
@@ -76,7 +89,7 @@ macro_rules! gen_target_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ ) => {
$(
#[derive(clap::Clap)]
#[derive(clap::Clap, derive_ahk::AhkFunction)]
pub struct $name {
/// Target index (zero-indexed)
target: usize,
@@ -88,6 +101,8 @@ macro_rules! gen_target_subcommand_args {
gen_target_subcommand_args! {
MoveToMonitor,
MoveToWorkspace,
SendToMonitor,
SendToWorkspace,
FocusMonitor,
FocusWorkspace
}
@@ -100,7 +115,7 @@ macro_rules! gen_workspace_subcommand_args {
( $( $name:ident: $(#[enum] $(@$arg_enum:tt)?)? $value:ty ),+ ) => (
paste! {
$(
#[derive(clap::Clap)]
#[derive(clap::Clap, derive_ahk::AhkFunction)]
pub struct [<Workspace $name>] {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -126,7 +141,7 @@ gen_workspace_subcommand_args! {
Tiling: #[enum] BooleanState
}
#[derive(Clap)]
#[derive(Clap, AhkFunction)]
struct Resize {
#[clap(arg_enum)]
edge: OperationDirection,
@@ -134,7 +149,7 @@ struct Resize {
sizing: Sizing,
}
#[derive(Clap)]
#[derive(Clap, AhkFunction)]
struct EnsureWorkspaces {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -142,33 +157,70 @@ struct EnsureWorkspaces {
workspace_count: usize,
}
#[derive(Clap)]
struct Padding {
/// Monitor index (zero-indexed)
monitor: usize,
/// Workspace index on the specified monitor (zero-indexed)
workspace: usize,
/// Pixels to pad with as an integer
size: i32,
macro_rules! gen_padding_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ ) => {
$(
#[derive(clap::Clap, derive_ahk::AhkFunction)]
pub struct $name {
/// Monitor index (zero-indexed)
monitor: usize,
/// Workspace index on the specified monitor (zero-indexed)
workspace: usize,
/// Pixels to pad with as an integer
size: i32,
}
)+
};
}
#[derive(Clap)]
struct PaddingAdjustment {
#[clap(arg_enum)]
sizing: Sizing,
/// Pixels to adjust by as an integer
adjustment: i32,
gen_padding_subcommand_args! {
ContainerPadding,
WorkspacePadding
}
#[derive(Clap)]
struct ApplicationTarget {
#[clap(arg_enum)]
identifier: ApplicationIdentifier,
/// Identifier as a string
id: String,
macro_rules! gen_padding_adjustment_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ ) => {
$(
#[derive(clap::Clap, derive_ahk::AhkFunction)]
pub struct $name {
#[clap(arg_enum)]
sizing: Sizing,
/// Pixels to adjust by as an integer
adjustment: i32,
}
)+
};
}
#[derive(Clap)]
gen_padding_adjustment_subcommand_args! {
AdjustContainerPadding,
AdjustWorkspacePadding
}
macro_rules! gen_application_target_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ ) => {
$(
#[derive(clap::Clap, derive_ahk::AhkFunction)]
pub struct $name {
#[clap(arg_enum)]
identifier: ApplicationIdentifier,
/// Identifier as a string
id: String,
}
)+
};
}
gen_application_target_subcommand_args! {
FloatRule,
ManageRule,
IdentifyTrayApplication
}
#[derive(Clap, AhkFunction)]
struct WorkspaceRule {
#[clap(arg_enum)]
identifier: ApplicationIdentifier,
@@ -187,7 +239,7 @@ struct Opts {
subcmd: SubCommand,
}
#[derive(Clap)]
#[derive(Clap, AhkLibrary)]
enum SubCommand {
/// Start komorebi.exe as a background process
Start,
@@ -220,6 +272,12 @@ enum SubCommand {
/// Move the focused window to the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
MoveToWorkspace(MoveToWorkspace),
/// Send the focused window to the specified monitor
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
SendToMonitor(SendToMonitor),
/// Send the focused window to the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
SendToWorkspace(SendToWorkspace),
/// Focus the specified monitor
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
FocusMonitor(FocusMonitor),
@@ -230,13 +288,15 @@ enum SubCommand {
NewWorkspace,
/// Adjust container padding on the focused workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
AdjustContainerPadding(PaddingAdjustment),
AdjustContainerPadding(AdjustContainerPadding),
/// Adjust workspace padding on the focused workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
AdjustWorkspacePadding(PaddingAdjustment),
AdjustWorkspacePadding(AdjustWorkspacePadding),
/// Set the layout on the focused workspace
ChangeLayout(SetLayout),
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
ChangeLayout(ChangeLayout),
/// Flip the layout on the focused workspace (BSP only)
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
FlipLayout(FlipLayout),
/// Promote the focused window to the top of the tree
Promote,
@@ -247,10 +307,10 @@ enum SubCommand {
EnsureWorkspaces(EnsureWorkspaces),
/// Set the container padding for the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
ContainerPadding(Padding),
ContainerPadding(ContainerPadding),
/// Set the workspace padding for the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkspacePadding(Padding),
WorkspacePadding(WorkspacePadding),
/// Set the layout for the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkspaceLayout(WorkspaceLayout),
@@ -278,23 +338,28 @@ enum SubCommand {
Unmanage,
/// Reload ~/komorebi.ahk (if it exists)
ReloadConfiguration,
/// Toggle the automatic reloading of ~/komorebi.ahk (if it exists)
/// Enable or disable watching of ~/komorebi.ahk (if it exists)
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
WatchConfiguration(WatchConfiguration),
/// Add a rule to always float the specified application
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
FloatRule(ApplicationTarget),
FloatRule(FloatRule),
/// Add a rule to always manage the specified application
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
ManageRule(ApplicationTarget),
ManageRule(ManageRule),
/// Add a rule to associate an application with a workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkspaceRule(WorkspaceRule),
/// Identify an application that closes to the system tray
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
IdentifyTrayApplication(ApplicationTarget),
IdentifyTrayApplication(IdentifyTrayApplication),
/// 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,
/// Generate a library of AutoHotKey helper functions
AhkLibrary,
}
pub fn send_message(bytes: &[u8]) -> Result<()> {
@@ -311,6 +376,30 @@ fn main() -> Result<()> {
let opts: Opts = Opts::parse();
match opts.subcmd {
SubCommand::AhkLibrary => {
let mut library = dirs::home_dir().context("there is no home directory")?;
library.push("komorebic.lib.ahk");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(library.clone())?;
file.write_all(SubCommand::generate_ahk_library().as_bytes())?;
println!(
"\nAHK helper library for komorebic written to {}",
library
.to_str()
.context("could not find the path to the generated ahk lib file")?
);
println!(
"\nYou can include the library at the top of your ~/komorebi.ahk config with this line:"
);
println!("\n#Include %A_ScriptDir%\\komorebic.lib.ahk");
}
SubCommand::Log => {
let mut color_log = std::env::temp_dir();
color_log.push("komorebi.log");
@@ -341,6 +430,12 @@ fn main() -> Result<()> {
SubCommand::MoveToWorkspace(arg) => {
send_message(&*SocketMessage::MoveContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::SendToMonitor(arg) => {
send_message(&*SocketMessage::SendContainerToMonitorNumber(arg.target).as_bytes()?)?;
}
SubCommand::SendToWorkspace(arg) => {
send_message(&*SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::ContainerPadding(arg) => {
send_message(
&*SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)
@@ -363,6 +458,9 @@ fn main() -> Result<()> {
&*SocketMessage::AdjustContainerPadding(arg.sizing, arg.adjustment).as_bytes()?,
)?;
}
SubCommand::ToggleFocusFollowsMouse => {
send_message(&*SocketMessage::ToggleFocusFollowsMouse.as_bytes()?)?;
}
SubCommand::ToggleTiling => {
send_message(&*SocketMessage::ToggleTiling.as_bytes()?)?;
}