Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
dd045a6cbc feat(stackbar): add stackbar manager module
This commit removes all stackbar-related code from Container, Workspace,
process_command, process_event etc. and centralizes it in the new
stackbar_manager module.

Instead of trying to figure out where in process_event and
process_command we should make stackbar-related changes, a notification
gets sent to a channel that stackbar_manager listens to whenever an
event or command has finished processing.

The stackbar_manager listener, upon receiving a notification, acquires a
lock on the WindowManager instance and updates stackbars for the focused
workspace on every monitor; this allows us to centralize all edge case
handling within the stackbar_manager listener's loop.

Global state related to stackbars has also been moved into the
stackbar_manager module, which also tracks the state of stackbar objects
(STACKBAR_STATE), mappings between stackbars and containers
(STACKBARS_CONTAINERS) and the mappings between stackbars and monitors
(STACKBARS_MONITORS).

A number of edge cases around stackbar behaviour have been addressed in
this commit (re #832), and stackbars now respect the "border_style"
configuration option.
2024-05-19 10:48:56 -07:00
65 changed files with 938 additions and 5374 deletions

View File

@@ -93,10 +93,9 @@ jobs:
target/${{ matrix.target }}/release/komorebi.exe
target/${{ matrix.target }}/release/komorebic.exe
target/${{ matrix.target }}/release/komorebic-no-console.exe
target/${{ matrix.target }}/release/komorebi-gui.exe
target/${{ matrix.target }}/release/komorebi.pdb
target/${{ matrix.target }}/release/komorebic.pdb
target/${{ matrix.target }}/release/komorebi-gui.pdb
target/${{ matrix.target }}/release/komorebic-no-console.pdb
target/wix/komorebi-*.msi
retention-days: 7
- name: Check GoReleaser

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@
/target
CHANGELOG.md
dummy.go
komorebic/applications.yaml
komorebi.ahk
komorebic/applications.yaml

View File

@@ -35,15 +35,6 @@ builds:
post:
- mkdir -p dist/windows_amd64
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic-no-console.exe" ".\dist\komorebic-no-console_windows_amd64_v1\komorebic-no-console.exe"
- id: komorebi-gui
main: dummy.go
goos: [ "windows" ]
goarch: [ "amd64" ]
binary: komorebi-gui
hooks:
post:
- mkdir -p dist/windows_amd64
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi-gui.exe" ".\dist\komorebi-gui_windows_amd64_v1\komorebi-gui.exe"
archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"

3254
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,25 +2,24 @@
resolver = "2"
members = [
"derive-ahk",
"komorebi",
"komorebi-client",
"komorebi-core",
"komorebi-gui",
"komorebic",
"komorebic-no-console",
]
[workspace.dependencies]
color-eyre = "0.6"
dirs = "5"
dunce = "1"
serde = { version = "1", features = ["derive"] }
serde_json = { package = "serde_json_lenient", version = "0.2" }
sysinfo = "0.30"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "2a0f7166da154880a1750b91829b1186d9c6a00c" }
windows-implement = { version = "0.53" }
windows-interface = { version = "0.53" }
windows-implement = { version = "0.53" }
dunce = "1"
dirs = "5"
color-eyre = "0.6"
serde_json = { package = "serde_json_lenient", version = "0.1" }
sysinfo = "0.30"
serde = { version = "1", features = ["derive"] }
uds_windows = "1"
[workspace.dependencies.windows]
version = "0.54"

14
derive-ahk/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "derive-ahk"
version = "0.1.1"
edition = "2021"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"

225
derive-ahk/src/lib.rs Normal file
View File

