mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-13 16:57:40 +01:00
Compare commits
1 Commits
v0.1.5
...
feature/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
355bcb6877 |
38
Cargo.lock
generated
38
Cargo.lock
generated
@@ -505,7 +505,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebi"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"bitflags",
|
||||
@@ -537,7 +537,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"clap",
|
||||
@@ -549,7 +549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komorebic"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"clap",
|
||||
@@ -1245,9 +1245,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.28"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8"
|
||||
checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"pin-project-lite",
|
||||
@@ -1319,9 +1319,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.24"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd0568dbfe3baf7048b7908d2b32bca0d81cd56bec6d2a8f894b01d74f86be3"
|
||||
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,9 +1452,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.20.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7524f6f9074f6326a1c167cd3dc2ed4e6916648a1a55116d029620af9b65fb1"
|
||||
checksum = "ef84dd25f4c69a271b1bba394532bf400523b43169de21dfc715e8f8e491053d"
|
||||
dependencies = [
|
||||
"const-sha1",
|
||||
"windows_gen",
|
||||
@@ -1463,9 +1463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_gen"
|
||||
version = "0.20.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be44a189bde96fc0e0cdd5b152b2d21c635c0c94c7d256aab4425477b2a2f37"
|
||||
checksum = "ac7bb21b8ff5e801232b72a6ff554b4cc0cef9ed9238188c3ca78fe3968a7e5d"
|
||||
dependencies = [
|
||||
"windows_quote",
|
||||
"windows_reader",
|
||||
@@ -1473,9 +1473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_macros"
|
||||
version = "0.20.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc1d78ce8a43d45b8da282383a2cb2ffcd5587cc3a9c341125d3181d2b701ede"
|
||||
checksum = "5566b8c51118769e4a9094a688bf1233a3f36aacbfc78f3b15817fe0b6e0442f"
|
||||
dependencies = [
|
||||
"syn",
|
||||
"windows_gen",
|
||||
@@ -1485,15 +1485,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_quote"
|
||||
version = "0.20.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51fa2185b18a6164a3fa3ea2b6c92ebc1b60f532ae5a85c57408ba6a5a064913"
|
||||
checksum = "4af8236a9493c38855f95cdd11b38b342512a5df4ee7473cffa828b5ebb0e39c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_reader"
|
||||
version = "0.20.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3daa5bd758f2f8f20cd93a79aedca20759779f43785fc77b08a4e8e1e5876bbb"
|
||||
checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab"
|
||||
|
||||
[[package]]
|
||||
name = "winput"
|
||||
|
||||
26
README.md
26
README.md
@@ -184,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
|
||||
@@ -220,10 +200,6 @@ 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
|
||||
stack Stack the focused window in the specified direction
|
||||
@@ -296,8 +272,6 @@ 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
|
||||
|
||||
@@ -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.20"
|
||||
windows = "0.19"
|
||||
|
||||
[build-dependencies]
|
||||
windows = "0.20"
|
||||
windows = "0.19"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
pub use windows::Handle;
|
||||
pub use windows::Result;
|
||||
|
||||
::windows::include_bindings!();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -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;
|
||||
@@ -53,10 +52,6 @@ pub enum SocketMessage {
|
||||
Stop,
|
||||
TogglePause,
|
||||
Retile,
|
||||
QuickSave,
|
||||
QuickLoad,
|
||||
Save(PathBuf),
|
||||
Load(PathBuf),
|
||||
FocusMonitorNumber(usize),
|
||||
FocusWorkspaceNumber(usize),
|
||||
ContainerPadding(usize, usize, i32),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
@@ -14,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;
|
||||
|
||||
@@ -329,62 +326,6 @@ impl WindowManager {
|
||||
self.invisible_borders = 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");
|
||||
|
||||
@@ -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()?;
|
||||
@@ -285,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -264,39 +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;
|
||||
|
||||
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(&invisible_borders)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for and add any new monitors that may have been plugged in
|
||||
WindowsApi::load_monitor_information(&mut self.monitors)?;
|
||||
|
||||
@@ -590,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(),
|
||||
&[],
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>) };
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -47,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,
|
||||
@@ -153,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(),
|
||||
@@ -324,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(())
|
||||
@@ -343,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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.5"
|
||||
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"]
|
||||
|
||||
@@ -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;
|
||||
@@ -268,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 {
|
||||
@@ -300,16 +288,6 @@ 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),
|
||||
@@ -431,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();
|
||||
|
||||
@@ -445,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)
|
||||
@@ -458,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!(
|
||||
@@ -578,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 {
|
||||
@@ -670,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();
|
||||
@@ -704,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();
|
||||
@@ -738,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)?;
|
||||
@@ -790,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
|
||||
|
||||
Reference in New Issue
Block a user