Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
c07689de90 refactor(cargo): upgrade to rust edition 2024 2025-02-27 17:12:49 -08:00
98 changed files with 2182 additions and 7139 deletions

View File

@@ -18,14 +18,6 @@ on:
workflow_dispatch:
jobs:
cargo-deny:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: EmbarkStudios/cargo-deny-action@v2
build:
strategy:
fail-fast: true
@@ -55,7 +47,7 @@ jobs:
key: ${{ matrix.platform.target }}
- run: cargo +nightly fmt --check
- run: cargo clippy
- run: cargo test
- run: cargo test --package komorebi --test compat
- uses: houseabsolute/actions-rust-cross@v1
with:
command: "build"

914
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@ members = [
[workspace.dependencies]
clap = { version = "4", features = ["derive", "wrap_help"] }
chrono-tz = "0.10"
chrono = "0.4"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
@@ -35,16 +34,16 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1"
sysinfo = "0.33"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "93949750b1f123fb79827ba4d66ffcab68055654" }
windows-numerics = { version = "0.2" }
windows-implement = { version = "0.60" }
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
windows-numerics = { version = "0.1" }
windows-implement = { version = "0.59" }
windows-interface = { version = "0.59" }
windows-core = { version = "0.61" }
windows-core = { version = "0.60" }
shadow-rs = "1"
which = "7"
[workspace.dependencies.windows]
version = "0.61"
version = "0.60"
features = [
"Foundation_Numerics",
"Win32_Devices",
@@ -72,4 +71,17 @@ features = [
"Win32_System_WindowsProgramming",
"Media",
"Media_Control"
]
]
[profile.dev-jeezy]
inherits = "dev"
debug = false
opt-level = 1
[profile.dev-jeezy.package."*"]
opt-level = 3
[profile.release-jeezy]
inherits = "release"
incremental = true
codegen-units = 256

View File

