refactor(client): use public interface exclusively

This commit demotes the komorebi-core crate to a module (core) inside of
the komorebi lib, resulting in the komorebi-client crate lib becoming
the single public interface for programming in Rust against komorebi.

komorebic and komorebi-gui now consume komorebi-client exclusively as
the means for sending and receiving messages to and from komorebi, so
that anyone wishing to integrate with komorebi will have all of the same
functionality to them as I do.
This commit is contained in:
LGUG2Z
2024-07-15 17:08:15 -07:00
parent 7653495e31
commit 81451cb17a
36 changed files with 479 additions and 554 deletions
+2 -2
View File
@@ -11,8 +11,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
komorebi-core = { path = "../komorebi-core" }
bitflags = { version = "2", features = ["serde"] }
clap = { version = "4", features = ["derive"] }
color-eyre = { workspace = true }
@@ -20,6 +18,7 @@ crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = { version = "3", features = ["termination"] }
dirs = { workspace = true }
dunce = { workspace = true }
getset = "0.1"
hex_color = { version = "3", features = ["serde"] }
hotwatch = "0.5"
@@ -34,6 +33,7 @@ regex = "1"
schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
strum = { version = "0.26", features = ["derive"] }
sysinfo = { workspace = true }
tracing = "0.1"
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::core::AnimationStyle;
use crate::core::Rect;
use color_eyre::Result;
use komorebi_core::AnimationStyle;
use komorebi_core::Rect;
use schemars::JsonSchema;
+2 -2
View File
@@ -8,8 +8,8 @@ use crate::border_manager::Z_ORDER;
use crate::WindowsApi;
use crate::WINDOWS_11;
use komorebi_core::BorderStyle;
use komorebi_core::Rect;
use crate::core::BorderStyle;
use crate::core::Rect;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
+3 -3
View File
@@ -2,12 +2,12 @@
mod border;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_core::BorderImplementation;
use komorebi_core::BorderStyle;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use schemars::JsonSchema;
@@ -23,6 +23,7 @@ use std::sync::Arc;
use std::sync::OnceLock;
use windows::Win32::Foundation::HWND;
use crate::core::WindowKind;
use crate::ring::Ring;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::Colour;
@@ -31,7 +32,6 @@ use crate::WindowManager;
use crate::WindowsApi;
use border::border_hwnds;
use border::Border;
use komorebi_core::WindowKind;
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
+42
View File
@@ -0,0 +1,42 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum AnimationStyle {
Linear,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseInCubic,
EaseInOutCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
}
File diff suppressed because it is too large Load Diff
+274
View File
@@ -0,0 +1,274 @@
use clap::ValueEnum;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::ApplicationIdentifier;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationOptions {
ObjectNameChange,
Layered,
TrayAndMultiWindow,
Force,
BorderOverflow,
}
impl ApplicationOptions {
#[must_use]
pub fn raw_cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
match self {
ApplicationOptions::ObjectNameChange => {
format!("komorebic.exe identify-object-name-change-application {kind} \"{id}\"",)
}
ApplicationOptions::Layered => {
format!("komorebic.exe identify-layered-application {kind} \"{id}\"",)
}
ApplicationOptions::TrayAndMultiWindow => {
format!("komorebic.exe identify-tray-application {kind} \"{id}\"",)
}
ApplicationOptions::Force => {
format!("komorebic.exe manage-rule {kind} \"{id}\"")
}
ApplicationOptions::BorderOverflow => {
unreachable!("deprecated");
}
}
}
#[must_use]
pub fn cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {
format!(
"RunWait('{}', , \"Hide\")",
ApplicationOptions::raw_cfgen(self, kind, id)
)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum MatchingRule {
Simple(IdWithIdentifier),
Composite(Vec<IdWithIdentifier>),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifier {
pub kind: ApplicationIdentifier,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub matching_strategy: Option<MatchingStrategy>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum MatchingStrategy {
Legacy,
Equals,
StartsWith,
EndsWith,
Contains,
Regex,
DoesNotEndWith,
DoesNotStartWith,
DoesNotEqual,
DoesNotContain,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifierAndComment {
pub kind: ApplicationIdentifier,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub matching_strategy: Option<MatchingStrategy>,
}
impl From<IdWithIdentifierAndComment> for IdWithIdentifier {
fn from(value: IdWithIdentifierAndComment) -> Self {
Self {
kind: value.kind,
id: value.id.clone(),
matching_strategy: value.matching_strategy,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationConfiguration {
pub name: String,
pub identifier: IdWithIdentifier,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<ApplicationOptions>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub float_identifiers: Option<Vec<MatchingRule>>,
}
impl ApplicationConfiguration {
pub fn populate_default_matching_strategies(&mut self) {
if self.identifier.matching_strategy.is_none() {
match self.identifier.kind {
ApplicationIdentifier::Exe | ApplicationIdentifier::Path => {
self.identifier.matching_strategy = Option::from(MatchingStrategy::Equals);
}
ApplicationIdentifier::Class | ApplicationIdentifier::Title => {}
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationConfigurationGenerator;
impl ApplicationConfigurationGenerator {
pub fn load(content: &str) -> Result<Vec<ApplicationConfiguration>> {
Ok(serde_yaml::from_str(content)?)
}
pub fn format(content: &str) -> Result<String> {
let mut cfgen = Self::load(content)?;
for cfg in &mut cfgen {
cfg.populate_default_matching_strategies();
}
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
Ok(serde_yaml::to_string(&cfgen)?)
}
fn merge(base_content: &str, override_content: &str) -> Result<Vec<ApplicationConfiguration>> {
let base_cfgen = Self::load(base_content)?;
let override_cfgen = Self::load(override_content)?;
let mut final_cfgen = base_cfgen.clone();
for entry in override_cfgen {
let mut replace_idx = None;
for (idx, base_entry) in base_cfgen.iter().enumerate() {
if base_entry.name == entry.name {
replace_idx = Option::from(idx);
}
}
match replace_idx {
None => final_cfgen.push(entry),
Some(idx) => final_cfgen[idx] = entry,
}
}
Ok(final_cfgen)
}
pub fn generate_pwsh(
base_content: &str,
override_content: Option<&str>,
) -> Result<Vec<String>> {
let mut cfgen = if let Some(override_content) = override_content {
Self::merge(base_content, override_content)?
} else {
Self::load(base_content)?
};
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
let mut lines = vec![String::from("# Generated by komorebic.exe"), String::new()];
let mut float_rules = vec![];
for app in cfgen {
lines.push(format!("# {}", app.name));
if let Some(options) = app.options {
for opt in options {
if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {
lines.push(String::from("# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
}
lines.push(opt.raw_cfgen(&app.identifier.kind, &app.identifier.id));
}
}
if let Some(float_identifiers) = app.float_identifiers {
for matching_rule in float_identifiers {
if let MatchingRule::Simple(float) = matching_rule {
let float_rule =
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
// if let Some(comment) = float.comment {
// lines.push(format!("# {comment}"));
// };
lines.push(float_rule);
}
}
}
}
lines.push(String::new());
}
Ok(lines)
}
pub fn generate_ahk(base_content: &str, override_content: Option<&str>) -> Result<Vec<String>> {
let mut cfgen = if let Some(override_content) = override_content {
Self::merge(base_content, override_content)?
} else {
Self::load(base_content)?
};
cfgen.sort_by(|a, b| a.name.cmp(&b.name));
let mut lines = vec![String::from("; Generated by komorebic.exe"), String::new()];
let mut float_rules = vec![];
for app in cfgen {
lines.push(format!("; {}", app.name));
if let Some(options) = app.options {
for opt in options {
if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {
lines.push(String::from("; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line"));
}
lines.push(opt.cfgen(&app.identifier.kind, &app.identifier.id));
}
}
if let Some(float_identifiers) = app.float_identifiers {
for matching_rule in float_identifiers {
if let MatchingRule::Simple(float) = matching_rule {
let float_rule = format!(
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
float.kind, float.id
);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
// if let Some(comment) = float.comment {
// lines.push(format!("; {comment}"));
// };
lines.push(float_rule);
}
}
}
}
lines.push(String::new());
}
Ok(lines)
}
}
+276
View File
@@ -0,0 +1,276 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::Path;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use super::Rect;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct CustomLayout(Vec<Column>);
impl Deref for CustomLayout {
type Target = Vec<Column>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for CustomLayout {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl CustomLayout {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let layout: Self = match path.extension() {
Some(extension) if extension == "yaml" || extension == "yml" => {
serde_json::from_reader(BufReader::new(File::open(path)?))?
}
Some(extension) if extension == "json" => {
serde_json::from_reader(BufReader::new(File::open(path)?))?
}
_ => return Err(anyhow!("custom layouts must be json or yaml files")),
};
if !layout.is_valid() {
bail!("the layout file provided was invalid");
}
Ok(layout)
}
#[must_use]
pub fn column_with_idx(&self, idx: usize) -> (usize, Option<&Column>) {
let column_idx = self.column_for_container_idx(idx);
let column = self.get(column_idx);
(column_idx, column)
}
#[must_use]
pub fn primary_idx(&self) -> Option<usize> {
for (i, column) in self.iter().enumerate() {
if let Column::Primary(_) = column {
return Option::from(i);
}
}
None
}
#[must_use]
pub fn primary_width_percentage(&self) -> Option<f32> {
for column in self.iter() {
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(percentage))) = column
{
return Option::from(*percentage);
}
}
None
}
pub fn set_primary_width_percentage(&mut self, percentage: f32) {
for column in self.iter_mut() {
if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(current))) = column {
*current = percentage;
}
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
// A valid layout must have at least one column
if self.is_empty() {
return false;
};
// Vertical column splits aren't supported at the moment
for column in self.iter() {
match column {
Column::Tertiary(ColumnSplit::Vertical)
| Column::Secondary(Some(ColumnSplitWithCapacity::Vertical(_))) => return false,
_ => {}
}
}
// The final column must not have a fixed capacity
match self.last() {
Some(Column::Tertiary(_)) => {}
_ => return false,
}
let mut primaries = 0;
let mut tertiaries = 0;
for column in self.iter() {
match column {
Column::Primary(_) => primaries += 1,
Column::Tertiary(_) => tertiaries += 1,
Column::Secondary(_) => {}
}
}
// There must only be one primary and one tertiary column
matches!(primaries, 1) && matches!(tertiaries, 1)
}
pub(crate) fn column_container_counts(&self) -> HashMap<usize, usize> {
let mut count_map = HashMap::new();
for (idx, column) in self.iter().enumerate() {
match column {
Column::Primary(_) | Column::Secondary(None) => {
count_map.insert(idx, 1);
}
Column::Secondary(Some(split)) => {
count_map.insert(
idx,
match split {
ColumnSplitWithCapacity::Vertical(n)
| ColumnSplitWithCapacity::Horizontal(n) => *n,
},
);
}
Column::Tertiary(_) => {}
}
}
count_map
}
#[must_use]
pub fn first_container_idx(&self, col_idx: usize) -> usize {
let count_map = self.column_container_counts();
let mut container_idx_accumulator = 0;
for i in 0..col_idx {
if let Some(n) = count_map.get(&i) {
container_idx_accumulator += n;
}
}
container_idx_accumulator
}
#[must_use]
pub fn column_for_container_idx(&self, idx: usize) -> usize {
let count_map = self.column_container_counts();
let mut container_idx_accumulator = 0;
// always -1 because we don't insert the tertiary column in the count_map
for i in 0..self.len() - 1 {
if let Some(n) = count_map.get(&i) {
container_idx_accumulator += n;
// The accumulator becomes greater than the window container index
// for the first time when we reach a column that contains that
// window container index
if container_idx_accumulator > idx {
return i;
}
}
}
// If the accumulator never reaches a point where it is greater than the
// window container index, then the only remaining possibility is the
// final tertiary column
self.len() - 1
}
#[must_use]
pub fn column_area(&self, work_area: &Rect, idx: usize, offset: Option<usize>) -> Rect {
let divisor = offset.map_or_else(|| self.len(), |offset| self.len() - offset);
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let equal_width = work_area.right / divisor as i32;
let mut left = work_area.left;
let right = equal_width;
for _ in 0..idx {
left += right;
}
Rect {
left,
top: work_area.top,
right,
bottom: work_area.bottom,
}
}
#[must_use]
pub fn column_area_with_last(
len: usize,
work_area: &Rect,
primary_right: i32,
last_column: Option<Rect>,
offset: Option<usize>,
) -> Rect {
let divisor = offset.map_or_else(|| len - 1, |offset| len - offset - 1);
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let equal_width = (work_area.right - primary_right) / divisor as i32;
let left = last_column.map_or(work_area.left, |last| last.left + last.right);
let right = equal_width;
Rect {
left,
top: work_area.top,
right,
bottom: work_area.bottom,
}
}
#[must_use]
pub fn main_column_area(
work_area: &Rect,
primary_right: i32,
last_column: Option<Rect>,
) -> Rect {
let left = last_column.map_or(work_area.left, |last| last.left + last.right);
Rect {
left,
top: work_area.top,
right: primary_right,
bottom: work_area.bottom,
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "column", content = "configuration")]
pub enum Column {
Primary(Option<ColumnWidth>),
Secondary(Option<ColumnSplitWithCapacity>),
Tertiary(ColumnSplit),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ColumnWidth {
WidthPercentage(f32),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ColumnSplit {
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ColumnSplitWithCapacity {
Horizontal(usize),
Vertical(usize),
}
+38
View File
@@ -0,0 +1,38 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum CycleDirection {
Previous,
Next,
}
impl CycleDirection {
#[must_use]
pub const fn next_idx(&self, idx: usize, len: NonZeroUsize) -> usize {
match self {
Self::Previous => {
if idx == 0 {
len.get() - 1
} else {
idx - 1
}
}
Self::Next => {
if idx == len.get() - 1 {
0
} else {
idx + 1
}
}
}
}
}
+177
View File
@@ -0,0 +1,177 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::OperationDirection;
use super::Rect;
use super::Sizing;
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Eq,
PartialEq,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
pub enum DefaultLayout {
BSP,
Columns,
Rows,
VerticalStack,
HorizontalStack,
UltrawideVerticalStack,
Grid,
RightMainVerticalStack,
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
}
impl DefaultLayout {
#[must_use]
#[allow(clippy::cast_precision_loss, clippy::only_used_in_recursion)]
pub fn resize(
&self,
unaltered: &Rect,
resize: &Option<Rect>,
edge: OperationDirection,
sizing: Sizing,
delta: i32,
) -> Option<Rect> {
if !matches!(
self,
Self::BSP
| Self::Columns
| Self::Rows
| Self::VerticalStack
| Self::RightMainVerticalStack
| Self::HorizontalStack
| Self::UltrawideVerticalStack
) {
return None;
};
let max_divisor = 1.005;
let mut r = resize.unwrap_or_default();
let resize_delta = delta;
match edge {
OperationDirection::Left => match sizing {
Sizing::Increase => {
// Some final checks to make sure the user can't infinitely resize to
// the point of pushing other windows out of bounds
// Note: These checks cannot take into account the changes made to the
// edges of adjacent windows at operation time, so it is still possible
// to push windows out of bounds by maxing out an Increase Left on a
// Window with index 1, and then maxing out a Decrease Right on a Window
// with index 0. I don't think it's worth trying to defensively program
// against this; if people end up in this situation they are better off
// just hitting the retile command
let diff = ((r.left + -resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.left += -resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.left - -resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.left -= -resize_delta;
}
}
},
OperationDirection::Up => match sizing {
Sizing::Increase => {
let diff = ((r.top + resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.top += -resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.top - resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.top -= -resize_delta;
}
}
},
OperationDirection::Right => match sizing {
Sizing::Increase => {
let diff = ((r.right + resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.right += resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.right - resize_delta) as f32).abs();
let max = unaltered.right as f32 / max_divisor;
if diff < max {
r.right -= resize_delta;
}
}
},
OperationDirection::Down => match sizing {
Sizing::Increase => {
let diff = ((r.bottom + resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.bottom += resize_delta;
}
}
Sizing::Decrease => {
let diff = ((r.bottom - resize_delta) as f32).abs();
let max = unaltered.bottom as f32 / max_divisor;
if diff < max {
r.bottom -= resize_delta;
}
}
},
};
if r.eq(&Rect::default()) {
None
} else {
Option::from(r)
}
}
#[must_use]
pub const fn cycle_next(self) -> Self {
match self {
Self::BSP => Self::Columns,
Self::Columns => Self::Rows,
Self::Rows => Self::VerticalStack,
Self::VerticalStack => Self::HorizontalStack,
Self::HorizontalStack => Self::UltrawideVerticalStack,
Self::UltrawideVerticalStack => Self::Grid,
Self::Grid => Self::RightMainVerticalStack,
Self::RightMainVerticalStack => Self::BSP,
}
}
#[must_use]
pub const fn cycle_previous(self) -> Self {
match self {
Self::BSP => Self::UltrawideVerticalStack,
Self::UltrawideVerticalStack => Self::HorizontalStack,
Self::HorizontalStack => Self::VerticalStack,
Self::VerticalStack => Self::Rows,
Self::Rows => Self::Columns,
Self::Columns => Self::Grid,
Self::Grid => Self::RightMainVerticalStack,
Self::RightMainVerticalStack => Self::BSP,
}
}
}
+475
View File
@@ -0,0 +1,475 @@
use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
use super::custom_layout::CustomLayout;
use super::DefaultLayout;
use super::OperationDirection;
pub trait Direction {
fn index_in_direction(
&self,
op_direction: OperationDirection,
idx: usize,
count: usize,
) -> Option<usize>;
fn is_valid_direction(
&self,
op_direction: OperationDirection,
idx: usize,
count: usize,
) -> bool;
fn up_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize;
fn down_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize;
fn left_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize;
fn right_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize;
}
impl Direction for DefaultLayout {
fn index_in_direction(
&self,
op_direction: OperationDirection,
idx: usize,
count: usize,
) -> Option<usize> {
match op_direction {
OperationDirection::Left => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.left_index(Some(op_direction), idx, Some(count)))
} else {
None
}
}
OperationDirection::Right => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.right_index(Some(op_direction), idx, Some(count)))
} else {
None
}
}
OperationDirection::Up => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.up_index(Some(op_direction), idx, Some(count)))
} else {
None
}
}
OperationDirection::Down => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.down_index(Some(op_direction), idx, Some(count)))
} else {
None
}
}
}
}
fn is_valid_direction(
&self,
op_direction: OperationDirection,
idx: usize,
count: usize,
) -> bool {
if count < 2 {
return false;
}
match op_direction {
OperationDirection::Up => match self {
Self::BSP => idx != 0 && idx != 1,
Self::Columns => false,
Self::Rows | Self::HorizontalStack => idx != 0,
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx > 2,
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
OperationDirection::Down => match self {
Self::BSP => idx != count - 1 && idx % 2 != 0,
Self::Columns => false,
Self::Rows => idx != count - 1,
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,
Self::HorizontalStack => idx == 0,
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
OperationDirection::Left => match self {
Self::BSP => idx != 0,
Self::Columns | Self::VerticalStack => idx != 0,
Self::RightMainVerticalStack => idx == 0,
Self::Rows => false,
Self::HorizontalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx != 1,
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
OperationDirection::Right => match self {
Self::BSP => idx % 2 == 0 && idx != count - 1,
Self::Columns => idx != count - 1,
Self::Rows => false,
Self::VerticalStack => idx == 0,
Self::RightMainVerticalStack => idx != 0,
Self::HorizontalStack => idx != 0 && idx != count - 1,
Self::UltrawideVerticalStack => match count {
2 => idx != 0,
_ => idx < 2,
},
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
}
}
fn up_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize {
match self {
Self::BSP => {
if idx % 2 == 0 {
idx - 1
} else {
idx - 2
}
}
Self::Columns => unreachable!(),
Self::Rows
| Self::VerticalStack
| Self::UltrawideVerticalStack
| Self::RightMainVerticalStack => idx - 1,
Self::HorizontalStack => 0,
Self::Grid => grid_neighbor(op_direction, idx, count),
}
}
fn down_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize {
match self {
Self::BSP
| Self::Rows
| Self::VerticalStack
| Self::UltrawideVerticalStack
| Self::RightMainVerticalStack => idx + 1,
Self::Columns => unreachable!(),
Self::HorizontalStack => 1,
Self::Grid => grid_neighbor(op_direction, idx, count),
}
}
fn left_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize {
match self {
Self::BSP => {
if idx % 2 == 0 {
idx - 2
} else {
idx - 1
}
}
Self::Columns | Self::HorizontalStack => idx - 1,
Self::Rows => unreachable!(),
Self::VerticalStack => 0,
Self::RightMainVerticalStack => 1,
Self::UltrawideVerticalStack => match idx {
0 => 1,
1 => unreachable!(),
_ => 0,
},
Self::Grid => grid_neighbor(op_direction, idx, count),
}
}
fn right_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize {
match self {
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
Self::Rows => unreachable!(),
Self::VerticalStack => 1,
Self::RightMainVerticalStack => 0,
Self::UltrawideVerticalStack => match idx {
1 => 0,
0 => 2,
_ => unreachable!(),
},
Self::Grid => grid_neighbor(op_direction, idx, count),
}
}
}
struct GridItem {
state: GridItemState,
row: usize,
num_rows: usize,
touching_edges: GridTouchingEdges,
}
enum GridItemState {
Valid,
Invalid,
}
#[allow(clippy::struct_excessive_bools)]
struct GridTouchingEdges {
left: bool,
right: bool,
up: bool,
down: bool,
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
fn get_grid_item(idx: usize, count: usize) -> GridItem {
let num_cols = (count as f32).sqrt().ceil() as usize;
let mut iter = 0;
for col in 0..num_cols {
let remaining_windows = count - iter;
let remaining_columns = num_cols - col;
let num_rows_in_this_col = remaining_windows / remaining_columns;
for row in 0..num_rows_in_this_col {
if iter == idx {
return GridItem {
state: GridItemState::Valid,
row: row + 1,
num_rows: num_rows_in_this_col,
touching_edges: GridTouchingEdges {
left: col == 0,
right: col == num_cols - 1,
up: row == 0,
down: row == num_rows_in_this_col - 1,
},
};
}
iter += 1;
}
}
GridItem {
state: GridItemState::Invalid,
row: 0,
num_rows: 0,
touching_edges: GridTouchingEdges {
left: true,
right: true,
up: true,
down: true,
},
}
}
fn is_grid_edge(op_direction: OperationDirection, idx: usize, count: usize) -> bool {
let item = get_grid_item(idx, count);
match item.state {
GridItemState::Invalid => false,
GridItemState::Valid => match op_direction {
OperationDirection::Left => item.touching_edges.left,
OperationDirection::Right => item.touching_edges.right,
OperationDirection::Up => item.touching_edges.up,
OperationDirection::Down => item.touching_edges.down,
},
}
}
fn grid_neighbor(
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
) -> usize {
let Some(op_direction) = op_direction else {
return 0;
};
let Some(count) = count else {
return 0;
};
let item = get_grid_item(idx, count);
match op_direction {
OperationDirection::Left => {
let item_from_prev_col = get_grid_item(idx - item.row, count);
if item.touching_edges.up && item.num_rows != item_from_prev_col.num_rows {
return idx - (item.num_rows - 1);
}
if item.num_rows != item_from_prev_col.num_rows && !item.touching_edges.down {
return idx - (item.num_rows - 1);
}
idx - item.num_rows
}
OperationDirection::Right => idx + item.num_rows,
OperationDirection::Up => idx - 1,
OperationDirection::Down => idx + 1,
}
}
impl Direction for CustomLayout {
fn index_in_direction(
&self,
op_direction: OperationDirection,
idx: usize,
count: usize,
) -> Option<usize> {
if count <= self.len() {
return DefaultLayout::Columns.index_in_direction(op_direction, idx, count);
}
match op_direction {
OperationDirection::Left => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.left_index(None, idx, None))
} else {
None
}
}
OperationDirection::Right => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.right_index(None, idx, None))
} else {
None
}
}
OperationDirection::Up => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.up_index(None, idx, None))
} else {
None
}
}
OperationDirection::Down => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.down_index(None, idx, None))
} else {
None
}
}
}
}
fn is_valid_direction(
&self,
op_direction: OperationDirection,
idx: usize,
count: usize,
) -> bool {
if count <= self.len() {
return DefaultLayout::Columns.is_valid_direction(op_direction, idx, count);
}
match op_direction {
OperationDirection::Left => idx != 0 && self.column_for_container_idx(idx) != 0,
OperationDirection::Right => {
idx != count - 1 && self.column_for_container_idx(idx) != self.len() - 1
}
OperationDirection::Up => {
if idx == 0 {
return false;
}
let (column_idx, column) = self.column_with_idx(idx);
column.map_or(false, |column| match column {
Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))
| Column::Tertiary(ColumnSplit::Horizontal) => {
self.column_for_container_idx(idx - 1) == column_idx
}
_ => false,
})
}
OperationDirection::Down => {
if idx == count - 1 {
return false;
}
let (column_idx, column) = self.column_with_idx(idx);
column.map_or(false, |column| match column {
Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))
| Column::Tertiary(ColumnSplit::Horizontal) => {
self.column_for_container_idx(idx + 1) == column_idx
}
_ => false,
})
}
}
}
fn up_index(
&self,
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
) -> usize {
idx - 1
}
fn down_index(
&self,
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
) -> usize {
idx + 1
}
fn left_index(
&self,
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
) -> usize {
let column_idx = self.column_for_container_idx(idx);
if column_idx - 1 == 0 {
0
} else {
self.first_container_idx(column_idx - 1)
}
}
fn right_index(
&self,
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
) -> usize {
let column_idx = self.column_for_container_idx(idx);
self.first_container_idx(column_idx + 1)
}
}
+32
View File
@@ -0,0 +1,32 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use super::Arrangement;
use super::CustomLayout;
use super::DefaultLayout;
use super::Direction;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum Layout {
Default(DefaultLayout),
Custom(CustomLayout),
}
impl Layout {
#[must_use]
pub fn as_boxed_direction(&self) -> Box<dyn Direction> {
match self {
Layout::Default(layout) => Box::new(*layout),
Layout::Custom(layout) => Box::new(layout.clone()),
}
}
#[must_use]
pub fn as_boxed_arrangement(&self) -> Box<dyn Arrangement> {
match self {
Layout::Default(layout) => Box::new(*layout),
Layout::Custom(layout) => Box::new(layout.clone()),
}
}
}
+449
View File
@@ -0,0 +1,449 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
pub use animation::AnimationStyle;
pub use arrangement::Arrangement;
pub use arrangement::Axis;
pub use custom_layout::CustomLayout;
pub use cycle_direction::CycleDirection;
pub use default_layout::DefaultLayout;
pub use direction::Direction;
pub use layout::Layout;
pub use operation_direction::OperationDirection;
pub use rect::Rect;
pub mod animation;
pub mod arrangement;
pub mod config_generation;
pub mod custom_layout;
pub mod cycle_direction;
pub mod default_layout;
pub mod direction;
pub mod layout;
pub mod operation_direction;
pub mod rect;
#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum SocketMessage {
// Window / Container Commands
FocusWindow(OperationDirection),
MoveWindow(OperationDirection),
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
StackAll,
UnstackAll,
ResizeWindowEdge(OperationDirection, Sizing),
ResizeWindowAxis(Axis, Sizing),
UnstackWindow,
CycleStack(CycleDirection),
MoveContainerToMonitorNumber(usize),
CycleMoveContainerToMonitor(CycleDirection),
MoveContainerToWorkspaceNumber(usize),
MoveContainerToNamedWorkspace(String),
CycleMoveContainerToWorkspace(CycleDirection),
SendContainerToMonitorNumber(usize),
CycleSendContainerToMonitor(CycleDirection),
SendContainerToWorkspaceNumber(usize),
CycleSendContainerToWorkspace(CycleDirection),
SendContainerToMonitorWorkspaceNumber(usize, usize),
MoveContainerToMonitorWorkspaceNumber(usize, usize),
SendContainerToNamedWorkspace(String),
CycleMoveWorkspaceToMonitor(CycleDirection),
MoveWorkspaceToMonitorNumber(usize),
SwapWorkspacesToMonitorNumber(usize),
ForceFocus,
Close,
Minimize,
Promote,
PromoteFocus,
PromoteWindow(OperationDirection),
ToggleFloat,
ToggleMonocle,
ToggleMaximize,
ToggleWindowContainerBehaviour,
WindowHidingBehaviour(HidingBehaviour),
ToggleCrossMonitorMoveBehaviour,
CrossMonitorMoveBehaviour(MoveBehaviour),
UnmanagedWindowOperationBehaviour(OperationBehaviour),
// Current Workspace Commands
ManageFocusedWindow,
UnmanageFocusedWindow,
AdjustContainerPadding(Sizing, i32),
AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout),
CycleLayout(CycleDirection),
ChangeLayoutCustom(PathBuf),
FlipLayout(Axis),
// Monitor and Workspace Commands
MonitorIndexPreference(usize, i32, i32, i32, i32),
DisplayIndexPreference(usize, String),
EnsureWorkspaces(usize, usize),
EnsureNamedWorkspaces(usize, Vec<String>),
NewWorkspace,
ToggleTiling,
Stop,
TogglePause,
Retile,
QuickSave,
QuickLoad,
Save(PathBuf),
Load(PathBuf),
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusLastWorkspace,
FocusWorkspaceNumber(usize),
FocusWorkspaceNumbers(usize),
FocusMonitorWorkspaceNumber(usize, usize),
FocusNamedWorkspace(String),
ContainerPadding(usize, usize, i32),
NamedWorkspaceContainerPadding(String, i32),
FocusedWorkspaceContainerPadding(i32),
WorkspacePadding(usize, usize, i32),
NamedWorkspacePadding(String, i32),
FocusedWorkspacePadding(i32),
WorkspaceTiling(usize, usize, bool),
NamedWorkspaceTiling(String, bool),
WorkspaceName(usize, usize, String),
WorkspaceLayout(usize, usize, DefaultLayout),
NamedWorkspaceLayout(String, DefaultLayout),
WorkspaceLayoutCustom(usize, usize, PathBuf),
NamedWorkspaceLayoutCustom(String, PathBuf),
WorkspaceLayoutRule(usize, usize, usize, DefaultLayout),
NamedWorkspaceLayoutRule(String, usize, DefaultLayout),
WorkspaceLayoutCustomRule(usize, usize, usize, PathBuf),
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
ClearWorkspaceLayoutRules(usize, usize),
ClearNamedWorkspaceLayoutRules(String),
// Configuration
ReloadConfiguration,
ReloadStaticConfiguration(PathBuf),
WatchConfiguration(bool),
CompleteConfiguration,
AltFocusHack(bool),
Animation(bool),
AnimationDuration(u64),
AnimationFps(u64),
AnimationStyle(AnimationStyle),
#[serde(alias = "ActiveWindowBorder")]
Border(bool),
#[serde(alias = "ActiveWindowBorderColour")]
BorderColour(WindowKind, u32, u32, u32),
#[serde(alias = "ActiveWindowBorderStyle")]
BorderStyle(BorderStyle),
BorderWidth(i32),
BorderOffset(i32),
BorderImplementation(BorderImplementation),
Transparency(bool),
TransparencyAlpha(u8),
InvisibleBorders(Rect),
StackbarMode(StackbarMode),
StackbarLabel(StackbarLabel),
StackbarFocusedTextColour(u32, u32, u32),
StackbarUnfocusedTextColour(u32, u32, u32),
StackbarBackgroundColour(u32, u32, u32),
StackbarHeight(i32),
StackbarTabWidth(i32),
StackbarFontSize(i32),
StackbarFontFamily(Option<String>),
WorkAreaOffset(Rect),
MonitorWorkAreaOffset(usize, Rect),
ResizeDelta(i32),
InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),
InitialNamedWorkspaceRule(ApplicationIdentifier, String, String),
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
NamedWorkspaceRule(ApplicationIdentifier, String, String),
ClearWorkspaceRules(usize, usize),
ClearNamedWorkspaceRules(String),
ClearAllWorkspaceRules,
FloatRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),
IdentifyObjectNameChangeApplication(ApplicationIdentifier, String),
IdentifyTrayApplication(ApplicationIdentifier, String),
IdentifyLayeredApplication(ApplicationIdentifier, String),
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
State,
GlobalState,
VisibleWindows,
MonitorInformation,
Query(StateQuery),
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
MouseFollowsFocus(bool),
ToggleMouseFollowsFocus,
RemoveTitleBar(ApplicationIdentifier, String),
ToggleTitleBars,
AddSubscriberSocket(String),
RemoveSubscriberSocket(String),
AddSubscriberPipe(String),
RemoveSubscriberPipe(String),
ApplicationSpecificConfigurationSchema,
NotificationSchema,
SocketSchema,
StaticConfigSchema,
GenerateStaticConfig,
DebugWindow(isize),
}
impl SocketMessage {
pub fn as_bytes(&self) -> Result<Vec<u8>> {
Ok(serde_json::to_string(self)?.as_bytes().to_vec())
}
}
impl FromStr for SocketMessage {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema)]
pub enum StackbarMode {
Always,
Never,
OnStack,
}
#[derive(
Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
)]
pub enum StackbarLabel {
#[default]
Process,
Title,
}
#[derive(
Default,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Display,
Serialize,
Deserialize,
JsonSchema,
ValueEnum,
)]
pub enum BorderStyle {
#[default]
/// Use the system border style
System,
/// Use the Windows 11-style rounded borders
Rounded,
/// Use the Windows 10-style square borders
Square,
}
#[derive(
Default,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Display,
Serialize,
Deserialize,
JsonSchema,
ValueEnum,
)]
pub enum BorderImplementation {
#[default]
/// Use the adjustable komorebi border implementation
Komorebi,
/// Use the thin Windows accent border implementation
Windows,
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
pub enum WindowKind {
Single,
Stack,
Monocle,
Unfocused,
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum StateQuery {
FocusedMonitorIndex,
FocusedWorkspaceIndex,
FocusedContainerIndex,
FocusedWindowIndex,
}
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
pub enum ApplicationIdentifier {
#[serde(alias = "exe")]
Exe,
#[serde(alias = "class")]
Class,
#[serde(alias = "title")]
Title,
#[serde(alias = "path")]
Path,
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum FocusFollowsMouseImplementation {
/// A custom FFM implementation (slightly more CPU-intensive)
Komorebi,
/// The native (legacy) Windows FFM implementation
Windows,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum WindowContainerBehaviour {
/// Create a new container for each new window
Create,
/// Append new windows to the focused window container
Append,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor
Swap,
/// Insert the window container into the focused workspace on the adjacent monitor
Insert,
/// Do nothing if trying to move a window container in the direction of an adjacent monitor
NoOp,
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum HidingBehaviour {
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
Hide,
/// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
Minimize,
/// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces
Cloak,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows
Op,
/// Ignore komorebic commands on temporarily unmanaged/floated windows
NoOp,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum Sizing {
Increase,
Decrease,
}
impl Sizing {
#[must_use]
pub const fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {
match self {
Self::Increase => value + adjustment,
Self::Decrease => {
if value > 0 && value - adjustment >= 0 {
value - adjustment
} else {
value
}
}
}
}
}
pub fn resolve_home_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let mut resolved_path = PathBuf::new();
let mut resolved = false;
for c in path.as_ref().components() {
match c {
std::path::Component::Normal(c)
if (c == "~" || c == "$Env:USERPROFILE" || c == "$HOME") && !resolved =>
{
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
resolved_path.extend(home.components());
resolved = true;
}
std::path::Component::Normal(c) if (c == "$Env:KOMOREBI_CONFIG_HOME") && !resolved => {
let komorebi_config_home =
PathBuf::from(std::env::var("KOMOREBI_CONFIG_HOME").ok().ok_or_else(|| {
anyhow!("there is no KOMOREBI_CONFIG_HOME environment variable set")
})?);
resolved_path.extend(komorebi_config_home.components());
resolved = true;
}
_ => resolved_path.push(c),
}
}
let parent = resolved_path
.parent()
.ok_or_else(|| anyhow!("cannot parse parent directory"))?;
Ok(if parent.is_dir() {
let file = resolved_path
.components()
.last()
.ok_or_else(|| anyhow!("cannot parse filename"))?;
dunce::canonicalize(parent)?.join(file)
} else {
resolved_path
})
}
+65
View File
@@ -0,0 +1,65 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::direction::Direction;
use super::Axis;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum OperationDirection {
Left,
Right,
Up,
Down,
}
impl OperationDirection {
#[must_use]
pub const fn opposite(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
Self::Up => Self::Down,
Self::Down => Self::Up,
}
}
fn flip(self, layout_flip: Option<Axis>) -> Self {
layout_flip.map_or(self, |flip| match self {
Self::Left => match flip {
Axis::Horizontal | Axis::HorizontalAndVertical => Self::Right,
Axis::Vertical => self,
},
Self::Right => match flip {
Axis::Horizontal | Axis::HorizontalAndVertical => Self::Left,
Axis::Vertical => self,
},
Self::Up => match flip {
Axis::Vertical | Axis::HorizontalAndVertical => Self::Down,
Axis::Horizontal => self,
},
Self::Down => match flip {
Axis::Vertical | Axis::HorizontalAndVertical => Self::Up,
Axis::Horizontal => self,
},
})
}
#[must_use]
pub fn destination(
self,
layout: &dyn Direction,
layout_flip: Option<Axis>,
idx: usize,
len: NonZeroUsize,
) -> Option<usize> {
layout.index_in_direction(self.flip(layout_flip), idx, len.get())
}
}
+97
View File
@@ -0,0 +1,97 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use windows::Win32::Foundation::RECT;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
pub struct Rect {
/// The left point in a Win32 Rect
pub left: i32,
/// The top point in a Win32 Rect
pub top: i32,
/// The right point in a Win32 Rect
pub right: i32,
/// The bottom point in a Win32 Rect
pub bottom: i32,
}
impl From<RECT> for Rect {
fn from(rect: RECT) -> Self {
Self {
left: rect.left,
top: rect.top,
right: rect.right - rect.left,
bottom: rect.bottom - rect.top,
}
}
}
impl From<Rect> for RECT {
fn from(rect: Rect) -> Self {
Self {
left: rect.left,
top: rect.top,
right: rect.right,
bottom: rect.bottom,
}
}
}
impl Rect {
/// decrease the size of self by the padding amount.
pub fn add_padding<T>(&mut self, padding: T)
where
T: Into<Option<i32>>,
{
if let Some(padding) = padding.into() {
self.left += padding;
self.top += padding;
self.right -= padding * 2;
self.bottom -= padding * 2;
}
}
/// increase the size of self by the margin amount.
pub fn add_margin(&mut self, margin: i32) {
self.left -= margin;
self.top -= margin;
self.right += margin * 2;
self.bottom += margin * 2;
}
pub fn left_padding(&mut self, padding: i32) {
self.left += padding;
}
pub fn right_padding(&mut self, padding: i32) {
self.right -= padding;
}
#[must_use]
pub const 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
}
#[must_use]
pub const fn scale(&self, system_dpi: i32, rect_dpi: i32) -> Rect {
Rect {
left: (self.left * rect_dpi) / system_dpi,
top: (self.top * rect_dpi) / system_dpi,
right: (self.right * rect_dpi) / system_dpi,
bottom: (self.bottom * rect_dpi) / system_dpi,
}
}
#[must_use]
pub const fn rect(&self) -> RECT {
RECT {
left: self.left,
top: self.top,
right: self.left + self.right,
bottom: self.top + self.bottom,
}
}
}
+5 -8
View File
@@ -8,6 +8,7 @@ pub mod com;
pub mod ring;
pub mod colour;
pub mod container;
pub mod core;
pub mod focus_manager;
pub mod monitor;
pub mod monitor_reconciliator;
@@ -48,6 +49,7 @@ use std::sync::Arc;
pub use animation::*;
pub use animation_manager::*;
pub use colour::*;
pub use core::*;
pub use process_command::*;
pub use process_event::*;
pub use static_config::*;
@@ -57,15 +59,10 @@ pub use window_manager_event::*;
pub use windows_api::WindowsApi;
pub use windows_api::*;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use color_eyre::Result;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::AnimationStyle;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
use komorebi_core::Rect;
use komorebi_core::SocketMessage;
use os_info::Version;
use parking_lot::Mutex;
use regex::Regex;
+1 -1
View File
@@ -12,7 +12,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use komorebi_core::Rect;
use crate::core::Rect;
use crate::container::Container;
use crate::ring::Ring;
+1 -1
View File
@@ -1,6 +1,7 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::border_manager;
use crate::core::Rect;
use crate::monitor;
use crate::monitor::Monitor;
use crate::monitor_reconciliator::hidden::Hidden;
@@ -10,7 +11,6 @@ use crate::WindowsApi;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_core::Rect;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
+17 -17
View File
@@ -21,23 +21,23 @@ use schemars::gen::SchemaSettings;
use schemars::schema_for;
use uds_windows::UnixStream;
use komorebi_core::config_generation::ApplicationConfiguration;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
use komorebi_core::BorderImplementation;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::SocketMessage;
use komorebi_core::StateQuery;
use komorebi_core::WindowContainerBehaviour;
use komorebi_core::WindowKind;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::ApplicationIdentifier;
use crate::core::Axis;
use crate::core::BorderImplementation;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::core::Sizing;
use crate::core::SocketMessage;
use crate::core::StateQuery;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowKind;
use crate::border_manager;
use crate::border_manager::IMPLEMENTATION;
+4 -4
View File
@@ -9,10 +9,10 @@ use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::WindowContainerBehaviour;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::core::Sizing;
use crate::core::WindowContainerBehaviour;
use crate::border_manager;
use crate::border_manager::BORDER_OFFSET;
+1 -1
View File
@@ -5,7 +5,7 @@ use winput::message_loop;
use winput::message_loop::Event;
use winput::Action;
use komorebi_core::FocusFollowsMouseImplementation;
use crate::core::FocusFollowsMouseImplementation;
use crate::window_manager::WindowManager;
+2 -2
View File
@@ -1,6 +1,8 @@
mod stackbar;
use crate::container::Container;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::stackbar_manager::stackbar::Stackbar;
use crate::WindowManager;
use crate::WindowsApi;
@@ -9,8 +11,6 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_core::StackbarLabel;
use komorebi_core::StackbarMode;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use std::collections::hash_map::Entry;
+3 -3
View File
@@ -2,6 +2,9 @@ use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::STYLE;
use crate::container::Container;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::core::StackbarLabel;
use crate::stackbar_manager::STACKBARS_CONTAINERS;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
@@ -15,9 +18,6 @@ use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WINDOWS_11;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_core::BorderStyle;
use komorebi_core::Rect;
use komorebi_core::StackbarLabel;
use std::os::windows::ffi::OsStrExt;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
+22 -22
View File
@@ -4,6 +4,9 @@ use crate::border_manager::IMPLEMENTATION;
use crate::border_manager::STYLE;
use crate::border_manager::Z_ORDER;
use crate::colour::Colour;
use crate::core::BorderImplementation;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::current_virtual_desktop;
use crate::monitor::Monitor;
use crate::monitor_reconciliator;
@@ -41,33 +44,30 @@ use crate::REGEX_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_RULES;
use komorebi_core::BorderImplementation;
use komorebi_core::StackbarLabel;
use komorebi_core::StackbarMode;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use crate::core::AnimationStyle;
use crate::core::ApplicationIdentifier;
use crate::core::BorderStyle;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::WindowContainerBehaviour;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
use hotwatch::Hotwatch;
use komorebi_core::config_generation::ApplicationConfiguration;
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
use komorebi_core::config_generation::ApplicationOptions;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::resolve_home_path;
use komorebi_core::AnimationStyle;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::BorderStyle;
use komorebi_core::DefaultLayout;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::HidingBehaviour;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationBehaviour;
use komorebi_core::Rect;
use komorebi_core::SocketMessage;
use komorebi_core::WindowContainerBehaviour;
use parking_lot::Mutex;
use regex::Regex;
use schemars::JsonSchema;
+6 -6
View File
@@ -15,12 +15,12 @@ use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::time::Duration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use color_eyre::eyre;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use regex::Regex;
use schemars::JsonSchema;
use serde::ser::SerializeStruct;
@@ -29,9 +29,9 @@ use serde::Serialize;
use serde::Serializer;
use windows::Win32::Foundation::HWND;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
use komorebi_core::Rect;
use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::animation::Animation;
use crate::styles::ExtendedWindowStyle;
+18 -18
View File
@@ -22,27 +22,28 @@ use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixListener;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::custom_layout::CustomLayout;
use komorebi_core::Arrangement;
use komorebi_core::Axis;
use komorebi_core::BorderStyle;
use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::HidingBehaviour;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::StackbarLabel;
use komorebi_core::WindowContainerBehaviour;
use crate::core::config_generation::MatchingRule;
use crate::core::custom_layout::CustomLayout;
use crate::core::Arrangement;
use crate::core::Axis;
use crate::core::BorderStyle;
use crate::core::CycleDirection;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::core::Sizing;
use crate::core::StackbarLabel;
use crate::core::WindowContainerBehaviour;
use crate::border_manager;
use crate::border_manager::STYLE;
use crate::container::Container;
use crate::core::StackbarMode;
use crate::current_virtual_desktop;
use crate::load_configuration;
use crate::monitor::Monitor;
@@ -80,7 +81,6 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_RULES;
use komorebi_core::StackbarMode;
#[derive(Debug)]
pub struct WindowManager {
+1 -1
View File
@@ -134,7 +134,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use komorebi_core::Rect;
use crate::core::Rect;
use crate::container::Container;
use crate::monitor;
+7 -7
View File
@@ -12,13 +12,13 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use komorebi_core::Axis;
use komorebi_core::CustomLayout;
use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::Layout;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use crate::core::Axis;
use crate::core::CustomLayout;
use crate::core::CycleDirection;
use crate::core::DefaultLayout;
use crate::core::Layout;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;