Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
506600d689 feat(bar): don't think i'll pursue this 2022-04-25 08:04:18 -07:00
36 changed files with 3367 additions and 976 deletions

1
.github/FUNDING.yml vendored
View File

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

View File

@@ -93,7 +93,7 @@ jobs:
if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi
kokai release --no-emoji --add-links github:commits,issues --ref "$(git tag --points-at HEAD)" >"CHANGELOG.md"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
uses: goreleaser/goreleaser-action@v2
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest

View File

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

2479
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,5 +4,6 @@ members = [
"derive-ahk",
"komorebi",
"komorebi-core",
"komorebi-bar",
"komorebic"
]

View File

@@ -155,26 +155,6 @@ the `AutoHotKey64.exe` executable for AutoHotKey v2 is in your `Path`. If both `
exist in your home directory, only `komorebi.ahk` will be loaded. An example of an AutoHotKey v2 configuration file
for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778dabf693ce9236c57b201cd).
#### Using Different AHK Executables
The preferred way to install AutoHotKey for use with `komorebi` is to install it via `scoop`:
```powershell
scoop install autohotkey
```
If you install AutoHotKey using a different method, the name of the executable file may differ from the name given by
`scoop`, and thus what is expected by default in `komorebi`.
You may override the executables that `komorebi` looks for to launch and reload `komorebi.ahk` configuration files using
by setting one of the following two environment variables depending on which version of AutoHotKey you wish to use:
- `$Env:KOMOREBI_AHK_V1_EXE`
- `$Env:KOMOREBI_AHK_V2_EXE`
Please keep in mind that even when setting a custom executable name using these environment variables, the executables
are still required to be in your `Path`.
### Common First-Time Tips
#### Generating Common Application-Specific Configurations
@@ -239,15 +219,6 @@ If you already have configuration files that you wish to keep, move them to the
The next time you run `komorebic start`, any files created by or loaded by _komorebi_ will be placed or expected to
exist in this folder.
#### Removing Gaps
If you would like to remove all gaps from a given workspace, both between windows themselves, and between the monitor edges and the windows, you can set the following two configuration options to `0` for the desired monitors and workspaces:
```powershell
komorebic.exe container-padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
komorebic.exe workspace padding <MONITOR_INDEX> <WORKSPACE_INDEX> 0
```
#### Floating Windows
Sometimes you will want a specific application to never be tiled, and instead float all the time. You add add rules to
@@ -504,8 +475,6 @@ unmanage Unmanage a window that was forcibly m
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
window-hiding-behaviour Set the window behaviour when switching workspaces / cycling stacks
cross-monitor-move-behaviour Set the behaviour when moving windows across monitor boundaries
toggle-cross-monitor-move-behaviour Toggle the behaviour when moving windows across monitor boundaries
unmanaged-window-operation-behaviour Set the operation behaviour when the focused window is not managed
float-rule Add a rule to always float the specified application
manage-rule Add a rule to always manage the specified application
@@ -542,9 +511,7 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Window stacks
- [x] Cycle through stacked windows
- [x] Change focused window by direction
- [x] Change focused window by direction across monitor boundary
- [x] Move focused window container in direction
- [x] Move focused window container in direction across monitor boundary
- [x] Move focused window container to monitor and follow
- [x] Move focused window container to workspace follow
- [x] Send focused window container to monitor
@@ -617,14 +584,14 @@ the IDE for completions and navigation:
## Logs and Debugging
Logs from `komorebi` will be appended to `%LOCALAPPDATA%/komorebi/komorebi.log`; this file is never rotated or overwritten, so it will keep
Logs from `komorebi` will be appended to `~/komorebi.log`; this file is never rotated or overwritten, so it will keep
growing until it is deleted by the user.
Whenever running the `komorebic stop` command or sending a Ctrl-C signal to `komorebi` directly, the `komorebi` process
ensures that all hidden windows are restored before termination.
If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known
to `komorebi` are stored and continuously updated in `%LOCALAPPDATA%/komorebi//komorebi.hwnd.json`.
to `komorebi` are stored and continuously updated in `~/komorebi.hwnd.json`.
### Restoring Windows

31
komorebi-bar/Cargo.toml Normal file
View File

@@ -0,0 +1,31 @@
[package]
name = "komorebi-bar"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
komorebi = { path = "../komorebi" }
komorebi-core = { path = "../komorebi-core" }
as-any = "0.3"
chrono = "0.4"
color-eyre = "0.6"
eframe = "0.17"
egui = "0.17"
lazy_static = "1.4"
miow = "0.4"
schemafy = "0.6"
serde = "1"
serde_json = "1"
parking_lot = "0.12"
local-ip-address = "0.4"
clipboard-win = "4.4"
sysinfo = "0.23"
[dependencies.windows]
version = "0.35"
features = [
"Win32_Graphics_Gdi",
]

125
komorebi-bar/src/bar.rs Normal file
View File

@@ -0,0 +1,125 @@
use crate::date::Date;
use crate::ram::Ram;
use crate::time::Time;
use crate::widget::BarWidget;
use crate::widget::Output;
use crate::widget::Widget;
use crate::IpAddress;
use crate::Storage;
use crate::Workspaces;
use clipboard_win::set_clipboard_string;
use color_eyre::owo_colors::OwoColorize;
use eframe::epi::App;
use eframe::epi::Frame;
use egui::style::Margin;
use egui::CentralPanel;
use egui::Color32;
use egui::Context;
use egui::Direction;
use egui::Layout;
use egui::Rounding;
use std::process::Command;
use std::sync::atomic::Ordering;
pub struct Bar {
pub background_rgb: Color32,
pub text_rgb: Color32,
pub workspaces: Workspaces,
pub time: Time,
pub date: Date,
pub ip_address: IpAddress,
pub memory: Ram,
pub storage: Storage,
}
impl App for Bar {
fn update(&mut self, ctx: &Context, frame: &Frame) {
let custom_frame = egui::Frame {
margin: Margin::symmetric(8.0, 8.0),
rounding: Rounding::none(),
fill: self.background_rgb,
..Default::default()
};
CentralPanel::default().frame(custom_frame).show(ctx, |ui| {
ui.horizontal(|horizontal| {
horizontal.style_mut().visuals.override_text_color = Option::from(self.text_rgb);
horizontal.with_layout(Layout::left_to_right(), |ltr| {
for (i, workspace) in self.workspaces.output().iter().enumerate() {
if workspace == "komorebi offline" {
ltr.label(workspace);
} else {
ctx.request_repaint();
if ltr
.selectable_label(*self.workspaces.selected.lock() == i, workspace)
.clicked()
{
let mut selected = self.workspaces.selected.lock();
*selected = i;
if let Err(error) = Workspaces::focus(i) {
eprintln!("{}", error)
};
}
}
}
});
horizontal.with_layout(Layout::right_to_left(), |rtl| {
for time in self.time.output() {
ctx.request_repaint();
if rtl.button(format!("🕐 {}", time)).clicked() {
self.time.format.toggle()
};
}
for date in self.date.output() {
if rtl.button(format!("📅 {}", date)).clicked() {
self.date.format.next()
};
}
for memory in self.memory.output() {
if rtl.button(format!("🐏 {}", memory)).clicked() {
if let Err(error) =
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).output()
{
eprintln!("{}", error)
}
};
}
for disk in self.storage.output() {
if rtl.button(format!("🖴 {}", disk)).clicked() {
if let Err(error) = Command::new("cmd.exe")
.args([
"/C",
"explorer.exe",
disk.split(' ').collect::<Vec<&str>>()[0],
])
.output()
{
eprintln!("{}", error)
}
};
}
for ip in self.ip_address.output() {
if rtl.button(format!("🌐 {}", ip)).clicked() {
if let Err(error) =
Command::new("cmd.exe").args(["/C", "ncpa.cpl"]).output()
{
eprintln!("{}", error)
}
};
}
});
})
});
}
fn name(&self) -> &str {
"komorebi-bar"
}
}

46
komorebi-bar/src/date.rs Normal file
View File

@@ -0,0 +1,46 @@
use crate::widget::BarWidget;
pub enum DateFormat {
MonthDateYear,
YearMonthDate,
DateMonthYear,
DayDateMonthYear,
}
impl DateFormat {
pub fn next(&mut self) {
match self {
DateFormat::MonthDateYear => *self = Self::YearMonthDate,
DateFormat::YearMonthDate => *self = Self::DateMonthYear,
DateFormat::DateMonthYear => *self = Self::DayDateMonthYear,
DateFormat::DayDateMonthYear => *self = Self::MonthDateYear,
};
}
fn fmt_string(&self) -> String {
match self {
DateFormat::MonthDateYear => String::from("%D"),
DateFormat::YearMonthDate => String::from("%F"),
DateFormat::DateMonthYear => String::from("%v"),
DateFormat::DayDateMonthYear => String::from("%A %e %B %Y"),
}
}
}
pub struct Date {
pub format: DateFormat,
}
impl Date {
pub fn init(format: DateFormat) -> Self {
Self { format }
}
}
impl BarWidget for Date {
fn output(&mut self) -> Vec<String> {
vec![chrono::Local::now()
.format(&self.format.fmt_string())
.to_string()]
}
}

View File

@@ -0,0 +1,27 @@
use crate::widget::BarWidget;
use local_ip_address::find_ifa;
use local_ip_address::local_ip;
pub struct IpAddress {
pub interface: String,
}
impl IpAddress {
pub fn init(interface: String) -> Self {
IpAddress { interface }
}
}
impl BarWidget for IpAddress {
fn output(&mut self) -> Vec<String> {
if let Ok(interfaces) = local_ip_address::list_afinet_netifas() {
if let Some((interface, ip_address)) =
local_ip_address::find_ifa(interfaces, &self.interface)
{
return vec![format!("{}: {}", interface, ip_address)];
}
}
vec![format!("{}: disconnected", self.interface)]
}
}

