Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
355bcb6877 feat(windows): expand api wrappers 2021-09-16 10:55:13 -07:00
25 changed files with 519 additions and 882 deletions

111
Cargo.lock generated
View File

@@ -73,9 +73,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.71"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
[[package]]
name = "cfg-if"
@@ -199,9 +199,9 @@ checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d"
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "crossbeam-channel"
@@ -249,9 +249,9 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.2.1"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1"
dependencies = [
"nix",
"winapi 0.3.9",
@@ -268,9 +268,9 @@ dependencies = [
[[package]]
name = "dirs"
version = "4.0.0"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
@@ -425,9 +425,9 @@ dependencies = [
[[package]]
name = "hotwatch"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39301670a6f5798b75f36a1b149a379a50df5aa7c71be50f4b41ec6eab445cb8"
checksum = "d61ee702e77f237b41761361a82e5c4bf6277dbb4bc8b6b7d745cb249cc82b31"
dependencies = [
"log",
"notify",
@@ -471,9 +471,9 @@ dependencies = [
[[package]]
name = "instant"
version = "0.1.11"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
dependencies = [
"cfg-if 1.0.0",
]
@@ -505,7 +505,7 @@ dependencies = [
[[package]]
name = "komorebi"
version = "0.1.6"
version = "0.1.3"
dependencies = [
"bindings",
"bitflags",
@@ -537,7 +537,7 @@ dependencies = [
[[package]]
name = "komorebi-core"
version = "0.1.6"
version = "0.1.3"
dependencies = [
"bindings",
"clap",
@@ -549,7 +549,7 @@ dependencies = [
[[package]]
name = "komorebic"
version = "0.1.6"
version = "0.1.3"
dependencies = [
"bindings",
"clap",
@@ -580,9 +580,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.103"
version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "lock_api"
@@ -701,9 +701,9 @@ dependencies = [
[[package]]
name = "nix"
version = "0.23.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187"
dependencies = [
"bitflags",
"cc",
@@ -853,9 +853,9 @@ checksum = "36d62894f5590e88d99d0d82918742ba8e5bff1985af15d4906b6a65f635adb2"
[[package]]
name = "ppv-lite86"
version = "0.2.14"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro-error"
@@ -883,18 +883,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.30"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.10"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
@@ -1113,24 +1113,24 @@ dependencies = [
[[package]]
name = "sharded-slab"
version = "0.1.4"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
checksum = "740223c51853f3145fe7c90360d2d4232f2b62e3449489c207eccde818979982"
dependencies = [
"lazy_static",
]
[[package]]
name = "slab"
version = "0.4.5"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[package]]
name = "smallvec"
version = "1.7.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "strsim"
@@ -1161,9 +1161,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.80"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
dependencies = [
"proc-macro2",
"quote",
@@ -1172,9 +1172,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.20.5"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e223c65cd36b485a34c2ce6e38efa40777d31c4166d9076030c74cdcf971679f"
checksum = "92d77883450d697c0010e60db3d940ed130b0ed81d27485edee981621b434e52"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
@@ -1245,9 +1245,9 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.29"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@@ -1268,9 +1268,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.18"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77"
dependencies = [
"proc-macro2",
"quote",
@@ -1279,9 +1279,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.21"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf"
dependencies = [
"lazy_static",
]
@@ -1319,9 +1319,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.2.25"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
checksum = "62af966210b88ad5776ee3ba12d5f35b8d6a2b2a12168f3080cf02b814d7376b"
dependencies = [
"ansi_term",
"chrono",
@@ -1357,9 +1357,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.9"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
@@ -1452,21 +1452,20 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.21.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f5f8d2ea79bf690bbee453fd4a1516ae426e5d5c7215d96cc0c3dc134fc4a0"
checksum = "ef84dd25f4c69a271b1bba394532bf400523b43169de21dfc715e8f8e491053d"
dependencies = [
"const-sha1",
"windows_gen",
"windows_macros",
"windows_reader",
]
[[package]]
name = "windows_gen"
version = "0.21.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e6994f42f8481387778cc608407d6703410672d57f32a66009419d7a18aa912"
checksum = "ac7bb21b8ff5e801232b72a6ff554b4cc0cef9ed9238188c3ca78fe3968a7e5d"
dependencies = [
"windows_quote",
"windows_reader",
@@ -1474,9 +1473,9 @@ dependencies = [
[[package]]
name = "windows_macros"
version = "0.21.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cc2357b1b03c19f056cb0e6d06011f80f54beadb4e36aee2ca98493c7cfc3c"
checksum = "5566b8c51118769e4a9094a688bf1233a3f36aacbfc78f3b15817fe0b6e0442f"
dependencies = [
"syn",
"windows_gen",
@@ -1486,15 +1485,15 @@ dependencies = [
[[package]]
name = "windows_quote"
version = "0.21.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf987b5288c15e1997226848f78f3ed3ef8b78dcfd71a201c8c8684163a7e4d"
checksum = "4af8236a9493c38855f95cdd11b38b342512a5df4ee7473cffa828b5ebb0e39c"
[[package]]
name = "windows_reader"
version = "0.21.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237b53e8b40766ea7db5da0d8c6c1442d21d0429f0ee7500d7b5688967bd9d7b"
checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab"
[[package]]
name = "winput"

View File

@@ -18,10 +18,6 @@ Translations of this document can be found in the project wiki:
- [komorebi 中文用户指南](https://github.com/LGUG2Z/komorebi/wiki/README-zh) (by [@crosstyan](https://github.com/crosstyan))
There is a [Discord server](https://discord.gg/vzBmPm6RkQ) available for _komorebi_-related discussion, help,
troubleshooting etc. If you have any specific feature requests or bugs to report, please create an issue in this
repository.
## Description
_komorebi_ only responds to [WinEvents](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) and the
@@ -188,26 +184,6 @@ passing it as an argument to the `--implementation` flag:
komorebic.exe toggle-focus-follows-mouse --implementation komorebi
```
#### Saving and Loading Resized Layouts
If you create a BSP layout through various resize adjustments that you want to be able to restore easily in the future,
it is possible to "quicksave" that layout to the system's temporary folder and load it later in the same session, or
alternatively, you may save it to a specific file to be loaded again at any point in the future.
```powershell
komorebic.exe quick-save # saves the focused workspace to $Env:TEMP\komorebi.quicksave.json
komorebic.exe quick-load # loads $Env:TEMP\komorebi.quicksave.json on the focused workspace
komorebic.exe save ~/layouts/primary.json # saves the focused workspace to $Env:USERPROFILE\layouts\primary.json
komorebic.exe load ~/layouts/secondary.json # loads $Env:USERPROFILE\layouts\secondary.json on the focused workspace
```
These layouts can be applied to arbitrary collections of windows on any workspace, as they only track the layout
dimensions and are not coupled to the applications that were running at the time of saving.
When layouts that expect more or less windows than the number currently on the focused workspace are loaded, `komorebi`
will automatically reconcile the difference.
## Configuration with `komorebic`
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
@@ -224,14 +200,8 @@ stop Stop the komorebi.exe process and restore all hidd
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)
quick-save Quicksave the current resize layout dimensions
quick-load Load the last quicksaved resize layout dimensions
save Save the current resize layout dimensions to a file
load Load the resize layout dimensions from a file
focus Change focus to the window in the specified direction
move Move the focused window in the specified direction
cycle-focus Change focus to the window in the specified cycle direction
cycle-move Move the focused window in the specified cycle direction
stack Stack the focused window in the specified direction
resize Resize the focused window in the specified direction
unstack Unstack the focused window
@@ -242,11 +212,8 @@ send-to-monitor Send the focused window to the specified monitor
send-to-workspace Send the focused window to the specified workspace
focus-monitor Focus the specified monitor
focus-workspace Focus the specified workspace on the focused monitor
cycle-monitor Focus the monitor in the given cycle direction
cycle-workspace Focus the workspace in the given cycle direction
new-workspace Create and append a new workspace on the focused monitor
invisible-borders Set the invisible border dimensions around each window
work-area-offset Set offsets to exclude parts of the work area from tiling
adjust-container-padding Adjust container padding on the focused workspace
adjust-workspace-padding Adjust workspace padding on the focused workspace
change-layout Set the layout on the focused workspace
@@ -305,24 +272,17 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Mouse follows focused container
- [x] Resize window container in direction
- [ ] Resize child window containers by split ratio
- [x] Quicksave and quickload layouts with resize dimensions
- [x] Save and load layouts with resize dimensions to/from specific files
- [x] Mouse drag to swap window container position
- [x] Mouse drag to resize window container
- [x] Configurable workspace and container gaps
- [x] BSP tree layout (`bsp`)
- [x] BSP tree layout
- [x] Flip BSP tree layout horizontally or vertically
- [x] Equal-width, max-height column layout (`columns`)
- [x] Equal-height, max-width row layout (`rows`)
- [x] Main half-height window with vertical stack layout (`horizontal-stack`)
- [x] Main half-width window with horizontal stack layout (`vertical-stack`)
- [x] 2x Main window (half and quarter-width) with horizontal stack layout (`ultrawide-vertical-stack`)
- [x] Equal-width, max-height column layout
- [x] Floating rules based on exe name, window title and class
- [x] Workspace rules based on exe name and window class
- [x] Additional manage rules based on exe name and window class
- [x] Identify applications which overflow their borders by exe name and class
- [x] Identify 'close/minimize to tray' applications by exe name and class
- [x] Configure work area offsets to preserve space for custom taskbars
- [x] Configure and compensate for the size of Windows 10's invisible borders
- [x] Toggle floating windows
- [x] Toggle monocle window

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.21"
windows = "0.19"
[build-dependencies]
windows = "0.21"
windows = "0.19"

View File

@@ -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

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
use std::num::NonZeroUsize;
use clap::ArgEnum;
use serde::Deserialize;
use serde::Serialize;
@@ -15,17 +13,17 @@ pub enum CycleDirection {
impl CycleDirection {
#[must_use]
pub const fn next_idx(&self, idx: usize, len: NonZeroUsize) -> usize {
pub const fn next_idx(&self, idx: usize, len: usize) -> usize {
match self {
Self::Previous => {
CycleDirection::Previous => {
if idx == 0 {
len.get() - 1
len - 1
} else {
idx - 1
}
}
Self::Next => {
if idx == len.get() - 1 {
CycleDirection::Next => {
if idx == len - 1 {
0
} else {
idx + 1

View File

@@ -16,9 +16,6 @@ pub enum Layout {
BSP,
Columns,
Rows,
VerticalStack,
HorizontalStack,
UltrawideVerticalStack,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
@@ -134,11 +131,7 @@ impl Layout {
}
#[must_use]
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::too_many_lines
)]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn calculate(
&self,
area: &Rect,
@@ -192,176 +185,6 @@ impl Layout {
layouts
}
Layout::VerticalStack => {
let mut layouts: Vec<Rect> = vec![];
layouts.resize(len, Rect::default());
let primary_right = match len {
1 => area.right,
_ => area.right / 2,
};
let mut main_left = area.left;
let mut stack_left = area.left + primary_right;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
main_left = main_left + area.right - primary_right;
stack_left = area.left;
}
_ => {}
}
let mut iter = layouts.iter_mut();
{
if let Some(first) = iter.next() {
first.left = main_left;
first.top = area.top;
first.right = primary_right;
first.bottom = area.bottom;
}
}
let bottom = area.bottom / (len - 1) as i32;
let mut top = 0;
for next in iter {
next.left = stack_left;
next.top = area.top + top;
next.right = area.right - primary_right;
next.bottom = bottom;
top += bottom;
}
layouts
}
Layout::HorizontalStack => {
let mut layouts: Vec<Rect> = vec![];
layouts.resize(len, Rect::default());
let bottom = match len {
1 => area.bottom,
_ => area.bottom / 2,
};
let mut main_top = area.top;
let mut stack_top = area.top + bottom;
match layout_flip {
Some(Flip::Vertical | Flip::HorizontalAndVertical) if len > 1 => {
main_top = main_top + area.bottom - bottom;
stack_top = area.top;
}
_ => {}
}
let mut iter = layouts.iter_mut();
{
if let Some(first) = iter.next() {
first.left = area.left;
first.top = main_top;
first.right = area.right;
first.bottom = bottom;
}
}
let right = area.right / (len - 1) as i32;
let mut left = 0;
for next in iter {
next.left = area.left + left;
next.top = stack_top;
next.right = right;
next.bottom = area.bottom - bottom;
left += right;
}
layouts
}
Layout::UltrawideVerticalStack => {
let mut layouts: Vec<Rect> = vec![];
layouts.resize(len, Rect::default());
let primary_right = match len {
1 => area.right,
_ => area.right / 2,
};
let secondary_right = match len {
1 => 0,
2 => area.right - primary_right,
_ => (area.right - primary_right) / 2,
};
let (primary_left, secondary_left, stack_left) = match len {
1 => (area.left, 0, 0),
2 => {
let mut primary = area.left + secondary_right;
let mut secondary = area.left;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
primary = area.left;
secondary = area.left + primary_right;
}
_ => {}
}
(primary, secondary, 0)
}
_ => {
let primary = area.left + secondary_right;
let mut secondary = area.left;
let mut stack = area.left + primary_right + secondary_right;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
secondary = area.left + primary_right + secondary_right;
stack = area.left;
}
_ => {}
}
(primary, secondary, stack)
}
};
let mut iter = layouts.iter_mut();
{
if let Some(first) = iter.next() {
first.left = primary_left;
first.top = area.top;
first.right = primary_right;
first.bottom = area.bottom;
}
}
{
if let Some(second) = iter.next() {
second.left = secondary_left;
second.top = area.top;
second.right = secondary_right;
second.bottom = area.bottom;
}
}
if len > 2 {
let height = area.bottom / (len - 2) as i32;
let mut y = 0;
for next in iter {
next.left = stack_left;
next.top = area.top + y;
next.right = secondary_right;
next.bottom = height;
y += height;
}
}
layouts
}
};
dimensions

View File

@@ -1,7 +1,6 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
use std::path::PathBuf;
use std::str::FromStr;
use clap::ArgEnum;
@@ -27,8 +26,6 @@ pub enum SocketMessage {
// Window / Container Commands
FocusWindow(OperationDirection),
MoveWindow(OperationDirection),
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
ResizeWindow(OperationDirection, Sizing),
UnstackWindow,
@@ -55,12 +52,6 @@ pub enum SocketMessage {
Stop,
TogglePause,
Retile,
QuickSave,
QuickLoad,
Save(PathBuf),
Load(PathBuf),
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusWorkspaceNumber(usize),
ContainerPadding(usize, usize, i32),
@@ -72,7 +63,6 @@ pub enum SocketMessage {
ReloadConfiguration,
WatchConfiguration(bool),
InvisibleBorders(Rect),
WorkAreaOffset(Rect),
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
FloatRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),

View File

@@ -57,39 +57,25 @@ impl OperationDirection {
len: usize,
) -> bool {
match Self::flip_direction(self, layout_flip) {
Self::Up => match layout {
OperationDirection::Up => match layout {
Layout::BSP => len > 2 && idx != 0 && idx != 1,
Layout::Columns => false,
Layout::Rows | Layout::HorizontalStack => idx != 0,
Layout::VerticalStack => idx != 0 && idx != 1,
Layout::UltrawideVerticalStack => idx > 2,
Layout::Rows => idx != 0,
},
Self::Down => match layout {
OperationDirection::Down => match layout {
Layout::BSP => len > 2 && idx != len - 1 && idx % 2 != 0,
Layout::Columns => false,
Layout::Rows => idx != len - 1,
Layout::VerticalStack => idx != 0 && idx != len - 1,
Layout::HorizontalStack => idx == 0,
Layout::UltrawideVerticalStack => idx > 1 && idx != len - 1,
},
Self::Left => match layout {
OperationDirection::Left => match layout {
Layout::BSP => len > 1 && idx != 0,
Layout::Columns | Layout::VerticalStack => idx != 0,
Layout::Columns => idx != 0,
Layout::Rows => false,
Layout::HorizontalStack => idx != 0 && idx != 1,
Layout::UltrawideVerticalStack => len > 1 && idx != 1,
},
Self::Right => match layout {
OperationDirection::Right => match layout {
Layout::BSP => len > 1 && idx % 2 == 0 && idx != len - 1,
Layout::Columns => idx != len - 1,
Layout::Rows => false,
Layout::VerticalStack => idx == 0,
Layout::HorizontalStack => idx != 0 && idx != len - 1,
Layout::UltrawideVerticalStack => match len {
0 | 1 => false,
2 => idx != 0,
_ => idx < 2,
},
},
}
}
@@ -106,16 +92,11 @@ impl OperationDirection {
}
}
Layout::Columns => unreachable!(),
Layout::Rows | Layout::VerticalStack | Layout::UltrawideVerticalStack => idx - 1,
Layout::HorizontalStack => 0,
Layout::Rows => idx - 1,
},
Self::Down => match layout {
Layout::BSP
| Layout::Rows
| Layout::VerticalStack
| Layout::UltrawideVerticalStack => idx + 1,
Layout::BSP | Layout::Rows => idx + 1,
Layout::Columns => unreachable!(),
Layout::HorizontalStack => 1,
},
Self::Left => match layout {
Layout::BSP => {
@@ -125,24 +106,12 @@ impl OperationDirection {
idx - 1
}
}
Layout::Columns | Layout::HorizontalStack => idx - 1,
Layout::Columns => idx - 1,
Layout::Rows => unreachable!(),
Layout::VerticalStack => 0,
Layout::UltrawideVerticalStack => match idx {
0 => 1,
1 => unreachable!(),
_ => 0,
},
},
Self::Right => match layout {
Layout::BSP | Layout::Columns | Layout::HorizontalStack => idx + 1,
Layout::BSP | Layout::Columns => idx + 1,
Layout::Rows => unreachable!(),
Layout::VerticalStack => 1,
Layout::UltrawideVerticalStack => match idx {
1 => 0,
0 => 2,
_ => unreachable!(),
},
},
}
}

View File

@@ -40,24 +40,16 @@ FloatRule("exe", "Wally.exe")
FloatRule("exe", "wincompose.exe")
FloatRule("exe", "1Password.exe")
FloatRule("exe", "Wox.exe")
FloatRule("exe", "ddm.exe")
FloatRule("class", "Chrome_RenderWidgetHostHWND") ; GOG Electron invisible overlay
FloatRule("class", "CEFCLIENT")
; Identify Minimize-to-Tray Applications
IdentifyTrayApplication("exe", "Discord.exe")
IdentifyTrayApplication("exe", "Spotify.exe")
IdentifyTrayApplication("exe", "GalaxyClient.exe")
; Identify Electron applications with overflowing borders
IdentifyBorderOverflow("exe", "Discord.exe")
IdentifyBorderOverflow("exe", "Spotify.exe")
IdentifyBorderOverflow("exe", "GalaxyClient.exe")
IdentifyBorderOverflow("class", "ZPFTEWndClass")
; Identify applications to be forcibly managed
ManageRule("exe", "GalaxyClient.exe")
; Change the focused window, Alt + Vim direction keys
!h::
Focus("left")

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.6"
version = "0.1.3"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -20,7 +20,7 @@ color-eyre = "0.5"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = "3"
dirs = "4"
dirs = "3"
getset = "0.1"
hotwatch = "0.4"
lazy_static = "1"

View File

@@ -19,9 +19,8 @@ use crate::workspace::Workspace;
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
id: isize,
#[getset(get = "pub", set = "pub")]
size: Rect,
#[getset(get = "pub", set = "pub")]
monitor_size: Rect,
#[getset(get = "pub")]
work_area_size: Rect,
workspaces: Ring<Workspace>,
#[serde(skip_serializing)]
@@ -31,13 +30,13 @@ pub struct Monitor {
impl_ring_elements!(Monitor, Workspace);
pub fn new(id: isize, size: Rect, work_area_size: Rect) -> Monitor {
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,
size,
monitor_size,
work_area_size,
workspaces,
workspace_names: HashMap::default(),
@@ -146,16 +145,12 @@ impl Monitor {
self.workspaces().len()
}
pub fn update_focused_workspace(
&mut self,
offset: Option<Rect>,
invisible_borders: &Rect,
) -> 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, offset, invisible_borders)?;
.update(&work_area, invisible_borders)?;
Ok(())
}

View File

@@ -1,9 +1,6 @@
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -15,7 +12,6 @@ use parking_lot::Mutex;
use uds_windows::UnixStream;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Rect;
use komorebi_core::SocketMessage;
use komorebi_core::StateQuery;
@@ -67,12 +63,6 @@ impl WindowManager {
SocketMessage::MoveWindow(direction) => {
self.move_container_in_direction(direction)?;
}
SocketMessage::CycleFocusWindow(direction) => {
self.focus_container_in_cycle_direction(direction)?;
}
SocketMessage::CycleMoveWindow(direction) => {
self.move_container_in_cycle_direction(direction)?;
}
SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?,
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
SocketMessage::CycleStack(direction) => {
@@ -137,16 +127,6 @@ impl WindowManager {
SocketMessage::ToggleTiling => {
self.toggle_tiling()?;
}
SocketMessage::CycleFocusMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(true)?;
}
SocketMessage::FocusMonitorNumber(monitor_idx) => {
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(true)?;
@@ -160,31 +140,6 @@ impl WindowManager {
SocketMessage::WorkspaceLayout(monitor_idx, workspace_idx, layout) => {
self.set_workspace_layout(monitor_idx, workspace_idx, layout)?;
}
SocketMessage::CycleFocusWorkspace(direction) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// 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)?;
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.focus_workspace(workspace_idx)?;
}
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
@@ -371,66 +326,6 @@ impl WindowManager {
self.invisible_borders = rect;
self.retile_all()?;
}
SocketMessage::WorkAreaOffset(rect) => {
self.work_area_offset = Option::from(rect);
self.retile_all()?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
let resize = workspace.resize_dimensions();
let mut quicksave_json = std::env::temp_dir();
quicksave_json.push("komorebi.quicksave.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(quicksave_json)?;
serde_json::to_writer_pretty(&file, &resize)?;
}
SocketMessage::QuickLoad => {
let workspace = self.focused_workspace_mut()?;
let mut quicksave_json = std::env::temp_dir();
quicksave_json.push("komorebi.quicksave.json");
let file = File::open(&quicksave_json).map_err(|_| {
anyhow!(
"no quicksave found at {}",
quicksave_json.display().to_string()
)
})?;
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::Save(path) => {
let workspace = self.focused_workspace_mut()?;
let resize = workspace.resize_dimensions();
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
serde_json::to_writer_pretty(&file, &resize)?;
}
SocketMessage::Load(path) => {
let workspace = self.focused_workspace_mut()?;
let file = File::open(&path)
.map_err(|_| anyhow!("no file found at {}", path.display().to_string()))?;
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
};
tracing::info!("processed");

View File

@@ -51,8 +51,7 @@ impl WindowManager {
// Make sure we have the most recently focused monitor from any event
match event {
WindowManagerEvent::MonitorPoll(_, window)
| WindowManagerEvent::FocusChange(_, window)
WindowManagerEvent::FocusChange(_, window)
| WindowManagerEvent::Show(_, window)
| WindowManagerEvent::MoveResizeEnd(_, window) => {
self.reconcile_monitors()?;
@@ -66,14 +65,13 @@ impl WindowManager {
}
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
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, offset, &invisible_borders)?;
workspace.update(&work_area, &invisible_borders)?;
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,
@@ -286,7 +284,7 @@ impl WindowManager {
self.update_focused_workspace(false)?;
}
}
WindowManagerEvent::MonitorPoll(..) | WindowManagerEvent::MouseCapture(..) => {}
WindowManagerEvent::MouseCapture(..) => {}
};
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows

View File

@@ -231,10 +231,6 @@ impl Window {
#[tracing::instrument(fields(exe, title))]
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
if let Some(WindowManagerEvent::MonitorPoll(_, _)) = event {
return Ok(true);
}
if self.title().is_err() {
return Ok(false);
}

View File

@@ -6,6 +6,7 @@ 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;
use hotwatch::notify::DebouncedEvent;
@@ -45,7 +46,6 @@ pub struct WindowManager {
pub command_listener: UnixListener,
pub is_paused: bool,
pub invisible_borders: Rect,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub hotwatch: Hotwatch,
pub virtual_desktop_id: Option<usize>,
@@ -57,7 +57,6 @@ pub struct State {
pub monitors: Ring<Monitor>,
pub is_paused: bool,
pub invisible_borders: Rect,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub has_pending_raise_op: bool,
pub float_identifiers: Vec<String>,
@@ -74,7 +73,6 @@ impl From<&mut WindowManager> for State {
monitors: wm.monitors.clone(),
is_paused: wm.is_paused,
invisible_borders: wm.invisible_borders,
work_area_offset: wm.work_area_offset,
focus_follows_mouse: wm.focus_follows_mouse.clone(),
has_pending_raise_op: wm.has_pending_raise_op,
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
@@ -146,7 +144,6 @@ impl WindowManager {
right: 14,
bottom: 7,
},
work_area_offset: None,
focus_follows_mouse: None,
hotwatch: Hotwatch::new()?,
virtual_desktop_id,
@@ -268,40 +265,6 @@ impl WindowManager {
// Remove any invalid monitors from our state
self.monitors_mut().retain(|m| !invalid.contains(&m.id()));
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
for monitor in self.monitors_mut() {
let mut should_update = false;
let reference = WindowsApi::monitor(monitor.id())?;
// TODO: If this is different, force a redraw
if reference.work_area_size() != monitor.work_area_size() {
monitor.set_work_area_size(Rect {
left: reference.work_area_size().left,
top: reference.work_area_size().top,
right: reference.work_area_size().right,
bottom: reference.work_area_size().bottom,
});
should_update = true;
}
if reference.size() != monitor.size() {
monitor.set_size(Rect {
left: reference.size().left,
top: reference.size().top,
right: reference.size().right,
bottom: reference.size().bottom,
});
should_update = true;
}
if should_update {
monitor.update_focused_workspace(offset, &invisible_borders)?;
}
}
// Check for and add any new monitors that may have been plugged in
WindowsApi::load_monitor_information(&mut self.monitors)?;
@@ -428,8 +391,6 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn retile_all(&mut self) -> Result<()> {
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
for monitor in self.monitors_mut() {
let work_area = *monitor.work_area_size();
let workspace = monitor
@@ -441,7 +402,7 @@ impl WindowManager {
*resize = None;
}
workspace.update(&work_area, offset, &invisible_borders)?;
workspace.update(&work_area, &invisible_borders)?;
}
Ok(())
@@ -541,11 +502,10 @@ impl WindowManager {
tracing::info!("updating");
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
self.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?
.update_focused_workspace(offset, &invisible_borders)?;
.update_focused_workspace(&invisible_borders)?;
if mouse_follows_focus {
if let Some(window) = self.focused_workspace()?.maximized_window() {
@@ -598,9 +558,9 @@ impl WindowManager {
) {
let unaltered = workspace.layout().calculate(
&work_area,
NonZeroUsize::new(len).ok_or_else(|| {
anyhow!("there must be at least one container to calculate a workspace layout")
})?,
NonZeroUsize::new(len).context(
"there must be at least one container to calculate a workspace layout",
)?,
workspace.container_padding(),
workspace.layout_flip(),
&[],
@@ -668,7 +628,6 @@ impl WindowManager {
tracing::info!("moving container");
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
let monitor = self
.focused_monitor_mut()
@@ -694,7 +653,7 @@ impl WindowManager {
target_monitor.add_container(container)?;
target_monitor.load_focused_workspace()?;
target_monitor.update_focused_workspace(offset, &invisible_borders)?;
target_monitor.update_focused_workspace(&invisible_borders)?;
if follow {
self.focus_monitor(idx)?;
@@ -748,52 +707,18 @@ impl WindowManager {
self.update_focused_workspace(true)
}
#[tracing::instrument(skip(self))]
pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("focusing container");
let workspace = self.focused_workspace_mut()?;
let new_idx = workspace
.new_idx_for_cycle_direction(direction)
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
workspace.focus_container(new_idx);
self.focused_window_mut()?.focus()?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn move_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("moving container");
let workspace = self.focused_workspace_mut()?;
let current_idx = workspace.focused_container_idx();
let new_idx = workspace
.new_idx_for_cycle_direction(direction)
.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);
self.update_focused_workspace(true)
}
#[tracing::instrument(skip(self))]
pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("cycling container windows");
let container = self.focused_container_mut()?;
let len = NonZeroUsize::new(container.windows().len())
.ok_or_else(|| anyhow!("there must be at least one window in a container"))?;
if len.get() == 1 {
if container.windows().len() == 1 {
return Err(anyhow!("there is only one window in this container"));
}
let current_idx = container.focused_window_idx();
let next_idx = direction.next_idx(current_idx, len);
let next_idx = direction.next_idx(current_idx, container.windows().len());
container.focus_window(next_idx);
container.load_focused_window();
@@ -1084,7 +1009,6 @@ impl WindowManager {
tracing::info!("setting workspace layout");
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
@@ -1104,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, offset, &invisible_borders)?;
workspace.update(&work_area, &invisible_borders)?;
Ok(())
} else {
Ok(self.update_focused_workspace(false)?)

View File

@@ -17,7 +17,6 @@ pub enum WindowManagerEvent {
Manage(Window),
Unmanage(Window),
Raise(Window),
MonitorPoll(WinEvent, Window),
}
impl Display for WindowManagerEvent {
@@ -65,13 +64,6 @@ impl Display for WindowManagerEvent {
WindowManagerEvent::Raise(window) => {
write!(f, "Raise (Window: {})", window)
}
WindowManagerEvent::MonitorPoll(winevent, window) => {
write!(
f,
"MonitorPoll (WinEvent: {}, Window: {})",
winevent, window
)
}
}
}
}
@@ -86,7 +78,6 @@ impl WindowManagerEvent {
| WindowManagerEvent::Show(_, window)
| WindowManagerEvent::MoveResizeEnd(_, window)
| WindowManagerEvent::MouseCapture(_, window)
| WindowManagerEvent::MonitorPoll(_, window)
| WindowManagerEvent::Raise(window)
| WindowManagerEvent::Manage(window)
| WindowManagerEvent::Unmanage(window) => window,
@@ -130,17 +121,6 @@ impl WindowManagerEvent {
None
}
}
WinEvent::ObjectCreate => {
if let Ok(title) = window.title() {
// Hidden COM support mechanism window that fires this event on both DPI/scaling
// changes and resolution changes, a good candidate for polling
if title == "OLEChannelWnd" {
return Option::from(Self::MonitorPoll(winevent, window));
}
}
None
}
_ => None,
}
}

View File

@@ -7,10 +7,11 @@ use color_eyre::eyre::anyhow;
use color_eyre::eyre::Error;
use color_eyre::Result;
use bindings::Handle;
use bindings::Result as WindowsCrateResult;
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;
@@ -27,11 +28,13 @@ 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;
@@ -40,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;
@@ -56,16 +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;
@@ -78,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;
@@ -89,11 +117,66 @@ use crate::ring::Ring;
use crate::set_window_position::SetWindowPosition;
use crate::windows_callbacks;
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),
Ok(T),
}
impl From<BOOL> for WindowsResult<(), Error> {
fn from(return_value: BOOL) -> Self {
if return_value.as_bool() {
Self::Ok(())
} else {
Self::Err(std::io::Error::last_os_error().into())
}
}
}
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() {
Self::Err(std::io::Error::last_os_error().into())
} else {
Self::Ok(return_value.0)
}
}
}
impl From<HANDLE> for WindowsResult<HANDLE, Error> {
fn from(return_value: HANDLE) -> Self {
if return_value.is_null() {
Self::Err(std::io::Error::last_os_error().into())
} else {
Self::Ok(return_value)
}
}
}
macro_rules! impl_from_integer_for_windows_result {
( $( $integer_type:ty ),+ ) => {
$(
@@ -109,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 {
@@ -120,40 +203,6 @@ impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
}
}
pub trait ProcessWindowsCrateResult<T> {
fn process(self) -> Result<T>;
}
macro_rules! impl_process_windows_crate_result {
( $($input:ty => $deref:ty),+ $(,)? ) => (
paste::paste! {
$(
impl ProcessWindowsCrateResult<$deref> for WindowsCrateResult<$input> {
fn process(self) -> Result<$deref> {
match self {
Ok(value) => Ok(value.0),
Err(error) => Err(error.into()),
}
}
}
)+
}
);
}
impl_process_windows_crate_result!(
HWND => isize,
);
impl<T> ProcessWindowsCrateResult<T> for WindowsCrateResult<T> {
fn process(self) -> Result<T> {
match self {
Ok(value) => Ok(value),
Err(error) => Err(error.into()),
}
}
}
pub struct WindowsApi;
impl WindowsApi {
@@ -161,16 +210,54 @@ impl WindowsApi {
callback: MONITORENUMPROC,
callback_data_address: isize,
) -> Result<()> {
unsafe {
Result::from(WindowsResult::from(unsafe {
EnumDisplayMonitors(
HDC(0),
std::ptr::null_mut(),
Option::from(callback),
LPARAM(callback_data_address),
)
}))
}
#[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);
}
}
}
.ok()
.process()
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>> {
@@ -192,9 +279,9 @@ impl WindowsApi {
}
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
unsafe { EnumWindows(Option::from(callback), LPARAM(callback_data_address)) }
.ok()
.process()
Result::from(WindowsResult::from(unsafe {
EnumWindows(Option::from(callback), LPARAM(callback_data_address))
}))
}
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
@@ -233,9 +320,9 @@ impl WindowsApi {
}
pub fn allow_set_foreground_window(process_id: u32) -> Result<()> {
unsafe { AllowSetForegroundWindow(process_id) }
.ok()
.process()
Result::from(WindowsResult::from(unsafe {
AllowSetForegroundWindow(process_id)
}))
}
pub fn monitor_from_window(hwnd: HWND) -> isize {
@@ -258,7 +345,7 @@ impl WindowsApi {
}
pub fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
unsafe {
Result::from(WindowsResult::from(unsafe {
SetWindowPos(
hwnd,
position,
@@ -268,9 +355,7 @@ impl WindowsApi {
layout.bottom,
SET_WINDOW_POS_FLAGS(flags),
)
}
.ok()
.process()
}))
}
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
@@ -292,25 +377,39 @@ impl WindowsApi {
}
pub fn foreground_window() -> Result<isize> {
unsafe { GetForegroundWindow() }.ok().process()
Result::from(WindowsResult::from(unsafe { GetForegroundWindow() }))
}
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
unsafe { SetForegroundWindow(hwnd) }.ok().process()
match WindowsResult::from(unsafe { SetForegroundWindow(hwnd) }) {
WindowsResult::Ok(_) => Ok(()),
WindowsResult::Err(error) => {
// TODO: Figure out the odd behaviour here, docs state that a zero value means
// TODO: that the window was not brought to the foreground, but this contradicts
// TODO: the behaviour that I have observed which resulted in this check
if error.to_string() == "The operation completed successfully. (os error 0)" {
Ok(())
} else {
Err(error)
}
}
}
}
#[allow(dead_code)]
pub fn top_window() -> Result<isize> {
unsafe { GetTopWindow(HWND::default()) }.ok().process()
Result::from(WindowsResult::from(unsafe { GetTopWindow(HWND::NULL).0 }))
}
pub fn desktop_window() -> Result<isize> {
unsafe { GetDesktopWindow() }.ok().process()
Result::from(WindowsResult::from(unsafe { GetDesktopWindow() }))
}
#[allow(dead_code)]
pub fn next_window(hwnd: HWND) -> Result<isize> {
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.ok().process()
Result::from(WindowsResult::from(unsafe {
GetWindow(hwnd, GW_HWNDNEXT).0
}))
}
#[allow(dead_code)]
@@ -331,24 +430,30 @@ impl WindowsApi {
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
let mut rect = unsafe { std::mem::zeroed() };
unsafe { GetWindowRect(hwnd, &mut rect) }.ok().process()?;
Result::from(WindowsResult::from(unsafe {
GetWindowRect(hwnd, &mut rect)
}))?;
Ok(Rect::from(rect))
}
fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
unsafe { SetCursorPos(x, y) }.ok().process()
Result::from(WindowsResult::from(unsafe { SetCursorPos(x, y) }))
}
pub fn cursor_pos() -> Result<POINT> {
let mut cursor_pos = POINT::default();
unsafe { GetCursorPos(&mut cursor_pos) }.ok().process()?;
let mut cursor_pos: POINT = unsafe { std::mem::zeroed() };
Result::from(WindowsResult::from(unsafe {
GetCursorPos(&mut cursor_pos)
}))?;
Ok(cursor_pos)
}
pub fn window_from_point(point: POINT) -> Result<isize> {
unsafe { WindowFromPoint(point) }.ok().process()
Result::from(WindowsResult::from(unsafe { WindowFromPoint(point) }))
}
pub fn window_at_cursor_pos() -> Result<isize> {
@@ -378,13 +483,24 @@ impl WindowsApi {
}
pub fn attach_thread_input(thread_id: u32, target_thread_id: u32, attach: bool) -> Result<()> {
unsafe { AttachThreadInput(thread_id, target_thread_id, attach) }
.ok()
.process()
Result::from(WindowsResult::from(unsafe {
AttachThreadInput(thread_id, target_thread_id, attach)
}))
}
pub fn set_focus(hwnd: HWND) -> Result<()> {
unsafe { SetFocus(hwnd) }.ok().map(|_| ()).process()
match WindowsResult::from(unsafe { SetFocus(hwnd) }) {
WindowsResult::Ok(_) => Ok(()),
WindowsResult::Err(error) => {
// If the window is not attached to the calling thread's message queue, the return value is NULL
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setfocus
if error.to_string() == "The operation completed successfully. (os error 0)" {
Ok(())
} else {
Err(error)
}
}
}
}
#[allow(dead_code)]
@@ -436,9 +552,9 @@ impl WindowsApi {
inherit_handle: bool,
process_id: u32,
) -> Result<HANDLE> {
unsafe { OpenProcess(access_rights, inherit_handle, process_id) }
.ok()
.process()
Result::from(WindowsResult::from(unsafe {
OpenProcess(access_rights, inherit_handle, process_id)
}))
}
pub fn process_handle(process_id: u32) -> Result<HANDLE> {
@@ -450,16 +566,14 @@ impl WindowsApi {
let mut path: Vec<u16> = vec![0; len as usize];
let text_ptr = path.as_mut_ptr();
unsafe {
Result::from(WindowsResult::from(unsafe {
QueryFullProcessImageNameW(
handle,
PROCESS_NAME_FORMAT(0),
PWSTR(text_ptr),
&mut len as *mut u32,
)
}
.ok()
.process()?;
}))?;
Ok(String::from_utf16(&path[..len as usize])?)
}
@@ -534,18 +648,18 @@ impl WindowsApi {
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
unsafe { GetMonitorInfoW(hmonitor, (&mut monitor_info as *mut MONITORINFO).cast()) }
.ok()
.process()?;
Result::from(WindowsResult::from(unsafe {
GetMonitorInfoW(hmonitor, (&mut monitor_info as *mut MONITORINFO).cast())
}))?;
Ok(monitor_info)
}
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
let monitor_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
pub fn monitor(hmonitor: HMONITOR) -> Result<Monitor> {
let monitor_info = Self::monitor_info_w(hmonitor)?;
Ok(monitor::new(
hmonitor,
hmonitor.0,
monitor_info.rcMonitor.into(),
monitor_info.rcWork.into(),
))
@@ -558,9 +672,9 @@ impl WindowsApi {
pv_param: *mut c_void,
update_flags: SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
) -> Result<()> {
unsafe { SystemParametersInfoW(action, ui_param, pv_param, update_flags) }
.ok()
.process()
Result::from(WindowsResult::from(unsafe {
SystemParametersInfoW(action, ui_param, pv_param, update_flags)
}))
}
#[allow(dead_code)]
@@ -596,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)
}
}

View File

@@ -42,13 +42,20 @@ pub extern "system" fn enum_display_monitor(
}
}
if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
if let Ok(m) = WindowsApi::monitor(hmonitor) {
monitors.elements_mut().push_back(m);
}
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>) };

View File

@@ -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);

View File

@@ -2,6 +2,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;
use getset::Getters;
@@ -9,7 +10,6 @@ use getset::MutGetters;
use getset::Setters;
use serde::Serialize;
use komorebi_core::CycleDirection;
use komorebi_core::Flip;
use komorebi_core::Layout;
use komorebi_core::OperationDirection;
@@ -48,7 +48,8 @@ pub struct Workspace {
#[serde(skip_serializing)]
#[getset(get = "pub", set = "pub")]
latest_layout: Vec<Rect>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
#[serde(skip_serializing)]
#[getset(get = "pub", get_mut = "pub")]
resize_dimensions: Vec<Option<Rect>>,
#[getset(get = "pub", set = "pub")]
tile: bool,
@@ -138,26 +139,8 @@ impl Workspace {
Ok(())
}
pub fn update(
&mut self,
work_area: &Rect,
offset: Option<Rect>,
invisible_borders: &Rect,
) -> Result<()> {
let container_padding = self.container_padding();
let mut adjusted_work_area = offset.map_or_else(
|| *work_area,
|offset| {
let mut with_offset = *work_area;
with_offset.left += offset.left;
with_offset.top += offset.top;
with_offset.right -= offset.right;
with_offset.bottom -= offset.bottom;
with_offset
},
);
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());
self.enforce_resize_constraints();
@@ -165,7 +148,6 @@ impl Workspace {
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding);
window.set_position(&adjusted_work_area, invisible_borders, true)?;
};
} else if let Some(window) = self.maximized_window_mut() {
@@ -173,11 +155,9 @@ impl Workspace {
} else if !self.containers().is_empty() {
let layouts = self.layout().calculate(
&adjusted_work_area,
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
anyhow!(
"there must be at least one container to calculate a workspace layout"
)
})?,
NonZeroUsize::new(self.containers().len()).context(
"there must be at least one container to calculate a workspace layout",
)?,
self.container_padding(),
self.layout_flip(),
self.resize_dimensions(),
@@ -344,14 +324,11 @@ impl Workspace {
}
pub fn promote_container(&mut self) -> Result<()> {
let resize = self.resize_dimensions_mut().remove(0);
let container = self
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?;
self.containers_mut().push_front(container);
self.resize_dimensions_mut().insert(0, resize);
self.resize_dimensions_mut().insert(0, None);
self.focus_container(0);
Ok(())
@@ -363,15 +340,8 @@ impl Workspace {
}
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
if idx < self.resize_dimensions().len() {
self.resize_dimensions_mut().remove(idx);
}
if idx < self.containers().len() {
return self.containers_mut().remove(idx);
}
None
self.resize_dimensions_mut().remove(idx);
self.containers_mut().remove(idx)
}
fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {
@@ -477,12 +447,6 @@ impl Workspace {
None
}
}
pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option<usize> {
Option::from(direction.next_idx(
self.focused_container_idx(),
NonZeroUsize::new(self.containers().len())?,
))
}
pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> {
let focused_idx = self.focused_container_idx();

View File

@@ -20,22 +20,6 @@ Log() {
Run, komorebic.exe log, , Hide
}
QuickSave() {
Run, komorebic.exe quick-save, , Hide
}
QuickLoad() {
Run, komorebic.exe quick-load, , Hide
}
Save(path) {
Run, komorebic.exe save %path%, , Hide
}
Load(path) {
Run, komorebic.exe load %path%, , Hide
}
Focus(operation_direction) {
Run, komorebic.exe focus %operation_direction%, , Hide
}
@@ -44,14 +28,6 @@ Move(operation_direction) {
Run, komorebic.exe move %operation_direction%, , Hide
}
CycleFocus(cycle_direction) {
Run, komorebic.exe cycle-focus %cycle_direction%, , Hide
}
CycleMove(cycle_direction) {
Run, komorebic.exe cycle-move %cycle_direction%, , Hide
}
Stack(operation_direction) {
Run, komorebic.exe stack %operation_direction%, , Hide
}
@@ -92,14 +68,6 @@ FocusWorkspace(target) {
Run, komorebic.exe focus-workspace %target%, , Hide
}
CycleMonitor(cycle_direction) {
Run, komorebic.exe cycle-monitor %cycle_direction%, , Hide
}
CycleWorkspace(cycle_direction) {
Run, komorebic.exe cycle-workspace %cycle_direction%, , Hide
}
NewWorkspace() {
Run, komorebic.exe new-workspace, , Hide
}
@@ -108,10 +76,6 @@ InvisibleBorders(left, top, right, bottom) {
Run, komorebic.exe invisible-borders %left% %top% %right% %bottom%, , Hide
}
WorkAreaOffset(left, top, right, bottom) {
Run, komorebic.exe work-area-offset %left% %top% %right% %bottom%, , Hide
}
AdjustContainerPadding(sizing, adjustment) {
Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.6"
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"]
@@ -17,7 +17,7 @@ komorebi-core = { path = "../komorebi-core" }
clap = "3.0.0-beta.4"
color-eyre = "0.5"
dirs = "4"
dirs = "3"
fs-tail = "0.1"
heck = "0.3"
paste = "1"

View File

@@ -13,7 +13,7 @@ use std::process::Command;
use clap::AppSettings;
use clap::ArgEnum;
use clap::Clap;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::ContextCompat;
use color_eyre::Result;
use fs_tail::TailedFile;
use heck::KebabCase;
@@ -79,10 +79,6 @@ macro_rules! gen_enum_subcommand_args {
gen_enum_subcommand_args! {
Focus: OperationDirection,
Move: OperationDirection,
CycleFocus: CycleDirection,
CycleMove: CycleDirection,
CycleMonitor: CycleDirection,
CycleWorkspace: CycleDirection,
Stack: OperationDirection,
CycleStack: CycleDirection,
FlipLayout: Flip,
@@ -167,18 +163,6 @@ struct InvisibleBorders {
bottom: i32,
}
#[derive(Clap, AhkFunction)]
struct WorkAreaOffset {
/// Size of the left work area offset (set right to left * 2 to maintain right padding)
left: i32,
/// Size of the top work area offset (set bottom to the same value to maintain bottom padding)
top: i32,
/// Size of the right work area offset
right: i32,
/// Size of the bottom work area offset
bottom: i32,
}
#[derive(Clap, AhkFunction)]
struct EnsureWorkspaces {
/// Monitor index (zero-indexed)
@@ -284,18 +268,6 @@ struct Start {
ffm: bool,
}
#[derive(Clap, AhkFunction)]
struct Save {
/// File to which the resize layout dimensions should be saved
path: String,
}
#[derive(Clap, AhkFunction)]
struct Load {
/// File from which the resize layout dimensions should be loaded
path: String,
}
#[derive(Clap)]
#[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)]
struct Opts {
@@ -316,28 +288,12 @@ enum SubCommand {
Query(Query),
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
Log,
/// Quicksave the current resize layout dimensions
QuickSave,
/// Load the last quicksaved resize layout dimensions
QuickLoad,
/// Save the current resize layout dimensions to a file
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Save(Save),
/// Load the resize layout dimensions from a file
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Load(Load),
/// Change focus to the window in the specified direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Focus(Focus),
/// Move the focused window in the specified direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Move(Move),
/// Change focus to the window in the specified cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleFocus(CycleFocus),
/// Move the focused window in the specified cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleMove(CycleMove),
/// Stack the focused window in the specified direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Stack(Stack),
@@ -367,20 +323,11 @@ enum SubCommand {
/// Focus the specified workspace on the focused monitor
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
FocusWorkspace(FocusWorkspace),
/// Focus the monitor in the given cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleMonitor(CycleMonitor),
/// Focus the workspace in the given cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleWorkspace(CycleWorkspace),
/// 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),
/// Set offsets to exclude parts of the work area from tiling
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkAreaOffset(WorkAreaOffset),
/// Adjust container padding on the focused workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
AdjustContainerPadding(AdjustContainerPadding),
@@ -462,7 +409,7 @@ enum SubCommand {
}
pub fn send_message(bytes: &[u8]) -> Result<()> {
let mut socket = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut socket = dirs::home_dir().context("there is no home directory")?;
socket.push("komorebi.sock");
let socket = socket.as_path();
@@ -476,8 +423,7 @@ fn main() -> Result<()> {
match opts.subcmd {
SubCommand::AhkLibrary => {
let mut library =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut library = dirs::home_dir().context("there is no home directory")?;
library.push("komorebic.lib.ahk");
let mut file = OpenOptions::new()
.write(true)
@@ -489,9 +435,9 @@ fn main() -> Result<()> {
println!(
"\nAHK helper library for komorebic written to {}",
library.to_str().ok_or_else(|| anyhow!(
"could not find the path to the generated ahk lib file"
))?
library
.to_str()
.context("could not find the path to the generated ahk lib file")?
);
println!(
@@ -524,12 +470,6 @@ fn main() -> Result<()> {
SubCommand::Move(arg) => {
send_message(&*SocketMessage::MoveWindow(arg.operation_direction).as_bytes()?)?;
}
SubCommand::CycleFocus(arg) => {
send_message(&*SocketMessage::CycleFocusWindow(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::CycleMove(arg) => {
send_message(&*SocketMessage::CycleMoveWindow(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::MoveToMonitor(arg) => {
send_message(&*SocketMessage::MoveContainerToMonitorNumber(arg.target).as_bytes()?)?;
}
@@ -553,17 +493,6 @@ fn main() -> Result<()> {
.as_bytes()?,
)?;
}
SubCommand::WorkAreaOffset(arg) => {
send_message(
&*SocketMessage::WorkAreaOffset(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)
@@ -626,9 +555,10 @@ fn main() -> Result<()> {
buf.pop(); // %USERPROFILE%\scoop\shims
buf.pop(); // %USERPROFILE%\scoop
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
Option::from(buf.to_str().ok_or_else(|| {
anyhow!("cannot create a string from the scoop komorebi path")
})?)
Option::from(
buf.to_str()
.context("cannot create a string from the scoop komorebi path")?,
)
}
}
} else {
@@ -702,12 +632,6 @@ fn main() -> Result<()> {
SubCommand::FocusWorkspace(arg) => {
send_message(&*SocketMessage::FocusWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::CycleMonitor(arg) => {
send_message(&*SocketMessage::CycleFocusMonitor(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::CycleWorkspace(arg) => {
send_message(&*SocketMessage::CycleFocusWorkspace(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::NewWorkspace => {
send_message(&*SocketMessage::NewWorkspace.as_bytes()?)?;
}
@@ -724,7 +648,7 @@ fn main() -> Result<()> {
)?;
}
SubCommand::State => {
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let home = dirs::home_dir().context("there is no home directory")?;
let mut socket = home;
socket.push("komorebic.sock");
let socket = socket.as_path();
@@ -758,7 +682,7 @@ fn main() -> Result<()> {
}
}
SubCommand::Query(arg) => {
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let home = dirs::home_dir().context("there is no home directory")?;
let mut socket = home;
socket.push("komorebic.sock");
let socket = socket.as_path();
@@ -792,8 +716,7 @@ fn main() -> Result<()> {
}
}
SubCommand::RestoreWindows => {
let mut hwnd_json =
dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
let mut hwnd_json = dirs::home_dir().context("there is no home directory")?;
hwnd_json.push("komorebi.hwnd.json");
let file = File::open(hwnd_json)?;
@@ -844,54 +767,11 @@ fn main() -> Result<()> {
SubCommand::Unmanage => {
send_message(&*SocketMessage::UnmanageFocusedWindow.as_bytes()?)?;
}
SubCommand::QuickSave => {
send_message(&*SocketMessage::QuickSave.as_bytes()?)?;
}
SubCommand::QuickLoad => {
send_message(&*SocketMessage::QuickLoad.as_bytes()?)?;
}
SubCommand::Save(arg) => {
send_message(&*SocketMessage::Save(resolve_windows_path(&arg.path)?).as_bytes()?)?;
}
SubCommand::Load(arg) => {
send_message(&*SocketMessage::Load(resolve_windows_path(&arg.path)?).as_bytes()?)?;
}
}
Ok(())
}
fn resolve_windows_path(raw_path: &str) -> Result<PathBuf> {
let path = if raw_path.starts_with('~') {
raw_path.replacen(
"~",
&dirs::home_dir()
.ok_or_else(|| anyhow!("there is no home directory"))?
.display()
.to_string(),
1,
)
} else {
raw_path.to_string()
};
let full_path = PathBuf::from(path);
let parent = full_path
.parent()
.ok_or_else(|| anyhow!("cannot parse directory"))?;
let file = full_path
.components()
.last()
.ok_or_else(|| anyhow!("cannot parse filename"))?;
let mut canonicalized = std::fs::canonicalize(parent)?;
canonicalized.push(file);
Ok(canonicalized)
}
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
// BOOL is returned but does not signify whether or not the operation was succesful
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow