mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-17 02:37:40 +01:00
Compare commits
2 Commits
fix/ahk-ar
...
feature/mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57825db886 | ||
|
|
bdf66cc362 |
3
.github/workflows/windows.yaml
vendored
3
.github/workflows/windows.yaml
vendored
@@ -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
3
.gitignore
vendored
@@ -3,4 +3,5 @@
|
||||
/target
|
||||
CHANGELOG.md
|
||||
dummy.go
|
||||
komorebic/applications.yaml
|
||||
komorebi.ahk
|
||||
komorebic/applications.yaml
|
||||
@@ -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"
|
||||
|
||||
3069
Cargo.lock
generated
3069
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"derive-ahk",
|
||||
"komorebi",
|
||||
"komorebi-client",
|
||||
"komorebi-core",
|
||||
"komorebi-gui",
|
||||
"komorebic",
|
||||
"komorebic-no-console",
|
||||
]
|
||||
|
||||
14
derive-ahk/Cargo.toml
Normal file
14
derive-ahk/Cargo.toml
Normal 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
225
derive-ahk/src/lib.rs
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
# gui
|
||||
|
||||
```
|
||||
Launch the komorebi-gui debugging tool
|
||||
|
||||
Usage: komorebic.exe gui
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.27-dev.0"
|
||||
version = "0.1.26-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-core"
|
||||
version = "0.1.27-dev.0"
|
||||
version = "0.1.26-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "komorebi-gui"
|
||||
version = "0.1.27-dev.0"
|
||||
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 }
|
||||
@@ -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
56
komorebi.sample.ahk
Normal 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)
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.27-dev.0"
|
||||
version = "0.1.26-dev.0"
|
||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||
description = "A tiling window manager for Windows"
|
||||
categories = ["tiling-window-manager", "windows"]
|
||||
@@ -48,7 +48,7 @@ windows-interface = { workspace = true }
|
||||
winput = "0.2"
|
||||
winreg = "0.52"
|
||||
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "2a0f7166da154880a1750b91829b1186d9c6a00c" }
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data" }
|
||||
|
||||
[features]
|
||||
deadlock_detection = []
|
||||
|
||||
@@ -27,7 +27,6 @@ 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;
|
||||
@@ -211,8 +210,6 @@ impl Border {
|
||||
}
|
||||
}
|
||||
EndPaint(window, &ps);
|
||||
DeleteObject(hpen);
|
||||
DeleteObject(hbrush);
|
||||
ValidateRect(window, None);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![warn(clippy::all)]
|
||||
|
||||
pub mod border_manager;
|
||||
pub mod com;
|
||||
#[macro_use]
|
||||
@@ -11,7 +9,6 @@ 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;
|
||||
|
||||
@@ -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,7 +30,6 @@ 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::window_manager::WindowManager;
|
||||
@@ -260,7 +258,6 @@ fn main() -> Result<()> {
|
||||
stackbar_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 || {
|
||||
|
||||
@@ -57,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;
|
||||
|
||||
@@ -205,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)?;
|
||||
@@ -217,18 +215,8 @@ impl WindowManager {
|
||||
WindowsApi::center_cursor_in_rect(&focused_window_rect)?;
|
||||
WindowsApi::left_click();
|
||||
}
|
||||
SocketMessage::Close => {
|
||||
Window {
|
||||
hwnd: WindowsApi::foreground_window()?,
|
||||
}
|
||||
.close()?;
|
||||
}
|
||||
SocketMessage::Minimize => {
|
||||
Window {
|
||||
hwnd: 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()?,
|
||||
|
||||
@@ -24,7 +24,6 @@ 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;
|
||||
@@ -121,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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +335,7 @@ impl WindowManager {
|
||||
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();
|
||||
let workspace_has_monocle_container = workspace.monocle_container().is_some();
|
||||
|
||||
if !workspace_contains_window && !needs_reconciliation {
|
||||
match behaviour {
|
||||
@@ -328,18 +353,10 @@ 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();
|
||||
if matches!(event, WindowManagerEvent::Uncloak(_, _)) {
|
||||
if workspace_contains_window && workspace_has_monocle_container {
|
||||
self.toggle_monocle()?;
|
||||
window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,9 +588,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
|
||||
@@ -611,16 +629,7 @@ impl WindowManager {
|
||||
border_manager::event_tx().send(border_manager::Notification)?;
|
||||
stackbar_manager::event_tx().send(stackbar_manager::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());
|
||||
}
|
||||
|
||||
tracing::info!("processed: {}", event.window().to_string());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::event_tx().send(border_manager::Notification)?;
|
||||
tracing::info!(
|
||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||
reaped_orphans.0,
|
||||
reaped_orphans.1,
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,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());
|
||||
|
||||
@@ -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;
|
||||
@@ -236,9 +235,6 @@ impl Stackbar {
|
||||
}
|
||||
|
||||
ReleaseDC(self.hwnd(), hdc);
|
||||
DeleteObject(hpen);
|
||||
DeleteObject(hbrush);
|
||||
DeleteObject(hfont);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -229,7 +229,7 @@ impl From<&Monitor> for MonitorConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
/// The `komorebi.json` static configuration file reference for `v0.1.26`
|
||||
/// 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")]
|
||||
@@ -322,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
|
||||
@@ -497,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 {
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::fmt::Write as _;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use komorebi_core::config_generation::IdWithIdentifier;
|
||||
use komorebi_core::config_generation::MatchingRule;
|
||||
@@ -269,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> {
|
||||
|
||||
@@ -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,7 +47,6 @@ 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;
|
||||
@@ -124,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,
|
||||
@@ -167,7 +164,6 @@ impl Default for GlobalState {
|
||||
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),
|
||||
)),
|
||||
@@ -1065,14 +1061,14 @@ impl WindowManager {
|
||||
|
||||
target_monitor.add_container(container, workspace_idx)?;
|
||||
|
||||
if let Some(workspace_idx) = workspace_idx {
|
||||
target_monitor.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
|
||||
target_monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
target_monitor.update_focused_workspace(offset)?;
|
||||
|
||||
if follow {
|
||||
if let Some(workspace_idx) = workspace_idx {
|
||||
target_monitor.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
|
||||
target_monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
target_monitor.update_focused_workspace(offset)?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
|
||||
@@ -1406,67 +1402,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() - 1);
|
||||
while workspace.focused_container_idx() > 0 {
|
||||
workspace.move_window_to_container(0)?;
|
||||
workspace.focus_container(workspace.containers().len() - 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()?;
|
||||
@@ -1503,15 +1438,7 @@ impl WindowManager {
|
||||
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)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -80,19 +80,8 @@ pub extern "system" fn win_event_hook(
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ impl Workspace {
|
||||
layout.bottom -= total_height;
|
||||
}
|
||||
|
||||
window.set_position(layout, false)?;
|
||||
window.set_position(&layout, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,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() {
|
||||
@@ -453,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()))
|
||||
}
|
||||
|
||||
@@ -690,10 +660,6 @@ impl Workspace {
|
||||
self.set_monocle_container_restore_idx(None);
|
||||
}
|
||||
|
||||
for c in self.containers() {
|
||||
c.restore();
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic-no-console"
|
||||
version = "0.1.27-dev.0"
|
||||
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"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.27-dev.0"
|
||||
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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![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::Local;
|
||||
use std::fs::File;
|
||||
@@ -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,13 +653,13 @@ struct FocusFollowsMouse {
|
||||
boolean_state: BooleanState,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct Border {
|
||||
#[clap(value_enum)]
|
||||
boolean_state: BooleanState,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Parser, AhkFunction)]
|
||||
struct BorderColour {
|
||||
#[clap(value_enum, short, long, default_value = "single")]
|
||||
window_kind: WindowKind,
|
||||
@@ -661,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
|
||||
@@ -696,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,
|
||||
@@ -753,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,
|
||||
@@ -761,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)]
|
||||
@@ -796,7 +806,7 @@ struct Opts {
|
||||
subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Parser, AhkLibrary)]
|
||||
enum SubCommand {
|
||||
#[clap(hide = true)]
|
||||
Docgen,
|
||||
@@ -818,8 +828,6 @@ 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,
|
||||
/// Query the current window manager state
|
||||
@@ -876,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")]
|
||||
@@ -1172,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")]
|
||||
@@ -1455,6 +1461,35 @@ 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 = Local::now().format("%Y-%m-%d").to_string();
|
||||
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
|
||||
@@ -1895,7 +1930,7 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
Start-Process '{ahk}' -ArgumentList '{config}' -WindowStyle hidden
|
||||
Start-Process '{ahk}' '{config}' -WindowStyle hidden
|
||||
"#,
|
||||
config = config_ahk.display()
|
||||
);
|
||||
@@ -1917,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 {
|
||||
@@ -2020,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()?)?;
|
||||
}
|
||||
@@ -2120,9 +2130,6 @@ 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()?);
|
||||
}
|
||||
|
||||
@@ -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,6 +67,7 @@ nav:
|
||||
- common-workflows/mouse-follows-focus.md
|
||||
- common-workflows/custom-layouts.md
|
||||
- common-workflows/dynamic-layout-switching.md
|
||||
- common-workflows/autohotkey.md
|
||||
- Release notes:
|
||||
- release/v0-1-22.md
|
||||
- Configuration reference: https://komorebi.lgug2z.com/schema
|
||||
@@ -80,7 +80,6 @@ nav:
|
||||
- cli/whkdrc.md
|
||||
- cli/state.md
|
||||
- cli/global-state.md
|
||||
- cli/gui.md
|
||||
- cli/visible-windows.md
|
||||
- cli/query.md
|
||||
- cli/subscribe-socket.md
|
||||
@@ -125,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
|
||||
@@ -142,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
|
||||
|
||||
10
schema.json
10
schema.json
@@ -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.26`",
|
||||
"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",
|
||||
|
||||
BIN
wix/License.rtf
BIN
wix/License.rtf
Binary file not shown.
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user