80
komorebi-bar/src/main.rs Normal file
View File

@@ -0,0 +1,80 @@
mod bar;
mod date;
mod ip_address;
mod ram;
mod storage;
mod time;
mod widget;
mod workspaces;
use crate::ip_address::IpAddress;
use crate::ram::Ram;
use crate::storage::Storage;
use bar::Bar;
use color_eyre::Result;
use date::Date;
use date::DateFormat;
use eframe::run_native;
use eframe::NativeOptions;
use egui::Color32;
use egui::Pos2;
use egui::Vec2;
use komorebi::WindowsApi;
use time::Time;
use time::TimeFormat;
use windows::Win32::Graphics::Gdi::HMONITOR;
use workspaces::Workspaces;
fn main() -> Result<()> {
let workspaces = Workspaces::init(0)?;
let time = Time::init(TimeFormat::TwentyFourHour);
let date = Date::init(DateFormat::DayDateMonthYear);
let ip_address = IpAddress::init(String::from("Ethernet"));
let app = Bar {
background_rgb: Color32::from_rgb(255, 0, 0),
text_rgb: Color32::from_rgb(255, 255, 255),
workspaces,
time,
date,
ip_address,
memory: Ram,
storage: Storage,
};
let mut win_option = NativeOptions {
decorated: false,
..Default::default()
};
// let hmonitors = WindowsApi::valid_hmonitors()?;
// for hmonitor in hmonitors {
// let info = WindowsApi::monitor_info(hmonitor)?;
// }
let info = WindowsApi::monitor_info_w(HMONITOR(65537))?;
let offset = Offsets {
vertical: 10.0,
horizontal: 200.0,
};
win_option.initial_window_pos = Option::from(Pos2::new(
info.rcWork.left as f32 + offset.horizontal,
info.rcWork.top as f32 + offset.vertical * 2.0,
));
win_option.initial_window_size = Option::from(Vec2::new(
info.rcWork.right as f32 - (offset.horizontal * 2.0),
info.rcWork.top as f32 - offset.vertical,
));
win_option.always_on_top = true;
run_native(Box::new(app), win_option);
}
struct Offsets {
vertical: f32,
horizontal: f32,
}

15
komorebi-bar/src/ram.rs Normal file
View File

@@ -0,0 +1,15 @@
use crate::widget::BarWidget;
use sysinfo::RefreshKind;
use sysinfo::System;
use sysinfo::SystemExt;
pub struct Ram;
impl BarWidget for Ram {
fn output(&mut self) -> Vec<String> {
let sys = System::new_with_specifics(RefreshKind::new().with_memory());
let used = sys.used_memory();
let total = sys.total_memory();
vec![format!("RAM: {}%", (used * 100) / total)]
}
}

View File

@@ -0,0 +1,35 @@
use crate::widget::BarWidget;
use crate::widget::Output;
use crate::widget::Widget;
use color_eyre::Result;
use sysinfo::DiskExt;
use sysinfo::RefreshKind;
use sysinfo::System;
use sysinfo::SystemExt;
pub struct Storage;
impl BarWidget for Storage {
fn output(&mut self) -> Vec<String> {
let sys = System::new_with_specifics(RefreshKind::new().with_disks_list());
let mut disks = vec![];
for disk in sys.disks() {
let mount = disk.mount_point();
let total = disk.total_space();
let available = disk.available_space();
let used = total - available;
disks.push(format!(
"{} {}%",
mount.to_string_lossy(),
(used * 100) / total
))
}
disks.reverse();
disks
}
}

40
komorebi-bar/src/time.rs Normal file
View File

@@ -0,0 +1,40 @@
use crate::widget::BarWidget;
pub enum TimeFormat {
TwelveHour,
TwentyFourHour,
}
impl TimeFormat {
pub fn toggle(&mut self) {
match self {
TimeFormat::TwelveHour => *self = TimeFormat::TwentyFourHour,
TimeFormat::TwentyFourHour => *self = TimeFormat::TwelveHour,
};
}
fn fmt_string(&self) -> String {
match self {
TimeFormat::TwelveHour => String::from("%l:%M:%S %p"),
TimeFormat::TwentyFourHour => String::from("%T"),
}
}
}
pub struct Time {
pub format: TimeFormat,
}
impl Time {
pub fn init(format: TimeFormat) -> Self {
Self { format }
}
}
impl BarWidget for Time {
fn output(&mut self) -> Vec<String> {
vec![chrono::Local::now()
.format(&self.format.fmt_string())
.to_string()]
}
}

View File

@@ -0,0 +1,25 @@
use as_any::AsAny;
use color_eyre::Result;
#[derive(Debug, Clone)]
pub enum Output {
SingleBox(String),
MultiBox(Vec<String>),
}
#[derive(Debug, Copy, Clone)]
pub enum RepaintStrategy {
Default,
Constant,
}
pub trait Widget: AsAny {
fn output(&mut self) -> Result<Output>;
fn repaint_strategy(&self) -> RepaintStrategy {
RepaintStrategy::Default
}
}
pub trait BarWidget {
fn output(&mut self) -> Vec<String>;
}

View File

