Compare commits

..

2 Commits

Author SHA1 Message Date
LGUG2Z
8ceed09c20 fix(wm): dynamically reserve monitor ring space
This commit makes a small change to dynamically keep reserving space in
the VecDeque that backs Ring<Monitor> until an index preference can be
contained within the current length.
2024-05-23 16:58:54 -07:00
LGUG2Z
1712042dda chore(dev): begin v0.1.27-dev 2024-05-23 16:47:26 -07:00
29 changed files with 448 additions and 310 deletions

3
.gitignore vendored
View File

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

13
Cargo.lock generated
View File

@@ -1116,6 +1116,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "derive-ahk"
version = "0.1.1"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -2391,9 +2400,11 @@ dependencies = [
"chrono",
"clap",
"color-eyre",
"derive-ahk",
"dirs",
"dunce",
"fs-tail",
"heck 0.5.0",
"komorebi-client",
"komorebi-core",
"lazy_static",
@@ -4959,7 +4970,7 @@ checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "win32-display-data"
version = "0.1.0"
source = "git+https://github.com/LGUG2Z/win32-display-data?rev=2a0f7166da154880a1750b91829b1186d9c6a00c#2a0f7166da154880a1750b91829b1186d9c6a00c"
source = "git+https://github.com/LGUG2Z/win32-display-data#2c47b9f1ca1f359ba2481d0b6ea8667ccd9d075c"
dependencies = [
"itertools",
"thiserror",

View File

@@ -2,6 +2,7 @@
resolver = "2"
members = [
"derive-ahk",
"komorebi",
"komorebi-client",
"komorebi-core",

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

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

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

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

View File

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

View File

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

View File

@@ -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
@@ -128,21 +128,3 @@ an offline machine to install.
Once installed, proceed to get the [example configurations](example-configurations.md) (none of the commands for
first-time set up and running komorebi require an internet connection).
## Uninstallation
Before uninstalling, first run `komorebic stop --whkd` to make sure that both
the `komorebi` and `whkd` processes have been stopped.
Then, depending on whether you installed with Scoop or WinGet, run `scoop
uninstall komorebi whkd` or `winget uninstall LGUG2Z.komorebi LGUG2Z.whkd`.
Finally, you can run the following commands in a PowerShell prompt to clean up
files created by the `quickstart` command and any other runtime files:
```powershell
rm $Env:USERPROFILE\komorebi.json
rm $Env:USERPROFILE\applications.yaml
rm $Env:USERPROFILE\.config\whkdrc
rm -r -Force $Env:LOCALAPPDATA\komorebi
```

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
#![warn(clippy::all)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
pub use komorebi::colour::Colour;

View File

@@ -1,5 +1,5 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::use_self)]
use std::path::Path;
use std::path::PathBuf;
@@ -43,8 +43,6 @@ pub enum SocketMessage {
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
StackAll,
UnstackAll,
ResizeWindowEdge(OperationDirection, Sizing),
ResizeWindowAxis(Axis, Sizing),
UnstackWindow,

View File

@@ -1,5 +1,3 @@
#![warn(clippy::all)]
use eframe::egui;
use eframe::egui::color_picker::Alpha;
use eframe::egui::Color32;
@@ -715,7 +713,7 @@ impl eframe::App for KomorebiGui {
.text_edit_singleline(workspace_name)
.lost_focus()
{
workspace.name.clone_from(workspace_name);
workspace.name = workspace_name.clone();
komorebi_client::send_message(
&SocketMessage::WorkspaceName(
monitor_idx,

56
komorebi.sample.ahk Normal file
View File

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

View File

@@ -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 = []

View File

@@ -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);
}

View File

@@ -1,5 +1,3 @@
#![warn(clippy::all)]
pub mod border_manager;
pub mod com;
#[macro_use]

View File

@@ -1,10 +1,9 @@
#![warn(clippy::all)]
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::redundant_pub_crate,
clippy::significant_drop_tightening,
clippy::significant_drop_in_scrutinee,
clippy::doc_markdown
clippy::significant_drop_in_scrutinee
)]
use std::path::PathBuf;

View File

@@ -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;

View File

@@ -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)?;

View File

@@ -27,7 +27,6 @@ use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::CreateFontIndirectW;
use windows::Win32::Graphics::Gdi::CreatePen;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::DeleteObject;
use windows::Win32::Graphics::Gdi::DrawTextW;
use windows::Win32::Graphics::Gdi::GetDC;
use windows::Win32::Graphics::Gdi::Rectangle;
@@ -236,9 +235,6 @@ impl Stackbar {
}
ReleaseDC(self.hwnd(), hdc);
DeleteObject(hpen);
DeleteObject(hbrush);
DeleteObject(hfont);
}
Ok(())

View File