@@ -7,9 +7,9 @@ Tiling Window Management for Windows.
<img alt="Tech for Palestine" src="https://badge.techforpalestine.org/default">
</a>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LGUG2Z/komorebi/.github/workflows/windows.yaml">
<img alt="GitHub" src="https://img.shields.io/github/license/LGUG2Z/komorebi">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/LGUG2Z/komorebi/total">
<img alt="GitHub commits since latest release (by date) for a branch" src="https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest">
<img alt="Active Individual Commercial Use Licenses" src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Flgug2z-ecstaticmagentacheetah.web.val.run&query=%24.&label=active%20individual%20commercial%20use%20licenses&cacheSeconds=3600&link=https%3A%2F%2Flgug2z.com%2Fsoftware%2Fkomorebi">
<a href="https://discord.gg/mGkn66PHkx">
<img alt="Discord" src="https://img.shields.io/discord/898554690126630914">
</a>
@@ -71,9 +71,6 @@ showcases the many awesome projects that exist in the _komorebi_ ecosystem.
## Licensing for Personal Use
`komorebi` is [educational source
software](https://lgug2z.com/articles/educational-source-software/).
`komorebi` is licensed under the [Komorebi 1.0.0
license](https://github.com/LGUG2Z/komorebi-license), which is a fork of the
[PolyForm Strict 1.0.0
@@ -102,8 +99,7 @@ me on GitHub.
[GitHub Sponsors is enabled for this
project](https://github.com/sponsors/LGUG2Z). Sponsors can claim custom roles on
the Discord server, get shout outs at the end of _komorebi_-related videos on
YouTube, gain the ability to submit feature requests on the issue tracker, and
receive releases of komorebi with "easter eggs" on physical media.
YouTube, and gain the ability to submit feature requests on the issue tracker.
If you would like to tip or sponsor the project but are unable to use GitHub
Sponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z), or
@@ -393,7 +389,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
```rust
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.35"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.34"}
use anyhow::Result;
use komorebi_client::Notification;

View File

@@ -1,96 +0,0 @@
[graph]
targets = [
"x86_64-pc-windows-msvc",
"i686-pc-windows-msvc",
"aarch64-pc-windows-msvc",
]
all-features = false
no-default-features = false
[output]
feature-depth = 1
[advisories]
ignore = [
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" }
]
[licenses]
allow = [
"0BSD",
"Apache-2.0",
"Artistic-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"ISC",
"MIT",
"MIT-0",
"MPL-2.0",
"OFL-1.1",
"Ubuntu-font-1.0",
"Unicode-3.0",
"Zlib",
"LicenseRef-Komorebi-1.0"
]
confidence-threshold = 0.8
[[licenses.clarify]]
crate = "komorebi"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-client"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebic"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebic-no-console"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-themes"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-gui"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-bar"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "base16-egui-themes"
expression = "MIT"
license-files = []
[bans]
multiple-versions = "allow"
wildcards = "allow"
highlight = "all"
workspace-default-features = "allow"
external-default-features = "allow"
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [
"https://github.com/LGUG2Z/base16-egui-themes",
"https://github.com/LGUG2Z/catppuccin-egui",
"https://github.com/LGUG2Z/windows-icons",
"https://github.com/LGUG2Z/win32-display-data",
]

View File

@@ -1,771 +0,0 @@
{
"licenses": [
[
"0BSD",
[
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"win32-display-data 0.1.0 git+https://github.com/LGUG2Z/win32-display-data?rev=55cebdebfbd68dbd14945a1ba90f6b05b7be2893"
]
],
[
"Apache-2.0",
[
"ab_glyph 0.2.29 registry+https://github.com/rust-lang/crates.io-index",
"ab_glyph_rasterizer 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_winit 0.23.1 registry+https://github.com/rust-lang/crates.io-index",
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.7 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.97 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.4.1 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"autocfg 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
"backtrace 0.3.71 registry+https://github.com/rust-lang/crates.io-index",
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.16 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.40 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.4 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.3 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.4.2 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.14 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"dpi 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"dyn-clone 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-winit 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"either 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"filetime 0.2.25 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"glutin 0.32.2 registry+https://github.com/rust-lang/crates.io-index",
"glutin_egl_sys 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"glutin_wgl_sys 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"half 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.61 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.5 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"imgref 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"immutable-chunkmap 2.0.6 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"is_terminal_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.32 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"khronos_api 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.0+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.21 registry+https://github.com/rust-lang/crates.io-index",
"litrs 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.26 registry+https://github.com/rust-lang/crates.io-index",
"miette 7.5.0 registry+https://github.com/rust-lang/crates.io-index",
"miette-derive 7.5.0 registry+https://github.com/rust-lang/crates.io-index",
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"ntapi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
"owned_ttf_parser 0.25.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.10 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
"png 0.17.16 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error-attr2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.94 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.39 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.11.1 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.12 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rustversion 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_json 1.0.140 registry+https://github.com/rust-lang/crates.io-index",
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"socket2 0.5.8 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"supports-hyperlinks 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
"supports-unicode 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.99 registry+https://github.com/rust-lang/crates.io-index",
"sync_wrapper 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.17.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.8 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.37 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.18.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"tzdb 0.7.2 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"unicode-linebreak 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"uom 0.36.0 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.4 registry+https://github.com/rust-lang/crates.io-index",
"utf16_iter 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"winit 0.30.9 registry+https://github.com/rust-lang/crates.io-index",
"wmi 0.15.1 registry+https://github.com/rust-lang/crates.io-index",
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Artistic-2.0",
[
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"BSD-2-Clause",
[
"av1-grain 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"rav1e 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"v_frame 0.3.8 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"BSD-3-Clause",
[
"alloc-no-stdlib 2.0.4 registry+https://github.com/rust-lang/crates.io-index",
"alloc-stdlib 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"avif-serialize 0.8.3 registry+https://github.com/rust-lang/crates.io-index",
"brotli 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"exr 1.73.0 registry+https://github.com/rust-lang/crates.io-index",
"lebe 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"ravif 0.11.11 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"BSL-1.0",
[
"clipboard-win 5.4.0 registry+https://github.com/rust-lang/crates.io-index",
"error-code 3.3.1 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"CC0-1.0",
[
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"imgref 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"notify 6.1.1 registry+https://github.com/rust-lang/crates.io-index",
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"ISC",
[
"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"libloading 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"starship-battery 0.10.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MIT",
[
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index",
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"aligned-vec 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.7 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.97 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.4.1 registry+https://github.com/rust-lang/crates.io-index",
"arg_enum_proc_macro 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"autocfg 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
"backtrace 0.3.71 registry+https://github.com/rust-lang/crates.io-index",
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"brotli 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
"built 0.7.7 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"bytes 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"catppuccin-egui 5.3.1 git+https://github.com/LGUG2Z/catppuccin-egui?rev=bdaff30959512c4f7ee7304117076a48633d777f",
"cc 1.2.16 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"cfg_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.40 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.4 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.3 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"color_quant 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.4.2 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.14 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"dyn-clone 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-winit 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"either 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"filetime 0.2.25 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"fs-tail 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"getset 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"glutin-winit 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"h2 0.4.8 registry+https://github.com/rust-lang/crates.io-index",
"half 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"http-body 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"http-body-util 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper 1.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hyper-util 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.61 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.5 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"immutable-chunkmap 2.0.6 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"is_terminal_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.32 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.0+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.21 registry+https://github.com/rust-lang/crates.io-index",
"litrs 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.26 registry+https://github.com/rust-lang/crates.io-index",
"loop9 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"matchers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"maybe-rayon 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"memchr 2.7.4 registry+https://github.com/rust-lang/crates.io-index",
"memoffset 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
"mime_guess2 2.0.5 registry+https://github.com/rust-lang/crates.io-index",
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"mio 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"nanoid 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"netdev 0.32.0 registry+https://github.com/rust-lang/crates.io-index",
"new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index",
"noop_proc_macro 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"ntapi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"nu-ansi-term 0.46.0 registry+https://github.com/rust-lang/crates.io-index",
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
"os_info 3.10.0 registry+https://github.com/rust-lang/crates.io-index",
"overload 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"owo-colors 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"owo-colors 4.2.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.10 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
"png 0.17.16 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error-attr2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.94 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.39 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"random_word 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.11.1 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.12 registry+https://github.com/rust-lang/crates.io-index",
"rgb 0.8.50 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rustversion 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"schannel 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"schemars 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
"schemars_derive 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_json 1.0.140 registry+https://github.com/rust-lang/crates.io-index",
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"simd-adler32 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"slab 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"socket2 0.5.8 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index",
"strum 0.27.1 registry+https://github.com/rust-lang/crates.io-index",
"strum_macros 0.27.1 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.99 registry+https://github.com/rust-lang/crates.io-index",
"synstructure 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.17.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"textwrap 0.16.2 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.8 registry+https://github.com/rust-lang/crates.io-index",
"tiff 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.37 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"tokio 1.43.0 registry+https://github.com/rust-lang/crates.io-index",
"tokio-native-tls 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"tokio-util 0.7.13 registry+https://github.com/rust-lang/crates.io-index",
"tower 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"tower-layer 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"tracing 0.1.41 registry+https://github.com/rust-lang/crates.io-index",
"tracing-appender 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"tracing-attributes 0.1.28 registry+https://github.com/rust-lang/crates.io-index",
"tracing-core 0.1.33 registry+https://github.com/rust-lang/crates.io-index",
"tracing-error 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"tracing-log 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"tracing-subscriber 0.3.19 registry+https://github.com/rust-lang/crates.io-index",
"try-lock 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.18.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"uds_windows 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"unsafe-libyaml 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"uom 0.36.0 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.4 registry+https://github.com/rust-lang/crates.io-index",
"utf16_iter 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
"want 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"which 7.0.2 registry+https://github.com/rust-lang/crates.io-index",
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
"winapi-util 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5",
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index",
"winsafe 0.0.19 registry+https://github.com/rust-lang/crates.io-index",
"wmi 0.15.1 registry+https://github.com/rust-lang/crates.io-index",
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"xml-rs 0.8.25 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MIT-0",
[
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.1 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MPL-2.0",
[
"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"OFL-1.1",
[
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Ubuntu-font-1.0",
[
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Unicode-3.0",
[
"icu_collections 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locid 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locid_transform 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locid_transform_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties 1.5.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider_macros 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"litemap 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"tinystr 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"writeable 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
"yoke 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"yoke-derive 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"zerofrom 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"zerofrom-derive 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"zerovec 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"zerovec-derive 0.10.3 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Unlicense",
[
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"memchr 2.7.4 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
"winapi-util 0.1.9 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Zlib",
[
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"const_format 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
"const_format_proc_macros 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"foldhash 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
]
]
]
}

View File

@@ -1,16 +0,0 @@
# cycle-empty-workspace
```
Focus the next empty workspace in the given cycle direction (if one exists)
Usage: komorebic.exe cycle-empty-workspace <CYCLE_DIRECTION>
Arguments:
<CYCLE_DIRECTION>
[possible values: previous, next]
Options:
-h, --help
Print help
```

View File

@@ -1,412 +0,0 @@
# Multi-Monitor Setup
You can set up komorebi to work with multiple monitors. To do so, first you start by setting up multiple monitor
configurations on your `komorebi.json` config file.
If you've used the [`komorebic quickstart`](../cli/quickstart.md) command you'll already have a `komorebi.json` config
file with one monitor config setup. Open this file and look for the `"monitors":` line, you should find something like
this:
```json
{
"monitors": [
{
"workspaces": [
{
"name": "I",
"layout": "BSP"
},
{
"name": "II",
"layout": "VerticalStack"
},
{
"name": "III",
"layout": "HorizontalStack"
},
{
"name": "IV",
"layout": "UltrawideVerticalStack"
},
{
"name": "V",
"layout": "Rows"
},
{
"name": "VI",
"layout": "Grid"
},
{
"name": "VII",
"layout": "RightMainVerticalStack"
}
]
}
]
}
```
For this example we will remove some workspaces to simplify the config so it is easier to look at, but feel free to
set up as many workspaces per monitor as you'd like. Here is the same configuration with only 3 workspaces.
```json
{
"monitors": [
{
"workspaces": [
{
"name": "I",
"layout": "BSP"
},
{
"name": "II",
"layout": "VerticalStack"
},
{
"name": "III",
"layout": "HorizontalStack"
}
]
}
]
}
```
Let's add another monitor:
```json
{
"monitors": [
// monitor 1, index 0
{
"workspaces": [
{
"name": "I",
"layout": "BSP"
},
{
"name": "II",
"layout": "VerticalStack"
},
{
"name": "III",
"layout": "HorizontalStack"
}
]
},
// monitor 2, index 1
{
"workspaces": [
{
"name": "1",
"layout": "BSP"
},
{
"name": "2",
"layout": "VerticalStack"
},
{
"name": "3",
"layout": "HorizontalStack"
}
]
}
]
}
```
Now have two monitor configurations. We have the first monitor configuration, which is index 0 (*usually
on programming languages the first item of a list starts with index 0*), this configuration has 3 workspaces with names
"I", "II" and "III". Then the 2nd monitor configuration, which is index 1, also has 3 workspaces with names "1", "2",
and "3" (you should always give unique names to your workspaces).
Now if you start komorebi with two monitors connected, the main monitor will use the configuration with index 0 and the
secondary monitor will use the configuration with index 1.
---
Let's say you have more monitors, or you want to make sure that a certain configuration is always applied to a certain
monitor. For this you will want to use the `display_index_preferences`.
Open up a terminal and type the following command: [ `komorebic monitor-info`](../cli/monitor-information.md). This
command will give you the information about your connected monitors, you want to look up the `serial_number_id`. You
should get something like this:
```
komorebic monitor-info
[
{
"id": 6620935,
"name": "DISPLAY1",
"device": "BOE0A1C",
"device_id": "BOE0A1C-5&a2bea0b&0&UID512",
"serial_number_id": "0",
"size": {
"left": 0,
"top": 0,
"right": 1920,
"bottom": 1080
}
},
{
"id": 181932057,
"name": "DISPLAY2",
"device": "VSC8C31",
"device_id": "VSC8C31-5&18560b1f&0&UID4356",
"serial_number_id": "UEP174021562",
"size": {
"left": 0,
"top": -1080,
"right": 1920,
"bottom": 1080
}
}
]
```
In this case the setup is a laptop with a secondary monitor connected. You'll need to figure out which monitor is which,
usually the display name's number should be similar to the numbers you can find on
`Windows Settings -> System -> Display`.
If you have trouble with this step you can always jump on Discord and ask for help (create a `Support` thread).
Once you know which monitor is which, you want to look up their `serial_number_id` to use that on
`display_index_preferences`, you can also use the `device_id`, it accepts both however there have been reported cases
where the `device_id` changes after a restart while the `serial_number_id` doesn't.
So with the example above, we want the laptop to always use the configuration index 0 and the other monitor to use
configuration index 1, so we map the configuration index number to the monitor `serial_number_id`/`device_id` like this:
```json
{
"display_index_preferences": {
"0": "0",
"1": "UEP174021562"
}
}
```
Again you could also have used the `device_id` like this:
```json
{
"display_index_preferences": {
"0": "BOE0A1C-5&a2bea0b&0&UID512",
"1": "VSC8C31-5&18560b1f&0&UID4356"
}
}
```
You should add this `display_index_preferences` option to your `komorebi.json` file. If you find that something is
not working as expected you can try to use the command `komorebic check`.
> [!IMPORTANT]
>
> **When using multiple monitors it is recommended to always set the `display_index_preferences`. If you don't you might
get some undefined behaviour.**
---
If you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so
using the `bar_configurations` array in your `komorebi.json` configuration file. You can refer to the
[multiple-bar-instances](multiple-bar-instances.md) documentation.
In this case it is important to use `display_index_preferences`, because if you don't, and you have 3 or more monitors,
disconnecting and reconnecting monitors may result in the bars for the monitors getting shifted around.
Consider this setup with 3 monitors (A, B and C):
```json
// HOME_MONITOR_1_BAR.json
{
"monitor_index": 0
//...
}
```
```json
// HOME_MONITOR_2_BAR.json
{
"monitor_index": 1
//...
}
```
```json
// WORK_MONITOR_1_BAR.json
{
"monitor_index": 2
//...
}
```
```json
{
"display_index_preferences": {
"0": "MONITOR_1_ID",
"1": "MONITOR_2_ID",
"2": "MONITOR_3_ID"
},
"bar_configurations": [
// this bar uses "monitor_index": 0,
"path/to/bar_config_1.json",
// this bar uses "monitor_index": 1,
"path/to/bar_config_2.json",
// this bar uses "monitor_index": 2,
"path/to/bar_config_3.json"
]
}
```
Komorebi uses an internal map to keep track of monitor to config indices, this map is called `monitor_usr_idx_map` it is
an internal variable to komorebi that you don't need to do anything with, but you can see it with the [
`komorebic state`](../cli/state.md) command (in case you need to debug something).
At first, komorebi will load all monitors and set the internal index map (`monitor_usr_idx_map`) as:
```json
{
// This is monitor A
"0": 0,
// This is monitor B
"1": 1,
// This is monitor C
"2": 2
}
```
Which kind of seems unnecessary, but imagine that then you disconnect monitor B (or it goes to sleep). Then komorebi
will only have 2 monitors with index 0 and 1, so the above map will be updated to this:
```jsonc
[
"0": 0, // This is monitor A
"2": 1, // This is now monitor C, because monitor B disconnected
]
```
So now the bar intended to be for monitor B, which was looking for index "1" on that map, doesn't see it and knows it
should be disabled. And the bar for monitor C looks at that map and knows that it's index "2" now maps to index 1 so it
uses that index internally to get all the correct values about the monitor.
If you didn't have the `display_index_preferences` set, then when you disconnected monitor B, komorebi wouldn't know
how to map the indices and would use default behaviour which would result in a map like this:
```json
{
// This is monitor A
"0": 0,
// This is monitor C, because monitor B disconnected. However the bars will think it is monitor B because it has index "1"
"1": 1
}
```
# Multiple Monitors on different machines
You can use the same `komorebi.json` to configure two different setups and then synchronize your config across machines.
However, if you do this it is important to be aware of a few things.
Firstly, using `display_index_preferences` is required in this case.
You will need to get the `serial_number_id` or `device_id` of all the monitors of all your setups. With that information
you would then set your config like this:
```json
{
"display_index_preferences": {
"0": "HOME_MONITOR_1_ID",
"1": "HOME_MONITOR_2_ID",
"2": "WORK_MONITOR_1_ID",
"3": "WORK_MONITOR_2_ID"
},
"monitors": [
// HOME_MONITOR_1
{
"workspaces": [
// ...
]
},
// HOME_MONITOR_2
{
"workspaces": [
// ...
]
},
// WORK_MONITOR_1
{
"workspaces": [
// ...
]
},
// WORK_MONITOR_2
{
"workspaces": [
// ...
]
}
]
}
```
> [!NOTE]
>
> *You can't use the same config on two different monitors, you have to make a duplicated config for each monitor!*
Then on the bar configs you need to set the bar's monitor index like this:
```json
// HOME_MONITOR_1_BAR.json
{
"monitor_index": 0
//...
}
```
```json
// HOME_MONITOR_2_BAR.json
{
"monitor_index": 1
//...
}
```
```json
// WORK_MONITOR_1_BAR.json
{
"monitor_index": 2
//...
}
```
```json
// WORK_MONITOR_2_BAR.json
{
"monitor_index": 3
//...
}
```
Although you will only ever have 2 monitors connected at any one time, and they'll always have index 0 and 1, the
above config will still work on both physical configurations.
This is because komorebi will apply the appropriate config to the loaded monitors and will create a map of the user
index (the index defined in the user config) to the actual monitor index, and the bar will use that map to know if it
should be enabled, and where it should be drawn.
### Things to keep in mind
* If you are using a laptop connected to one monitor at work and a different one at home, the work monitor and the home
monitor are considered different monitors by komorebi
* When you disconnect from work, komorebi will keep the work monitor cached
* You can still use a laptop alone without any monitor and if you need a window that was on the other monitor you can
press the taskbar icon or use `alt + tab` to bring it to focus and that window will now be part of the laptop monitor
* If you then reconnect the work monitor, the cached version will be applied with all its windows (except any window(s)
you might have moved to another monitor)
* If however, instead of reconnecting the work monitor, you connect the home monitor, then the work monitor will still
remain cached, and komorebi will load the home monitor from the cache (if it exists)
* Sometimes when you disconnect/reconnect a monitor the event might be missed by komorebi, meaning that Windows will
show you both monitors but komorebi won't know about the existence of one of them
* If you notice this type of weird behaviour, always run the [
`komorebic monitor-info`](../cli/monitor-information.md)
command and validate if one of the monitors is missing
* To fix this you can try disconnecting and reconnecting the monitor again, or restarting komorebi

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.bar.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.bar.json",
"monitor": 0,
"font_family": "JetBrains Mono",
"theme": {

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",

View File

@@ -1,46 +0,0 @@
# Focusing Windows
Windows can be focused in a direction (left, down, up, right) using the [`komorebic focus`](../cli/focus.md) command.
```
# example showing how you might bind this command
alt + h : komorebic focus left
alt + j : komorebic focus down
alt + k : komorebic focus up
alt + l : komorebic focus right
```
Windows can be focused in a cycle direction (previous, next) using the [`komorebic cycle-focus`](../cli/cycle-focus.md)
command.
```
# example showing you might bind this command
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
```
It is possible to attempt to focus the first window, on any workspace, matching an exe using the [
`komorebic eager-focus`](../cli/eager-focus.md) command.
```
# example showing how you might bind this command
win + 1 : komorebic eager-focus firefox.exe
```
The window at the largest tile can be focused using the [`komorebic promote-focus`](../cli/promote-focus.md) command.
```
# example showing how you might bind this command
alt + return : komorebic promote-focus
```
The behaviour when attempting to call `komorebic focus` when at the left or right edge of a monitor is determined by
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
When set to `Workspace`, the next workspace on the same monitor will be focused.
When set to `Monitor`, the focused workspace on the next monitor in the given direction will be focused.

View File

@@ -1,59 +0,0 @@
# Focusing Workspaces
Workspaces on the focused monitor can be focused by their index using the [
`komorebic focus-workspace`](../cli/focus-workspace.md) command.
If this command is called with an index for a workspace which does not exist, that workspace, and all workspace indexes
required to get to that workspace, will be created.
```
# example showing how you might bind this command
alt + 1 : komorebic focus-workspace 0
alt + 2 : komorebic focus-workspace 1
alt + 3 : komorebic focus-workspace 2
```
Workspaces on the focused monitor can be focused in a cycle direction (previous, next) using the [
`komorebic cycle-workspace`](../cli/cycle-workspace.md) command.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-workspace previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-workspace next # oem_6 is ]
```
Workspaces on other monitors can be focused by both the monitor index and the workspace index using the [
`komorebic focus-monitor-workspace`](../cli/focus-monitor-workspace.md) command.
```
# example showing how you might bind this command
alt + 1 : komorebic focus-monitor-workspace 0 0
alt + 2 : komorebic focus-monitor-workspace 0 1
alt + 3 : komorebic focus-monitor-workspace 1 0
```
Workspaces on any monitor can be focused by their name (given that all workspace names across all monitors are unique)
using the [`komorebic focus-named-workspace`](../cli/focus-named-workspace.md) command.
```
# example showing how you might bind this command
alt + c : komorebic focus-named-workspace coding
```
Workspaces on all monitors can be set to the same index (emulating single workspaces which span across all monitors)
using the [`komorebic focus-workspaces`](../cli/focus-workspaces.md) command.
```
# example showing how you might bind this command
alt + 1 : komorebic focus-workspaces 0
alt + 2 : komorebic focus-workspaces 1
alt + 3 : komorebic focus-workspaces 2
```
The last focused workspace on the focused monitor can be re-focused using the [
`komorebic focus-last-workspace`](../cli/focus-last-workspace.md) command.

View File

@@ -1,59 +0,0 @@
# Moving Windows Across Workspaces
Windows can be moved to another workspace on the focused monitor using the [
`komorebic move-to-workspace`](../cli/move-to-workspace.md) command. This command will also move your focus to the
target workspace.
```
# example showing how you might bind this command
alt + shift + 1 : komorebic move-to-workspace 0
alt + shift + 2 : komorebic move-to-workspace 1
alt + shift + 3 : komorebic move-to-workspace 2
```
Windows can be sent to another workspace on the focused monitor using the [
`komorebic send-to-workspace`](../cli/send-to-workspace.md) command. This command will keep your focus on the origin
workspace.
```
# example showing how you might bind this command
alt + shift + 1 : komorebic send-to-workspace 0
alt + shift + 2 : komorebic send-to-workspace 1
alt + shift + 3 : komorebic send-to-workspace 2
```
Windows can be moved to another workspace on the focused monitor in a cycle direction (previous, next) using the [
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will also move your focus
to the target workspace.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-move-to-workspace previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-move-to-workspace next # oem_6 is ]
```
Windows can be sent to another workspace on the focused monitor in a cycle direction (previous, next) using the [
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will keep your focus on
the origin workspace.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-send-to-workspace previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-send-to-workspace next # oem_6 is ]
```
Windows can be moved or sent to the focused workspace on a another monitor using the [
`komorebic move-to-monitor`](../cli/move-to-monitor.md) and [`komorebic send-to-monitor`](../cli/send-to-monitor.md)
commands.
Windows can be moved or sent to the focused workspace on a monitor in a cycle direction (previous, next) using the [
`komorebic cycle-move-to-monitor`](../cli/cycle-move-to-monitor.md) and [
`komorebic cycle-send-to-monitor`](../cli/cycle-send-to-monitor.md) commands.
Windows can be moved or sent to a named workspace on any monitor (given that all workspace names across all monitors are
unique) using the [`komorebic move-to-named-workspace`](../cli/move-to-named-workspace.md) and [
`komorebic send-to-named-workspace`](../cli/send-to-named-workspace.md) commands

View File

@@ -1,50 +0,0 @@
# Moving Windows
Windows can be moved in a direction (left, down, up, right) using the [`komorebic move`](../cli/move.md) command.
```
# example showing how you might bind this command
alt + shift + h : komorebic move left
alt + shift + j : komorebic move down
alt + shift + k : komorebic move up
alt + shift + l : komorebic move right
```
Windows can be moved in a cycle direction (previous, next) using the [`komorebic cycle-move`](../cli/cycle-move.md)
command.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-move previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-move next # oem_6 is ]
```
The focused window can be moved to the largest tile using the [`komorebic promote`](../cli/promote.md) command.
```
# example showing how you might bind this command
alt + shift + return : komorebic promote
```
The behaviour when attempting to call `komorebic move` when at the left or right edge of a monitor is determined by
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
When set to `Workspace`, the focused window will be moved to the next workspace on the focused monitor in the given
direction
When set to `Monitor`, the focused window will be moved to the focused workspace on the next monitor in the given
direction.
The behaviour when calling `komorebic move` with `cross_boundary_behaviour` set to `Monitor` can be further refined with
the [`cross_monitor_move_behaviour`](https://komorebi.lgug2z.com/schema#cross_monitor_move_behaviour) configuration
option.
When set to `Swap`, the focused window will be swapped with the window at the corresponding edge of the adjacent monitor
When set to `Insert`, the focused window will be inserted into the focused workspace on the adjacent monitor.
When set to `NoOp`, the focused window will not be moved across a monitor boundary, though focusing across monitor
boundaries will continue to function.

View File

@@ -1,52 +0,0 @@
# Stacking Windows
Windows can be stacked in a direction (left, down, up, right) using the [`komorebic stack`](../cli/stack.md) command.
```
# example showing how you might bind this command
alt + left : komorebic stack left
alt + down : komorebic stack down
alt + up : komorebic stack up
alt + right : komorebic stack right
```
Windows can be popped from a stack using the [`komorebic unstack`](../cli/unstack.md) command.
```
# example showing how you might bind this command
alt + oem_1 : komorebic unstack # oem_1 is ;
```
Windows in a stack can be focused in a cycle direction (previous, next) using the [
`komorebic cycle-stack`](../cli/cycle-stack.md) command.
```
# example showing how you might bind this command
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
```
Windows in a stack can have their positions in the stack moved in a cycle direction (previous, next) using the [
`komorebic cycle-stack-index`](../cli/cycle-stack-index.md) command.
```
# example showing how you might bind this command
alt + shift + oem_4 : komorebic cycle-stack-index previous # oem_4 is [
alt + shift + oem_6 : komorebic cycle-stack-index next # oem_6 is ]
```
Windows in a stack can be focused by their index in the stack using the [
`komorebic focus-stack-window`](../cli/focus-stack-window.md) command.
All windows on the focused workspace can be combined into a single stack using the [
`komorebic stack-all`](../cli/stack-all.md) command.
All windows in a focused stack can be popped using the [`komorebic unstack-all`](../cli/unstack-all.md) command.
It is possible to tell the window manager to stack the next opened window on top of the currently focused window by
using the [
`komorebic toggle-workspace-window-container-behaviour`](../cli/toggle-workspace-window-container-behaviour.md) command.

View File

@@ -19,31 +19,22 @@ install-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
install-target target:
cargo +stable install --path {{ target }} --locked --no-default-features
install-targets-with-jsonschema *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just install-target-with-jsonschema $_ }
install-target-with-jsonschema target:
cargo +stable install --path {{ target }} --locked
install:
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
install-with-jsonschema:
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
build-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
build-target target:
cargo +stable build --package {{ target }} --locked --release --no-default-features
cargo +stable build --package {{ target }} --locked --profile release-jeezy
build:
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
copy-target target:
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
cp .\target\release-jeezy\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
copy-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just copy-target $_ }
@@ -55,7 +46,7 @@ copy:
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
run target:
cargo +stable run --bin {{ target }} --locked --no-default-features
cargo +stable run --bin {{ target }} --locked
warn target $RUST_LOG="warn":
just run {{ target }}
@@ -70,7 +61,7 @@ trace target $RUST_LOG="trace":
just run {{ target }}
deadlock $RUST_LOG="trace":
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
cargo +stable run --bin komorebi --locked --features deadlock_detection
docgen:
cargo run --package komorebic -- docgen

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-bar"
version = "0.1.36"
edition = "2021"
version = "0.1.35"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -9,7 +9,6 @@ edition = "2021"
komorebi-client = { path = "../komorebi-client" }
komorebi-themes = { path = "../komorebi-themes" }
chrono-tz = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
color-eyre = { workspace = true }
@@ -21,14 +20,13 @@ egui-phosphor = "0.9"
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
lazy_static = { workspace = true }
netdev = "0.33"
netdev = "0.32"
num = "0.4"
num-derive = "0.4"
num-traits = "0.2"
random_word = { version = "0.5", features = ["en"] }
random_word = { version = "0.4", features = ["en"] }
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true, optional = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
starship-battery = "0.10"
@@ -37,8 +35,4 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
windows = { workspace = true }
windows-core = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
[features]
default = ["schemars"]
schemars = ["dep:schemars"]
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }

View File

@@ -1,25 +1,25 @@
use crate::config::get_individual_spacing;
use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING;
use crate::KomorebiEvent;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_LEFT;
use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crate::config::KomobarConfig;
use crate::config::KomobarTheme;
use crate::config::MonitorConfigOrIndex;
use crate::config::Position;
use crate::config::PositionConfig;
use crate::config::get_individual_spacing;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
use crate::render::Color32Ext;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::render::RenderExt;
use crate::widgets::komorebi::Komorebi;
use crate::widgets::komorebi::KomorebiNotificationState;
use crate::widgets::widget::BarWidget;
use crate::widgets::widget::WidgetConfig;
use crate::KomorebiEvent;
use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_LEFT;
use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crossbeam_channel::Receiver;
use crossbeam_channel::TryRecvError;
use eframe::egui::Align;
@@ -47,16 +47,16 @@ use komorebi_client::KomorebiTheme;
use komorebi_client::MonitorNotification;
use komorebi_client::NotificationEvent;
use komorebi_client::SocketMessage;
use komorebi_themes::catppuccin_egui;
use komorebi_themes::Base16Value;
use komorebi_themes::Catppuccin;
use komorebi_themes::CatppuccinValue;
use komorebi_themes::catppuccin_egui;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
pub struct Komobar {
pub hwnd: Option<isize>,
@@ -373,10 +373,14 @@ impl Komobar {
}
}
} else if self.komorebi_notification_state.is_some() && !self.disabled {
tracing::warn!("couldn't find the monitor index of this bar! Disabling the bar until the monitor connects...");
tracing::warn!(
"couldn't find the monitor index of this bar! Disabling the bar until the monitor connects..."
);
self.disabled = true;
} else {
tracing::warn!("couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi.");
tracing::warn!(
"couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi."
);
self.disabled = true;
}
@@ -419,7 +423,9 @@ impl Komobar {
end.x -= margin.left + margin.right;
if end.y == 0.0 {
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
tracing::warn!(
"position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default"
)
}
self.size_rect = komorebi_client::Rect {

View File

@@ -1,24 +1,24 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use starship_battery::units::ratio::percent;
use starship_battery::Manager;
use starship_battery::State;
use starship_battery::units::ratio::percent;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct BatteryConfig {
/// Enable the Battery widget
pub enable: bool,

View File

@@ -1,19 +1,19 @@
use crate::render::Grouping;
use crate::widgets::widget::WidgetConfig;
use crate::DEFAULT_PADDING;
use crate::render::Grouping;
use crate::widget::WidgetConfig;
use eframe::egui::Pos2;
use eframe::egui::TextBuffer;
use eframe::egui::Vec2;
use komorebi_client::KomorebiTheme;
use komorebi_client::Rect;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.bar.json` configuration file reference for `v0.1.36`
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.bar.json` configuration file reference for `v0.1.35`
pub struct KomobarConfig {
/// Bar height (default: 50)
pub height: Option<f32>,
@@ -115,7 +115,9 @@ impl KomobarConfig {
}
if display {
println!("\nYour bar configuration file contains some options that have been renamed or deprecated:\n");
println!(
"\nYour bar 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) {
@@ -136,8 +138,7 @@ impl KomobarConfig {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct PositionConfig {
/// The desired starting position of the bar (0,0 = top left of the screen)
#[serde(alias = "position")]
@@ -147,15 +148,13 @@ pub struct PositionConfig {
pub end: Option<Position>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct FrameConfig {
/// Margin inside the painted frame
pub inner_margin: Position,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum MonitorConfigOrIndex {
/// The monitor index where you want the bar to show
@@ -164,8 +163,7 @@ pub enum MonitorConfigOrIndex {
MonitorConfig(MonitorConfig),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct MonitorConfig {
/// Komorebi monitor index of the monitor on which to render the bar
pub index: usize,
@@ -176,8 +174,7 @@ pub struct MonitorConfig {
pub type Padding = SpacingKind;
pub type Margin = SpacingKind;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
// WARNING: To any developer messing with this code in the future: The order here matters!
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
@@ -228,23 +225,20 @@ impl SpacingKind {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GroupedSpacingConfig {
pub vertical: Option<GroupedSpacingOptions>,
pub horizontal: Option<GroupedSpacingOptions>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum GroupedSpacingOptions {
Symmetrical(f32),
Split(f32, f32),
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct IndividualSpacingConfig {
pub top: f32,
pub bottom: f32,
@@ -346,8 +340,7 @@ impl KomobarConfig {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct Position {
/// X coordinate
pub x: f32,
@@ -373,8 +366,7 @@ impl From<Position> for Pos2 {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "palette")]
pub enum KomobarTheme {
/// A theme from catppuccin-egui
@@ -410,8 +402,7 @@ impl From<KomorebiTheme> for KomobarTheme {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum LabelPrefix {
/// Show no prefix
None,
@@ -423,8 +414,7 @@ pub enum LabelPrefix {
IconAndText,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum DisplayFormat {
/// Show only icon
Icon,
@@ -440,8 +430,7 @@ pub enum DisplayFormat {
macro_rules! extend_enum {
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq)]
pub enum $new_enum {
// Add new variants
$(
@@ -473,12 +462,12 @@ extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
#[cfg(test)]
mod tests {
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum OriginalDisplayFormat {
/// Show None Of The Things
NoneOfTheThings,

View File

@@ -1,13 +1,14 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
@@ -16,8 +17,7 @@ use std::time::Instant;
use sysinfo::RefreshKind;
use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct CpuConfig {
/// Enable the Cpu widget
pub enable: bool,

View File

@@ -1,24 +1,20 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use chrono::Local;
use chrono_tz::Tz;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::WidgetText;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
use std::time::Instant;
/// Custom format with additive modifiers for integer format specifiers
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct CustomModifiers {
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
format: String,
@@ -42,7 +38,7 @@ impl CustomModifiers {
}
// get the strftime value of modifier
let formatted_modifier = Local::now().format(modifier).to_string();
let formatted_modifier = chrono::Local::now().format(modifier).to_string();
// find the gotten value in the original output
if let Some(pos) = modified_output.find(&formatted_modifier) {
@@ -59,8 +55,7 @@ impl CustomModifiers {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct DateConfig {
/// Enable the Date widget
pub enable: bool,
@@ -68,41 +63,19 @@ pub struct DateConfig {
pub format: DateFormat,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
///
/// Use a custom format to display additional information, i.e.:
/// ```json
/// {
/// "Date": {
/// "enable": true,
/// "format": { "Custom": "%D %Z (Tokyo)" },
/// "timezone": "Asia/Tokyo"
/// }
///}
/// ```
pub timezone: Option<String>,
}
impl From<DateConfig> for Date {
fn from(value: DateConfig) -> Self {
let data_refresh_interval = 1;
Self {
enable: value.enable,
format: value.format,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
timezone: value.timezone,
data_refresh_interval,
last_state: String::new(),
last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum DateFormat {
/// Month/Date/Year format (09/08/24)
MonthDateYear,
@@ -146,46 +119,19 @@ pub struct Date {
pub enable: bool,
pub format: DateFormat,
label_prefix: LabelPrefix,
timezone: Option<String>,
data_refresh_interval: u64,
last_state: String,
last_updated: Instant,
}
impl Date {
fn output(&mut self) -> String {
let mut output = self.last_state.clone();
let now = Instant::now();
let formatted = chrono::Local::now()
.format(&self.format.fmt_string())
.to_string();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
let formatted = match &self.timezone {
Some(timezone) => match timezone.parse::<Tz>() {
Ok(tz) => Local::now()
.with_timezone(&tz)
.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
Err(_) => format!("Invalid timezone: {}", timezone),
},
None => Local::now()
.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
};
// if custom modifiers are used, apply them
output = match &self.format {
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
_ => formatted,
};
self.last_state.clone_from(&output);
self.last_updated = now;
// if custom modifiers are used, apply them
match &self.format {
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
_ => formatted,
}
output
}
}

View File

@@ -1,13 +1,14 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::WidgetText;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
@@ -22,8 +23,7 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
const ERROR_TEXT: &str = "Error";
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KeyboardConfig {
/// Enable the Input widget
pub enable: bool,

View File

@@ -1,17 +1,16 @@
use crate::ICON_CACHE;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX;
use crate::bar::apply_theme;
use crate::config::DisplayFormat;
use crate::config::KomobarTheme;
use crate::config::WorkspacesDisplayFormat;
use crate::komorebi_layout::KomorebiLayout;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widgets::komorebi_layout::KomorebiLayout;
use crate::widgets::widget::BarWidget;
use crate::ICON_CACHE;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX;
use eframe::egui::vec2;
use crate::widget::BarWidget;
use eframe::egui::Color32;
use eframe::egui::ColorImage;
use eframe::egui::Context;
@@ -28,6 +27,7 @@ use eframe::egui::TextureHandle;
use eframe::egui::TextureOptions;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::vec2;
use image::RgbaImage;
use komorebi_client::Container;
use komorebi_client::NotificationEvent;
@@ -37,6 +37,7 @@ use komorebi_client::SocketMessage;
use komorebi_client::Window;
use komorebi_client::Workspace;
use komorebi_client::WorkspaceLayer;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
@@ -46,8 +47,7 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::Ordering;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiConfig {
/// Configure the Workspaces widget
pub workspaces: Option<KomorebiWorkspacesConfig>,
@@ -61,8 +61,7 @@ pub struct KomorebiConfig {
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiWorkspacesConfig {
/// Enable the Komorebi Workspaces widget
pub enable: bool,
@@ -72,8 +71,7 @@ pub struct KomorebiWorkspacesConfig {
pub display: Option<WorkspacesDisplayFormat>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiLayoutConfig {
/// Enable the Komorebi Layout widget
pub enable: bool,
@@ -83,19 +81,13 @@ pub struct KomorebiLayoutConfig {
pub display: Option<DisplayFormat>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiWorkspaceLayerConfig {
/// Enable the Komorebi Workspace Layer widget
pub enable: bool,
/// Display format of the current layer
pub display: Option<DisplayFormat>,
/// Show the widget event if the layer is Tiling
pub show_when_tiling: Option<bool>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiFocusedWindowConfig {
/// Enable the Komorebi Focused Window widget
pub enable: bool,
@@ -105,8 +97,7 @@ pub struct KomorebiFocusedWindowConfig {
pub display: Option<DisplayFormat>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiConfigurationSwitcherConfig {
/// Enable the Komorebi Configurations widget
pub enable: bool,
@@ -321,107 +312,29 @@ impl BarWidget for Komorebi {
.map(|(_, _, layer)| layer);
if let Some(layer) = layer {
if (layer_config.show_when_tiling.unwrap_or_default()
&& matches!(layer, WorkspaceLayer::Tiling))
|| matches!(layer, WorkspaceLayer::Floating)
{
let display_format = layer_config.display.unwrap_or(DisplayFormat::Text);
let size = Vec2::splat(config.icon_font_id.size);
config.apply_on_widget(false, ui, |ui| {
let layer_frame = SelectableFrame::new(false)
.show(ui, |ui| {
if display_format != DisplayFormat::Text {
if matches!(layer, WorkspaceLayer::Tiling) {
let (response, painter) =
ui.allocate_painter(size, Sense::hover());
let color = ui.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let corner =
CornerRadius::same((rect.width() * 0.1) as u8);
rect = rect.shrink(stroke.width);
// tiling
let mut rect_left = response.rect;
rect_left.set_width(rect.width() * 0.48);
rect_left.set_height(rect.height() * 0.98);
let mut rect_right = rect_left;
rect_left = rect_left.translate(Vec2::new(
rect.width() * 0.01 + stroke.width,
rect.width() * 0.01 + stroke.width,
));
rect_right = rect_right.translate(Vec2::new(
rect.width() * 0.51 + stroke.width,
rect.width() * 0.01 + stroke.width,
));
painter.rect_filled(rect_left, corner, color);
painter.rect_stroke(
rect_right,
corner,
stroke,
StrokeKind::Outside,
);
} else {
let (response, painter) =
ui.allocate_painter(size, Sense::hover());
let color = ui.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let corner =
CornerRadius::same((rect.width() * 0.1) as u8);
rect = rect.shrink(stroke.width);
// floating
let mut rect_left = response.rect;
rect_left.set_width(rect.width() * 0.65);
rect_left.set_height(rect.height() * 0.65);
let mut rect_right = rect_left;
rect_left = rect_left.translate(Vec2::new(
rect.width() * 0.01 + stroke.width,
rect.width() * 0.01 + stroke.width,
));
rect_right = rect_right.translate(Vec2::new(
rect.width() * 0.34 + stroke.width,
rect.width() * 0.34 + stroke.width,
));
painter.rect_filled(rect_left, corner, color);
painter.rect_stroke(
rect_right,
corner,
stroke,
StrokeKind::Outside,
);
}
}
if display_format != DisplayFormat::Icon {
ui.add(Label::new(layer.to_string()).selectable(false));
}
})
.on_hover_text(layer.to_string());
if layer_frame.clicked()
&& komorebi_client::send_batch([
SocketMessage::FocusMonitorAtCursor,
SocketMessage::MouseFollowsFocus(false),
SocketMessage::ToggleWorkspaceLayer,
SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
),
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n\
MouseFollowsFocus(false),
ToggleWorkspaceLayer,
MouseFollowsFocus({})",
let name = layer.to_string();
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
.show(ui, |ui| ui.add(Label::new(name).selectable(false)))
.clicked()
&& komorebi_client::send_batch([
SocketMessage::MouseFollowsFocus(false),
SocketMessage::ToggleWorkspaceLayer,
SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
);
}
});
}
),
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n\
MouseFollowsFocus(false),
ToggleWorkspaceLayer,
MouseFollowsFocus({})",
komorebi_notification_state.mouse_follows_focus,
);
}
});
}
}
}
@@ -686,9 +599,13 @@ impl KomorebiNotificationState {
grouping,
render_config,
);
tracing::info!("removed theme from updated komorebi.json and applied default theme");
tracing::info!(
"removed theme from updated komorebi.json and applied default theme"
);
} else {
tracing::warn!("theme was removed from updated komorebi.json but there was no default theme to apply");
tracing::warn!(
"theme was removed from updated komorebi.json but there was no default theme to apply"
);
}
}
}

View File

@@ -1,8 +1,7 @@
use crate::config::DisplayFormat;
use crate::komorebi::KomorebiLayoutConfig;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::komorebi::KomorebiLayoutConfig;
use eframe::egui::vec2;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::FontId;
@@ -13,17 +12,18 @@ use eframe::egui::Stroke;
use eframe::egui::StrokeKind;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::vec2;
use komorebi_client::SocketMessage;
use serde::de::Error;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::de::Error;
use serde_json::from_str;
use std::fmt::Display;
use std::fmt::Formatter;
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq)]
#[serde(untagged)]
pub enum KomorebiLayout {
Default(komorebi_client::DefaultLayout),
@@ -105,22 +105,12 @@ impl KomorebiLayout {
}
}
KomorebiLayout::Monocle => {
if komorebi_client::send_batch([
SocketMessage::FocusMonitorAtCursor,
SocketMessage::ToggleMonocle,
])
.is_err()
{
if komorebi_client::send_message(&SocketMessage::ToggleMonocle).is_err() {
tracing::error!("could not send message to komorebi: ToggleMonocle");
}
}
KomorebiLayout::Floating => {
if komorebi_client::send_batch([
SocketMessage::FocusMonitorAtCursor,
SocketMessage::ToggleTiling,
])
.is_err()
{
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err() {
tracing::error!("could not send message to komorebi: ToggleTiling");
}
}

View File

@@ -1,9 +1,21 @@
mod bar;
mod battery;
mod config;
mod cpu;
mod date;
mod keyboard;
mod komorebi;
mod komorebi_layout;
mod media;
mod memory;
mod network;
mod render;
mod selected_frame;
mod storage;
mod time;
mod ui;
mod widgets;
mod update;
mod widget;
use crate::bar::Komobar;
use crate::config::KomobarConfig;
@@ -18,23 +30,24 @@ use hotwatch::Hotwatch;
use image::RgbaImage;
use komorebi_client::SocketMessage;
use komorebi_client::SubscribeOptions;
use schemars::r#gen::SchemaSettings;
use std::collections::HashMap;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::LazyLock;
use std::sync::Mutex;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::LazyLock;
use std::sync::Mutex;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows_core::BOOL;
@@ -112,16 +125,15 @@ fn main() -> color_eyre::Result<()> {
let opts: Opts = Opts::parse();
#[cfg(feature = "schemars")]
if opts.schema {
let settings = schemars::gen::SchemaSettings::default().with(|s| {
let settings = SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<KomobarConfig>();
let r#gen = settings.into_generator();
let socket_message = r#gen.into_root_schema_for::<KomobarConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
@@ -137,13 +149,15 @@ fn main() -> color_eyre::Result<()> {
}
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1");
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
}
color_eyre::install()?;
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LOG", "info") };
}
tracing::subscriber::set_global_default(
@@ -337,7 +351,7 @@ fn main() -> color_eyre::Result<()> {
let ctx_komorebi = cc.egui_ctx.clone();
std::thread::spawn(move || {
let subscriber_name = format!("komorebi-bar-{}", random_word::get(random_word::Lang::En));
let subscriber_name = format!("komorebi-bar-{}", random_word::r#gen(random_word::Lang::En));
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
filter_state_changes: true,

View File

@@ -1,22 +1,22 @@
use crate::MAX_LABEL_WIDTH;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widgets::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::Ordering;
use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct MediaConfig {
/// Enable the Media widget
pub enable: bool,

View File

@@ -1,13 +1,14 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
@@ -16,8 +17,7 @@ use std::time::Instant;
use sysinfo::RefreshKind;
use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct MemoryConfig {
/// Enable the Memory widget
pub enable: bool,

View File

@@ -1,14 +1,15 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use num_derive::FromPrimitive;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
@@ -17,8 +18,7 @@ use std::time::Duration;
use std::time::Instant;
use sysinfo::Networks;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct NetworkConfig {
/// Enable the Network widget
pub enable: bool,

View File

@@ -11,16 +11,16 @@ use eframe::egui::Margin;
use eframe::egui::Shadow;
use eframe::egui::TextStyle;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind")]
pub enum Grouping {
/// No grouping is applied
@@ -339,8 +339,7 @@ impl RenderConfig {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GroupingConfig {
/// Styles for the grouping
pub style: Option<GroupingStyle>,
@@ -350,8 +349,7 @@ pub struct GroupingConfig {
pub rounding: Option<RoundingConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum GroupingStyle {
#[serde(alias = "CtByte")]
Default,
@@ -371,8 +369,7 @@ pub enum GroupingStyle {
DefaultWithGlowB0O1S2,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum RoundingConfig {
/// All 4 corners are the same

View File

@@ -1,10 +1,8 @@
use eframe::egui::Color32;
use eframe::egui::CursorIcon;
use eframe::egui::Frame;
use eframe::egui::Margin;
use eframe::egui::Response;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::Ui;
/// Same as SelectableLabel, but supports all content
@@ -25,34 +23,26 @@ impl SelectableFrame {
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
if ui.is_rect_visible(response.rect) {
// take into account the stroke width
let inner_margin = Margin::symmetric(
ui.style().spacing.button_padding.x as i8 - 1,
ui.style().spacing.button_padding.y as i8 - 1,
ui.style().spacing.button_padding.x as i8,
ui.style().spacing.button_padding.y as i8,
);
// since the stroke is drawn inside the frame, we always reserve space for it
if response.hovered() || response.highlighted() || response.has_focus() {
if selected
|| response.hovered()
|| response.highlighted()
|| response.has_focus()
{
let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_stroke.color))
.corner_radius(visuals.corner_radius)
.fill(visuals.bg_fill)
.inner_margin(inner_margin)
.show(ui, add_contents);
} else if selected {
let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_fill))
.stroke(visuals.bg_stroke)
.corner_radius(visuals.corner_radius)
.fill(visuals.bg_fill)
.inner_margin(inner_margin)
.show(ui, add_contents);
} else {
Frame::NONE
.stroke(Stroke::new(1.0, Color32::TRANSPARENT))
.inner_margin(inner_margin)
.show(ui, add_contents);
}

View File

@@ -1,13 +1,14 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
@@ -15,8 +16,7 @@ use std::time::Duration;
use std::time::Instant;
use sysinfo::Disks;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct StorageConfig {
/// Enable the Storage widget
pub enable: bool,

View File

@@ -2,11 +2,7 @@ use crate::bar::Alignment;
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use chrono::Local;
use chrono::NaiveTime;
use chrono_tz::Tz;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
@@ -16,62 +12,13 @@ use eframe::egui::Stroke;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::text::LayoutJob;
use eframe::epaint::StrokeKind;
use lazy_static::lazy_static;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
use std::time::Instant;
lazy_static! {
static ref TIME_RANGES: Vec<(&'static str, NaiveTime)> = {
vec![
(
egui_phosphor::regular::MOON,
NaiveTime::from_hms_opt(0, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::ALARM,
NaiveTime::from_hms_opt(6, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::BREAD,
NaiveTime::from_hms_opt(6, 1, 0).expect("invalid"),
),
(
egui_phosphor::regular::BARBELL,
NaiveTime::from_hms_opt(6, 30, 0).expect("invalid"),
),
(
egui_phosphor::regular::COFFEE,
NaiveTime::from_hms_opt(8, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::CLOCK,
NaiveTime::from_hms_opt(8, 30, 0).expect("invalid"),
),
(
egui_phosphor::regular::HAMBURGER,
NaiveTime::from_hms_opt(12, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::CLOCK_AFTERNOON,
NaiveTime::from_hms_opt(12, 30, 0).expect("invalid"),
),
(
egui_phosphor::regular::FORK_KNIFE,
NaiveTime::from_hms_opt(18, 0, 0).expect("invalid"),
),
(
egui_phosphor::regular::MOON_STARS,
NaiveTime::from_hms_opt(18, 30, 0).expect("invalid"),
),
]
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct TimeConfig {
/// Enable the Time widget
pub enable: bool,
@@ -79,46 +26,19 @@ pub struct TimeConfig {
pub format: TimeFormat,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
///
/// Use a custom format to display additional information, i.e.:
/// ```json
/// {
/// "Time": {
/// "enable": true,
/// "format": { "Custom": "%T %Z (Tokyo)" },
/// "timezone": "Asia/Tokyo"
/// }
///}
/// ```
pub timezone: Option<String>,
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)
pub changing_icon: Option<bool>,
}
impl From<TimeConfig> for Time {
fn from(value: TimeConfig) -> Self {
// using 1 second made the widget look "less accurate" and lagging (especially having multiple with seconds).
// This is still better than getting an update every frame
let data_refresh_interval = 500;
Self {
enable: value.enable,
format: value.format,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
timezone: value.timezone,
changing_icon: value.changing_icon.unwrap_or_default(),
data_refresh_interval_millis: data_refresh_interval,
last_state: TimeOutput::new(),
last_updated: Instant::now()
.checked_sub(Duration::from_millis(data_refresh_interval))
.unwrap(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum TimeFormat {
/// Twelve-hour format (with seconds)
TwelveHour,
@@ -162,94 +82,20 @@ impl TimeFormat {
}
}
#[derive(Clone, Debug)]
struct TimeOutput {
label: String,
icon: String,
}
impl TimeOutput {
fn new() -> Self {
Self {
label: String::new(),
icon: String::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Time {
pub enable: bool,
pub format: TimeFormat,
label_prefix: LabelPrefix,
timezone: Option<String>,
changing_icon: bool,
data_refresh_interval_millis: u64,
last_state: TimeOutput,
last_updated: Instant,
}
impl Time {
fn output(&mut self) -> TimeOutput {
let mut output = self.last_state.clone();
let now = Instant::now();
if now.duration_since(self.last_updated)
> Duration::from_millis(self.data_refresh_interval_millis)
{
let (formatted, current_time) = match &self.timezone {
Some(timezone) => match timezone.parse::<Tz>() {
Ok(tz) => {
let dt = Local::now().with_timezone(&tz);
(
dt.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
Some(dt.time()),
)
}
Err(_) => (format!("Invalid timezone: {:?}", timezone), None),
},
None => {
let dt = Local::now();
(
dt.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string(),
Some(dt.time()),
)
}
};
if current_time.is_none() {
return TimeOutput {
label: formatted,
icon: egui_phosphor::regular::WARNING_CIRCLE.to_string(),
};
}
let current_range = match &self.changing_icon {
true => TIME_RANGES
.iter()
.rev()
.find(|&(_, start)| current_time.unwrap() > *start)
.cloned(),
false => None,
}
.unwrap_or((egui_phosphor::regular::CLOCK, NaiveTime::default()));
output = TimeOutput {
label: formatted,
icon: current_range.0.to_string(),
};
self.last_state.clone_from(&output);
self.last_updated = now;
}
output
fn output(&mut self) -> String {
chrono::Local::now()
.format(&self.format.fmt_string())
.to_string()
.trim()
.to_string()
}
fn paint_binary_circle(
@@ -263,37 +109,36 @@ impl Time {
let full_height = size;
let height = full_height / 4.0;
let width = height;
let offset = height / 2.0 - height / 8.0;
let (response, painter) =
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
let color = ctx.style().visuals.text_color();
let c = response.rect.center();
let r = height / 2.0 - 0.5;
if number == 1 || number == 3 || number == 5 || number == 7 || number == 9 {
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r, color);
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r, color);
} else {
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r / 2.5, color);
}
if number == 2 || number == 3 || number == 6 || number == 7 {
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r, color);
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r, color);
} else {
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r / 2.5, color);
}
if number == 4 || number == 5 || number == 6 || number == 7 {
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r, color);
} else if max_power > 2 {
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r / 2.5, color);
}
if number == 8 || number == 9 {
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r, color);
} else if max_power > 3 {
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r / 2.5, color);
}
}
@@ -308,10 +153,9 @@ impl Time {
let full_height = size;
let height = full_height / 4.0;
let width = height * 1.5;
let offset = height / 2.0 - height / 8.0;
let (response, painter) =
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
let color = ctx.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
@@ -329,25 +173,22 @@ impl Time {
};
if max_power == 2 {
let mut rect = response
.rect
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
let mut rect = response.rect.shrink(stroke.width);
rect.set_height(rect.height() - height * 2.0);
rect = rect.translate(Vec2::new(0.0, height * 2.0 + offset));
rect = rect.translate(Vec2::new(0.0, height * 2.0));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
} else if max_power == 3 {
let mut rect = response
.rect
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
let mut rect = response.rect.shrink(stroke.width);
rect.set_height(rect.height() - height);
rect = rect.translate(Vec2::new(0.0, height + offset));
rect = rect.translate(Vec2::new(0.0, height));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
} else {
let mut rect = response
.rect
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
rect = rect.translate(Vec2::new(0.0, 0.0 + offset));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
painter.rect_stroke(
response.rect.shrink(stroke.width),
round_all,
stroke,
StrokeKind::Outside,
);
}
let mut rect_bin = response.rect;
@@ -356,7 +197,7 @@ impl Time {
if number == 1 || number == 5 || number == 9 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, height * 3.0 + offset * 2.0)),
rect_bin.translate(Vec2::new(0.0, height * 3.0)),
round_bottom,
color,
);
@@ -364,7 +205,7 @@ impl Time {
if number == 2 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
if max_power == 2 {
round_top
} else {
@@ -376,7 +217,7 @@ impl Time {
if number == 3 {
rect_bin.set_height(height * 2.0);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
round_bottom,
color,
);
@@ -384,7 +225,7 @@ impl Time {
if number == 4 || number == 5 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
if max_power == 3 {
round_top
} else {
@@ -396,7 +237,7 @@ impl Time {
if number == 6 {
rect_bin.set_height(height * 2.0);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
if max_power == 3 {
round_top
} else {
@@ -408,7 +249,7 @@ impl Time {
if number == 7 {
rect_bin.set_height(height * 3.0);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, height + offset * 2.0)),
rect_bin.translate(Vec2::new(0.0, height)),
if max_power == 3 {
round_all
} else {
@@ -419,11 +260,7 @@ impl Time {
}
if number == 8 || number == 9 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, 0.0 + offset * 2.0)),
round_top,
color,
);
painter.rect_filled(rect_bin.translate(Vec2::new(0.0, 0.0)), round_top, color);
}
}
}
@@ -432,13 +269,15 @@ impl BarWidget for Time {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let mut output = self.output();
if !output.label.is_empty() {
let use_binary_circle = output.label.starts_with('c');
let use_binary_rectangle = output.label.starts_with('r');
if !output.is_empty() {
let use_binary_circle = output.starts_with('c');
let use_binary_rectangle = output.starts_with('r');
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => output.icon,
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::CLOCK.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
@@ -447,12 +286,12 @@ impl BarWidget for Time {
);
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
output.label.insert_str(0, "TIME: ");
output.insert_str(0, "TIME: ");
}
if !use_binary_circle && !use_binary_rectangle {
layout_job.append(
&output.label,
&output,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
@@ -475,9 +314,9 @@ impl BarWidget for Time {
if use_binary_circle || use_binary_rectangle {
let ordered_output = if is_reversed {
output.label.chars().rev().collect()
output.chars().rev().collect()
} else {
output.label
output
};
for (section_index, section) in

View File

@@ -1,21 +1,21 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct UpdateConfig {
/// Enable the Update widget
pub enable: bool,

View File

@@ -1,28 +1,29 @@
use crate::battery::Battery;
use crate::battery::BatteryConfig;
use crate::cpu::Cpu;
use crate::cpu::CpuConfig;
use crate::date::Date;
use crate::date::DateConfig;
use crate::keyboard::Keyboard;
use crate::keyboard::KeyboardConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiConfig;
use crate::media::Media;
use crate::media::MediaConfig;
use crate::memory::Memory;
use crate::memory::MemoryConfig;
use crate::network::Network;
use crate::network::NetworkConfig;
use crate::render::RenderConfig;
use crate::widgets::battery::Battery;
use crate::widgets::battery::BatteryConfig;
use crate::widgets::cpu::Cpu;
use crate::widgets::cpu::CpuConfig;
use crate::widgets::date::Date;
use crate::widgets::date::DateConfig;
use crate::widgets::keyboard::Keyboard;
use crate::widgets::keyboard::KeyboardConfig;
use crate::widgets::komorebi::Komorebi;
use crate::widgets::komorebi::KomorebiConfig;
use crate::widgets::media::Media;
use crate::widgets::media::MediaConfig;
use crate::widgets::memory::Memory;
use crate::widgets::memory::MemoryConfig;
use crate::widgets::network::Network;
use crate::widgets::network::NetworkConfig;
use crate::widgets::storage::Storage;
use crate::widgets::storage::StorageConfig;
use crate::widgets::time::Time;
use crate::widgets::time::TimeConfig;
use crate::widgets::update::Update;
use crate::widgets::update::UpdateConfig;
use crate::storage::Storage;
use crate::storage::StorageConfig;
use crate::time::Time;
use crate::time::TimeConfig;
use crate::update::Update;
use crate::update::UpdateConfig;
use eframe::egui::Context;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -30,8 +31,7 @@ pub trait BarWidget {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum WidgetConfig {
Battery(BatteryConfig),
Cpu(CpuConfig),

View File

@@ -1,13 +0,0 @@
pub mod battery;
pub mod cpu;
pub mod date;
pub mod keyboard;
pub mod komorebi;
mod komorebi_layout;
pub mod media;
pub mod memory;
pub mod network;
pub mod storage;
pub mod time;
pub mod update;
pub mod widget;

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-client"
version = "0.1.36"
edition = "2021"
version = "0.1.35"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -10,7 +10,3 @@ komorebi = { path = "../komorebi" }
uds_windows = { workspace = true }
serde_json = { workspace = true }
[features]
default = ["schemars"]
schemars = ["komorebi/schemars"]

View File

@@ -1,10 +1,28 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc)]
pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::AnimationsConfig;
pub use komorebi::AspectRatio;
pub use komorebi::BorderColours;
pub use komorebi::CrossBoundaryBehaviour;
pub use komorebi::GlobalState;
pub use komorebi::KomorebiTheme;
pub use komorebi::MonitorConfig;
pub use komorebi::Notification;
pub use komorebi::NotificationEvent;
pub use komorebi::PredefinedAspectRatio;
pub use komorebi::RuleDebug;
pub use komorebi::StackbarConfig;
pub use komorebi::State;
pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig;
pub use komorebi::WindowContainerBehaviour;
pub use komorebi::WindowsApi;
pub use komorebi::WorkspaceConfig;
pub use komorebi::animation::PerAnimationPrefixConfig;
pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::border_manager::BorderInfo;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
pub use komorebi::config_generation::ApplicationConfiguration;
@@ -13,8 +31,6 @@ pub use komorebi::config_generation::IdWithIdentifierAndComment;
pub use komorebi::config_generation::MatchingRule;
pub use komorebi::config_generation::MatchingStrategy;
pub use komorebi::container::Container;
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
pub use komorebi::core::resolve_home_path;
pub use komorebi::core::AnimationStyle;
pub use komorebi::core::ApplicationIdentifier;
pub use komorebi::core::Arrangement;
@@ -43,6 +59,8 @@ pub use komorebi::core::StackbarLabel;
pub use komorebi::core::StackbarMode;
pub use komorebi::core::StateQuery;
pub use komorebi::core::WindowKind;
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
pub use komorebi::core::resolve_home_path;
pub use komorebi::monitor::Monitor;
pub use komorebi::monitor_reconciliator::MonitorNotification;
pub use komorebi::ring::Ring;
@@ -51,26 +69,6 @@ pub use komorebi::window_manager_event::WindowManagerEvent;
pub use komorebi::workspace::Workspace;
pub use komorebi::workspace::WorkspaceGlobals;
pub use komorebi::workspace::WorkspaceLayer;
pub use komorebi::AnimationsConfig;
pub use komorebi::AppSpecificConfigurationPath;
pub use komorebi::AspectRatio;
pub use komorebi::BorderColours;
pub use komorebi::CrossBoundaryBehaviour;
pub use komorebi::GlobalState;
pub use komorebi::KomorebiTheme;
pub use komorebi::MonitorConfig;
pub use komorebi::Notification;
pub use komorebi::NotificationEvent;
pub use komorebi::PredefinedAspectRatio;
pub use komorebi::RuleDebug;
pub use komorebi::StackbarConfig;
pub use komorebi::State;
pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig;
pub use komorebi::WindowContainerBehaviour;
pub use komorebi::WindowsApi;
pub use komorebi::WorkspaceConfig;
use komorebi::DATA_DIR;

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-gui"
version = "0.1.36"
edition = "2021"
version = "0.1.35"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -10,7 +10,7 @@ komorebi-client = { path = "../komorebi-client" }
eframe = { workspace = true }
egui_extras = { workspace = true }
random_word = { version = "0.5", features = ["en"] }
random_word = { version = "0.4", features = ["en"] }
serde_json = { workspace = true }
windows-core = { workspace = true }
windows = { workspace = true }

View File

@@ -1,9 +1,9 @@
#![warn(clippy::all)]
use eframe::egui;
use eframe::egui::color_picker::Alpha;
use eframe::egui::Color32;
use eframe::egui::ViewportBuilder;
use eframe::egui::color_picker::Alpha;
use komorebi_client::BorderStyle;
use komorebi_client::Colour;
use komorebi_client::DefaultLayout;
@@ -101,7 +101,7 @@ impl From<&komorebi_client::Workspace> for WorkspaceConfig {
let name = value
.name()
.to_owned()
.unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());
.unwrap_or_else(|| random_word::r#gen(random_word::Lang::En).to_string());
Self {
layout,
@@ -248,10 +248,11 @@ impl eframe::App for KomorebiGui {
ui.collapsing("Window Rules", |ui| {
let window = Window::from(self.debug_hwnd);
let label = if let (Ok(title), Ok(exe)) = (window.title(), window.exe()) {
format!("{title} ({exe})")
} else {
String::from("Select a Window")
let label = match (window.title(), window.exe()) {
(Ok(title), Ok(exe)) => {
format!("{title} ({exe})")
}
_ => String::from("Select a Window"),
};
if ui.button("Refresh Windows").clicked() {

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-themes"
version = "0.1.36"
edition = "2021"
version = "0.1.35"
edition = "2024"
[dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "96f26c88d83781f234d42222293ec73d23a39ad8" }

View File

@@ -1,9 +1,9 @@
[package]
name = "komorebi"
version = "0.1.36"
version = "0.1.35"
description = "A tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -29,7 +29,7 @@ os_info = "3.10"
parking_lot = "0.12"
paste = { workspace = true }
regex = "1"
schemars = { workspace = true, optional = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
@@ -55,9 +55,6 @@ shadow-rs = { workspace = true }
[dev-dependencies]
reqwest = { version = "0.12", features = ["blocking"] }
uuid = { version = "1", features = ["v4"] }
[features]
default = ["schemars"]
deadlock_detection = ["parking_lot/deadlock_detection"]
schemars = ["dep:schemars"]

View File

@@ -1,5 +1,5 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use super::prefix::AnimationPrefix;

View File

@@ -1,18 +1,19 @@
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use super::RenderDispatcher;
use super::ANIMATION_DURATION_GLOBAL;
use super::ANIMATION_FPS;
use super::ANIMATION_MANAGER;
use super::RenderDispatcher;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct AnimationEngine;
impl AnimationEngine {

View File

@@ -1,5 +1,5 @@
use crate::core::Rect;
use crate::AnimationStyle;
use crate::core::Rect;
use super::style::apply_ease_func;

View File

@@ -4,9 +4,9 @@ use crate::core::animation::AnimationStyle;
use lazy_static::lazy_static;
use prefix::AnimationPrefix;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use parking_lot::Mutex;
@@ -18,12 +18,11 @@ pub mod prefix;
pub mod render_dispatcher;
pub use render_dispatcher::RenderDispatcher;
pub mod style;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(untagged)]
pub enum PerAnimationPrefixConfig<T> {
Prefix(HashMap<AnimationPrefix, T>),

View File

@@ -1,14 +1,24 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Display, EnumString, ValueEnum,
Copy,
Clone,
Debug,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum AnimationPrefix {

View File

@@ -1,35 +1,34 @@
use crate::border_manager::window_kind_colour;
use crate::border_manager::RenderTarget;
use crate::border_manager::WindowKind;
use crate::WINDOWS_11;
use crate::WindowsApi;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::FOCUS_STATE;
use crate::border_manager::RENDER_TARGETS;
use crate::border_manager::RenderTarget;
use crate::border_manager::STYLE;
use crate::border_manager::WindowKind;
use crate::border_manager::window_kind_colour;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::windows_api;
use crate::WindowsApi;
use crate::WINDOWS_11;
use color_eyre::eyre::anyhow;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::LazyLock;
use std::sync::OnceLock;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use windows::Win32::Foundation::FALSE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::TRUE;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
@@ -38,35 +37,34 @@ use windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;
use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;
use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
use windows::Win32::Graphics::Gdi::CreateRectRgn;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::ValidateRect;
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::LoadCursorW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SetCursor;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::BOOL;
use windows_core::PCWSTR;
@@ -116,8 +114,6 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
#[derive(Debug, Clone)]
pub struct Border {
pub hwnd: isize,
pub id: String,
pub monitor_idx: Option<usize>,
pub render_target: OnceLock<RenderTarget>,
pub tracking_hwnd: isize,
pub window_rect: Rect,
@@ -134,8 +130,6 @@ impl From<isize> for Border {
fn from(value: isize) -> Self {
Self {
hwnd: value,
id: String::new(),
monitor_idx: None,
render_target: OnceLock::new(),
tracking_hwnd: 0,
window_rect: Rect::default(),
@@ -155,11 +149,7 @@ impl Border {
HWND(windows_api::as_ptr!(self.hwnd))
}
pub fn create(
id: &str,
tracking_hwnd: isize,
monitor_idx: usize,
) -> color_eyre::Result<Box<Self>> {
pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result<Self> {
let name: Vec<u16> = format!("komoborder-{id}\0").encode_utf16().collect();
let class_name = PCWSTR(name.as_ptr());
@@ -178,12 +168,9 @@ impl Border {
let (border_sender, border_receiver) = mpsc::channel();
let instance = h_module.0 as isize;
let container_id = id.to_owned();
std::thread::spawn(move || -> color_eyre::Result<()> {
let mut border = Self {
hwnd: 0,
id: container_id,
monitor_idx: Some(monitor_idx),
render_target: OnceLock::new(),
tracking_hwnd,
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
@@ -196,15 +183,12 @@ impl Border {
brushes: HashMap::new(),
};
let border_pointer = &raw mut border;
let border_pointer = std::ptr::addr_of!(border);
let hwnd =
WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance, border_pointer)?;
let boxed = unsafe {
(*border_pointer).hwnd = hwnd;
Box::from_raw(border_pointer)
};
border_sender.send(boxed)?;
border.hwnd = hwnd;
border_sender.send(border_pointer as isize)?;
let mut msg: MSG = MSG::default();
@@ -223,7 +207,8 @@ impl Border {
Ok(())
});
let mut border = border_receiver.recv()?;
let border_ref = border_receiver.recv()?;
let border = unsafe { &mut *(border_ref as *mut Border) };
// I have literally no idea, apparently this is to get rid of the black pixels
// around the edges of rounded corners? @lukeyou05 borrowed this from PowerToys
@@ -272,7 +257,6 @@ impl Border {
WindowKind::Monocle,
WindowKind::Unfocused,
WindowKind::Floating,
WindowKind::UnfocusedLocked,
] {
let color = window_kind_colour(window_kind);
let color = D2D1_COLOR_F {
@@ -308,13 +292,17 @@ impl Border {
}
};
Ok(border)
let mut render_targets = RENDER_TARGETS.lock();
render_targets.insert(border.hwnd, RenderTarget(render_target));
Ok(border.clone())
},
Err(error) => Err(error.into()),
}
}
pub fn destroy(&self) -> color_eyre::Result<()> {
let mut render_targets = RENDER_TARGETS.lock();
render_targets.remove(&self.hwnd);
WindowsApi::close_window(self.hwnd)
}
@@ -341,16 +329,6 @@ impl Border {
) -> LRESULT {
unsafe {
match message {
WM_SETCURSOR => match LoadCursorW(None, IDC_ARROW) {
Ok(cursor) => {
SetCursor(Some(cursor));
LRESULT(0)
}
Err(error) => {
tracing::error!("{error}");
LRESULT(1)
}
},
WM_CREATE => {
let mut border_pointer: *mut Border =
GetWindowLongPtrW(window, GWLP_USERDATA) as _;
@@ -383,7 +361,7 @@ impl Border {
return LRESULT(0);
}
let reference_hwnd = (*border_pointer).tracking_hwnd;
let reference_hwnd = lparam.0;
let old_rect = (*border_pointer).window_rect;
let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default();
@@ -497,6 +475,11 @@ impl Border {
});
// Get window kind and color
(*border_pointer).window_kind = FOCUS_STATE
.lock()
.get(&(window.0 as isize))
.copied()
.unwrap_or(WindowKind::Unfocused);
let window_kind = (*border_pointer).window_kind;
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
render_target.BeginDraw();

View File

@@ -1,38 +1,37 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod border;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::WindowKind;
use crate::ring::Ring;
use crate::windows_api;
use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::Colour;
use crate::Rgb;
use crate::WindowManager;
use crate::WindowsApi;
use border::border_hwnds;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::WindowKind;
use crate::ring::Ring;
use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator::ALT_TAB_HWND;
pub use border::Border;
use border::border_hwnds;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::ops::Deref;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
use strum::Display;
use windows::Win32::Foundation::HWND;
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
@@ -48,8 +47,6 @@ lazy_static! {
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
pub static ref UNFOCUSED: AtomicU32 =
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(128, 128, 128))));
pub static ref UNFOCUSED_LOCKED: AtomicU32 =
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(158, 8, 8))));
pub static ref MONOCLE: AtomicU32 =
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153))));
pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66))));
@@ -58,8 +55,11 @@ lazy_static! {
}
lazy_static! {
static ref BORDER_STATE: Mutex<HashMap<String, Box<Border>>> = Mutex::new(HashMap::new());
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, String>> = Mutex::new(HashMap::new());
static ref BORDERS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, Border>> = Mutex::new(HashMap::new());
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
static ref RENDER_TARGETS: Mutex<HashMap<isize, RenderTarget>> = Mutex::new(HashMap::new());
}
#[derive(Debug, Clone)]
@@ -76,18 +76,6 @@ impl Deref for RenderTarget {
pub struct Notification(pub Option<isize>);
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct BorderInfo {
pub border_hwnd: isize,
pub window_kind: WindowKind,
}
impl BorderInfo {
pub fn hwnd(&self) -> HWND {
HWND(windows_api::as_ptr!(self.border_hwnd))
}
}
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
@@ -102,13 +90,8 @@ fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
WINDOWS_BORDERS.lock().get(&hwnd).and_then(|id| {
BORDER_STATE.lock().get(id).map(|b| BorderInfo {
border_hwnd: b.hwnd,
window_kind: b.window_kind,
})
})
pub fn window_border(hwnd: isize) -> Option<Border> {
WINDOWS_BORDERS.lock().get(&hwnd).cloned()
}
pub fn send_notification(hwnd: Option<isize>) {
@@ -124,11 +107,15 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()
);
for (_, border) in borders.drain() {
let _ = destroy_border(border);
for (_, border) in borders.iter() {
let _ = border.destroy();
}
borders.clear();
BORDERS_MONITORS.lock().clear();
WINDOWS_BORDERS.lock().clear();
FOCUS_STATE.lock().clear();
RENDER_TARGETS.lock().clear();
let mut remaining_hwnds = vec![];
@@ -141,7 +128,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
for hwnd in remaining_hwnds {
let _ = destroy_border(Box::new(Border::from(hwnd)));
let _ = Border::from(hwnd).destroy();
}
}
@@ -151,7 +138,6 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
fn window_kind_colour(focus_kind: WindowKind) -> u32 {
match focus_kind {
WindowKind::Unfocused => UNFOCUSED.load(Ordering::Relaxed),
WindowKind::UnfocusedLocked => UNFOCUSED_LOCKED.load(Ordering::Relaxed),
WindowKind::Single => FOCUSED.load(Ordering::Relaxed),
WindowKind::Stack => STACK.load(Ordering::Relaxed),
WindowKind::Monocle => MONOCLE.load(Ordering::Relaxed),
@@ -160,13 +146,15 @@ fn window_kind_colour(focus_kind: WindowKind) -> u32 {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});
@@ -232,11 +220,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let window_kind = if idx != ws.focused_container_idx()
|| monitor_idx != focused_monitor_idx
{
if ws.locked_containers().contains(&idx) {
WindowKind::UnfocusedLocked
} else {
WindowKind::Unfocused
}
WindowKind::Unfocused
} else if c.windows().len() > 1 {
WindowKind::Stack
} else {
@@ -295,20 +279,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// border.
(fw != &foreground_window
&& window_border(*fw)
.is_some_and(|b| b.window_kind == WindowKind::Floating))
.map(|b| b.window_kind == WindowKind::Floating)
.unwrap_or_default())
});
// when the focused window has an `Unfocused` border kind, usually this happens if
// we focus an admin window and then refocus the previously focused window. For
// komorebi it will have the same state has before, however the previously focused
// window changed its border to unfocused so now we need to update it again.
if !should_process_notification
&& window_border(notification.0.unwrap_or_default())
.is_some_and(|b| b.window_kind == WindowKind::Unfocused)
{
should_process_notification = true;
}
if !should_process_notification && switch_focus_to_from_floating_window {
should_process_notification = true;
}
@@ -327,7 +301,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
let mut borders = BORDER_STATE.lock();
let mut borders_monitors = BORDERS_MONITORS.lock();
let mut windows_borders = WINDOWS_BORDERS.lock();
let mut focus_state = FOCUS_STATE.lock();
// If borders are disabled
if !BORDER_ENABLED.load_consume()
@@ -337,11 +313,14 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|| ALT_TAB_HWND.load().is_some()
{
// Destroy the borders we know about
for (_, border) in borders.drain() {
destroy_border(border)?;
for (_, border) in borders.iter() {
border.destroy()?;
}
borders.clear();
borders_monitors.clear();
windows_borders.clear();
focus_state.clear();
previous_is_paused = is_paused;
continue 'receiver;
@@ -356,6 +335,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, _| true,
)?;
@@ -366,21 +347,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Handle the monocle container separately
if let Some(monocle) = ws.monocle_container() {
let mut new_border = false;
let focused_window_hwnd =
monocle.focused_window().map(|w| w.hwnd).unwrap_or_default();
let id = monocle.id().clone();
let border = match borders.entry(id.clone()) {
let border = match borders.entry(monocle.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(
match Border::create(
monocle.id(),
focused_window_hwnd,
monitor_idx,
monocle.focused_window().copied().unwrap_or_default().hwnd,
) {
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
Ok(border) => {
new_border = true;
entry.insert(border)
}
_ => {
continue 'monitors;
}
}
}
};
@@ -391,44 +371,32 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
WindowKind::Monocle
};
border.window_kind = new_focus_state;
focus_state.insert(border.hwnd, new_focus_state);
// Update the borders tracking_hwnd in case it changed and remove the
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
if border.tracking_hwnd != focused_window_hwnd {
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {
// Only remove the border from `windows_borders` if it
// still corresponds to the same border, if doesn't then
// it means it was already updated by another border for
// that window and in that case we don't want to remove it.
if previous == &id {
windows_borders.remove(&border.tracking_hwnd);
}
}
border.tracking_hwnd = focused_window_hwnd;
if !WindowsApi::is_window_visible(border.hwnd) {
WindowsApi::restore_window(border.hwnd);
}
}
let reference_hwnd =
monocle.focused_window().copied().unwrap_or_default().hwnd;
// Update the border's monitor idx in case it changed
border.monitor_idx = Some(monitor_idx);
let rect = WindowsApi::window_rect(focused_window_hwnd)?;
border.window_rect = rect;
let rect = WindowsApi::window_rect(reference_hwnd)?;
if new_border {
border.set_position(&rect, focused_window_hwnd)?;
border.set_position(&rect, reference_hwnd)?;
}
border.invalidate();
windows_borders.insert(focused_window_hwnd, id);
borders_monitors.insert(monocle.id().clone(), monitor_idx);
windows_borders.insert(
monocle.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
let border_hwnd = border.hwnd;
// Remove all borders on this monitor except monocle
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, b| border_hwnd != b.hwnd,
)?;
@@ -447,6 +415,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, _| true,
)?;
@@ -469,145 +439,214 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|id, _| !container_and_floating_window_ids.contains(id),
)?;
'containers: for (idx, c) in ws.containers().iter().enumerate() {
// In case this container is a stack we need to check it's
// unfocused windows to remove any attached border
let is_stack = c.windows().len() > 1;
if is_stack {
let focused_window_idx = c.focused_window_idx();
let potential_stacked_border_handles = c
.windows()
.iter()
.enumerate()
.flat_map(|(i, w)| {
if i != focused_window_idx {
windows_borders.get(&w.hwnd).map(|b| b.hwnd)
} else {
None
}
})
.collect::<Vec<_>>();
if !potential_stacked_border_handles.is_empty() {
tracing::debug!(
"purging stacked borders: {:?}",
potential_stacked_border_handles
);
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, b| potential_stacked_border_handles.contains(&b.hwnd),
)?;
}
}
let focused_window_hwnd =
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
let id = c.id().clone();
// Get the border entry for this container from the map or create one
let mut new_border = false;
let border = match borders.entry(id.clone()) {
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) =
Border::create(c.id(), focused_window_hwnd, monitor_idx)
{
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
match Border::create(c.id(), focused_window_hwnd) {
Ok(border) => {
new_border = true;
entry.insert(border)
}
_ => {
continue 'monitors;
}
}
}
};
let last_focus_state = border.window_kind;
#[allow(unused_assignments)]
let mut last_focus_state = None;
let new_focus_state = if idx != ws.focused_container_idx()
|| monitor_idx != focused_monitor_idx
|| focused_window_hwnd != foreground_window
{
if ws.locked_containers().contains(&idx) {
WindowKind::UnfocusedLocked
} else {
WindowKind::Unfocused
}
WindowKind::Unfocused
} else if c.windows().len() > 1 {
WindowKind::Stack
} else {
WindowKind::Single
};
border.window_kind = new_focus_state;
// Update the borders `tracking_hwnd` in case it changed and remove the
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
last_focus_state = focus_state.get(&border.hwnd).copied();
// If this container's border was previously tracking a different
// window, then we need to destroy that border and create a new one
// tracking the correct window.
if border.tracking_hwnd != focused_window_hwnd {
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {
// Only remove the border from `windows_borders` if it
// still corresponds to the same border, if doesn't then
// it means it was already updated by another border for
// that window and in that case we don't want to remove it.
if previous == &id {
windows_borders.remove(&border.tracking_hwnd);
// Create new border
match Border::create(
c.id(),
c.focused_window().copied().unwrap_or_default().hwnd,
) {
Ok(b) => {
// Destroy previously stacked border window and remove its hwnd
// and tracking_hwnd.
border.destroy()?;
focus_state.remove(&border.hwnd);
if let Some(previous) =
windows_borders.get(&border.tracking_hwnd)
{
// Only remove the border from `windows_borders` if it
// still is the same border, if it isn't then it means it
// was already updated by another border for that window
// and in that case we don't want to remove it.
if previous.hwnd == border.hwnd {
windows_borders.remove(&border.tracking_hwnd);
}
}
// Replace with new border
new_border = true;
*border = b;
}
_ => {
continue 'monitors;
}
}
border.tracking_hwnd = focused_window_hwnd;
if !WindowsApi::is_window_visible(border.hwnd) {
WindowsApi::restore_window(border.hwnd);
}
}
// Update the border's monitor idx in case it changed
border.monitor_idx = Some(monitor_idx);
// avoid getting into a thread restart loop if we try to look up
// rect info for a window that has been destroyed by the time
// we get here
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
Ok(rect) => rect,
Err(_) => {
remove_border(c.id(), &mut borders, &mut windows_borders)?;
remove_border(
c.id(),
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
)?;
continue 'containers;
}
};
border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer;
let should_invalidate = new_border
|| (last_focus_state != new_focus_state)
|| layer_changed;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => {
(last_focus_state != new_focus_state) || layer_changed
}
};
if new_border || should_invalidate {
border.set_position(&rect, focused_window_hwnd)?;
}
if should_invalidate {
border.set_position(&rect, focused_window_hwnd)?;
border.invalidate();
}
windows_borders.insert(focused_window_hwnd, id);
borders_monitors.insert(c.id().clone(), monitor_idx);
windows_borders.insert(
c.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
focus_state.insert(border.hwnd, new_focus_state);
}
{
for window in ws.floating_windows() {
let mut new_border = false;
let id = window.hwnd.to_string();
let border = match borders.entry(id.clone()) {
let border = match borders.entry(window.hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(
&window.hwnd.to_string(),
window.hwnd,
monitor_idx,
) {
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
match Border::create(&window.hwnd.to_string(), window.hwnd)
{
Ok(border) => {
new_border = true;
entry.insert(border)
}
_ => {
continue 'monitors;
}
}
}
};
let last_focus_state = border.window_kind;
#[allow(unused_assignments)]
let mut last_focus_state = None;
let mut new_focus_state = WindowKind::Unfocused;
let new_focus_state = if foreground_window == window.hwnd {
WindowKind::Floating
} else {
WindowKind::Unfocused
};
if foreground_window == window.hwnd {
new_focus_state = WindowKind::Floating;
}
border.window_kind = new_focus_state;
// Update the border's monitor idx in case it changed
border.monitor_idx = Some(monitor_idx);
last_focus_state = focus_state.get(&border.hwnd).copied();
let rect = WindowsApi::window_rect(window.hwnd)?;
border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer;
let should_invalidate = new_border
|| (last_focus_state != new_focus_state)
|| layer_changed;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => {
last_focus_state != new_focus_state || layer_changed
}
};
if new_border {
border.set_position(&rect, window.hwnd)?;
}
if should_invalidate {
border.set_position(&rect, window.hwnd)?;
border.invalidate();
}
windows_borders.insert(window.hwnd, id);
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
windows_borders.insert(window.hwnd, border.clone());
focus_state.insert(border.hwnd, new_focus_state);
}
}
}
@@ -630,27 +669,24 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
/// the container id and the border and returns a bool, if true that border
/// will be removed.
fn remove_borders(
borders: &mut HashMap<String, Box<Border>>,
windows_borders: &mut HashMap<isize, String>,
borders: &mut HashMap<String, Border>,
windows_borders: &mut HashMap<isize, Border>,
focus_state: &mut HashMap<isize, WindowKind>,
borders_monitors: &mut HashMap<String, usize>,
monitor_idx: usize,
condition: impl Fn(&String, &Border) -> bool,
) -> color_eyre::Result<()> {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
// if border is on this monitor
if border.monitor_idx.is_some_and(|idx| idx == monitor_idx)
// and the condition applies
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& condition(id, border)
// and the border is visible (we don't remove hidden borders)
&& WindowsApi::is_window_visible(border.hwnd)
{
// we mark it to be removed
to_remove.push(id.clone());
}
}
for id in &to_remove {
remove_border(id, borders, windows_borders)?;
remove_border(id, borders, windows_borders, focus_state, borders_monitors)?;
}
Ok(())
@@ -659,73 +695,22 @@ fn remove_borders(
/// Removes the border with `id` and all its related info from all maps
fn remove_border(
id: &str,
borders: &mut HashMap<String, Box<Border>>,
windows_borders: &mut HashMap<isize, String>,
borders: &mut HashMap<String, Border>,
windows_borders: &mut HashMap<isize, Border>,
focus_state: &mut HashMap<isize, WindowKind>,
borders_monitors: &mut HashMap<String, usize>,
) -> color_eyre::Result<()> {
if let Some(removed_border) = borders.remove(id) {
removed_border.destroy()?;
windows_borders.remove(&removed_border.tracking_hwnd);
destroy_border(removed_border)?;
focus_state.remove(&removed_border.hwnd);
}
borders_monitors.remove(id);
Ok(())
}
/// IMPORTANT: BEWARE when changing this function. We need to make sure that we don't let the
/// `Box<Border>` be dropped normally. We need to turn the `Box` into the raw pointer and use that
/// pointer to call the `.destroy()` funtion of the border so it closes the window. This way the
/// `Box` is consumed and the pointer is dropped like a normal `Copy` number instead of trying to
/// drop the struct it points to. The actual border is owned by the thread that created the window
/// and once the window closes that thread gets out of its loop, finishes and properly disposes of
/// the border.
fn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {
let raw_pointer = Box::into_raw(border);
unsafe {
(*raw_pointer).destroy()?;
}
Ok(())
}
/// Removes the border around window with `tracking_hwnd` if it exists
pub fn delete_border(tracking_hwnd: isize) {
std::thread::spawn(move || {
let id = {
WINDOWS_BORDERS
.lock()
.get(&tracking_hwnd)
.cloned()
.unwrap_or_default()
};
let mut borders = BORDER_STATE.lock();
let mut windows_borders = WINDOWS_BORDERS.lock();
if let Err(error) = remove_border(&id, &mut borders, &mut windows_borders) {
tracing::error!("Failed to delete border: {}", error);
}
});
}
/// Shows the border around window with `tracking_hwnd` if it exists
pub fn show_border(tracking_hwnd: isize) {
std::thread::spawn(move || {
if let Some(border_info) = window_border(tracking_hwnd) {
WindowsApi::restore_window(border_info.border_hwnd);
}
});
}
/// Hides the border around window with `tracking_hwnd` if it exists, unless the border kind is a
/// `Stack` border.
pub fn hide_border(tracking_hwnd: isize) {
std::thread::spawn(move || {
if let Some(border_info) = window_border(tracking_hwnd) {
WindowsApi::hide_window(border_info.border_hwnd);
}
});
}
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ZOrder {
Top,
NoTopMost,

View File

@@ -1,19 +1,14 @@
use hex_color::HexColor;
use komorebi_themes::Color32;
#[cfg(feature = "schemars")]
use schemars::gen::SchemaGenerator;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
use schemars::schema::InstanceType;
#[cfg(feature = "schemars")]
use schemars::schema::Schema;
#[cfg(feature = "schemars")]
use schemars::schema::SchemaObject;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(untagged)]
pub enum Colour {
/// Colour represented as RGB
@@ -59,8 +54,7 @@ impl From<Colour> for Color32 {
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub struct Hex(HexColor);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Hex {
impl JsonSchema for Hex {
fn schema_name() -> String {
String::from("Hex")
}
@@ -84,8 +78,7 @@ impl From<Colour> for u32 {
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Rgb {
/// Red
pub r: u32,

View File

@@ -6,17 +6,17 @@
use std::ffi::c_void;
use std::ops::Deref;
use windows::core::IUnknown;
use windows::core::IUnknown_Vtbl;
use windows::core::GUID;
use windows::core::HRESULT;
use windows::core::HSTRING;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::SIZE;
use windows::Win32::UI::Shell::Common::IObjectArray;
use windows::core::GUID;
use windows::core::HRESULT;
use windows::core::HSTRING;
use windows::core::IUnknown;
use windows::core::IUnknown_Vtbl;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows_core::BOOL;
type DesktopID = GUID;
@@ -129,7 +129,7 @@ pub unsafe trait IApplicationView: IUnknown {
pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17
pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;
pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)
-> HRESULT;
-> HRESULT;
/*** IApplicationView methods ***/
pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20

View File

@@ -11,11 +11,11 @@ use interfaces::IServiceProvider;
use std::ffi::c_void;
use windows::Win32::Foundation::HWND;
use windows::Win32::System::Com::CLSCTX_ALL;
use windows::Win32::System::Com::COINIT_MULTITHREADED;
use windows::Win32::System::Com::CoCreateInstance;
use windows::Win32::System::Com::CoInitializeEx;
use windows::Win32::System::Com::CoUninitialize;
use windows::Win32::System::Com::CLSCTX_ALL;
use windows::Win32::System::Com::COINIT_MULTITHREADED;
use windows_core::Interface;
struct ComInit();
@@ -64,7 +64,7 @@ fn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplication
})
}
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {
COM_INIT.with(|_| {
let provider = get_iservice_provider();

View File

@@ -2,14 +2,14 @@ use std::collections::VecDeque;
use getset::Getters;
use nanoid::nanoid;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::ring::Ring;
use crate::window::Window;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, JsonSchema)]
pub struct Container {
#[getset(get = "pub")]
id: String,
@@ -52,17 +52,13 @@ impl Container {
}
}
/// Hides the unfocused windows of the container and restores the focused one. This function
/// is used to make sure we update the window that should be shown on a stack. If the container
/// isn't a stack this function won't change anything.
pub fn load_focused_window(&mut self) {
let focused_idx = self.focused_window_idx();
for (i, window) in self.windows_mut().iter_mut().enumerate() {
if i == focused_idx {
window.restore_with_border(false);
window.restore();
} else {
window.hide_with_border(false);
window.hide();
}
}
}
@@ -102,13 +98,14 @@ impl Container {
}
pub fn idx_for_window(&self, hwnd: isize) -> Option<usize> {
let mut idx = None;
for (i, window) in self.windows().iter().enumerate() {
if window.hwnd == hwnd {
return Option::from(i);
idx = Option::from(i);
}
}
None
idx
}
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
@@ -140,114 +137,3 @@ impl Container {
self.windows.focus(idx);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contains_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should return true for existing windows
assert!(container.contains_window(1));
assert_eq!(container.idx_for_window(1), Some(1));
// Should return false since window 4 doesn't exist
assert!(!container.contains_window(4));
assert_eq!(container.idx_for_window(4), None);
}
#[test]
fn test_remove_window_by_idx() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Remove window 1
container.remove_window_by_idx(1);
// Should only have 2 windows left
assert_eq!(container.windows().len(), 2);
// Should return false since window 1 was removed
assert!(!container.contains_window(1));
}
#[test]
fn test_remove_focused_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should be focused on the last created window
assert_eq!(container.focused_window_idx(), 2);
// Remove the focused window
container.remove_focused_window();
// Should be focused on the window before the removed one
assert_eq!(container.focused_window_idx(), 1);
// Should only have 2 windows left
assert_eq!(container.windows().len(), 2);
}
#[test]
fn test_add_window() {
let mut container = Container::default();
container.add_window(Window::from(1));
assert_eq!(container.windows().len(), 1);
assert_eq!(container.focused_window_idx(), 0);
assert!(container.contains_window(1));
}
#[test]
fn test_focus_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should focus on the last created window
assert_eq!(container.focused_window_idx(), 2);
// focus on the window at index 1
container.focus_window(1);
// Should be focused on window 1
assert_eq!(container.focused_window_idx(), 1);
// focus on the window at index 0
container.focus_window(0);
// Should be focused on window 0
assert_eq!(container.focused_window_idx(), 0);
}
#[test]
fn test_idx_for_window() {
let mut container = Container::default();
for i in 0..3 {
container.add_window(Window::from(i));
}
// Should return the index of the window
assert_eq!(container.idx_for_window(1), Some(1));
// Should return None since window 4 doesn't exist
assert_eq!(container.idx_for_window(4), None);
}
}

View File

@@ -1,12 +1,22 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
pub enum AnimationStyle {
Linear,
EaseInSine,

View File

@@ -1,17 +1,18 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
use super::CustomLayout;
use super::DefaultLayout;
use super::Rect;
use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
pub trait Arrangement {
fn calculate(
@@ -602,8 +603,18 @@ impl Arrangement for CustomLayout {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
pub enum Axis {
Horizontal,
Vertical,

View File

@@ -2,6 +2,7 @@ use crate::config_generation::ApplicationConfiguration;
use crate::config_generation::ApplicationOptions;
use crate::config_generation::MatchingRule;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
@@ -9,12 +10,10 @@ use std::ops::Deref;
use std::ops::DerefMut;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationSpecificConfiguration(pub BTreeMap<String, AscApplicationRulesOrSchema>);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum AscApplicationRulesOrSchema {
AscApplicationRules(AscApplicationRules),
@@ -47,8 +46,7 @@ impl ApplicationSpecificConfiguration {
}
/// Rules that determine how an application is handled
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct AscApplicationRules {
/// Rules to ignore specific windows
#[serde(skip_serializing_if = "Option::is_none")]

View File

@@ -1,5 +1,6 @@
use clap::ValueEnum;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -7,8 +8,9 @@ use strum::EnumString;
use super::ApplicationIdentifier;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationOptions {
@@ -50,16 +52,14 @@ impl ApplicationOptions {
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum MatchingRule {
Simple(IdWithIdentifier),
Composite(Vec<IdWithIdentifier>),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceMatchingRule {
pub monitor_index: usize,
pub workspace_index: usize,
@@ -67,8 +67,7 @@ pub struct WorkspaceMatchingRule {
pub initial_only: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifier {
pub kind: ApplicationIdentifier,
pub id: String,
@@ -76,8 +75,7 @@ pub struct IdWithIdentifier {
pub matching_strategy: Option<MatchingStrategy>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, JsonSchema)]
pub enum MatchingStrategy {
Legacy,
Equals,
@@ -91,8 +89,7 @@ pub enum MatchingStrategy {
DoesNotContain,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifierAndComment {
pub kind: ApplicationIdentifier,
pub id: String,
@@ -112,8 +109,7 @@ impl From<IdWithIdentifierAndComment> for IdWithIdentifier {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationConfiguration {
pub name: String,
pub identifier: IdWithIdentifier,
@@ -137,8 +133,7 @@ impl ApplicationConfiguration {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationConfigurationGenerator;
impl ApplicationConfigurationGenerator {

View File

@@ -5,16 +5,16 @@ use std::ops::Deref;
use std::ops::DerefMut;
use std::path::Path;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use super::Rect;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct CustomLayout(Vec<Column>);
impl Deref for CustomLayout {
@@ -250,8 +250,7 @@ impl CustomLayout {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "column", content = "configuration")]
pub enum Column {
Primary(Option<ColumnWidth>),
@@ -259,21 +258,18 @@ pub enum Column {
Tertiary(ColumnSplit),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ColumnWidth {
WidthPercentage(f32),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ColumnSplit {
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum ColumnSplitWithCapacity {
Horizontal(usize),
Vertical(usize),

View File

@@ -1,13 +1,15 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum CycleDirection {
Previous,
Next,

View File

@@ -1,4 +1,5 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -9,9 +10,18 @@ use super::Rect;
use super::Sizing;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Eq,
PartialEq,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DefaultLayout {
BSP,
Columns,

View File

@@ -1,9 +1,9 @@
use super::DefaultLayout;
use super::OperationDirection;
use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
use super::custom_layout::CustomLayout;
use super::DefaultLayout;
use super::OperationDirection;
pub trait Direction {
fn index_in_direction(

View File

@@ -1,3 +1,4 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -6,8 +7,7 @@ use super::CustomLayout;
use super::DefaultLayout;
use super::Direction;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum Layout {
Default(DefaultLayout),
Custom(CustomLayout),

View File

@@ -6,15 +6,16 @@ use std::path::PathBuf;
use std::str::FromStr;
use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use crate::animation::prefix::AnimationPrefix;
use crate::KomorebiTheme;
use crate::animation::prefix::AnimationPrefix;
pub use animation::AnimationStyle;
pub use arrangement::Arrangement;
pub use arrangement::Axis;
@@ -44,8 +45,7 @@ pub mod operation_direction;
pub mod pathext;
pub mod rect;
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum SocketMessage {
// Window / Container Commands
@@ -84,9 +84,6 @@ pub enum SocketMessage {
PromoteFocus,
PromoteWindow(OperationDirection),
EagerFocus(String),
LockMonitorWorkspaceContainer(usize, usize, usize),
UnlockMonitorWorkspaceContainer(usize, usize, usize),
ToggleLock,
ToggleFloat,
ToggleMonocle,
ToggleMaximize,
@@ -125,7 +122,6 @@ pub enum SocketMessage {
Load(PathBuf),
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
CycleFocusEmptyWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusMonitorAtCursor,
FocusLastWorkspace,
@@ -245,23 +241,24 @@ impl FromStr for SocketMessage {
}
}
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct SubscribeOptions {
/// Only emit notifications when the window manager state has changed
pub filter_state_changes: bool,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema, ValueEnum,
)]
pub enum StackbarMode {
Always,
Never,
OnStack,
}
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
)]
pub enum StackbarLabel {
#[default]
Process,
@@ -269,9 +266,18 @@ pub enum StackbarLabel {
}
#[derive(
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
Default,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Display,
Serialize,
Deserialize,
JsonSchema,
ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum BorderStyle {
#[default]
/// Use the system border style
@@ -283,9 +289,18 @@ pub enum BorderStyle {
}
#[derive(
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
Default,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Display,
Serialize,
Deserialize,
JsonSchema,
ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum BorderImplementation {
#[default]
/// Use the adjustable komorebi border implementation
@@ -298,29 +313,27 @@ pub enum BorderImplementation {
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Eq,
Hash,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WindowKind {
Single,
Stack,
Monocle,
#[default]
Unfocused,
UnfocusedLocked,
Floating,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum StateQuery {
FocusedMonitorIndex,
FocusedWorkspaceIndex,
@@ -330,9 +343,18 @@ pub enum StateQuery {
}
#[derive(
Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ApplicationIdentifier {
#[serde(alias = "exe")]
Exe,
@@ -344,8 +366,18 @@ pub enum ApplicationIdentifier {
Path,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
pub enum FocusFollowsMouseImplementation {
/// A custom FFM implementation (slightly more CPU-intensive)
Komorebi,
@@ -353,8 +385,7 @@ pub enum FocusFollowsMouseImplementation {
Windows,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct WindowManagementBehaviour {
/// The current WindowContainerBehaviour to be used
pub current_behaviour: WindowContainerBehaviour,
@@ -365,9 +396,18 @@ pub struct WindowManagementBehaviour {
}
#[derive(
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
Clone,
Copy,
Debug,
Default,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WindowContainerBehaviour {
/// Create a new container for each new window
#[default]
@@ -376,8 +416,18 @@ pub enum WindowContainerBehaviour {
Append,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor
Swap,
@@ -387,8 +437,18 @@ pub enum MoveBehaviour {
NoOp,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
pub enum CrossBoundaryBehaviour {
/// Attempt to perform actions across a workspace boundary
Workspace,
@@ -396,8 +456,18 @@ pub enum CrossBoundaryBehaviour {
Monitor,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
pub enum HidingBehaviour {
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
Hide,
@@ -407,8 +477,18 @@ pub enum HidingBehaviour {
Cloak,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows
Op,
@@ -416,8 +496,9 @@ pub enum OperationBehaviour {
NoOp,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum Sizing {
Increase,
Decrease,

View File

@@ -1,16 +1,18 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::direction::Direction;
use super::Axis;
use super::direction::Direction;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum OperationDirection {
Left,
Right,

View File

@@ -1,9 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use windows::Win32::Foundation::RECT;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
pub struct Rect {
/// The left point in a Win32 Rect
pub left: i32,

View File

@@ -44,13 +44,15 @@ pub fn send_notification(hwnd: isize) {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});

View File

@@ -9,7 +9,6 @@ pub mod colour;
pub mod container;
pub mod core;
pub mod focus_manager;
pub mod locked_deque;
pub mod monitor;
pub mod monitor_reconciliator;
pub mod process_command;
@@ -41,12 +40,12 @@ use std::io::Write;
use std::net::TcpStream;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub use colour::*;
pub use core::*;
@@ -66,14 +65,14 @@ use crate::core::config_generation::WorkspaceMatchingRule;
use color_eyre::Result;
use os_info::Version;
use parking_lot::Mutex;
use parking_lot::RwLock;
use regex::Regex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixStream;
use which::which;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use winreg::enums::HKEY_CURRENT_USER;
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
@@ -133,8 +132,8 @@ lazy_static! {
static ref TRANSPARENCY_BLACKLIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref DISPLAY_INDEX_PREFERENCES: Arc<RwLock<HashMap<usize, String>>> =
Arc::new(RwLock::new(HashMap::new()));
static ref DISPLAY_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, String>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref WORKSPACE_MATCHING_RULES: Arc<Mutex<Vec<WorkspaceMatchingRule>>> =
Arc::new(Mutex::new(Vec::new()));
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
@@ -174,8 +173,6 @@ lazy_static! {
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
]));
static ref DUPLICATE_MONITOR_SERIAL_IDS: Arc<RwLock<Vec<String>>> =
Arc::new(RwLock::new(Vec::new()));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
@@ -283,8 +280,7 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
current
}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
@@ -292,8 +288,7 @@ pub enum NotificationEvent {
Monitor(MonitorNotification),
}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,

View File

@@ -1,316 +0,0 @@
use std::collections::BTreeSet;
use std::collections::VecDeque;
pub struct LockedDeque<'a, T> {
deque: &'a mut VecDeque<T>,
locked_indices: &'a mut BTreeSet<usize>,
}
impl<'a, T: PartialEq> LockedDeque<'a, T> {
pub fn new(deque: &'a mut VecDeque<T>, locked_indices: &'a mut BTreeSet<usize>) -> Self {
Self {
deque,
locked_indices,
}
}
pub fn insert(&mut self, index: usize, value: T) -> usize {
insert_respecting_locks(self.deque, self.locked_indices, index, value)
}
pub fn remove(&mut self, index: usize) -> Option<T> {
remove_respecting_locks(self.deque, self.locked_indices, index)
}
}
pub fn insert_respecting_locks<T>(
deque: &mut VecDeque<T>,
locked_idx: &mut BTreeSet<usize>,
idx: usize,
value: T,
) -> usize {
if idx == deque.len() {
deque.push_back(value);
return idx;
}
let mut new_deque = VecDeque::with_capacity(deque.len() + 1);
let mut temp_locked_deque = VecDeque::new();
let mut j = 0;
let mut corrected_idx = idx;
for (i, el) in deque.drain(..).enumerate() {
if i == idx {
corrected_idx = j;
}
if locked_idx.contains(&i) {
temp_locked_deque.push_back(el);
} else {
new_deque.push_back(el);
j += 1;
}
}
new_deque.insert(corrected_idx, value);
for (locked_el, locked_idx) in temp_locked_deque.into_iter().zip(locked_idx.iter()) {
new_deque.insert(*locked_idx, locked_el);
if *locked_idx <= corrected_idx {
corrected_idx += 1;
}
}
*deque = new_deque;
corrected_idx
}
pub fn remove_respecting_locks<T>(
deque: &mut VecDeque<T>,
locked_idx: &mut BTreeSet<usize>,
idx: usize,
) -> Option<T> {
if idx >= deque.len() {
return None;
}
let final_size = deque.len() - 1;
let mut new_deque = VecDeque::with_capacity(final_size);
let mut temp_locked_deque = VecDeque::new();
let mut removed = None;
let mut removed_locked_idx = None;
for (i, el) in deque.drain(..).enumerate() {
if i == idx {
removed = Some(el);
removed_locked_idx = locked_idx.contains(&i).then_some(i);
} else if locked_idx.contains(&i) {
temp_locked_deque.push_back(el);
} else {
new_deque.push_back(el);
}
}
if let Some(i) = removed_locked_idx {
let mut above = locked_idx.split_off(&i);
above.pop_first();
locked_idx.extend(above.into_iter().map(|i| i - 1));
}
while locked_idx.last().is_some_and(|i| *i >= final_size) {
locked_idx.pop_last();
}
let extra_invalid_idx = (new_deque.len()
..(new_deque.len() + temp_locked_deque.len() - locked_idx.len()))
.collect::<Vec<_>>();
for (locked_el, locked_idx) in temp_locked_deque
.into_iter()
.zip(locked_idx.iter().chain(extra_invalid_idx.iter()))
{
new_deque.insert(*locked_idx, locked_el);
}
*deque = new_deque;
removed
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
use std::collections::VecDeque;
#[test]
fn test_insert_respecting_locks() {
// Test case 1: Basic insertion with locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
// Insert at index 0, should shift elements while keeping index 2 locked
insert_respecting_locks(&mut deque, &mut locked, 0, 99);
assert_eq!(deque, VecDeque::from(vec![99, 0, 2, 1, 3, 4]));
// Element '2' remains at index 2, element '1' that was at index 1 is now at index 3
}
// Test case 2: Insert at a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
// Try to insert at locked index 2, should insert at index 3 instead
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 2, 99);
assert_eq!(actual_index, 3);
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 99, 3, 4]));
}
// Test case 3: Multiple locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(1); // Lock index 1
locked.insert(3); // Lock index 3
// Insert at index 0, should maintain locked indices
insert_respecting_locks(&mut deque, &mut locked, 0, 99);
assert_eq!(deque, VecDeque::from(vec![99, 1, 0, 3, 2, 4]));
// Elements '1' and '3' remain at indices 1 and 3
}
// Test case 4: Insert at end
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
// Insert at end of deque
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 5, 99);
assert_eq!(actual_index, 5);
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4, 99]));
}
// Test case 5: Empty deque
{
let mut deque = VecDeque::new();
let mut locked = BTreeSet::new();
// Insert into empty deque
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 0, 99);
assert_eq!(actual_index, 0);
assert_eq!(deque, VecDeque::from(vec![99]));
}
// Test case 6: All indices locked
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
for i in 0..5 {
locked.insert(i); // Lock all indices
}
// Try to insert at index 2, should insert at the end
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 2, 99);
assert_eq!(actual_index, 5);
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4, 99]));
}
// Test case 7: Consecutive locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
locked.insert(3); // Lock index 3
// Insert at index 1, should maintain consecutive locked indices
insert_respecting_locks(&mut deque, &mut locked, 1, 99);
assert_eq!(deque, VecDeque::from(vec![0, 99, 2, 3, 1, 4]));
// Elements '2' and '3' remain at indices 2 and 3
}
}
#[test]
fn test_remove_respecting_locks() {
// Test case 1: Remove a non-locked index before a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 0);
assert_eq!(removed, Some(0));
assert_eq!(deque, VecDeque::from(vec![1, 3, 2, 4]));
assert!(locked.contains(&2)); // Index 2 should still be locked
}
// Test case 2: Remove a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 2);
assert_eq!(removed, Some(2));
assert_eq!(deque, VecDeque::from(vec![0, 1, 3, 4]));
assert!(!locked.contains(&2)); // Index 2 should be unlocked
}
// Test case 3: Remove an index after a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(1); // Lock index 1
let removed = remove_respecting_locks(&mut deque, &mut locked, 3);
assert_eq!(removed, Some(3));
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 4]));
assert!(locked.contains(&1)); // Index 1 should still be locked
}
// Test case 4: Multiple locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(1); // Lock index 1
locked.insert(3); // Lock index 3
let removed = remove_respecting_locks(&mut deque, &mut locked, 0);
assert_eq!(removed, Some(0));
assert_eq!(deque, VecDeque::from(vec![2, 1, 4, 3]));
assert!(locked.contains(&1) && locked.contains(&3)); // Both indices should still be locked
}
// Test case 5: Remove the last element
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 4);
assert_eq!(removed, Some(4));
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3]));
assert!(locked.contains(&2)); // Index 2 should still be locked
}
// Test case 6: Invalid index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 10);
assert_eq!(removed, None);
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4])); // Deque unchanged
assert!(locked.contains(&2)); // Lock unchanged
}
// Test case 7: Remove enough elements to make a locked index invalid
{
let mut deque = VecDeque::from(vec![0, 1, 2]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
remove_respecting_locks(&mut deque, &mut locked, 0);
assert_eq!(deque, VecDeque::from(vec![1, 2]));
assert!(!locked.contains(&2)); // Index 2 should now be invalid
}
// Test case 8: Removing an element before multiple locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4, 5]);
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
locked.insert(4); // Lock index 4
let removed = remove_respecting_locks(&mut deque, &mut locked, 1);
assert_eq!(removed, Some(1));
assert_eq!(deque, VecDeque::from(vec![0, 3, 2, 5, 4]));
assert!(locked.contains(&2) && locked.contains(&4)); // Both indices should still be locked
}
}
}

View File

@@ -10,27 +10,32 @@
use std::env::temp_dir;
use std::net::Shutdown;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use clap::Parser;
use color_eyre::Result;
use crossbeam_utils::Backoff;
use komorebi::animation::AnimationEngine;
use komorebi::animation::ANIMATION_ENABLED_GLOBAL;
use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
use komorebi::animation::AnimationEngine;
use parking_lot::Mutex;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use sysinfo::Process;
use sysinfo::ProcessesToUpdate;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use uds_windows::UnixStream;
use komorebi::CUSTOM_FFM;
use komorebi::DATA_DIR;
use komorebi::HOME_DIR;
use komorebi::INITIAL_CONFIGURATION_LOADED;
use komorebi::SESSION_ID;
use komorebi::border_manager;
use komorebi::focus_manager;
use komorebi::load_configuration;
@@ -49,23 +54,20 @@ use komorebi::window_manager::WindowManager;
use komorebi::windows_api::WindowsApi;
use komorebi::winevent_listener;
use komorebi::workspace_reconciliator;
use komorebi::CUSTOM_FFM;
use komorebi::DATA_DIR;
use komorebi::HOME_DIR;
use komorebi::INITIAL_CONFIGURATION_LOADED;
use komorebi::SESSION_ID;
shadow_rs::shadow!(build);
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1");
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
}
color_eyre::install()?;
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LOG", "info") };
}
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
@@ -124,20 +126,22 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
#[tracing::instrument]
fn detect_deadlocks() {
// Create a background thread which checks for deadlocks every 10s
std::thread::spawn(move || loop {
tracing::info!("running deadlock detector");
std::thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
std::thread::spawn(move || {
loop {
tracing::info!("running deadlock detector");
std::thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
tracing::error!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
tracing::error!("deadlock #{}", i);
for t in threads {
tracing::error!("thread id: {:#?}", t.thread_id());
tracing::error!("{:#?}", t.backtrace());
tracing::error!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
tracing::error!("deadlock #{}", i);
for t in threads {
tracing::error!("thread id: {:#?}", t.thread_id());
tracing::error!("{:#?}", t.backtrace());
}
}
}
});
@@ -192,7 +196,9 @@ fn main() -> Result<()> {
}
if len > 1 {
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
tracing::error!(
"komorebi.exe is already running, please exit the existing process before starting a new one"
);
std::process::exit(1);
}
}
@@ -235,7 +241,6 @@ fn main() -> Result<()> {
} else {
Arc::new(Mutex::new(WindowManager::new(
winevent_listener::event_rx(),
None,
)?))
};

View File

@@ -2,33 +2,41 @@ use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::core::Rect;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
use crate::workspace::WorkspaceLayer;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DefaultLayout;
use crate::Layout;
use crate::OperationDirection;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
#[derive(
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, PartialEq,
Debug,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
pub id: isize,
@@ -383,7 +391,6 @@ impl Monitor {
};
target_workspace.floating_windows_mut().push(window);
target_workspace.set_layer(WorkspaceLayer::Floating);
} else {
let container = workspace
.remove_focused_container()
@@ -400,8 +407,6 @@ impl Monitor {
Some(workspace) => workspace,
};
target_workspace.set_layer(WorkspaceLayer::Tiling);
if let Some(direction) = direction {
self.add_container_with_direction(
container,
@@ -468,139 +473,3 @@ impl Monitor {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_container() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add container to the default workspace
m.add_container(Container::default(), Some(0)).unwrap();
// Should contain a container in the current focused workspace
let workspace = m.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
}
#[test]
fn test_remove_workspace_by_idx() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// Create workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Create workspace 3
m.focus_workspace(new_workspace_index + 1).unwrap();
// Should have 3 workspaces
assert_eq!(m.workspaces().len(), 3);
// Remove workspace 1
m.remove_workspace_by_idx(1);
// Should have only 2 workspaces
assert_eq!(m.workspaces().len(), 2);
}
#[test]
fn test_remove_workspaces() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// Create workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Create workspace 3
m.focus_workspace(new_workspace_index + 1).unwrap();
// Should have 3 workspaces
assert_eq!(m.workspaces().len(), 3);
// Remove all workspaces
m.remove_workspaces();
// All workspaces should be removed
assert_eq!(m.workspaces().len(), 0);
}
#[test]
fn test_focus_workspace() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// Focus workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Should be focused on workspace 2
assert_eq!(m.focused_workspace_idx(), 1);
}
#[test]
fn test_new_workspace_idx() {
let m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let new_workspace_index = m.new_workspace_idx();
// Should be the last workspace index: 1
assert_eq!(new_workspace_index, 1);
}
}

View File

@@ -1,7 +1,6 @@
use std::sync::mpsc;
use std::time::Duration;
use windows::core::PCWSTR;
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_DISPLAY_ADAPTER;
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_MONITOR;
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL;
@@ -11,10 +10,6 @@ use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::System::Power::POWERBROADCAST_SETTING;
use windows::Win32::System::SystemServices::GUID_LIDSWITCH_STATE_CHANGE;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::DBT_CONFIGCHANGED;
@@ -23,6 +18,9 @@ use windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEREMOVECOMPLETE;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVTYP_DEVICEINTERFACE;
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC;
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND;
@@ -30,6 +28,7 @@ use windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND;
use windows::Win32::UI::WindowsAndMessaging::PBT_POWERSETTINGCHANGE;
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
use windows::Win32::UI::WindowsAndMessaging::WM_POWERBROADCAST;
@@ -38,10 +37,11 @@ use windows::Win32::UI::WindowsAndMessaging::WM_WTSSESSION_CHANGE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_LOCK;
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_UNLOCK;
use windows::core::PCWSTR;
use crate::WindowsApi;
use crate::monitor_reconciliator;
use crate::windows_api;
use crate::WindowsApi;
// This is a hidden window specifically spawned to listen to system-wide events related to monitors
#[derive(Debug, Clone, Copy)]
@@ -224,14 +224,18 @@ impl Hidden {
WM_WTSSESSION_CHANGE => {
match wparam.0 as u32 {
WTS_SESSION_LOCK => {
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked");
tracing::debug!(
"WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::SessionLocked,
);
}
WTS_SESSION_UNLOCK => {
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked");
tracing::debug!(
"WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::SessionUnlocked,
@@ -251,7 +255,8 @@ impl Hidden {
// and resolution changes here
WM_DISPLAYCHANGE => {
tracing::debug!(
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed", wparam.0
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed",
wparam.0
);
monitor_reconciliator::send_notification(
@@ -265,8 +270,8 @@ impl Hidden {
#[allow(clippy::cast_possible_truncation)]
if wparam.0 as u32 == SPI_SETWORKAREA.0 {
tracing::debug!(
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
);
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::WorkAreaChanged,

View File

@@ -1,5 +1,11 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::WORKSPACE_MATCHING_RULES;
use crate::WindowManager;
use crate::WindowsApi;
use crate::border_manager;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::Rect;
@@ -7,30 +13,22 @@ use crate::monitor;
use crate::monitor::Monitor;
use crate::monitor_reconciliator::hidden::Hidden;
use crate::notify_subscribers;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::WindowManager;
use crate::WindowsApi;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::WORKSPACE_MATCHING_RULES;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
pub mod hidden;
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum MonitorNotification {
ResolutionScalingChanged,
@@ -68,49 +66,17 @@ pub fn send_notification(notification: MonitorNotification) {
}
pub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) {
let dip = DISPLAY_INDEX_PREFERENCES.read();
let mut dip_ids = dip.values();
let preferred_id = if dip_ids.any(|id| id == monitor.device_id()) {
monitor.device_id().clone()
} else if dip_ids.any(|id| Some(id) == monitor.serial_number_id().as_ref()) {
monitor.serial_number_id().clone().unwrap_or_default()
} else {
serial_or_device_id.to_string()
};
let mut monitor_cache = MONITOR_CACHE
.get_or_init(|| Mutex::new(HashMap::new()))
.lock();
monitor_cache.insert(preferred_id, monitor);
monitor_cache.insert(serial_or_device_id.to_string(), monitor);
}
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
let all_displays = win32_display_data::connected_displays_all()
Ok(win32_display_data::connected_displays_all()
.flatten()
.collect::<Vec<_>>();
let mut serial_id_map = HashMap::new();
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
*serial_id_map.entry(id.clone()).or_insert(0) += 1;
}
}
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
if serial_id_map.get(id).copied().unwrap_or_default() > 1 {
let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();
if !dupes.contains(id) {
(*dupes).push(id.clone());
}
}
}
}
Ok(all_displays
.into_iter()
.map(|mut display| {
.map(|display| {
let path = display.device_path;
let (device, device_id) = if path.is_empty() {
@@ -127,13 +93,6 @@ pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
if let Some(id) = &display.serial_number_id {
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
if dupes.contains(id) {
display.serial_number_id = None;
}
}
monitor::new(
display.hmonitor,
display.size.into(),
@@ -153,16 +112,18 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
tracing::info!("created hidden window to listen for monitor-related events");
std::thread::spawn(move || loop {
match handle_notifications(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)
std::thread::spawn(move || {
loop {
match handle_notifications(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)
}
}
}
}
@@ -301,15 +262,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Make sure that in our state any attached displays have the latest Win32 data
for monitor in wm.monitors_mut() {
for attached in &attached_devices {
let serial_number_ids_match = if let (Some(attached_snid), Some(m_snid)) =
(attached.serial_number_id(), monitor.serial_number_id())
if attached.serial_number_id().eq(monitor.serial_number_id())
|| attached.device_id().eq(monitor.device_id())
{
attached_snid.eq(m_snid)
} else {
false
};
if serial_number_ids_match || attached.device_id().eq(monitor.device_id()) {
monitor.set_id(attached.id());
monitor.set_device(attached.device().clone());
monitor.set_device_id(attached.device_id().clone());
@@ -427,18 +382,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
workspace_rules.remove(i);
}
// Let's add their state to the cache for later, make sure to use what
// the user set as preference as the id.
let dip = DISPLAY_INDEX_PREFERENCES.read();
let mut dip_ids = dip.values();
let preferred_id = if dip_ids.any(|id| id == m.device_id()) {
m.device_id().clone()
} else if dip_ids.any(|id| Some(id) == m.serial_number_id().as_ref()) {
m.serial_number_id().clone().unwrap_or_default()
} else {
id
};
monitor_cache.insert(preferred_id, m.clone());
// Let's add their state to the cache for later
monitor_cache.insert(id, m.clone());
}
}
@@ -518,7 +463,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
cache_hit = true;
cached_id = id.clone();
tracing::info!("found monitor and workspace configuration for {id} in the monitor cache, applying");
tracing::info!(
"found monitor and workspace configuration for {id} in the monitor cache, applying"
);
// If it does, update the cached monitor info with the new one and
// load the cached monitor removing any window that has since been

View File

@@ -1,9 +1,3 @@
use color_eyre::eyre::anyhow;
use color_eyre::eyre::OptionExt;
use color_eyre::Result;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::fs::File;
use std::fs::OpenOptions;
@@ -14,17 +8,22 @@ use std::net::TcpListener;
use std::net::TcpStream;
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use schemars::r#gen::SchemaSettings;
use schemars::schema_for;
use uds_windows::UnixStream;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::ApplicationIdentifier;
use crate::core::Axis;
use crate::core::BorderImplementation;
@@ -38,7 +37,34 @@ use crate::core::SocketMessage;
use crate::core::StateQuery;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowKind;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::GlobalState;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::Notification;
use crate::NotificationEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
use crate::SUBSCRIPTION_SOCKETS;
use crate::State;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
use crate::animation::ANIMATION_FPS;
@@ -65,29 +91,6 @@ use crate::windows_api::WindowsApi;
use crate::winevent_listener;
use crate::workspace::WorkspaceLayer;
use crate::workspace::WorkspaceWindowLocation;
use crate::GlobalState;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKETS;
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use stackbar_manager::STACKBAR_LABEL;
use stackbar_manager::STACKBAR_MODE;
@@ -98,42 +101,44 @@ use stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
#[tracing::instrument]
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
let wm = wm.clone();
std::thread::spawn(move || {
loop {
let wm = wm.clone();
let _ = std::thread::spawn(move || {
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
let _ = std::thread::spawn(move || {
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => {
let wm_clone = wm.clone();
std::thread::spawn(move || {
match stream.set_read_timeout(Some(Duration::from_secs(1))) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
match read_commands_uds(&wm_clone, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
});
}
Err(error) => {
tracing::error!("{}", error);
break;
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => {
let wm_clone = wm.clone();
std::thread::spawn(move || {
match stream.set_read_timeout(Some(Duration::from_secs(1))) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
match read_commands_uds(&wm_clone, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
});
}
Err(error) => {
tracing::error!("{}", error);
break;
}
}
}
}
})
.join();
})
.join();
tracing::error!("restarting failed thread");
tracing::error!("restarting failed thread");
}
});
}
@@ -206,9 +211,7 @@ impl WindowManager {
let initial_state = State::from(self.as_ref());
match message {
SocketMessage::CycleFocusEmptyWorkspace(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusWorkspaceNumber(_) => {
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
if let Some(monitor) = self.focused_monitor_mut() {
let idx = monitor.focused_workspace_idx();
monitor.set_last_focused_workspace(Option::from(idx));
@@ -251,11 +254,9 @@ impl WindowManager {
if let Some((monitor_idx, workspace_idx)) = monitor_workspace_indices {
if monitor_idx != focused_monitor_idx {
self.focus_monitor(monitor_idx)?;
let focused_ws_idx = self.focused_workspace_idx()?;
if focused_ws_idx != workspace_idx {
self.focus_workspace(workspace_idx)?;
}
} else if workspace_idx != focused_workspace_idx {
}
if workspace_idx != focused_workspace_idx {
self.focus_workspace(workspace_idx)?;
}
}
@@ -334,9 +335,11 @@ impl WindowManager {
SocketMessage::UnstackAll => self.unstack_all()?,
SocketMessage::CycleStack(direction) => {
self.cycle_container_window_in_direction(direction)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::CycleStackIndex(direction) => {
self.cycle_container_window_index_in_direction(direction)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::FocusStackWindow(idx) => {
// In case you are using this command on a bar on a monitor
@@ -347,6 +350,7 @@ impl WindowManager {
self.focus_monitor(monitor_idx)?;
}
self.focus_container_window(idx)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::ForceFocus => {
let focused_window = self.focused_window()?;
@@ -360,41 +364,6 @@ impl WindowManager {
SocketMessage::Minimize => {
Window::from(WindowsApi::foreground_window()?).minimize();
}
SocketMessage::LockMonitorWorkspaceContainer(
monitor_idx,
workspace_idx,
container_idx,
) => {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("no monitor at the given index")?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("no workspace at the given index")?;
workspace.locked_containers.insert(container_idx);
}
SocketMessage::UnlockMonitorWorkspaceContainer(
monitor_idx,
workspace_idx,
container_idx,
) => {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("no monitor at the given index")?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("no workspace at the given index")?;
workspace.locked_containers.remove(&container_idx);
}
SocketMessage::ToggleLock => self.toggle_lock()?,
SocketMessage::ToggleFloat => self.toggle_float()?,
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
@@ -937,55 +906,6 @@ impl WindowManager {
self.focus_workspace(workspace_idx)?;
}
SocketMessage::CycleFocusEmptyWorkspace(direction) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
}
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let mut empty_workspaces = vec![];
for (idx, w) in focused_monitor.workspaces().iter().enumerate() {
if w.is_empty() {
empty_workspaces.push(idx);
}
}
if !empty_workspaces.is_empty() {
let mut workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
while !empty_workspaces.contains(&workspace_idx) {
workspace_idx = direction.next_idx(
workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
}
self.focus_workspace(workspace_idx)?;
}
}
SocketMessage::CloseWorkspace => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
@@ -1190,7 +1110,7 @@ impl WindowManager {
);
}
SocketMessage::DisplayIndexPreference(index_preference, ref display) => {
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.write();
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.insert(index_preference, display.clone());
}
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {
@@ -1296,7 +1216,7 @@ impl WindowManager {
let container_len = workspace.containers().len();
let no_layout_rules = workspace.layout_rules().is_empty();
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if let Layout::Custom(custom) = workspace.layout_mut() {
if matches!(axis, Axis::Horizontal) {
#[allow(clippy::cast_precision_loss)]
let percentage = custom
@@ -1458,7 +1378,9 @@ impl WindowManager {
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Windows) => {
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
tracing::warn!(
"ignoring command that could mix different focus follows mouse implementations"
);
}
}
}
@@ -1482,7 +1404,9 @@ impl WindowManager {
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Komorebi) => {
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
tracing::warn!(
"ignoring command that could mix different focus follows mouse implementations"
);
}
}
}
@@ -1615,7 +1539,7 @@ impl WindowManager {
!workspace.apply_window_based_work_area_offset(),
);
self.retile_all(true)?;
self.retile_all(false)?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
@@ -1724,33 +1648,41 @@ impl WindowManager {
}
SocketMessage::ToggleWorkspaceWindowContainerBehaviour => {
let current_global_behaviour = self.window_management_behaviour.current_behaviour;
if let Some(behaviour) = self
match self
.focused_workspace_mut()?
.window_container_behaviour_mut()
{
match behaviour {
Some(behaviour) => match behaviour {
WindowContainerBehaviour::Create => {
*behaviour = WindowContainerBehaviour::Append
}
WindowContainerBehaviour::Append => {
*behaviour = WindowContainerBehaviour::Create
}
},
_ => {
self.focused_workspace_mut()?
.set_window_container_behaviour(Some(match current_global_behaviour {
WindowContainerBehaviour::Create => {
WindowContainerBehaviour::Append
}
WindowContainerBehaviour::Append => {
WindowContainerBehaviour::Create
}
}));
}
} else {
self.focused_workspace_mut()?
.set_window_container_behaviour(Some(match current_global_behaviour {
WindowContainerBehaviour::Create => WindowContainerBehaviour::Append,
WindowContainerBehaviour::Append => WindowContainerBehaviour::Create,
}));
};
}
SocketMessage::ToggleWorkspaceFloatOverride => {
let current_global_override = self.window_management_behaviour.float_override;
if let Some(float_override) = self.focused_workspace_mut()?.float_override_mut() {
*float_override = !*float_override;
} else {
self.focused_workspace_mut()?
.set_float_override(Some(!current_global_override));
match self.focused_workspace_mut()?.float_override_mut() {
Some(float_override) => {
*float_override = !*float_override;
}
_ => {
self.focused_workspace_mut()?
.set_float_override(Some(!current_global_override));
}
};
}
SocketMessage::WindowHidingBehaviour(behaviour) => {
@@ -1819,10 +1751,6 @@ impl WindowManager {
WindowKind::Unfocused => {
border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::UnfocusedLocked => {
border_manager::UNFOCUSED_LOCKED
.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Floating => {
border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
@@ -1914,49 +1842,35 @@ impl WindowManager {
*STACKBAR_FONT_FAMILY.lock() = font_family.clone();
}
SocketMessage::ApplicationSpecificConfigurationSchema => {
#[cfg(feature = "schemars")]
{
let asc = schemars::schema_for!(
Vec<crate::core::config_generation::ApplicationConfiguration>
);
let schema = serde_json::to_string_pretty(&asc)?;
let asc = schema_for!(Vec<ApplicationConfiguration>);
let schema = serde_json::to_string_pretty(&asc)?;
reply.write_all(schema.as_bytes())?;
}
reply.write_all(schema.as_bytes())?;
}
SocketMessage::NotificationSchema => {
#[cfg(feature = "schemars")]
{
let notification = schemars::schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
reply.write_all(schema.as_bytes())?;
}
reply.write_all(schema.as_bytes())?;
}
SocketMessage::SocketSchema => {
#[cfg(feature = "schemars")]
{
let socket_message = schemars::schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
let socket_message = schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
}
reply.write_all(schema.as_bytes())?;
}
SocketMessage::StaticConfigSchema => {
#[cfg(feature = "schemars")]
{
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let settings = SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
let r#gen = settings.into_generator();
let socket_message = r#gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
}
reply.write_all(schema.as_bytes())?;
}
SocketMessage::GenerateStaticConfig => {
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
@@ -2108,73 +2022,3 @@ pub fn read_commands_tcp(
Ok(())
}
#[cfg(test)]
mod tests {
use crate::monitor;
use crate::window_manager::WindowManager;
use crate::Rect;
use crate::SocketMessage;
use crate::WindowManagerEvent;
use crossbeam_channel::bounded;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
use uds_windows::UnixStream;
use uuid::Uuid;
fn send_socket_message(socket: &PathBuf, message: SocketMessage) {
let mut stream = UnixStream::connect(socket).unwrap();
stream
.set_write_timeout(Some(Duration::from_secs(1)))
.unwrap();
stream
.write_all(serde_json::to_string(&message).unwrap().as_bytes())
.unwrap();
}
#[test]
fn test_receive_socket_message() {
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
bounded(1);
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
let socket_path = PathBuf::from(&socket_name);
let mut wm = WindowManager::new(receiver, Some(socket_path.clone())).unwrap();
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
wm.monitors_mut().push_back(m);
// send a message
send_socket_message(&socket_path, SocketMessage::FocusWorkspaceNumber(5));
let (stream, _) = wm.command_listener.accept().unwrap();
let reader = BufReader::new(stream.try_clone().unwrap());
let next = reader.lines().next();
// read and deserialize the message
let message_string = next.unwrap().unwrap();
let message = SocketMessage::from_str(&message_string).unwrap();
assert!(matches!(message, SocketMessage::FocusWorkspaceNumber(5)));
// process the message
wm.process_command(message, stream).unwrap();
// check the updated window manager state
assert_eq!(wm.focused_workspace_idx().unwrap(), 5);
std::fs::remove_file(socket_path).unwrap();
}
}

View File

@@ -1,10 +1,10 @@
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
@@ -13,6 +13,13 @@ use crate::core::Rect;
use crate::core::Sizing;
use crate::core::WindowContainerBehaviour;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::Notification;
use crate::NotificationEvent;
use crate::REGEX_IDENTIFIERS;
use crate::State;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::border_manager;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
@@ -20,8 +27,8 @@ use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::stackbar_manager;
use crate::transparency_manager;
use crate::window::should_act;
use crate::window::RuleDebug;
use crate::window::should_act;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
@@ -30,13 +37,6 @@ use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
#[tracing::instrument]
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
@@ -395,19 +395,8 @@ impl WindowManager {
&& !matches!(event, WindowManagerEvent::Manage(_)));
if behaviour.float_override {
// Center floating windows if we are already on the `Floating`
// layer and the window doesn't match a `floating_windows` rule and
// the workspace is not a floating workspace
let center_spawned_floats =
matches!(workspace.layer, WorkspaceLayer::Floating)
&& !should_float
&& workspace.tile;
workspace.floating_windows_mut().push(window);
workspace.set_layer(WorkspaceLayer::Floating);
if center_spawned_floats {
let mut floating_window = window;
floating_window.center(&workspace.globals().work_area)?;
}
self.update_focused_workspace(false, false)?;
} else {
match behaviour.current_behaviour {
@@ -672,7 +661,7 @@ impl WindowManager {
let mut ops = vec![];
macro_rules! resize_op {
($coordinate:expr, $comparator:tt, $direction:expr) => {{
($coordinate:expr_2021, $comparator:tt, $direction:expr_2021) => {{
let adjusted = $coordinate * 2;
let sizing = if adjusted $comparator 0 {
Sizing::Decrease

View File

@@ -1,9 +1,9 @@
use std::sync::Arc;
use parking_lot::Mutex;
use winput::Action;
use winput::message_loop;
use winput::message_loop::Event;
use winput::Action;
use crate::core::FocusFollowsMouseImplementation;

View File

@@ -1,13 +1,13 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::border_manager;
use crate::notify_subscribers;
use crate::winevent::WinEvent;
use crate::DATA_DIR;
use crate::NotificationEvent;
use crate::Window;
use crate::WindowManager;
use crate::WindowManagerEvent;
use crate::DATA_DIR;
use crate::border_manager;
use crate::notify_subscribers;
use crate::winevent::WinEvent;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
@@ -53,13 +53,15 @@ pub fn listen_for_notifications(
) {
watch_for_orphans(known_hwnds);
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});
@@ -148,16 +150,18 @@ fn watch_for_orphans(known_hwnds: HashMap<isize, (usize, usize)>) {
*cache = known_hwnds;
}
std::thread::spawn(move || loop {
match find_orphans() {
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)
std::thread::spawn(move || {
loop {
match find_orphans() {
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)
}
}
}
}

View File

@@ -1,10 +1,10 @@
use std::collections::VecDeque;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Ring<T> {
elements: VecDeque<T>,
focused: usize,

View File

@@ -1,26 +1,26 @@
mod stackbar;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WindowManager;
use crate::WindowsApi;
use crate::container::Container;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::stackbar_manager::stackbar::Stackbar;
use crate::WindowManager;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
pub static STACKBAR_FONT_SIZE: AtomicI32 = AtomicI32::new(0); // 0 will produce the system default
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
@@ -71,13 +71,15 @@ pub fn should_have_stackbar(window_count: usize) -> bool {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});
@@ -193,13 +195,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Get the stackbar entry for this container from the map or create one
let stackbar = match stackbars.entry(container.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(stackbar) = Stackbar::create(container.id()) {
entry.insert(stackbar)
} else {
Entry::Vacant(entry) => match Stackbar::create(container.id()) {
Ok(stackbar) => entry.insert(stackbar),
_ => {
continue 'receiver;
}
}
},
};
stackbars_monitors.insert(container.id().clone(), monitor_idx);

View File

@@ -1,3 +1,6 @@
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WINDOWS_11;
use crate::WindowsApi;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::STYLE;
@@ -5,7 +8,6 @@ use crate::container::Container;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::core::StackbarLabel;
use crate::stackbar_manager::STACKBARS_CONTAINERS;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
use crate::stackbar_manager::STACKBAR_FONT_SIZE;
@@ -14,16 +16,13 @@ use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBARS_CONTAINERS;
use crate::windows_api;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WINDOWS_11;
use crossbeam_utils::atomic::AtomicConsume;
use std::os::windows::ffi::OsStrExt;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::time::Duration;
use windows::core::PCWSTR;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HWND;
@@ -33,38 +32,38 @@ 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::DT_CENTER;
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
use windows::Win32::Graphics::Gdi::DT_VCENTER;
use windows::Win32::Graphics::Gdi::DeleteObject;
use windows::Win32::Graphics::Gdi::DrawTextW;
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
use windows::Win32::Graphics::Gdi::FW_BOLD;
use windows::Win32::Graphics::Gdi::GetDC;
use windows::Win32::Graphics::Gdi::GetDeviceCaps;
use windows::Win32::Graphics::Gdi::LOGFONTW;
use windows::Win32::Graphics::Gdi::LOGPIXELSY;
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::ReleaseDC;
use windows::Win32::Graphics::Gdi::RoundRect;
use windows::Win32::Graphics::Gdi::SelectObject;
use windows::Win32::Graphics::Gdi::SetBkColor;
use windows::Win32::Graphics::Gdi::SetTextColor;
use windows::Win32::Graphics::Gdi::DT_CENTER;
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
use windows::Win32::Graphics::Gdi::DT_VCENTER;
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
use windows::Win32::Graphics::Gdi::FW_BOLD;
use windows::Win32::Graphics::Gdi::LOGFONTW;
use windows::Win32::Graphics::Gdi::LOGPIXELSY;
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::System::WindowsProgramming::MulDiv;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
@@ -72,6 +71,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
use windows::core::PCWSTR;
#[derive(Debug)]
pub struct Stackbar {
@@ -342,15 +342,15 @@ impl Stackbar {
window.set_position(&focused_window_rect, false)
{
tracing::error!(
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
*window,
err
);
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
*window,
err
);
}
}
// Restore the window corresponding to the tab we have clicked
window.restore_with_border(false);
window.restore();
if let Err(err) = window.focus(false) {
tracing::error!(
"stackbar WMLBUTTONDOWN focus error: hwnd {} ({})",
@@ -361,7 +361,7 @@ impl Stackbar {
} else {
// Hide any windows in the stack that don't correspond to the window
// we have clicked
window.hide_with_border(false);
window.hide();
}
}
}

View File

@@ -1,4 +1,28 @@
use crate::animation::PerAnimationPrefixConfig;
use crate::AspectRatio;
use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::PredefinedAspectRatio;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
@@ -7,35 +31,15 @@ use crate::animation::ANIMATION_FPS;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::animation::DEFAULT_ANIMATION_FPS;
use crate::asc::ApplicationSpecificConfiguration;
use crate::asc::AscApplicationRulesOrSchema;
use crate::animation::PerAnimationPrefixConfig;
use crate::border_manager;
use crate::border_manager::ZOrder;
use crate::border_manager::IMPLEMENTATION;
use crate::border_manager::STYLE;
use crate::border_manager::ZOrder;
use crate::colour::Colour;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use crate::core::AnimationStyle;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::current_virtual_desktop;
use crate::monitor;
use crate::monitor::Monitor;
@@ -57,37 +61,35 @@ use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::workspace::Workspace;
use crate::AspectRatio;
use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::PredefinedAspectRatio;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::asc::ApplicationSpecificConfiguration;
use crate::asc::AscApplicationRulesOrSchema;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::AnimationStyle;
use crate::core::BorderStyle;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
use hotwatch::Hotwatch;
use parking_lot::Mutex;
use regex::Regex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
@@ -95,13 +97,12 @@ use std::collections::HashSet;
use std::io::ErrorKind;
use std::io::Write;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct BorderColours {
/// Border colour when the container contains a single window
#[serde(skip_serializing_if = "Option::is_none")]
@@ -120,8 +121,7 @@ pub struct BorderColours {
pub unfocused: Option<Colour>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct WorkspaceConfig {
/// Name
pub name: String,
@@ -243,8 +243,7 @@ impl From<&Workspace> for WorkspaceConfig {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct MonitorConfig {
/// Workspace configurations
pub workspaces: Vec<WorkspaceConfig>,
@@ -302,19 +301,8 @@ impl From<&Monitor> for MonitorConfig {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum AppSpecificConfigurationPath {
/// A single applications.json file
Single(PathBuf),
/// Multiple applications.json files
Multiple(Vec<PathBuf>),
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.36`
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
/// The `komorebi.json` static configuration file reference for `v0.1.35`
pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required
#[serde(skip_serializing_if = "Option::is_none")]
@@ -352,7 +340,7 @@ pub struct StaticConfig {
pub mouse_follows_focus: Option<bool>,
/// Path to applications.json from komorebi-application-specific-configurations (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,
pub app_specific_configuration_path: Option<PathBuf>,
/// Width of the window border (default: 8)
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_width")]
@@ -461,8 +449,7 @@ pub struct StaticConfig {
pub floating_window_aspect_ratio: Option<AspectRatio>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct AnimationsConfig {
/// Enable or disable animations (default: false)
pub enabled: PerAnimationPrefixConfig<bool>,
@@ -477,8 +464,7 @@ pub struct AnimationsConfig {
pub fps: Option<u64>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "palette")]
pub enum KomorebiTheme {
/// A theme from catppuccin-egui
@@ -500,9 +486,6 @@ pub enum KomorebiTheme {
/// Border colour when the container is unfocused (default: Base)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is unfocused and locked (default: Red)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_locked_border: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar focused tab text colour (default: Green)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::CatppuccinValue>,
@@ -535,9 +518,6 @@ pub enum KomorebiTheme {
/// Border colour when the container is unfocused (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused and locked (default: Base08)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_locked_border: Option<komorebi_themes::Base16Value>,
/// Stackbar focused tab text colour (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
@@ -575,7 +555,9 @@ impl StaticConfig {
}
if display {
println!("\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n")
println!(
"\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n"
)
}
}
@@ -600,7 +582,9 @@ impl StaticConfig {
}
if display {
println!("\nYour configuration file contains some options that have been renamed or deprecated:\n");
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) {
@@ -634,8 +618,7 @@ impl StaticConfig {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct TabsConfig {
/// Width of a stackbar tab
#[serde(skip_serializing_if = "Option::is_none")]
@@ -657,8 +640,7 @@ pub struct TabsConfig {
pub font_size: Option<i32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct StackbarConfig {
/// Stackbar height
#[serde(skip_serializing_if = "Option::is_none")]
@@ -756,7 +738,7 @@ impl From<&WindowManager> for StaticConfig {
.collect::<Vec<_>>(),
),
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.read().clone()),
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()),
stackbar: None,
animation: None,
theme: None,
@@ -784,7 +766,7 @@ impl StaticConfig {
}
if let Some(display_index_preferences) = &self.display_index_preferences {
let mut preferences = DISPLAY_INDEX_PREFERENCES.write();
let mut preferences = DISPLAY_INDEX_PREFERENCES.lock();
preferences.clone_from(display_index_preferences);
}
@@ -1020,35 +1002,140 @@ impl StaticConfig {
}
if let Some(path) = &self.app_specific_configuration_path {
match path {
AppSpecificConfigurationPath::Single(path) => handle_asc_file(
path,
&mut ignore_identifiers,
&mut object_name_change_identifiers,
&mut layered_identifiers,
&mut tray_and_multi_window_identifiers,
&mut manage_identifiers,
&mut floating_applications,
&mut transparency_blacklist,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?,
AppSpecificConfigurationPath::Multiple(paths) => {
for path in paths {
handle_asc_file(
path,
&mut ignore_identifiers,
&mut object_name_change_identifiers,
&mut layered_identifiers,
&mut tray_and_multi_window_identifiers,
&mut manage_identifiers,
&mut floating_applications,
&mut transparency_blacklist,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?
match path.extension() {
None => {}
Some(ext) => match ext.to_string_lossy().to_string().as_str() {
"yaml" => {
tracing::info!("loading applications.yaml from: {}", path.display());
let path = resolve_home_path(path)?;
let content = std::fs::read_to_string(path)?;
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(rules) = &mut entry.ignore_identifiers {
populate_rules(
rules,
&mut ignore_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(ref options) = entry.options {
let options = options.clone();
for o in options {
match o {
ApplicationOptions::ObjectNameChange => {
populate_option(
&mut entry,
&mut object_name_change_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::Layered => {
populate_option(
&mut entry,
&mut layered_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::TrayAndMultiWindow => {
populate_option(
&mut entry,
&mut tray_and_multi_window_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::Force => {
populate_option(
&mut entry,
&mut manage_identifiers,
&mut regex_identifiers,
)?;
}
ApplicationOptions::BorderOverflow => {} // deprecated
}
}
}
}
}
}
"json" => {
tracing::info!("loading applications.json from: {}", path.display());
let path = resolve_home_path(path)?;
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
for entry in asc.values_mut() {
match entry {
AscApplicationRulesOrSchema::Schema(_) => {}
AscApplicationRulesOrSchema::AscApplicationRules(entry) => {
if let Some(rules) = &mut entry.ignore {
populate_rules(
rules,
&mut ignore_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.manage {
populate_rules(
rules,
&mut manage_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.floating {
populate_rules(
rules,
&mut floating_applications,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.transparency_ignore {
populate_rules(
rules,
&mut transparency_blacklist,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.tray_and_multi_window {
populate_rules(
rules,
&mut tray_and_multi_window_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.layered {
populate_rules(
rules,
&mut layered_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.object_name_change {
populate_rules(
rules,
&mut object_name_change_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.slow_application {
populate_rules(
rules,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?;
}
}
}
}
}
_ => {}
},
}
}
@@ -1064,16 +1151,7 @@ impl StaticConfig {
let mut value: Self = serde_json::from_str(&content)?;
if let Some(path) = &mut value.app_specific_configuration_path {
match path {
AppSpecificConfigurationPath::Single(path) => {
*path = resolve_home_path(&*path)?;
}
AppSpecificConfigurationPath::Multiple(paths) => {
for path in paths {
*path = resolve_home_path(&*path)?;
}
}
}
*path = resolve_home_path(&*path)?;
}
if let Some(monitors) = &mut value.monitors {
@@ -1196,7 +1274,7 @@ impl StaticConfig {
let mut wm = wm.lock();
let configs_with_preference: Vec<_> =
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
let mut configs_used = Vec::new();
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
@@ -1206,7 +1284,7 @@ impl StaticConfig {
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
(monitor
.serial_number_id()
@@ -1305,7 +1383,7 @@ impl StaticConfig {
.filter(|i| !configs_used.contains(i))
{
let id = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.get(i).cloned()
};
if let (Some(id), Some(monitor_config)) =
@@ -1365,7 +1443,7 @@ impl StaticConfig {
value.apply_globals()?;
let configs_with_preference: Vec<_> =
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
let mut configs_used = Vec::new();
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
@@ -1375,7 +1453,7 @@ impl StaticConfig {
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
(monitor
.serial_number_id()
@@ -1477,7 +1555,7 @@ impl StaticConfig {
.filter(|i| !configs_used.contains(i))
{
let id = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.get(i).cloned()
};
if let (Some(id), Some(monitor_config)) =
@@ -1641,164 +1719,3 @@ fn populate_rules(
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn handle_asc_file(
path: &PathBuf,
ignore_identifiers: &mut Vec<MatchingRule>,
object_name_change_identifiers: &mut Vec<MatchingRule>,
layered_identifiers: &mut Vec<MatchingRule>,
tray_and_multi_window_identifiers: &mut Vec<MatchingRule>,
manage_identifiers: &mut Vec<MatchingRule>,
floating_applications: &mut Vec<MatchingRule>,
transparency_blacklist: &mut Vec<MatchingRule>,
slow_application_identifiers: &mut Vec<MatchingRule>,
regex_identifiers: &mut HashMap<String, Regex>,
) -> Result<()> {
match path.extension() {
None => {}
Some(ext) => match ext.to_string_lossy().to_string().as_str() {
"yaml" => {
tracing::info!("loading applications.yaml from: {}", path.display());
let path = resolve_home_path(path)?;
let content = std::fs::read_to_string(path)?;
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(rules) = &mut entry.ignore_identifiers {
populate_rules(rules, ignore_identifiers, regex_identifiers)?;
}
if let Some(ref options) = entry.options {
let options = options.clone();
for o in options {
match o {
ApplicationOptions::ObjectNameChange => {
populate_option(
&mut entry,
object_name_change_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::Layered => {
populate_option(
&mut entry,
layered_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::TrayAndMultiWindow => {
populate_option(
&mut entry,
tray_and_multi_window_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::Force => {
populate_option(
&mut entry,
manage_identifiers,
regex_identifiers,
)?;
}
ApplicationOptions::BorderOverflow => {} // deprecated
}
}
}
}
}
"json" => {
tracing::info!("loading applications.json from: {}", path.display());
let path = resolve_home_path(path)?;
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
for entry in asc.values_mut() {
match entry {
AscApplicationRulesOrSchema::Schema(_) => {}
AscApplicationRulesOrSchema::AscApplicationRules(entry) => {
if let Some(rules) = &mut entry.ignore {
populate_rules(rules, ignore_identifiers, regex_identifiers)?;
}
if let Some(rules) = &mut entry.manage {
populate_rules(rules, manage_identifiers, regex_identifiers)?;
}
if let Some(rules) = &mut entry.floating {
populate_rules(rules, floating_applications, regex_identifiers)?;
}
if let Some(rules) = &mut entry.transparency_ignore {
populate_rules(rules, transparency_blacklist, regex_identifiers)?;
}
if let Some(rules) = &mut entry.tray_and_multi_window {
populate_rules(
rules,
tray_and_multi_window_identifiers,
regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.layered {
populate_rules(rules, layered_identifiers, regex_identifiers)?;
}
if let Some(rules) = &mut entry.object_name_change {
populate_rules(
rules,
object_name_change_identifiers,
regex_identifiers,
)?;
}
if let Some(rules) = &mut entry.slow_application {
populate_rules(
rules,
slow_application_identifiers,
regex_identifiers,
)?;
}
}
}
}
}
_ => {}
},
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::StaticConfig;
#[test]
fn backwards_compat() {
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
let docs = vec![
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27",
"0.1.28", "0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33", "0.1.34",
];
let mut versions = vec![];
let client = reqwest::blocking::Client::new();
for version in root {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for version in docs {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for (version, config) in versions {
println!("{version}");
StaticConfig::read_raw(&config).unwrap();
}
}
}

View File

@@ -1,18 +1,18 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::Colour;
use crate::KomorebiTheme;
use crate::border_manager;
use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::Colour;
use crate::KomorebiTheme;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::OnceLock;
use std::sync::atomic::Ordering;
pub struct Notification(KomorebiTheme);
@@ -50,13 +50,15 @@ pub fn send_notification(theme: KomorebiTheme) {
}
pub fn listen_for_notifications() {
std::thread::spawn(move || loop {
match handle_notifications() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
std::thread::spawn(move || {
loop {
match handle_notifications() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});
@@ -76,7 +78,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
monocle_border,
floating_border,
unfocused_border,
unfocused_locked_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
@@ -88,7 +89,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
monocle_border,
floating_border,
unfocused_border,
unfocused_locked_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
@@ -114,10 +114,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
.color32(name.as_theme());
let unfocused_locked_border = unfocused_locked_border
.unwrap_or(komorebi_themes::CatppuccinValue::Red)
.color32(name.as_theme());
let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::CatppuccinValue::Green)
.color32(name.as_theme());
@@ -136,7 +132,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
monocle_border,
floating_border,
unfocused_border,
unfocused_locked_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
@@ -149,7 +144,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
monocle_border,
floating_border,
unfocused_border,
unfocused_locked_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
@@ -171,10 +165,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
.unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name);
let unfocused_locked_border = unfocused_locked_border
.unwrap_or(komorebi_themes::Base16Value::Base08)
.color32(*name);
let floating_border = floating_border
.unwrap_or(komorebi_themes::Base16Value::Base09)
.color32(*name);
@@ -197,7 +187,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
monocle_border,
floating_border,
unfocused_border,
unfocused_locked_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
@@ -211,10 +200,6 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
border_manager::FLOATING.store(u32::from(Colour::from(floating_border)), Ordering::SeqCst);
border_manager::UNFOCUSED
.store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst);
border_manager::UNFOCUSED_LOCKED.store(
u32::from(Colour::from(unfocused_locked_border)),
Ordering::SeqCst,
);
STACKBAR_TAB_BACKGROUND_COLOUR.store(
u32::from(Colour::from(stackbar_background)),

View File

@@ -4,17 +4,17 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU8;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU8;
use crate::should_act;
use crate::REGEX_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::Window;
use crate::WindowManager;
use crate::WindowsApi;
use crate::REGEX_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::should_act;
pub static TRANSPARENCY_ENABLED: AtomicBool = AtomicBool::new(false);
pub static TRANSPARENCY_ALPHA: AtomicU8 = AtomicU8::new(200);
@@ -49,13 +49,15 @@ pub fn send_notification() {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});
@@ -175,7 +177,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
match window.transparent() {
Err(error) => {
let hwnd = foreground_hwnd;
tracing::error!("failed to make unfocused window {hwnd} transparent: {error}" )
tracing::error!(
"failed to make unfocused window {hwnd} transparent: {error}"
)
}
Ok(..) => {
known_hwnds.lock().push(window.hwnd);

View File

@@ -1,8 +1,7 @@
use crate::animation::lerp::Lerp;
use crate::animation::prefix::new_animation_key;
use crate::animation::prefix::AnimationPrefix;
use crate::animation::AnimationEngine;
use crate::animation::RenderDispatcher;
use crate::AnimationStyle;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
@@ -10,44 +9,15 @@ use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_MANAGER;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::border_manager;
use crate::animation::AnimationEngine;
use crate::animation::RenderDispatcher;
use crate::animation::lerp::Lerp;
use crate::animation::prefix::AnimationPrefix;
use crate::animation::prefix::new_animation_key;
use crate::com::SetCloak;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::focus_manager;
use crate::stackbar_manager;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api;
use crate::windows_api::WindowsApi;
use crate::AnimationStyle;
use crate::FLOATING_APPLICATIONS;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
use color_eyre::eyre;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use regex::Regex;
use serde::ser::SerializeStruct;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
@@ -57,15 +27,47 @@ use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use color_eyre::Result;
use color_eyre::eyre;
use crossbeam_utils::atomic::AtomicConsume;
use regex::Regex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use serde::ser::SerializeStruct;
use strum::Display;
use strum::EnumString;
use windows::Win32::Foundation::HWND;
use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
pub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);
pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema, PartialEq)]
pub struct Window {
pub hwnd: isize,
}
@@ -85,8 +87,7 @@ impl From<HWND> for Window {
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct WindowDetails {
pub title: String,
pub exe: String,
@@ -298,8 +299,9 @@ impl RenderDispatcher for TransparencyRenderDispatcher {
}
}
#[derive(Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
)]
#[serde(untagged)]
pub enum AspectRatio {
/// A predefined aspect ratio
@@ -314,8 +316,9 @@ impl Default for AspectRatio {
}
}
#[derive(Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
)]
pub enum PredefinedAspectRatio {
/// 21:9
Ultrawide,
@@ -477,7 +480,7 @@ impl Window {
WindowsApi::is_window_visible(self.hwnd)
}
pub fn hide_with_border(self, hide_border: bool) {
pub fn hide(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
programmatically_hidden_hwnds.push(self.hwnd);
@@ -489,16 +492,9 @@ impl Window {
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd),
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2),
}
if hide_border {
border_manager::hide_border(self.hwnd);
}
}
pub fn hide(self) {
self.hide_with_border(true);
}
pub fn restore_with_border(self, restore_border: bool) {
pub fn restore(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds
.iter()
@@ -514,13 +510,6 @@ impl Window {
}
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0),
}
if restore_border {
border_manager::show_border(self.hwnd);
}
}
pub fn restore(self) {
self.restore_with_border(true);
}
pub fn minimize(self) {
@@ -753,8 +742,8 @@ impl Window {
/// it raises it as well.
pub fn raise(self) -> Result<()> {
WindowsApi::raise_window(self.hwnd)?;
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::raise_window(border_info.border_hwnd)?;
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::raise_window(border.hwnd)?;
}
Ok(())
}
@@ -765,8 +754,8 @@ impl Window {
/// it lowers it as well.
pub fn lower(self) -> Result<()> {
WindowsApi::lower_window(self.hwnd)?;
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::lower_window(border_info.border_hwnd)?;
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::lower_window(border.hwnd)?;
}
Ok(())
}
@@ -897,7 +886,7 @@ fn window_is_eligible(
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let should_ignore = if let Some(rule) = should_act(
let should_ignore = match should_act(
title,
exe_name,
class,
@@ -905,14 +894,15 @@ fn window_is_eligible(
&ignore_identifiers,
&regex_identifiers,
) {
debug.matches_ignore_identifier = Some(rule);
true
} else {
false
Some(rule) => {
debug.matches_ignore_identifier = Some(rule);
true
}
_ => false,
};
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
let managed_override = if let Some(rule) = should_act(
let managed_override = match should_act(
title,
exe_name,
class,
@@ -920,10 +910,11 @@ fn window_is_eligible(
&manage_identifiers,
&regex_identifiers,
) {
debug.matches_managed_override = Some(rule);
true
} else {
false
Some(rule) => {
debug.matches_managed_override = Some(rule);
true
}
_ => false,
};
let floating_identifiers = FLOATING_APPLICATIONS.lock();
@@ -943,7 +934,7 @@ fn window_is_eligible(
}
let layered_whitelist = LAYERED_WHITELIST.lock();
let mut allow_layered = if let Some(rule) = should_act(
let mut allow_layered = match should_act(
title,
exe_name,
class,
@@ -951,10 +942,11 @@ fn window_is_eligible(
&layered_whitelist,
&regex_identifiers,
) {
debug.matches_layered_whitelist = Some(rule);
true
} else {
false
Some(rule) => {
debug.matches_layered_whitelist = Some(rule);
true
}
_ => false,
};
let known_layered_hwnds = transparency_manager::known_hwnds();
@@ -981,7 +973,7 @@ fn window_is_eligible(
};
let titlebars_removed = NO_TITLEBAR.lock();
let allow_titlebar_removed = if let Some(rule) = should_act(
let allow_titlebar_removed = match should_act(
title,
exe_name,
class,
@@ -989,10 +981,11 @@ fn window_is_eligible(
&titlebars_removed,
&regex_identifiers,
) {
debug.matches_no_titlebar = Some(rule);
true
} else {
false
Some(rule) => {
debug.matches_no_titlebar = Some(rule);
true
}
_ => false,
};
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
use std::fmt::Display;
use std::fmt::Formatter;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::window::should_act;
use crate::window::Window;
use crate::winevent::WinEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::REGEX_IDENTIFIERS;
use crate::window::Window;
use crate::window::should_act;
use crate::winevent::WinEvent;
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "content")]
pub enum WindowManagerEvent {
Destroy(WinEvent, Window),

View File

@@ -4,15 +4,12 @@ use std::collections::VecDeque;
use std::convert::TryFrom;
use std::mem::size_of;
use color_eyre::Result;
use color_eyre::eyre::Error;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::eyre::Error;
use color_eyre::Result;
use windows::core::Result as WindowsCrateResult;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HMODULE;
@@ -21,8 +18,9 @@ use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::POINT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Dwm::DWMWA_BORDER_COLOR;
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
use windows::Win32::Graphics::Dwm::DWMWA_COLOR_NONE;
@@ -30,52 +28,56 @@ use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::RoundRect;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::Power::RegisterPowerSettingNotification;
use windows::Win32::System::Power::HPOWERNOTIFY;
use windows::Win32::System::Power::RegisterPowerSettingNotification;
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
use windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::Win32::System::Threading::OpenProcess;
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
@@ -86,15 +88,37 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use windows::Win32::UI::WindowsAndMessaging::IsZoomed;
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
use windows::Win32::UI::WindowsAndMessaging::MoveWindow;
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterDeviceNotificationW;
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
@@ -102,34 +126,6 @@ use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
@@ -140,24 +136,27 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::core::Result as WindowsCrateResult;
use windows_core::BOOL;
use crate::core::Rect;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::Window;
use crate::WindowManager;
use crate::container::Container;
use crate::monitor;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::set_window_position::SetWindowPosition;
use crate::windows_callbacks;
use crate::Window;
use crate::WindowManager;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::MONITOR_INDEX_PREFERENCES;
macro_rules! as_ptr {
($value:expr) => {
($value:expr_2021) => {
$value as *mut core::ffi::c_void
};
}
@@ -259,30 +258,7 @@ impl WindowsApi {
let monitors = &mut wm.monitors;
let monitor_usr_idx_map = &mut wm.monitor_usr_idx_map;
let all_displays = win32_display_data::connected_displays_all()
.flatten()
.collect::<Vec<_>>();
let mut serial_id_map = HashMap::new();
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
*serial_id_map.entry(id.clone()).or_insert(0) += 1;
}
}
for d in &all_displays {
if let Some(id) = &d.serial_number_id {
if serial_id_map.get(id).copied().unwrap_or_default() > 1 {
let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();
if !dupes.contains(id) {
(*dupes).push(id.clone());
}
}
}
}
'read: for mut display in all_displays {
'read: for display in win32_display_data::connected_displays_all().flatten() {
let path = display.device_path.clone();
let (device, device_id) = if path.is_empty() {
@@ -305,13 +281,6 @@ impl WindowsApi {
}
}
if let Some(id) = &display.serial_number_id {
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
if dupes.contains(id) {
display.serial_number_id = None;
}
}
let m = monitor::new(
display.hmonitor,
display.size.into(),
@@ -330,7 +299,7 @@ impl WindowsApi {
}
}
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
for (index, id) in &*display_index_preferences {
if m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || id.eq(m.device_id())
{
@@ -365,7 +334,7 @@ impl WindowsApi {
// Rebuild monitor index map
*monitor_usr_idx_map = HashMap::new();
let mut added_monitor_idxs = Vec::new();
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.read() {
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.lock() {
if let Some(m_idx) = monitors.elements().iter().position(|m| {
m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || m.device_id() == id
}) {
@@ -1003,7 +972,7 @@ impl WindowsApi {
}
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
for mut display in win32_display_data::connected_displays_all().flatten() {
for display in win32_display_data::connected_displays_all().flatten() {
if display.hmonitor == hmonitor {
let path = display.device_path;
@@ -1021,13 +990,6 @@ impl WindowsApi {
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
if let Some(id) = &display.serial_number_id {
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
if dupes.contains(id) {
display.serial_number_id = None;
}
}
let monitor = monitor::new(
hmonitor,
display.size.into(),
@@ -1075,7 +1037,9 @@ impl WindowsApi {
tracing::info!("current value of ForegroundLockTimeout is {value}");
if value != 0 {
tracing::info!("updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating");
tracing::info!(
"updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating"
);
Self::system_parameters_info_w(
SPI_SETFOREGROUNDLOCKTIMEOUT,
@@ -1198,7 +1162,7 @@ impl WindowsApi {
pub fn create_border_window(
name: PCWSTR,
instance: isize,
border: *mut Border,
border: *const Border,
) -> Result<isize> {
unsafe {
CreateWindowExW(

View File

@@ -12,11 +12,11 @@ use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
use windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW;
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
@@ -105,16 +105,12 @@ pub extern "system" fn win_event_hook(
WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy
) && !has_filtered_style(hwnd)
{
let border_info = border_manager::window_border(hwnd.0 as isize);
let border_window = border_manager::window_border(hwnd.0 as isize);
if let Some(border_info) = border_info {
if let Some(border) = border_window {
unsafe {
let _ = SendNotifyMessageW(
border_info.hwnd(),
event,
WPARAM(0),
LPARAM(hwnd.0 as isize),
);
let _ =
SendNotifyMessageW(border.hwnd(), event, WPARAM(0), LPARAM(hwnd.0 as isize));
}
}
}

View File

@@ -1,5 +1,6 @@
#![allow(clippy::use_self)]
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -88,8 +89,7 @@ use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[repr(u32)]
#[allow(dead_code)]
pub enum WinEvent {

View File

@@ -5,11 +5,11 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use windows::Win32::UI::Accessibility::SetWinEventHook;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_OUTOFCONTEXT;
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_SKIPOWNPROCESS;

View File

@@ -1,20 +1,19 @@
use std::collections::BTreeSet;
use std::collections::VecDeque;
use std::fmt::Display;
use std::fmt::Formatter;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::border_manager;
use crate::core::Axis;
use crate::core::CustomLayout;
use crate::core::CycleDirection;
@@ -23,10 +22,16 @@ use crate::core::Layout;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
use crate::WindowContainerBehaviour;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::container::Container;
use crate::locked_deque::LockedDeque;
use crate::ring::Ring;
use crate::should_act;
use crate::stackbar_manager;
@@ -35,19 +40,20 @@ use crate::static_config::WorkspaceConfig;
use crate::window::Window;
use crate::window::WindowDetails;
use crate::windows_api::WindowsApi;
use crate::WindowContainerBehaviour;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
#[allow(clippy::struct_field_names)]
#[derive(
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, PartialEq,
Debug,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Workspace {
#[getset(get = "pub", set = "pub")]
pub name: Option<String>,
@@ -92,15 +98,12 @@ pub struct Workspace {
pub globals: WorkspaceGlobals,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layer: WorkspaceLayer,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub locked_containers: BTreeSet<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
pub workspace_config: Option<WorkspaceConfig>,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum WorkspaceLayer {
#[default]
Tiling,
@@ -143,7 +146,6 @@ impl Default for Workspace {
layer: Default::default(),
globals: Default::default(),
workspace_config: None,
locked_containers: Default::default(),
}
}
}
@@ -167,9 +169,9 @@ pub enum WorkspaceWindowLocation {
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Settings setup either by the parent monitor or by the `WindowManager`
pub struct WorkspaceGlobals {
pub container_padding: Option<i32>,
@@ -287,7 +289,7 @@ impl Workspace {
}
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
if let Some(container) = self.monocle_container() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window() {
container.restore();
window.focus(mouse_follows_focus)?;
@@ -323,16 +325,10 @@ impl Workspace {
if self.maximized_window().is_none() && matches!(self.layer, WorkspaceLayer::Tiling) {
window.focus(mouse_follows_focus)?;
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.restore();
maximized_window.focus(mouse_follows_focus)?;
} else if let Some(floating_window) = self.floating_windows().first() {
floating_window.focus(mouse_follows_focus)?;
}
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.restore();
maximized_window.focus(mouse_follows_focus)?;
} else if let Some(floating_window) = self.floating_windows().first() {
floating_window.focus(mouse_follows_focus)?;
}
Ok(())
@@ -811,8 +807,9 @@ impl Workspace {
),
};
let insertion_idx = self.insert_container_at_idx(primary_idx, container);
self.resize_dimensions_mut()[insertion_idx] = resize;
self.containers_mut().insert(primary_idx, container);
self.resize_dimensions_mut().insert(primary_idx, resize);
self.focus_container(primary_idx);
Ok(())
@@ -828,38 +825,21 @@ impl Workspace {
self.focus_first_container();
}
// this fn respects locked container indexes - we should use it for pretty much everything
// except monocle and maximize toggles
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) -> usize {
let mut locked_containers = self.locked_containers().clone();
let mut ld = LockedDeque::new(self.containers_mut(), &mut locked_containers);
let insertion_idx = ld.insert(idx, container);
self.locked_containers = locked_containers;
if insertion_idx > self.resize_dimensions().len() {
self.resize_dimensions_mut().push(None);
} else {
self.resize_dimensions_mut().insert(insertion_idx, None);
}
self.focus_container(insertion_idx);
insertion_idx
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
self.containers_mut().insert(idx, container);
self.focus_container(idx);
}
// this fn respects locked container indexes - we should use it for pretty much everything
// except monocle and maximize toggles
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
let mut locked_containers = self.locked_containers().clone();
let mut ld = LockedDeque::new(self.containers_mut(), &mut locked_containers);
let container = ld.remove(idx);
self.locked_containers = locked_containers;
if idx < self.resize_dimensions().len() {
self.resize_dimensions_mut().remove(idx);
}
container
if idx < self.containers().len() {
return self.containers_mut().remove(idx);
}
None
}
fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {
@@ -874,8 +854,6 @@ impl Workspace {
}
pub fn remove_window(&mut self, hwnd: isize) -> Result<()> {
border_manager::delete_border(hwnd);
if self.floating_windows().iter().any(|w| w.hwnd == hwnd) {
self.floating_windows_mut().retain(|w| w.hwnd != hwnd);
return Ok(());
@@ -933,7 +911,15 @@ impl Workspace {
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.remove_container_by_idx(container_idx);
self.containers_mut()
.remove(container_idx)
.ok_or_else(|| anyhow!("there is no container"))?;
// Whenever a container is empty, we need to remove any resize dimensions for it too
if self.resize_dimensions().get(container_idx).is_some() {
self.resize_dimensions_mut().remove(container_idx);
}
self.focus_previous_container();
} else {
container.load_focused_window();
@@ -970,7 +956,6 @@ impl Workspace {
len,
)
}
pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option<usize> {
Option::from(direction.next_idx(
self.focused_container_idx(),
@@ -978,7 +963,6 @@ impl Workspace {
))
}
// this is what we use for stacking
pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> {
let focused_idx = self.focused_container_idx();
@@ -992,7 +976,8 @@ impl Workspace {
// This is a little messy
let adjusted_target_container_index = if container.windows().is_empty() {
self.remove_container_by_idx(focused_idx);
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
if focused_idx < target_container_idx {
target_container_idx.saturating_sub(1)
@@ -1031,7 +1016,8 @@ impl Workspace {
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.remove_container_by_idx(focused_container_idx);
self.containers_mut().remove(focused_container_idx);
self.resize_dimensions_mut().remove(focused_container_idx);
} else {
container.load_focused_window();
}
@@ -1051,8 +1037,8 @@ impl Workspace {
let mut container = Container::default();
container.add_window(window);
self.insert_container_at_idx(focused_idx, container);
self.containers_mut().insert(focused_idx, container);
self.resize_dimensions_mut().insert(focused_idx, None);
Ok(())
}
@@ -1067,7 +1053,19 @@ impl Workspace {
let mut container = Container::default();
container.add_window(window);
self.insert_container_at_idx(next_idx, container);
if next_idx > self.containers().len() {
self.containers_mut().push_back(container);
} else {
self.containers_mut().insert(next_idx, container);
}
if next_idx > self.resize_dimensions().len() {
self.resize_dimensions_mut().push(None);
} else {
self.resize_dimensions_mut().insert(next_idx, None);
}
self.focus_container(next_idx);
}
pub fn new_floating_window(&mut self) -> Result<()> {
@@ -1101,7 +1099,8 @@ impl Workspace {
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.remove_container_by_idx(focused_idx);
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
if focused_idx == self.containers().len() {
self.focus_container(focused_idx.saturating_sub(1));
@@ -1373,10 +1372,6 @@ impl Workspace {
pub fn new_monocle_container(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
// we shouldn't use remove_container_by_idx here because it doesn't make sense for
// monocle and maximized toggles which take over the whole screen before being reinserted
// at the same index to respect locked container indexes
let container = self
.containers_mut()
.remove(focused_idx)
@@ -1414,9 +1409,6 @@ impl Workspace {
.resize(restore_idx, Container::default());
}
// we shouldn't use insert_container_at_index here because it doesn't make sense for
// monocle and maximized toggles which take over the whole screen before being reinserted
// at the same index to respect locked container indexes
self.containers_mut().insert(restore_idx, container);
self.focus_container(restore_idx);
self.focused_container_mut()
@@ -1488,9 +1480,6 @@ impl Workspace {
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
// we shouldn't use remove_container_by_idx here because it doesn't make sense for
// monocle and maximized toggles which take over the whole screen before being reinserted
// at the same index to respect locked container indexes
self.containers_mut().remove(focused_idx);
if self.resize_dimensions().get(focused_idx).is_some() {
self.resize_dimensions_mut().remove(focused_idx);
@@ -1530,11 +1519,8 @@ impl Workspace {
let mut container = Container::default();
container.windows_mut().push_back(window);
// we shouldn't use insert_container_at_index here because it doesn't make sense for
// monocle and maximized toggles which take over the whole screen before being reinserted
// at the same index to respect locked container indexes
self.containers_mut().insert(restore_idx, container);
self.focus_container(restore_idx);
self.focused_container_mut()
@@ -1648,626 +1634,3 @@ impl Workspace {
self.focus_container(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::container::Container;
use crate::Window;
use std::collections::BTreeSet;
use std::collections::HashMap;
#[test]
fn test_locked_containers_with_new_window() {
let mut ws = Workspace::default();
let mut state = HashMap::new();
let mut locked = BTreeSet::new();
// add 3 containers
for i in 0..4 {
let container = Container::default();
state.insert(i, container.id().to_string());
ws.add_container_to_back(container);
}
assert_eq!(ws.containers().len(), 4);
// set index 3 locked
locked.insert(3);
ws.locked_containers = locked;
// focus container at index 2
ws.focus_container(2);
// simulate a new window being launched on this workspace
ws.new_container_for_window(Window::from(123));
// new length should be 5, with the focus on the new window at index 4
assert_eq!(ws.containers().len(), 5);
assert_eq!(ws.focused_container_idx(), 4);
assert_eq!(
ws.focused_container()
.unwrap()
.focused_window()
.unwrap()
.hwnd,
123
);
// when inserting a new container at index 0, index 3's container should not change
ws.focus_container(0);
ws.new_container_for_window(Window::from(234));
assert_eq!(
ws.containers()[3].id().to_string(),
state.get(&3).unwrap().to_string()
);
}
#[test]
fn test_locked_containers_remove_window() {
let mut ws = Workspace::default();
let mut locked = BTreeSet::new();
// add 4 containers
for i in 0..4 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
ws.add_container_to_back(container);
}
assert_eq!(ws.containers().len(), 4);
// set index 1 locked
locked.insert(1);
ws.locked_containers = locked;
ws.remove_window(0).unwrap();
assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 2);
// index 1 should still be the same
assert_eq!(ws.containers()[1].focused_window().unwrap().hwnd, 1);
assert_eq!(ws.containers()[2].focused_window().unwrap().hwnd, 3);
}
#[test]
fn test_locked_containers_toggle_float() {
let mut ws = Workspace::default();
let mut locked = BTreeSet::new();
// add 4 containers
for i in 0..4 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
ws.add_container_to_back(container);
}
assert_eq!(ws.containers().len(), 4);
// set index 1 locked
locked.insert(1);
ws.locked_containers = locked;
// set index 0 focused
ws.focus_container(0);
// float index 0
ws.new_floating_window().unwrap();
assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 2);
// index 1 should still be the same
assert_eq!(ws.containers()[1].focused_window().unwrap().hwnd, 1);
assert_eq!(ws.containers()[2].focused_window().unwrap().hwnd, 3);
// unfloat - have to do this semi-manually becuase of calls to WindowsApi in
// new_container_for_floating_window which usually handles unfloating
let window = ws.floating_windows_mut().pop().unwrap();
let mut container = Container::default();
container.add_window(window);
ws.insert_container_at_idx(ws.focused_container_idx(), container);
// all indexes should be at their original position
for i in 0..4 {
assert_eq!(
ws.containers()[i].focused_window().unwrap().hwnd,
i as isize
);
}
}
#[test]
fn test_locked_containers_stack() {
let mut ws = Workspace::default();
let mut locked = BTreeSet::new();
// add 6 containers
for i in 0..6 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
ws.add_container_to_back(container);
}
assert_eq!(ws.containers().len(), 6);
// set index 4 locked
locked.insert(4);
ws.locked_containers = locked;
// set index 3 focused
ws.focus_container(3);
// stack index 3 on top of index 2
ws.move_window_to_container(2).unwrap();
assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 0);
assert_eq!(ws.containers()[1].focused_window().unwrap().hwnd, 1);
assert_eq!(ws.containers()[2].windows().len(), 2);
assert_eq!(ws.containers()[3].focused_window().unwrap().hwnd, 5);
// index 4 should still be the same
assert_eq!(ws.containers()[4].focused_window().unwrap().hwnd, 4);
// unstack
ws.new_container_for_focused_window().unwrap();
// all indexes should be at their original position
for i in 0..6 {
assert_eq!(
ws.containers()[i].focused_window().unwrap().hwnd,
i as isize
)
}
}
#[test]
fn test_contains_window() {
// Create default workspace
let mut workspace = Workspace::default();
// Add a window to the container
let mut container = Container::default();
container.windows_mut().push_back(Window::from(0));
// Add container
workspace.add_container_to_back(container);
// Should be true
assert!(workspace.contains_window(0));
// Should be false
assert!(!workspace.is_empty())
}
#[test]
fn test_add_container_to_back() {
let mut workspace = Workspace::default();
{
// Container with 3 windows
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Get focused container. Should be the index of the last container added
let container = workspace.focused_container_mut().unwrap();
// Should be focused on the container with 1 window
assert_eq!(container.windows().len(), 1);
}
#[test]
fn test_add_container_to_front() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_front(container);
}
{
// Container with 3 windows
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_front(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Get focused container. Should be the index of the last container added
let container = workspace.focused_container_mut().unwrap();
// Should be focused on the container with 3 windows
assert_eq!(container.windows().len(), 3);
}
#[test]
fn test_remove_focused_container() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
{
// Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Should be focused on the container at index 1
assert_eq!(workspace.focused_container_idx(), 1);
// Store the container at index 1 before removal
let container_to_remove = workspace.containers().get(1).cloned();
workspace.remove_focused_container();
// Should only have 1 container
assert_eq!(workspace.containers().len(), 1);
// Should be focused on the container at index 0
assert_eq!(workspace.focused_container_idx(), 0);
// Ensure the container at index 1 before removal is no longer present
assert!(container_to_remove.is_some());
assert!(!workspace
.containers()
.contains(&container_to_remove.unwrap()));
}
#[test]
fn test_insert_container_at_idx() {
let mut workspace = Workspace::default();
for i in 0..4 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 4 containers
assert_eq!(workspace.containers().len(), 4);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 3);
// Insert a container at index 4
workspace.insert_container_at_idx(4, Container::default());
// Should have 5 containers
assert_eq!(workspace.containers().len(), 5);
// Should be focused on the newly inserted container
assert_eq!(workspace.focused_container_idx(), 4);
}
#[test]
fn test_remove_container_by_idx() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Store the container at index 1 before removal
let container_to_remove = workspace.containers().get(1).cloned();
// Remove the container at index 1
workspace.remove_container_by_idx(1);
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Ensure the container at index 1 before removal is no longer present
assert!(container_to_remove.is_some());
assert!(!workspace
.containers()
.contains(&container_to_remove.unwrap()));
}
#[test]
fn test_remove_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Store the container at index 2 before removal
let container_to_remove = workspace.containers().get(2).cloned();
// Remove the container at index 2
workspace.remove_container(2);
// Should be focused on the previous container which is index 1
assert_eq!(workspace.focused_container_idx(), 1);
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
// Ensure the container at index 1 before removal is no longer present
assert!(container_to_remove.is_some());
assert!(!workspace
.containers()
.contains(&container_to_remove.unwrap()));
}
#[test]
fn test_focus_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Focus on container 1
workspace.focus_container(1);
assert_eq!(workspace.focused_container_idx(), 1);
// Focus on container 0
workspace.focus_container(0);
assert_eq!(workspace.focused_container_idx(), 0);
// Focus on container 2
workspace.focus_container(2);
assert_eq!(workspace.focused_container_idx(), 2);
}
#[test]
fn test_focus_previous_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Focus on the previous container
workspace.focus_previous_container();
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 1);
}
#[test]
fn test_focus_last_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Change focus to the first container for the test
workspace.focus_container(0);
assert_eq!(workspace.focused_container_idx(), 0);
// Focus on the last container
workspace.focus_last_container();
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 2);
}
#[test]
fn test_focus_first_container() {
let mut workspace = Workspace::default();
for i in 0..3 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
// Should have 3 containers
assert_eq!(workspace.containers().len(), 3);
// Should be focused on the last container
assert_eq!(workspace.focused_container_idx(), 2);
// Focus on the first container
workspace.focus_first_container();
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 0);
}
#[test]
fn test_swap_containers() {
let mut workspace = Workspace::default();
{
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
{
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
{
// Should be focused on container 1
assert_eq!(workspace.focused_container_idx(), 1);
// Should have 1 window
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
// Swap containers 0 and 1
workspace.swap_containers(0, 1);
{
// Should be focused on container 0
assert_eq!(workspace.focused_container_idx(), 1);
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 3);
}
}
#[test]
fn test_new_container_for_window() {
let mut workspace = Workspace::default();
{
let mut container = Container::default();
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
}
// Add new window to container
workspace.new_container_for_window(Window::from(2));
// Container 0 should have 1 window
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
// Should return true that window 2 exists
assert!(workspace.contains_window(2));
}
#[test]
fn test_move_window_to_container() {
let mut workspace = Workspace::default();
{
// Container with 0 windows
let container = Container::default();
workspace.add_container_to_back(container);
}
{
// Container with 3 windows
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
// Move A Window from container 1 to container 0
workspace.move_window_to_container(0).unwrap();
// Focus on container 0
workspace.focus_container(0);
// Container 0 should have 1 window
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
#[test]
fn test_remove_window() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
// Remove window 1
workspace.remove_window(1).ok();
// Should have 2 windows
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 2);
// Check that window 1 is removed
assert!(!workspace.contains_window(1));
}
#[test]
fn test_new_container_for_focused_window() {
let mut workspace = Workspace::default();
{
// Container with 1 window
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
workspace.add_container_to_back(container);
}
// Add focused window to new container
workspace.new_container_for_focused_window().ok();
// Should have 2 containers
assert_eq!(workspace.containers().len(), 2);
{
// Inspect new container. Should contain 1 window. Window name should be 0
workspace.focus_container(1);
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
assert!(workspace.contains_window(0));
}
}
}

View File

@@ -1,7 +1,7 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::border_manager;
use crate::WindowManager;
use crate::border_manager;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
@@ -51,16 +51,18 @@ pub fn send_notification(monitor_idx: usize, workspace_idx: usize) {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
std::thread::spawn(move || {
loop {
match handle_notifications(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)
}
}
}
}

29
komorebi/tests/compat.rs Normal file
View File

@@ -0,0 +1,29 @@
use komorebi::StaticConfig;
#[test]
fn backwards_compat() {
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
let docs = vec![
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27", "0.1.28",
"0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33",
];
let mut versions = vec![];
let client = reqwest::blocking::Client::new();
for version in root {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for version in docs {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for (version, config) in versions {
println!("{version}");
StaticConfig::read_raw(&config).unwrap();
}
}

View File

@@ -1,9 +1,9 @@
[package]
name = "komorebic-no-console"
version = "0.1.36"
version = "0.1.35"
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,9 +1,9 @@
[package]
name = "komorebic"
version = "0.1.36"
version = "0.1.35"
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -21,7 +21,7 @@ miette = { version = "7", features = ["fancy"] }
paste = { workspace = true }
powershell_script = "1.0"
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true, optional = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
shadow-rs = { workspace = true }
@@ -34,9 +34,5 @@ windows = { workspace = true }
reqwest = { version = "0.12", features = ["blocking"] }
shadow-rs = { workspace = true }
[features]
default = ["schemars"]
schemars = ["dep:schemars"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] }

View File

@@ -17,29 +17,31 @@ use std::time::Duration;
use clap::CommandFactory;
use clap::Parser;
use clap::ValueEnum;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use dirs::data_local_dir;
use fs_tail::TailedFile;
use komorebi_client::ApplicationSpecificConfiguration;
use komorebi_client::Notification;
use komorebi_client::resolve_home_path;
use komorebi_client::send_message;
use komorebi_client::send_query;
use komorebi_client::AppSpecificConfigurationPath;
use komorebi_client::ApplicationSpecificConfiguration;
use lazy_static::lazy_static;
use miette::NamedSource;
use miette::Report;
use miette::SourceOffset;
use miette::SourceSpan;
use paste::paste;
use schemars::r#gen::SchemaSettings;
use schemars::schema_for;
use serde::Deserialize;
use sysinfo::ProcessesToUpdate;
use which::which;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use komorebi_client::ApplicationConfigurationGenerator;
use komorebi_client::ApplicationIdentifier;
@@ -156,7 +158,6 @@ gen_enum_subcommand_args! {
CycleMoveToMonitor: CycleDirection,
CycleMonitor: CycleDirection,
CycleWorkspace: CycleDirection,
CycleEmptyWorkspace: CycleDirection,
CycleMoveWorkspaceToMonitor: CycleDirection,
Stack: OperationDirection,
CycleStack: CycleDirection,
@@ -1131,9 +1132,6 @@ enum SubCommand {
/// Focus the workspace in the given cycle direction
#[clap(arg_required_else_help = true)]
CycleWorkspace(CycleWorkspace),
/// Focus the next empty workspace in the given cycle direction (if one exists)
#[clap(arg_required_else_help = true)]
CycleEmptyWorkspace(CycleWorkspace),
/// Move the focused workspace to the specified monitor
#[clap(arg_required_else_help = true)]
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
@@ -1283,8 +1281,6 @@ enum SubCommand {
ToggleMonocle,
/// Toggle native maximization for the focused window
ToggleMaximize,
/// Toggle a lock for the focused container, ensuring it will not be displaced by any new windows
ToggleLock,
/// Restore all hidden windows (debugging command)
RestoreWindows,
/// Force komorebi to manage the focused window
@@ -1545,7 +1541,9 @@ fn main() -> Result<()> {
let whkdrc = include_str!("../../docs/whkdrc.sample");
std::fs::write(WHKD_CONFIG_DIR.join("whkdrc"), whkdrc)?;
println!("Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created");
println!(
"Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created"
);
println!("You can now run komorebic start --whkd --bar");
}
SubCommand::EnableAutostart(args) => {
@@ -1591,8 +1589,12 @@ fn main() -> Result<()> {
.env("TARGET_ARGS", arguments)
.output()?;
println!("NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,");
println!("you need to add this to System Properties > Environment Variables > User Variables");
println!(
"NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,"
);
println!(
"you need to add this to System Properties > Environment Variables > User Variables"
);
println!("in order for the autostart command to work properly");
}
SubCommand::DisableAutostart => {
@@ -1656,19 +1658,25 @@ fn main() -> Result<()> {
println!("{:?}", Report::new(diagnostic));
}
println!("Found komorebi.json; this file can be passed to the start command with the --config flag\n");
println!(
"Found komorebi.json; this file can be passed to the start command with the --config flag\n"
);
if let Ok(config) = StaticConfig::read(&static_config) {
match config.app_specific_configuration_path {
None => {
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
println!(
"Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"
);
}
Some(AppSpecificConfigurationPath::Single(path)) => {
Some(path) => {
if !Path::exists(Path::new(&path)) {
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
println!(
"Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n",
path.display()
);
}
}
_ => {}
}
}
@@ -1684,9 +1692,14 @@ fn main() -> Result<()> {
StaticConfig::end_of_life(&raw);
if config_whkd.exists() {
println!("Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n", config_whkd.to_string_lossy());
println!(
"Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n",
config_whkd.to_string_lossy()
);
} else {
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
println!(
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
);
}
} else if config_pwsh.exists() {
println!("Found komorebi.ps1; this file will be autoloaded by komorebi\n");
@@ -1696,13 +1709,17 @@ fn main() -> Result<()> {
config_whkd.to_string_lossy()
);
} else {
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
println!(
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
);
}
} else if config_ahk.exists() {
println!("Found komorebi.ahk; this file will be autoloaded by komorebi\n");
} else {
println!("No komorebi configuration found in {home_display}\n");
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
println!(
"If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n"
);
}
let client = reqwest::blocking::Client::new();
@@ -1724,7 +1741,9 @@ fn main() -> Result<()> {
{
let trimmed = release.tag_name.trim_start_matches("v");
if trimmed > version {
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
println!(
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
);
}
}
}
@@ -1949,9 +1968,6 @@ fn main() -> Result<()> {
SubCommand::ToggleMaximize => {
send_message(&SocketMessage::ToggleMaximize)?;
}
SubCommand::ToggleLock => {
send_message(&SocketMessage::ToggleLock)?;
}
SubCommand::WorkspaceLayout(arg) => {
send_message(&SocketMessage::WorkspaceLayout(
arg.monitor,
@@ -2042,38 +2058,45 @@ fn main() -> Result<()> {
}
if arg.whkd && which("whkd").is_err() {
bail!("could not find whkd, please make sure it is installed before using the --whkd flag");
bail!(
"could not find whkd, please make sure it is installed before using the --whkd flag"
);
}
if arg.masir && which("masir").is_err() {
bail!("could not find masir, please make sure it is installed before using the --masir flag");
bail!(
"could not find masir, please make sure it is installed before using the --masir flag"
);
}
if arg.ahk && which(&ahk).is_err() {
bail!("could not find autohotkey, please make sure it is installed before using the --ahk flag");
bail!(
"could not find autohotkey, please make sure it is installed before using the --ahk flag"
);
}
let mut buf: PathBuf;
// The komorebi.ps1 shim will only exist in the Path if installed by Scoop
let exec = if let Ok(output) = Command::new("where.exe").arg("komorebi.ps1").output() {
let stdout = String::from_utf8(output.stdout)?;
match stdout.trim() {
"" => None,
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
stdout if !stdout.contains("scoop") => None,
stdout => {
buf = PathBuf::from(stdout);
buf.pop(); // %USERPROFILE%\scoop\shims
buf.pop(); // %USERPROFILE%\scoop
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
Some(buf.to_str().ok_or_else(|| {
anyhow!("cannot create a string from the scoop komorebi path")
})?)
let exec = match Command::new("where.exe").arg("komorebi.ps1").output() {
Ok(output) => {
let stdout = String::from_utf8(output.stdout)?;
match stdout.trim() {
"" => None,
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
stdout if !stdout.contains("scoop") => None,
stdout => {
buf = PathBuf::from(stdout);
buf.pop(); // %USERPROFILE%\scoop\shims
buf.pop(); // %USERPROFILE%\scoop
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
Some(buf.to_str().ok_or_else(|| {
anyhow!("cannot create a string from the scoop komorebi path")
})?)
}
}
}
} else {
None
_ => None,
};
let mut flags = vec![];
@@ -2275,18 +2298,28 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
println!("\nThank you for using komorebi!\n");
println!("# Commercial Use License");
println!("* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work");
println!(
"* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work"
);
println!("\n# Personal Use Sponsorship");
println!("* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference");
println!(
"* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference"
);
println!("* Leave a tip https://ko-fi.com/lgug2z - An alternative to GitHub Sponsors");
println!("\n# Community");
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
println!(
"* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops"
);
println!(
"* Subscribe to https://youtube.com/@LGUG2Z - Development videos, feature previews and release overviews"
);
println!("* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem");
println!(
"* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem"
);
println!("\n# Documentation");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
println!(
"* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands"
);
let bar_config = arg.config.map_or_else(
|| {
@@ -2333,7 +2366,9 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
{
let trimmed = release.tag_name.trim_start_matches("v");
if trimmed > version {
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
println!(
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
);
}
}
}
@@ -2635,11 +2670,6 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::CycleWorkspace(arg) => {
send_message(&SocketMessage::CycleFocusWorkspace(arg.cycle_direction))?;
}
SubCommand::CycleEmptyWorkspace(arg) => {
send_message(&SocketMessage::CycleFocusEmptyWorkspace(
arg.cycle_direction,
))?;
}
SubCommand::NewWorkspace => {
send_message(&SocketMessage::NewWorkspace)?;
}
@@ -2963,7 +2993,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
file.write_all(formatted_content.as_bytes())?;
println!("File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration");
println!(
"File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration"
);
}
SubCommand::FetchAppSpecificConfiguration => {
let content = reqwest::blocking::get("https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json")?
@@ -2979,50 +3011,40 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
file.write_all(content.as_bytes())?;
println!("Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n");
println!(
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
output_file.display().to_string().replace("\\", "/")
"Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n"
);
println!(
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
output_file.display().to_string().replace("\\", "/")
);
}
SubCommand::ApplicationSpecificConfigurationSchema => {
#[cfg(feature = "schemars")]
{
let asc = schemars::schema_for!(ApplicationSpecificConfiguration);
let schema = serde_json::to_string_pretty(&asc)?;
println!("{schema}");
}
let asc = schema_for!(ApplicationSpecificConfiguration);
let schema = serde_json::to_string_pretty(&asc)?;
println!("{schema}");
}
SubCommand::NotificationSchema => {
#[cfg(feature = "schemars")]
{
let notification = schemars::schema_for!(komorebi_client::Notification);
let schema = serde_json::to_string_pretty(&notification)?;
println!("{schema}");
}
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
println!("{schema}");
}
SubCommand::SocketSchema => {
#[cfg(feature = "schemars")]
{
let socket_message = schemars::schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}
let socket_message = schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}
SubCommand::StaticConfigSchema => {
#[cfg(feature = "schemars")]
{
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let settings = SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}
let r#gen = settings.into_generator();
let socket_message = r#gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}
SubCommand::GenerateStaticConfig => {
print_query(&SocketMessage::GenerateStaticConfig);

View File

@@ -55,12 +55,6 @@ nav:
- Installation: installation.md
- Example configurations: example-configurations.md
- Troubleshooting: troubleshooting.md
- Usage:
- usage/focusing-windows.md
- usage/moving-windows.md
- usage/stacking-windows.md
- usage/focusing-workspaces.md
- usage/moving-windows-across-workspaces.md
- Common workflows:
- common-workflows/komorebi-config-home.md
- common-workflows/autostart.md
@@ -77,7 +71,6 @@ nav:
- common-workflows/dynamic-layout-switching.md
- common-workflows/set-display-index.md
- common-workflows/multiple-bar-instances.md
- common-workflows/multi-monitor-setup.md
- Configuration reference: https://komorebi.lgug2z.com/schema
- Bar reference: https://komorebi-bar.lgug2z.com/schema
- CLI reference:
@@ -143,7 +136,6 @@ nav:
- cli/close-workspace.md
- cli/cycle-monitor.md
- cli/cycle-workspace.md
- cli/cycle-empty-workspace.md
- cli/move-workspace-to-monitor.md
- cli/cycle-move-workspace-to-monitor.md
- cli/swap-workspaces-with-monitor.md

View File

@@ -265,10 +265,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Date\": { \"enable\": true, \"format\": { \"Custom\": \"%D %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -515,53 +511,9 @@
"enable"
],
"properties": {
"display": {
"description": "Display format of the current layer",
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
},
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
},
"show_when_tiling": {
"description": "Show the widget event if the layer is Tiling",
"type": "boolean"
}
}
},
@@ -896,10 +848,6 @@
"format"
],
"properties": {
"changing_icon": {
"description": "Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)",
"type": "boolean"
},
"enable": {
"description": "Enable the Time widget",
"type": "boolean"
@@ -996,10 +944,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Time\": { \"enable\": true, \"format\": { \"Custom\": \"%T %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -1675,10 +1619,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Date\": { \"enable\": true, \"format\": { \"Custom\": \"%D %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -1925,53 +1865,9 @@
"enable"
],
"properties": {
"display": {
"description": "Display format of the current layer",
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
},
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
},
"show_when_tiling": {
"description": "Show the widget event if the layer is Tiling",
"type": "boolean"
}
}
},
@@ -2306,10 +2202,6 @@
"format"
],
"properties": {
"changing_icon": {
"description": "Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)",
"type": "boolean"
},
"enable": {
"description": "Enable the Time widget",
"type": "boolean"
@@ -2406,10 +2298,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Time\": { \"enable\": true, \"format\": { \"Custom\": \"%T %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -3018,10 +2906,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Date\": { \"enable\": true, \"format\": { \"Custom\": \"%D %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}
@@ -3268,53 +3152,9 @@
"enable"
],
"properties": {
"display": {
"description": "Display format of the current layer",
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
},
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
},
"show_when_tiling": {
"description": "Show the widget event if the layer is Tiling",
"type": "boolean"
}
}
},
@@ -3649,10 +3489,6 @@
"format"
],
"properties": {
"changing_icon": {
"description": "Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)",
"type": "boolean"
},
"enable": {
"description": "Enable the Time widget",
"type": "boolean"
@@ -3749,10 +3585,6 @@
]
}
]
},
"timezone": {
"description": "TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n\nUse a custom format to display additional information, i.e.: ```json { \"Time\": { \"enable\": true, \"format\": { \"Custom\": \"%T %Z (Tokyo)\" }, \"timezone\": \"Asia/Tokyo\" } } ```",
"type": "string"
}
}
}

View File

@@ -131,19 +131,7 @@
},
"app_specific_configuration_path": {
"description": "Path to applications.json from komorebi-application-specific-configurations (default: None)",
"anyOf": [
{
"description": "A single applications.json file",
"type": "string"
},
{
"description": "Multiple applications.json files",
"type": "array",
"items": {
"type": "string"
}
}
]
"type": "string"
},
"bar_configurations": {
"description": "Komorebi status bar configuration files for multiple instances on different monitors",
@@ -2211,38 +2199,6 @@
"Mantle",
"Crust"
]
},
"unfocused_locked_border": {
"description": "Border colour when the container is unfocused and locked (default: Red)",
"type": "string",
"enum": [
"Rosewater",
"Flamingo",
"Pink",
"Mauve",
"Red",
"Maroon",
"Peach",
"Yellow",
"Green",
"Teal",
"Sky",
"Sapphire",
"Blue",
"Lavender",
"Text",
"Subtext1",
"Subtext0",
"Overlay2",
"Overlay1",
"Overlay0",
"Surface2",
"Surface1",
"Surface0",
"Base",
"Mantle",
"Crust"
]
}
}
},
@@ -2732,28 +2688,6 @@
"Base0E",
"Base0F"
]
},
"unfocused_locked_border": {
"description": "Border colour when the container is unfocused and locked (default: Base08)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
}
}
}