@@ -0,0 +1,179 @@
use crate::widget::BarWidget;
use color_eyre::Report;
use color_eyre::Result;
use komorebi::Notification;
use komorebi::State;
use miow::pipe::NamedPipe;
use parking_lot::Mutex;
use std::io::Read;
use std::process::Command;
use std::sync::Arc;
use std::thread;
use std::thread::sleep;
use std::time::Duration;
pub struct Workspaces {
pub enabled: bool,
pub monitor_idx: usize,
pub connected: Arc<Mutex<bool>>,
pub pipe: Arc<Mutex<NamedPipe>>,
pub state: Arc<Mutex<State>>,
pub selected: Arc<Mutex<usize>>,
}
impl BarWidget for Workspaces {
fn output(&mut self) -> Vec<String> {
let state = self.state.lock();
let mut workspaces = vec![];
if let Some(primary_monitor) = state.monitors.elements().get(self.monitor_idx) {
for (i, workspace) in primary_monitor.workspaces().iter().enumerate() {
workspaces.push(if let Some(name) = workspace.name() {
name.clone()
} else {
format!("{}", i + 1)
});
}
}
if workspaces.is_empty() || !*self.connected.lock() {
vec!["komorebi offline".to_string()]
} else {
workspaces
}
}
}
const PIPE: &str = r#"\\.\pipe\"#;
impl Workspaces {
pub fn focus(index: usize) -> Result<()> {
Ok(Command::new("cmd.exe")
.args([
"/C",
"komorebic.exe",
"focus-workspace",
&format!("{}", index),
])
.output()
.map(|_| ())?)
}
pub fn init(monitor_idx: usize) -> Result<Self> {
let name = format!("bar-{}", monitor_idx);
let pipe = format!("{}\\{}", PIPE, name);
let mut named_pipe = NamedPipe::new(pipe)?;
let mut output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", &name])
.output()?;
while !output.status.success() {
println!(
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
output.status.code()
);
sleep(Duration::from_secs(5));
output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", &name])
.output()?;
}
named_pipe.connect()?;
let mut buf = vec![0; 4096];
let mut bytes_read = named_pipe.read(&mut buf)?;
let mut data = String::from_utf8(buf[0..bytes_read].to_vec())?;
while data == "\n" {
bytes_read = named_pipe.read(&mut buf)?;
data = String::from_utf8(buf[0..bytes_read].to_vec())?;
}
let notification: Notification = serde_json::from_str(&data)?;
let mut workspaces = Self {
enabled: true,
monitor_idx,
connected: Arc::new(Mutex::new(true)),
pipe: Arc::new(Mutex::new(named_pipe)),
state: Arc::new(Mutex::new(notification.state)),
selected: Arc::new(Mutex::new(0)),
};
workspaces.listen()?;
Ok(workspaces)
}
pub fn listen(&mut self) -> Result<()> {
let state = self.state.clone();
let pipe = self.pipe.clone();
let connected = self.connected.clone();
let selected = self.selected.clone();
thread::spawn(move || -> Result<()> {
let mut buf = vec![0; 4096];
loop {
let mut named_pipe = pipe.lock();
match (*named_pipe).read(&mut buf) {
Ok(bytes_read) => {
let data = String::from_utf8(buf[0..bytes_read].to_vec())?;
if data == "\n" {
continue;
}
let notification: Notification = serde_json::from_str(&data)?;
let mut sl = selected.lock();
*sl = notification.state.monitors.elements()[0].focused_workspace_idx();
let mut st = state.lock();
*st = notification.state;
}
Err(error) => {
// Broken pipe
if error.raw_os_error().unwrap() == 109 {
{
let mut cn = connected.lock();
*cn = false;
}
named_pipe.disconnect()?;
let mut output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", "bar"])
.output()?;
while !output.status.success() {
println!(
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
output.status.code()
);
sleep(Duration::from_secs(5));
output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", "bar"])
.output()?;
}
named_pipe.connect()?;
{
let mut cn = connected.lock();
*cn = true;
}
} else {
return Err(Report::from(error));
}
}
}
}
});
Ok(())
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-core"
version = "0.1.10"
version = "0.1.8"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -15,7 +15,7 @@ strum = { version = "0.24", features = ["derive"] }
schemars = "0.8"
[dependencies.windows]
version = "0.39"
version = "0.35"
features = [
"Win32_Foundation",
]

View File

@@ -57,8 +57,6 @@ pub enum SocketMessage {
ToggleMaximize,
ToggleWindowContainerBehaviour,
WindowHidingBehaviour(HidingBehaviour),
ToggleCrossMonitorMoveBehaviour,
CrossMonitorMoveBehaviour(MoveBehaviour),
UnmanagedWindowOperationBehaviour(OperationBehaviour),
// Current Workspace Commands
ManageFocusedWindow,
@@ -163,13 +161,6 @@ pub enum WindowContainerBehaviour {
Append,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum MoveBehaviour {
Swap,
Insert,
}
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum HidingBehaviour {

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.10"
version = "0.1.8"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -8,6 +8,14 @@ repository = "https://github.com/LGUG2Z/komorebi"
license = "MIT"
edition = "2021"
[lib]
name = "komorebi"
path = "src/lib.rs"
[[bin]]
name = "komorebi"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
@@ -29,7 +37,7 @@ paste = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.24", features = ["derive"] }
sysinfo = "0.24"
sysinfo = "0.23"
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
@@ -41,17 +49,16 @@ winreg = "0.10"
schemars = "0.8"
[dependencies.windows]
version = "0.39"
version = "0.35"
features = [
"Win32_Foundation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_System_Threading",
"Win32_System_RemoteDesktop",
"Win32_UI_Accessibility",
"Win32_UI_HiDpi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Accessibility",
"Win32_UI_WindowsAndMessaging"
]
[features]

View File

@@ -3,14 +3,15 @@ use std::collections::VecDeque;
use getset::Getters;
use nanoid::nanoid;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::ring::Ring;
use crate::window::Window;
#[derive(Debug, Clone, Serialize, Getters, JsonSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
pub struct Container {
#[serde(skip_serializing)]
#[serde(skip)]
#[getset(get = "pub")]
id: String,
windows: Ring<Window>,

236
komorebi/src/lib.rs Normal file
View File

@@ -0,0 +1,236 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use which::which;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use komorebi_core::HidingBehaviour;
use komorebi_core::SocketMessage;
#[macro_use]
mod ring;
mod container;
mod monitor;
mod process_command;
mod process_event;
mod process_movement;
mod set_window_position;
mod styles;
mod window;
mod window_manager;
mod window_manager_event;
mod windows_api;
mod windows_callbacks;
mod winevent;
mod winevent_listener;
mod workspace;
pub use process_command::listen_for_commands;
pub use process_event::listen_for_events;
pub use process_movement::listen_for_movements;
pub use window_manager::State;
pub use window_manager::WindowManager;
pub use window_manager_event::WindowManagerEvent;
pub use windows_api::WindowsApi;
pub use winevent_listener::WinEventListener;
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec![
"explorer.exe".to_string(),
"firefox.exe".to_string(),
"chrome.exe".to_string(),
"idea64.exe".to_string(),
"ApplicationFrameHost.exe".to_string(),
"steam.exe".to_string(),
]));
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"firefox.exe".to_string(),
"idea64.exe".to_string(),
]));
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
// mstsc.exe creates these on Windows 11 when a WSL process is launched
// https://github.com/LGUG2Z/komorebi/issues/74
"OPContainerClass".to_string(),
"IHWindowClass".to_string()
]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"X410.exe".to_string(),
"mstsc.exe".to_string(),
"vcxsrv.exe".to_string(),
]));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
Arc::new(Mutex::new(HidingBehaviour::Minimize));
pub static ref HOME_DIR: PathBuf = {
if let Ok(home_path) = std::env::var("KOMOREBI_CONFIG_HOME") {
let home = PathBuf::from(&home_path);
if home.as_path().is_dir() {
home
} else {
panic!(
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
}
} else {
dirs::home_dir().expect("there is no home directory")
}
};
}
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
// This is the path on Windows 10
let mut current = hkcu
.open_subkey(format!(
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
SESSION_ID.load(Ordering::SeqCst)
))
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
// This is the path on Windows 11
if current.is_none() {
current = hkcu
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
}
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
// exist until one has been created in the task view
// The registry value will also not exist on user login if virtual desktops have been created
// but the task view has not been initiated
// In both of these cases, we return None, and the virtual desktop validation will never run. In
// the latter case, if the user desires this validation after initiating the task view, komorebi
// should be restarted, and then when this // fn runs again for the first time, it will pick up
// the value of CurrentVirtualDesktop and validate against it accordingly
current
}
pub fn load_configuration() -> Result<()> {
let home = HOME_DIR.clone();
let mut config_v1 = home.clone();
config_v1.push("komorebi.ahk");
let mut config_v2 = home;
config_v2.push("komorebi.ahk2");
if config_v1.exists() && which("autohotkey.exe").is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v1
.as_os_str()
.to_str()
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
Command::new("autohotkey.exe")
.arg(config_v1.as_os_str())
.output()?;
} else if config_v2.exists() && which("AutoHotkey64.exe").is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v2
.as_os_str()
.to_str()
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
Command::new("AutoHotkey64.exe")
.arg(config_v2.as_os_str())
.output()?;
};
Ok(())
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
Socket(SocketMessage),
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,
}
fn notify_subscribers(notification: &str) {
let mut stale_subscriptions = vec![];
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
for (subscriber, pipe) in subscriptions.iter_mut() {
match writeln!(pipe, "{}", notification) {
Ok(_) => {
tracing::debug!("pushed notification to subscriber: {}", subscriber);
}
Err(error) => {
// ERROR_FILE_NOT_FOUND
// 2 (0x2)
// The system cannot find the file specified.
// ERROR_NO_DATA
// 232 (0xE8)
// The pipe is being closed.
// Remove the subscription; the process will have to subscribe again
if let Some(2 | 232) = error.raw_os_error() {
let subscriber_cl = subscriber.clone();
stale_subscriptions.push(subscriber_cl);
}
}
}
}
for subscriber in stale_subscriptions {
tracing::warn!("removing stale subscription: {}", subscriber);
subscriptions.remove(&subscriber);
}
}

View File

