mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-20 06:18:04 +01:00
Compare commits
48 Commits
feature/ba
...
feature/dp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe350edff | ||
|
|
26ec574452 | ||
|
|
1c7a5ccb42 | ||
|
|
876439b96b | ||
|
|
d1b1a9e006 | ||
|
|
04791f427b | ||
|
|
e9bccd0316 | ||
|
|
29201b6b94 | ||
|
|
8efce49f2c | ||
|
|
6c022f8d69 | ||
|
|
748659db35 | ||
|
|
91c7f0588c | ||
|
|
60ec439d06 | ||
|
|
ead175ddbc | ||
|
|
5dd3e76602 | ||
|
|
ebcd7ce224 | ||
|
|
b982021573 | ||
|
|
3c84bfd27e | ||
|
|
c874bfc7bf | ||
|
|
85f9c381e5 | ||
|
|
336a4e358f | ||
|
|
4576078b96 | ||
|
|
39971774ea | ||
|
|
7da431081e | ||
|
|
7cc69a4a40 | ||
|
|
5f325a7458 | ||
|
|
34a7b2eb0c | ||
|
|
b08eb0d50c | ||
|
|
005a95b1e6 | ||
|
|
092e36b8b3 | ||
|
|
70be6f4ea4 | ||
|
|
e09d55e71a | ||
|
|
7cef7b53b5 | ||
|
|
8594e72d31 | ||
|
|
bc22ab699f | ||
|
|
b3844af1f3 | ||
|
|
b43f03ce83 | ||
|
|
5c1cfe7b2e | ||
|
|
6269e5972c | ||
|
|
ffa0b0b55e | ||
|
|
66199c5b15 | ||
|
|
aa42a64a48 | ||
|
|
17f1923423 | ||
|
|
5cfc3e831b | ||
|
|
aaf0434053 | ||
|
|
2224479c30 | ||
|
|
957588f60d | ||
|
|
d111d68c0b |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: LGUG2Z
|
||||
2
.github/workflows/windows.yaml
vendored
2
.github/workflows/windows.yaml
vendored
@@ -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@v2
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
version: latest
|
||||
|
||||
@@ -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\komorebi.exe"
|
||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64_v1\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\komorebic.exe"
|
||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64_v1\komorebic.exe"
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
@@ -42,19 +42,4 @@ checksum:
|
||||
name_template: checksums.txt
|
||||
|
||||
changelog:
|
||||
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"
|
||||
sort: asc
|
||||
2447
Cargo.lock
generated
2447
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,5 @@ members = [
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"komorebi-bar",
|
||||
"komorebic"
|
||||
]
|
||||
|
||||
37
README.md
37
README.md
@@ -155,6 +155,26 @@ 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
|
||||
@@ -219,6 +239,15 @@ 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
|
||||
@@ -475,6 +504,8 @@ 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
|
||||
@@ -511,7 +542,9 @@ 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
|
||||
@@ -584,14 +617,14 @@ the IDE for completions and navigation:
|
||||
|
||||
## Logs and Debugging
|
||||
|
||||
Logs from `komorebi` will be appended to `~/komorebi.log`; this file is never rotated or overwritten, so it will keep
|
||||
Logs from `komorebi` will be appended to `%LOCALAPPDATA%/komorebi/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 `~/komorebi.hwnd.json`.
|
||||
to `komorebi` are stored and continuously updated in `%LOCALAPPDATA%/komorebi//komorebi.hwnd.json`.
|
||||
|
||||
### Restoring Windows
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
[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",
|
||||
]
|
||||
@@ -1,125 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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()]
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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)]
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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)]
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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()]
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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>;
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.8"
|
||||
version = "0.1.10"
|
||||
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.35"
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
]
|
||||
|
||||
@@ -57,6 +57,8 @@ pub enum SocketMessage {
|
||||
ToggleMaximize,
|
||||
ToggleWindowContainerBehaviour,
|
||||
WindowHidingBehaviour(HidingBehaviour),
|
||||
ToggleCrossMonitorMoveBehaviour,
|
||||
CrossMonitorMoveBehaviour(MoveBehaviour),
|
||||
UnmanagedWindowOperationBehaviour(OperationBehaviour),
|
||||
// Current Workspace Commands
|
||||
ManageFocusedWindow,
|
||||
@@ -161,6 +163,13 @@ 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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.8"
|
||||
version = "0.1.10"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
@@ -8,14 +8,6 @@ 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]
|
||||
@@ -37,7 +29,7 @@ paste = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
sysinfo = "0.23"
|
||||
sysinfo = "0.24"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
@@ -49,16 +41,17 @@ winreg = "0.10"
|
||||
schemars = "0.8"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.35"
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
[features]
|
||||
|
||||
@@ -3,15 +3,14 @@ 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, Deserialize, Getters, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, JsonSchema)]
|
||||
pub struct Container {
|
||||
#[serde(skip)]
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get = "pub")]
|
||||
id: String,
|
||||
windows: Ring<Window>,
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
#![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);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,150 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::missing_errors_doc, clippy::redundant_pub_crate)]
|
||||
|
||||
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::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;
|
||||
#[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::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;
|
||||
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);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
@@ -44,8 +157,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
let home = HOME_DIR.clone();
|
||||
let appender = tracing_appender::rolling::never(home, "komorebi.log");
|
||||
let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "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);
|
||||
@@ -97,6 +209,134 @@ 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() {
|
||||
@@ -146,14 +386,14 @@ fn main() -> Result<()> {
|
||||
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe").collect();
|
||||
|
||||
if matched_procs.len() > 1 {
|
||||
let mut shim_is_active = false;
|
||||
let mut len = matched_procs.len();
|
||||
for proc in matched_procs {
|
||||
if proc.root().ends_with("shims") {
|
||||
shim_is_active = true;
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !shim_is_active {
|
||||
if len > 1 {
|
||||
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -167,11 +407,12 @@ 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 = WinEventListener::new(Arc::new(Mutex::new(outgoing)));
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||
|
||||
@@ -8,7 +8,6 @@ use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Rect;
|
||||
@@ -17,9 +16,7 @@ use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||
pub struct Monitor {
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
id: isize,
|
||||
@@ -28,7 +25,7 @@ pub struct Monitor {
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
work_area_size: Rect,
|
||||
workspaces: Ring<Workspace>,
|
||||
#[serde(skip)]
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_mut = "pub")]
|
||||
workspace_names: HashMap<usize, String>,
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -378,15 +379,40 @@ impl WindowManager {
|
||||
SocketMessage::ResizeWindowAxis(axis, sizing) => {
|
||||
// If the user has a custom layout, allow for the resizing of the primary column
|
||||
// with this signal
|
||||
if let Layout::Custom(ref mut custom) = self.focused_workspace_mut()?.layout_mut() {
|
||||
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 matches!(axis, Axis::Horizontal) {
|
||||
let percentage = custom
|
||||
.primary_width_percentage()
|
||||
.unwrap_or(100 / custom.len());
|
||||
|
||||
match sizing {
|
||||
Sizing::Increase => custom.set_primary_width_percentage(percentage + 5),
|
||||
Sizing::Decrease => custom.set_primary_width_percentage(percentage - 5),
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise proceed with the resizing logic for individual window containers in the
|
||||
@@ -681,6 +707,19 @@ 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;
|
||||
}
|
||||
@@ -722,7 +761,7 @@ impl WindowManager {
|
||||
notify_subscribers(&serde_json::to_string(&Notification {
|
||||
event: NotificationEvent::Socket(message.clone()),
|
||||
state: self.as_ref().into(),
|
||||
})?);
|
||||
})?)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -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,6 +288,7 @@ 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
|
||||
@@ -340,7 +341,10 @@ 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;
|
||||
let is_move = moved_across_monitors
|
||||
|| resize.right == 0 && resize.bottom == 0
|
||||
|| resize.right.abs() == invisible_borders.right
|
||||
&& resize.bottom.abs() == invisible_borders.bottom;
|
||||
|
||||
if is_move {
|
||||
tracing::info!("moving with mouse");
|
||||
@@ -483,8 +487,7 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
let mut hwnd_json = HOME_DIR.clone();
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
@@ -495,7 +498,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(())
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
pub struct Ring<T> {
|
||||
elements: VecDeque<T>,
|
||||
focused: usize,
|
||||
|
||||
@@ -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, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Clone, Copy, 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() {
|
||||
display.push_str(&format!(", title: {}", title));
|
||||
write!(display, ", title: {}", title)?;
|
||||
}
|
||||
|
||||
if let Ok(exe) = self.exe() {
|
||||
display.push_str(&format!(", exe: {}", exe));
|
||||
write!(display, ", exe: {}", exe)?;
|
||||
}
|
||||
|
||||
if let Ok(class) = self.class() {
|
||||
display.push_str(&format!(", class: {}", class));
|
||||
write!(display, ", class: {}", class)?;
|
||||
}
|
||||
|
||||
display.push(')');
|
||||
write!(display, ")")?;
|
||||
|
||||
write!(f, "{}", display)
|
||||
}
|
||||
@@ -199,7 +199,19 @@ 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();
|
||||
WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?;
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Raise Window to foreground
|
||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||
@@ -281,23 +293,35 @@ 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();
|
||||
if float_identifiers.contains(&title)
|
||||
|| float_identifiers.contains(&exe_name)
|
||||
|| float_identifiers.contains(&class) {
|
||||
return Ok(false);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let managed_override = {
|
||||
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
manage_identifiers.contains(&exe_name) || manage_identifiers.contains(&class)
|
||||
manage_identifiers.contains(&exe_name)
|
||||
|| manage_identifiers.contains(&class)
|
||||
|| manage_identifiers.contains(&title)
|
||||
};
|
||||
|
||||
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(&exe_name)
|
||||
|| layered_whitelist.contains(&class)
|
||||
|| layered_whitelist.contains(&title)
|
||||
};
|
||||
|
||||
let allow_wsl2_gui = {
|
||||
|
||||
@@ -12,7 +12,6 @@ use hotwatch::notify::DebouncedEvent;
|
||||
use hotwatch::Hotwatch;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixListener;
|
||||
|
||||
@@ -23,6 +22,7 @@ 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,6 +40,7 @@ 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;
|
||||
@@ -58,6 +59,7 @@ 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,
|
||||
@@ -67,13 +69,14 @@ pub struct WindowManager {
|
||||
pub pending_move_op: Option<(usize, usize, usize)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Serialize, 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,
|
||||
@@ -101,6 +104,7 @@ 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,
|
||||
@@ -143,10 +147,7 @@ impl EnforceWorkspaceRuleOp {
|
||||
impl WindowManager {
|
||||
#[tracing::instrument]
|
||||
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
|
||||
let home = HOME_DIR.clone();
|
||||
let mut socket = home;
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
@@ -175,6 +176,7 @@ 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,
|
||||
@@ -267,6 +269,39 @@ 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()?;
|
||||
@@ -906,13 +941,24 @@ impl WindowManager {
|
||||
|
||||
tracing::info!("focusing container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
let workspace = self.focused_workspace()?;
|
||||
let new_idx = workspace.new_idx_for_direction(direction);
|
||||
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
workspace.focus_container(new_idx);
|
||||
self.focused_window_mut()?.focus(self.mouse_follows_focus)?;
|
||||
|
||||
Ok(())
|
||||
@@ -924,15 +970,103 @@ impl WindowManager {
|
||||
|
||||
tracing::info!("moving container");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
let workspace = self.focused_workspace()?;
|
||||
|
||||
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"))?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
workspace.swap_containers(current_idx, new_idx);
|
||||
workspace.focus_container(new_idx);
|
||||
self.update_focused_workspace(self.mouse_follows_focus)
|
||||
}
|
||||
|
||||
@@ -1630,6 +1764,13 @@ 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()
|
||||
@@ -1662,7 +1803,7 @@ impl WindowManager {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
|
||||
pub fn monitor_idx_from_current_pos(&self) -> Option<usize> {
|
||||
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
|
||||
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
@@ -1688,6 +1829,22 @@ 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");
|
||||
|
||||
@@ -2,14 +2,13 @@ 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, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum WindowManagerEvent {
|
||||
Destroy(WinEvent, Window),
|
||||
@@ -29,56 +28,56 @@ pub enum WindowManagerEvent {
|
||||
impl Display for WindowManagerEvent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WindowManagerEvent::Manage(window) => {
|
||||
Self::Manage(window) => {
|
||||
write!(f, "Manage (Window: {})", window)
|
||||
}
|
||||
WindowManagerEvent::Unmanage(window) => {
|
||||
Self::Unmanage(window) => {
|
||||
write!(f, "Unmanage (Window: {})", window)
|
||||
}
|
||||
WindowManagerEvent::Destroy(winevent, window) => {
|
||||
Self::Destroy(winevent, window) => {
|
||||
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::FocusChange(winevent, window) => {
|
||||
Self::FocusChange(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"FocusChange (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::Hide(winevent, window) => {
|
||||
Self::Hide(winevent, window) => {
|
||||
write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::Minimize(winevent, window) => {
|
||||
Self::Minimize(winevent, window) => {
|
||||
write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::Show(winevent, window) => {
|
||||
Self::Show(winevent, window) => {
|
||||
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeStart(winevent, window) => {
|
||||
Self::MoveResizeStart(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MoveResizeStart (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(winevent, window) => {
|
||||
Self::MoveResizeEnd(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MoveResizeEnd (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::MouseCapture(winevent, window) => {
|
||||
Self::MouseCapture(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MouseCapture (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::Raise(window) => {
|
||||
Self::Raise(window) => {
|
||||
write!(f, "Raise (Window: {})", window)
|
||||
}
|
||||
WindowManagerEvent::MonitorPoll(winevent, window) => {
|
||||
Self::MonitorPoll(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MonitorPoll (WinEvent: {}, Window: {})",
|
||||
@@ -90,25 +89,23 @@ impl Display for WindowManagerEvent {
|
||||
}
|
||||
|
||||
impl WindowManagerEvent {
|
||||
#[must_use]
|
||||
pub const fn window(self) -> Window {
|
||||
match self {
|
||||
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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
||||
match winevent {
|
||||
WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),
|
||||
|
||||
@@ -38,6 +38,8 @@ 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;
|
||||
@@ -130,7 +132,7 @@ macro_rules! impl_process_windows_crate_integer_wrapper_result {
|
||||
$(
|
||||
impl ProcessWindowsCrateResult<$deref> for $input {
|
||||
fn process(self) -> Result<$deref> {
|
||||
if self.0 == 0 {
|
||||
if self == $input(0) {
|
||||
Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(self.0)
|
||||
@@ -239,14 +241,12 @@ 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,7 +366,6 @@ 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;
|
||||
|
||||
@@ -377,12 +376,10 @@ 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() }
|
||||
}
|
||||
@@ -537,25 +534,18 @@ 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>())?;
|
||||
@@ -577,8 +567,14 @@ impl WindowsApi {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_process_dpi_awareness_context() -> Result<()> {
|
||||
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn system_parameters_info_w(
|
||||
pub fn system_parameters_info_w(
|
||||
action: SYSTEM_PARAMETERS_INFO_ACTION,
|
||||
ui_param: u32,
|
||||
pv_param: *mut c_void,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||
@@ -89,7 +88,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, Debug, Serialize, Deserialize, Display, JsonSchema)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Display, JsonSchema)]
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
pub enum WinEvent {
|
||||
|
||||
@@ -32,15 +32,14 @@ pub struct WinEventListener {
|
||||
outgoing_events: Arc<Mutex<Sender<WindowManagerEvent>>>,
|
||||
}
|
||||
|
||||
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 new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener {
|
||||
WinEventListener {
|
||||
hook: Arc::new(AtomicIsize::new(0)),
|
||||
outgoing_events: outgoing,
|
||||
}
|
||||
}
|
||||
|
||||
impl WinEventListener {
|
||||
pub fn start(self) {
|
||||
let hook = self.hook.clone();
|
||||
let outgoing = self.outgoing_events.lock().clone();
|
||||
|
||||
@@ -8,7 +8,6 @@ use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use komorebi_core::Axis;
|
||||
@@ -23,21 +22,19 @@ use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||
pub struct Workspace {
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
#[getset(set = "pub")]
|
||||
name: Option<String>,
|
||||
containers: Ring<Container>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
monocle_container: Option<Container>,
|
||||
#[serde(skip)]
|
||||
#[serde(skip_serializing)]
|
||||
#[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)]
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
@@ -52,7 +49,7 @@ pub struct Workspace {
|
||||
workspace_padding: Option<i32>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
container_padding: Option<i32>,
|
||||
#[serde(skip)]
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
latest_layout: Vec<Rect>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
@@ -415,7 +412,11 @@ impl Workspace {
|
||||
self.focus_last_container();
|
||||
}
|
||||
|
||||
fn remove_container_by_idx(&mut self, idx: usize) -> Option<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> {
|
||||
if idx < self.resize_dimensions().len() {
|
||||
self.resize_dimensions_mut().remove(idx);
|
||||
}
|
||||
|
||||
@@ -256,6 +256,14 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.8"
|
||||
version = "0.1.10"
|
||||
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 = "0.3"
|
||||
powershell_script = "1.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.8"
|
||||
uds_windows = "1"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.35"
|
||||
version = "0.39"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
|
||||
@@ -36,6 +36,7 @@ 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;
|
||||
@@ -60,6 +61,9 @@ 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 {
|
||||
@@ -115,6 +119,7 @@ gen_enum_subcommand_args! {
|
||||
MouseFollowsFocus: BooleanState,
|
||||
Query: StateQuery,
|
||||
WindowHidingBehaviour: HidingBehaviour,
|
||||
CrossMonitorMoveBehaviour: MoveBehaviour,
|
||||
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
||||
}
|
||||
|
||||
@@ -207,6 +212,7 @@ 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,
|
||||
}
|
||||
|
||||
@@ -624,6 +630,11 @@ 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),
|
||||
@@ -675,12 +686,9 @@ enum SubCommand {
|
||||
}
|
||||
|
||||
pub fn send_message(bytes: &[u8]) -> Result<()> {
|
||||
let mut socket = HOME_DIR.clone();
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
let mut stream = UnixStream::connect(&socket)?;
|
||||
Ok(stream.write_all(&*bytes)?)
|
||||
Ok(stream.write_all(bytes)?)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
@@ -720,46 +728,47 @@ fn main() -> Result<()> {
|
||||
color_log.push("komorebi.log");
|
||||
let file = TailedFile::new(File::open(color_log)?);
|
||||
let locked = file.lock();
|
||||
for line in locked.lines() {
|
||||
println!("{}", line?);
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
for line in locked.lines().flatten() {
|
||||
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,
|
||||
)
|
||||
@@ -767,11 +776,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,
|
||||
@@ -782,7 +791,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
SubCommand::WorkAreaOffset(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkAreaOffset(Rect {
|
||||
&SocketMessage::WorkAreaOffset(Rect {
|
||||
left: arg.left,
|
||||
top: arg.top,
|
||||
right: arg.right,
|
||||
@@ -793,50 +802,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)?,
|
||||
@@ -846,7 +855,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
SubCommand::WorkspaceLayoutRule(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceLayoutRule(
|
||||
&SocketMessage::WorkspaceLayoutRule(
|
||||
arg.monitor,
|
||||
arg.workspace,
|
||||
arg.at_container_count,
|
||||
@@ -857,7 +866,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
SubCommand::WorkspaceCustomLayoutRule(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceLayoutCustomRule(
|
||||
&SocketMessage::WorkspaceLayoutCustomRule(
|
||||
arg.monitor,
|
||||
arg.workspace,
|
||||
arg.at_container_count,
|
||||
@@ -868,13 +877,12 @@ 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()?,
|
||||
)?;
|
||||
}
|
||||
@@ -922,7 +930,7 @@ fn main() -> Result<()> {
|
||||
},
|
||||
);
|
||||
|
||||
match powershell_script::run(&script, true) {
|
||||
match powershell_script::run(&script) {
|
||||
Ok(output) => {
|
||||
println!("{}", output);
|
||||
}
|
||||
@@ -932,49 +940,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,
|
||||
)
|
||||
@@ -982,23 +990,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()?,
|
||||
)?;
|
||||
}
|
||||
@@ -1019,7 +1027,7 @@ fn main() -> Result<()> {
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&*SocketMessage::State.as_bytes()?)?;
|
||||
send_message(&SocketMessage::State.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
match listener.accept() {
|
||||
@@ -1053,7 +1061,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() {
|
||||
@@ -1071,8 +1079,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
SubCommand::RestoreWindows => {
|
||||
let mut hwnd_json = HOME_DIR.clone();
|
||||
hwnd_json.push("komorebi.hwnd.json");
|
||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||
|
||||
let file = File::open(hwnd_json)?;
|
||||
let reader = BufReader::new(file);
|
||||
@@ -1083,93 +1090,96 @@ 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()?)?;
|
||||
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()?)?;
|
||||
}
|
||||
SubCommand::UnmanagedWindowOperationBehaviour(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour)
|
||||
&SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour)
|
||||
.as_bytes()?,
|
||||
)?;
|
||||
}
|
||||
@@ -1241,7 +1251,7 @@ fn main() -> Result<()> {
|
||||
},
|
||||
};
|
||||
|
||||
send_message(&*SocketMessage::NotificationSchema.as_bytes()?)?;
|
||||
send_message(&SocketMessage::NotificationSchema.as_bytes()?)?;
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
match listener.accept() {
|
||||
|
||||
Reference in New Issue
Block a user