@@ -333,7 +333,7 @@ impl StaticConfig {
let mut display = false;
for aliases in map.values() {
for (_, aliases) in &map {
for a in aliases {
if raw.contains(a) {
display = true;
@@ -497,12 +497,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 {

View File

@@ -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> {

View File

@@ -1065,14 +1065,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 +1406,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 +1442,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)?;
}

View File

@@ -220,7 +220,7 @@ impl WindowsApi {
}
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
Ok(win32_display_data::connected_displays_all()
Ok(win32_display_data::connected_displays()
.flatten()
.map(|d| {
let name = d.device_name.trim_start_matches(r"\\.\").to_string();
@@ -232,7 +232,7 @@ impl WindowsApi {
}
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
'read: for display in win32_display_data::connected_displays_all().flatten() {
'read: for display in win32_display_data::connected_displays().flatten() {
let path = display.device_path.clone();
let mut split: Vec<_> = path.split('#').collect();
split.remove(0);
@@ -276,7 +276,8 @@ impl WindowsApi {
if monitors.elements().is_empty() {
monitors.elements_mut().push_back(m);
} else if let Some(preference) = index_preference {
while *preference > monitors.elements().len() {
let current_len = monitors.elements().len();
while *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();

View File

@@ -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()))
}

View File

@@ -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"

View File

@@ -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,6 +39,8 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use derive_ahk::AhkFunction;
use derive_ahk::AhkLibrary;
use komorebi_client::StaticConfig;
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
use komorebi_core::ApplicationIdentifier;
@@ -99,6 +102,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 +141,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 +181,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 +206,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 +230,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 +262,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 +284,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 +293,7 @@ pub struct ClearWorkspaceLayoutRules {
workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct WorkspaceCustomLayout {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -294,7 +305,7 @@ pub struct WorkspaceCustomLayout {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct NamedWorkspaceCustomLayout {
/// Target workspace name
workspace: String,
@@ -303,7 +314,7 @@ pub struct NamedWorkspaceCustomLayout {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct WorkspaceLayoutRule {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -318,7 +329,7 @@ pub struct WorkspaceLayoutRule {
layout: DefaultLayout,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct NamedWorkspaceLayoutRule {
/// Target workspace name
workspace: String,
@@ -330,7 +341,7 @@ pub struct NamedWorkspaceLayoutRule {
layout: DefaultLayout,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct WorkspaceCustomLayoutRule {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -345,7 +356,7 @@ pub struct WorkspaceCustomLayoutRule {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct NamedWorkspaceCustomLayoutRule {
/// Target workspace name
workspace: String,
@@ -357,7 +368,7 @@ pub struct NamedWorkspaceCustomLayoutRule {
path: PathBuf,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct Resize {
#[clap(value_enum)]
edge: OperationDirection,
@@ -365,7 +376,7 @@ struct Resize {
sizing: Sizing,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct ResizeAxis {
#[clap(value_enum)]
axis: Axis,
@@ -373,13 +384,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 +402,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 +414,7 @@ struct GlobalWorkAreaOffset {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct MonitorWorkAreaOffset {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -417,7 +428,7 @@ struct MonitorWorkAreaOffset {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct MonitorIndexPreference {
/// Preferred monitor index (zero-indexed)
index_preference: usize,
@@ -431,7 +442,7 @@ struct MonitorIndexPreference {
bottom: i32,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct DisplayIndexPreference {
/// Preferred monitor index (zero-indexed)
index_preference: usize,
@@ -439,7 +450,7 @@ struct DisplayIndexPreference {
display: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct EnsureWorkspaces {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -447,7 +458,7 @@ struct EnsureWorkspaces {
workspace_count: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct EnsureNamedWorkspaces {
/// Monitor index (zero-indexed)
monitor: usize,
@@ -455,7 +466,7 @@ struct EnsureNamedWorkspaces {
names: Vec<String>,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct FocusMonitorWorkspace {
/// Target monitor index (zero-indexed)
target_monitor: usize,
@@ -463,7 +474,7 @@ struct FocusMonitorWorkspace {
target_workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
pub struct SendToMonitorWorkspace {
/// Target monitor index (zero-indexed)
target_monitor: usize,
@@ -471,7 +482,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 +494,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 +512,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 +534,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 +555,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 +575,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 +596,7 @@ gen_application_target_subcommand_args! {
RemoveTitleBar,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct InitialWorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -597,7 +608,7 @@ struct InitialWorkspaceRule {
workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct InitialNamedWorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -607,7 +618,7 @@ struct InitialNamedWorkspaceRule {
workspace: String,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct WorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -619,7 +630,7 @@ struct WorkspaceRule {
workspace: usize,
}
#[derive(Parser)]
#[derive(Parser, AhkFunction)]
struct NamedWorkspaceRule {
#[clap(value_enum)]
identifier: ApplicationIdentifier,
@@ -629,13 +640,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 +654,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 +672,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 +707,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 +764,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 +772,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 +807,7 @@ struct Opts {
subcmd: SubCommand,
}
#[derive(Parser)]
#[derive(Parser, AhkLibrary)]
enum SubCommand {
#[clap(hide = true)]
Docgen,
@@ -876,10 +887,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 +1179,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 +1464,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 +1933,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()
);
@@ -2020,15 +2058,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()?)?;
}

View File

@@ -57,7 +57,6 @@ nav:
- Troubleshooting: troubleshooting.md
- Common workflows:
- common-workflows/komorebi-config-home.md
- common-workflows/autohotkey.md
- common-workflows/borders.md
- common-workflows/stackbar.md
- common-workflows/remove-gaps.md
@@ -68,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