@@ -0,0 +1,225 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![no_implicit_prelude]
use ::std::clone::Clone;
use ::std::convert::From;
use ::std::convert::Into;
use ::std::format;
use ::std::iter::Extend;
use ::std::iter::Iterator;
use ::std::matches;
use ::std::option::Option::Some;
use ::std::string::String;
use ::std::string::ToString;
use ::std::unreachable;
use ::std::vec::Vec;
use ::quote::quote;
use ::syn::parse_macro_input;
use ::syn::Data;
use ::syn::DataEnum;
use ::syn::DeriveInput;
use ::syn::Fields;
use ::syn::FieldsNamed;
use ::syn::FieldsUnnamed;
use ::syn::Meta;
use ::syn::NestedMeta;
#[allow(clippy::too_many_lines)]
#[proc_macro_derive(AhkFunction)]
pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
match input.data {
Data::Struct(s) => match s.fields {
Fields::Named(FieldsNamed { named, .. }) => {
let argument_idents = named
.iter()
// Filter out the flags
.filter(|&f| {
let mut include = true;
for attribute in &f.attrs {
if let ::std::result::Result::Ok(Meta::List(list)) =
attribute.parse_meta()
{
for nested in list.nested {
if let NestedMeta::Meta(Meta::Path(path)) = nested {
if path.is_ident("long") {
include = false;
}
}
}
}
}
include
})
.map(|f| &f.ident);
let argument_idents_clone = argument_idents.clone();
let called_arguments = quote! {#(%#argument_idents_clone%) *}
.to_string()
.replace(" %", "%")
.replace("% ", "%")
.replace("%%", "% %");
let flag_idents = named
.iter()
// Filter only the flags
.filter(|f| {
let mut include = false;
for attribute in &f.attrs {
if let ::std::result::Result::Ok(Meta::List(list)) =
attribute.parse_meta()
{
for nested in list.nested {
if let NestedMeta::Meta(Meta::Path(path)) = nested {
// Identify them using the --long flag name
if path.is_ident("long") {
include = true;
}
}
}
}
}
include
})
.map(|f| &f.ident);
let has_flags = flag_idents.clone().count() != 0;
if has_flags {
let flag_idents_concat = flag_idents.clone();
let argument_idents_concat = argument_idents.clone();
// Concat the args and flag args if there are flags
let all_arguments =
quote! {#(#argument_idents_concat,) * #(#flag_idents_concat), *}
.to_string();
let flag_idents_clone = flag_idents.clone();
let flags = quote! {#(--#flag_idents_clone) *}
.to_string()
.replace("- - ", "--")
.replace('_', "-");
let called_flag_arguments = quote! {#(%#flag_idents%) *}
.to_string()
.replace(" %", "%")
.replace("% ", "%")
.replace("%%", "% %");
let flags_split: Vec<_> = flags.split(' ').collect();
let flag_args_split: Vec<_> = called_flag_arguments.split(' ').collect();
let mut consolidated_flags: Vec<String> = Vec::new();
for (idx, flag) in flags_split.iter().enumerate() {
consolidated_flags.push(format!("{} {}", flag, flag_args_split[idx]));
}
let all_flags = consolidated_flags.join(" ");
quote! {
impl AhkFunction for #name {
fn generate_ahk_function() -> String {
::std::format!(r#"
{}({}) {{
RunWait, komorebic.exe {} {} {}, , Hide
}}"#,
::std::stringify!(#name),
#all_arguments,
::std::stringify!(#name).to_kebab_case(),
#called_arguments,
#all_flags,
)
}
}
}
} else {
let arguments = quote! {#(#argument_idents), *}.to_string();
quote! {
impl AhkFunction for #name {
fn generate_ahk_function() -> String {
::std::format!(r#"
{}({}) {{
RunWait, komorebic.exe {} {}, , Hide
}}"#,
::std::stringify!(#name),
#arguments,
::std::stringify!(#name).to_kebab_case(),
#called_arguments
)
}
}
}
}
}
_ => unreachable!("only to be used on structs with named fields"),
},
_ => unreachable!("only to be used on structs"),
}
.into()
}
#[proc_macro_derive(AhkLibrary)]
pub fn ahk_library(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
match input.data {
Data::Enum(DataEnum { variants, .. }) => {
let enums = variants.iter().filter(|&v| {
matches!(v.fields, Fields::Unit) || matches!(v.fields, Fields::Unnamed(..))
});
let mut stream = ::proc_macro2::TokenStream::new();
for variant in enums.clone() {
match &variant.fields {
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
for field in unnamed {
stream.extend(quote! {
v.push(#field::generate_ahk_function());
});
}
}
Fields::Unit => {
let name = &variant.ident;
stream.extend(quote! {
v.push(::std::format!(r#"
{}() {{
RunWait, komorebic.exe {}, , Hide
}}"#,
::std::stringify!(#name),
::std::stringify!(#name).to_kebab_case()
));
});
}
Fields::Named(_) => {
unreachable!("only to be used with unnamed and unit fields");
}
}
}
quote! {
impl #name {
fn generate_ahk_library() -> String {
let mut v: Vec<String> = vec![String::from("; Generated by komorebic.exe")];
#stream
v.join("\n")
}
}
}
}
_ => unreachable!("only to be used on enums"),
}
.into()
}

View File

@@ -3,7 +3,7 @@
```
Generate a library of AutoHotKey helper functions
Usage: komorebic.exe komorebic.exe ahk-library
Usage: komorebic.exe ahk-library
Options:
-h, --help

View File

@@ -1,16 +0,0 @@
# cycle-move-workspace-to-monitor
```
Move the focused workspace monitor in the given cycle direction
Usage: komorebic.exe cycle-move-workspace-to-monitor <CYCLE_DIRECTION>
Arguments:
<CYCLE_DIRECTION>
[possible values: previous, next]
Options:
-h, --help
Print help
```

View File

@@ -1,12 +0,0 @@
# gui
```
Launch the komorebi-gui debugging tool
Usage: komorebic.exe gui
Options:
-h, --help
Print help
```

View File

@@ -1,12 +0,0 @@
# monitor-information
```
Show information about connected monitors
Usage: komorebic.exe monitor-information
Options:
-h, --help
Print help
```

View File

@@ -1,16 +0,0 @@
# promote-window
```
Promote the window in the specified direction
Usage: komorebic.exe promote-window <OPERATION_DIRECTION>
Arguments:
<OPERATION_DIRECTION>
[possible values: left, right, up, down]
Options:
-h, --help
Print help
```

View File

@@ -1,12 +0,0 @@
# stack-all
```
Stack all windows on the focused workspace
Usage: komorebic.exe stack-all
Options:
-h, --help
Print help
```

View File

@@ -1,16 +0,0 @@
# transparency-alpha
```
Set the alpha value for unfocused window transparency
Usage: komorebic.exe transparency-alpha <ALPHA>
Arguments:
<ALPHA>
Alpha
Options:
-h, --help
Print help
```

View File

@@ -1,16 +0,0 @@
# transparency
```
Enable or disable transparency for unfocused windows
Usage: komorebic.exe transparency <BOOLEAN_STATE>
Arguments:
<BOOLEAN_STATE>
[possible values: enable, disable]
Options:
-h, --help
Print help
```

View File

@@ -1,12 +0,0 @@
# unstack-all
```
Unstack all windows in the focused container
Usage: komorebic.exe unstack-all
Options:
-h, --help
Print help
```

View File

@@ -1,4 +1,4 @@
# AutoHotkey
# AutoHotKey
If you would like to use Autohotkey, please make sure you have AutoHotKey v2
installed.
@@ -10,8 +10,8 @@ able to craft their own configuration files.
If you would like to try out AHK, here is a simple sample configuration which
largely matches the `whkdrc` sample configuration.
```autohotkey
{% include "./komorebi.ahk.txt" %}
```
{% include "../komorebi.ahk" %}
```
By default, the `komorebi.ahk` file should be located in the `$Env:USERPROFILE`
@@ -19,4 +19,4 @@ directory, however, if `$Env:KOMOREBI_CONFIG_HOME` is set, it should be located
there.
Once the file is in place, you can stop komorebi and whkd by running `komorebic stop --whkd`,
and then start komorebi with Autohotkey by running `komorebic start --ahk`.
and then start komorebi with Autohotkey by running `komorebic start --ahk`.

View File

@@ -1,4 +1,4 @@
# Dynamic Layout Switching
# Dynamically Layout Switching
With `komorebi` it is possible to define rules to automatically change the
layout on a specified workspace when a threshold of window containers is met.

View File

@@ -1,17 +0,0 @@
# Setting a Given Display to a Specific Index
If you would like `komorebi` to remember monitor index positions, you will need to set the `display_index_preferences`
configuration option in the static configuration file.
Display IDs can be found using `komorebic monitor-information`.
Then, in `komorebi.json`, you simply need to specify the preferred index position for each display ID:
```json
{
"display_index_preferences": {
"0": "DEL4310-5&1a6c0954&0&UID209155",
"1": "<another-display_id>"
}
}
```

View File

@@ -1,11 +1,11 @@
# Getting started
`komorebi` is a tiling window manager for Windows that is comprised of two
main binaries, `komorebi.exe`, which contains the window manager itself,
`komorebi` is a tiling window manager for Windows that is comprised comprised
of two main binaries, `komorebi.exe`, which contains the window manager itself,
and `komorebic.exe`, which is the main way to send commands to the tiling
window manager.
It is important to note that neither `komorebi.exe` nor `komorebic.exe` handle
It is important to note that neither `komorebi.exe` or `komorebic.exe` handle
key bindings, because `komorebi` is a tiling window manager and not a hotkey
daemon.
@@ -27,7 +27,7 @@ to manipulate the window manager, you use
`komorebi` is available pre-built to install via
[Scoop](https://scoop.sh/#/apps?q=komorebi) and
[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also build
[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also built
it from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.
- [Scoop](#scoop)
@@ -37,7 +37,7 @@ it from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.
## Long path support
It is highly recommended that you enable support for long paths in Windows by
It highly recommended that you enable support for long paths in Windows by
running the following command in an Administrator Terminal before installing
`komorebi`.
@@ -45,7 +45,7 @@ running the following command in an Administrator Terminal before installing
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
```
## Disabling unnecessary system animations
## Disabling Unnecessary System Animations
It is highly recommended that you enable the "Turn off all unnecessary animations (when possible)" option in
"Control Panel > Ease of Access > Ease of Access Centre / Make the computer easier to see" for the best performance with
@@ -114,7 +114,6 @@ Clone the git repository, enter the directory, and build the following binaries:
cargo +stable install --path komorebi --locked
cargo +stable install --path komorebic --locked
cargo +stable install --path komorebic-no-console --locked
cargo +stable install --path komorebi-gui --locked
```
If the binaries have been built and added to your `$PATH` correctly, you should
@@ -128,21 +127,3 @@ an offline machine to install.
Once installed, proceed to get the [example configurations](example-configurations.md) (none of the commands for
first-time set up and running komorebi require an internet connection).
## Uninstallation
Before uninstalling, first run `komorebic stop --whkd` to make sure that both
the `komorebi` and `whkd` processes have been stopped.
Then, depending on whether you installed with Scoop or WinGet, run `scoop
uninstall komorebi whkd` or `winget uninstall LGUG2Z.komorebi LGUG2Z.whkd`.
Finally, you can run the following commands in a PowerShell prompt to clean up
files created by the `quickstart` command and any other runtime files:
```powershell
rm $Env:USERPROFILE\komorebi.json
rm $Env:USERPROFILE\applications.yaml
rm $Env:USERPROFILE\.config\whkdrc
rm -r -Force $Env:LOCALAPPDATA\komorebi
```

View File

@@ -1,71 +0,0 @@
#Requires AutoHotkey v2.0.2
#SingleInstance Force
Komorebic(cmd) {
RunWait(format("komorebic.exe {}", cmd), , "Hide")
}
!q::Komorebic("close")
!m::Komorebic("minimize")
; Focus windows
!h::Komorebic("focus left")
!j::Komorebic("focus down")
!k::Komorebic("focus up")
!l::Komorebic("focus right")
!+[::Komorebic("cycle-focus previous")
!+]::Komorebic("cycle-focus next")
; Move windows
!+h::Komorebic("move left")
!+j::Komorebic("move down")
!+k::Komorebic("move up")
!+l::Komorebic("move right")
; Stack windows
!Left::Komorebic("stack left")
!Down::Komorebic("stack down")
!Up::Komorebic("stack up")
!Right::Komorebic("stack right")
!;::Komorebic("unstack")
![::Komorebic("cycle-stack previous")
!]::Komorebic("cycle-stack next")
; Resize
!=::Komorebic("resize-axis horizontal increase")
!-::Komorebic("resize-axis horizontal decrease")
!+=::Komorebic("resize-axis vertical increase")
!+_::Komorebic("resize-axis vertical decrease")
; Manipulate windows
!t::Komorebic("toggle-float")
!f::Komorebic("toggle-monocle")
; Window manager options
!+r::Komorebic("retile")
!p::Komorebic("toggle-pause")
; Layouts
!x::Komorebic("flip-layout horizontal")
!y::Komorebic("flip-layout vertical")
; Workspaces
!1::Komorebic("focus-workspace 0")
!2::Komorebic("focus-workspace 1")
!3::Komorebic("focus-workspace 2")
!4::Komorebic("focus-workspace 3")
!5::Komorebic("focus-workspace 4")
!6::Komorebic("focus-workspace 5")
!7::Komorebic("focus-workspace 6")
!8::Komorebic("focus-workspace 7")
; Move windows across workspaces
!+1::Komorebic("move-to-workspace 0")
!+2::Komorebic("move-to-workspace 1")
!+3::Komorebic("move-to-workspace 2")
!+4::Komorebic("move-to-workspace 3")
!+5::Komorebic("move-to-workspace 4")
!+6::Komorebic("move-to-workspace 5")
!+7::Komorebic("move-to-workspace 6")
!+8::Komorebic("move-to-workspace 7")

View File

@@ -1,5 +1,3 @@
# v0.1.22
In addition to the [changelog](https://github.com/LGUG2Z/komorebi/releases/tag/v0.1.22) of new features and fixes,
please note the following changes from `v0.1.21` to adjust your configuration files accordingly.
@@ -51,8 +49,8 @@ A 1px border is drawn around the window edge. Users may see a gap for a single p
transparent edge - this is the windows themed edge, and is not present for all applications.
```json
{
{
"border_offset": 0,
"border_width": 1
}
```
```

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-client"
version = "0.1.27"
version = "0.1.26-dev.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,4 +1,4 @@
#![warn(clippy::all)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
pub use komorebi::colour::Colour;
@@ -29,7 +29,6 @@ pub use komorebi_core::Layout;
pub use komorebi_core::OperationDirection;
pub use komorebi_core::Rect;
pub use komorebi_core::SocketMessage;
pub use komorebi_core::StackbarLabel;
pub use komorebi_core::StackbarMode;
pub use komorebi_core::WindowKind;

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-core"
version = "0.1.27"
version = "0.1.26-dev.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -604,16 +604,7 @@ impl Arrangement for CustomLayout {
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum Axis {
Horizontal,

View File

@@ -14,7 +14,7 @@ use serde::Serialize;
use crate::Rect;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct CustomLayout(Vec<Column>);
impl Deref for CustomLayout {
@@ -250,7 +250,7 @@ impl CustomLayout {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "column", content = "configuration")]
pub enum Column {
Primary(Option<ColumnWidth>),
@@ -258,18 +258,18 @@ pub enum Column {
Tertiary(ColumnSplit),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnWidth {
WidthPercentage(f32),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnSplit {
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub enum ColumnSplitWithCapacity {
Horizontal(usize),
Vertical(usize),

View File

@@ -90,13 +90,9 @@ impl Direction for DefaultLayout {
idx: usize,
count: usize,
) -> bool {
if count < 2 {
return false;
}
match op_direction {
OperationDirection::Up => match self {
Self::BSP => idx != 0 && idx != 1,
Self::BSP => count > 2 && idx != 0 && idx != 1,
Self::Columns => false,
Self::Rows | Self::HorizontalStack => idx != 0,
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
@@ -104,7 +100,7 @@ impl Direction for DefaultLayout {
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
OperationDirection::Down => match self {
Self::BSP => idx != count - 1 && idx % 2 != 0,
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
Self::Columns => false,
Self::Rows => idx != count - 1,
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,
@@ -113,22 +109,23 @@ impl Direction for DefaultLayout {
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
OperationDirection::Left => match self {
Self::BSP => idx != 0,
Self::BSP => count > 1 && 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::UltrawideVerticalStack => count > 1 && idx != 1,
Self::Grid => !is_grid_edge(op_direction, idx, count),
},
OperationDirection::Right => match self {
Self::BSP => idx % 2 == 0 && idx != count - 1,
Self::BSP => count > 1 && 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 {
0 | 1 => false,
2 => idx != 0,
_ => idx < 2,
},

View File

@@ -7,7 +7,7 @@ use crate::CustomLayout;
use crate::DefaultLayout;
use crate::Direction;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum Layout {
Default(DefaultLayout),
Custom(CustomLayout),

View File

@@ -1,5 +1,5 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::use_self)]
use std::path::Path;
use std::path::PathBuf;
@@ -43,8 +43,6 @@ pub enum SocketMessage {
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
StackAll,
UnstackAll,
ResizeWindowEdge(OperationDirection, Sizing),
ResizeWindowAxis(Axis, Sizing),
UnstackWindow,
@@ -142,8 +140,6 @@ pub enum SocketMessage {
BorderStyle(BorderStyle),
BorderWidth(i32),
BorderOffset(i32),
Transparency(bool),
TransparencyAlpha(u8),
InvisibleBorders(Rect),
StackbarMode(StackbarMode),
StackbarLabel(StackbarLabel),
@@ -168,7 +164,6 @@ pub enum SocketMessage {
State,
GlobalState,
VisibleWindows,
MonitorInformation,
Query(StateQuery),
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
@@ -232,16 +227,7 @@ pub enum BorderStyle {
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum WindowKind {
Single,

View File

@@ -1,14 +0,0 @@
[package]
name = "komorebi-gui"
version = "0.1.27"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
egui_extras = { version = "0.27" }
eframe = "0.27"
komorebi-client = { path = "../komorebi-client" }
serde_json = "1"
random_word = { version = "0.4.3", features = ["en"] }
windows = { workspace = true }

View File

@@ -1,807 +0,0 @@
#![warn(clippy::all)]
use eframe::egui;
use eframe::egui::color_picker::Alpha;
use eframe::egui::Color32;
use eframe::egui::ViewportBuilder;
use komorebi_client::BorderStyle;
use komorebi_client::Colour;
use komorebi_client::DefaultLayout;
use komorebi_client::GlobalState;
use komorebi_client::Layout;
use komorebi_client::Rect;
use komorebi_client::Rgb;
use komorebi_client::RuleDebug;
use komorebi_client::SocketMessage;
use komorebi_client::StackbarLabel;
use komorebi_client::StackbarMode;
use komorebi_client::State;
use komorebi_client::Window;
use komorebi_client::WindowKind;
use std::collections::HashMap;
use std::time::Duration;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
fn main() {
let native_options = eframe::NativeOptions {
viewport: ViewportBuilder::default()
.with_always_on_top()
.with_inner_size([320.0, 500.0]),
follow_system_theme: true,
..Default::default()
};
let _ = eframe::run_native(
"komorebi-gui",
native_options,
Box::new(|cc| Box::new(KomorebiGui::new(cc))),
);
}
struct BorderColours {
single: Color32,
stack: Color32,
monocle: Color32,
unfocused: Color32,
}
struct BorderConfig {
border_enabled: bool,
border_colours: BorderColours,
border_style: BorderStyle,
border_offset: i32,
border_width: i32,
}
struct StackbarConfig {
mode: StackbarMode,
label: StackbarLabel,
height: i32,
width: i32,
focused_text_colour: Color32,
unfocused_text_colour: Color32,
background_colour: Color32,
}
struct MonitorConfig {
size: Rect,
work_area_offset: Rect,
workspaces: Vec<WorkspaceConfig>,
}
impl From<&komorebi_client::Monitor> for MonitorConfig {
fn from(value: &komorebi_client::Monitor) -> Self {
let mut workspaces = vec![];
for ws in value.workspaces() {
workspaces.push(WorkspaceConfig::from(ws));
}
Self {
size: *value.size(),
work_area_offset: value.work_area_offset().unwrap_or_default(),
workspaces,
}
}
}
struct WorkspaceConfig {
name: String,
tile: bool,
layout: DefaultLayout,
container_padding: i32,
workspace_padding: i32,
}
impl From<&komorebi_client::Workspace> for WorkspaceConfig {
fn from(value: &komorebi_client::Workspace) -> Self {
let layout = match value.layout() {
Layout::Default(layout) => *layout,
Layout::Custom(_) => DefaultLayout::BSP,
};
let name = value
.name()
.to_owned()
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
Self {
layout,
name,
tile: *value.tile(),
workspace_padding: value.workspace_padding().unwrap_or(20),
container_padding: value.container_padding().unwrap_or(20),
}
}
}
struct KomorebiGui {
border_config: BorderConfig,
stackbar_config: StackbarConfig,
mouse_follows_focus: bool,
monitors: Vec<MonitorConfig>,
workspace_names: HashMap<usize, Vec<String>>,
debug_hwnd: isize,
debug_windows: Vec<Window>,
debug_rule: Option<RuleDebug>,
}
fn colour32(colour: Option<Colour>) -> Color32 {
match colour {
Some(Colour::Rgb(rgb)) => Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8),
Some(Colour::Hex(hex)) => {
let rgb = Rgb::from(hex);
Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8)
}
None => Color32::from_rgb(0, 0, 0),
}
}
impl KomorebiGui {
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
// Restore app state using cc.storage (requires the "persistence" feature).
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
// for e.g. egui::PaintCallback.
let global_state: GlobalState = serde_json::from_str(
&komorebi_client::send_query(&SocketMessage::GlobalState).unwrap(),
)
.unwrap();
let state: State =
serde_json::from_str(&komorebi_client::send_query(&SocketMessage::State).unwrap())
.unwrap();
let border_colours = BorderColours {
single: colour32(global_state.border_colours.single),
stack: colour32(global_state.border_colours.stack),
monocle: colour32(global_state.border_colours.monocle),
unfocused: colour32(global_state.border_colours.unfocused),
};
let border_config = BorderConfig {
border_enabled: global_state.border_enabled,
border_colours,
border_style: global_state.border_style,
border_offset: global_state.border_offset,
border_width: global_state.border_width,
};
let mut monitors = vec![];
for m in state.monitors.elements() {
monitors.push(MonitorConfig::from(m));
}
let mut workspace_names = HashMap::new();
for (monitor_idx, m) in monitors.iter().enumerate() {
for ws in &m.workspaces {
let names = workspace_names.entry(monitor_idx).or_insert_with(Vec::new);
names.push(ws.name.clone());
}
}
let stackbar_config = StackbarConfig {
mode: global_state.stackbar_mode,
height: global_state.stackbar_height,
width: global_state.stackbar_tab_width,
label: global_state.stackbar_label,
focused_text_colour: colour32(Some(global_state.stackbar_focused_text_colour)),
unfocused_text_colour: colour32(Some(global_state.stackbar_unfocused_text_colour)),
background_colour: colour32(Some(global_state.stackbar_tab_background_colour)),
};
let mut debug_windows = vec![];
unsafe {
EnumWindows(
Some(enum_window),
windows::Win32::Foundation::LPARAM(&mut debug_windows as *mut Vec<Window> as isize),
)
.unwrap();
};
Self {
border_config,
mouse_follows_focus: state.mouse_follows_focus,
monitors,
workspace_names,
debug_hwnd: 0,
debug_windows,
stackbar_config,
debug_rule: None,
}
}
}
extern "system" fn enum_window(
hwnd: windows::Win32::Foundation::HWND,
lparam: windows::Win32::Foundation::LPARAM,
) -> windows::Win32::Foundation::BOOL {
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
let window = Window { hwnd: hwnd.0 };
if window.is_window()
&& !window.is_miminized()
&& window.is_visible()
&& window.title().is_ok()
&& window.exe().is_ok()
{
windows.push(window);
}
true.into()
}
fn json_view_ui(ui: &mut egui::Ui, code: &str) {
let language = "json";
let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);
}
impl eframe::App for KomorebiGui {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ctx.set_pixels_per_point(2.0);
egui::ScrollArea::vertical().show(ui, |ui| {
ui.set_width(ctx.screen_rect().width());
ui.collapsing("Debugging", |ui| {
ui.collapsing("Window Rules", |ui| {
let window = Window {
hwnd: self.debug_hwnd,
};
let label = if let (Ok(title), Ok(exe)) = (window.title(), window.exe()) {
format!("{title} ({exe})")
} else {
String::from("Select a Window")
};
if ui.button("Refresh Windows").clicked() {
let mut debug_windows = vec![];
unsafe {
EnumWindows(
Some(enum_window),
windows::Win32::Foundation::LPARAM(
&mut debug_windows as *mut Vec<Window> as isize,
),
)
.unwrap();
};
self.debug_windows = debug_windows;
}
egui::ComboBox::from_label("Select a Window")
.selected_text(label)
.show_ui(ui, |ui| {
for w in &self.debug_windows {
if ui
.selectable_value(
&mut self.debug_hwnd,
w.hwnd,
format!(
"{} ({})",
w.title().unwrap(),
w.exe().unwrap()
),
)
.changed()
{
let debug_rule: RuleDebug = serde_json::from_str(
&komorebi_client::send_query(
&SocketMessage::DebugWindow(self.debug_hwnd),
)
.unwrap(),
)
.unwrap();
self.debug_rule = Some(debug_rule)
}
}
});
if let Some(debug_rule) = &self.debug_rule {
json_view_ui(ui, &serde_json::to_string_pretty(debug_rule).unwrap())
}
});
});
ui.collapsing("Mouse", |ui| {
if ui
.toggle_value(&mut self.mouse_follows_focus, "Mouse Follows Focus")
.changed()
{
komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
self.mouse_follows_focus,
))
.unwrap();
}
});
ui.collapsing("Border", |ui| {
if ui
.toggle_value(&mut self.border_config.border_enabled, "Border")
.changed()
{
komorebi_client::send_message(&SocketMessage::Border(
self.border_config.border_enabled,
))
.unwrap();
}
ui.collapsing("Colours", |ui| {
ui.collapsing("Single", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.border_config.border_colours.single,
Alpha::Opaque,
) {
komorebi_client::send_message(&SocketMessage::BorderColour(
WindowKind::Single,
self.border_config.border_colours.single.r() as u32,
self.border_config.border_colours.single.g() as u32,
self.border_config.border_colours.single.b() as u32,
))
.unwrap();
}
});
ui.collapsing("Stack", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.border_config.border_colours.stack,
Alpha::Opaque,
) {
komorebi_client::send_message(&SocketMessage::BorderColour(
WindowKind::Stack,
self.border_config.border_colours.stack.r() as u32,
self.border_config.border_colours.stack.g() as u32,
self.border_config.border_colours.stack.b() as u32,
))
.unwrap();
}
});
ui.collapsing("Monocle", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.border_config.border_colours.monocle,
Alpha::Opaque,
) {
komorebi_client::send_message(&SocketMessage::BorderColour(
WindowKind::Monocle,
self.border_config.border_colours.monocle.r() as u32,
self.border_config.border_colours.monocle.g() as u32,
self.border_config.border_colours.monocle.b() as u32,
))
.unwrap();
}
});
ui.collapsing("Unfocused", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.border_config.border_colours.unfocused,
Alpha::Opaque,
) {
komorebi_client::send_message(&SocketMessage::BorderColour(
WindowKind::Unfocused,
self.border_config.border_colours.unfocused.r() as u32,
self.border_config.border_colours.unfocused.g() as u32,
self.border_config.border_colours.unfocused.b() as u32,
))
.unwrap();
}
})
});
ui.collapsing("Style", |ui| {
for option in [
BorderStyle::System,
BorderStyle::Rounded,
BorderStyle::Square,
] {
if ui
.add(egui::SelectableLabel::new(
self.border_config.border_style == option,
option.to_string(),
))
.clicked()
{
self.border_config.border_style = option;
komorebi_client::send_message(&SocketMessage::BorderStyle(
self.border_config.border_style,
))
.unwrap();
std::thread::sleep(Duration::from_secs(1));
komorebi_client::send_message(&SocketMessage::Retile).unwrap();
}
}
});
ui.collapsing("Width", |ui| {
if ui
.add(egui::Slider::new(
&mut self.border_config.border_width,
-50..=50,
))
.changed()
{
komorebi_client::send_message(&SocketMessage::BorderWidth(
self.border_config.border_width,
))
.unwrap();
};
});
ui.collapsing("Offset", |ui| {
if ui
.add(egui::Slider::new(
&mut self.border_config.border_offset,
-50..=50,
))
.changed()
{
komorebi_client::send_message(&SocketMessage::BorderOffset(
self.border_config.border_offset,
))
.unwrap();
};
});
});
ui.collapsing("Stackbar", |ui| {
for option in [
StackbarMode::Never,
StackbarMode::OnStack,
StackbarMode::Always,
] {
if ui
.add(egui::SelectableLabel::new(
self.stackbar_config.mode == option,
option.to_string(),
))
.clicked()
{
self.stackbar_config.mode = option;
komorebi_client::send_message(&SocketMessage::StackbarMode(
self.stackbar_config.mode,
))
.unwrap();
komorebi_client::send_message(&SocketMessage::Retile).unwrap()
}
}
ui.collapsing("Label", |ui| {
for option in [StackbarLabel::Process, StackbarLabel::Title] {
if ui
.add(egui::SelectableLabel::new(
self.stackbar_config.label == option,
option.to_string(),
))
.clicked()
{
self.stackbar_config.label = option;
komorebi_client::send_message(&SocketMessage::StackbarLabel(
self.stackbar_config.label,
))
.unwrap();
}
}
});
ui.collapsing("Colours", |ui| {
ui.collapsing("Focused Text", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.stackbar_config.focused_text_colour,
Alpha::Opaque,
) {
komorebi_client::send_message(
&SocketMessage::StackbarFocusedTextColour(
self.stackbar_config.focused_text_colour.r() as u32,
self.stackbar_config.focused_text_colour.g() as u32,
self.stackbar_config.focused_text_colour.b() as u32,
),
)
.unwrap();
}
});
ui.collapsing("Unfocused Text", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.stackbar_config.unfocused_text_colour,
Alpha::Opaque,
) {
komorebi_client::send_message(
&SocketMessage::StackbarUnfocusedTextColour(
self.stackbar_config.unfocused_text_colour.r() as u32,
self.stackbar_config.unfocused_text_colour.g() as u32,
self.stackbar_config.unfocused_text_colour.b() as u32,
),
)
.unwrap();
}
});
ui.collapsing("Background", |ui| {
if egui::color_picker::color_picker_color32(
ui,
&mut self.stackbar_config.background_colour,
Alpha::Opaque,
) {
komorebi_client::send_message(
&SocketMessage::StackbarBackgroundColour(
self.stackbar_config.background_colour.r() as u32,
self.stackbar_config.background_colour.g() as u32,
self.stackbar_config.background_colour.b() as u32,
),
)
.unwrap();
}
})
});
ui.collapsing("Width", |ui| {
if ui
.add(egui::Slider::new(&mut self.stackbar_config.width, 0..=500))
.drag_stopped()
{
komorebi_client::send_message(&SocketMessage::StackbarTabWidth(
self.stackbar_config.width,
))
.unwrap();
komorebi_client::send_message(&SocketMessage::Retile).unwrap()
};
});
ui.collapsing("Height", |ui| {
if ui
.add(egui::Slider::new(&mut self.stackbar_config.height, 0..=100))
.drag_stopped()
{
komorebi_client::send_message(&SocketMessage::StackbarHeight(
self.stackbar_config.height,
))
.unwrap();
komorebi_client::send_message(&SocketMessage::Retile).unwrap()
};
});
});
for (monitor_idx, monitor) in self.monitors.iter_mut().enumerate() {
ui.collapsing(
format!(
"Monitor {monitor_idx} ({}x{})",
monitor.size.right, monitor.size.bottom
),
|ui| {
ui.collapsing("Work Area Offset", |ui| {
if ui
.add(
egui::Slider::new(
&mut monitor.work_area_offset.left,
0..=500,
)
.text("Left"),
)
.drag_stopped()
{
komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
monitor.work_area_offset,
),
)
.unwrap();
};
if ui
.add(
egui::Slider::new(
&mut monitor.work_area_offset.top,
0..=500,
)
.text("Top"),
)
.drag_stopped()
{
komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
monitor.work_area_offset,
),
)
.unwrap();
};
if ui
.add(
egui::Slider::new(
&mut monitor.work_area_offset.right,
0..=500,
)
.text("Right"),
)
.drag_stopped()
{
komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
monitor.work_area_offset,
),
)
.unwrap();
};
if ui
.add(
egui::Slider::new(
&mut monitor.work_area_offset.bottom,
0..=500,
)
.text("Bottom"),
)
.drag_stopped()
{
komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
monitor.work_area_offset,
),
)
.unwrap();
};
});
ui.collapsing("Workspaces", |ui| {
for (workspace_idx, workspace) in
monitor.workspaces.iter_mut().enumerate()
{
ui.collapsing(
format!("Workspace {workspace_idx} ({})", workspace.name),
|ui| {
if ui.button("Focus").clicked() {
komorebi_client::send_message(
&SocketMessage::MouseFollowsFocus(false),
)
.unwrap();
komorebi_client::send_message(
&SocketMessage::FocusMonitorWorkspaceNumber(
monitor_idx,
workspace_idx,
),
)
.unwrap();
komorebi_client::send_message(
&SocketMessage::MouseFollowsFocus(
self.mouse_follows_focus,
),
)
.unwrap();
}
if ui
.toggle_value(&mut workspace.tile, "Tiling")
.changed()
{
komorebi_client::send_message(
&SocketMessage::WorkspaceTiling(
monitor_idx,
workspace_idx,
workspace.tile,
),
)
.unwrap();
}
ui.collapsing("Name", |ui| {
let monitor_workspaces = self
.workspace_names
.get_mut(&monitor_idx)
.unwrap();
let workspace_name =
&mut monitor_workspaces[workspace_idx];
if ui
.text_edit_singleline(workspace_name)
.lost_focus()
{
workspace.name.clone_from(workspace_name);
komorebi_client::send_message(
&SocketMessage::WorkspaceName(
monitor_idx,
workspace_idx,
workspace.name.clone(),
),
)
.unwrap();
}
});
ui.collapsing("Layout", |ui| {
for option in [
DefaultLayout::BSP,
DefaultLayout::Columns,
DefaultLayout::Rows,
DefaultLayout::VerticalStack,
DefaultLayout::HorizontalStack,
DefaultLayout::UltrawideVerticalStack,
DefaultLayout::Grid,
] {
if ui
.add(egui::SelectableLabel::new(
workspace.layout == option,
option.to_string(),
))
.clicked()
{
workspace.layout = option;
komorebi_client::send_message(
&SocketMessage::WorkspaceLayout(
monitor_idx,
workspace_idx,
workspace.layout,
),
)
.unwrap();
}
}
});
ui.collapsing("Container Padding", |ui| {
if ui
.add(egui::Slider::new(
&mut workspace.container_padding,
0..=100,
))
.drag_stopped()
{
komorebi_client::send_message(
&SocketMessage::ContainerPadding(
monitor_idx,
workspace_idx,
workspace.container_padding,
),
)
.unwrap();
};
});
ui.collapsing("Workspace Padding", |ui| {
if ui
.add(egui::Slider::new(
&mut workspace.workspace_padding,
0..=100,
))
.drag_stopped()
{
komorebi_client::send_message(
&SocketMessage::WorkspacePadding(
monitor_idx,
workspace_idx,
workspace.workspace_padding,
),
)
.unwrap();
};
});
},
);
}
});
},
);
}
});
});
}
}

56
komorebi.sample.ahk Normal file
View File

@@ -0,0 +1,56 @@
#SingleInstance Force
; Load library
#Include komorebic.lib.ahk
; Focus windows
!h::Focus("left")
!j::Focus("down")
!k::Focus("up")
!l::Focus("right")
!+[::CycleFocus("previous")
!+]::CycleFocus("next")
; Move windows
!+h::Move("left")
!+j::Move("down")
!+k::Move("up")
!+l::Move("right")
!+Enter::Promote()
; Stack windows
!Left::Stack("left")
!Right::Stack("right")
!Up::Stack("up")
!Down::Stack("down")
!;::Unstack()
![::CycleStack("previous")
!]::CycleStack("next")
; Resize
!=::ResizeAxis("horizontal", "increase")
!-::ResizeAxis("horizontal", "decrease")
!+=::ResizeAxis("vertical", "increase")
!+-::ResizeAxis("vertical", "decrease")
; Manipulate windows
!t::ToggleFloat()
!+f::ToggleMonocle()
; Window manager options
!+r::Retile()
!p::TogglePause()
; Layouts
!x::FlipLayout("horizontal")
!y::FlipLayout("vertical")
; Workspaces
!1::FocusWorkspace(0)
!2::FocusWorkspace(1)
!3::FocusWorkspace(2)
; Move windows across workspaces
!+1::MoveToWorkspace(0)
!+2::MoveToWorkspace(1)
!+3::MoveToWorkspace(2)

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.27"
version = "0.1.26-dev.0"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -18,7 +18,7 @@ clap = { version = "4", features = ["derive"] }
color-eyre = { workspace = true }
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = { version = "3", features = ["termination"] }
ctrlc = "3"
dirs = { workspace = true }
getset = "0.1"
hex_color = { version = "3", features = ["serde"] }
@@ -28,7 +28,7 @@ miow = "0.6"
nanoid = "0.4"
net2 = "0.2"
os_info = "3.8"
parking_lot = "0.12"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
paste = "1"
regex = "1"
schemars = "0.8"
@@ -42,12 +42,13 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uds_windows = "1"
which = "6"
widestring = "1"
win32-display-data = { workspace = true }
windows = { workspace = true }
windows-implement = { workspace = true }
windows-interface = { workspace = true }
winput = "0.2"
winreg = "0.52"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data" }
[features]
deadlock_detection = ["parking_lot/deadlock_detection"]
deadlock_detection = []

View File

@@ -4,6 +4,7 @@ use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::FOCUSED;
use crate::border_manager::FOCUS_STATE;
use crate::border_manager::MONOCLE;
use crate::border_manager::RECT_STATE;
use crate::border_manager::STACK;
use crate::border_manager::STYLE;
use crate::border_manager::UNFOCUSED;
@@ -26,12 +27,12 @@ use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::BeginPaint;
use windows::Win32::Graphics::Gdi::CreatePen;
use windows::Win32::Graphics::Gdi::DeleteObject;
use windows::Win32::Graphics::Gdi::EndPaint;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::RoundRect;
use windows::Win32::Graphics::Gdi::SelectObject;
use windows::Win32::Graphics::Gdi::ValidateRect;
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME;
use windows::Win32::Graphics::Gdi::PS_SOLID;
@@ -98,19 +99,13 @@ impl Border {
let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), h_module)?;
hwnd_sender.send(hwnd)?;
let mut msg: MSG = MSG::default();
loop {
unsafe {
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
tracing::debug!("border window event processing thread shutdown");
break;
};
TranslateMessage(&msg);
DispatchMessageW(&msg);
let mut message = MSG::default();
unsafe {
while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() {
TranslateMessage(&message);
DispatchMessageW(&message);
std::thread::sleep(Duration::from_millis(10));
}
std::thread::sleep(Duration::from_millis(10))
}
Ok(())
@@ -125,22 +120,25 @@ impl Border {
WindowsApi::close_window(self.hwnd())
}
pub fn update(&self, rect: &Rect, mut should_invalidate: bool) -> color_eyre::Result<()> {
pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> {
// Make adjustments to the border
let mut rect = *rect;
rect.add_margin(BORDER_WIDTH.load(Ordering::SeqCst));
rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst));
// Store the border rect so that it can be used by the callback
{
let mut rects = RECT_STATE.lock();
rects.insert(self.hwnd, rect);
}
// Update the position of the border if required
if !WindowsApi::window_rect(self.hwnd())?.eq(&rect) {
WindowsApi::set_border_pos(self.hwnd(), &rect, HWND((Z_ORDER.load()).into()))?;
should_invalidate = true;
WindowsApi::set_border_pos(self.hwnd(), &rect, HWND((*Z_ORDER.lock()).into()))?;
}
// Invalidate the rect to trigger the callback to update colours etc.
if should_invalidate {
self.invalidate();
}
self.invalidate();
Ok(())
}
@@ -158,68 +156,63 @@ impl Border {
unsafe {
match message {
WM_PAINT => {
let mut ps = PAINTSTRUCT::default();
let hdc = BeginPaint(window, &mut ps);
let rects = RECT_STATE.lock();
// With the rect that we set in Self::update
match WindowsApi::window_rect(window) {
Ok(rect) => {
// Grab the focus kind for this border
let focus_kind = {
FOCUS_STATE
.lock()
.get(&window.0)
.copied()
.unwrap_or(WindowKind::Unfocused)
};
// With the rect that we stored in Self::update
if let Some(rect) = rects.get(&window.0).copied() {
// Grab the focus kind for this border
let focus_kind = {
FOCUS_STATE
.lock()
.get(&window.0)
.copied()
.unwrap_or(WindowKind::Unfocused)
};
// Set up the brush to draw the border
let hpen = CreatePen(
PS_SOLID | PS_INSIDEFRAME,
BORDER_WIDTH.load(Ordering::SeqCst),
COLORREF(match focus_kind {
WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
WindowKind::Stack => STACK.load(Ordering::SeqCst),
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
}),
);
// Set up the brush to draw the border
let mut ps = PAINTSTRUCT::default();
let hdc = BeginPaint(window, &mut ps);
let hpen = CreatePen(
PS_SOLID | PS_INSIDEFRAME,
BORDER_WIDTH.load(Ordering::SeqCst),
COLORREF(match focus_kind {
WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
WindowKind::Stack => STACK.load(Ordering::SeqCst),
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
}),
);
let hbrush = WindowsApi::create_solid_brush(0);
let hbrush = WindowsApi::create_solid_brush(0);
// Draw the border
SelectObject(hdc, hpen);
SelectObject(hdc, hbrush);
// TODO(raggi): this is approximately the correct curvature for
// the top left of a Windows 11 window (DWMWCP_DEFAULT), but
// often the bottom right has a different shape. Furthermore if
// the window was made with DWMWCP_ROUNDSMALL then this is the
// wrong size. In the future we should read the DWM properties
// of windows and attempt to match appropriately.
match STYLE.load() {
BorderStyle::System => {
if *WINDOWS_11 {
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
} else {
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
}
}
BorderStyle::Rounded => {
// Draw the border
SelectObject(hdc, hpen);
SelectObject(hdc, hbrush);
// TODO(raggi): this is approximately the correct curvature for
// the top left of a Windows 11 window (DWMWCP_DEFAULT), but
// often the bottom right has a different shape. Furthermore if
// the window was made with DWMWCP_ROUNDSMALL then this is the
// wrong size. In the future we should read the DWM properties
// of windows and attempt to match appropriately.
match *STYLE.lock() {
BorderStyle::System => {
if *WINDOWS_11 {
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
}
BorderStyle::Square => {
} else {
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
}
}
DeleteObject(hpen);
DeleteObject(hbrush);
}
Err(error) => {
tracing::error!("could not get border rect: {}", error.to_string())
BorderStyle::Rounded => {
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
}
BorderStyle::Square => {
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
}
}
EndPaint(window, &ps);
ValidateRect(window, None);
}
EndPaint(window, &ps);
LRESULT(0)
}
WM_DESTROY => {

View File

@@ -4,7 +4,6 @@ mod border;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_core::BorderStyle;
use lazy_static::lazy_static;
@@ -19,11 +18,13 @@ use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
use std::time::Instant;
use windows::Win32::Foundation::HWND;
use crate::ring::Ring;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::Colour;
use crate::Rect;
use crate::Rgb;
use crate::WindowManager;
use crate::WindowsApi;
@@ -37,8 +38,8 @@ pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true);
lazy_static! {
pub static ref Z_ORDER: AtomicCell<ZOrder> = AtomicCell::new(ZOrder::Bottom);
pub static ref STYLE: AtomicCell<BorderStyle> = AtomicCell::new(BorderStyle::System);
pub static ref Z_ORDER: Arc<Mutex<ZOrder>> = Arc::new(Mutex::new(ZOrder::Bottom));
pub static ref STYLE: Arc<Mutex<BorderStyle>> = Arc::new(Mutex::new(BorderStyle::System));
pub static ref FOCUSED: AtomicU32 =
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
pub static ref UNFOCUSED: AtomicU32 =
@@ -51,6 +52,7 @@ lazy_static! {
lazy_static! {
static ref BORDERS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
static ref RECT_STATE: Mutex<HashMap<isize, Rect>> = Mutex::new(HashMap::new());
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
}
@@ -59,23 +61,17 @@ pub struct Notification;
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
CHANNEL.get_or_init(crossbeam_channel::unbounded)
}
fn event_tx() -> Sender<Notification> {
pub fn event_tx() -> Sender<Notification> {
channel().0.clone()
}
fn event_rx() -> Receiver<Notification> {
pub fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn send_notification() {
if event_tx().try_send(Notification).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn destroy_all_borders() -> color_eyre::Result<()> {
let mut borders = BORDER_STATE.lock();
tracing::info!(
@@ -88,6 +84,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
}
borders.clear();
RECT_STATE.lock().clear();
BORDERS_MONITORS.lock().clear();
FOCUS_STATE.lock().clear();
@@ -126,57 +123,28 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
tracing::info!("listening");
let receiver = event_rx();
let mut instant: Option<Instant> = None;
event_tx().send(Notification)?;
let mut previous_snapshot = Ring::default();
let mut previous_pending_move_op = None;
let mut previous_is_paused = false;
'receiver: for _ in receiver {
// Check the wm state every time we receive a notification
let state = wm.lock();
let is_paused = state.is_paused;
let focused_monitor_idx = state.focused_monitor_idx();
let monitors = state.monitors.clone();
let pending_move_op = state.pending_move_op;
drop(state);
let mut should_process_notification = true;
if monitors == previous_snapshot
// handle the window dragging edge case
&& pending_move_op == previous_pending_move_op
{
should_process_notification = false;
if let Some(instant) = instant {
if instant.elapsed().lt(&Duration::from_millis(50)) {
continue 'receiver;
}
}
// handle the pause edge case
if is_paused && !previous_is_paused {
should_process_notification = true;
}
// handle the unpause edge case
if previous_is_paused && !is_paused {
should_process_notification = true;
}
// handle the retile edge case
if !should_process_notification && BORDER_STATE.lock().is_empty() {
should_process_notification = true;
}
if !should_process_notification {
tracing::trace!("monitor state matches latest snapshot, skipping notification");
continue 'receiver;
}
instant = Some(Instant::now());
let mut borders = BORDER_STATE.lock();
let mut borders_monitors = BORDERS_MONITORS.lock();
// Check the wm state every time we receive a notification
let state = wm.lock();
// If borders are disabled
if !BORDER_ENABLED.load_consume()
// Or if the wm is paused
|| is_paused
|| state.is_paused
// Or if we are handling an alt-tab across workspaces
|| ALT_TAB_HWND.load().is_some()
{
@@ -186,12 +154,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
borders.clear();
previous_is_paused = is_paused;
continue 'receiver;
}
'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {
let focused_monitor_idx = state.focused_monitor_idx();
'monitors: for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
// Only operate on the focused workspace of each monitor
if let Some(ws) = m.focused_workspace() {
// Workspaces with tiling disabled don't have borders
@@ -208,7 +176,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
borders.remove(id);
}
continue 'monitors;
continue 'receiver;
}
// Handle the monocle container separately
@@ -219,7 +187,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Ok(border) = Border::create(monocle.id()) {
entry.insert(border)
} else {
continue 'monitors;
continue 'receiver;
}
}
};
@@ -242,7 +210,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
monocle.focused_window().copied().unwrap_or_default().hwnd(),
)?;
border.update(&rect, true)?;
border.update(&rect)?;
let border_hwnd = border.hwnd;
let mut to_remove = vec![];
@@ -258,7 +226,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
for id in &to_remove {
borders.remove(id);
}
continue 'monitors;
}
@@ -305,9 +272,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
for (idx, c) in ws.containers().iter().enumerate() {
// Update border when moving or resizing with mouse
if pending_move_op.is_some() && idx == ws.focused_container_idx() {
let restore_z_order = Z_ORDER.load();
Z_ORDER.store(ZOrder::TopMost);
if state.pending_move_op.is_some() && idx == ws.focused_container_idx() {
let restore_z_order = *Z_ORDER.lock();
*Z_ORDER.lock() = ZOrder::TopMost;
let mut rect = WindowsApi::window_rect(
c.focused_window().copied().unwrap_or_default().hwnd(),
@@ -320,7 +287,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Ok(border) = Border::create(c.id()) {
entry.insert(border)
} else {
continue 'monitors;
continue 'receiver;
}
}
};
@@ -331,13 +298,13 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if rect != new_rect {
rect = new_rect;
border.update(&rect, true)?;
border.update(&rect)?;
}
}
Z_ORDER.store(restore_z_order);
*Z_ORDER.lock() = restore_z_order;
continue 'monitors;
continue 'receiver;
}
// Get the border entry for this container from the map or create one
@@ -347,49 +314,38 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Ok(border) = Border::create(c.id()) {
entry.insert(border)
} else {
continue 'monitors;
continue 'receiver;
}
}
};
borders_monitors.insert(c.id().clone(), monitor_idx);
#[allow(unused_assignments)]
let mut last_focus_state = None;
let new_focus_state = if idx != ws.focused_container_idx()
|| monitor_idx != focused_monitor_idx
{
WindowKind::Unfocused
} else if c.windows().len() > 1 {
WindowKind::Stack
} else {
WindowKind::Single
};
// Update the focused state for all containers on this workspace
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
focus_state.insert(
border.hwnd,
if idx != ws.focused_container_idx()
|| monitor_idx != focused_monitor_idx
{
WindowKind::Unfocused
} else if c.windows().len() > 1 {
WindowKind::Stack
} else {
WindowKind::Single
},
);
}
let rect = WindowsApi::window_rect(
c.focused_window().copied().unwrap_or_default().hwnd(),
)?;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => last_focus_state != new_focus_state,
};
border.update(&rect, should_invalidate)?;
border.update(&rect)?;
}
}
}
previous_snapshot = monitors;
previous_pending_move_op = pending_move_op;
previous_is_paused = is_paused;
}
Ok(())

View File

@@ -104,7 +104,11 @@ impl Container {
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
let window = self.windows_mut().remove(idx);
self.focus_window(idx.saturating_sub(1));
if idx != 0 {
self.focus_window(idx - 1);
};
window
}
@@ -115,7 +119,7 @@ impl Container {
pub fn add_window(&mut self, window: Window) {
self.windows_mut().push_back(window);
self.focus_window(self.windows().len().saturating_sub(1));
self.focus_window(self.windows().len() - 1);
}
#[tracing::instrument(skip(self))]

View File

@@ -1,5 +1,3 @@
#![warn(clippy::all)]
pub mod border_manager;
pub mod com;
#[macro_use]
@@ -11,12 +9,10 @@ pub mod monitor_reconciliator;
pub mod process_command;
pub mod process_event;
pub mod process_movement;
pub mod reaper;
pub mod set_window_position;
pub mod stackbar_manager;
pub mod static_config;
pub mod styles;
pub mod transparency_manager;
pub mod window;
pub mod window_manager;
pub mod window_manager_event;

View File

@@ -1,10 +1,9 @@
#![warn(clippy::all)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::redundant_pub_crate,
clippy::significant_drop_tightening,
clippy::significant_drop_in_scrutinee,
clippy::doc_markdown
clippy::significant_drop_in_scrutinee
)]
use std::path::PathBuf;
@@ -31,10 +30,8 @@ use komorebi::process_command::listen_for_commands;
use komorebi::process_command::listen_for_commands_tcp;
use komorebi::process_event::listen_for_events;
use komorebi::process_movement::listen_for_movements;
use komorebi::reaper;
use komorebi::stackbar_manager;
use komorebi::static_config::StaticConfig;
use komorebi::transparency_manager;
use komorebi::window_manager::WindowManager;
use komorebi::windows_api::WindowsApi;
use komorebi::winevent_listener;
@@ -259,10 +256,8 @@ fn main() -> Result<()> {
border_manager::listen_for_notifications(wm.clone());
stackbar_manager::listen_for_notifications(wm.clone());
transparency_manager::listen_for_notifications(wm.clone());
workspace_reconciliator::listen_for_notifications(wm.clone());
monitor_reconciliator::listen_for_notifications(wm.clone())?;
reaper::watch_for_orphans(wm.clone());
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {

View File

@@ -19,16 +19,7 @@ use crate::ring::Ring;
use crate::workspace::Workspace;
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
)]
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
@@ -127,7 +118,7 @@ impl Monitor {
if idx == 0 {
self.workspaces_mut().push_back(Workspace::default());
} else {
self.focus_workspace(idx.saturating_sub(1)).ok()?;
self.focus_workspace(idx - 1).ok()?;
};
None

View File

@@ -1,5 +1,4 @@
use std::sync::mpsc;
use std::time::Duration;
use windows::core::PCWSTR;
use windows::Win32::Foundation::HWND;
@@ -69,19 +68,13 @@ impl Hidden {
let hwnd = WindowsApi::create_hidden_window(PCWSTR(name.as_ptr()), h_module)?;
hwnd_sender.send(hwnd)?;
let mut msg: MSG = MSG::default();
let mut message = MSG::default();
loop {
unsafe {
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
tracing::debug!("hidden window event processing thread shutdown");
break;
};
TranslateMessage(&msg);
DispatchMessageW(&msg);
unsafe {
while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() {
TranslateMessage(&message);
DispatchMessageW(&message);
}
std::thread::sleep(Duration::from_millis(10))
}
Ok(())
@@ -110,7 +103,7 @@ impl Hidden {
tracing::debug!(
"WM_POWERBROADCAST event received - resume from suspend"
);
monitor_reconciliator::send_notification(
let _ = monitor_reconciliator::event_tx().send(
monitor_reconciliator::Notification::ResumingFromSuspendedState,
);
LRESULT(0)
@@ -120,9 +113,8 @@ impl Hidden {
tracing::debug!(
"WM_POWERBROADCAST event received - entering suspended state"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::Notification::EnteringSuspendedState,
);
let _ = monitor_reconciliator::event_tx()
.send(monitor_reconciliator::Notification::EnteringSuspendedState);
LRESULT(0)
}
_ => LRESULT(0),
@@ -133,16 +125,14 @@ impl Hidden {
WTS_SESSION_LOCK => {
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked");
monitor_reconciliator::send_notification(
monitor_reconciliator::Notification::SessionLocked,
);
let _ = monitor_reconciliator::event_tx()
.send(monitor_reconciliator::Notification::SessionLocked);
}
WTS_SESSION_UNLOCK => {
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked");
monitor_reconciliator::send_notification(
monitor_reconciliator::Notification::SessionUnlocked,
);
let _ = monitor_reconciliator::event_tx()
.send(monitor_reconciliator::Notification::SessionUnlocked);
}
_ => {}
}
@@ -161,9 +151,8 @@ impl Hidden {
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed", wparam.0
);
monitor_reconciliator::send_notification(
monitor_reconciliator::Notification::ResolutionScalingChanged,
);
let _ = monitor_reconciliator::event_tx()
.send(monitor_reconciliator::Notification::ResolutionScalingChanged);
LRESULT(0)
}
// Unfortunately this is the event sent with ButteryTaskbar which I use a lot
@@ -175,9 +164,8 @@ impl Hidden {
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::Notification::WorkAreaChanged,
);
let _ = monitor_reconciliator::event_tx()
.send(monitor_reconciliator::Notification::WorkAreaChanged);
}
LRESULT(0)
}
@@ -189,9 +177,8 @@ impl Hidden {
tracing::debug!(
"WM_DEVICECHANGE event received with DBT_DEVNODES_CHANGED - display added or removed"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::Notification::DisplayConnectionChange,
);
let _ = monitor_reconciliator::event_tx()
.send(monitor_reconciliator::Notification::DisplayConnectionChange);
}
LRESULT(0)

View File

@@ -40,20 +40,14 @@ pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
}
fn event_tx() -> Sender<Notification> {
pub fn event_tx() -> Sender<Notification> {
channel().0.clone()
}
fn event_rx() -> Receiver<Notification> {
pub fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn send_notification(notification: Notification) {
if event_tx().try_send(notification).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn insert_in_monitor_cache(device_id: &str, config: MonitorConfig) {
let mut monitor_cache = MONITOR_CACHE
.get_or_init(|| Mutex::new(HashMap::new()))
@@ -63,7 +57,7 @@ pub fn insert_in_monitor_cache(device_id: &str, config: MonitorConfig) {
}
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
Ok(win32_display_data::connected_displays_all()
Ok(win32_display_data::connected_displays()
.flatten()
.map(|display| {
let path = display.device_path;
@@ -166,7 +160,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if should_update {
tracing::info!("updated work area for {}", monitor.device_id());
monitor.update_focused_workspace(offset)?;
border_manager::send_notification();
border_manager::event_tx().send(border_manager::Notification)?;
} else {
tracing::debug!(
"work areas match, reconciliation not required for {}",
@@ -213,7 +207,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
);
monitor.update_focused_workspace(offset)?;
border_manager::send_notification();
border_manager::event_tx().send(border_manager::Notification)?;
} else {
tracing::debug!(
"resolutions match, reconciliation not required for {}",
@@ -400,7 +394,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Second retile to fix DPI/resolution related jank
wm.retile_all(true)?;
// Border updates to fix DPI/resolution related jank
border_manager::send_notification();
border_manager::event_tx().send(border_manager::Notification)?;
}
}
}

View File

@@ -45,7 +45,6 @@ use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::stackbar_manager;
use crate::static_config::StaticConfig;
use crate::transparency_manager;
use crate::window::RuleDebug;
use crate::window::Window;
use crate::window_manager;
@@ -81,33 +80,26 @@ use stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
#[tracing::instrument]
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
let wm = wm.clone();
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
let _ = std::thread::spawn(move || {
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => match read_commands_uds(&wm, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
},
Err(error) => {
tracing::error!("{}", error);
break;
}
std::thread::spawn(move || {
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => match read_commands_uds(&wm, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
},
Err(error) => {
tracing::error!("{}", error);
break;
}
}
})
.join();
tracing::error!("restarting failed thread");
}
});
}
@@ -213,8 +205,6 @@ impl WindowManager {
}
SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?,
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
SocketMessage::StackAll => self.stack_all()?,
SocketMessage::UnstackAll => self.unstack_all()?,
SocketMessage::CycleStack(direction) => {
self.cycle_container_window_in_direction(direction)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
@@ -225,12 +215,8 @@ impl WindowManager {
WindowsApi::center_cursor_in_rect(&focused_window_rect)?;
WindowsApi::left_click();
}
SocketMessage::Close => {
Window::from(WindowsApi::foreground_window()?).close()?;
}
SocketMessage::Minimize => {
Window::from(WindowsApi::foreground_window()?).minimize();
}
SocketMessage::Close => self.focused_window()?.close()?,
SocketMessage::Minimize => self.focused_window()?.minimize(),
SocketMessage::ToggleFloat => self.toggle_float()?,
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
@@ -798,22 +784,15 @@ impl WindowManager {
}
}
let visible_windows_state = serde_json::to_string_pretty(&monitor_visible_windows)
.unwrap_or_else(|error| error.to_string());
let visible_windows_state =
match serde_json::to_string_pretty(&monitor_visible_windows) {
Ok(state) => state,
Err(error) => error.to_string(),
};
reply.write_all(visible_windows_state.as_bytes())?;
}
SocketMessage::MonitorInformation => {
let mut monitors = HashMap::new();
for monitor in self.monitors() {
monitors.insert(monitor.device_id(), monitor.size());
}
let monitors_state = serde_json::to_string_pretty(&monitors)
.unwrap_or_else(|error| error.to_string());
reply.write_all(monitors_state.as_bytes())?;
}
SocketMessage::Query(query) => {
let response = match query {
StateQuery::FocusedMonitorIndex => self.focused_monitor_idx(),
@@ -1256,7 +1235,8 @@ impl WindowManager {
}
},
SocketMessage::BorderStyle(style) => {
STYLE.store(style);
let mut border_style = STYLE.lock();
*border_style = style;
}
SocketMessage::BorderWidth(width) => {
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
@@ -1264,12 +1244,6 @@ impl WindowManager {
SocketMessage::BorderOffset(offset) => {
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
}
SocketMessage::Transparency(enable) => {
transparency_manager::TRANSPARENCY_ENABLED.store(enable, Ordering::SeqCst);
}
SocketMessage::TransparencyAlpha(alpha) => {
transparency_manager::TRANSPARENCY_ALPHA.store(alpha, Ordering::SeqCst);
}
SocketMessage::StackbarMode(mode) => {
STACKBAR_MODE.store(mode);
}
@@ -1342,7 +1316,7 @@ impl WindowManager {
self.update_focused_workspace(false, false)?;
}
SocketMessage::DebugWindow(hwnd) => {
let window = Window::from(hwnd);
let window = Window { hwnd };
let mut rule_debug = RuleDebug::default();
let _ = window.should_manage(None, &mut rule_debug);
let schema = serde_json::to_string_pretty(&rule_debug)?;
@@ -1360,9 +1334,8 @@ impl WindowManager {
};
notify_subscribers(&serde_json::to_string(&notification)?)?;
border_manager::send_notification();
transparency_manager::send_notification();
stackbar_manager::send_notification();
border_manager::event_tx().send(border_manager::Notification)?;
stackbar_manager::event_tx().send(stackbar_manager::Notification)?;
tracing::info!("processed");
Ok(())

View File

@@ -6,7 +6,6 @@ use std::time::Instant;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use komorebi_core::OperationDirection;
@@ -20,13 +19,11 @@ use crate::border_manager::BORDER_WIDTH;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::stackbar_manager;
use crate::transparency_manager;
use crate::window::should_act;
use crate::window::RuleDebug;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent;
use crate::workspace_reconciliator;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
@@ -45,8 +42,7 @@ pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
tracing::info!("listening");
loop {
if let Ok(event) = receiver.recv() {
let mut guard = wm.lock();
match guard.process_event(event) {
match wm.lock().process_event(event) {
Ok(()) => {}
Err(error) => {
if cfg!(debug_assertions) {
@@ -77,32 +73,7 @@ impl WindowManager {
// All event handlers below this point should only be processed if the event is
// related to a window that should be managed by the WindowManager.
if !should_manage {
let mut transparency_override = false;
if transparency_manager::TRANSPARENCY_ENABLED.load_consume() {
for m in self.monitors() {
for w in m.workspaces() {
let event_hwnd = event.window().hwnd;
let visible_hwnds = w
.visible_windows()
.iter()
.flatten()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
if w.contains_managed_window(event_hwnd)
&& !visible_hwnds.contains(&event_hwnd)
{
transparency_override = true;
}
}
}
}
if !transparency_override {
return Ok(());
}
return Ok(());
}
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
@@ -149,11 +120,37 @@ impl WindowManager {
_ => {}
}
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
let offset = self.work_area_offset;
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
let work_area = *monitor.work_area_size();
let window_based_work_area_offset = (
monitor.window_based_work_area_offset_limit(),
monitor.window_based_work_area_offset(),
);
let offset = if monitor.work_area_offset().is_some() {
monitor.work_area_offset()
} else {
offset
};
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
if let WindowManagerEvent::FocusChange(_, window) = event {
let _ = workspace.focus_changed(window.hwnd);
}
let reaped_orphans = workspace.reap_orphans()?;
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
workspace.update(&work_area, offset, window_based_work_area_offset)?;
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,
reaped_orphans.1,
i,
j
);
}
}
}
@@ -299,7 +296,13 @@ impl WindowManager {
}
}
workspace_reconciliator::send_notification(i, j);
workspace_reconciliator::event_tx().send(
workspace_reconciliator::Notification {
monitor_idx: i,
workspace_idx: j,
},
)?;
needs_reconciliation = true;
}
}
@@ -331,10 +334,8 @@ impl WindowManager {
if proceed {
let behaviour = self.window_container_behaviour;
let workspace = self.focused_workspace_mut()?;
let workspace_contains_window = workspace.contains_window(window.hwnd);
let monocle_container = workspace.monocle_container().clone();
if !workspace_contains_window && !needs_reconciliation {
if !workspace.contains_window(window.hwnd) && !needs_reconciliation {
match behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
@@ -349,21 +350,6 @@ impl WindowManager {
}
}
}
if workspace_contains_window {
let mut monocle_window_event = false;
if let Some(ref monocle) = monocle_container {
if let Some(monocle_window) = monocle.focused_window() {
if monocle_window.hwnd == window.hwnd {
monocle_window_event = true;
}
}
}
if !monocle_window_event && monocle_container.is_some() {
window.hide();
}
}
}
}
WindowManagerEvent::MoveResizeStart(_, window) => {
@@ -593,9 +579,10 @@ impl WindowManager {
}
}
}
WindowManagerEvent::MouseCapture(..)
| WindowManagerEvent::Cloak(..)
| WindowManagerEvent::TitleUpdate(..) => {}
WindowManagerEvent::ForceUpdate(_) => {
self.update_focused_workspace(false, true)?;
}
WindowManagerEvent::MouseCapture(..) | WindowManagerEvent::Cloak(..) => {}
};
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
@@ -630,20 +617,10 @@ impl WindowManager {
};
notify_subscribers(&serde_json::to_string(&notification)?)?;
border_manager::send_notification();
transparency_manager::send_notification();
stackbar_manager::send_notification();
// Too many spammy OBJECT_NAMECHANGE events from JetBrains IDEs
if !matches!(
event,
WindowManagerEvent::Show(WinEvent::ObjectNameChange, _)
) {
tracing::info!("processed: {}", event.window().to_string());
} else {
tracing::trace!("processed: {}", event.window().to_string());
}
border_manager::event_tx().send(border_manager::Notification)?;
stackbar_manager::event_tx().send(stackbar_manager::Notification)?;
tracing::info!("processed: {}", event.window().to_string());
Ok(())
}
}

View File

@@ -1,66 +0,0 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::border_manager;
use crate::WindowManager;
use parking_lot::Mutex;
use std::sync::Arc;
use std::time::Duration;
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match find_orphans(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
}
}
});
}
pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("watching");
let arc = wm.clone();
loop {
std::thread::sleep(Duration::from_secs(1));
let mut wm = arc.lock();
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let work_area = *monitor.work_area_size();
let window_based_work_area_offset = (
monitor.window_based_work_area_offset_limit(),
monitor.window_based_work_area_offset(),
);
let offset = if monitor.work_area_offset().is_some() {
monitor.work_area_offset()
} else {
offset
};
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, offset, window_based_work_area_offset)?;
border_manager::send_notification();
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,
reaped_orphans.1,
i,
j
);
}
}
}
}
}

View File

@@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Ring<T> {
elements: VecDeque<T>,
focused: usize,

View File

@@ -40,23 +40,17 @@ pub struct Notification;
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
CHANNEL.get_or_init(crossbeam_channel::unbounded)
}
fn event_tx() -> Sender<Notification> {
pub fn event_tx() -> Sender<Notification> {
channel().0.clone()
}
fn event_rx() -> Receiver<Notification> {
pub fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn send_notification() {
if event_tx().try_send(Notification).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn should_have_stackbar(window_count: usize) -> bool {
match STACKBAR_MODE.load() {
StackbarMode::Always => true,
@@ -142,27 +136,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
continue 'receiver;
}
// Destroy any stackbars not associated with the focused workspace
let container_ids = ws
.containers()
.iter()
.map(|c| c.id().clone())
.collect::<Vec<_>>();
let mut to_remove = vec![];
for (id, stackbar) in stackbars.iter() {
if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& !container_ids.contains(id)
{
stackbar.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
stackbars.remove(id);
}
let container_padding = ws
.container_padding()
.unwrap_or_else(|| DEFAULT_CONTAINER_PADDING.load_consume());

View File

@@ -27,7 +27,6 @@ use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::CreateFontIndirectW;
use windows::Win32::Graphics::Gdi::CreatePen;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::DeleteObject;
use windows::Win32::Graphics::Gdi::DrawTextW;
use windows::Win32::Graphics::Gdi::GetDC;
use windows::Win32::Graphics::Gdi::Rectangle;
@@ -120,17 +119,11 @@ impl Stackbar {
SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?;
hwnd_sender.send(hwnd)?;
let mut msg: MSG = MSG::default();
loop {
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
tracing::debug!("stackbar window event processing thread shutdown");
break;
};
let mut msg = MSG::default();
while GetMessageW(&mut msg, hwnd, 0, 0).into() {
TranslateMessage(&msg);
DispatchMessageW(&msg);
std::thread::sleep(Duration::from_millis(10))
std::thread::sleep(Duration::from_millis(10));
}
}
@@ -204,7 +197,7 @@ impl Stackbar {
bottom: height,
};
match STYLE.load() {
match *STYLE.lock() {
BorderStyle::System => {
if *WINDOWS_11 {
RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, 20, 20);
@@ -242,9 +235,6 @@ impl Stackbar {
}
ReleaseDC(self.hwnd(), hdc);
DeleteObject(hpen);
DeleteObject(hbrush);
DeleteObject(hfont);
}
Ok(())

View File

@@ -14,7 +14,6 @@ use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::transparency_manager;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
@@ -230,7 +229,7 @@ impl From<&Monitor> for MonitorConfig {
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.json` static configuration file reference for `v0.1.27`
/// The `komorebi.json` static configuration file reference for `v0.1.25`
pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required
#[serde(skip_serializing_if = "Option::is_none")]
@@ -279,12 +278,6 @@ pub struct StaticConfig {
/// Active window border z-order (default: System)
#[serde(skip_serializing_if = "Option::is_none")]
pub border_z_order: Option<ZOrder>,
/// Add transparency to unfocused windows (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub transparency: Option<bool>,
/// Alpha value for unfocused window transparency [[0-255]] (default: 200)
#[serde(skip_serializing_if = "Option::is_none")]
pub transparency_alpha: Option<u8>,
/// Global default workspace padding (default: 10)
#[serde(skip_serializing_if = "Option::is_none")]
pub default_workspace_padding: Option<i32>,
@@ -329,48 +322,6 @@ pub struct StaticConfig {
pub stackbar: Option<StackbarConfig>,
}
impl StaticConfig {
pub fn aliases(raw: &str) {
let mut map = HashMap::new();
map.insert("border", ["active_window_border"]);
map.insert("border_width", ["active_window_border_width"]);
map.insert("border_offset", ["active_window_border_offset"]);
map.insert("border_colours", ["active_window_border_colours"]);
map.insert("border_style", ["active_window_border_style"]);
let mut display = false;
for aliases in map.values() {
for a in aliases {
if raw.contains(a) {
display = true;
}
}
}
if display {
println!("\nYour configuration file contains some options that have been renamed or deprecated:\n");
for (canonical, aliases) in map {
for alias in aliases {
if raw.contains(alias) {
println!(r#""{alias}" is now "{canonical}""#);
}
}
}
}
}
pub fn deprecated(raw: &str) {
let deprecated = ["invisible_borders"];
for option in deprecated {
if raw.contains(option) {
println!(r#""{option}" is deprecated and can be removed"#);
}
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct TabsConfig {
/// Width of a stackbar tab
@@ -475,14 +426,8 @@ impl From<&WindowManager> for StaticConfig {
border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)),
border: Option::from(border_manager::BORDER_ENABLED.load(Ordering::SeqCst)),
border_colours,
transparency: Option::from(
transparency_manager::TRANSPARENCY_ENABLED.load(Ordering::SeqCst),
),
transparency_alpha: Option::from(
transparency_manager::TRANSPARENCY_ALPHA.load(Ordering::SeqCst),
),
border_style: Option::from(STYLE.load()),
border_z_order: Option::from(Z_ORDER.load()),
border_style: Option::from(*STYLE.lock()),
border_z_order: Option::from(*Z_ORDER.lock()),
default_workspace_padding: Option::from(
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
),
@@ -510,12 +455,12 @@ impl StaticConfig {
fn apply_globals(&mut self) -> Result<()> {
if let Some(monitor_index_preferences) = &self.monitor_index_preferences {
let mut preferences = MONITOR_INDEX_PREFERENCES.lock();
preferences.clone_from(monitor_index_preferences);
*preferences = monitor_index_preferences.clone();
}
if let Some(display_index_preferences) = &self.display_index_preferences {
let mut preferences = DISPLAY_INDEX_PREFERENCES.lock();
preferences.clone_from(display_index_preferences);
*preferences = display_index_preferences.clone();
}
if let Some(behaviour) = self.window_hiding_behaviour {
@@ -556,12 +501,8 @@ impl StaticConfig {
}
}
STYLE.store(self.border_style.unwrap_or_default());
transparency_manager::TRANSPARENCY_ENABLED
.store(self.transparency.unwrap_or(false), Ordering::SeqCst);
transparency_manager::TRANSPARENCY_ALPHA
.store(self.transparency_alpha.unwrap_or(200), Ordering::SeqCst);
let border_style = self.border_style.unwrap_or_default();
*STYLE.lock() = border_style;
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();

View File

@@ -1,179 +0,0 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU8;
use std::sync::Arc;
use std::sync::OnceLock;
use windows::Win32::Foundation::HWND;
use crate::Window;
use crate::WindowManager;
use crate::WindowsApi;
pub static TRANSPARENCY_ENABLED: AtomicBool = AtomicBool::new(false);
pub static TRANSPARENCY_ALPHA: AtomicU8 = AtomicU8::new(200);
static KNOWN_HWNDS: OnceLock<Mutex<Vec<isize>>> = OnceLock::new();
pub struct Notification;
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
pub fn known_hwnds() -> Vec<isize> {
let known = KNOWN_HWNDS.get_or_init(|| Mutex::new(Vec::new())).lock();
known.iter().copied().collect()
}
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
}
fn event_tx() -> Sender<Notification> {
channel().0.clone()
}
fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn send_notification() {
if event_tx().try_send(Notification).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
}
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("listening");
let receiver = event_rx();
event_tx().send(Notification)?;
'receiver: for _ in receiver {
let known_hwnds = KNOWN_HWNDS.get_or_init(|| Mutex::new(Vec::new()));
if !TRANSPARENCY_ENABLED.load_consume() {
for hwnd in known_hwnds.lock().iter() {
if let Err(error) = Window::from(*hwnd).opaque() {
tracing::error!("failed to make window {hwnd} opaque: {error}")
}
}
continue 'receiver;
}
known_hwnds.lock().clear();
// Check the wm state every time we receive a notification
let state = wm.lock();
let focused_monitor_idx = state.focused_monitor_idx();
'monitors: for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
let focused_workspace_idx = m.focused_workspace_idx();
'workspaces: for (workspace_idx, ws) in m.workspaces().iter().enumerate() {
// Only operate on the focused workspace of each monitor
// Workspaces with tiling disabled don't have transparent windows
if !ws.tile() || workspace_idx != focused_workspace_idx {
for window in ws.visible_windows().iter().flatten() {
if let Err(error) = window.opaque() {
let hwnd = window.hwnd;
tracing::error!("failed to make window {hwnd} opaque: {error}")
}
}
continue 'workspaces;
}
// Monocle container is never transparent
if let Some(monocle) = ws.monocle_container() {
if let Some(window) = monocle.focused_window() {
if let Err(error) = window.opaque() {
let hwnd = window.hwnd;
tracing::error!("failed to make monocle window {hwnd} opaque: {error}")
}
}
continue 'monitors;
}
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();
let is_maximized = WindowsApi::is_zoomed(HWND(foreground_hwnd));
if is_maximized {
if let Err(error) = Window::from(foreground_hwnd).opaque() {
let hwnd = foreground_hwnd;
tracing::error!("failed to make maximized window {hwnd} opaque: {error}")
}
continue 'monitors;
}
for (idx, c) in ws.containers().iter().enumerate() {
// Update the transparency for all containers on this workspace
// If the window is not focused on the current workspace, or isn't on the focused monitor
// make it transparent
#[allow(clippy::collapsible_else_if)]
if idx != ws.focused_container_idx() || monitor_idx != focused_monitor_idx {
let focused_window_idx = c.focused_window_idx();
for (window_idx, window) in c.windows().iter().enumerate() {
if window_idx == focused_window_idx {
match window.transparent() {
Err(error) => {
let hwnd = foreground_hwnd;
tracing::error!(
"failed to make unfocused window {hwnd} transparent: {error}"
)
}
Ok(..) => {
known_hwnds.lock().push(window.hwnd);
}
}
} else {
// just in case, this is useful when people are clicking around
// on unfocused stackbar tabs
known_hwnds.lock().push(window.hwnd);
}
}
// Otherwise, make it opaque
} else {
let focused_window_idx = c.focused_window_idx();
for (window_idx, window) in c.windows().iter().enumerate() {
if window_idx != focused_window_idx {
known_hwnds.lock().push(window.hwnd);
} else {
if let Err(error) =
c.focused_window().copied().unwrap_or_default().opaque()
{
let hwnd = foreground_hwnd;
tracing::error!(
"failed to make focused window {hwnd} opaque: {error}"
)
}
}
}
};
}
}
}
}
Ok(())
}

View File

@@ -7,8 +7,8 @@ use std::fmt::Write as _;
use std::time::Duration;
use color_eyre::eyre;
use color_eyre::eyre::anyhow;
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;
@@ -26,7 +26,6 @@ use komorebi_core::Rect;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::FLOAT_IDENTIFIERS;
@@ -39,23 +38,11 @@ use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema)]
pub struct Window {
pub hwnd: isize,
}
impl From<isize> for Window {
fn from(value: isize) -> Self {
Self { hwnd: value }
}
}
impl From<HWND> for Window {
fn from(value: HWND) -> Self {
Self { hwnd: value.0 }
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct WindowDetails {
@@ -263,10 +250,7 @@ impl Window {
let mut ex_style = self.ex_style()?;
ex_style.insert(ExtendedWindowStyle::LAYERED);
self.update_ex_style(&ex_style)?;
WindowsApi::set_transparent(
self.hwnd(),
transparency_manager::TRANSPARENCY_ALPHA.load_consume(),
)
WindowsApi::set_transparent(self.hwnd())
}
pub fn opaque(self) -> Result<()> {
@@ -286,12 +270,12 @@ impl Window {
pub fn style(self) -> Result<WindowStyle> {
let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd())?)?;
Ok(WindowStyle::from_bits_truncate(bits))
WindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn ex_style(self) -> Result<ExtendedWindowStyle> {
let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd())?)?;
Ok(ExtendedWindowStyle::from_bits_truncate(bits))
ExtendedWindowStyle::from_bits(bits).ok_or_else(|| anyhow!("there is no gwl style"))
}
pub fn title(self) -> Result<String> {
@@ -391,7 +375,7 @@ impl Window {
if let (Ok(style), Ok(ex_style)) = (&self.style(), &self.ex_style()) {
debug.window_style = Some(*style);
debug.extended_window_style = Some(*ex_style);
let eligible = window_is_eligible(self.hwnd, &title, &exe_name, &class, &path, style, ex_style, event, debug);
let eligible = window_is_eligible(&title, &exe_name, &class, &path, style, ex_style, event, debug);
debug.should_manage = eligible;
return Ok(eligible);
}
@@ -411,7 +395,6 @@ pub struct RuleDebug {
pub has_title: bool,
pub is_cloaked: bool,
pub allow_cloaked: bool,
pub allow_layered_transparency: bool,
pub window_style: Option<WindowStyle>,
pub extended_window_style: Option<ExtendedWindowStyle>,
pub title: Option<String>,
@@ -428,7 +411,6 @@ pub struct RuleDebug {
#[allow(clippy::too_many_arguments)]
fn window_is_eligible(
hwnd: isize,
title: &String,
exe_name: &String,
class: &String,
@@ -483,7 +465,7 @@ fn window_is_eligible(
}
let layered_whitelist = LAYERED_WHITELIST.lock();
let mut allow_layered = if let Some(rule) = should_act(
let allow_layered = if let Some(rule) = should_act(
title,
exe_name,
class,
@@ -497,14 +479,8 @@ fn window_is_eligible(
false
};
let known_layered_hwnds = transparency_manager::known_hwnds();
allow_layered = if known_layered_hwnds.contains(&hwnd) {
debug.allow_layered_transparency = true;
true
} else {
allow_layered
};
// TODO: might need this for transparency
// let allow_layered = true;
let allow_wsl2_gui = {
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();

View File

@@ -37,7 +37,6 @@ 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::border_manager;
@@ -48,14 +47,12 @@ use crate::load_configuration;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_LABEL;
use crate::stackbar_manager::STACKBAR_MODE;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::static_config::StaticConfig;
use crate::transparency_manager;
use crate::window::Window;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
@@ -125,7 +122,6 @@ pub struct GlobalState {
pub border_offset: i32,
pub border_width: i32,
pub stackbar_mode: StackbarMode,
pub stackbar_label: StackbarLabel,
pub stackbar_focused_text_colour: Colour,
pub stackbar_unfocused_text_colour: Colour,
pub stackbar_tab_background_colour: Colour,
@@ -164,11 +160,10 @@ impl Default for GlobalState {
border_manager::UNFOCUSED.load(Ordering::SeqCst),
))),
},
border_style: STYLE.load(),
border_style: *STYLE.lock(),
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst),
stackbar_mode: STACKBAR_MODE.load(),
stackbar_label: STACKBAR_LABEL.load(),
stackbar_focused_text_colour: Colour::Rgb(Rgb::from(
STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
)),
@@ -520,7 +515,7 @@ impl WindowManager {
// Hide the window we are about to remove if it is on the currently focused workspace
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
Window::from(op.hwnd).hide();
Window { hwnd: op.hwnd }.hide();
should_update_focused_workspace = true;
}
@@ -550,7 +545,7 @@ impl WindowManager {
.get_mut(op.target_workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
target_workspace.new_container_for_window(Window::from(op.hwnd));
target_workspace.new_container_for_window(Window { hwnd: op.hwnd });
}
// Only re-tile the focused workspace if we need to
@@ -598,14 +593,14 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn manage_focused_window(&mut self) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Manage(Window::from(hwnd));
let event = WindowManagerEvent::Manage(Window { hwnd });
Ok(winevent_listener::event_tx().send(event)?)
}
#[tracing::instrument(skip(self))]
pub fn unmanage_focused_window(&mut self) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Unmanage(Window::from(hwnd));
let event = WindowManagerEvent::Unmanage(Window { hwnd });
Ok(winevent_listener::event_tx().send(event)?)
}
@@ -614,7 +609,6 @@ impl WindowManager {
let mut hwnd = None;
let workspace = self.focused_workspace()?;
// first check the focused workspace
if let Some(container_idx) = workspace.container_idx_from_current_point() {
if let Some(container) = workspace.containers().get(container_idx) {
if let Some(window) = container.focused_window() {
@@ -623,34 +617,6 @@ impl WindowManager {
}
}
// then check all workspaces
if hwnd.is_none() {
for monitor in self.monitors() {
for ws in monitor.workspaces() {
if let Some(container_idx) = ws.container_idx_from_current_point() {
if let Some(container) = ws.containers().get(container_idx) {
if let Some(window) = container.focused_window() {
hwnd = Some(window.hwnd);
}
}
}
}
}
}
// finally try matching the other way using a hwnd returned from the cursor pos
if hwnd.is_none() {
let cursor_pos_hwnd = WindowsApi::window_at_cursor_pos()?;
for monitor in self.monitors() {
for ws in monitor.workspaces() {
if ws.container_for_window(cursor_pos_hwnd).is_some() {
hwnd = Some(cursor_pos_hwnd);
}
}
}
}
if let Some(hwnd) = hwnd {
if self.has_pending_raise_op
|| self.focused_window()?.hwnd == hwnd
@@ -662,13 +628,15 @@ impl WindowManager {
return Ok(());
}
let event = WindowManagerEvent::Raise(Window::from(hwnd));
let event = WindowManagerEvent::Raise(Window { hwnd });
self.has_pending_raise_op = true;
winevent_listener::event_tx().send(event)?;
} else {
tracing::debug!(
"not raising unknown window: {}",
Window::from(WindowsApi::window_at_cursor_pos()?)
Window {
hwnd: WindowsApi::window_at_cursor_pos()?
}
);
}
@@ -792,7 +760,9 @@ impl WindowManager {
window.focus(self.mouse_follows_focus)?;
}
} else {
let desktop_window = Window::from(WindowsApi::desktop_window()?);
let desktop_window = Window {
hwnd: WindowsApi::desktop_window()?,
};
let rect = self.focused_monitor_size()?;
WindowsApi::center_cursor_in_rect(&rect)?;
@@ -933,7 +903,6 @@ impl WindowManager {
tracing::info!("restoring all hidden windows");
let no_titlebar = NO_TITLEBAR.lock();
let known_transparent_hwnds = transparency_manager::known_hwnds();
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
@@ -943,10 +912,6 @@ impl WindowManager {
window.add_title_bar()?;
}
if known_transparent_hwnds.contains(&window.hwnd) {
window.opaque()?;
}
window.restore();
}
}
@@ -1068,14 +1033,6 @@ impl WindowManager {
tracing::info!("moving container");
let focused_monitor_idx = self.focused_monitor_idx();
if focused_monitor_idx == monitor_idx {
if let Some(workspace_idx) = workspace_idx {
return self.move_container_to_workspace(workspace_idx, follow);
}
}
let offset = self.work_area_offset;
let mouse_follows_focus = self.mouse_follows_focus;
@@ -1095,12 +1052,6 @@ impl WindowManager {
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?;
let container_hwnds = container
.windows()
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
monitor.update_focused_workspace(offset)?;
let target_monitor = self
@@ -1114,22 +1065,9 @@ impl WindowManager {
target_monitor.focus_workspace(workspace_idx)?;
}
if let Some(workspace) = target_monitor.focused_workspace() {
if !*workspace.tile() {
for hwnd in container_hwnds {
Window::from(hwnd).center(target_monitor.work_area_size())?;
}
}
}
target_monitor.load_focused_workspace(mouse_follows_focus)?;
target_monitor.update_focused_workspace(offset)?;
// this second one is for DPI changes when the target is another monitor
// if we don't do this the layout on the other monitor could look funny
// until it is interacted with again
target_monitor.update_focused_workspace(offset)?;
if follow {
self.focus_monitor(monitor_idx)?;
}
@@ -1175,7 +1113,7 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no monitor"))?;
target_monitor.workspaces_mut().push_back(workspace);
target_monitor.focus_workspace(target_monitor.workspaces().len().saturating_sub(1))?;
target_monitor.focus_workspace(target_monitor.workspaces().len() - 1)?;
target_monitor.load_focused_workspace(mouse_follows_focus)?;
}
@@ -1191,11 +1129,7 @@ impl WindowManager {
tracing::info!("focusing container");
let new_idx = if workspace.monocle_container().is_some() {
None
} else {
workspace.new_idx_for_direction(direction)
};
let new_idx = workspace.new_idx_for_direction(direction);
let mut cross_monitor_monocle = false;
@@ -1310,9 +1244,10 @@ impl WindowManager {
let origin_workspace =
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
origin_workspace.focus_container(
origin_workspace.focused_container_idx().saturating_sub(1),
);
if origin_workspace.focused_container_idx() != 0 {
origin_workspace
.focus_container(origin_workspace.focused_container_idx() - 1);
}
}
}
@@ -1463,67 +1398,6 @@ impl WindowManager {
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn stack_all(&mut self) -> Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("stacking all windows on workspace");
let workspace = self.focused_workspace_mut()?;
let mut focused_hwnd = None;
if let Some(container) = workspace.focused_container() {
if let Some(window) = container.focused_window() {
focused_hwnd = Some(window.hwnd);
}
}
workspace.focus_container(workspace.containers().len().saturating_sub(1));
while workspace.focused_container_idx() > 0 {
workspace.move_window_to_container(0)?;
workspace.focus_container(workspace.containers().len().saturating_sub(1));
}
if let Some(hwnd) = focused_hwnd {
workspace.focus_container_by_window(hwnd)?;
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn unstack_all(&mut self) -> Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("unstacking all windows in container");
let workspace = self.focused_workspace_mut()?;
let mut focused_hwnd = None;
if let Some(container) = workspace.focused_container() {
if let Some(window) = container.focused_window() {
focused_hwnd = Some(window.hwnd);
}
}
let initial_focused_container_index = workspace.focused_container_idx();
let mut focused_container = workspace.focused_container().cloned();
while let Some(focused) = &focused_container {
if focused.windows().len() > 1 {
workspace.new_container_for_focused_window()?;
workspace.focus_container(initial_focused_container_index);
focused_container = workspace.focused_container().cloned();
} else {
focused_container = None;
}
}
if let Some(hwnd) = focused_hwnd {
workspace.focus_container_by_window(hwnd)?;
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn add_window_to_container(&mut self, direction: OperationDirection) -> Result<()> {
self.handle_unmanaged_window_behaviour()?;
@@ -1555,20 +1429,12 @@ impl WindowManager {
Layout::Default(DefaultLayout::Grid)
| Layout::Default(DefaultLayout::UltrawideVerticalStack)
) {
new_idx.saturating_sub(1)
new_idx - 1
} else {
new_idx
};
if let Some(current) = workspace.focused_container() {
if current.windows().len() > 1 {
workspace.focus_container(adjusted_new_index);
workspace.move_window_to_container(current_container_idx)?;
} else {
workspace.move_window_to_container(adjusted_new_index)?;
}
}
workspace.move_window_to_container(adjusted_new_index)?;
self.update_focused_workspace(self.mouse_follows_focus, false)?;
}

View File

@@ -27,7 +27,7 @@ pub enum WindowManagerEvent {
Manage(Window),
Unmanage(Window),
Raise(Window),
TitleUpdate(WinEvent, Window),
ForceUpdate(Window),
}
impl Display for WindowManagerEvent {
@@ -75,8 +75,8 @@ impl Display for WindowManagerEvent {
Self::Raise(window) => {
write!(f, "Raise (Window: {window})")
}
Self::TitleUpdate(winevent, window) => {
write!(f, "TitleUpdate (WinEvent: {winevent}, Window: {window})")
Self::ForceUpdate(window) => {
write!(f, "ForceUpdate (Window: {window})")
}
}
}
@@ -98,7 +98,7 @@ impl WindowManagerEvent {
| Self::Raise(window)
| Self::Manage(window)
| Self::Unmanage(window)
| Self::TitleUpdate(_, window) => window,
| Self::ForceUpdate(window) => window,
}
}
@@ -141,7 +141,7 @@ impl WindowManagerEvent {
let class = &window.class().ok()?;
let path = &window.path().ok()?;
let should_trigger_show = should_act(
let should_trigger = should_act(
title,
exe_name,
class,
@@ -151,10 +151,10 @@ impl WindowManagerEvent {
)
.is_some();
if should_trigger_show {
if should_trigger {
Option::from(Self::Show(winevent, window))
} else {
Option::from(Self::TitleUpdate(winevent, window))
None
}
}
_ => None,

View File

@@ -220,7 +220,7 @@ impl WindowsApi {
}
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
Ok(win32_display_data::connected_displays_all()
Ok(win32_display_data::connected_displays()
.flatten()
.map(|d| {
let name = d.device_name.trim_start_matches(r"\\.\").to_string();
@@ -232,7 +232,7 @@ impl WindowsApi {
}
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
'read: for display in win32_display_data::connected_displays_all().flatten() {
'read: for display in win32_display_data::connected_displays().flatten() {
let path = display.device_path.clone();
let mut split: Vec<_> = path.split('#').collect();
split.remove(0);
@@ -276,7 +276,8 @@ impl WindowsApi {
if monitors.elements().is_empty() {
monitors.elements_mut().push_back(m);
} else if let Some(preference) = index_preference {
while *preference > monitors.elements().len() {
let current_len = monitors.elements().len();
if *preference > current_len {
monitors.elements_mut().reserve(1);
}
@@ -790,7 +791,7 @@ impl WindowsApi {
}
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
for display in win32_display_data::connected_displays_all().flatten() {
for display in win32_display_data::connected_displays().flatten() {
if display.hmonitor == hmonitor {
let path = display.device_path;
let mut split: Vec<_> = path.split('#').collect();
@@ -979,10 +980,11 @@ impl WindowsApi {
.process()
}
pub fn set_transparent(hwnd: HWND, alpha: u8) -> Result<()> {
pub fn set_transparent(hwnd: HWND) -> Result<()> {
unsafe {
#[allow(clippy::cast_sign_loss)]
SetLayeredWindowAttributes(hwnd, COLORREF(-1i32 as u32), alpha, LWA_ALPHA)?;
// TODO: alpha should be configurable
SetLayeredWindowAttributes(hwnd, COLORREF(-1i32 as u32), 150, LWA_ALPHA)?;
}
Ok(())

View File

@@ -22,7 +22,7 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let is_maximized = WindowsApi::is_zoomed(hwnd);
if is_visible && is_window && !is_minimized {
let window = Window::from(hwnd);
let window = Window { hwnd: hwnd.0 };
if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) {
if should_manage {
@@ -48,7 +48,7 @@ pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL {
let is_minimized = WindowsApi::is_iconic(hwnd);
if is_visible && is_window && !is_minimized {
let window = Window::from(hwnd);
let window = Window { hwnd: hwnd.0 };
if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) {
if should_manage {
@@ -74,25 +74,14 @@ pub extern "system" fn win_event_hook(
return;
}
let window = Window::from(hwnd);
let window = Window { hwnd: hwnd.0 };
let winevent = match WinEvent::try_from(event) {
Ok(event) => event,
Err(_) => return,
};
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
None => {
tracing::trace!(
"Unhandled WinEvent: {winevent} (hwnd: {}, exe: {}, title: {}, class: {})",
window.hwnd,
window.exe().unwrap_or_default(),
window.title().unwrap_or_default(),
window.class().unwrap_or_default()
);
return;
}
None => return,
Some(event) => event,
};

View File

@@ -1,5 +1,4 @@
use std::sync::OnceLock;
use std::time::Duration;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
@@ -35,26 +34,23 @@ pub fn start() {
)
};
let mut msg: MSG = MSG::default();
loop {
let mut msg: MSG = MSG::default();
unsafe {
if !GetMessageW(&mut msg, HWND(0), 0, 0).as_bool() {
tracing::debug!("windows event processing thread shutdown");
tracing::info!("windows event processing shutdown");
break;
};
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
std::thread::sleep(Duration::from_millis(10))
}
})
});
}
fn channel() -> &'static (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
CHANNEL.get_or_init(crossbeam_channel::unbounded)
}
pub fn event_tx() -> Sender<WindowManagerEvent> {

View File

@@ -38,16 +38,7 @@ use crate::REMOVE_TITLEBARS;
#[allow(clippy::struct_field_names)]
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
)]
pub struct Workspace {
#[getset(get = "pub", set = "pub")]
@@ -366,7 +357,7 @@ impl Workspace {
layout.bottom -= total_height;
}
window.set_position(layout, false)?;
window.set_position(&layout, false)?;
}
}
@@ -406,28 +397,6 @@ impl Workspace {
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
let mut hwnds = vec![];
let mut floating_hwnds = vec![];
let mut remove_monocle = false;
let mut remove_maximized = false;
if let Some(monocle) = &self.monocle_container {
let window_count = monocle.windows().len();
let mut orphan_count = 0;
for window in monocle.windows() {
if !window.is_window() {
hwnds.push(window.hwnd);
orphan_count += 1;
}
}
remove_monocle = orphan_count == window_count;
}
if let Some(window) = &self.maximized_window {
if !window.is_window() {
hwnds.push(window.hwnd);
remove_maximized = true;
}
}
for window in self.visible_windows_mut().into_iter().flatten() {
if !window.is_window() {
@@ -462,14 +431,6 @@ impl Workspace {
self.containers_mut()
.retain(|c| !container_ids.contains(c.id()));
if remove_monocle {
self.set_monocle_container(None);
}
if remove_maximized {
self.set_maximized_window(None);
}
Ok((hwnds.len() + floating_hwnds.len(), container_ids.len()))
}
@@ -699,10 +660,6 @@ impl Workspace {
self.set_monocle_container_restore_idx(None);
}
for c in self.containers() {
c.restore();
}
return Ok(());
}
}
@@ -802,7 +759,7 @@ impl Workspace {
self.resize_dimensions_mut().remove(focused_idx);
if focused_idx < target_container_idx {
target_container_idx.saturating_sub(1)
target_container_idx - 1
} else {
target_container_idx
}
@@ -924,8 +881,8 @@ impl Workspace {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
if focused_idx == self.containers().len() {
self.focus_container(focused_idx.saturating_sub(1));
if focused_idx == self.containers().len() && focused_idx != 0 {
self.focus_container(focused_idx - 1);
}
} else {
container.load_focused_window();
@@ -1333,8 +1290,7 @@ impl Workspace {
.ok_or_else(|| anyhow!("there is no monocle container"))?;
let window = *window;
if !self.containers().is_empty() && restore_idx > self.containers().len().saturating_sub(1)
{
if !self.containers().is_empty() && restore_idx > self.containers().len() - 1 {
self.containers_mut()
.resize(restore_idx, Container::default());
}
@@ -1423,10 +1379,13 @@ impl Workspace {
pub fn focus_previous_container(&mut self) {
let focused_idx = self.focused_container_idx();
self.focus_container(focused_idx.saturating_sub(1));
if focused_idx != 0 {
self.focus_container(focused_idx - 1);
}
}
fn focus_last_container(&mut self) {
self.focus_container(self.containers().len().saturating_sub(1));
self.focus_container(self.containers().len() - 1);
}
}

View File

@@ -30,26 +30,14 @@ pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
}
fn event_tx() -> Sender<Notification> {
pub fn event_tx() -> Sender<Notification> {
channel().0.clone()
}
fn event_rx() -> Receiver<Notification> {
pub fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn send_notification(monitor_idx: usize, workspace_idx: usize) {
if event_tx()
.try_send(Notification {
monitor_idx,
workspace_idx,
})
.is_err()
{
tracing::warn!("channel is full; dropping notification")
}
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
@@ -118,7 +106,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Unblock the border manager
ALT_TAB_HWND.store(None);
// Send a notification to the border manager to update the borders
border_manager::send_notification();
border_manager::event_tx().send(border_manager::Notification)?;
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic-no-console"
version = "0.1.27"
version = "0.1.26-dev.0"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.27"
version = "0.1.26-dev.0"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
@@ -11,6 +11,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" }
komorebi-client = { path = "../komorebi-client" }
@@ -20,6 +21,7 @@ color-eyre = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
fs-tail = "0.1"
heck = "0.5"
lazy_static = "1"
miette = { version = "7", features = ["fancy"] }
paste = "1"
@@ -32,7 +34,6 @@ sysinfo = { workspace = true }
thiserror = "1"
uds_windows = "1"
which = "6"
win32-display-data = { workspace = true }
windows = { workspace = true }
[build-dependencies]

View File

@@ -1,7 +1,7 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::doc_markdown)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
use chrono::Utc;
use chrono::Local;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
@@ -24,6 +24,7 @@ use color_eyre::eyre::bail;
use color_eyre::Result;
use dirs::data_local_dir;
use fs_tail::TailedFile;
use heck::ToKebabCase;
use komorebi_core::resolve_home_path;
use lazy_static::lazy_static;
use miette::NamedSource;
@@ -38,7 +39,8 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use komorebi_client::StaticConfig;
use derive_ahk::AhkFunction;
use derive_ahk::AhkLibrary;
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
@@ -99,6 +101,14 @@ lazy_static! {
};
}
trait AhkLibrary {
fn generate_ahk_library() -> String;
}
trait AhkFunction {
fn generate_ahk_function() -> String;
}
#[derive(thiserror::Error, Debug, miette::Diagnostic)]
#[error("{message}")]
#[diagnostic(code(komorebi::configuration), help("try fixing this syntax error"))]
@@ -130,7 +140,7 @@ macro_rules! gen_enum_subcommand_args {
( $( $name:ident: $element:ty ),+ $(,)? ) => {
$(
paste! {
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
#[clap(value_enum)]
[<$element:snake>]: $element
@@ -170,7 +180,7 @@ macro_rules! gen_target_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
/// Target index (zero-indexed)
target: usize,
@@ -195,7 +205,7 @@ macro_rules! gen_named_target_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
/// Target workspace name
workspace: String,
@@ -219,7 +229,7 @@ macro_rules! gen_workspace_subcommand_args {
( $( $name:ident: $(#[enum] $(@$value_enum:tt)?)? $value:ty ),+ $(,)? ) => (
paste! {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct [<Workspace $name>] {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -251,7 +261,7 @@ macro_rules! gen_named_workspace_subcommand_args {
( $( $name:ident: $(#[enum] $(@$value_enum:tt)?)? $value:ty ),+ $(,)? ) => (
paste! {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct [<NamedWorkspace $name>] {
/// Target workspace name
workspace: String,
@@ -273,7 +283,7 @@ gen_named_workspace_subcommand_args! {
Tiling: #[enum] BooleanState,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct ClearWorkspaceLayoutRules {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -282,7 +292,7 @@ pub struct ClearWorkspaceLayoutRules {
workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct WorkspaceCustomLayout {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -294,7 +304,7 @@ pub struct WorkspaceCustomLayout {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct NamedWorkspaceCustomLayout {
/// Target workspace name
workspace: String,
@@ -303,7 +313,7 @@ pub struct NamedWorkspaceCustomLayout {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct WorkspaceLayoutRule {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -318,7 +328,7 @@ pub struct WorkspaceLayoutRule {
layout: DefaultLayout,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct NamedWorkspaceLayoutRule {
/// Target workspace name
workspace: String,
@@ -330,7 +340,7 @@ pub struct NamedWorkspaceLayoutRule {
layout: DefaultLayout,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct WorkspaceCustomLayoutRule {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -345,7 +355,7 @@ pub struct WorkspaceCustomLayoutRule {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct NamedWorkspaceCustomLayoutRule {
/// Target workspace name
workspace: String,
@@ -357,7 +367,7 @@ pub struct NamedWorkspaceCustomLayoutRule {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct Resize {
#[clap(value_enum)]
edge: OperationDirection,
@@ -365,7 +375,7 @@ struct Resize {
sizing: Sizing,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct ResizeAxis {
#[clap(value_enum)]
axis: Axis,
@@ -373,13 +383,13 @@ struct ResizeAxis {
sizing: Sizing,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct ResizeDelta {
/// The delta of pixels by which to increase or decrease window dimensions when resizing
pixels: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct InvisibleBorders {
/// Size of the left invisible border
left: i32,
@@ -391,7 +401,7 @@ struct InvisibleBorders {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct GlobalWorkAreaOffset {
/// Size of the left work area offset (set right to left * 2 to maintain right padding)
left: i32,
@@ -403,7 +413,7 @@ struct GlobalWorkAreaOffset {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct MonitorWorkAreaOffset {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -417,7 +427,7 @@ struct MonitorWorkAreaOffset {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct MonitorIndexPreference {
/// Preferred monitor index (zero-indexed)
index_preference: usize,
@@ -431,7 +441,7 @@ struct MonitorIndexPreference {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct DisplayIndexPreference {
/// Preferred monitor index (zero-indexed)
index_preference: usize,
@@ -439,7 +449,7 @@ struct DisplayIndexPreference {
display: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct EnsureWorkspaces {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -447,7 +457,7 @@ struct EnsureWorkspaces {
workspace_count: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct EnsureNamedWorkspaces {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -455,7 +465,7 @@ struct EnsureNamedWorkspaces {
names: Vec<String>,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct FocusMonitorWorkspace {
/// Target monitor index (zero-indexed)
target_monitor: usize,
@@ -463,7 +473,7 @@ struct FocusMonitorWorkspace {
target_workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct SendToMonitorWorkspace {
/// Target monitor index (zero-indexed)
target_monitor: usize,
@@ -471,7 +481,7 @@ pub struct SendToMonitorWorkspace {
target_workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct MoveToMonitorWorkspace {
/// Target monitor index (zero-indexed)
target_monitor: usize,
@@ -483,7 +493,7 @@ macro_rules! gen_focused_workspace_padding_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
/// Pixels size to set as an integer
size: i32,
@@ -501,7 +511,7 @@ macro_rules! gen_padding_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -523,7 +533,7 @@ macro_rules! gen_named_padding_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
/// Target workspace name
workspace: String,
@@ -544,7 +554,7 @@ macro_rules! gen_padding_adjustment_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
#[clap(value_enum)]
sizing: Sizing,
@@ -564,7 +574,7 @@ macro_rules! gen_application_target_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ $(,)? ) => {
$(
#[derive(clap::Parser)]
#[derive(clap::Parser, derive_ahk::AhkFunction)]
pub struct $name {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -585,7 +595,7 @@ gen_application_target_subcommand_args! {
RemoveTitleBar,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct InitialWorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -597,7 +607,7 @@ struct InitialWorkspaceRule {
workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct InitialNamedWorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -607,7 +617,7 @@ struct InitialNamedWorkspaceRule {
workspace: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct WorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -619,7 +629,7 @@ struct WorkspaceRule {
workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct NamedWorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -629,13 +639,13 @@ struct NamedWorkspaceRule {
workspace: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct ToggleFocusFollowsMouse {
#[clap(value_enum, short, long, default_value = "windows")]
implementation: FocusFollowsMouseImplementation,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct FocusFollowsMouse {
#[clap(value_enum, short, long, default_value = "windows")]
implementation: FocusFollowsMouseImplementation,
@@ -643,25 +653,13 @@ struct FocusFollowsMouse {
boolean_state: BooleanState,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct Border {
#[clap(value_enum)]
boolean_state: BooleanState,
}
#[derive(Parser)]
struct Transparency {
#[clap(value_enum)]
boolean_state: BooleanState,
}
#[derive(Parser)]
struct TransparencyAlpha {
/// Alpha
alpha: u8,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct BorderColour {
#[clap(value_enum, short, long, default_value = "single")]
window_kind: WindowKind,
@@ -673,19 +671,19 @@ struct BorderColour {
b: u32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct BorderWidth {
/// Desired width of the window border
width: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct BorderOffset {
/// Desired offset of the window border
offset: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
#[allow(clippy::struct_excessive_bools)]
struct Start {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
@@ -708,56 +706,56 @@ struct Start {
ahk: bool,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct Stop {
/// Stop whkd if it is running as a background process
#[clap(long)]
whkd: bool,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct SaveResize {
/// File to which the resize layout dimensions should be saved
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct LoadResize {
/// File from which the resize layout dimensions should be loaded
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct LoadCustomLayout {
/// JSON or YAML file from which the custom layout definition should be loaded
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct SubscribeSocket {
/// Name of the socket to send event notifications to
socket: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct UnsubscribeSocket {
/// Name of the socket to stop sending event notifications to
socket: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct SubscribePipe {
/// Name of the pipe to send event notifications to (without "\\.\pipe\" prepended)
named_pipe: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct UnsubscribePipe {
/// Name of the pipe to stop sending event notifications to (without "\\.\pipe\" prepended)
named_pipe: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct AhkAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
path: PathBuf,
@@ -765,7 +763,7 @@ struct AhkAppSpecificConfiguration {
override_path: Option<PathBuf>,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct PwshAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
path: PathBuf,
@@ -773,19 +771,19 @@ struct PwshAppSpecificConfiguration {
override_path: Option<PathBuf>,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct FormatAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct AltFocusHack {
#[clap(value_enum)]
boolean_state: BooleanState,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct EnableAutostart {
/// Path to a static configuration JSON file
#[clap(action, short, long)]
@@ -808,7 +806,7 @@ struct Opts {
subcmd: SubCommand,
}
#[derive(Parser)]
#[derive(Parser, AhkLibrary)]
enum SubCommand {
#[clap(hide = true)]
Docgen,
@@ -830,13 +828,8 @@ enum SubCommand {
State,
/// Show a JSON representation of the current global state
GlobalState,
/// Launch the komorebi-gui debugging tool
Gui,
/// Show a JSON representation of visible windows
VisibleWindows,
/// Show information about connected monitors
#[clap(alias = "monitor-info")]
MonitorInformation,
/// Query the current window manager state
#[clap(arg_required_else_help = true)]
Query(Query),
@@ -891,10 +884,6 @@ enum SubCommand {
/// Stack the focused window in the specified direction
#[clap(arg_required_else_help = true)]
Stack(Stack),
/// Stack all windows on the focused workspace
StackAll,
/// Unstack all windows in the focused container
UnstackAll,
/// Resize the focused window in the specified direction
#[clap(arg_required_else_help = true)]
#[clap(alias = "resize")]
@@ -1176,12 +1165,6 @@ enum SubCommand {
#[clap(arg_required_else_help = true)]
#[clap(alias = "active-window-border-offset")]
BorderOffset(BorderOffset),
/// Enable or disable transparency for unfocused windows
#[clap(arg_required_else_help = true)]
Transparency(Transparency),
/// Set the alpha value for unfocused window transparency
#[clap(arg_required_else_help = true)]
TransparencyAlpha(TransparencyAlpha),
/// Enable or disable focus follows mouse for the operating system
#[clap(arg_required_else_help = true)]
FocusFollowsMouse(FocusFollowsMouse),
@@ -1193,6 +1176,8 @@ enum SubCommand {
MouseFollowsFocus(MouseFollowsFocus),
/// Toggle mouse follows focus on all workspaces
ToggleMouseFollowsFocus,
/// Generate a library of AutoHotKey helper functions
AhkLibrary,
/// Generate common app-specific configurations and fixes to use in komorebi.ahk
#[clap(arg_required_else_help = true)]
#[clap(alias = "ahk-asc")]
@@ -1302,25 +1287,21 @@ fn main() -> Result<()> {
}
}
SubCommand::Quickstart => {
let home_dir = dirs::home_dir().expect("could not find home dir");
let config_dir = home_dir.join(".config");
let local_appdata_dir = data_local_dir().expect("could not find localdata dir");
let data_dir = local_appdata_dir.join("komorebi");
std::fs::create_dir_all(&*WHKD_CONFIG_DIR)?;
std::fs::create_dir_all(&*HOME_DIR)?;
std::fs::create_dir_all(&config_dir)?;
std::fs::create_dir_all(data_dir)?;
let mut komorebi_json = include_str!("../../docs/komorebi.example.json").to_string();
if std::env::var("KOMOREBI_CONFIG_HOME").is_ok() {
komorebi_json =
komorebi_json.replace("Env:USERPROFILE", "Env:KOMOREBI_CONFIG_HOME");
}
let komorebi_json = include_str!("../../docs/komorebi.example.json");
std::fs::write(HOME_DIR.join("komorebi.json"), komorebi_json)?;
let applications_yaml = include_str!("../applications.yaml");
std::fs::write(HOME_DIR.join("applications.yaml"), applications_yaml)?;
let whkdrc = include_str!("../../docs/whkdrc.sample");
std::fs::write(WHKD_CONFIG_DIR.join("whkdrc"), whkdrc)?;
std::fs::write(config_dir.join("whkdrc"), whkdrc)?;
println!("Example ~/komorebi.json, ~/.config/whkdrc and latest ~/applications.yaml files downloaded");
println!("You can now run komorebic start --whkd");
@@ -1480,8 +1461,37 @@ fn main() -> Result<()> {
println!("{}", whkdrc.display());
}
}
SubCommand::AhkLibrary => {
let library = HOME_DIR.join("komorebic.lib.ahk");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(library.clone())?;
let output: String = SubCommand::generate_ahk_library();
let fixed_id = output.replace("%id%", "\"%id%\"");
let fixed_stop_def = fixed_id.replace("Stop(whkd)", "Stop()");
let fixed_output =
fixed_stop_def.replace("komorebic.exe stop --whkd %whkd%", "komorebic.exe stop");
file.write_all(fixed_output.as_bytes())?;
println!(
"\nAHKv1 helper library for komorebic written to {}",
library.to_string_lossy()
);
println!("\nYou can convert this file to AHKv2 syntax using https://github.com/mmikeww/AHK-v2-script-converter");
println!(
"\nYou can include the converted library at the top of your komorebi.ahk config with this line:"
);
println!("\n#Include komorebic.lib.ahk");
}
SubCommand::Log => {
let timestamp = Utc::now().format("%Y-%m-%d").to_string();
let timestamp = Local::now().format("%Y-%m-%d").to_string();
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
let file = TailedFile::new(File::open(color_log)?);
let locked = file.lock();
@@ -1942,25 +1952,6 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
);
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
let static_config = arg.config.map_or_else(
|| {
let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() {
Option::from(komorebi_json)
} else {
None
}
},
Option::from,
);
if let Some(config) = static_config {
let path = resolve_home_path(config)?;
let raw = std::fs::read_to_string(path)?;
StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw);
}
}
SubCommand::Stop(arg) => {
if arg.whkd {
@@ -2045,15 +2036,9 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
SubCommand::Stack(arg) => {
send_message(&SocketMessage::StackWindow(arg.operation_direction).as_bytes()?)?;
}
SubCommand::StackAll => {
send_message(&SocketMessage::StackAll.as_bytes()?)?;
}
SubCommand::Unstack => {
send_message(&SocketMessage::UnstackWindow.as_bytes()?)?;
}
SubCommand::UnstackAll => {
send_message(&SocketMessage::UnstackAll.as_bytes()?)?;
}
SubCommand::CycleStack(arg) => {
send_message(&SocketMessage::CycleStack(arg.cycle_direction).as_bytes()?)?;
}
@@ -2145,15 +2130,9 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
SubCommand::GlobalState => {
print_query(&SocketMessage::GlobalState.as_bytes()?);
}
SubCommand::Gui => {
Command::new("komorebi-gui").spawn()?;
}
SubCommand::VisibleWindows => {
print_query(&SocketMessage::VisibleWindows.as_bytes()?);
}
SubCommand::MonitorInformation => {
print_query(&SocketMessage::MonitorInformation.as_bytes()?);
}
SubCommand::Query(arg) => {
print_query(&SocketMessage::Query(arg.state_query).as_bytes()?);
}
@@ -2269,12 +2248,6 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
SubCommand::BorderOffset(arg) => {
send_message(&SocketMessage::BorderOffset(arg.offset).as_bytes()?)?;
}
SubCommand::Transparency(arg) => {
send_message(&SocketMessage::Transparency(arg.boolean_state.into()).as_bytes()?)?;
}
SubCommand::TransparencyAlpha(arg) => {
send_message(&SocketMessage::TransparencyAlpha(arg.alpha).as_bytes()?)?;
}
SubCommand::ResizeDelta(arg) => {
send_message(&SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
}
@@ -2417,11 +2390,6 @@ fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
unsafe { ShowWindow(hwnd, command) };
}
fn remove_transparency(hwnd: HWND) {
let _ = komorebi_client::Window::from(hwnd.0).opaque();
}
fn restore_window(hwnd: HWND) {
show_window(hwnd, SW_RESTORE);
remove_transparency(hwnd);
}

View File

@@ -57,7 +57,6 @@ nav:
- Troubleshooting: troubleshooting.md
- Common workflows:
- common-workflows/komorebi-config-home.md
- common-workflows/autohotkey.md
- common-workflows/borders.md
- common-workflows/stackbar.md
- common-workflows/remove-gaps.md
@@ -68,7 +67,7 @@ nav:
- common-workflows/mouse-follows-focus.md
- common-workflows/custom-layouts.md
- common-workflows/dynamic-layout-switching.md
- common-workflows/set-display-index.md
- common-workflows/autohotkey.md
- Release notes:
- release/v0-1-22.md
- Configuration reference: https://komorebi.lgug2z.com/schema
@@ -81,9 +80,7 @@ nav:
- cli/whkdrc.md
- cli/state.md
- cli/global-state.md
- cli/gui.md
- cli/visible-windows.md
- cli/monitor-information.md
- cli/query.md
- cli/subscribe-socket.md
- cli/unsubscribe-socket.md
@@ -102,8 +99,6 @@ nav:
- cli/cycle-focus.md
- cli/cycle-move.md
- cli/stack.md
- cli/stack-all.md
- cli/unstack-all.md
- cli/resize-edge.md
- cli/resize-axis.md
- cli/unstack.md
@@ -129,7 +124,6 @@ nav:
- cli/cycle-monitor.md
- cli/cycle-workspace.md
- cli/move-workspace-to-monitor.md
- cli/cycle-move-workspace-to-monitor.md
- cli/swap-workspaces-with-monitor.md
- cli/new-workspace.md
- cli/resize-delta.md
@@ -146,7 +140,6 @@ nav:
- cli/flip-layout.md
- cli/promote.md
- cli/promote-focus.md
- cli/promote-window.md
- cli/retile.md
- cli/monitor-index-preference.md
- cli/display-index-preference.md
@@ -200,12 +193,11 @@ nav:
- cli/border-colour.md
- cli/border-width.md
- cli/border-offset.md
- cli/transparency.md
- cli/transparency-alpha.md
- cli/focus-follows-mouse.md
- cli/toggle-focus-follows-mouse.md
- cli/mouse-follows-focus.md
- cli/toggle-mouse-follows-focus.md
- cli/ahk-library.md
- cli/ahk-app-specific-configuration.md
- cli/pwsh-app-specific-configuration.md
- cli/format-app-specific-configuration.md

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.27`",
"description": "The `komorebi.json` static configuration file reference for `v0.1.25`",
"type": "object",
"properties": {
"app_specific_configuration_path": {
@@ -1039,14 +1039,6 @@
"type": "integer",
"format": "int32"
},
"label": {
"description": "Stackbar height",
"type": "string",
"enum": [
"Process",
"Title"
]
},
"mode": {
"description": "Stackbar mode",
"type": "string",
@@ -1183,16 +1175,6 @@
}
}
},
"transparency": {
"description": "Add transparency to unfocused windows (default: false)",
"type": "boolean"
},
"transparency_alpha": {
"description": "Alpha value for unfocused window transparency [[0-255]] (default: 200)",
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"tray_and_multi_window_applications": {
"description": "Identify tray and multi-window applications",
"type": "array",

Binary file not shown.

View File

@@ -95,9 +95,6 @@
<Component Id='binary2' Guid='*'>
<File Id='exe2' Name='komorebic-no-console.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic-no-console.exe' KeyPath='yes' />
</Component>
<Component Id='binary3' Guid='*'>
<File Id='exe3' Name='komorebi-gui.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-gui.exe' KeyPath='yes' />
</Component>
</Directory>
</Directory>
</Directory>
@@ -116,8 +113,6 @@
<ComponentRef Id='binary2' />
<ComponentRef Id='binary3' />
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
<ComponentRef Id='Path' />
</Feature>