@@ -1,150 +1,37 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::redundant_pub_crate)]
#![allow(clippy::missing_errors_doc)]
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU32;
use clap::Parser;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[cfg(feature = "deadlock_detection")]
use std::thread;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use clap::Parser;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use lazy_static::lazy_static;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Serialize;
use sysinfo::Process;
use sysinfo::ProcessExt;
use sysinfo::SystemExt;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use which::which;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use komorebi_core::HidingBehaviour;
use komorebi_core::SocketMessage;
use crate::process_command::listen_for_commands;
use crate::process_event::listen_for_events;
use crate::process_movement::listen_for_movements;
use crate::window_manager::State;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;
mod container;
mod monitor;
mod process_command;
mod process_event;
mod process_movement;
mod set_window_position;
mod styles;
mod window;
mod window_manager;
mod window_manager_event;
mod windows_api;
mod windows_callbacks;
mod winevent;
mod winevent_listener;
mod workspace;
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec![
"explorer.exe".to_string(),
"firefox.exe".to_string(),
"chrome.exe".to_string(),
"idea64.exe".to_string(),
"ApplicationFrameHost.exe".to_string(),
"steam.exe".to_string(),
]));
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"firefox.exe".to_string(),
"idea64.exe".to_string(),
]));
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, (usize, usize)>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
// mstsc.exe creates these on Windows 11 when a WSL process is launched
// https://github.com/LGUG2Z/komorebi/issues/74
"OPContainerClass".to_string(),
"IHWindowClass".to_string()
]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"X410.exe".to_string(),
"mstsc.exe".to_string(),
"vcxsrv.exe".to_string(),
]));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
Arc::new(Mutex::new(HidingBehaviour::Minimize));
static ref HOME_DIR: PathBuf = {
if let Ok(home_path) = std::env::var("KOMOREBI_CONFIG_HOME") {
let home = PathBuf::from(&home_path);
if home.as_path().is_dir() {
home
} else {
panic!(
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
}
} else {
dirs::home_dir().expect("there is no home directory")
}
};
static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
static ref AHK_V1_EXE: String = {
let mut ahk_v1: String = String::from("autohotkey.exe");
if let Ok(komorebi_ahk_v1_exe) = std::env::var("KOMOREBI_AHK_V1_EXE") {
if which(&komorebi_ahk_v1_exe).is_ok() {
ahk_v1 = komorebi_ahk_v1_exe;
}
}
ahk_v1
};
static ref AHK_V2_EXE: String = {
let mut ahk_v2: String = String::from("AutoHotkey64.exe");
if let Ok(komorebi_ahk_v2_exe) = std::env::var("KOMOREBI_AHK_V2_EXE") {
if which(&komorebi_ahk_v2_exe).is_ok() {
ahk_v2 = komorebi_ahk_v2_exe;
}
}
ahk_v2
};
}
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
use komorebi::listen_for_commands;
use komorebi::listen_for_events;
use komorebi::listen_for_movements;
use komorebi::load_configuration;
use komorebi::WinEventListener;
use komorebi::WindowManager;
use komorebi::WindowManagerEvent;
use komorebi::WindowsApi;
use komorebi::CUSTOM_FFM;
use komorebi::HOME_DIR;
use komorebi::SESSION_ID;
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
@@ -157,7 +44,8 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
std::env::set_var("RUST_LOG", "info");
}
let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "komorebi.log");
let home = HOME_DIR.clone();
let appender = tracing_appender::rolling::never(home, "komorebi.log");
let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log");
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
@@ -209,134 +97,6 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
Ok((guard, color_guard))
}
pub fn load_configuration() -> Result<()> {
let home = HOME_DIR.clone();
let mut config_v1 = home.clone();
config_v1.push("komorebi.ahk");
let mut config_v2 = home;
config_v2.push("komorebi.ahk2");
if config_v1.exists() && which(&*AHK_V1_EXE).is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v1
.as_os_str()
.to_str()
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
Command::new("autohotkey.exe")
.arg(config_v1.as_os_str())
.output()?;
} else if config_v2.exists() && which(&*AHK_V2_EXE).is_ok() {
tracing::info!(
"loading configuration file: {}",
config_v2
.as_os_str()
.to_str()
.ok_or_else(|| anyhow!("cannot convert path to string"))?
);
Command::new("AutoHotkey64.exe")
.arg(config_v2.as_os_str())
.output()?;
};
Ok(())
}
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
// This is the path on Windows 10
let mut current = hkcu
.open_subkey(format!(
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
SESSION_ID.load(Ordering::SeqCst)
))
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
// This is the path on Windows 11
if current.is_none() {
current = hkcu
.open_subkey(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops"#)
.ok()
.and_then(
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
Ok(current) => Option::from(current.bytes),
Err(_) => None,
},
);
}
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
// exist until one has been created in the task view
// The registry value will also not exist on user login if virtual desktops have been created
// but the task view has not been initiated
// In both of these cases, we return None, and the virtual desktop validation will never run. In
// the latter case, if the user desires this validation after initiating the task view, komorebi
// should be restarted, and then when this // fn runs again for the first time, it will pick up
// the value of CurrentVirtualDesktop and validate against it accordingly
current
}
#[derive(Debug, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
Socket(SocketMessage),
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,
}
pub fn notify_subscribers(notification: &str) -> Result<()> {
let mut stale_subscriptions = vec![];
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
for (subscriber, pipe) in subscriptions.iter_mut() {
match writeln!(pipe, "{}", notification) {
Ok(_) => {
tracing::debug!("pushed notification to subscriber: {}", subscriber);
}
Err(error) => {
// ERROR_FILE_NOT_FOUND
// 2 (0x2)
// The system cannot find the file specified.
// ERROR_NO_DATA
// 232 (0xE8)
// The pipe is being closed.
// Remove the subscription; the process will have to subscribe again
if let Some(2 | 232) = error.raw_os_error() {
let subscriber_cl = subscriber.clone();
stale_subscriptions.push(subscriber_cl);
}
}
}
}
for subscriber in stale_subscriptions {
tracing::warn!("removing stale subscription: {}", subscriber);
subscriptions.remove(&subscriber);
}
Ok(())
}
#[cfg(feature = "deadlock_detection")]
#[tracing::instrument]
fn detect_deadlocks() {
@@ -386,14 +146,14 @@ fn main() -> Result<()> {
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe").collect();
if matched_procs.len() > 1 {
let mut len = matched_procs.len();
let mut shim_is_active = false;
for proc in matched_procs {
if proc.root().ends_with("shims") {
len -= 1;
shim_is_active = true;
}
}
if len > 1 {
if !shim_is_active {
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
}
@@ -407,12 +167,11 @@ fn main() -> Result<()> {
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
WindowsApi::set_process_dpi_awareness_context()?;
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
crossbeam_channel::unbounded();
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
let winevent_listener = WinEventListener::new(Arc::new(Mutex::new(outgoing)));
winevent_listener.start();
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(

View File

@@ -8,6 +8,7 @@ use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use komorebi_core::Rect;
@@ -16,7 +17,9 @@ use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
#[derive(
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
)]
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
id: isize,
@@ -25,7 +28,7 @@ pub struct Monitor {
#[getset(get = "pub", set = "pub")]
work_area_size: Rect,
workspaces: Ring<Workspace>,
#[serde(skip_serializing)]
#[serde(skip)]
#[getset(get_mut = "pub")]
workspace_names: HashMap<usize, String>,
}

View File

@@ -20,7 +20,6 @@ use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
@@ -379,40 +378,15 @@ impl WindowManager {
SocketMessage::ResizeWindowAxis(axis, sizing) => {
// If the user has a custom layout, allow for the resizing of the primary column
// with this signal
let workspace = self.focused_workspace_mut()?;
let container_len = workspace.containers().len();
let no_layout_rules = workspace.layout_rules().is_empty();
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if let Layout::Custom(ref mut custom) = self.focused_workspace_mut()?.layout_mut() {
if matches!(axis, Axis::Horizontal) {
let percentage = custom
.primary_width_percentage()
.unwrap_or(100 / custom.len());
if no_layout_rules {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
}
}
} else {
for rule in workspace.layout_rules_mut() {
if container_len >= rule.0 {
if let Layout::Custom(ref mut custom) = rule.1 {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5);
}
}
}
}
}
match sizing {
Sizing::Increase => custom.set_primary_width_percentage(percentage + 5),
Sizing::Decrease => custom.set_primary_width_percentage(percentage - 5),
}
}
// Otherwise proceed with the resizing logic for individual window containers in the
@@ -707,19 +681,6 @@ impl WindowManager {
let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();
*hiding_behaviour = behaviour;
}
SocketMessage::ToggleCrossMonitorMoveBehaviour => {
match self.cross_monitor_move_behaviour {
MoveBehaviour::Swap => {
self.cross_monitor_move_behaviour = MoveBehaviour::Insert;
}
MoveBehaviour::Insert => {
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
}
}
}
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
self.cross_monitor_move_behaviour = behaviour;
}
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
self.unmanaged_window_operation_behaviour = behaviour;
}
@@ -761,7 +722,7 @@ impl WindowManager {
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: self.as_ref().into(),
})?)?;
})?);
}
Ok(())

View File

@@ -19,8 +19,8 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::DATA_DIR;
use crate::HIDDEN_HWNDS;
use crate::HOME_DIR;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
#[tracing::instrument]
@@ -288,7 +288,6 @@ impl WindowManager {
.ok_or_else(|| anyhow!("cannot get monitor idx from current position"))?;
let new_window_behaviour = self.window_container_behaviour;
let invisible_borders = self.invisible_borders;
let workspace = self.focused_workspace_mut()?;
if workspace
@@ -341,10 +340,7 @@ impl WindowManager {
// If we have moved across the monitors, use that override, otherwise determine
// if a move has taken place by ruling out a resize
let is_move = moved_across_monitors
|| resize.right == 0 && resize.bottom == 0
|| resize.right.abs() == invisible_borders.right
&& resize.bottom.abs() == invisible_borders.bottom;
let is_move = moved_across_monitors || resize.right == 0 && resize.bottom == 0;
if is_move {
tracing::info!("moving with mouse");
@@ -487,7 +483,8 @@ impl WindowManager {
}
}
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let mut hwnd_json = HOME_DIR.clone();
hwnd_json.push("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
@@ -498,7 +495,7 @@ impl WindowManager {
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::WindowManager(*event),
state: self.as_ref().into(),
})?)?;
})?);
tracing::info!("processed: {}", event.window().to_string());
Ok(())

View File

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

View File

