mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-24 18:31:22 +01:00
feat(wm): initial commit
One week of blissful, in-the-zone coding, applying all of the lessons learnt from the development of yatta.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.idea
|
||||
1100
Cargo.lock
generated
Normal file
1100
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"bindings",
|
||||
"komorebi",
|
||||
"komorebi-core",
|
||||
"komorebic"
|
||||
]
|
||||
13
bindings/Cargo.toml
Normal file
13
bindings/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "bindings"
|
||||
version = "0.1.0"
|
||||
authors = ["Jade Iqbal"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
windows = "0.17.2"
|
||||
|
||||
[build-dependencies]
|
||||
windows = "0.17.2"
|
||||
29
bindings/build.rs
Normal file
29
bindings/build.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
fn main() {
|
||||
windows::build!(
|
||||
Windows::Win32::Foundation::{
|
||||
POINT,
|
||||
RECT,
|
||||
BOOL,
|
||||
PWSTR,
|
||||
HWND,
|
||||
LPARAM,
|
||||
},
|
||||
// error: `Windows.Win32.Graphics.Dwm.DWMWA_CLOAKED` not found in metadata
|
||||
Windows::Win32::Graphics::Dwm::*,
|
||||
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
|
||||
Windows::Win32::Graphics::Gdi::*,
|
||||
Windows::Win32::System::Threading::{
|
||||
PROCESS_ACCESS_RIGHTS,
|
||||
PROCESS_NAME_FORMAT,
|
||||
OpenProcess,
|
||||
QueryFullProcessImageNameW,
|
||||
GetCurrentThreadId,
|
||||
AttachThreadInput,
|
||||
GetCurrentProcessId
|
||||
},
|
||||
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
|
||||
Windows::Win32::UI::Accessibility::{SetWinEventHook, HWINEVENTHOOK},
|
||||
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
|
||||
Windows::Win32::UI::WindowsAndMessaging::*,
|
||||
);
|
||||
}
|
||||
1
bindings/src/lib.rs
Normal file
1
bindings/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
::windows::include_bindings!();
|
||||
15
komorebi-core/Cargo.toml
Normal file
15
komorebi-core/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bindings = { package = "bindings", path = "../bindings" }
|
||||
|
||||
color-eyre = "0.5.11"
|
||||
clap = "3.0.0-beta.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
strum = { version = "0.21", features = ["derive"] }
|
||||
34
komorebi-core/src/cycle_direction.rs
Normal file
34
komorebi-core/src/cycle_direction.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use clap::Clap;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum CycleDirection {
|
||||
Previous,
|
||||
Next,
|
||||
}
|
||||
|
||||
impl CycleDirection {
|
||||
pub fn next_idx(&self, idx: usize, len: usize) -> usize {
|
||||
match self {
|
||||
CycleDirection::Previous => {
|
||||
if idx == 0 {
|
||||
len - 1
|
||||
} else {
|
||||
idx - 1
|
||||
}
|
||||
}
|
||||
CycleDirection::Next => {
|
||||
if idx == len - 1 {
|
||||
0
|
||||
} else {
|
||||
idx + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
193
komorebi-core/src/layout.rs
Normal file
193
komorebi-core/src/layout.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
use clap::Clap;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::Rect;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum Layout {
|
||||
BSP,
|
||||
Columns,
|
||||
Rows,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum LayoutFlip {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
HorizontalAndVertical,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
count: usize,
|
||||
container_padding: Option<i32>,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
) -> Vec<Rect> {
|
||||
let mut dimensions = match self {
|
||||
Layout::BSP => self.fibonacci(area, count, layout_flip),
|
||||
Layout::Columns => {
|
||||
let right = area.right / count as i32;
|
||||
let mut left = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..count {
|
||||
layouts.push(Rect {
|
||||
left: area.left + left,
|
||||
top: area.top,
|
||||
right,
|
||||
bottom: area.bottom,
|
||||
});
|
||||
|
||||
left += right;
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
Layout::Rows => {
|
||||
let bottom = area.bottom / count as i32;
|
||||
let mut top = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..count {
|
||||
layouts.push(Rect {
|
||||
left: area.left,
|
||||
top: area.top + top,
|
||||
right: area.right,
|
||||
bottom,
|
||||
});
|
||||
|
||||
top += bottom;
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
};
|
||||
|
||||
dimensions
|
||||
.iter_mut()
|
||||
.for_each(|l| l.add_padding(container_padding));
|
||||
|
||||
dimensions
|
||||
}
|
||||
|
||||
pub fn fibonacci(
|
||||
&self,
|
||||
area: &Rect,
|
||||
count: usize,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
) -> Vec<Rect> {
|
||||
let mut dimensions = vec![];
|
||||
|
||||
for _ in 0..count {
|
||||
dimensions.push(Rect::default())
|
||||
}
|
||||
|
||||
let mut left = area.left;
|
||||
let mut top = area.top;
|
||||
let mut bottom = area.bottom;
|
||||
let mut right = area.right;
|
||||
|
||||
for i in 0..count {
|
||||
if i % 2 != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let half_width = right / 2;
|
||||
let half_height = bottom / 2;
|
||||
|
||||
let (main_x, alt_x, new_y, alt_y);
|
||||
|
||||
match layout_flip {
|
||||
Some(flip) => match flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
main_x = left + half_width;
|
||||
alt_x = left;
|
||||
|
||||
new_y = top + half_height;
|
||||
alt_y = top;
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
new_y = top;
|
||||
alt_y = top + half_height;
|
||||
|
||||
main_x = left;
|
||||
alt_x = left + half_width;
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => {
|
||||
main_x = left + half_width;
|
||||
alt_x = left;
|
||||
new_y = top;
|
||||
alt_y = top + half_height;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
main_x = left;
|
||||
alt_x = left + half_width;
|
||||
new_y = top + half_height;
|
||||
alt_y = top;
|
||||
}
|
||||
}
|
||||
|
||||
match count - i {
|
||||
1 => {
|
||||
set_dimensions(&mut dimensions[i], left, top, right, bottom);
|
||||
}
|
||||
2 => {
|
||||
set_dimensions(&mut dimensions[i], main_x, top, half_width, bottom);
|
||||
set_dimensions(&mut dimensions[i + 1], alt_x, top, half_width, bottom);
|
||||
}
|
||||
_ => {
|
||||
set_dimensions(&mut dimensions[i], main_x, top, half_width, bottom);
|
||||
set_dimensions(
|
||||
&mut dimensions[i + 1],
|
||||
alt_x,
|
||||
alt_y,
|
||||
half_width,
|
||||
half_height,
|
||||
);
|
||||
|
||||
left = alt_x;
|
||||
top = new_y;
|
||||
right = half_width;
|
||||
bottom = half_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dimensions
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn next(&mut self) {
|
||||
match self {
|
||||
Layout::BSP => *self = Layout::Columns,
|
||||
Layout::Columns => *self = Layout::Rows,
|
||||
Layout::Rows => *self = Layout::BSP,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
match self {
|
||||
Layout::BSP => *self = Layout::Rows,
|
||||
Layout::Columns => *self = Layout::BSP,
|
||||
Layout::Rows => *self = Layout::Columns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dimensions(rect: &mut Rect, left: i32, top: i32, right: i32, bottom: i32) {
|
||||
rect.bottom = bottom;
|
||||
rect.right = right;
|
||||
rect.left = left;
|
||||
rect.top = top;
|
||||
}
|
||||
105
komorebi-core/src/lib.rs
Normal file
105
komorebi-core/src/lib.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Clap;
|
||||
use color_eyre::Result;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
pub use cycle_direction::CycleDirection;
|
||||
pub use layout::Layout;
|
||||
pub use layout::LayoutFlip;
|
||||
pub use operation_direction::OperationDirection;
|
||||
pub use rect::Rect;
|
||||
|
||||
pub mod cycle_direction;
|
||||
pub mod layout;
|
||||
pub mod operation_direction;
|
||||
pub mod rect;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
|
||||
pub enum SocketMessage {
|
||||
// Window / Container Commands
|
||||
FocusWindow(OperationDirection),
|
||||
MoveWindow(OperationDirection),
|
||||
StackWindow(OperationDirection),
|
||||
UnstackWindow,
|
||||
CycleStack(CycleDirection),
|
||||
MoveContainerToMonitorNumber(usize),
|
||||
MoveContainerToWorkspaceNumber(usize),
|
||||
Promote,
|
||||
ToggleFloat,
|
||||
ToggleMonocle,
|
||||
// Current Workspace Commands
|
||||
AdjustContainerPadding(Sizing, i32),
|
||||
AdjustWorkspacePadding(Sizing, i32),
|
||||
ChangeLayout(Layout),
|
||||
FlipLayout(LayoutFlip),
|
||||
// Monitor and Workspace Commands
|
||||
Stop,
|
||||
TogglePause,
|
||||
Retile,
|
||||
FocusMonitorNumber(usize),
|
||||
FocusWorkspaceNumber(usize),
|
||||
ContainerPadding(usize, usize, i32),
|
||||
WorkspacePadding(usize, usize, i32),
|
||||
WorkspaceName(usize, usize, String),
|
||||
SetLayout(usize, usize, Layout),
|
||||
// Configuration
|
||||
FloatClass(String),
|
||||
FloatExe(String),
|
||||
FloatTitle(String),
|
||||
// TODO: Add some state query commands
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
pub fn as_bytes(&self) -> Result<Vec<u8>> {
|
||||
Ok(serde_json::to_string(self)?.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
Ok(serde_json::from_slice(bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SocketMessage {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum Sizing {
|
||||
Increase,
|
||||
Decrease,
|
||||
}
|
||||
|
||||
impl Sizing {
|
||||
pub fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {
|
||||
match self {
|
||||
Sizing::Increase => value + adjustment,
|
||||
Sizing::Decrease => {
|
||||
if value > 0 && value - adjustment >= 0 {
|
||||
value - adjustment
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum ResizeEdge {
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
91
komorebi-core/src/operation_direction.rs
Normal file
91
komorebi-core/src/operation_direction.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use clap::Clap;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::Layout;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum OperationDirection {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl OperationDirection {
|
||||
pub fn can_resize(&self, layout: Layout, idx: usize, len: usize) -> bool {
|
||||
match layout {
|
||||
Layout::BSP => match self {
|
||||
Self::Left => len != 0 && idx != 0,
|
||||
Self::Up => len > 2 && idx != 0 && idx != 1,
|
||||
Self::Right => len > 1 && idx % 2 == 0 && idx != len - 1,
|
||||
Self::Down => len > 2 && idx != len - 1 && idx % 2 != 0,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self, layout: Layout, idx: usize, len: usize) -> bool {
|
||||
match self {
|
||||
OperationDirection::Up => match layout {
|
||||
Layout::BSP => len > 2 && idx != 0 && idx != 1,
|
||||
Layout::Columns => false,
|
||||
Layout::Rows => idx != 0,
|
||||
},
|
||||
OperationDirection::Down => match layout {
|
||||
Layout::BSP => len > 2 && idx != len - 1 && idx % 2 != 0,
|
||||
Layout::Columns => false,
|
||||
Layout::Rows => idx != len - 1,
|
||||
},
|
||||
OperationDirection::Left => match layout {
|
||||
Layout::BSP => len > 1 && idx != 0,
|
||||
Layout::Columns => idx != 0,
|
||||
Layout::Rows => false,
|
||||
},
|
||||
OperationDirection::Right => match layout {
|
||||
Layout::BSP => len > 1 && idx % 2 == 0,
|
||||
Layout::Columns => idx != len - 1,
|
||||
Layout::Rows => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_idx(&self, layout: Layout, idx: usize) -> usize {
|
||||
match self {
|
||||
OperationDirection::Up => match layout {
|
||||
Layout::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 1
|
||||
} else {
|
||||
idx - 2
|
||||
}
|
||||
}
|
||||
Layout::Columns => unreachable!(),
|
||||
Layout::Rows => idx - 1,
|
||||
},
|
||||
OperationDirection::Down => match layout {
|
||||
Layout::BSP | Layout::Rows => idx + 1,
|
||||
Layout::Columns => unreachable!(),
|
||||
},
|
||||
OperationDirection::Left => match layout {
|
||||
Layout::BSP => {
|
||||
if idx % 2 == 0 {
|
||||
idx - 2
|
||||
} else {
|
||||
idx - 1
|
||||
}
|
||||
}
|
||||
Layout::Columns => idx - 1,
|
||||
Layout::Rows => unreachable!(),
|
||||
},
|
||||
OperationDirection::Right => match layout {
|
||||
Layout::BSP | Layout::Columns => idx + 1,
|
||||
Layout::Rows => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
49
komorebi-core/src/rect.rs
Normal file
49
komorebi-core/src/rect.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rect {
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
pub right: i32,
|
||||
pub bottom: i32,
|
||||
}
|
||||
|
||||
impl Default for Rect {
|
||||
fn default() -> Self {
|
||||
Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RECT> for Rect {
|
||||
fn from(rect: RECT) -> Self {
|
||||
Rect {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right - rect.left,
|
||||
bottom: rect.bottom - rect.top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn add_padding(&mut self, padding: Option<i32>) {
|
||||
if let Some(padding) = padding {
|
||||
self.left += padding;
|
||||
self.top += padding;
|
||||
self.right -= padding * 2;
|
||||
self.bottom -= padding * 2;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, point: (i32, i32)) -> bool {
|
||||
point.0 >= self.left
|
||||
&& point.0 <= self.left + self.right
|
||||
&& point.1 >= self.top
|
||||
&& point.1 <= self.top + self.bottom
|
||||
}
|
||||
}
|
||||
15
komorebi.iml
Normal file
15
komorebi.iml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUST_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/bindings/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/komorebi-core/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/komorebi/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/komorebic/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
555
komorebi/Cargo.lock
generated
Normal file
555
komorebi/Cargo.lock
generated
Normal file
@@ -0,0 +1,555 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindings"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"color-spantrace",
|
||||
"eyre",
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-spantrace"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-core",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-sha1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "komorebi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"bitflags",
|
||||
"color-eyre",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"strum",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab69019741fca4d98be3c62d2b75254528b5432233fd8a4d2739fec20278de48"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"matchers",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f1c7f11b289e450f78d55dd9dc09ae91c6ae8faed980bbf3e3a4c8f166ac259"
|
||||
dependencies = [
|
||||
"const-sha1",
|
||||
"windows_gen",
|
||||
"windows_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_gen"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57f5facfb04bc84b5fcd27018266d90ce272e11f8b91745dfdd47282e8e0607e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_macros"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c32753c378262520a4fa70c2e4389f4649e751faab2a887090567cff192d299"
|
||||
dependencies = [
|
||||
"syn",
|
||||
"windows_gen",
|
||||
]
|
||||
26
komorebi/Cargo.toml
Normal file
26
komorebi/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bindings = { package = "bindings", path = "../bindings" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
bitflags = "1.2.1"
|
||||
color-eyre = "0.5.11"
|
||||
crossbeam-channel = "0.5.1"
|
||||
crossbeam-utils = "0.8.5"
|
||||
ctrlc = "3"
|
||||
dirs = "3"
|
||||
eyre = "0.6.5"
|
||||
lazy_static = "1.4.0"
|
||||
nanoid = "0.4.0"
|
||||
strum = { version = "0.21", features = ["derive"] }
|
||||
sysinfo = "0.19"
|
||||
tracing = "0.1.26"
|
||||
tracing-appender = "0.1.2"
|
||||
tracing-subscriber = "0.2.19"
|
||||
uds_windows = "1"
|
||||
120
komorebi/src/container.rs
Normal file
120
komorebi/src/container.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use nanoid::nanoid;
|
||||
|
||||
use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Container {
|
||||
id: String,
|
||||
windows: Ring<Window>,
|
||||
}
|
||||
|
||||
impl Default for Container {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: nanoid!(),
|
||||
windows: Ring::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for &Container {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn hide(&mut self) {
|
||||
for window in self.windows_mut() {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_focused_window(&mut self) {
|
||||
let focused_idx = self.focused_window_idx();
|
||||
for (i, window) in self.windows_mut().iter_mut().enumerate() {
|
||||
if i == focused_idx {
|
||||
window.restore();
|
||||
} else {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for window in self.windows() {
|
||||
if window.hwnd == hwnd {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn idx_for_window(&self, hwnd: isize) -> Option<usize> {
|
||||
let mut idx = None;
|
||||
for (i, window) in self.windows().iter().enumerate() {
|
||||
if window.hwnd == hwnd {
|
||||
idx = Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
|
||||
self.windows_mut().remove(idx)
|
||||
}
|
||||
|
||||
pub fn remove_focused_window(&mut self) -> Option<Window> {
|
||||
let focused_idx = self.focused_window_idx();
|
||||
let window = self.remove_window_by_idx(focused_idx);
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_window(focused_idx - 1);
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
pub fn add_window(&mut self, window: Window) {
|
||||
self.windows_mut().push_back(window);
|
||||
self.focus_window(self.windows().len() - 1);
|
||||
}
|
||||
|
||||
pub fn focused_window(&self) -> Option<&Window> {
|
||||
self.windows.focused()
|
||||
}
|
||||
|
||||
pub const fn focused_window_idx(&self) -> usize {
|
||||
self.windows.focused_idx()
|
||||
}
|
||||
|
||||
pub fn focused_window_mut(&mut self) -> Option<&mut Window> {
|
||||
self.windows.focused_mut()
|
||||
}
|
||||
|
||||
pub fn focus_window(&mut self, idx: usize) {
|
||||
tracing::info!("focusing window at index: {}", idx);
|
||||
self.windows.focus(idx);
|
||||
}
|
||||
|
||||
pub const fn windows(&self) -> &VecDeque<Window> {
|
||||
self.windows.elements()
|
||||
}
|
||||
|
||||
pub fn windows_mut(&mut self) -> &mut VecDeque<Window> {
|
||||
self.windows.elements_mut()
|
||||
}
|
||||
|
||||
pub fn visible_window_mut(&mut self) -> Option<&mut Window> {
|
||||
self.focused_window_mut()
|
||||
}
|
||||
|
||||
pub const fn id(&self) -> &String {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
125
komorebi/src/main.rs
Normal file
125
komorebi/src/main.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use lazy_static::lazy_static;
|
||||
use sysinfo::SystemExt;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::process_command::listen_for_commands;
|
||||
use crate::process_event::listen_for_events;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
mod container;
|
||||
mod monitor;
|
||||
mod process_command;
|
||||
mod process_event;
|
||||
mod ring;
|
||||
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 FLOAT_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_EXES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref FLOAT_TITLES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
|
||||
}
|
||||
|
||||
fn setup() -> Result<WorkerGuard> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let appender = tracing_appender::rolling::never(home, "komorebi.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
||||
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::fmt::Subscriber::builder()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.finish()
|
||||
.with(
|
||||
tracing_subscriber::fmt::Layer::default()
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
match std::env::args().count() {
|
||||
1 => {
|
||||
let mut system = sysinfo::System::new_all();
|
||||
system.refresh_processes();
|
||||
|
||||
if system.process_by_name("komorebi.exe").len() > 1 {
|
||||
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let _guard = setup()?;
|
||||
|
||||
let process_id = WindowsApi::current_process_id();
|
||||
WindowsApi::allow_set_foreground_window(process_id)?;
|
||||
|
||||
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
crossbeam_channel::unbounded();
|
||||
|
||||
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||
winevent_listener.start();
|
||||
|
||||
let wm = Arc::new(Mutex::new(window_manager::new(Arc::new(Mutex::new(
|
||||
incoming,
|
||||
)))?));
|
||||
|
||||
wm.lock().unwrap().init()?;
|
||||
listen_for_commands(wm.clone());
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||
ctrlc::set_handler(move || {
|
||||
ctrlc_sender
|
||||
.send(())
|
||||
.expect("could not send signal on ctrl-c channel");
|
||||
})?;
|
||||
|
||||
ctrlc_receiver
|
||||
.recv()
|
||||
.expect("could not receive signal on ctrl-c channel");
|
||||
|
||||
tracing::error!(
|
||||
"received ctrl-c, restoring all hidden windows and terminating process"
|
||||
);
|
||||
wm.lock().unwrap().restore_all_windows();
|
||||
std::process::exit(130);
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
152
komorebi/src/monitor.rs
Normal file
152
komorebi/src/monitor.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Monitor {
|
||||
id: isize,
|
||||
monitor_size: Rect,
|
||||
work_area_size: Rect,
|
||||
workspaces: Ring<Workspace>,
|
||||
workspace_names: HashMap<usize, String>,
|
||||
}
|
||||
|
||||
pub fn new(id: isize, monitor_size: Rect, work_area_size: Rect) -> Monitor {
|
||||
Monitor {
|
||||
id,
|
||||
monitor_size,
|
||||
work_area_size,
|
||||
workspaces: Ring::default(),
|
||||
workspace_names: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
pub fn load_focused_workspace(&mut self) {
|
||||
let focused_idx = self.focused_workspace_idx();
|
||||
for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
|
||||
if i == focused_idx {
|
||||
workspace.restore();
|
||||
} else {
|
||||
workspace.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_container(&mut self, container: Container) -> Result<()> {
|
||||
let workspace = self
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?;
|
||||
|
||||
workspace.add_container(container);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_container_to_workspace(
|
||||
&mut self,
|
||||
target_workspace_idx: usize,
|
||||
follow: bool,
|
||||
) -> Result<()> {
|
||||
let container = self
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
|
||||
let workspaces = self.workspaces_mut();
|
||||
|
||||
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
||||
None => {
|
||||
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
||||
workspaces.get_mut(target_workspace_idx).unwrap()
|
||||
}
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
target_workspace.add_container(container);
|
||||
|
||||
if follow {
|
||||
self.focus_workspace(target_workspace_idx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn focused_workspace(&self) -> Option<&Workspace> {
|
||||
self.workspaces.focused()
|
||||
}
|
||||
|
||||
pub const fn focused_workspace_idx(&self) -> usize {
|
||||
self.workspaces.focused_idx()
|
||||
}
|
||||
|
||||
pub fn focused_workspace_mut(&mut self) -> Option<&mut Workspace> {
|
||||
self.workspaces.focused_mut()
|
||||
}
|
||||
|
||||
pub fn focus_workspace(&mut self, idx: usize) -> Result<()> {
|
||||
{
|
||||
let workspaces = self.workspaces_mut();
|
||||
|
||||
tracing::info!("focusing workspace at index: {}", idx);
|
||||
if workspaces.get(idx).is_none() {
|
||||
workspaces.resize(idx + 1, Workspace::default());
|
||||
}
|
||||
|
||||
self.workspaces.focus(idx);
|
||||
}
|
||||
|
||||
// Always set the latest known name when creating the workspace for the first time
|
||||
{
|
||||
let name = { self.workspace_names.get(&idx).cloned() };
|
||||
if name.is_some() {
|
||||
self.workspaces_mut()
|
||||
.get_mut(idx)
|
||||
.context("there is no workspace")?
|
||||
.set_name(name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_focused_workspace(&mut self) -> Result<()> {
|
||||
tracing::info!("updating workspace: {}", self.focused_workspace_idx());
|
||||
let work_area = self.work_area_size().clone();
|
||||
|
||||
self.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.update(&work_area)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const fn workspaces(&self) -> &VecDeque<Workspace> {
|
||||
self.workspaces.elements()
|
||||
}
|
||||
|
||||
pub fn workspaces_mut(&mut self) -> &mut VecDeque<Workspace> {
|
||||
self.workspaces.elements_mut()
|
||||
}
|
||||
|
||||
pub fn workspace_names_mut(&mut self) -> &mut HashMap<usize, String> {
|
||||
&mut self.workspace_names
|
||||
}
|
||||
|
||||
pub const fn id(&self) -> isize {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub const fn work_area_size(&self) -> &Rect {
|
||||
&self.work_area_size
|
||||
}
|
||||
}
|
||||
147
komorebi/src/process_command.rs
Normal file
147
komorebi/src/process_command.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use komorebi_core::SocketMessage;
|
||||
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::FLOAT_CLASSES;
|
||||
use crate::FLOAT_EXES;
|
||||
use crate::FLOAT_TITLES;
|
||||
|
||||
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||
let listener = wm
|
||||
.lock()
|
||||
.unwrap()
|
||||
.command_listener
|
||||
.try_clone()
|
||||
.expect("could not clone unix listener");
|
||||
|
||||
thread::spawn(move || {
|
||||
tracing::info!("listening for commands");
|
||||
for client in listener.incoming() {
|
||||
match client {
|
||||
Ok(stream) => match wm.lock().unwrap().process_command(stream) {
|
||||
Ok(()) => tracing::info!("command processed"),
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::error!("{}", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub fn process_command(&mut self, stream: UnixStream) -> Result<()> {
|
||||
let stream = BufReader::new(stream);
|
||||
for line in stream.lines() {
|
||||
let message = SocketMessage::from_str(&line?)?;
|
||||
|
||||
if self.is_paused {
|
||||
if let SocketMessage::TogglePause = message {
|
||||
tracing::info!("resuming window management");
|
||||
self.is_paused = !self.is_paused;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("ignoring commands while paused");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("processing command: {}", &message);
|
||||
match message {
|
||||
SocketMessage::Promote => self.promote_container_to_front()?,
|
||||
SocketMessage::FocusWindow(direction) => {
|
||||
self.focus_container_in_direction(direction)?;
|
||||
}
|
||||
SocketMessage::MoveWindow(direction) => {
|
||||
self.move_container_in_direction(direction)?;
|
||||
}
|
||||
SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?,
|
||||
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
|
||||
SocketMessage::CycleStack(direction) => {
|
||||
self.cycle_container_window_in_direction(direction)?;
|
||||
}
|
||||
SocketMessage::ToggleFloat => self.toggle_float()?,
|
||||
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
|
||||
SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {
|
||||
self.set_container_padding(monitor_idx, workspace_idx, size)?;
|
||||
}
|
||||
SocketMessage::WorkspacePadding(monitor_idx, workspace_idx, size) => {
|
||||
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
|
||||
}
|
||||
SocketMessage::FloatClass(target) => {
|
||||
let mut float_classes = FLOAT_CLASSES.lock().unwrap();
|
||||
if !float_classes.contains(&target) {
|
||||
float_classes.push(target);
|
||||
}
|
||||
}
|
||||
SocketMessage::FloatExe(target) => {
|
||||
let mut float_exes = FLOAT_EXES.lock().unwrap();
|
||||
if !float_exes.contains(&target) {
|
||||
float_exes.push(target);
|
||||
}
|
||||
}
|
||||
SocketMessage::FloatTitle(target) => {
|
||||
let mut float_titles = FLOAT_TITLES.lock().unwrap();
|
||||
if !float_titles.contains(&target) {
|
||||
float_titles.push(target);
|
||||
}
|
||||
}
|
||||
SocketMessage::AdjustContainerPadding(sizing, adjustment) => {
|
||||
self.adjust_container_padding(sizing, adjustment)?;
|
||||
}
|
||||
SocketMessage::AdjustWorkspacePadding(sizing, adjustment) => {
|
||||
self.adjust_workspace_padding(sizing, adjustment)?;
|
||||
}
|
||||
SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => {
|
||||
self.move_container_to_workspace(workspace_idx, true)?;
|
||||
}
|
||||
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
|
||||
self.move_container_to_monitor(monitor_idx, true)?;
|
||||
}
|
||||
SocketMessage::TogglePause => self.is_paused = !self.is_paused,
|
||||
SocketMessage::FocusMonitorNumber(monitor_idx) => {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
self.update_focused_workspace(true)?;
|
||||
}
|
||||
SocketMessage::Retile => {
|
||||
for monitor in self.monitors_mut() {
|
||||
let work_area = monitor.work_area_size().clone();
|
||||
monitor
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.update(&work_area)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
|
||||
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout(layout)?,
|
||||
SocketMessage::SetLayout(monitor_idx, workspace_idx, layout) => {
|
||||
self.set_workspace_layout(monitor_idx, workspace_idx, layout)?;
|
||||
}
|
||||
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
SocketMessage::Stop => {
|
||||
tracing::error!("received stop command, restoring all hidden windows and terminating process");
|
||||
self.restore_all_windows();
|
||||
std::process::exit(0)
|
||||
}
|
||||
SocketMessage::WorkspaceName(monitor_idx, workspace_idx, name) => {
|
||||
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
124
komorebi/src/process_event.rs
Normal file
124
komorebi/src/process_event.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::select;
|
||||
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
|
||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||
let receiver = wm.lock().unwrap().incoming_events.lock().unwrap().clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
tracing::info!("listening for events");
|
||||
loop {
|
||||
select! {
|
||||
recv(receiver) -> mut maybe_event => {
|
||||
if let Ok(event) = maybe_event.as_mut() {
|
||||
match wm.lock().unwrap().process_event(event) {
|
||||
Ok(()) => {},
|
||||
Err(error) => tracing::error!("{}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub fn process_event(&mut self, event: &mut WindowManagerEvent) -> Result<()> {
|
||||
if self.is_paused {
|
||||
tracing::info!("ignoring events while paused");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Make sure we have the most recently focused monitor from any event
|
||||
match event {
|
||||
WindowManagerEvent::FocusChange(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::MoveResizeStart(_, window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||
let monitor_idx = self
|
||||
.monitor_idx_from_window(window)
|
||||
.context("there is no monitor associated with this window, it may have already been destroyed")?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||
let work_area = monitor.work_area_size().clone();
|
||||
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||
let reaped_orphans = workspace.reap_orphans()?;
|
||||
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
||||
workspace.update(&work_area)?;
|
||||
tracing::info!(
|
||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||
reaped_orphans.0,
|
||||
reaped_orphans.1,
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(event, WindowManagerEvent::MouseCapture(_, _)) {
|
||||
tracing::trace!("only reaping orphans for mouse capture event");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("processing event: {}", event);
|
||||
|
||||
match event {
|
||||
WindowManagerEvent::Minimize(_, window) | WindowManagerEvent::Destroy(_, window) => {
|
||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
|
||||
WindowManagerEvent::Hide(_, window) => {
|
||||
// explorer.exe is always returns true even if it's running in the background,
|
||||
// but we want to be able to handle the event when File Explorer windows close properly
|
||||
if !window.is_window() || window.exe()? == "explorer.exe" {
|
||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::FocusChange(_, window) => self
|
||||
.focused_workspace_mut()?
|
||||
.focus_container_by_window(window.hwnd)?,
|
||||
WindowManagerEvent::Show(_, window) => {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if workspace.containers().is_empty() || !workspace.contains_window(window.hwnd) {
|
||||
workspace.new_container_for_window(*window);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::MoveResizeStart(_, _window) => {
|
||||
// TODO: Implement dragging resize (one day)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(_, _window) => {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
let focused_idx = workspace.focused_container_idx();
|
||||
|
||||
match workspace.container_idx_from_current_point() {
|
||||
Some(target_idx) => {
|
||||
workspace.swap_containers(focused_idx, target_idx);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
None => self.update_focused_workspace(true)?,
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::MouseCapture(..) => {}
|
||||
};
|
||||
|
||||
tracing::info!("finished processing event: {}", event);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
46
komorebi/src/ring.rs
Normal file
46
komorebi/src/ring.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ring<T> {
|
||||
elements: VecDeque<T>,
|
||||
focused: usize,
|
||||
}
|
||||
|
||||
impl<T> Default for Ring<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
elements: VecDeque::default(),
|
||||
focused: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Ring<T> {
|
||||
pub const fn elements(&self) -> &VecDeque<T> {
|
||||
&self.elements
|
||||
}
|
||||
|
||||
pub fn elements_mut(&mut self) -> &mut VecDeque<T> {
|
||||
&mut self.elements
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, idx: usize) {
|
||||
self.focused = idx;
|
||||
}
|
||||
|
||||
pub fn focused(&self) -> Option<&T> {
|
||||
self.elements.get(self.focused)
|
||||
}
|
||||
|
||||
pub const fn focused_idx(&self) -> usize {
|
||||
self.focused
|
||||
}
|
||||
|
||||
pub fn focused_mut(&mut self) -> Option<&mut T> {
|
||||
self.elements.get_mut(self.focused)
|
||||
}
|
||||
|
||||
pub fn swap(&mut self, i: usize, j: usize) {
|
||||
self.elements.swap(i, j);
|
||||
}
|
||||
}
|
||||
122
komorebi/src/styles.rs
Normal file
122
komorebi/src/styles.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CHILDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CLIPCHILDREN;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_CLIPSIBLINGS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_DLGFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_ACCEPTFILES;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_APPWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CLIENTEDGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_COMPOSITED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTEXTHELP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTROLPARENT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_DLGMODALFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYOUTRTL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFTSCROLLBAR;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_LTRREADING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_MDICHILD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOINHERITLAYOUT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOPARENTNOTIFY;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_PALETTEWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHTSCROLLBAR;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_RTLREADING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_STATICEDGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_TRANSPARENT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_EX_WINDOWEDGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_GROUP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_HSCROLL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_ICONIC;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_POPUPWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_SIZEBOX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TABSTOP;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_THICKFRAME;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TILED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_TILEDWINDOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct GwlStyle: u32 {
|
||||
const BORDER = WS_BORDER.0;
|
||||
const CAPTION = WS_CAPTION.0;
|
||||
const CHILD = WS_CHILD.0;
|
||||
const CHILDWINDOW = WS_CHILDWINDOW.0;
|
||||
const CLIPCHILDREN = WS_CLIPCHILDREN.0;
|
||||
const CLIPSIBLINGS = WS_CLIPSIBLINGS.0;
|
||||
const DISABLED = WS_DISABLED.0;
|
||||
const DLGFRAME = WS_DLGFRAME.0;
|
||||
const GROUP = WS_GROUP.0;
|
||||
const HSCROLL = WS_HSCROLL.0;
|
||||
const ICONIC = WS_ICONIC.0;
|
||||
const MAXIMIZE = WS_MAXIMIZE.0;
|
||||
const MAXIMIZEBOX = WS_MAXIMIZEBOX.0;
|
||||
const MINIMIZE = WS_MINIMIZE.0;
|
||||
const MINIMIZEBOX = WS_MINIMIZEBOX.0;
|
||||
const OVERLAPPED = WS_OVERLAPPED.0;
|
||||
const OVERLAPPEDWINDOW = WS_OVERLAPPEDWINDOW.0;
|
||||
const POPUP = WS_POPUP.0;
|
||||
const POPUPWINDOW = WS_POPUPWINDOW.0;
|
||||
const SIZEBOX = WS_SIZEBOX.0;
|
||||
const SYSMENU = WS_SYSMENU.0;
|
||||
const TABSTOP = WS_TABSTOP.0;
|
||||
const THICKFRAME = WS_THICKFRAME.0;
|
||||
const TILED = WS_TILED.0;
|
||||
const TILEDWINDOW = WS_TILEDWINDOW.0;
|
||||
const VISIBLE = WS_VISIBLE.0;
|
||||
const VSCROLL = WS_VSCROLL.0;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
pub struct GwlExStyle: u32 {
|
||||
const ACCEPTFILES = WS_EX_ACCEPTFILES.0;
|
||||
const APPWINDOW = WS_EX_APPWINDOW.0;
|
||||
const CLIENTEDGE = WS_EX_CLIENTEDGE.0;
|
||||
const COMPOSITED = WS_EX_COMPOSITED.0;
|
||||
const CONTEXTHELP = WS_EX_CONTEXTHELP.0;
|
||||
const CONTROLPARENT = WS_EX_CONTROLPARENT.0;
|
||||
const DLGMODALFRAME = WS_EX_DLGMODALFRAME.0;
|
||||
const LAYERED = WS_EX_LAYERED.0;
|
||||
const LAYOUTRTL = WS_EX_LAYOUTRTL.0;
|
||||
const LEFT = WS_EX_LEFT.0;
|
||||
const LEFTSCROLLBAR = WS_EX_LEFTSCROLLBAR.0;
|
||||
const LTRREADING = WS_EX_LTRREADING.0;
|
||||
const MDICHILD = WS_EX_MDICHILD.0;
|
||||
const NOACTIVATE = WS_EX_NOACTIVATE.0;
|
||||
const NOINHERITLAYOUT = WS_EX_NOINHERITLAYOUT.0;
|
||||
const NOPARENTNOTIFY = WS_EX_NOPARENTNOTIFY.0;
|
||||
const NOREDIRECTIONBITMAP = WS_EX_NOREDIRECTIONBITMAP.0;
|
||||
const OVERLAPPEDWINDOW = WS_EX_OVERLAPPEDWINDOW.0;
|
||||
const PALETTEWINDOW = WS_EX_PALETTEWINDOW.0;
|
||||
const RIGHT = WS_EX_RIGHT.0;
|
||||
const RIGHTSCROLLBAR = WS_EX_RIGHTSCROLLBAR.0;
|
||||
const RTLREADING = WS_EX_RTLREADING.0;
|
||||
const STATICEDGE = WS_EX_STATICEDGE.0;
|
||||
const TOOLWINDOW = WS_EX_TOOLWINDOW.0;
|
||||
const TOPMOST = WS_EX_TOPMOST.0;
|
||||
const TRANSPARENT = WS_EX_TRANSPARENT.0;
|
||||
const WINDOWEDGE = WS_EX_WINDOWEDGE.0;
|
||||
}
|
||||
}
|
||||
211
komorebi/src/window.rs
Normal file
211
komorebi/src/window.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::styles::GwlExStyle;
|
||||
use crate::styles::GwlStyle;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::FLOAT_CLASSES;
|
||||
use crate::FLOAT_EXES;
|
||||
use crate::FLOAT_TITLES;
|
||||
use crate::LAYERED_EXE_WHITELIST;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Window {
|
||||
pub(crate) hwnd: isize,
|
||||
pub(crate) original_style: GwlStyle,
|
||||
}
|
||||
|
||||
impl Display for Window {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut display = format!("(hwnd: {}", self.hwnd);
|
||||
|
||||
if let Ok(title) = self.title() {
|
||||
display.push_str(&format!(", title: {}", title));
|
||||
}
|
||||
|
||||
if let Ok(exe) = self.exe() {
|
||||
display.push_str(&format!(", exe: {}", exe));
|
||||
}
|
||||
|
||||
if let Ok(class) = self.class() {
|
||||
display.push_str(&format!(", class: {}", class));
|
||||
}
|
||||
|
||||
display.push(')');
|
||||
|
||||
write!(f, "{}", display)
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub const fn hwnd(&self) -> HWND {
|
||||
HWND(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, layout: &Rect) -> Result<()> {
|
||||
WindowsApi::set_window_pos(self.hwnd(), layout)
|
||||
}
|
||||
|
||||
pub fn hide(&self) {
|
||||
WindowsApi::hide_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn restore(&self) {
|
||||
WindowsApi::restore_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn focus(&self) -> Result<()> {
|
||||
// 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)?;
|
||||
|
||||
// Raise Window to foreground
|
||||
WindowsApi::set_foreground_window(self.hwnd())?;
|
||||
|
||||
// Center cursor in Window
|
||||
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
||||
|
||||
// This isn't really needed when the above command works as expected via AHK
|
||||
WindowsApi::set_focus(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn update_style(&self, style: GwlStyle) -> Result<()> {
|
||||
WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?)
|
||||
}
|
||||
|
||||
pub fn restore_style(&self) -> Result<()> {
|
||||
self.update_style(self.original_style)
|
||||
}
|
||||
|
||||
pub fn remove_border(&self) -> Result<()> {
|
||||
let mut style = self.style()?;
|
||||
style.remove(GwlStyle::BORDER);
|
||||
self.update_style(style)
|
||||
}
|
||||
|
||||
pub fn add_border(&self) -> Result<()> {
|
||||
let mut style = self.style()?;
|
||||
style.insert(GwlStyle::BORDER);
|
||||
self.update_style(style)
|
||||
}
|
||||
|
||||
pub fn remove_padding_and_title_bar(&self) -> Result<()> {
|
||||
let mut style = self.style()?;
|
||||
style.remove(GwlStyle::THICKFRAME);
|
||||
style.remove(GwlStyle::CAPTION);
|
||||
self.update_style(style)
|
||||
}
|
||||
|
||||
pub fn add_padding_padding_and_title_bar(&self) -> Result<()> {
|
||||
let mut style = self.style()?;
|
||||
style.insert(GwlStyle::THICKFRAME);
|
||||
style.insert(GwlStyle::CAPTION);
|
||||
self.update_style(style)
|
||||
}
|
||||
|
||||
pub fn style(&self) -> Result<GwlStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
|
||||
GwlStyle::from_bits(bits).context("there is no gwl style")
|
||||
}
|
||||
|
||||
pub fn ex_style(&self) -> Result<GwlExStyle> {
|
||||
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
|
||||
GwlExStyle::from_bits(bits).context("there is no gwl style")
|
||||
}
|
||||
|
||||
pub fn title(&self) -> Result<String> {
|
||||
WindowsApi::window_text_w(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn exe(&self) -> Result<String> {
|
||||
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
WindowsApi::exe(WindowsApi::process_handle(process_id)?)
|
||||
}
|
||||
|
||||
pub fn class(&self) -> Result<String> {
|
||||
WindowsApi::real_window_class_w(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn is_cloaked(&self) -> Result<bool> {
|
||||
WindowsApi::is_window_cloaked(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn is_window(self) -> bool {
|
||||
WindowsApi::is_window(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn should_manage(&self, event: Option<WindowManagerEvent>) -> Result<bool> {
|
||||
let classes = FLOAT_CLASSES.lock().unwrap();
|
||||
let exes = FLOAT_EXES.lock().unwrap();
|
||||
let titles = FLOAT_TITLES.lock().unwrap();
|
||||
|
||||
if self.title().is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let is_cloaked = self.is_cloaked()?;
|
||||
|
||||
let mut allow_cloaked = false;
|
||||
if let Some(WindowManagerEvent::Hide(_, _)) = event {
|
||||
allow_cloaked = true;
|
||||
}
|
||||
|
||||
match (allow_cloaked, is_cloaked) {
|
||||
// If allowing cloaked windows, we don't need to check the cloaked status
|
||||
(true, _) |
|
||||
// If not allowing cloaked windows, we need to ensure the window is not cloaked
|
||||
(false, false) => {
|
||||
if let (Ok(title), Ok(exe_name)) = (self.title(), self.exe()) {
|
||||
if titles.contains(&title) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if exes.contains(&exe_name) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Ok(class) = self.class() {
|
||||
if classes.contains(&class) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
let allow_layered = LAYERED_EXE_WHITELIST.lock().unwrap().contains(&exe_name);
|
||||
|
||||
let style = self.style()?;
|
||||
let ex_style = self.ex_style()?;
|
||||
|
||||
if style.contains(GwlStyle::CAPTION)
|
||||
&& ex_style.contains(GwlExStyle::WINDOWEDGE)
|
||||
&& !ex_style.contains(GwlExStyle::DLGMODALFRAME)
|
||||
// Get a lot of dupe events coming through that make the redrawing go crazy
|
||||
// on FocusChange events if I don't filter out this one. But, if we are
|
||||
// allowing a specific layered window on the whitelist (like Steam), it should
|
||||
// pass this check
|
||||
&& (allow_layered || !ex_style.contains(GwlExStyle::LAYERED))
|
||||
{
|
||||
Ok(true)
|
||||
} else {
|
||||
if let Some(event) = event {
|
||||
tracing::debug!("ignoring window: {} (event: {})", self, event);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
546
komorebi/src/window_manager.rs
Normal file
546
komorebi/src/window_manager.rs
Normal file
@@ -0,0 +1,546 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::io::ErrorKind;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use uds_windows::UnixListener;
|
||||
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::LayoutFlip;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
use komorebi_core::Sizing;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowManager {
|
||||
pub monitors: Ring<Monitor>,
|
||||
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||
pub command_listener: UnixListener,
|
||||
pub is_paused: bool,
|
||||
}
|
||||
|
||||
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<WindowManager> {
|
||||
let home = dirs::home_dir().context("there is no home directory")?;
|
||||
let mut socket = home;
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
match std::fs::remove_file(&socket) {
|
||||
Ok(_) => {}
|
||||
Err(error) => match error.kind() {
|
||||
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||
ErrorKind::NotFound => {}
|
||||
_ => {
|
||||
return Err(error.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let listener = UnixListener::bind(&socket)?;
|
||||
|
||||
Ok(WindowManager {
|
||||
monitors: Ring::default(),
|
||||
incoming_events: incoming,
|
||||
command_listener: listener,
|
||||
is_paused: false,
|
||||
})
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
tracing::info!("initialising");
|
||||
WindowsApi::load_monitor_information(&mut self.monitors)?;
|
||||
WindowsApi::load_workspace_information(&mut self.monitors)?;
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||
tracing::info!("updating monitor: {}", self.focused_monitor_idx());
|
||||
|
||||
self.focused_monitor_mut()
|
||||
.context("there is no monitor")?
|
||||
.update_focused_workspace()?;
|
||||
|
||||
if mouse_follows_focus {
|
||||
self.focused_window_mut()?.focus()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore_all_windows(&mut self) {
|
||||
for monitor in self.monitors_mut() {
|
||||
for workspace in monitor.workspaces_mut() {
|
||||
for containers in workspace.containers_mut() {
|
||||
for window in containers.windows_mut() {
|
||||
window.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_container_to_monitor(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
|
||||
let container = monitor
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
|
||||
let target_monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
target_monitor.add_container(container)?;
|
||||
target_monitor.load_focused_workspace();
|
||||
|
||||
if follow {
|
||||
self.focus_monitor(idx)?;
|
||||
}
|
||||
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> {
|
||||
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
|
||||
monitor.move_container_to_workspace(idx, follow)?;
|
||||
monitor.load_focused_workspace();
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.context("this is not a valid direction from the current position")?;
|
||||
|
||||
workspace.focus_container(new_idx);
|
||||
self.focused_window_mut()?.focus()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let current_idx = workspace.focused_container_idx();
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.context("this is not a valid direction from the current position")?;
|
||||
|
||||
workspace.swap_containers(current_idx, new_idx);
|
||||
workspace.focus_container(new_idx);
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
||||
let container = self.focused_container_mut()?;
|
||||
|
||||
if container.windows().len() == 1 {
|
||||
return Err(eyre::anyhow!("there is only one window in this container"));
|
||||
}
|
||||
|
||||
let current_idx = container.focused_window_idx();
|
||||
let next_idx = direction.next_idx(current_idx, container.windows().len());
|
||||
|
||||
container.focus_window(next_idx);
|
||||
container.load_focused_window();
|
||||
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn add_window_to_container(&mut self, direction: OperationDirection) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
let current_container_idx = workspace.focused_container_idx();
|
||||
|
||||
let is_valid = direction.is_valid(
|
||||
workspace.layout(),
|
||||
workspace.focused_container_idx(),
|
||||
workspace.containers_mut().len(),
|
||||
);
|
||||
|
||||
if is_valid {
|
||||
let new_idx = workspace
|
||||
.new_idx_for_direction(direction)
|
||||
.context("this is not a valid direction from the current position")?;
|
||||
|
||||
let adjusted_new_index = if new_idx > current_container_idx {
|
||||
new_idx - 1
|
||||
} else {
|
||||
new_idx
|
||||
};
|
||||
|
||||
workspace.move_window_to_container(adjusted_new_index)?;
|
||||
self.update_focused_workspace(true)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn promote_container_to_front(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.promote_container()?;
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn remove_window_from_container(&mut self) -> Result<()> {
|
||||
if self.focused_container()?.windows().len() == 1 {
|
||||
return Err(eyre::anyhow!("a container must have at least one window"));
|
||||
}
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
workspace.new_container_for_focused_window()?;
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn toggle_float(&mut self) -> Result<()> {
|
||||
let hwnd = WindowsApi::foreground_window()?;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let mut is_floating_window = false;
|
||||
|
||||
for window in workspace.floating_windows() {
|
||||
if window.hwnd == hwnd {
|
||||
is_floating_window = true;
|
||||
}
|
||||
}
|
||||
|
||||
if is_floating_window {
|
||||
self.unfloat_window()?;
|
||||
} else {
|
||||
self.float_window()?;
|
||||
}
|
||||
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn float_window(&mut self) -> Result<()> {
|
||||
let work_area = self.focused_monitor_work_area()?;
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.new_floating_window()?;
|
||||
|
||||
let window = workspace
|
||||
.floating_windows_mut()
|
||||
.last_mut()
|
||||
.context("there is no floating window")?;
|
||||
|
||||
let half_width = work_area.right / 2;
|
||||
let half_weight = work_area.bottom / 2;
|
||||
|
||||
let center = Rect {
|
||||
left: work_area.left + ((work_area.right - half_width) / 2),
|
||||
top: work_area.top + ((work_area.bottom - half_weight) / 2),
|
||||
right: half_width,
|
||||
bottom: half_weight,
|
||||
};
|
||||
|
||||
window.set_position(¢er)?;
|
||||
window.focus()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unfloat_window(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.new_container_for_floating_window()
|
||||
}
|
||||
|
||||
pub fn toggle_monocle(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
match workspace.monocle_container() {
|
||||
None => self.monocle_on()?,
|
||||
Some(_) => self.monocle_off()?,
|
||||
}
|
||||
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn monocle_on(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.new_monocle_container()
|
||||
}
|
||||
|
||||
pub fn monocle_off(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.reintegrate_monocle_container()
|
||||
}
|
||||
|
||||
pub fn flip_layout(&mut self, layout_flip: LayoutFlip) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match workspace.layout_flip() {
|
||||
None => workspace.set_layout_flip(Option::from(layout_flip)),
|
||||
Some(current_layout_flip) => {
|
||||
match current_layout_flip {
|
||||
LayoutFlip::Horizontal => match layout_flip {
|
||||
LayoutFlip::Horizontal => workspace.set_layout_flip(None),
|
||||
LayoutFlip::Vertical => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
LayoutFlip::HorizontalAndVertical => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
},
|
||||
LayoutFlip::Vertical => match layout_flip {
|
||||
LayoutFlip::Horizontal => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
LayoutFlip::Vertical => workspace.set_layout_flip(None),
|
||||
LayoutFlip::HorizontalAndVertical => workspace
|
||||
.set_layout_flip(Option::from(LayoutFlip::HorizontalAndVertical)),
|
||||
},
|
||||
LayoutFlip::HorizontalAndVertical => {
|
||||
match layout_flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
workspace.set_layout_flip(Option::from(LayoutFlip::Vertical));
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
workspace.set_layout_flip(Option::from(LayoutFlip::Horizontal));
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => workspace.set_layout_flip(None),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
pub fn change_workspace_layout(&mut self, layout: Layout) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.set_layout(layout);
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
pub fn adjust_workspace_padding(&mut self, sizing: Sizing, adjustment: i32) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let padding = workspace
|
||||
.workspace_padding()
|
||||
.context("there is no workspace padding")?;
|
||||
|
||||
workspace.set_workspace_padding(Option::from(sizing.adjust_by(padding, adjustment)));
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
pub fn adjust_container_padding(&mut self, sizing: Sizing, adjustment: i32) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let padding = workspace
|
||||
.container_padding()
|
||||
.context("there is no container padding")?;
|
||||
|
||||
workspace.set_container_padding(Option::from(sizing.adjust_by(padding, adjustment)));
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
pub fn set_workspace_layout(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
layout: Layout,
|
||||
) -> Result<()> {
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
let work_area = monitor.work_area_size().clone();
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
workspace.set_layout(layout);
|
||||
|
||||
// If this is the focused workspace on a non-focused screen, let's update it
|
||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||
workspace.update(&work_area)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(self.update_focused_workspace(false)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_workspace_padding(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
size: i32,
|
||||
) -> Result<()> {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
workspace.set_workspace_padding(Option::from(size));
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
pub fn set_workspace_name(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
name: String,
|
||||
) -> Result<()> {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
workspace.set_name(Option::from(name.clone()));
|
||||
monitor.workspace_names_mut().insert(workspace_idx, name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_container_padding(
|
||||
&mut self,
|
||||
monitor_idx: usize,
|
||||
workspace_idx: usize,
|
||||
size: i32,
|
||||
) -> Result<()> {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.context("there is no monitor")?;
|
||||
|
||||
workspace.set_container_padding(Option::from(size));
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub const fn monitors(&self) -> &VecDeque<Monitor> {
|
||||
self.monitors.elements()
|
||||
}
|
||||
|
||||
pub fn monitors_mut(&mut self) -> &mut VecDeque<Monitor> {
|
||||
self.monitors.elements_mut()
|
||||
}
|
||||
|
||||
pub fn focused_monitor(&self) -> Option<&Monitor> {
|
||||
self.monitors.focused()
|
||||
}
|
||||
|
||||
pub const fn focused_monitor_idx(&self) -> usize {
|
||||
self.monitors.focused_idx()
|
||||
}
|
||||
|
||||
pub fn focused_monitor_mut(&mut self) -> Option<&mut Monitor> {
|
||||
self.monitors.focused_mut()
|
||||
}
|
||||
|
||||
pub fn focused_monitor_work_area(&self) -> Result<Rect> {
|
||||
Ok(self
|
||||
.focused_monitor()
|
||||
.context("there is no monitor")?
|
||||
.work_area_size()
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub fn focus_monitor(&mut self, idx: usize) -> Result<()> {
|
||||
if self.monitors().get(idx).is_some() {
|
||||
self.monitors.focus(idx);
|
||||
} else {
|
||||
return Err(eyre::anyhow!("this is not a valid monitor index"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn monitor_idx_from_window(&mut self, window: &Window) -> Option<usize> {
|
||||
let hmonitor = WindowsApi::monitor_from_window(window.hwnd());
|
||||
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
if monitor.id() == hmonitor {
|
||||
return Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn focused_workspace(&self) -> Result<&Workspace> {
|
||||
self.focused_monitor()
|
||||
.context("there is no monitor")?
|
||||
.focused_workspace()
|
||||
.context("there is no workspace")
|
||||
}
|
||||
|
||||
pub fn focused_workspace_mut(&mut self) -> Result<&mut Workspace> {
|
||||
self.focused_monitor_mut()
|
||||
.context("there is no monitor")?
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")
|
||||
}
|
||||
|
||||
pub fn focus_workspace(&mut self, idx: usize) -> Result<()> {
|
||||
let monitor = self
|
||||
.focused_monitor_mut()
|
||||
.context("there is no workspace")?;
|
||||
|
||||
monitor.focus_workspace(idx)?;
|
||||
monitor.load_focused_workspace();
|
||||
|
||||
self.update_focused_workspace(true)
|
||||
}
|
||||
|
||||
pub fn focused_container(&self) -> Result<&Container> {
|
||||
self.focused_workspace()?
|
||||
.focused_container()
|
||||
.context("there is no container")
|
||||
}
|
||||
|
||||
pub fn focused_container_mut(&mut self) -> Result<&mut Container> {
|
||||
self.focused_workspace_mut()?
|
||||
.focused_container_mut()
|
||||
.context("there is no container")
|
||||
}
|
||||
|
||||
fn focused_window_mut(&mut self) -> Result<&mut Window> {
|
||||
self.focused_container_mut()?
|
||||
.focused_window_mut()
|
||||
.context("there is no window")
|
||||
}
|
||||
}
|
||||
90
komorebi/src/window_manager_event.rs
Normal file
90
komorebi/src/window_manager_event.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::window::Window;
|
||||
use crate::winevent::WinEvent;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum WindowManagerEvent {
|
||||
Destroy(WinEvent, Window),
|
||||
FocusChange(WinEvent, Window),
|
||||
Hide(WinEvent, Window),
|
||||
Minimize(WinEvent, Window),
|
||||
Show(WinEvent, Window),
|
||||
MoveResizeStart(WinEvent, Window),
|
||||
MoveResizeEnd(WinEvent, Window),
|
||||
MouseCapture(WinEvent, Window),
|
||||
}
|
||||
|
||||
impl Display for WindowManagerEvent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WindowManagerEvent::Destroy(winevent, window) => {
|
||||
write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::FocusChange(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"FocusChange (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::Hide(winevent, window) => {
|
||||
write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::Minimize(winevent, window) => {
|
||||
write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::Show(winevent, window) => {
|
||||
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeStart(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MoveResizeStart (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MoveResizeEnd (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::MouseCapture(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MouseCapture (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowManagerEvent {
|
||||
pub const fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {
|
||||
match winevent {
|
||||
WinEvent::ObjectDestroy => Some(Self::Destroy(winevent, window)),
|
||||
|
||||
WinEvent::ObjectCloaked | WinEvent::ObjectHide => Some(Self::Hide(winevent, window)),
|
||||
|
||||
WinEvent::SystemMinimizeStart => Some(Self::Minimize(winevent, window)),
|
||||
|
||||
WinEvent::ObjectShow | WinEvent::ObjectUncloaked | WinEvent::SystemMinimizeEnd => {
|
||||
Some(Self::Show(winevent, window))
|
||||
}
|
||||
|
||||
WinEvent::ObjectFocus | WinEvent::SystemForeground => {
|
||||
Some(Self::FocusChange(winevent, window))
|
||||
}
|
||||
WinEvent::SystemMoveSizeStart => Some(Self::MoveResizeStart(winevent, window)),
|
||||
WinEvent::SystemMoveSizeEnd => Some(Self::MoveResizeEnd(winevent, window)),
|
||||
WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => {
|
||||
Some(Self::MouseCapture(winevent, window))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
470
komorebi/src/windows_api.rs
Normal file
470
komorebi/src/windows_api.rs
Normal file
@@ -0,0 +1,470 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
use eyre::Error;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::BOOL;
|
||||
use bindings::Windows::Win32::Foundation::HANDLE;
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::Foundation::LPARAM;
|
||||
use bindings::Windows::Win32::Foundation::POINT;
|
||||
use bindings::Windows::Win32::Foundation::PWSTR;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||
use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HDC;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO};
|
||||
use bindings::Windows::Win32::System::Threading::AttachThreadInput;
|
||||
use bindings::Windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use bindings::Windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use bindings::Windows::Win32::System::Threading::OpenProcess;
|
||||
use bindings::Windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||
use bindings::Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
||||
use bindings::Windows::Win32::System::Threading::PROCESS_NAME_FORMAT;
|
||||
use bindings::Windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
|
||||
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::SetFocus;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsIconic;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::{monitor, windows_callbacks};
|
||||
|
||||
pub enum WindowsResult<T, E> {
|
||||
Err(E),
|
||||
Ok(T),
|
||||
}
|
||||
|
||||
impl From<BOOL> for WindowsResult<(), Error> {
|
||||
fn from(return_value: BOOL) -> Self {
|
||||
if return_value.as_bool() {
|
||||
Self::Ok(())
|
||||
} else {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HWND> for WindowsResult<isize, Error> {
|
||||
fn from(return_value: HWND) -> Self {
|
||||
if return_value.is_null() {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Self::Ok(return_value.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HANDLE> for WindowsResult<HANDLE, Error> {
|
||||
fn from(return_value: HANDLE) -> Self {
|
||||
if return_value.is_null() {
|
||||
Self::Err(std::io::Error::last_os_error().into())
|
||||
} else {
|
||||
Self::Ok(return_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for WindowsResult<isize, Error> {
|
||||
fn from(return_value: isize) -> Self {
|
||||
match return_value {
|
||||
0 => Self::Err(std::io::Error::last_os_error().into()),
|
||||
_ => Self::Ok(return_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for WindowsResult<u32, Error> {
|
||||
fn from(return_value: u32) -> Self {
|
||||
match return_value {
|
||||
0 => Self::Err(std::io::Error::last_os_error().into()),
|
||||
_ => Self::Ok(return_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for WindowsResult<i32, Error> {
|
||||
fn from(return_value: i32) -> Self {
|
||||
match return_value {
|
||||
0 => Self::Err(std::io::Error::last_os_error().into()),
|
||||
_ => Self::Ok(return_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> From<WindowsResult<T, E>> for Result<T, E> {
|
||||
fn from(result: WindowsResult<T, E>) -> Self {
|
||||
match result {
|
||||
WindowsResult::Err(error) => Self::Err(error),
|
||||
WindowsResult::Ok(ok) => Self::Ok(ok),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsApi;
|
||||
|
||||
impl WindowsApi {
|
||||
pub fn enum_display_monitors(
|
||||
callback: MONITORENUMPROC,
|
||||
callback_data_address: isize,
|
||||
) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
EnumDisplayMonitors(
|
||||
HDC(0),
|
||||
std::ptr::null_mut(),
|
||||
Option::from(callback),
|
||||
LPARAM(callback_data_address),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||
Self::enum_display_monitors(
|
||||
windows_callbacks::enum_display_monitor,
|
||||
monitors as *mut Ring<Monitor> as isize,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
EnumWindows(Option::from(callback), LPARAM(callback_data_address))
|
||||
}))
|
||||
}
|
||||
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||
for monitor in monitors.elements_mut() {
|
||||
if monitor.workspaces().is_empty() {
|
||||
let mut workspace = Workspace::default();
|
||||
|
||||
// EnumWindows will enumerate through windows on all monitors
|
||||
Self::enum_windows(
|
||||
windows_callbacks::enum_window,
|
||||
workspace.containers_mut() as *mut VecDeque<Container> as isize,
|
||||
)?;
|
||||
|
||||
// So we have to prune each monitor's primary workspace of undesired windows here
|
||||
let mut windows_on_other_monitors = vec![];
|
||||
|
||||
for container in workspace.containers_mut() {
|
||||
for window in container.windows() {
|
||||
if Self::monitor_from_window(window.hwnd()) != monitor.id() {
|
||||
windows_on_other_monitors.push(window.hwnd().0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for hwnd in windows_on_other_monitors {
|
||||
workspace.remove_window(hwnd)?;
|
||||
}
|
||||
|
||||
monitor.workspaces_mut().push_back(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn allow_set_foreground_window(process_id: u32) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
AllowSetForegroundWindow(process_id)
|
||||
}))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn set_window_pos(hwnd: HWND, layout: &Rect) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_NOTOPMOST,
|
||||
layout.left,
|
||||
layout.top,
|
||||
layout.right,
|
||||
layout.bottom,
|
||||
SWP_NOACTIVATE,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
|
||||
// BOOL is returned but does not signify whether or not the operation was succesful
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
||||
unsafe { ShowWindow(hwnd, command) };
|
||||
}
|
||||
|
||||
pub fn hide_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_HIDE);
|
||||
}
|
||||
|
||||
pub fn restore_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_RESTORE);
|
||||
}
|
||||
|
||||
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
|
||||
match WindowsResult::from(unsafe { SetForegroundWindow(hwnd) }) {
|
||||
WindowsResult::Ok(_) => Ok(()),
|
||||
WindowsResult::Err(error) => {
|
||||
// TODO: Figure out the odd behaviour here, docs state that a zero value means
|
||||
// TODO: that the window was not brought to the foreground, but this contradicts
|
||||
// TODO: the behaviour that I have observed which resulted in this check
|
||||
if error.to_string() == "The operation completed successfully. (os error 0)" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn foreground_window() -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe { GetForegroundWindow().0 }))
|
||||
}
|
||||
|
||||
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
|
||||
let mut rect = unsafe { std::mem::zeroed() };
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetWindowRect(hwnd, &mut rect)
|
||||
}))?;
|
||||
|
||||
Ok(Rect::from(rect))
|
||||
}
|
||||
|
||||
fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe { SetCursorPos(x, y) }))
|
||||
}
|
||||
|
||||
pub fn cursor_pos() -> Result<POINT> {
|
||||
let mut cursor_pos: POINT = unsafe { std::mem::zeroed() };
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetCursorPos(&mut cursor_pos)
|
||||
}))?;
|
||||
|
||||
Ok(cursor_pos)
|
||||
}
|
||||
|
||||
pub fn center_cursor_in_rect(rect: &Rect) -> Result<()> {
|
||||
Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))
|
||||
}
|
||||
|
||||
pub fn window_thread_process_id(hwnd: HWND) -> (u32, u32) {
|
||||
let mut process_id: u32 = 0;
|
||||
|
||||
// Behaviour is undefined if an invalid HWND is given
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
|
||||
let thread_id = unsafe { GetWindowThreadProcessId(hwnd, &mut process_id) };
|
||||
|
||||
(process_id, thread_id)
|
||||
}
|
||||
|
||||
pub fn current_thread_id() -> u32 {
|
||||
unsafe { GetCurrentThreadId() }
|
||||
}
|
||||
|
||||
pub fn current_process_id() -> u32 {
|
||||
unsafe { GetCurrentProcessId() }
|
||||
}
|
||||
|
||||
pub fn attach_thread_input(thread_id: u32, target_thread_id: u32, attach: bool) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
AttachThreadInput(thread_id, target_thread_id, attach)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn set_focus(hwnd: HWND) -> Result<()> {
|
||||
match WindowsResult::from(unsafe { SetFocus(hwnd) }) {
|
||||
WindowsResult::Ok(_) => Ok(()),
|
||||
WindowsResult::Err(error) => {
|
||||
// If the window is not attached to the calling thread's message queue, the return value is NULL
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setfocus
|
||||
if error.to_string() == "The operation completed successfully. (os error 0)" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_window_long_ptr_w(
|
||||
hwnd: HWND,
|
||||
index: WINDOW_LONG_PTR_INDEX,
|
||||
new_value: isize,
|
||||
) -> Result<()> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
SetWindowLongPtrW(hwnd, index, new_value)
|
||||
}))
|
||||
.map(|_| {})
|
||||
}
|
||||
|
||||
pub fn gwl_style(hwnd: HWND) -> Result<isize> {
|
||||
Self::window_long_ptr_w(hwnd, GWL_STYLE)
|
||||
}
|
||||
pub fn gwl_ex_style(hwnd: HWND) -> Result<isize> {
|
||||
Self::window_long_ptr_w(hwnd, GWL_EXSTYLE)
|
||||
}
|
||||
|
||||
fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result<isize> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetWindowLongPtrW(hwnd, index)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn update_style(hwnd: HWND, new_value: isize) -> Result<()> {
|
||||
Self::set_window_long_ptr_w(hwnd, GWL_STYLE, new_value)
|
||||
}
|
||||
|
||||
pub fn window_text_w(hwnd: HWND) -> Result<String> {
|
||||
let mut text: [u16; 512] = [0; 512];
|
||||
match WindowsResult::from(unsafe {
|
||||
GetWindowTextW(hwnd, PWSTR(text.as_mut_ptr()), text.len().try_into()?)
|
||||
}) {
|
||||
WindowsResult::Ok(len) => {
|
||||
let length = usize::try_from(len)?;
|
||||
Ok(String::from_utf16(&text[..length])?)
|
||||
}
|
||||
WindowsResult::Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_process(
|
||||
access_rights: PROCESS_ACCESS_RIGHTS,
|
||||
inherit_handle: bool,
|
||||
process_id: u32,
|
||||
) -> Result<HANDLE> {
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
OpenProcess(access_rights, inherit_handle, process_id)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn process_handle(process_id: u32) -> Result<HANDLE> {
|
||||
Self::open_process(PROCESS_QUERY_INFORMATION, false, process_id)
|
||||
}
|
||||
|
||||
pub fn exe_path(handle: HANDLE) -> Result<String> {
|
||||
let mut len = 260_u32;
|
||||
let mut path: Vec<u16> = vec![0; len as usize];
|
||||
let text_ptr = path.as_mut_ptr();
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
QueryFullProcessImageNameW(
|
||||
handle,
|
||||
PROCESS_NAME_FORMAT(0),
|
||||
PWSTR(text_ptr),
|
||||
&mut len as *mut u32,
|
||||
)
|
||||
}))?;
|
||||
|
||||
Ok(String::from_utf16(&path[..len as usize])?)
|
||||
}
|
||||
|
||||
pub fn exe(handle: HANDLE) -> Result<String> {
|
||||
Ok(Self::exe_path(handle)?
|
||||
.split('\\')
|
||||
.last()
|
||||
.context("there is no last element")?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn real_window_class_w(hwnd: HWND) -> Result<String> {
|
||||
const BUF_SIZE: usize = 512;
|
||||
let mut class: [u16; BUF_SIZE] = [0; BUF_SIZE];
|
||||
|
||||
let len = Result::from(WindowsResult::from(unsafe {
|
||||
RealGetWindowClassW(hwnd, PWSTR(class.as_mut_ptr()), u32::try_from(BUF_SIZE)?)
|
||||
}))?;
|
||||
|
||||
Ok(String::from_utf16(&class[0..len as usize])?)
|
||||
}
|
||||
|
||||
pub fn is_window_cloaked(hwnd: HWND) -> Result<bool> {
|
||||
let mut cloaked: u32 = 0;
|
||||
|
||||
unsafe {
|
||||
DwmGetWindowAttribute(
|
||||
hwnd,
|
||||
std::mem::transmute::<_, u32>(DWMWA_CLOAKED),
|
||||
(&mut cloaked as *mut u32).cast(),
|
||||
u32::try_from(std::mem::size_of::<u32>())?,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(matches!(
|
||||
cloaked,
|
||||
DWM_CLOAKED_APP | DWM_CLOAKED_SHELL | DWM_CLOAKED_INHERITED
|
||||
))
|
||||
}
|
||||
|
||||
pub fn is_window(hwnd: HWND) -> bool {
|
||||
unsafe { IsWindow(hwnd) }.into()
|
||||
}
|
||||
|
||||
pub fn is_window_visible(hwnd: HWND) -> bool {
|
||||
unsafe { IsWindowVisible(hwnd) }.into()
|
||||
}
|
||||
|
||||
pub fn is_iconic(hwnd: HWND) -> bool {
|
||||
unsafe { IsIconic(hwnd) }.into()
|
||||
}
|
||||
|
||||
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>())?;
|
||||
|
||||
Result::from(WindowsResult::from(unsafe {
|
||||
GetMonitorInfoW(hmonitor, (&mut monitor_info as *mut MONITORINFO).cast())
|
||||
}))?;
|
||||
|
||||
Ok(monitor_info)
|
||||
}
|
||||
|
||||
pub fn monitor(hmonitor: HMONITOR) -> Result<Monitor> {
|
||||
let monitor_info = Self::monitor_info_w(hmonitor)?;
|
||||
|
||||
Ok(monitor::new(
|
||||
hmonitor.0,
|
||||
monitor_info.rcMonitor.into(),
|
||||
monitor_info.rcWork.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
125
komorebi/src/windows_callbacks.rs
Normal file
125
komorebi/src/windows_callbacks.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::BOOL;
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::Foundation::LPARAM;
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HDC;
|
||||
use bindings::Windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use bindings::Windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::styles::GwlStyle;
|
||||
use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||
|
||||
pub extern "system" fn enum_display_monitor(
|
||||
hmonitor: HMONITOR,
|
||||
_: HDC,
|
||||
_: *mut RECT,
|
||||
lparam: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = unsafe { &mut *(lparam.0 as *mut Ring<Monitor>) };
|
||||
if let Ok(m) = WindowsApi::monitor(hmonitor) {
|
||||
monitors.elements_mut().push_back(m);
|
||||
}
|
||||
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
||||
|
||||
let is_visible = WindowsApi::is_window_visible(hwnd);
|
||||
let is_window = WindowsApi::is_window(hwnd);
|
||||
let is_minimized = WindowsApi::is_iconic(hwnd);
|
||||
|
||||
if is_visible && is_window && !is_minimized {
|
||||
let mut window = Window {
|
||||
hwnd: hwnd.0,
|
||||
original_style: GwlStyle::empty(),
|
||||
};
|
||||
|
||||
if let Ok(style) = window.style() {
|
||||
window.original_style = style;
|
||||
}
|
||||
|
||||
if let Ok(should_manage) = window.should_manage(None) {
|
||||
if should_manage {
|
||||
let mut container = Container::default();
|
||||
container.windows_mut().push_back(window);
|
||||
containers.push_back(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub extern "system" fn win_event_hook(
|
||||
_h_win_event_hook: HWINEVENTHOOK,
|
||||
event: u32,
|
||||
hwnd: HWND,
|
||||
id_object: i32,
|
||||
_id_child: i32,
|
||||
_id_event_thread: u32,
|
||||
_dwms_event_time: u32,
|
||||
) {
|
||||
// OBJID_WINDOW
|
||||
if id_object != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut window = Window {
|
||||
hwnd: hwnd.0,
|
||||
original_style: GwlStyle::empty(),
|
||||
};
|
||||
|
||||
if let Ok(style) = window.style() {
|
||||
window.original_style = style;
|
||||
}
|
||||
|
||||
let winevent = unsafe { ::std::mem::transmute(event) };
|
||||
let event_type = if let Some(event) = WindowManagerEvent::from_win_event(winevent, window) {
|
||||
event
|
||||
} else {
|
||||
// Some apps like Firefox don't send ObjectCreate or ObjectShow on launch
|
||||
// This spams the message queue, but I don't know what else to do. On launch
|
||||
// it only sends the following WinEvents :/
|
||||
//
|
||||
// [yatta\src\windows_event.rs:110] event = 32780 ObjectNameChange
|
||||
// [yatta\src\windows_event.rs:110] event = 32779 ObjectLocationChange
|
||||
let object_name_change_on_launch =
|
||||
vec!["firefox.exe".to_string(), "idea64.exe".to_string()];
|
||||
|
||||
if let Ok(exe) = window.exe() {
|
||||
if winevent == WinEvent::ObjectNameChange {
|
||||
if object_name_change_on_launch.contains(&exe) {
|
||||
WindowManagerEvent::Show(winevent, window)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(should_manage) = window.should_manage(Option::from(event_type)) {
|
||||
if should_manage {
|
||||
WINEVENT_CALLBACK_CHANNEL
|
||||
.lock()
|
||||
.unwrap()
|
||||
.0
|
||||
.send(event_type)
|
||||
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||
}
|
||||
}
|
||||
}
|
||||
173
komorebi/src/winevent.rs
Normal file
173
komorebi/src/winevent.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_START;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_CARET;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END_APPLICATION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_LAYOUT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_START_APPLICATION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_REGION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SCROLL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SIMPLE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_ACCELERATORCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CLOAKED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CONTENTSCROLLED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CREATE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DEFACTIONCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESCRIPTIONCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCANCEL;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCOMPLETE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGDROPPED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGENTER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGLEAVE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_FOCUS;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HELPCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_CHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_SHOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_INVOKED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LIVEREGIONCHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_NAMECHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_PARENTCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_REORDER;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONADD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONREMOVE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONWITHIN;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SHOW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_STATECHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTSELECTIONCHANGED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_UNCLOAKED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_VALUECHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_START;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ALERT;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ARRANGMENTPREVIEW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTUREEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTURESTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DESKTOPSWITCH;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_FOREGROUND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_IME_KEY_NOTIFICATION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZEEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZESTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZEEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZESTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SOUND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHEND;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPDROPPED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPGRABBED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPOVERTARGET;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_CANCELLED;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHSTART;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, strum::Display)]
|
||||
#[repr(u32)]
|
||||
pub enum WinEvent {
|
||||
AiaEnd = EVENT_AIA_END,
|
||||
AiaStart = EVENT_AIA_START,
|
||||
ConsoleCaret = EVENT_CONSOLE_CARET,
|
||||
ConsoleEnd = EVENT_CONSOLE_END,
|
||||
ConsoleEndApplication = EVENT_CONSOLE_END_APPLICATION,
|
||||
ConsoleLayout = EVENT_CONSOLE_LAYOUT,
|
||||
ConsoleStartApplication = EVENT_CONSOLE_START_APPLICATION,
|
||||
ConsoleUpdateRegion = EVENT_CONSOLE_UPDATE_REGION,
|
||||
ConsoleUpdateScroll = EVENT_CONSOLE_UPDATE_SCROLL,
|
||||
ConsoleUpdateSimple = EVENT_CONSOLE_UPDATE_SIMPLE,
|
||||
ObjectAcceleratorChange = EVENT_OBJECT_ACCELERATORCHANGE,
|
||||
ObjectCloaked = EVENT_OBJECT_CLOAKED,
|
||||
ObjectContentScrolled = EVENT_OBJECT_CONTENTSCROLLED,
|
||||
ObjectCreate = EVENT_OBJECT_CREATE,
|
||||
ObjectDefActionChange = EVENT_OBJECT_DEFACTIONCHANGE,
|
||||
ObjectDescriptionChange = EVENT_OBJECT_DESCRIPTIONCHANGE,
|
||||
ObjectDestroy = EVENT_OBJECT_DESTROY,
|
||||
ObjectDragCancel = EVENT_OBJECT_DRAGCANCEL,
|
||||
ObjectDragComplete = EVENT_OBJECT_DRAGCOMPLETE,
|
||||
ObjectDragDropped = EVENT_OBJECT_DRAGDROPPED,
|
||||
ObjectDragEnter = EVENT_OBJECT_DRAGENTER,
|
||||
ObjectDragLeave = EVENT_OBJECT_DRAGLEAVE,
|
||||
ObjectDragStart = EVENT_OBJECT_DRAGSTART,
|
||||
ObjectEnd = EVENT_OBJECT_END,
|
||||
ObjectFocus = EVENT_OBJECT_FOCUS,
|
||||
ObjectHelpChange = EVENT_OBJECT_HELPCHANGE,
|
||||
ObjectHide = EVENT_OBJECT_HIDE,
|
||||
ObjectHostedObjectsInvalidated = EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED,
|
||||
ObjectImeChange = EVENT_OBJECT_IME_CHANGE,
|
||||
ObjectImeHide = EVENT_OBJECT_IME_HIDE,
|
||||
ObjectImeShow = EVENT_OBJECT_IME_SHOW,
|
||||
ObjectInvoked = EVENT_OBJECT_INVOKED,
|
||||
ObjectLiveRegionChanged = EVENT_OBJECT_LIVEREGIONCHANGED,
|
||||
ObjectLocationChange = EVENT_OBJECT_LOCATIONCHANGE,
|
||||
ObjectNameChange = EVENT_OBJECT_NAMECHANGE,
|
||||
ObjectParentChange = EVENT_OBJECT_PARENTCHANGE,
|
||||
ObjectReorder = EVENT_OBJECT_REORDER,
|
||||
ObjectSelection = EVENT_OBJECT_SELECTION,
|
||||
ObjectSelectionAdd = EVENT_OBJECT_SELECTIONADD,
|
||||
ObjectSelectionRemove = EVENT_OBJECT_SELECTIONREMOVE,
|
||||
ObjectSelectionWithin = EVENT_OBJECT_SELECTIONWITHIN,
|
||||
ObjectShow = EVENT_OBJECT_SHOW,
|
||||
ObjectStateChange = EVENT_OBJECT_STATECHANGE,
|
||||
ObjectTextEditConversionTargetChanged = EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED,
|
||||
ObjectTextSelectionChanged = EVENT_OBJECT_TEXTSELECTIONCHANGED,
|
||||
ObjectUncloaked = EVENT_OBJECT_UNCLOAKED,
|
||||
ObjectValueChange = EVENT_OBJECT_VALUECHANGE,
|
||||
OemDefinedEnd = EVENT_OEM_DEFINED_END,
|
||||
OemDefinedStart = EVENT_OEM_DEFINED_START,
|
||||
SystemAlert = EVENT_SYSTEM_ALERT,
|
||||
SystemArrangementPreview = EVENT_SYSTEM_ARRANGMENTPREVIEW,
|
||||
SystemCaptureEnd = EVENT_SYSTEM_CAPTUREEND,
|
||||
SystemCaptureStart = EVENT_SYSTEM_CAPTURESTART,
|
||||
SystemContextHelpEnd = EVENT_SYSTEM_CONTEXTHELPEND,
|
||||
SystemContextHelpStart = EVENT_SYSTEM_CONTEXTHELPSTART,
|
||||
SystemDesktopSwitch = EVENT_SYSTEM_DESKTOPSWITCH,
|
||||
SystemDialogEnd = EVENT_SYSTEM_DIALOGEND,
|
||||
SystemDialogStart = EVENT_SYSTEM_DIALOGSTART,
|
||||
SystemDragDropEnd = EVENT_SYSTEM_DRAGDROPEND,
|
||||
SystemDragDropStart = EVENT_SYSTEM_DRAGDROPSTART,
|
||||
SystemEnd = EVENT_SYSTEM_END,
|
||||
SystemForeground = EVENT_SYSTEM_FOREGROUND,
|
||||
SystemImeKeyNotification = EVENT_SYSTEM_IME_KEY_NOTIFICATION,
|
||||
SystemMenuEnd = EVENT_SYSTEM_MENUEND,
|
||||
SystemMenuPopupEnd = EVENT_SYSTEM_MENUPOPUPEND,
|
||||
SystemMenuPopupStart = EVENT_SYSTEM_MENUPOPUPSTART,
|
||||
SystemMenuStart = EVENT_SYSTEM_MENUSTART,
|
||||
SystemMinimizeEnd = EVENT_SYSTEM_MINIMIZEEND,
|
||||
SystemMinimizeStart = EVENT_SYSTEM_MINIMIZESTART,
|
||||
SystemMoveSizeEnd = EVENT_SYSTEM_MOVESIZEEND,
|
||||
SystemMoveSizeStart = EVENT_SYSTEM_MOVESIZESTART,
|
||||
SystemScrollingEnd = EVENT_SYSTEM_SCROLLINGEND,
|
||||
SystemScrollingStart = EVENT_SYSTEM_SCROLLINGSTART,
|
||||
SystemSound = EVENT_SYSTEM_SOUND,
|
||||
SystemSwitchEnd = EVENT_SYSTEM_SWITCHEND,
|
||||
SystemSwitchStart = EVENT_SYSTEM_SWITCHSTART,
|
||||
SystemSwitcherAppDropped = EVENT_SYSTEM_SWITCHER_APPDROPPED,
|
||||
SystemSwitcherAppGrabbed = EVENT_SYSTEM_SWITCHER_APPGRABBED,
|
||||
SystemSwitcherAppOverTarget = EVENT_SYSTEM_SWITCHER_APPOVERTARGET,
|
||||
SystemSwitcherCancelled = EVENT_SYSTEM_SWITCHER_CANCELLED,
|
||||
UiaEventIdSEnd = EVENT_UIA_EVENTID_END,
|
||||
UiaEventIdStart = EVENT_UIA_EVENTID_START,
|
||||
UiaPropIdSEnd = EVENT_UIA_PROPID_END,
|
||||
UiaPropIdStart = EVENT_UIA_PROPID_START,
|
||||
}
|
||||
107
komorebi/src/winevent_listener.rs
Normal file
107
komorebi/src/winevent_listener.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::HWND;
|
||||
use bindings::Windows::Win32::UI::Accessibility::SetWinEventHook;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::PeekMessageW;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::PM_REMOVE;
|
||||
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_callbacks;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref WINEVENT_CALLBACK_CHANNEL: Arc<Mutex<(Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>)>> =
|
||||
Arc::new(Mutex::new(crossbeam_channel::unbounded()));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WinEventListener {
|
||||
hook: Arc<AtomicIsize>,
|
||||
outgoing_events: Arc<Mutex<Sender<WindowManagerEvent>>>,
|
||||
}
|
||||
|
||||
pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener {
|
||||
WinEventListener {
|
||||
hook: Arc::new(AtomicIsize::new(0)),
|
||||
outgoing_events: outgoing,
|
||||
}
|
||||
}
|
||||
|
||||
impl WinEventListener {
|
||||
pub fn start(self) {
|
||||
let hook = self.hook.clone();
|
||||
let outgoing = self.outgoing_events.lock().unwrap().clone();
|
||||
|
||||
thread::spawn(move || unsafe {
|
||||
let hook_ref = SetWinEventHook(
|
||||
EVENT_MIN as u32,
|
||||
EVENT_MAX as u32,
|
||||
None,
|
||||
Some(windows_callbacks::win_event_hook),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
hook.store(hook_ref.0, Ordering::SeqCst);
|
||||
|
||||
// The code in the callback doesn't work in its own loop, needs to be within
|
||||
// the MessageLoop callback for the winevent callback to even fire
|
||||
MessageLoop::start(10, |_msg| {
|
||||
if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().unwrap().1.try_recv() {
|
||||
match outgoing.send(event) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
tracing::error!("{}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MessageLoop;
|
||||
|
||||
impl MessageLoop {
|
||||
pub fn start(sleep: u64, cb: impl Fn(Option<MSG>) -> bool) {
|
||||
Self::start_with_sleep(sleep, cb);
|
||||
}
|
||||
|
||||
fn start_with_sleep(sleep: u64, cb: impl Fn(Option<MSG>) -> bool) {
|
||||
let mut msg: MSG = MSG::default();
|
||||
loop {
|
||||
let mut value: Option<MSG> = None;
|
||||
unsafe {
|
||||
if !bool::from(!PeekMessageW(&mut msg, HWND(0), 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
|
||||
value = Some(msg);
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(sleep));
|
||||
|
||||
if !cb(value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
537
komorebi/src/workspace.rs
Normal file
537
komorebi/src/workspace.rs
Normal file
@@ -0,0 +1,537 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use color_eyre::Result;
|
||||
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::LayoutFlip;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Workspace {
|
||||
name: Option<String>,
|
||||
containers: Ring<Container>,
|
||||
monocle_container: Option<Container>,
|
||||
monocle_restore_idx: Option<usize>,
|
||||
floating_windows: Vec<Window>,
|
||||
layout: Layout,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
workspace_padding: Option<i32>,
|
||||
container_padding: Option<i32>,
|
||||
latest_layout: Vec<Rect>,
|
||||
}
|
||||
|
||||
impl Default for Workspace {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
containers: Ring::default(),
|
||||
monocle_container: None,
|
||||
monocle_restore_idx: None,
|
||||
floating_windows: Vec::default(),
|
||||
layout: Layout::BSP,
|
||||
layout_flip: None,
|
||||
workspace_padding: Option::from(20),
|
||||
container_padding: Option::from(5),
|
||||
latest_layout: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn hide(&mut self) {
|
||||
for container in self.containers_mut() {
|
||||
for window in container.windows_mut() {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(&mut self) {
|
||||
for container in self.containers_mut() {
|
||||
if let Some(window) = container.visible_window_mut() {
|
||||
window.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, work_area: &Rect) -> Result<()> {
|
||||
let mut adjusted_work_area = work_area.clone();
|
||||
adjusted_work_area.add_padding(self.workspace_padding());
|
||||
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window) = container.focused_window_mut() {
|
||||
window.set_position(&adjusted_work_area)?;
|
||||
window.focus()?;
|
||||
}
|
||||
} else {
|
||||
let layouts = self.layout().calculate(
|
||||
&adjusted_work_area,
|
||||
self.containers().len(),
|
||||
self.container_padding(),
|
||||
self.layout_flip(),
|
||||
);
|
||||
|
||||
let windows = self.visible_windows_mut();
|
||||
for (i, window) in windows.into_iter().enumerate() {
|
||||
if let (Some(window), Some(layout)) = (window, layouts.get(i)) {
|
||||
window.set_position(layout)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.set_latest_layout(layouts);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
|
||||
let mut hwnds = vec![];
|
||||
for window in self.visible_windows_mut().into_iter().flatten() {
|
||||
if !window.is_window() {
|
||||
hwnds.push(window.hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
for hwnd in &hwnds {
|
||||
tracing::debug!("reaping hwnd: {}", hwnd);
|
||||
self.remove_window(*hwnd)?;
|
||||
}
|
||||
|
||||
let mut container_ids = vec![];
|
||||
for container in self.containers() {
|
||||
if container.windows().is_empty() {
|
||||
container_ids.push(container.id().clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.containers_mut()
|
||||
.retain(|c| !container_ids.contains(c.id()));
|
||||
|
||||
Ok((hwnds.len(), container_ids.len()))
|
||||
}
|
||||
|
||||
pub fn focus_container_by_window(&mut self, hwnd: isize) -> Result<()> {
|
||||
let container_idx = self
|
||||
.container_idx_for_window(hwnd)
|
||||
.context("there is no container/window")?;
|
||||
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.get_mut(container_idx)
|
||||
.context("there is no container")?;
|
||||
|
||||
let window_idx = container
|
||||
.idx_for_window(hwnd)
|
||||
.context("there is no window")?;
|
||||
|
||||
container.focus_window(window_idx);
|
||||
self.focus_container(container_idx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn container_idx_from_current_point(&self) -> Option<usize> {
|
||||
let mut idx = None;
|
||||
|
||||
let point = WindowsApi::cursor_pos().ok()?;
|
||||
|
||||
for (i, _container) in self.containers().iter().enumerate() {
|
||||
if let Some(rect) = self.latest_layout().get(i) {
|
||||
if rect.contains_point((point.x, point.y)) {
|
||||
idx = Option::from(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for x in self.containers() {
|
||||
if x.contains_window(hwnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn promote_container(&mut self) -> Result<()> {
|
||||
let container = self
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
self.containers_mut().push_front(container);
|
||||
self.focus_container(0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_container(&mut self, container: Container) {
|
||||
self.containers_mut().push_back(container);
|
||||
self.focus_container(self.containers().len() - 1);
|
||||
}
|
||||
|
||||
fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
|
||||
self.containers_mut().remove(idx)
|
||||
}
|
||||
|
||||
fn container_idx_for_window(&mut self, hwnd: isize) -> Option<usize> {
|
||||
let mut idx = None;
|
||||
for (i, x) in self.containers().iter().enumerate() {
|
||||
if x.contains_window(hwnd) {
|
||||
idx = Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn remove_window(&mut self, hwnd: isize) -> Result<()> {
|
||||
let container_idx = self
|
||||
.container_idx_for_window(hwnd)
|
||||
.context("there is no window")?;
|
||||
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.get_mut(container_idx)
|
||||
.context("there is no container")?;
|
||||
|
||||
let window_idx = container
|
||||
.windows()
|
||||
.iter()
|
||||
.position(|window| window.hwnd == hwnd)
|
||||
.context("there is no window")?;
|
||||
|
||||
container
|
||||
.remove_window_by_idx(window_idx)
|
||||
.context("there is no window")?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut()
|
||||
.remove(container_idx)
|
||||
.context("there is no container")?;
|
||||
}
|
||||
|
||||
if container_idx != 0 {
|
||||
self.focus_container(container_idx - 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_focused_container(&mut self) -> Option<Container> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let container = self.remove_container_by_idx(focused_idx);
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
}
|
||||
|
||||
container
|
||||
}
|
||||
|
||||
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
|
||||
if direction.is_valid(
|
||||
self.layout,
|
||||
self.focused_container_idx(),
|
||||
self.containers().len(),
|
||||
) {
|
||||
Option::from(direction.new_idx(self.layout, self.containers.focused_idx()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
|
||||
// This is a little messy
|
||||
let adjusted_target_container_index = if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
if focused_idx < target_container_idx {
|
||||
target_container_idx - 1
|
||||
} else {
|
||||
target_container_idx
|
||||
}
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
target_container_idx
|
||||
};
|
||||
|
||||
let target_container = self
|
||||
.containers_mut()
|
||||
.get_mut(adjusted_target_container_index)
|
||||
.context("there is no container")?;
|
||||
|
||||
target_container.add_window(window);
|
||||
|
||||
self.focus_container(adjusted_target_container_index);
|
||||
self.focused_container_mut()
|
||||
.context("there is no container")?
|
||||
.load_focused_window();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_container_for_focused_window(&mut self) -> Result<()> {
|
||||
let focused_container_idx = self.focused_container_idx();
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_container_idx);
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
|
||||
self.new_container_for_window(window);
|
||||
|
||||
let mut container = Container::default();
|
||||
container.add_window(window);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_container_for_floating_window(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let window = self
|
||||
.remove_focused_floating_window()
|
||||
.context("there is no floating window")?;
|
||||
|
||||
let mut container = Container::default();
|
||||
container.add_window(window);
|
||||
self.containers_mut().insert(focused_idx, container);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_container_for_window(&mut self, window: Window) {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let len = self.containers().len();
|
||||
|
||||
let mut container = Container::default();
|
||||
container.add_window(window);
|
||||
|
||||
if focused_idx == len - 1 {
|
||||
self.containers_mut().resize(len, Container::default());
|
||||
}
|
||||
|
||||
self.containers_mut().insert(focused_idx + 1, container);
|
||||
self.focus_container(focused_idx + 1);
|
||||
}
|
||||
|
||||
pub fn new_floating_window(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
|
||||
self.floating_windows_mut().push(window);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_monocle_container(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let container = self
|
||||
.containers_mut()
|
||||
.remove(focused_idx)
|
||||
.context("there is not container")?;
|
||||
|
||||
self.monocle_container = Option::from(container);
|
||||
self.monocle_restore_idx = Option::from(focused_idx);
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
}
|
||||
|
||||
self.monocle_container_mut()
|
||||
.context("there is no monocle container")?
|
||||
.load_focused_window();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reintegrate_monocle_container(&mut self) -> Result<()> {
|
||||
let restore_idx = self
|
||||
.monocle_restore_idx()
|
||||
.context("there is no monocle restore index")?;
|
||||
|
||||
let container = self
|
||||
.monocle_container_mut()
|
||||
.context("there is no monocle container")?;
|
||||
|
||||
let container = container.clone();
|
||||
if restore_idx > self.containers().len() - 1 {
|
||||
self.containers_mut()
|
||||
.resize(restore_idx, Container::default());
|
||||
}
|
||||
|
||||
self.containers_mut().insert(restore_idx, container);
|
||||
self.focus_container(restore_idx);
|
||||
self.focused_container_mut()
|
||||
.context("there is no container")?
|
||||
.load_focused_window();
|
||||
|
||||
self.monocle_container = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const fn monocle_container(&self) -> Option<&Container> {
|
||||
self.monocle_container.as_ref()
|
||||
}
|
||||
|
||||
pub fn monocle_container_mut(&mut self) -> Option<&mut Container> {
|
||||
self.monocle_container.as_mut()
|
||||
}
|
||||
|
||||
pub const fn monocle_restore_idx(&self) -> Option<usize> {
|
||||
self.monocle_restore_idx
|
||||
}
|
||||
|
||||
pub fn focused_container(&self) -> Option<&Container> {
|
||||
self.containers.focused()
|
||||
}
|
||||
|
||||
pub const fn focused_container_idx(&self) -> usize {
|
||||
self.containers.focused_idx()
|
||||
}
|
||||
|
||||
pub fn focused_container_mut(&mut self) -> Option<&mut Container> {
|
||||
self.containers.focused_mut()
|
||||
}
|
||||
|
||||
pub fn focus_container(&mut self, idx: usize) {
|
||||
tracing::info!("focusing container at index: {}", idx);
|
||||
self.containers.focus(idx);
|
||||
}
|
||||
|
||||
pub const fn containers(&self) -> &VecDeque<Container> {
|
||||
self.containers.elements()
|
||||
}
|
||||
|
||||
pub fn containers_mut(&mut self) -> &mut VecDeque<Container> {
|
||||
self.containers.elements_mut()
|
||||
}
|
||||
|
||||
pub fn swap_containers(&mut self, i: usize, j: usize) {
|
||||
tracing::info!("swapping containers: {}, {}", i, j);
|
||||
self.containers.swap(i, j);
|
||||
self.focus_container(j);
|
||||
}
|
||||
|
||||
pub fn remove_focused_floating_window(&mut self) -> Option<Window> {
|
||||
let hwnd = WindowsApi::foreground_window().ok()?;
|
||||
|
||||
let mut idx = None;
|
||||
for (i, window) in self.floating_windows.iter().enumerate() {
|
||||
if hwnd == window.hwnd {
|
||||
idx = Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
match idx {
|
||||
None => None,
|
||||
Some(idx) => {
|
||||
if self.floating_windows.get(idx).is_some() {
|
||||
Option::from(self.floating_windows_mut().remove(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn floating_windows(&self) -> &Vec<Window> {
|
||||
&self.floating_windows
|
||||
}
|
||||
|
||||
pub fn floating_windows_mut(&mut self) -> &mut Vec<Window> {
|
||||
self.floating_windows.as_mut()
|
||||
}
|
||||
|
||||
pub fn visible_windows_mut(&mut self) -> Vec<Option<&mut Window>> {
|
||||
let mut vec = vec![];
|
||||
for container in self.containers_mut() {
|
||||
vec.push(container.visible_window_mut());
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
pub const fn layout(&self) -> Layout {
|
||||
self.layout
|
||||
}
|
||||
|
||||
pub const fn layout_flip(&self) -> Option<LayoutFlip> {
|
||||
self.layout_flip
|
||||
}
|
||||
|
||||
pub const fn workspace_padding(&self) -> Option<i32> {
|
||||
self.workspace_padding
|
||||
}
|
||||
|
||||
pub const fn container_padding(&self) -> Option<i32> {
|
||||
self.container_padding
|
||||
}
|
||||
|
||||
pub const fn latest_layout(&self) -> &Vec<Rect> {
|
||||
&self.latest_layout
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: Option<String>) {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
pub fn set_layout(&mut self, layout: Layout) {
|
||||
self.layout = layout;
|
||||
}
|
||||
|
||||
pub fn set_layout_flip(&mut self, layout_flip: Option<LayoutFlip>) {
|
||||
self.layout_flip = layout_flip;
|
||||
}
|
||||
|
||||
pub fn set_workspace_padding(&mut self, padding: Option<i32>) {
|
||||
self.workspace_padding = padding;
|
||||
}
|
||||
|
||||
pub fn set_container_padding(&mut self, padding: Option<i32>) {
|
||||
self.container_padding = padding;
|
||||
}
|
||||
|
||||
pub fn set_latest_layout(&mut self, layout: Vec<Rect>) {
|
||||
self.latest_layout = layout;
|
||||
}
|
||||
}
|
||||
15
komorebic/Cargo.toml
Normal file
15
komorebic/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
|
||||
clap = "3.0.0-beta.2"
|
||||
dirs = "3"
|
||||
powershell_script = "0.1.5"
|
||||
uds_windows = "1"
|
||||
color-eyre = "0.5.11"
|
||||
247
komorebic/src/main.rs
Normal file
247
komorebic/src/main.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
use std::io::Write;
|
||||
|
||||
use clap::Clap;
|
||||
use color_eyre::Result;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use komorebi_core::CycleDirection;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::LayoutFlip;
|
||||
use komorebi_core::OperationDirection;
|
||||
use komorebi_core::Sizing;
|
||||
use komorebi_core::SocketMessage;
|
||||
|
||||
#[derive(Clap)]
|
||||
#[clap(version = "1.0", author = "Jade Iqbal <jadeiqbal@fastmail.com>")]
|
||||
struct Opts {
|
||||
#[clap(subcommand)]
|
||||
subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
enum SubCommand {
|
||||
Focus(OperationDirection),
|
||||
Move(OperationDirection),
|
||||
Stack(OperationDirection),
|
||||
Unstack,
|
||||
CycleStack(CycleDirection),
|
||||
MoveToMonitor(Target),
|
||||
MoveToWorkspace(Target),
|
||||
FocusMonitor(Target),
|
||||
FocusWorkspace(Target),
|
||||
Promote,
|
||||
Retile,
|
||||
ContainerPadding(SizeForMonitorWorkspace),
|
||||
WorkspacePadding(SizeForMonitorWorkspace),
|
||||
WorkspaceName(NameForMonitorWorkspace),
|
||||
ToggleFloat,
|
||||
TogglePause,
|
||||
ToggleMonocle,
|
||||
Start,
|
||||
Stop,
|
||||
FloatClass(FloatTarget),
|
||||
FloatExe(FloatTarget),
|
||||
FloatTitle(FloatTarget),
|
||||
AdjustContainerPadding(SizingAdjustment),
|
||||
AdjustWorkspacePadding(SizingAdjustment),
|
||||
FlipLayout(LayoutFlip),
|
||||
Layout(LayoutForMonitorWorkspace),
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct SizeForMonitorWorkspace {
|
||||
monitor: usize,
|
||||
workspace: usize,
|
||||
size: i32,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct NameForMonitorWorkspace {
|
||||
monitor: usize,
|
||||
workspace: usize,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct LayoutForMonitorWorkspace {
|
||||
monitor: usize,
|
||||
workspace: usize,
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct Target {
|
||||
number: usize,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct SizingAdjustment {
|
||||
sizing: Sizing,
|
||||
adjustment: i32,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct FloatTarget {
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub fn send_message(bytes: &[u8]) {
|
||||
let mut socket = dirs::home_dir().unwrap();
|
||||
socket.push("komorebi.sock");
|
||||
let socket = socket.as_path();
|
||||
|
||||
let mut stream = match UnixStream::connect(&socket) {
|
||||
Err(_) => panic!("server is not running"),
|
||||
Ok(stream) => stream,
|
||||
};
|
||||
|
||||
if stream.write_all(&*bytes).is_err() {
|
||||
panic!("couldn't send message")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::Focus(direction) => {
|
||||
let bytes = SocketMessage::FocusWindow(direction).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Promote => {
|
||||
let bytes = SocketMessage::Promote.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::TogglePause => {
|
||||
let bytes = SocketMessage::TogglePause.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Retile => {
|
||||
let bytes = SocketMessage::Retile.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Move(direction) => {
|
||||
let bytes = SocketMessage::MoveWindow(direction).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::MoveToMonitor(display) => {
|
||||
let bytes = SocketMessage::MoveContainerToMonitorNumber(display.number)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::MoveToWorkspace(workspace) => {
|
||||
let bytes = SocketMessage::MoveContainerToWorkspaceNumber(workspace.number)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::ContainerPadding(gap) => {
|
||||
let bytes = SocketMessage::ContainerPadding(gap.monitor, gap.workspace, gap.size)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::WorkspacePadding(gap) => {
|
||||
let bytes = SocketMessage::WorkspacePadding(gap.monitor, gap.workspace, gap.size)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::AdjustWorkspacePadding(sizing_adjustment) => {
|
||||
let bytes = SocketMessage::AdjustWorkspacePadding(
|
||||
sizing_adjustment.sizing,
|
||||
sizing_adjustment.adjustment,
|
||||
)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::AdjustContainerPadding(sizing_adjustment) => {
|
||||
let bytes = SocketMessage::AdjustContainerPadding(
|
||||
sizing_adjustment.sizing,
|
||||
sizing_adjustment.adjustment,
|
||||
)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::ToggleFloat => {
|
||||
let bytes = SocketMessage::ToggleFloat.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::ToggleMonocle => {
|
||||
let bytes = SocketMessage::ToggleMonocle.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Layout(layout) => {
|
||||
let bytes = SocketMessage::SetLayout(layout.monitor, layout.workspace, layout.layout)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Start => {
|
||||
let script = r#"Start-Process komorebi -WindowStyle hidden"#;
|
||||
match powershell_script::run(script, true) {
|
||||
Ok(output) => {
|
||||
println!("{}", output);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
SubCommand::Stop => {
|
||||
let bytes = SocketMessage::Stop.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::FloatClass(target) => {
|
||||
let bytes = SocketMessage::FloatClass(target.id).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::FloatExe(target) => {
|
||||
let bytes = SocketMessage::FloatExe(target.id).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::FloatTitle(target) => {
|
||||
let bytes = SocketMessage::FloatTitle(target.id).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Stack(direction) => {
|
||||
let bytes = SocketMessage::StackWindow(direction).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::Unstack => {
|
||||
let bytes = SocketMessage::UnstackWindow.as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::CycleStack(direction) => {
|
||||
let bytes = SocketMessage::CycleStack(direction).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::FlipLayout(flip) => {
|
||||
let bytes = SocketMessage::FlipLayout(flip).as_bytes()?;
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::FocusMonitor(target) => {
|
||||
let bytes = SocketMessage::FocusMonitorNumber(target.number)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::FocusWorkspace(target) => {
|
||||
let bytes = SocketMessage::FocusWorkspaceNumber(target.number)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
SubCommand::WorkspaceName(name) => {
|
||||
let bytes = SocketMessage::WorkspaceName(name.monitor, name.workspace, name.value)
|
||||
.as_bytes()
|
||||
.unwrap();
|
||||
send_message(&*bytes);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user