@@ -1,13 +1,13 @@
use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::ser::Error;
use serde::ser::SerializeStruct;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use windows::Win32::Foundation::HWND;
@@ -27,7 +27,7 @@ use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy, JsonSchema)]
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
pub struct Window {
pub(crate) hwnd: isize,
}
@@ -37,18 +37,18 @@ impl Display for Window {
let mut display = format!("(hwnd: {}", self.hwnd);
if let Ok(title) = self.title() {
write!(display, ", title: {}", title)?;
display.push_str(&format!(", title: {}", title));
}
if let Ok(exe) = self.exe() {
write!(display, ", exe: {}", exe)?;
display.push_str(&format!(", exe: {}", exe));
}
if let Ok(class) = self.class() {
write!(display, ", class: {}", class)?;
display.push_str(&format!(", class: {}", class));
}
write!(display, ")")?;
display.push(')');
write!(f, "{}", display)
}
@@ -199,19 +199,7 @@ impl Window {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
let current_thread_id = WindowsApi::current_thread_id();
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
// hook has been installed
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not attach to window thread input processing mechanism, but continuing execution of focus(): {}",
error
);
}
};
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
// Raise Window to foreground
match WindowsApi::set_foreground_window(self.hwnd()) {
@@ -293,35 +281,23 @@ impl Window {
// If not allowing cloaked windows, we need to ensure the window is not cloaked
(false, false) => {
if let (Ok(title), Ok(exe_name), Ok(class)) = (self.title(), self.exe(), self.class()) {
let mut should_float = false;
{
let float_identifiers = FLOAT_IDENTIFIERS.lock();
for identifier in float_identifiers.iter() {
if title.starts_with(identifier) || title.ends_with(identifier) ||
class.starts_with(identifier) || class.ends_with(identifier) ||
identifier == &exe_name {
should_float = true;
}
if float_identifiers.contains(&title)
|| float_identifiers.contains(&exe_name)
|| float_identifiers.contains(&class) {
return Ok(false);
}
}
let managed_override = {
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
manage_identifiers.contains(&exe_name)
|| manage_identifiers.contains(&class)
|| manage_identifiers.contains(&title)
manage_identifiers.contains(&exe_name) || manage_identifiers.contains(&class)
};
if should_float && !managed_override {
return Ok(false);
}
let allow_layered = {
let layered_whitelist = LAYERED_WHITELIST.lock();
layered_whitelist.contains(&exe_name)
|| layered_whitelist.contains(&class)
|| layered_whitelist.contains(&title)
layered_whitelist.contains(&exe_name) || layered_whitelist.contains(&class)
};
let allow_wsl2_gui = {

View File

@@ -12,6 +12,7 @@ use hotwatch::notify::DebouncedEvent;
use hotwatch::Hotwatch;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixListener;
@@ -22,7 +23,6 @@ use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
@@ -40,7 +40,6 @@ use crate::windows_api::WindowsApi;
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
use crate::workspace::Workspace;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::DATA_DIR;
use crate::FLOAT_IDENTIFIERS;
use crate::HOME_DIR;
use crate::LAYERED_WHITELIST;
@@ -59,7 +58,6 @@ pub struct WindowManager {
pub work_area_offset: Option<Rect>,
pub resize_delta: i32,
pub window_container_behaviour: WindowContainerBehaviour,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub unmanaged_window_operation_behaviour: OperationBehaviour,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub mouse_follows_focus: bool,
@@ -69,14 +67,13 @@ pub struct WindowManager {
pub pending_move_op: Option<(usize, usize, usize)>,
}
#[derive(Debug, Serialize, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct State {
pub monitors: Ring<Monitor>,
pub is_paused: bool,
pub invisible_borders: Rect,
pub resize_delta: i32,
pub new_window_behaviour: WindowContainerBehaviour,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub mouse_follows_focus: bool,
@@ -104,7 +101,6 @@ impl From<&WindowManager> for State {
work_area_offset: wm.work_area_offset,
resize_delta: wm.resize_delta,
new_window_behaviour: wm.window_container_behaviour,
cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour,
focus_follows_mouse: wm.focus_follows_mouse.clone(),
mouse_follows_focus: wm.mouse_follows_focus,
has_pending_raise_op: wm.has_pending_raise_op,
@@ -147,7 +143,10 @@ impl EnforceWorkspaceRuleOp {
impl WindowManager {
#[tracing::instrument]
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
let socket = DATA_DIR.join("komorebi.sock");
let home = HOME_DIR.clone();
let mut socket = home;
socket.push("komorebi.sock");
let socket = socket.as_path();
match std::fs::remove_file(&socket) {
Ok(_) => {}
@@ -176,7 +175,6 @@ impl WindowManager {
virtual_desktop_id: current_virtual_desktop(),
work_area_offset: None,
window_container_behaviour: WindowContainerBehaviour::Create,
cross_monitor_move_behaviour: MoveBehaviour::Swap,
unmanaged_window_operation_behaviour: OperationBehaviour::Op,
resize_delta: 50,
focus_follows_mouse: None,
@@ -269,39 +267,6 @@ impl WindowManager {
Ok(())
}
pub fn monitor_index_in_direction(&self, direction: OperationDirection) -> Option<usize> {
let current_monitor_size = self.focused_monitor_size().ok()?;
for (idx, monitor) in self.monitors.elements().iter().enumerate() {
match direction {
OperationDirection::Left => {
if monitor.size().left + monitor.size().right == current_monitor_size.left {
return Option::from(idx);
}
}
OperationDirection::Right => {
if current_monitor_size.right + current_monitor_size.left == monitor.size().left
{
return Option::from(idx);
}
}
OperationDirection::Up => {
if monitor.size().top + monitor.size().bottom == current_monitor_size.top {
return Option::from(idx);
}
}
OperationDirection::Down => {
if current_monitor_size.top + current_monitor_size.bottom == monitor.size().top
{
return Option::from(idx);
}
}
}
}
None
}
#[tracing::instrument(skip(self))]
pub fn reconcile_monitors(&mut self) -> Result<()> {
let valid_hmonitors = WindowsApi::valid_hmonitors()?;
@@ -941,24 +906,13 @@ impl WindowManager {
tracing::info!("focusing container");
let workspace = self.focused_workspace()?;
let new_idx = workspace.new_idx_for_direction(direction);
let workspace = self.focused_workspace_mut()?;
// if there is no container in that direction for this workspace
match new_idx {
None => {
let monitor_idx = self
.monitor_index_in_direction(direction)
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
self.focus_monitor(monitor_idx)?;
}
Some(idx) => {
let workspace = self.focused_workspace_mut()?;
workspace.focus_container(idx);
}
}
let new_idx = workspace
.new_idx_for_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(self.mouse_follows_focus)?;
Ok(())
@@ -970,103 +924,15 @@ impl WindowManager {
tracing::info!("moving container");
let workspace = self.focused_workspace()?;
let workspace = self.focused_workspace_mut()?;
let origin_container_idx = workspace.focused_container_idx();
let origin_monitor_idx = self.focused_monitor_idx();
let target_container_idx = workspace.new_idx_for_direction(direction);
match target_container_idx {
// If there is nowhere to move on the current workspace, try to move it onto the monitor
// in that direction if there is one
None => {
let target_monitor_idx = self
.monitor_index_in_direction(direction)
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
{
// remove the container from the origin monitor workspace
let origin_container = self
.focused_workspace_mut()?
.remove_container_by_idx(origin_container_idx)
.ok_or_else(|| {
anyhow!("could not remove container at given origin index")
})?;
// focus the target monitor
self.focus_monitor(target_monitor_idx)?;
// get the focused workspace on the target monitor
let target_workspace = self.focused_workspace_mut()?;
// insert the origin container into the focused workspace on the target monitor
// at the position where the currently focused container on that workspace is
target_workspace.insert_container_at_idx(
target_workspace.focused_container_idx(),
origin_container,
);
// if there is only one container on the target workspace after the insertion
// it means that there won't be one swapped back, so we have to decrement the
// focused position
if target_workspace.containers().len() == 1 {
let origin_workspace =
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
origin_workspace
.focus_container(origin_workspace.focused_container_idx() - 1);
}
}
// if our MoveBehaviour is Swap, let's try to send back the window container
// whose position which just took over
if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::Swap) {
{
let target_workspace = self.focused_workspace_mut()?;
// if the target workspace doesn't have more than one container, this means it
// was previously empty, by only doing the second part of the swap when there is
// more than one container, we can fall back to a "move" if there is nothing to
// swap with on the target monitor
if target_workspace.containers().len() > 1 {
// remove the container from the target monitor workspace
let target_container = target_workspace
// this is now focused_container_idx + 1 because we have inserted our origin container
.remove_container_by_idx(
target_workspace.focused_container_idx() + 1,
)
.ok_or_else(|| {
anyhow!("could not remove container at given target index")
})?;
let origin_workspace =
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
// insert the container from the target monitor workspace into the origin monitor workspace
// at the same position from which our origin container was removed
origin_workspace
.insert_container_at_idx(origin_container_idx, target_container);
}
}
}
// make sure to update the origin monitor workspace layout because it is no
// longer focused so it won't get updated at the end of this fn
let offset = self.work_area_offset;
let invisible_borders = self.invisible_borders;
self.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
.update_focused_workspace(offset, &invisible_borders)?;
}
Some(new_idx) => {
let workspace = self.focused_workspace_mut()?;
workspace.swap_containers(origin_container_idx, new_idx);
workspace.focus_container(new_idx);
}
}
let current_idx = workspace.focused_container_idx();
let new_idx = workspace
.new_idx_for_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(self.mouse_follows_focus)
}
@@ -1764,13 +1630,6 @@ impl WindowManager {
self.update_focused_workspace(false)
}
pub fn focused_monitor_size(&self) -> Result<Rect> {
Ok(*self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?
.size())
}
pub fn focused_monitor_work_area(&self) -> Result<Rect> {
Ok(*self
.focused_monitor()
@@ -1803,7 +1662,7 @@ impl WindowManager {
None
}
pub fn monitor_idx_from_current_pos(&self) -> Option<usize> {
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
for (i, monitor) in self.monitors().iter().enumerate() {
@@ -1829,22 +1688,6 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no workspace"))
}
pub fn focused_workspace_for_monitor_idx(&self, idx: usize) -> Result<&Workspace> {
self.monitors()
.get(idx)
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace"))
}
pub fn focused_workspace_for_monitor_idx_mut(&mut self, idx: usize) -> Result<&mut Workspace> {
self.monitors_mut()
.get_mut(idx)
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))
}
#[tracing::instrument(skip(self))]
pub fn focus_workspace(&mut self, idx: usize) -> Result<()> {
tracing::info!("focusing workspace");

View File

@@ -2,13 +2,14 @@ use std::fmt::Display;
use std::fmt::Formatter;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::window::Window;
use crate::winevent::WinEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum WindowManagerEvent {
Destroy(WinEvent, Window),
@@ -28,56 +29,56 @@ pub enum WindowManagerEvent {
impl Display for WindowManagerEvent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Manage(window) => {
WindowManagerEvent::Manage(window) => {
write!(f, "Manage (Window: {})", window)
}
Self::Unmanage(window) => {
WindowManagerEvent::Unmanage(window) => {
write!(f, "Unmanage (Window: {})", window)
}
Self::Destroy(winevent, window) => {
WindowManagerEvent::Destroy(winevent, window) => {
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
}
Self::FocusChange(winevent, window) => {
WindowManagerEvent::FocusChange(winevent, window) => {
write!(
f,
"FocusChange (WinEvent: {}, Window: {})",
winevent, window
)
}
Self::Hide(winevent, window) => {
WindowManagerEvent::Hide(winevent, window) => {
write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window)
}
Self::Minimize(winevent, window) => {
WindowManagerEvent::Minimize(winevent, window) => {
write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window)
}
Self::Show(winevent, window) => {
WindowManagerEvent::Show(winevent, window) => {
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
}
Self::MoveResizeStart(winevent, window) => {
WindowManagerEvent::MoveResizeStart(winevent, window) => {
write!(
f,
"MoveResizeStart (WinEvent: {}, Window: {})",
winevent, window
)
}
Self::MoveResizeEnd(winevent, window) => {
WindowManagerEvent::MoveResizeEnd(winevent, window) => {
write!(
f,
"MoveResizeEnd (WinEvent: {}, Window: {})",
winevent, window
)
}
Self::MouseCapture(winevent, window) => {
WindowManagerEvent::MouseCapture(winevent, window) => {
write!(
f,
"MouseCapture (WinEvent: {}, Window: {})",
winevent, window
)
}
Self::Raise(window) => {
WindowManagerEvent::Raise(window) => {
write!(f, "Raise (Window: {})", window)
}
Self::MonitorPoll(winevent, window) => {
WindowManagerEvent::MonitorPoll(winevent, window) => {
write!(
f,
"MonitorPoll (WinEvent: {}, Window: {})",
@@ -89,23 +90,25 @@ impl Display for WindowManagerEvent {
}
impl WindowManagerEvent {
#[must_use]
pub const fn window(self) -> Window {
match self {
Self::Destroy(_, window)
| Self::FocusChange(_, window)
| Self::Hide(_, window)
| Self::Minimize(_, window)
| Self::Show(_, window)
| Self::MoveResizeStart(_, window)
| Self::MoveResizeEnd(_, window)
| Self::MouseCapture(_, window)
| Self::MonitorPoll(_, window)
| Self::Raise(window)
| Self::Manage(window)
| Self::Unmanage(window) => window,
WindowManagerEvent::Destroy(_, window)
| WindowManagerEvent::FocusChange(_, window)
| WindowManagerEvent::Hide(_, window)
| WindowManagerEvent::Minimize(_, window)
| WindowManagerEvent::Show(_, window)
| WindowManagerEvent::MoveResizeStart(_, window)
| WindowManagerEvent::MoveResizeEnd(_, window)
| WindowManagerEvent::MouseCapture(_, window)
| WindowManagerEvent::MonitorPoll(_, window)
| WindowManagerEvent::Raise(window)
| WindowManagerEvent::Manage(window)
| WindowManagerEvent::Unmanage(window) => window,
}
}
#[must_use]
pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
match winevent {
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),

View File

@@ -38,8 +38,6 @@ use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
@@ -132,7 +130,7 @@ macro_rules! impl_process_windows_crate_integer_wrapper_result {
$(
impl ProcessWindowsCrateResult<$deref> for $input {
fn process(self) -> Result<$deref> {
if self == $input(0) {
if self.0 == 0 {
Err(std::io::Error::last_os_error().into())
} else {
Ok(self.0)
@@ -241,12 +239,14 @@ impl WindowsApi {
.process()
}
#[must_use]
pub fn monitor_from_window(hwnd: HWND) -> isize {
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0
}
#[must_use]
pub fn monitor_from_point(point: POINT) -> isize {
// MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
@@ -366,6 +366,7 @@ impl WindowsApi {
Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))
}
#[must_use]
pub fn window_thread_process_id(hwnd: HWND) -> (u32, u32) {
let mut process_id: u32 = 0;
@@ -376,10 +377,12 @@ impl WindowsApi {
(process_id, thread_id)
}
#[must_use]
pub fn current_thread_id() -> u32 {
unsafe { GetCurrentThreadId() }
}
#[must_use]
pub fn current_process_id() -> u32 {
unsafe { GetCurrentProcessId() }
}
@@ -534,18 +537,25 @@ impl WindowsApi {
))
}
#[must_use]
pub fn is_window(hwnd: HWND) -> bool {
unsafe { IsWindow(hwnd) }.into()
}
#[must_use]
pub fn is_window_visible(hwnd: HWND) -> bool {
unsafe { IsWindowVisible(hwnd) }.into()
}
#[must_use]
pub fn is_iconic(hwnd: HWND) -> bool {
unsafe { IsIconic(hwnd) }.into()
}
pub fn monitor_info(hmonitor: isize) -> Result<MONITORINFO> {
Self::monitor_info_w(HMONITOR(hmonitor))
}
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFO> {
let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
monitor_info.cbSize = u32::try_from(std::mem::size_of::<MONITORINFO>())?;
@@ -567,14 +577,8 @@ impl WindowsApi {
))
}
pub fn set_process_dpi_awareness_context() -> Result<()> {
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }
.ok()
.process()
}
#[allow(dead_code)]
pub fn system_parameters_info_w(
fn system_parameters_info_w(
action: SYSTEM_PARAMETERS_INFO_ACTION,
ui_param: u32,
pv_param: *mut c_void,

View File

@@ -1,6 +1,7 @@
#![allow(clippy::use_self)]
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
@@ -88,7 +89,7 @@ use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Display, JsonSchema)]
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[repr(u32)]
#[allow(dead_code)]
pub enum WinEvent {

View File

@@ -32,14 +32,15 @@ pub struct WinEventListener {
outgoing_events: Arc<Mutex<Sender<WindowManagerEvent>>>,
}
pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener {
WinEventListener {
hook: Arc::new(AtomicIsize::new(0)),
outgoing_events: outgoing,
}
}
impl WinEventListener {
#[must_use]
pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> Self {
Self {
hook: Arc::new(AtomicIsize::new(0)),
outgoing_events: outgoing,
}
}
pub fn start(self) {
let hook = self.hook.clone();
let outgoing = self.outgoing_events.lock().clone();

View File

@@ -8,6 +8,7 @@ use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use komorebi_core::Axis;
@@ -22,19 +23,21 @@ use crate::ring::Ring;
use crate::window::Window;
use crate::windows_api::WindowsApi;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
#[derive(
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
)]
pub struct Workspace {
#[getset(set = "pub")]
#[getset(get = "pub", set = "pub")]
name: Option<String>,
containers: Ring<Container>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
monocle_container: Option<Container>,
#[serde(skip_serializing)]
#[serde(skip)]
#[getset(get_copy = "pub", set = "pub")]
monocle_container_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
maximized_window: Option<Window>,
#[serde(skip_serializing)]
#[serde(skip)]
#[getset(get_copy = "pub", set = "pub")]
maximized_window_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub")]
@@ -49,7 +52,7 @@ pub struct Workspace {
workspace_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
container_padding: Option<i32>,
#[serde(skip_serializing)]
#[serde(skip)]
#[getset(get = "pub", set = "pub")]
latest_layout: Vec<Rect>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
@@ -412,11 +415,7 @@ impl Workspace {
self.focus_last_container();
}
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
self.containers_mut().insert(idx, container);
}
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
if idx < self.resize_dimensions().len() {
self.resize_dimensions_mut().remove(idx);
}

View File

@@ -256,14 +256,6 @@ WindowHidingBehaviour(hiding_behaviour) {
Run, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide
}
CrossMonitorMoveBehaviour(move_behaviour) {
Run, komorebic.exe cross-monitor-move-behaviour %move_behaviour%, , Hide
}
ToggleCrossMonitorMoveBehaviour() {
Run, komorebic.exe toggle-cross-monitor-move-behaviour, , Hide
}
UnmanagedWindowOperationBehaviour(operation_behaviour) {
Run, komorebic.exe unmanaged-window-operation-behaviour %operation_behaviour%, , Hide
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.10"
version = "0.1.8"
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"]
@@ -21,14 +21,14 @@ fs-tail = "0.1"
heck = "0.4"
lazy_static = "1"
paste = "1"
powershell_script = "1.0"
powershell_script = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.8"
uds_windows = "1"
[dependencies.windows]
version = "0.39"
version = "0.35"
features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging"

View File

@@ -36,7 +36,6 @@ use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::HidingBehaviour;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
@@ -61,9 +60,6 @@ lazy_static! {
dirs::home_dir().expect("there is no home directory")
}
};
static ref DATA_DIR: PathBuf = dirs::data_local_dir()
.expect("there is no local data directory")
.join("komorebi");
}
trait AhkLibrary {
@@ -119,7 +115,6 @@ gen_enum_subcommand_args! {
MouseFollowsFocus: BooleanState,
Query: StateQuery,
WindowHidingBehaviour: HidingBehaviour,
CrossMonitorMoveBehaviour: MoveBehaviour,
UnmanagedWindowOperationBehaviour: OperationBehaviour,
}
@@ -212,7 +207,6 @@ pub struct WorkspaceLayoutRule {
/// The number of window containers on-screen required to trigger this layout rule
at_container_count: usize,
#[clap(arg_enum)]
layout: DefaultLayout,
}
@@ -630,11 +624,6 @@ enum SubCommand {
/// Set the window behaviour when switching workspaces / cycling stacks
#[clap(arg_required_else_help = true)]
WindowHidingBehaviour(WindowHidingBehaviour),
/// Set the behaviour when moving windows across monitor boundaries
#[clap(arg_required_else_help = true)]
CrossMonitorMoveBehaviour(CrossMonitorMoveBehaviour),
/// Toggle the behaviour when moving windows across monitor boundaries
ToggleCrossMonitorMoveBehaviour,
/// Set the operation behaviour when the focused window is not managed
#[clap(arg_required_else_help = true)]
UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour),
@@ -686,9 +675,12 @@ enum SubCommand {
}
pub fn send_message(bytes: &[u8]) -> Result<()> {
let socket = DATA_DIR.join("komorebi.sock");
let mut socket = HOME_DIR.clone();
socket.push("komorebi.sock");
let socket = socket.as_path();
let mut stream = UnixStream::connect(&socket)?;
Ok(stream.write_all(bytes)?)
Ok(stream.write_all(&*bytes)?)
}
#[allow(clippy::too_many_lines)]
@@ -728,47 +720,46 @@ fn main() -> Result<()> {
color_log.push("komorebi.log");
let file = TailedFile::new(File::open(color_log)?);
let locked = file.lock();
#[allow(clippy::significant_drop_in_scrutinee)]
for line in locked.lines().flatten() {
println!("{}", line);
for line in locked.lines() {
println!("{}", line?);
}
}
SubCommand::Focus(arg) => {
send_message(&SocketMessage::FocusWindow(arg.operation_direction).as_bytes()?)?;
send_message(&*SocketMessage::FocusWindow(arg.operation_direction).as_bytes()?)?;
}
SubCommand::Promote => {
send_message(&SocketMessage::Promote.as_bytes()?)?;
send_message(&*SocketMessage::Promote.as_bytes()?)?;
}
SubCommand::TogglePause => {
send_message(&SocketMessage::TogglePause.as_bytes()?)?;
send_message(&*SocketMessage::TogglePause.as_bytes()?)?;
}
SubCommand::Retile => {
send_message(&SocketMessage::Retile.as_bytes()?)?;
send_message(&*SocketMessage::Retile.as_bytes()?)?;
}
SubCommand::Move(arg) => {
send_message(&SocketMessage::MoveWindow(arg.operation_direction).as_bytes()?)?;
send_message(&*SocketMessage::MoveWindow(arg.operation_direction).as_bytes()?)?;
}
SubCommand::CycleFocus(arg) => {
send_message(&SocketMessage::CycleFocusWindow(arg.cycle_direction).as_bytes()?)?;
send_message(&*SocketMessage::CycleFocusWindow(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::CycleMove(arg) => {
send_message(&SocketMessage::CycleMoveWindow(arg.cycle_direction).as_bytes()?)?;
send_message(&*SocketMessage::CycleMoveWindow(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::MoveToMonitor(arg) => {
send_message(&SocketMessage::MoveContainerToMonitorNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::MoveContainerToMonitorNumber(arg.target).as_bytes()?)?;
}
SubCommand::MoveToWorkspace(arg) => {
send_message(&SocketMessage::MoveContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::MoveContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::SendToMonitor(arg) => {
send_message(&SocketMessage::SendContainerToMonitorNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::SendContainerToMonitorNumber(arg.target).as_bytes()?)?;
}
SubCommand::SendToWorkspace(arg) => {
send_message(&SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::SendToMonitorWorkspace(arg) => {
send_message(
&SocketMessage::SendContainerToMonitorWorkspaceNumber(
&*SocketMessage::SendContainerToMonitorWorkspaceNumber(
arg.target_monitor,
arg.target_workspace,
)
@@ -776,11 +767,11 @@ fn main() -> Result<()> {
)?;
}
SubCommand::MoveWorkspaceToMonitor(arg) => {
send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
}
SubCommand::InvisibleBorders(arg) => {
send_message(
&SocketMessage::InvisibleBorders(Rect {
&*SocketMessage::InvisibleBorders(Rect {
left: arg.left,
top: arg.top,
right: arg.right,
@@ -791,7 +782,7 @@ fn main() -> Result<()> {
}
SubCommand::WorkAreaOffset(arg) => {
send_message(
&SocketMessage::WorkAreaOffset(Rect {
&*SocketMessage::WorkAreaOffset(Rect {
left: arg.left,
top: arg.top,
right: arg.right,
@@ -802,50 +793,50 @@ fn main() -> Result<()> {
}
SubCommand::ContainerPadding(arg) => {
send_message(
&SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)
&*SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)
.as_bytes()?,
)?;
}
SubCommand::WorkspacePadding(arg) => {
send_message(
&SocketMessage::WorkspacePadding(arg.monitor, arg.workspace, arg.size)
&*SocketMessage::WorkspacePadding(arg.monitor, arg.workspace, arg.size)
.as_bytes()?,
)?;
}
SubCommand::AdjustWorkspacePadding(arg) => {
send_message(
&SocketMessage::AdjustWorkspacePadding(arg.sizing, arg.adjustment).as_bytes()?,
&*SocketMessage::AdjustWorkspacePadding(arg.sizing, arg.adjustment).as_bytes()?,
)?;
}
SubCommand::AdjustContainerPadding(arg) => {
send_message(
&SocketMessage::AdjustContainerPadding(arg.sizing, arg.adjustment).as_bytes()?,
&*SocketMessage::AdjustContainerPadding(arg.sizing, arg.adjustment).as_bytes()?,
)?;
}
SubCommand::ToggleFocusFollowsMouse(arg) => {
send_message(&SocketMessage::ToggleFocusFollowsMouse(arg.implementation).as_bytes()?)?;
send_message(&*SocketMessage::ToggleFocusFollowsMouse(arg.implementation).as_bytes()?)?;
}
SubCommand::ToggleTiling => {
send_message(&SocketMessage::ToggleTiling.as_bytes()?)?;
send_message(&*SocketMessage::ToggleTiling.as_bytes()?)?;
}
SubCommand::ToggleFloat => {
send_message(&SocketMessage::ToggleFloat.as_bytes()?)?;
send_message(&*SocketMessage::ToggleFloat.as_bytes()?)?;
}
SubCommand::ToggleMonocle => {
send_message(&SocketMessage::ToggleMonocle.as_bytes()?)?;
send_message(&*SocketMessage::ToggleMonocle.as_bytes()?)?;
}
SubCommand::ToggleMaximize => {
send_message(&SocketMessage::ToggleMaximize.as_bytes()?)?;
send_message(&*SocketMessage::ToggleMaximize.as_bytes()?)?;
}
SubCommand::WorkspaceLayout(arg) => {
send_message(
&SocketMessage::WorkspaceLayout(arg.monitor, arg.workspace, arg.value)
&*SocketMessage::WorkspaceLayout(arg.monitor, arg.workspace, arg.value)
.as_bytes()?,
)?;
}
SubCommand::WorkspaceCustomLayout(arg) => {
send_message(
&SocketMessage::WorkspaceLayoutCustom(
&*SocketMessage::WorkspaceLayoutCustom(
arg.monitor,
arg.workspace,
resolve_windows_path(&arg.path)?,
@@ -855,7 +846,7 @@ fn main() -> Result<()> {
}
SubCommand::WorkspaceLayoutRule(arg) => {
send_message(
&SocketMessage::WorkspaceLayoutRule(
&*SocketMessage::WorkspaceLayoutRule(
arg.monitor,
arg.workspace,
arg.at_container_count,
@@ -866,7 +857,7 @@ fn main() -> Result<()> {
}
SubCommand::WorkspaceCustomLayoutRule(arg) => {
send_message(
&SocketMessage::WorkspaceLayoutCustomRule(
&*SocketMessage::WorkspaceLayoutCustomRule(
arg.monitor,
arg.workspace,
arg.at_container_count,
@@ -877,12 +868,13 @@ fn main() -> Result<()> {
}
SubCommand::ClearWorkspaceLayoutRules(arg) => {
send_message(
&SocketMessage::ClearWorkspaceLayoutRules(arg.monitor, arg.workspace).as_bytes()?,
&*SocketMessage::ClearWorkspaceLayoutRules(arg.monitor, arg.workspace)
.as_bytes()?,
)?;
}
SubCommand::WorkspaceTiling(arg) => {
send_message(
&SocketMessage::WorkspaceTiling(arg.monitor, arg.workspace, arg.value.into())
&*SocketMessage::WorkspaceTiling(arg.monitor, arg.workspace, arg.value.into())
.as_bytes()?,
)?;
}
@@ -930,7 +922,7 @@ fn main() -> Result<()> {
},
);
match powershell_script::run(&script) {
match powershell_script::run(&script, true) {
Ok(output) => {
println!("{}", output);
}
@@ -940,49 +932,49 @@ fn main() -> Result<()> {
}
}
SubCommand::Stop => {
send_message(&SocketMessage::Stop.as_bytes()?)?;
send_message(&*SocketMessage::Stop.as_bytes()?)?;
}
SubCommand::FloatRule(arg) => {
send_message(&SocketMessage::FloatRule(arg.identifier, arg.id).as_bytes()?)?;
send_message(&*SocketMessage::FloatRule(arg.identifier, arg.id).as_bytes()?)?;
}
SubCommand::ManageRule(arg) => {
send_message(&SocketMessage::ManageRule(arg.identifier, arg.id).as_bytes()?)?;
send_message(&*SocketMessage::ManageRule(arg.identifier, arg.id).as_bytes()?)?;
}
SubCommand::WorkspaceRule(arg) => {
send_message(
&SocketMessage::WorkspaceRule(arg.identifier, arg.id, arg.monitor, arg.workspace)
&*SocketMessage::WorkspaceRule(arg.identifier, arg.id, arg.monitor, arg.workspace)
.as_bytes()?,
)?;
}
SubCommand::Stack(arg) => {
send_message(&SocketMessage::StackWindow(arg.operation_direction).as_bytes()?)?;
send_message(&*SocketMessage::StackWindow(arg.operation_direction).as_bytes()?)?;
}
SubCommand::Unstack => {
send_message(&SocketMessage::UnstackWindow.as_bytes()?)?;
send_message(&*SocketMessage::UnstackWindow.as_bytes()?)?;
}
SubCommand::CycleStack(arg) => {
send_message(&SocketMessage::CycleStack(arg.cycle_direction).as_bytes()?)?;
send_message(&*SocketMessage::CycleStack(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::ChangeLayout(arg) => {
send_message(&SocketMessage::ChangeLayout(arg.default_layout).as_bytes()?)?;
send_message(&*SocketMessage::ChangeLayout(arg.default_layout).as_bytes()?)?;
}
SubCommand::LoadCustomLayout(arg) => {
send_message(
&SocketMessage::ChangeLayoutCustom(resolve_windows_path(&arg.path)?).as_bytes()?,
&*SocketMessage::ChangeLayoutCustom(resolve_windows_path(&arg.path)?).as_bytes()?,
)?;
}
SubCommand::FlipLayout(arg) => {
send_message(&SocketMessage::FlipLayout(arg.axis).as_bytes()?)?;
send_message(&*SocketMessage::FlipLayout(arg.axis).as_bytes()?)?;
}
SubCommand::FocusMonitor(arg) => {
send_message(&SocketMessage::FocusMonitorNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::FocusMonitorNumber(arg.target).as_bytes()?)?;
}
SubCommand::FocusWorkspace(arg) => {
send_message(&SocketMessage::FocusWorkspaceNumber(arg.target).as_bytes()?)?;
send_message(&*SocketMessage::FocusWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::FocusMonitorWorkspace(arg) => {
send_message(
&SocketMessage::FocusMonitorWorkspaceNumber(
&*SocketMessage::FocusMonitorWorkspaceNumber(
arg.target_monitor,
arg.target_workspace,
)
@@ -990,23 +982,23 @@ fn main() -> Result<()> {
)?;
}
SubCommand::CycleMonitor(arg) => {
send_message(&SocketMessage::CycleFocusMonitor(arg.cycle_direction).as_bytes()?)?;
send_message(&*SocketMessage::CycleFocusMonitor(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::CycleWorkspace(arg) => {
send_message(&SocketMessage::CycleFocusWorkspace(arg.cycle_direction).as_bytes()?)?;
send_message(&*SocketMessage::CycleFocusWorkspace(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::NewWorkspace => {
send_message(&SocketMessage::NewWorkspace.as_bytes()?)?;
send_message(&*SocketMessage::NewWorkspace.as_bytes()?)?;
}
SubCommand::WorkspaceName(name) => {
send_message(
&SocketMessage::WorkspaceName(name.monitor, name.workspace, name.value)
&*SocketMessage::WorkspaceName(name.monitor, name.workspace, name.value)
.as_bytes()?,
)?;
}
SubCommand::EnsureWorkspaces(workspaces) => {
send_message(
&SocketMessage::EnsureWorkspaces(workspaces.monitor, workspaces.workspace_count)
&*SocketMessage::EnsureWorkspaces(workspaces.monitor, workspaces.workspace_count)
.as_bytes()?,
)?;
}
@@ -1027,7 +1019,7 @@ fn main() -> Result<()> {
},
};
send_message(&SocketMessage::State.as_bytes()?)?;
send_message(&*SocketMessage::State.as_bytes()?)?;
let listener = UnixListener::bind(&socket)?;
match listener.accept() {
@@ -1061,7 +1053,7 @@ fn main() -> Result<()> {
},
};
send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)?;
send_message(&*SocketMessage::Query(arg.state_query).as_bytes()?)?;
let listener = UnixListener::bind(&socket)?;
match listener.accept() {
@@ -1079,7 +1071,8 @@ fn main() -> Result<()> {
}
}
SubCommand::RestoreWindows => {
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let mut hwnd_json = HOME_DIR.clone();
hwnd_json.push("komorebi.hwnd.json");
let file = File::open(hwnd_json)?;
let reader = BufReader::new(file);
@@ -1090,96 +1083,93 @@ fn main() -> Result<()> {
}
}
SubCommand::ResizeEdge(resize) => {
send_message(&SocketMessage::ResizeWindowEdge(resize.edge, resize.sizing).as_bytes()?)?;
send_message(
&*SocketMessage::ResizeWindowEdge(resize.edge, resize.sizing).as_bytes()?,
)?;
}
SubCommand::ResizeAxis(arg) => {
send_message(&SocketMessage::ResizeWindowAxis(arg.axis, arg.sizing).as_bytes()?)?;
send_message(&*SocketMessage::ResizeWindowAxis(arg.axis, arg.sizing).as_bytes()?)?;
}
SubCommand::FocusFollowsMouse(arg) => {
send_message(
&SocketMessage::FocusFollowsMouse(arg.implementation, arg.boolean_state.into())
&*SocketMessage::FocusFollowsMouse(arg.implementation, arg.boolean_state.into())
.as_bytes()?,
)?;
}
SubCommand::ReloadConfiguration => {
send_message(&SocketMessage::ReloadConfiguration.as_bytes()?)?;
send_message(&*SocketMessage::ReloadConfiguration.as_bytes()?)?;
}
SubCommand::WatchConfiguration(arg) => {
send_message(&SocketMessage::WatchConfiguration(arg.boolean_state.into()).as_bytes()?)?;
send_message(
&*SocketMessage::WatchConfiguration(arg.boolean_state.into()).as_bytes()?,
)?;
}
SubCommand::IdentifyObjectNameChangeApplication(target) => {
send_message(
&SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)
&*SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)
.as_bytes()?,
)?;
}
SubCommand::IdentifyTrayApplication(target) => {
send_message(
&SocketMessage::IdentifyTrayApplication(target.identifier, target.id).as_bytes()?,
&*SocketMessage::IdentifyTrayApplication(target.identifier, target.id)
.as_bytes()?,
)?;
}
SubCommand::IdentifyLayeredApplication(target) => {
send_message(
&SocketMessage::IdentifyLayeredApplication(target.identifier, target.id)
&*SocketMessage::IdentifyLayeredApplication(target.identifier, target.id)
.as_bytes()?,
)?;
}
SubCommand::IdentifyBorderOverflowApplication(target) => {
send_message(
&SocketMessage::IdentifyBorderOverflowApplication(target.identifier, target.id)
&*SocketMessage::IdentifyBorderOverflowApplication(target.identifier, target.id)
.as_bytes()?,
)?;
}
SubCommand::Manage => {
send_message(&SocketMessage::ManageFocusedWindow.as_bytes()?)?;
send_message(&*SocketMessage::ManageFocusedWindow.as_bytes()?)?;
}
SubCommand::Unmanage => {
send_message(&SocketMessage::UnmanageFocusedWindow.as_bytes()?)?;
send_message(&*SocketMessage::UnmanageFocusedWindow.as_bytes()?)?;
}
SubCommand::QuickSaveResize => {
send_message(&SocketMessage::QuickSave.as_bytes()?)?;
send_message(&*SocketMessage::QuickSave.as_bytes()?)?;
}
SubCommand::QuickLoadResize => {
send_message(&SocketMessage::QuickLoad.as_bytes()?)?;
send_message(&*SocketMessage::QuickLoad.as_bytes()?)?;
}
SubCommand::SaveResize(arg) => {
send_message(&SocketMessage::Save(resolve_windows_path(&arg.path)?).as_bytes()?)?;
send_message(&*SocketMessage::Save(resolve_windows_path(&arg.path)?).as_bytes()?)?;
}
SubCommand::LoadResize(arg) => {
send_message(&SocketMessage::Load(resolve_windows_path(&arg.path)?).as_bytes()?)?;
send_message(&*SocketMessage::Load(resolve_windows_path(&arg.path)?).as_bytes()?)?;
}
SubCommand::Subscribe(arg) => {
send_message(&SocketMessage::AddSubscriber(arg.named_pipe).as_bytes()?)?;
send_message(&*SocketMessage::AddSubscriber(arg.named_pipe).as_bytes()?)?;
}
SubCommand::Unsubscribe(arg) => {
send_message(&SocketMessage::RemoveSubscriber(arg.named_pipe).as_bytes()?)?;
send_message(&*SocketMessage::RemoveSubscriber(arg.named_pipe).as_bytes()?)?;
}
SubCommand::ToggleMouseFollowsFocus => {
send_message(&SocketMessage::ToggleMouseFollowsFocus.as_bytes()?)?;
send_message(&*SocketMessage::ToggleMouseFollowsFocus.as_bytes()?)?;
}
SubCommand::MouseFollowsFocus(arg) => {
send_message(&SocketMessage::MouseFollowsFocus(arg.boolean_state.into()).as_bytes()?)?;
send_message(&*SocketMessage::MouseFollowsFocus(arg.boolean_state.into()).as_bytes()?)?;
}
SubCommand::ResizeDelta(arg) => {
send_message(&SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
send_message(&*SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
}
SubCommand::ToggleWindowContainerBehaviour => {
send_message(&SocketMessage::ToggleWindowContainerBehaviour.as_bytes()?)?;
send_message(&*SocketMessage::ToggleWindowContainerBehaviour.as_bytes()?)?;
}
SubCommand::WindowHidingBehaviour(arg) => {
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?;
}
SubCommand::CrossMonitorMoveBehaviour(arg) => {
send_message(
&SocketMessage::CrossMonitorMoveBehaviour(arg.move_behaviour).as_bytes()?,
)?;
}
SubCommand::ToggleCrossMonitorMoveBehaviour => {
send_message(&SocketMessage::ToggleCrossMonitorMoveBehaviour.as_bytes()?)?;
send_message(&*SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?;
}
SubCommand::UnmanagedWindowOperationBehaviour(arg) => {
send_message(
&SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour)
&*SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour)
.as_bytes()?,
)?;
}
@@ -1251,7 +1241,7 @@ fn main() -> Result<()> {
},
};
send_message(&SocketMessage::NotificationSchema.as_bytes()?)?;
send_message(&*SocketMessage::NotificationSchema.as_bytes()?)?;
let listener = UnixListener::bind(&socket)?;
match listener.accept() {