Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 86f74a491d chore(deps): bump sysinfo from 0.37.2 to 0.38.0
---
updated-dependencies:
- dependency-name: sysinfo
  dependency-version: 0.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 16:39:25 +00:00
49 changed files with 2320 additions and 8477 deletions
-1
View File
@@ -1 +0,0 @@
*.json text diff
+6 -6
View File
@@ -13,8 +13,8 @@ on:
- hotfix/* - hotfix/*
tags: tags:
- v* - v*
schedule: # schedule:
- cron: "30 0 * * 0" # Every day at 00:30 UTC # - cron: "30 0 * * 0" # Every day at 00:30 UTC
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -65,7 +65,7 @@ jobs:
- run: | - run: |
cargo install cargo-wix cargo install cargo-wix
cargo wix --no-build -p komorebi --nocapture -I .\wix\main.wxs --target ${{ matrix.platform.target }} cargo wix --no-build -p komorebi --nocapture -I .\wix\main.wxs --target ${{ matrix.platform.target }}
- uses: actions/upload-artifact@v7 - uses: actions/upload-artifact@v6
with: with:
name: komorebi-${{ matrix.platform.target }}-${{ github.sha }} name: komorebi-${{ matrix.platform.target }}-${{ github.sha }}
path: | path: |
@@ -87,7 +87,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- shell: bash - shell: bash
run: echo "VERSION=nightly" >> $GITHUB_ENV run: echo "VERSION=nightly" >> $GITHUB_ENV
- uses: actions/download-artifact@v8 - uses: actions/download-artifact@v7
- run: | - run: |
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
@@ -136,7 +136,7 @@ jobs:
run: | run: |
TAG=${{ github.event.release.tag_name }} TAG=${{ github.event.release.tag_name }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- uses: actions/download-artifact@v8 - uses: actions/download-artifact@v7
- run: | - run: |
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
@@ -178,7 +178,7 @@ jobs:
run: | run: |
TAG=${{ github.ref_name }} TAG=${{ github.ref_name }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- uses: actions/download-artifact@v8 - uses: actions/download-artifact@v7
- run: | - run: |
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
-1
View File
@@ -12,4 +12,3 @@ komorebic/applications.json
/.xwin-cache /.xwin-cache
result result
/.direnv /.direnv
procdump.exe
Generated
+893 -1030
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -30,7 +30,7 @@ lazy_static = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = { package = "serde_json_lenient", version = "0.2" } serde_json = { package = "serde_json_lenient", version = "0.2" }
serde_yaml = "0.9" serde_yaml = "0.9"
strum = { version = "0.28", features = ["derive"] } strum = { version = "0.27", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-appender = "0.2" tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+2 -2
View File
@@ -79,7 +79,7 @@ Please refer to the [documentation](https://lgug2z.github.io/komorebi) for instr
to [install](https://lgug2z.github.io/komorebi/installation.html) and to [install](https://lgug2z.github.io/komorebi/installation.html) and
[configure](https://lgug2z.github.io/komorebi/example-configurations.html) [configure](https://lgug2z.github.io/komorebi/example-configurations.html)
_komorebi_, [common workflows](https://lgug2z.github.io/komorebi/common-workflows/komorebi-config-home.html), a complete _komorebi_, [common workflows](https://lgug2z.github.io/komorebi/common-workflows/komorebi-config-home.html), a complete
[configuration schema reference](https://komorebi-starlight.lgug2z.workers.dev/reference/komorebi-windows/) and a [configuration schema reference](https://komorebi.lgug2z.com/schema) and a
complete [CLI reference](https://lgug2z.github.io/komorebi/cli/quickstart.html). complete [CLI reference](https://lgug2z.github.io/komorebi/cli/quickstart.html).
## Community ## Community
@@ -424,7 +424,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. Below is a simple example of how to use `komorebi-client` in a basic Rust application.
```rust ```rust
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi" } // komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.39"}
use anyhow::Result; use anyhow::Result;
use komorebi_client::Notification; use komorebi_client::Notification;
+232 -227
View File
@@ -22,13 +22,13 @@
"ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index", "ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index",
"aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index", "aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index", "allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"anstream 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "anstream 0.6.21 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.14 registry+https://github.com/rust-lang/crates.io-index", "anstyle 1.0.13 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "anstyle-parse 0.2.7 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index", "anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index", "anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.102 registry+https://github.com/rust-lang/crates.io-index", "anyhow 1.0.100 registry+https://github.com/rust-lang/crates.io-index",
"approx 0.5.1 registry+https://github.com/rust-lang/crates.io-index", "approx 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.6.1 registry+https://github.com/rust-lang/crates.io-index", "arboard 3.6.1 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index", "arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"as-slice 0.2.1 registry+https://github.com/rust-lang/crates.io-index", "as-slice 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
@@ -41,38 +41,36 @@
"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index", "beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.3 registry+https://github.com/rust-lang/crates.io-index", "bit_field 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 1.3.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.11.1 registry+https://github.com/rust-lang/crates.io-index", "bitflags 2.10.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 4.10.0 registry+https://github.com/rust-lang/crates.io-index", "bitstream-io 4.9.0 registry+https://github.com/rust-lang/crates.io-index",
"block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index", "block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"by_address 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index", "bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index", "bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.61 registry+https://github.com/rust-lang/crates.io-index", "cc 1.2.55 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 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index", "cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"chacha20 0.10.0 registry+https://github.com/rust-lang/crates.io-index", "chrono 0.4.43 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.44 registry+https://github.com/rust-lang/crates.io-index",
"chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index", "chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.6.1 registry+https://github.com/rust-lang/crates.io-index", "clap 4.5.57 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.6.0 registry+https://github.com/rust-lang/crates.io-index", "clap_builder 4.5.57 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.6.1 registry+https://github.com/rust-lang/crates.io-index", "clap_derive 4.5.55 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "clap_lex 0.7.7 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index", "color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index", "color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.5 registry+https://github.com/rust-lang/crates.io-index", "colorchoice 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"core2 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index", "cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index",
"cpufeatures 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index", "crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index", "crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 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-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", "crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index", "crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.5.2 registry+https://github.com/rust-lang/crates.io-index", "ctrlc 3.5.1 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index", "curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index", "deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.5.8 registry+https://github.com/rust-lang/crates.io-index", "deranged 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
"digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index", "digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index", "dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index", "dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -97,32 +95,32 @@
"encoding_rs 0.8.35 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 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", "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.33.3 registry+https://github.com/rust-lang/crates.io-index", "epaint 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index", "epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 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", "eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fast-srgb8 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
"fdeflate 0.3.7 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.27 registry+https://github.com/rust-lang/crates.io-index", "filetime 0.2.27 registry+https://github.com/rust-lang/crates.io-index",
"find-msvc-tools 0.1.9 registry+https://github.com/rust-lang/crates.io-index", "find-msvc-tools 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.9 registry+https://github.com/rust-lang/crates.io-index", "flate2 1.1.9 registry+https://github.com/rust-lang/crates.io-index",
"fnv 1.0.7 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.2 registry+https://github.com/rust-lang/crates.io-index", "form_urlencoded 1.2.2 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.32 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.32 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.32 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.32 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.32 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.32 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.32 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.32 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.32 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.1.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.17 registry+https://github.com/rust-lang/crates.io-index", "getrandom 0.2.17 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index", "getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index", "gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.14.2 registry+https://github.com/rust-lang/crates.io-index", "gif 0.14.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.4 registry+https://github.com/rust-lang/crates.io-index", "git2 0.20.4 registry+https://github.com/rust-lang/crates.io-index",
"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index", "gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"glob 0.3.3 registry+https://github.com/rust-lang/crates.io-index", "glob 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
@@ -134,7 +132,7 @@
"hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.17.0 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.16.1 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index", "heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index", "hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index", "hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -145,26 +143,26 @@
"iana-time-zone 0.1.65 registry+https://github.com/rust-lang/crates.io-index", "iana-time-zone 0.1.65 registry+https://github.com/rust-lang/crates.io-index",
"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index", "ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.2 registry+https://github.com/rust-lang/crates.io-index", "idna_adapter 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.10 registry+https://github.com/rust-lang/crates.io-index", "image 0.25.9 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index", "image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"imgref 1.12.1 registry+https://github.com/rust-lang/crates.io-index", "imgref 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index", "indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index", "indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.14.0 registry+https://github.com/rust-lang/crates.io-index", "indexmap 2.13.0 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.12.0 registry+https://github.com/rust-lang/crates.io-index", "ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"iri-string 0.7.12 registry+https://github.com/rust-lang/crates.io-index", "iri-string 0.7.10 registry+https://github.com/rust-lang/crates.io-index",
"is_debug 1.1.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.2 registry+https://github.com/rust-lang/crates.io-index", "is_terminal_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 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.18 registry+https://github.com/rust-lang/crates.io-index", "itoa 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index", "jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index", "jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index",
"khronos_api 3.1.0 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", "lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.186 registry+https://github.com/rust-lang/crates.io-index", "libc 0.2.180 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index", "libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.28 registry+https://github.com/rust-lang/crates.io-index", "libz-sys 1.1.23 registry+https://github.com/rust-lang/crates.io-index",
"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index", "linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index",
"litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index", "lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index",
@@ -179,16 +177,15 @@
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index", "miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"moxcms 0.8.1 registry+https://github.com/rust-lang/crates.io-index", "moxcms 0.7.11 registry+https://github.com/rust-lang/crates.io-index",
"native-tls 0.2.18 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", "net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"no_std_io2 0.9.3 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 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.3 registry+https://github.com/rust-lang/crates.io-index", "ntapi 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num 0.4.3 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-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-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-conv 0.2.1 registry+https://github.com/rust-lang/crates.io-index", "num-conv 0.2.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-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-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-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
@@ -196,54 +193,57 @@
"num-rational 0.4.2 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", "num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"object 0.37.3 registry+https://github.com/rust-lang/crates.io-index", "object 0.37.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.21.4 registry+https://github.com/rust-lang/crates.io-index", "once_cell 1.21.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index", "once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"owned_ttf_parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index", "owned_ttf_parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"palette 0.7.6 registry+https://github.com/rust-lang/crates.io-index", "palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"palette_derive 0.7.6 registry+https://github.com/rust-lang/crates.io-index", "palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index", "parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index", "parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index", "paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"pastey 0.1.1 registry+https://github.com/rust-lang/crates.io-index", "pastey 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index", "percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.17 registry+https://github.com/rust-lang/crates.io-index", "pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.33 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.16.8 registry+https://github.com/rust-lang/crates.io-index", "png 0.16.8 registry+https://github.com/rust-lang/crates.io-index",
"png 0.18.1 registry+https://github.com/rust-lang/crates.io-index", "png 0.18.0 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 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.21 registry+https://github.com/rust-lang/crates.io-index", "ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.106 registry+https://github.com/rust-lang/crates.io-index", "proc-macro2 1.0.106 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index", "profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index", "profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.31 registry+https://github.com/rust-lang/crates.io-index", "psm 0.1.29 registry+https://github.com/rust-lang/crates.io-index",
"pxfm 0.1.29 registry+https://github.com/rust-lang/crates.io-index", "pxfm 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 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", "quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.45 registry+https://github.com/rust-lang/crates.io-index", "quote 1.0.44 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.10.1 registry+https://github.com/rust-lang/crates.io-index", "rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.6 registry+https://github.com/rust-lang/crates.io-index", "rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.9.4 registry+https://github.com/rust-lang/crates.io-index", "rand 0.9.2 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index", "rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.9.0 registry+https://github.com/rust-lang/crates.io-index", "rand_chacha 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.10.1 registry+https://github.com/rust-lang/crates.io-index", "rand_core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index", "rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.9.5 registry+https://github.com/rust-lang/crates.io-index", "rand_core 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_pcg 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 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.12.0 registry+https://github.com/rust-lang/crates.io-index", "rayon 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index", "rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index", "ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index", "ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.12.3 registry+https://github.com/rust-lang/crates.io-index", "regex 1.12.3 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.14 registry+https://github.com/rust-lang/crates.io-index", "regex-automata 0.4.14 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.10 registry+https://github.com/rust-lang/crates.io-index", "regex-syntax 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index", "reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index",
"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index", "roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.27 registry+https://github.com/rust-lang/crates.io-index", "rustc-demangle 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index", "rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.14.1 registry+https://github.com/rust-lang/crates.io-index", "rustls-pki-types 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.23 registry+https://github.com/rust-lang/crates.io-index", "ryu 1.0.22 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"semver 1.0.28 registry+https://github.com/rust-lang/crates.io-index", "semver 1.0.27 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index", "serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index", "serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.228 registry+https://github.com/rust-lang/crates.io-index", "serde_derive 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
@@ -252,47 +252,47 @@
"serde_json_lenient 0.2.4 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_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_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.18.0 registry+https://github.com/rust-lang/crates.io-index", "serde_with 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.18.0 registry+https://github.com/rust-lang/crates.io-index", "serde_with_macros 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index", "serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index", "serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index", "sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.7.1 registry+https://github.com/rust-lang/crates.io-index", "shadow-rs 1.7.0 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index", "shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index", "shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index", "shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 1.0.2 registry+https://github.com/rust-lang/crates.io-index", "siphasher 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.15.1 registry+https://github.com/rust-lang/crates.io-index", "smallvec 1.15.1 registry+https://github.com/rust-lang/crates.io-index",
"smol_str 0.2.2 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.6.3 registry+https://github.com/rust-lang/crates.io-index", "socket2 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index", "stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"stacker 0.1.24 registry+https://github.com/rust-lang/crates.io-index", "stacker 0.1.22 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.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-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"supports-hyperlinks 3.2.0 registry+https://github.com/rust-lang/crates.io-index", "supports-hyperlinks 3.2.0 registry+https://github.com/rust-lang/crates.io-index",
"supports-unicode 3.0.0 registry+https://github.com/rust-lang/crates.io-index", "supports-unicode 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"symlink 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index", "syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.117 registry+https://github.com/rust-lang/crates.io-index", "syn 2.0.114 registry+https://github.com/rust-lang/crates.io-index",
"sync_wrapper 1.0.2 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.27.0 registry+https://github.com/rust-lang/crates.io-index", "tempfile 3.24.0 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.4 registry+https://github.com/rust-lang/crates.io-index", "terminal_size 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 2.0.18 registry+https://github.com/rust-lang/crates.io-index", "thiserror 2.0.18 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 2.0.18 registry+https://github.com/rust-lang/crates.io-index", "thiserror-impl 2.0.18 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index", "thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.47 registry+https://github.com/rust-lang/crates.io-index", "time 0.3.46 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.8 registry+https://github.com/rust-lang/crates.io-index", "time-core 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index", "toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 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.20.0 registry+https://github.com/rust-lang/crates.io-index", "typenum 1.19.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.3 registry+https://github.com/rust-lang/crates.io-index", "tz-rs 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"tzdb 0.7.3 registry+https://github.com/rust-lang/crates.io-index", "tzdb 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.4 registry+https://github.com/rust-lang/crates.io-index", "tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index", "unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.24 registry+https://github.com/rust-lang/crates.io-index", "unicode-ident 1.0.22 registry+https://github.com/rust-lang/crates.io-index",
"unicode-linebreak 0.1.5 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.13.2 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.1.14 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index", "unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index", "unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
@@ -300,11 +300,10 @@
"url 2.5.8 registry+https://github.com/rust-lang/crates.io-index", "url 2.5.8 registry+https://github.com/rust-lang/crates.io-index",
"utf8_iter 1.0.4 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", "utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"uuid 1.23.1 registry+https://github.com/rust-lang/crates.io-index",
"vcpkg 0.2.15 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", "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", "web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.2.1 registry+https://github.com/rust-lang/crates.io-index", "webbrowser 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index", "weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index",
"win-msgbox 0.2.2 registry+https://github.com/rust-lang/crates.io-index", "win-msgbox 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index", "winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
@@ -347,6 +346,7 @@
"windows-strings 0.5.1 registry+https://github.com/rust-lang/crates.io-index", "windows-strings 0.5.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.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.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-sys 0.60.2 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.60.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.61.2 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.61.2 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.48.5 registry+https://github.com/rust-lang/crates.io-index",
@@ -363,15 +363,17 @@
"windows_x86_64_msvc 0.48.5 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", "windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index", "windows_x86_64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index",
"winit 0.30.13 registry+https://github.com/rust-lang/crates.io-index", "winit 0.30.12 registry+https://github.com/rust-lang/crates.io-index",
"wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index", "wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index", "yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.48 registry+https://github.com/rust-lang/crates.io-index", "zerocopy 0.8.38 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.8.48 registry+https://github.com/rust-lang/crates.io-index", "zerocopy-derive 0.8.38 registry+https://github.com/rust-lang/crates.io-index",
"zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index", "zeroize 1.8.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-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index", "zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 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.5.15 registry+https://github.com/rust-lang/crates.io-index" "zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.5.12 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
@@ -393,8 +395,8 @@
"av1-grain 0.2.5 registry+https://github.com/rust-lang/crates.io-index", "av1-grain 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"rav1e 0.8.1 registry+https://github.com/rust-lang/crates.io-index", "rav1e 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"v_frame 0.3.9 registry+https://github.com/rust-lang/crates.io-index", "v_frame 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.48 registry+https://github.com/rust-lang/crates.io-index", "zerocopy 0.8.38 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.8.48 registry+https://github.com/rust-lang/crates.io-index" "zerocopy-derive 0.8.38 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
@@ -402,7 +404,7 @@
[ [
"alloc-no-stdlib 2.0.4 registry+https://github.com/rust-lang/crates.io-index", "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", "alloc-stdlib 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"avif-serialize 0.8.8 registry+https://github.com/rust-lang/crates.io-index", "avif-serialize 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index", "brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index", "brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index",
"curve25519-dalek 4.1.3 registry+https://github.com/rust-lang/crates.io-index", "curve25519-dalek 4.1.3 registry+https://github.com/rust-lang/crates.io-index",
@@ -410,9 +412,9 @@
"encoding_rs 0.8.35 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.74.0 registry+https://github.com/rust-lang/crates.io-index", "exr 1.74.0 registry+https://github.com/rust-lang/crates.io-index",
"lebe 0.5.3 registry+https://github.com/rust-lang/crates.io-index", "lebe 0.5.3 registry+https://github.com/rust-lang/crates.io-index",
"moxcms 0.8.1 registry+https://github.com/rust-lang/crates.io-index", "moxcms 0.7.11 registry+https://github.com/rust-lang/crates.io-index",
"pxfm 0.1.29 registry+https://github.com/rust-lang/crates.io-index", "pxfm 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"ravif 0.13.0 registry+https://github.com/rust-lang/crates.io-index", "ravif 0.12.0 registry+https://github.com/rust-lang/crates.io-index",
"subtle 2.6.1 registry+https://github.com/rust-lang/crates.io-index" "subtle 2.6.1 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
@@ -421,16 +423,15 @@
[ [
"clipboard-win 5.4.1 registry+https://github.com/rust-lang/crates.io-index", "clipboard-win 5.4.1 registry+https://github.com/rust-lang/crates.io-index",
"error-code 3.3.2 registry+https://github.com/rust-lang/crates.io-index", "error-code 3.3.2 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.23 registry+https://github.com/rust-lang/crates.io-index" "ryu 1.0.22 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
"CC0-1.0", "CC0-1.0",
[ [
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index", "dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"fast-srgb8 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"file-id 0.1.0 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.12.1 registry+https://github.com/rust-lang/crates.io-index", "imgref 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"notify 6.1.1 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" "notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
] ]
@@ -456,12 +457,12 @@
"aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index", "aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"aligned-vec 0.6.4 registry+https://github.com/rust-lang/crates.io-index", "aligned-vec 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index", "allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"anstream 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "anstream 0.6.21 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.14 registry+https://github.com/rust-lang/crates.io-index", "anstyle 1.0.13 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "anstyle-parse 0.2.7 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index", "anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index", "anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.102 registry+https://github.com/rust-lang/crates.io-index", "anyhow 1.0.100 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.6.1 registry+https://github.com/rust-lang/crates.io-index", "arboard 3.6.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", "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", "arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
@@ -476,13 +477,12 @@
"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index", "beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.3 registry+https://github.com/rust-lang/crates.io-index", "bit_field 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 1.3.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.11.1 registry+https://github.com/rust-lang/crates.io-index", "bitflags 2.10.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 4.10.0 registry+https://github.com/rust-lang/crates.io-index", "bitstream-io 4.9.0 registry+https://github.com/rust-lang/crates.io-index",
"block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index", "block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index", "brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index", "brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index",
"built 0.8.0 registry+https://github.com/rust-lang/crates.io-index", "built 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"by_address 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index", "bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index", "bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index",
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index", "byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -491,39 +491,38 @@
"calm_io 0.1.1 registry+https://github.com/rust-lang/crates.io-index", "calm_io 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"calmio_filters 0.1.0 registry+https://github.com/rust-lang/crates.io-index", "calmio_filters 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"catppuccin-egui 5.6.0 git+https://github.com/LGUG2Z/catppuccin-egui?rev=b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a", "catppuccin-egui 5.6.0 git+https://github.com/LGUG2Z/catppuccin-egui?rev=b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a",
"cc 1.2.61 registry+https://github.com/rust-lang/crates.io-index", "cc 1.2.55 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 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index", "cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"cfg_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index", "cfg_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"chacha20 0.10.0 registry+https://github.com/rust-lang/crates.io-index", "chrono 0.4.43 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.44 registry+https://github.com/rust-lang/crates.io-index",
"chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index", "chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"chumsky 0.9.3 registry+https://github.com/rust-lang/crates.io-index", "chumsky 0.9.3 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.6.1 registry+https://github.com/rust-lang/crates.io-index", "clap 4.5.57 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.6.0 registry+https://github.com/rust-lang/crates.io-index", "clap_builder 4.5.57 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.6.1 registry+https://github.com/rust-lang/crates.io-index", "clap_derive 4.5.55 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "clap_lex 0.7.7 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index", "color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index", "color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"color-thief 0.2.2 registry+https://github.com/rust-lang/crates.io-index", "color-thief 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"color_quant 1.1.0 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.5 registry+https://github.com/rust-lang/crates.io-index", "colorchoice 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"core2 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index", "cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index",
"cpufeatures 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index", "crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index", "crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 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-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", "crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index", "crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.5.2 registry+https://github.com/rust-lang/crates.io-index", "ctrlc 3.5.1 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index", "curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"darling 0.23.0 registry+https://github.com/rust-lang/crates.io-index", "darling 0.21.3 registry+https://github.com/rust-lang/crates.io-index",
"darling_core 0.23.0 registry+https://github.com/rust-lang/crates.io-index", "darling_core 0.21.3 registry+https://github.com/rust-lang/crates.io-index",
"darling_macro 0.23.0 registry+https://github.com/rust-lang/crates.io-index", "darling_macro 0.21.3 registry+https://github.com/rust-lang/crates.io-index",
"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index", "deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.5.8 registry+https://github.com/rust-lang/crates.io-index", "deranged 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
"digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index", "digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index", "dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index", "dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -547,39 +546,40 @@
"encoding_rs 0.8.35 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 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", "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.33.3 registry+https://github.com/rust-lang/crates.io-index", "epaint 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index", "epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"equator 0.4.2 registry+https://github.com/rust-lang/crates.io-index", "equator 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"equator-macro 0.4.2 registry+https://github.com/rust-lang/crates.io-index", "equator-macro 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 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", "eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fast-srgb8 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.4.1 registry+https://github.com/rust-lang/crates.io-index", "fax 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"fax 0.2.7 registry+https://github.com/rust-lang/crates.io-index", "fax_derive 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"fdeflate 0.3.7 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.27 registry+https://github.com/rust-lang/crates.io-index", "filetime 0.2.27 registry+https://github.com/rust-lang/crates.io-index",
"find-msvc-tools 0.1.9 registry+https://github.com/rust-lang/crates.io-index", "find-msvc-tools 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.9 registry+https://github.com/rust-lang/crates.io-index", "flate2 1.1.9 registry+https://github.com/rust-lang/crates.io-index",
"flavours 0.7.2 git+https://github.com/LGUG2Z/flavours?rev=24518c129918fe3260aa559eded7657e50752cb1", "flavours 0.7.2 git+https://github.com/LGUG2Z/flavours",
"fnv 1.0.7 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", "font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.2 registry+https://github.com/rust-lang/crates.io-index", "form_urlencoded 1.2.2 registry+https://github.com/rust-lang/crates.io-index",
"fs-tail 0.1.4 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.32 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.32 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.32 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.32 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.32 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.32 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.32 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.32 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.32 registry+https://github.com/rust-lang/crates.io-index", "futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"generic-array 0.14.7 registry+https://github.com/rust-lang/crates.io-index", "generic-array 0.14.7 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.17 registry+https://github.com/rust-lang/crates.io-index", "getrandom 0.2.17 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index", "getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index", "gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.14.2 registry+https://github.com/rust-lang/crates.io-index", "gif 0.14.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.4 registry+https://github.com/rust-lang/crates.io-index", "git2 0.20.4 registry+https://github.com/rust-lang/crates.io-index",
"glob 0.3.3 registry+https://github.com/rust-lang/crates.io-index", "glob 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index", "glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -589,7 +589,7 @@
"hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.17.0 registry+https://github.com/rust-lang/crates.io-index", "hashbrown 0.16.1 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index", "heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index", "hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index", "hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -598,31 +598,31 @@
"http-body 1.0.1 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.3 registry+https://github.com/rust-lang/crates.io-index", "http-body-util 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index", "httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper 1.9.0 registry+https://github.com/rust-lang/crates.io-index", "hyper 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper-tls 0.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.20 registry+https://github.com/rust-lang/crates.io-index", "hyper-util 0.1.20 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.65 registry+https://github.com/rust-lang/crates.io-index", "iana-time-zone 0.1.65 registry+https://github.com/rust-lang/crates.io-index",
"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index", "ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.2 registry+https://github.com/rust-lang/crates.io-index", "idna_adapter 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"image 0.23.14 registry+https://github.com/rust-lang/crates.io-index", "image 0.23.14 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.10 registry+https://github.com/rust-lang/crates.io-index", "image 0.25.9 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index", "image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index", "indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index", "indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.14.0 registry+https://github.com/rust-lang/crates.io-index", "indexmap 2.13.0 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.12.0 registry+https://github.com/rust-lang/crates.io-index", "ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"iri-string 0.7.12 registry+https://github.com/rust-lang/crates.io-index", "iri-string 0.7.10 registry+https://github.com/rust-lang/crates.io-index",
"is_debug 1.1.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.2 registry+https://github.com/rust-lang/crates.io-index", "is_terminal_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 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.18 registry+https://github.com/rust-lang/crates.io-index", "itoa 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index", "jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index", "jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index",
"lazy_static 1.5.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.186 registry+https://github.com/rust-lang/crates.io-index", "libc 0.2.180 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index", "libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.28 registry+https://github.com/rust-lang/crates.io-index", "libz-sys 1.1.23 registry+https://github.com/rust-lang/crates.io-index",
"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index", "linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index",
"litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index", "litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index", "lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index",
@@ -634,7 +634,7 @@
"mac-addr 0.3.0 registry+https://github.com/rust-lang/crates.io-index", "mac-addr 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"matchers 0.2.0 registry+https://github.com/rust-lang/crates.io-index", "matchers 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"maybe-rayon 0.1.1 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.8.0 registry+https://github.com/rust-lang/crates.io-index", "memchr 2.7.6 registry+https://github.com/rust-lang/crates.io-index",
"memoffset 0.9.1 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 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
"mime_guess2 2.3.1 registry+https://github.com/rust-lang/crates.io-index", "mime_guess2 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
@@ -642,24 +642,23 @@
"miniz_oxide 0.3.7 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"mio 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "mio 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index", "miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"nanoid 0.4.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.18 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", "net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"netdev 0.41.0 registry+https://github.com/rust-lang/crates.io-index", "netdev 0.40.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", "new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"no_std_io2 0.9.3 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 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", "nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index",
"nom 8.0.0 registry+https://github.com/rust-lang/crates.io-index", "nom 8.0.0 registry+https://github.com/rust-lang/crates.io-index",
"noop_proc_macro 0.3.0 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.3 registry+https://github.com/rust-lang/crates.io-index", "ntapi 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"nu-ansi-term 0.50.3 registry+https://github.com/rust-lang/crates.io-index", "nu-ansi-term 0.50.3 registry+https://github.com/rust-lang/crates.io-index",
"num 0.4.3 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-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-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-conv 0.2.1 registry+https://github.com/rust-lang/crates.io-index", "num-conv 0.2.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-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-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-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
@@ -667,13 +666,13 @@
"num-rational 0.4.2 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", "num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"object 0.37.3 registry+https://github.com/rust-lang/crates.io-index", "object 0.37.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.21.4 registry+https://github.com/rust-lang/crates.io-index", "once_cell 1.21.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index", "once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"open 5.3.4 registry+https://github.com/rust-lang/crates.io-index", "open 5.3.3 registry+https://github.com/rust-lang/crates.io-index",
"os_info 3.14.0 registry+https://github.com/rust-lang/crates.io-index", "os_info 3.14.0 registry+https://github.com/rust-lang/crates.io-index",
"owo-colors 4.3.0 registry+https://github.com/rust-lang/crates.io-index", "owo-colors 4.2.3 registry+https://github.com/rust-lang/crates.io-index",
"palette 0.7.6 registry+https://github.com/rust-lang/crates.io-index", "palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"palette_derive 0.7.6 registry+https://github.com/rust-lang/crates.io-index", "palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index", "parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index", "parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index", "paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
@@ -681,54 +680,59 @@
"percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index", "percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index",
"phf 0.11.3 registry+https://github.com/rust-lang/crates.io-index", "phf 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf 0.12.1 registry+https://github.com/rust-lang/crates.io-index", "phf 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
"phf_generator 0.11.3 registry+https://github.com/rust-lang/crates.io-index", "phf 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"phf_macros 0.11.3 registry+https://github.com/rust-lang/crates.io-index", "phf_codegen 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"phf_generator 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"phf_shared 0.11.3 registry+https://github.com/rust-lang/crates.io-index", "phf_shared 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf_shared 0.12.1 registry+https://github.com/rust-lang/crates.io-index", "phf_shared 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.17 registry+https://github.com/rust-lang/crates.io-index", "phf_shared 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.33 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.16.8 registry+https://github.com/rust-lang/crates.io-index", "png 0.16.8 registry+https://github.com/rust-lang/crates.io-index",
"png 0.18.1 registry+https://github.com/rust-lang/crates.io-index", "png 0.18.0 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 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", "powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index", "ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.106 registry+https://github.com/rust-lang/crates.io-index", "proc-macro2 1.0.106 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index", "profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index", "profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.31 registry+https://github.com/rust-lang/crates.io-index", "psm 0.1.29 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 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", "quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.45 registry+https://github.com/rust-lang/crates.io-index", "quote 1.0.44 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.10.1 registry+https://github.com/rust-lang/crates.io-index", "rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.6 registry+https://github.com/rust-lang/crates.io-index", "rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.9.4 registry+https://github.com/rust-lang/crates.io-index", "rand 0.9.2 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index", "rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.9.0 registry+https://github.com/rust-lang/crates.io-index", "rand_chacha 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.10.1 registry+https://github.com/rust-lang/crates.io-index", "rand_core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index", "rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.9.5 registry+https://github.com/rust-lang/crates.io-index", "rand_core 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_pcg 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"random_word 0.5.2 registry+https://github.com/rust-lang/crates.io-index", "random_word 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 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.12.0 registry+https://github.com/rust-lang/crates.io-index", "rayon 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index", "rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index", "ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index", "ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.12.3 registry+https://github.com/rust-lang/crates.io-index", "regex 1.12.3 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.14 registry+https://github.com/rust-lang/crates.io-index", "regex-automata 0.4.14 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.10 registry+https://github.com/rust-lang/crates.io-index", "regex-syntax 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index", "reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index",
"rgb 0.8.53 registry+https://github.com/rust-lang/crates.io-index", "rgb 0.8.52 registry+https://github.com/rust-lang/crates.io-index",
"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index", "roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.27 registry+https://github.com/rust-lang/crates.io-index", "rustc-demangle 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index", "rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.14.1 registry+https://github.com/rust-lang/crates.io-index", "rustls-pki-types 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"same-file 1.0.6 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.29 registry+https://github.com/rust-lang/crates.io-index", "schannel 0.1.28 registry+https://github.com/rust-lang/crates.io-index",
"schemars 1.2.1 registry+https://github.com/rust-lang/crates.io-index", "schemars 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"schemars_derive 1.2.1 registry+https://github.com/rust-lang/crates.io-index", "schemars_derive 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"scoped_threadpool 0.1.9 registry+https://github.com/rust-lang/crates.io-index", "scoped_threadpool 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"semver 1.0.28 registry+https://github.com/rust-lang/crates.io-index", "semver 1.0.27 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index", "serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index", "serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.228 registry+https://github.com/rust-lang/crates.io-index", "serde_derive 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
@@ -737,49 +741,47 @@
"serde_json_lenient 0.2.4 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_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_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.18.0 registry+https://github.com/rust-lang/crates.io-index", "serde_with 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.18.0 registry+https://github.com/rust-lang/crates.io-index", "serde_with_macros 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index", "serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index", "serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index", "sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.7.1 registry+https://github.com/rust-lang/crates.io-index", "shadow-rs 1.7.0 registry+https://github.com/rust-lang/crates.io-index",
"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index", "sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index", "shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index", "shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index", "shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"simd-adler32 0.3.9 registry+https://github.com/rust-lang/crates.io-index", "simd-adler32 0.3.8 registry+https://github.com/rust-lang/crates.io-index",
"simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index", "simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 1.0.2 registry+https://github.com/rust-lang/crates.io-index", "siphasher 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"slab 0.4.12 registry+https://github.com/rust-lang/crates.io-index", "slab 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.15.1 registry+https://github.com/rust-lang/crates.io-index", "smallvec 1.15.1 registry+https://github.com/rust-lang/crates.io-index",
"smol_str 0.2.2 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.6.3 registry+https://github.com/rust-lang/crates.io-index", "socket2 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index", "stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"stacker 0.1.24 registry+https://github.com/rust-lang/crates.io-index", "stacker 0.1.22 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.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", "strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index",
"strum 0.28.0 registry+https://github.com/rust-lang/crates.io-index", "strum 0.27.2 registry+https://github.com/rust-lang/crates.io-index",
"strum_macros 0.28.0 registry+https://github.com/rust-lang/crates.io-index", "strum_macros 0.27.2 registry+https://github.com/rust-lang/crates.io-index",
"symlink 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index", "syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.117 registry+https://github.com/rust-lang/crates.io-index", "syn 2.0.114 registry+https://github.com/rust-lang/crates.io-index",
"synstructure 0.13.2 registry+https://github.com/rust-lang/crates.io-index", "synstructure 0.13.2 registry+https://github.com/rust-lang/crates.io-index",
"sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index", "sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index",
"sysinfo 0.38.4 registry+https://github.com/rust-lang/crates.io-index", "sysinfo 0.37.2 registry+https://github.com/rust-lang/crates.io-index",
"systray-util 0.2.0 registry+https://github.com/rust-lang/crates.io-index", "tempfile 3.24.0 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.27.0 registry+https://github.com/rust-lang/crates.io-index", "terminal_size 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"textwrap 0.16.2 registry+https://github.com/rust-lang/crates.io-index", "textwrap 0.16.2 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 2.0.18 registry+https://github.com/rust-lang/crates.io-index", "thiserror 2.0.18 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 2.0.18 registry+https://github.com/rust-lang/crates.io-index", "thiserror-impl 2.0.18 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index", "thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index",
"tiff 0.11.3 registry+https://github.com/rust-lang/crates.io-index", "tiff 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"tiff 0.6.1 registry+https://github.com/rust-lang/crates.io-index", "tiff 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.47 registry+https://github.com/rust-lang/crates.io-index", "time 0.3.46 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.8 registry+https://github.com/rust-lang/crates.io-index", "time-core 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"tokio 1.52.1 registry+https://github.com/rust-lang/crates.io-index", "tokio 1.49.0 registry+https://github.com/rust-lang/crates.io-index",
"tokio-macros 2.7.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-native-tls 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"tokio-util 0.7.18 registry+https://github.com/rust-lang/crates.io-index", "tokio-util 0.7.18 registry+https://github.com/rust-lang/crates.io-index",
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index", "toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
@@ -788,21 +790,21 @@
"tower-layer 0.3.3 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", "tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"tracing 0.1.44 registry+https://github.com/rust-lang/crates.io-index", "tracing 0.1.44 registry+https://github.com/rust-lang/crates.io-index",
"tracing-appender 0.2.5 registry+https://github.com/rust-lang/crates.io-index", "tracing-appender 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"tracing-attributes 0.1.31 registry+https://github.com/rust-lang/crates.io-index", "tracing-attributes 0.1.31 registry+https://github.com/rust-lang/crates.io-index",
"tracing-core 0.1.36 registry+https://github.com/rust-lang/crates.io-index", "tracing-core 0.1.36 registry+https://github.com/rust-lang/crates.io-index",
"tracing-error 0.2.1 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-log 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"tracing-subscriber 0.3.23 registry+https://github.com/rust-lang/crates.io-index", "tracing-subscriber 0.3.22 registry+https://github.com/rust-lang/crates.io-index",
"try-lock 0.2.5 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", "ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.20.0 registry+https://github.com/rust-lang/crates.io-index", "typenum 1.19.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.3 registry+https://github.com/rust-lang/crates.io-index", "tz-rs 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.4 registry+https://github.com/rust-lang/crates.io-index", "tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"uds_windows 1.2.1 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.9.0 registry+https://github.com/rust-lang/crates.io-index", "unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.24 registry+https://github.com/rust-lang/crates.io-index", "unicode-ident 1.0.22 registry+https://github.com/rust-lang/crates.io-index",
"unicode-segmentation 1.13.2 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.1.14 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index", "unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index", "unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
@@ -811,15 +813,14 @@
"url 2.5.8 registry+https://github.com/rust-lang/crates.io-index", "url 2.5.8 registry+https://github.com/rust-lang/crates.io-index",
"utf8_iter 1.0.4 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", "utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"uuid 1.23.1 registry+https://github.com/rust-lang/crates.io-index",
"vcpkg 0.2.15 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", "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", "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", "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", "web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.2.1 registry+https://github.com/rust-lang/crates.io-index", "webbrowser 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index", "weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index",
"which 8.0.2 registry+https://github.com/rust-lang/crates.io-index", "which 8.0.0 registry+https://github.com/rust-lang/crates.io-index",
"win-msgbox 0.2.2 registry+https://github.com/rust-lang/crates.io-index", "win-msgbox 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"winapi 0.3.9 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.11 registry+https://github.com/rust-lang/crates.io-index", "winapi-util 0.1.11 registry+https://github.com/rust-lang/crates.io-index",
@@ -864,6 +865,7 @@
"windows-strings 0.5.1 registry+https://github.com/rust-lang/crates.io-index", "windows-strings 0.5.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.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.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-sys 0.60.2 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.60.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.61.2 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.61.2 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.48.5 registry+https://github.com/rust-lang/crates.io-index",
@@ -881,25 +883,28 @@
"windows_x86_64_msvc 0.52.6 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",
"windows_x86_64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index", "windows_x86_64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index",
"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index", "winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"winreg 0.56.0 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.2 registry+https://github.com/rust-lang/crates.io-index", "wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"xml-rs 0.8.28 registry+https://github.com/rust-lang/crates.io-index", "xml-rs 0.8.28 registry+https://github.com/rust-lang/crates.io-index",
"y4m 0.8.0 registry+https://github.com/rust-lang/crates.io-index", "y4m 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index", "yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.48 registry+https://github.com/rust-lang/crates.io-index", "zerocopy 0.8.38 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.8.48 registry+https://github.com/rust-lang/crates.io-index", "zerocopy-derive 0.8.38 registry+https://github.com/rust-lang/crates.io-index",
"zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index", "zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index",
"zmij 1.0.21 registry+https://github.com/rust-lang/crates.io-index", "zmij 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index", "zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 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.5.15 registry+https://github.com/rust-lang/crates.io-index" "zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.5.12 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
"MIT-0", "MIT-0",
[ [
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index", "dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.4 registry+https://github.com/rust-lang/crates.io-index" "tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
@@ -924,25 +929,25 @@
[ [
"Unicode-3.0", "Unicode-3.0",
[ [
"icu_collections 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_collections 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_locale_core 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_locale_core 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_normalizer 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer_data 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_normalizer_data 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_properties 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties_data 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_properties_data 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider 2.2.0 registry+https://github.com/rust-lang/crates.io-index", "icu_provider 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"litemap 0.8.2 registry+https://github.com/rust-lang/crates.io-index", "litemap 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"potential_utf 0.1.5 registry+https://github.com/rust-lang/crates.io-index", "potential_utf 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"tinystr 0.8.3 registry+https://github.com/rust-lang/crates.io-index", "tinystr 0.8.2 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.24 registry+https://github.com/rust-lang/crates.io-index", "unicode-ident 1.0.22 registry+https://github.com/rust-lang/crates.io-index",
"writeable 0.6.3 registry+https://github.com/rust-lang/crates.io-index", "writeable 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"yoke 0.8.2 registry+https://github.com/rust-lang/crates.io-index", "yoke 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"yoke-derive 0.8.2 registry+https://github.com/rust-lang/crates.io-index", "yoke-derive 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"zerofrom 0.1.7 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.7 registry+https://github.com/rust-lang/crates.io-index", "zerofrom-derive 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"zerotrie 0.2.4 registry+https://github.com/rust-lang/crates.io-index", "zerotrie 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"zerovec 0.11.6 registry+https://github.com/rust-lang/crates.io-index", "zerovec 0.11.5 registry+https://github.com/rust-lang/crates.io-index",
"zerovec-derive 0.11.3 registry+https://github.com/rust-lang/crates.io-index" "zerovec-derive 0.11.2 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
@@ -951,7 +956,7 @@
"aho-corasick 1.1.4 registry+https://github.com/rust-lang/crates.io-index", "aho-corasick 1.1.4 registry+https://github.com/rust-lang/crates.io-index",
"byteorder 1.5.0 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", "byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"memchr 2.8.0 registry+https://github.com/rust-lang/crates.io-index", "memchr 2.7.6 registry+https://github.com/rust-lang/crates.io-index",
"same-file 1.0.6 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", "walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
"winapi-util 0.1.11 registry+https://github.com/rust-lang/crates.io-index" "winapi-util 0.1.11 registry+https://github.com/rust-lang/crates.io-index"
@@ -963,19 +968,19 @@
"adler32 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "adler32 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index", "bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index", "bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index",
"const_format 0.2.36 registry+https://github.com/rust-lang/crates.io-index", "const_format 0.2.35 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", "const_format_proc_macros 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"foldhash 0.1.5 registry+https://github.com/rust-lang/crates.io-index", "foldhash 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index", "glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"konst 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
"konst_macro_rules 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index", "miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 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-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index", "zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 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.5.15 registry+https://github.com/rust-lang/crates.io-index" "zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.5.12 registry+https://github.com/rust-lang/crates.io-index"
] ]
] ]
] ]
@@ -1,218 +0,0 @@
# System Tray
The System Tray widget brings native Windows system tray icons into
`komorebi-bar`. It intercepts tray icon data by creating a hidden window that
mimics the Windows taskbar, receiving the same broadcast messages that
applications send via `Shell_NotifyIcon`.
## Basic configuration
```json
{
"right_widgets": [
{
"Systray": {
"enable": true
}
}
]
}
```
## Hiding icons
The `hidden_icons` config field accepts a list of rules. Each rule can be either
a plain string or a structured object.
A **plain string** matches the exe name (case-insensitive). This is the original
format, so existing configs continue to work without changes:
```json
"hidden_icons": [
"SecurityHealthSystray.exe",
"PhoneExperienceHost.exe"
]
```
A **structured object** matches one or more icon properties. All specified fields
must match (AND logic). By default matching is exact and case-insensitive.
```json
"hidden_icons": [
{ "exe": "svchost.exe", "tooltip": "Some Specific App" },
{ "guid": "{7820AE73-23E3-4229-82C1-E41CB67D5B9C}" },
{ "tooltip": "App I want hidden" }
]
```
The two forms can be mixed freely:
```json
"hidden_icons": [
"PhoneExperienceHost.exe",
{ "exe": "svchost.exe", "tooltip": "Specific Notification" },
{ "guid": "{7820AE73-23E3-4229-82C1-E41CB67D5B9C}" }
]
```
Available fields for structured rules:
| Field | Description |
|-----------|----------------------------------------------------------|
| `exe` | Executable name (e.g. `"SecurityHealthSystray.exe"`) |
| `tooltip` | Tooltip text shown on hover |
| `guid` | Icon GUID — most stable identifier across app restarts |
### Matching strategies
Each field can be a plain string (exact case-insensitive match) or an object
with `value` and `matching_strategy` for advanced matching. This uses the same
`MatchingStrategy` as komorebi's window rules.
```json
"hidden_icons": [
{
"exe": "explorer.exe",
"tooltip": { "value": "Network", "matching_strategy": "StartsWith" }
}
]
```
The above hides explorer.exe icons whose tooltip starts with "Network", while
leaving other explorer.exe icons visible.
Available strategies:
| Strategy | Description |
|---------------------|---------------------------------------------------|
| `Equals` | Exact match (default when using a plain string) |
| `StartsWith` | Value starts with the given text |
| `EndsWith` | Value ends with the given text |
| `Contains` | Value contains the given text |
| `Regex` | Value matches a regular expression |
| `DoesNotEqual` | Value does not exactly equal the given text |
| `DoesNotStartWith` | Value does not start with the given text |
| `DoesNotEndWith` | Value does not end with the given text |
| `DoesNotContain` | Value does not contain the given text |
All strategies except `Regex` are case-insensitive. For case-insensitive regex,
include `(?i)` in the pattern.
Plain strings and strategy objects can be mixed across fields:
```json
{
"exe": "explorer.exe",
"tooltip": { "value": "notification", "matching_strategy": "Contains" }
}
```
Run komorebi-bar with `RUST_LOG=info` to see the exe, tooltip, and GUID of every
systray icon in the log output.
## Stale icon cleanup
Some applications (e.g. Docker Desktop) may exit without properly removing their
tray icon. The widget detects these stale icons by checking whether the owning
window still exists via the Win32 `IsWindow` API.
### Automatic cleanup
By default, the widget checks for stale icons every 60 seconds. The interval
can be configured with `stale_icons_check_interval` (in seconds). The value is
clamped between 30 and 600. Set to 0 to disable automatic cleanup.
```json
"stale_icons_check_interval": 120
```
### Refresh button
A manual refresh button can be shown by setting `refresh_button`. Clicking it
immediately removes any stale icons.
- `"Visible"` — shows the button in the main icon area
- `"Overflow"` — shows the button in the hidden/overflow section (appears when
the overflow toggle is expanded)
```json
"refresh_button": "Overflow"
```
When set to `"Overflow"`, the overflow toggle arrow will appear even if there are
no hidden icons, so the refresh button remains accessible.
## Info button
An info button can be shown to open a floating panel that lists all systray icons
with their exe name, tooltip, GUID, and visibility status. This is useful for
identifying which icons to filter with `hidden_icons` rules.
- `"Visible"` — shows the button in the main icon area
- `"Overflow"` — shows the button in the hidden/overflow section
```json
"info_button": "Visible"
```
The info panel shows **all** icons, including those hidden by rules or the OS.
Each row shows the icon image, exe name, tooltip, GUID, and whether it is visible.
Copy buttons are provided on the exe, tooltip, and GUID cells for easy copying
(e.g. to paste a GUID into a filter rule).
Like the refresh button, setting `info_button` to `"Overflow"` will make the
overflow toggle arrow appear even if there are no hidden icons.
## Shortcuts button
A button that toggles komorebi-shortcuts. If the shortcuts process is running
it will be killed; otherwise it will be started.
- `"Visible"` — shows the button in the main icon area
- `"Overflow"` — shows the button in the hidden/overflow section
```json
"shortcuts_button": "Visible"
```
Like the other buttons, setting `shortcuts_button` to `"Overflow"` will make the
overflow toggle arrow appear even if there are no hidden icons.
## Mouse interactions
The widget supports left-click, right-click, middle-click, and double-click on
tray icons. Double-click sends the `LeftDoubleClick` action (via systray-util
0.2.0), which delivers `WM_LBUTTONDBLCLK` and `NIN_SELECT` messages to the icon.
## Click fallbacks
Some systray icons register a click callback but never actually respond to click
messages, effectively becoming "zombie" icons from an interaction standpoint. For
known problematic icons, the widget overrides the native click action with a
direct shell command. Fallback commands take priority — if a fallback is defined
for an icon, it always runs regardless of whether the icon reports itself as
clickable.
| Exe | Tooltip condition | Fallback command |
|--------------------------------|-------------------|---------------------------------|
| `SecurityHealthSystray.exe` | any | `start windowsdefender://` |
| `explorer.exe` | ends with `%` | `start ms-settings:apps-volume` |
| `explorer.exe` | empty | `start ms-settings:batterysaver`|
## Full example
```json
{
"Systray": {
"enable": true,
"hidden_icons": [
"SecurityHealthSystray.exe",
{ "exe": "explorer.exe", "tooltip": { "value": "Network", "matching_strategy": "StartsWith" } }
],
"stale_icons_check_interval": 60,
"refresh_button": "Overflow",
"info_button": "Visible",
"shortcuts_button": "Overflow"
}
}
```
-40
View File
@@ -1,40 +0,0 @@
# Komorebi Bar
`komorebi-bar` is a status bar for komorebi that renders on top of the tiling
window manager. It is configured through a `komorebi.bar.json` file, either
alongside your `komorebi.json` or at the path specified in the
`bar_configurations` array.
## Widgets
Widgets are placed in the `left_widgets`, `center_widgets`, or `right_widgets`
arrays. Each widget is an object with the widget type as key and its
configuration as value.
| Widget | Description |
|--------------|--------------------------------------------------------|
| `Komorebi` | Workspaces, layout, focused window, and more |
| `Battery` | Battery level and charging status |
| `Date` | Current date in configurable format |
| `Time` | Current time in configurable format |
| `Media` | Currently playing media information |
| `Memory` | System memory usage |
| `Network` | Network activity and connection status |
| `Storage` | Disk usage information |
| `Update` | Komorebi update notification |
| `Systray` | Windows system tray icons |
Widgets with dedicated documentation pages:
- [System Tray](bar-widgets/systray.md)
> Dedicated pages for the remaining widgets will be added in the future.
## Schema
The full configuration schema is available at
[komorebi-bar.lgug2z.com/schema](https://komorebi-bar.lgug2z.com/schema).
For running a bar on each monitor, see
[Multiple Bar Instances](multiple-bar-instances.md) and
[Multi-Monitor Setup](multi-monitor-setup.md).
-137
View File
@@ -172,143 +172,6 @@ consistently to all splits of that type throughout the layout. Additional values
- Unspecified ratios default to sharing the remaining space equally - Unspecified ratios default to sharing the remaining space equally
- You only need to specify the ratios you want to customize; trailing values can be omitted - You only need to specify the ratios you want to customize; trailing values can be omitted
## Layout Options Rules
You can dynamically change `layout_options` based on the number of containers on a workspace
using `layout_options_rules`. This uses the same threshold-based logic as `layout_rules`:
when the container count is greater than or equal to a threshold, the highest matching
threshold's options are used.
Rules **fully replace** the base `layout_options` when they match. If no rule matches, the
base `layout_options` is used.
### Configuration
```json
{
"monitors": [
{
"workspaces": [
{
"name": "main",
"layout": "VerticalStack",
"layout_options": {
"column_ratios": [0.6],
"row_ratios": [0.4]
},
"layout_options_rules": {
"3": { "column_ratios": [0.55] },
"5": { "column_ratios": [0.3, 0.3, 0.3], "row_ratios": [0.5] }
}
}
]
}
]
}
```
In the example above:
| Container Count | Effective `layout_options` |
|-----------------|---------------------------|
| 1-2 | Base: `column_ratios: [0.6]`, `row_ratios: [0.4]` |
| 3-4 | Rule "3": `column_ratios: [0.55]` (no row_ratios, no scrolling, no grid) |
| 5+ | Rule "5": `column_ratios: [0.3, 0.3, 0.3]`, `row_ratios: [0.5]` |
Rules can include any field that `layout_options` supports: `column_ratios`, `row_ratios`,
`scrolling`, and `grid`. When a rule matches, it completely replaces the base options. Fields
not specified in the matching rule default to their standard defaults (not the base
`layout_options` values).
### Example: Scrolling Layout with Dynamic Columns
```json
{
"layout": "Scrolling",
"layout_options": {
"scrolling": { "columns": 2 }
},
"layout_options_rules": {
"4": { "scrolling": { "columns": 3 } },
"7": { "scrolling": { "columns": 4 } }
}
}
```
This increases the visible scrolling columns as more windows are added.
## Layout Defaults
You can define global per-layout default `layout_options` and `layout_options_rules` using
the top-level `layout_defaults` setting. This avoids repeating the same configuration across
every workspace that uses the same layout.
### Configuration
```json
{
"layout_defaults": {
"VerticalStack": {
"layout_options": { "column_ratios": [0.7] },
"layout_options_rules": {
"2": { "column_ratios": [0.7] },
"3": { "column_ratios": [0.55] },
"5": { "column_ratios": [0.4] }
}
},
"Columns": {
"layout_options": { "column_ratios": [0.3, 0.4] },
"layout_options_rules": {
"4": { "column_ratios": [0.2, 0.3, 0.3] }
}
},
"HorizontalStack": {
"layout_options": { "row_ratios": [0.6] }
}
},
"monitors": [
{
"workspaces": [
{
"name": "main",
"layout": "VerticalStack"
}
]
}
]
}
```
In this example, every workspace using `VerticalStack`, `Columns`, or `HorizontalStack`
automatically gets the global `layout_options` and `layout_options_rules` without needing
to specify them per-workspace. Note that `VerticalStack` only has 2 columns (main + stack),
so only a single `column_ratios` value is meaningful, while `Columns` distributes windows
across multiple columns where additional ratios control each column's width.
### Resolution Cascade
Global defaults act as a fallback. If a workspace defines **either** `layout_options` or
`layout_options_rules`, it **completely replaces** all global `layout_defaults` for that
layout. Global defaults are only used when the workspace has **neither** setting.
Within the effective source (workspace or global):
1. Try threshold match from the rules (highest matching threshold wins)
2. If a rule matches → use it (full replacement of base options)
3. Otherwise → use the base `layout_options`
### Override Examples
| Workspace Config | Global Config | Effective Behavior |
|------------------|---------------|--------------------|
| No `layout_options`, no rules | `layout_defaults` has both | Uses global base + global rules |
| Has `layout_options` only | `layout_defaults` has both | Workspace base only (all globals ignored) |
| Has `layout_options_rules` only | `layout_defaults` has both | Workspace rules only (all globals ignored) |
| Has both | `layout_defaults` has both | All workspace (all globals ignored) |
This "complete replacement" semantic means you never get a mix of workspace and global
settings for the same layout. If you override anything at the workspace level, you take
full control of that layout's options for that workspace.
## Progressive Ratio Behavior ## Progressive Ratio Behavior
Ratios are applied progressively as windows are added. For example, with `row_ratios: [0.3, 0.5]` in a VerticalStack: Ratios are applied progressively as windows are added. For example, with `row_ratios: [0.3, 0.5]` in a VerticalStack:
+1 -1
View File
@@ -83,7 +83,7 @@ is a crude hack trying to compensate for the insistence of Microsoft Windows
design teams to make custom borders with widths that are actually visible to design teams to make custom borders with widths that are actually visible to
the user a thing of the past and removing this capability from the Win32 API. the user a thing of the past and removing this capability from the Win32 API.
I know it's buggy, and I know that most of the time it sucks, but this is something I know it's buggy, and I know that most of the it sucks, but this is something
you should be bring up with the billion dollar company and not with me, the you should be bring up with the billion dollar company and not with me, the
solo developer. solo developer.
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.41/schema.bar.json", "$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.bar.json",
"font_family": "JetBrains Mono", "font_family": "JetBrains Mono",
"theme": { "theme": {
"palette": "Base16", "palette": "Base16",
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.41/schema.json", "$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json", "app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak", "window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert", "cross_monitor_move_behaviour": "Insert",
+2 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-bar" name = "komorebi-bar"
version = "0.1.41" version = "0.1.40"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -18,17 +18,15 @@ dirs = { workspace = true }
dunce = { workspace = true } dunce = { workspace = true }
eframe = { workspace = true } eframe = { workspace = true }
egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" } egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" }
egui_extras = { workspace = true }
font-loader = "0.11" font-loader = "0.11"
hotwatch = { workspace = true } hotwatch = { workspace = true }
image = "0.25" image = "0.25"
lazy_static = { workspace = true } lazy_static = { workspace = true }
netdev = "0.41" netdev = "0.40"
num = "0.4" num = "0.4"
num-derive = "0.4" num-derive = "0.4"
num-traits = "0.2" num-traits = "0.2"
parking_lot = { workspace = true } parking_lot = { workspace = true }
regex = "1"
random_word = { version = "0.5", features = ["en"] } random_word = { version = "0.5", features = ["en"] }
reqwest = { version = "0.12", features = ["blocking"] } reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true, optional = true } schemars = { workspace = true, optional = true }
@@ -36,8 +34,6 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
starship-battery = "0.10" starship-battery = "0.10"
sysinfo = { workspace = true } sysinfo = { workspace = true }
systray-util = "0.2.0"
tokio = { version = "1", features = ["rt", "sync", "time"] }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
which = { workspace = true } which = { workspace = true }
+3 -15
View File
@@ -18,7 +18,6 @@ use crate::render::Color32Ext;
use crate::render::Grouping; use crate::render::Grouping;
use crate::render::RenderConfig; use crate::render::RenderConfig;
use crate::render::RenderExt; use crate::render::RenderExt;
use crate::take_widget_clicked;
use crate::widgets::komorebi::Komorebi; use crate::widgets::komorebi::Komorebi;
use crate::widgets::komorebi::MonitorInfo; use crate::widgets::komorebi::MonitorInfo;
use crate::widgets::widget::BarWidget; use crate::widgets::widget::BarWidget;
@@ -1083,10 +1082,6 @@ impl eframe::App for Komobar {
let frame = render_config.change_frame_on_bar(frame, &ctx.style()); let frame = render_config.change_frame_on_bar(frame, &ctx.style());
CentralPanel::default().frame(frame).show(ctx, |ui| { CentralPanel::default().frame(frame).show(ctx, |ui| {
// Variable to store command to execute after widgets are rendered
// This allows widgets to mark clicks as consumed before bar processes them
let mut pending_command: Option<crate::config::MouseMessage> = None;
if let Some(mouse_config) = &self.config.mouse { if let Some(mouse_config) = &self.config.mouse {
let command = if ui let command = if ui
.input(|i| i.pointer.button_double_clicked(PointerButton::Primary)) .input(|i| i.pointer.button_double_clicked(PointerButton::Primary))
@@ -1187,9 +1182,9 @@ impl eframe::App for Komobar {
&None &None
}; };
// Store the command to execute after widgets are rendered if let Some(command) = command {
// This allows widgets to mark clicks as consumed command.execute(self.mouse_follows_focus);
pending_command = command.clone(); }
} }
// Apply grouping logic for the bar as a whole // Apply grouping logic for the bar as a whole
@@ -1321,13 +1316,6 @@ impl eframe::App for Komobar {
}); });
}); });
} }
// Execute the deferred mouse command only if no widget consumed the click
if let Some(command) = pending_command
&& !take_widget_clicked()
{
command.execute(self.mouse_follows_focus);
}
}); });
} }
} }
+1 -21
View File
@@ -15,7 +15,7 @@ use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.bar.json` configuration file reference for `v0.1.41` /// The `komorebi.bar.json` configuration file reference for `v0.1.40`
pub struct KomobarConfig { pub struct KomobarConfig {
/// Bar height /// Bar height
#[cfg_attr(feature = "schemars", schemars(extend("default" = 50)))] #[cfg_attr(feature = "schemars", schemars(extend("default" = 50)))]
@@ -621,26 +621,6 @@ extend_enum!(
AllIconsAndTextOnSelected, AllIconsAndTextOnSelected,
}); });
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Media widget display format
pub enum MediaDisplayFormat {
/// Show only the media info icon
Icon,
/// Show only the media info text (artist - title)
Text,
/// Show both icon and text
IconAndText,
/// Show only the control buttons (previous, play/pause, next)
ControlsOnly,
/// Show icon with control buttons
IconAndControls,
/// Show text with control buttons
TextAndControls,
/// Show icon, text, and control buttons
Full,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde::Deserialize; use serde::Deserialize;
-16
View File
@@ -38,8 +38,6 @@ use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId; use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows_core::BOOL; use windows_core::BOOL;
use std::sync::atomic::AtomicBool;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400); pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0); pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0); pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0);
@@ -48,20 +46,6 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
pub static BAR_HEIGHT: f32 = 50.0; pub static BAR_HEIGHT: f32 = 50.0;
pub static DEFAULT_PADDING: f32 = 10.0; pub static DEFAULT_PADDING: f32 = 10.0;
/// Flag to indicate that a widget has consumed a click event this frame.
/// This prevents the bar's global mouse handler from also processing the click.
pub static WIDGET_CLICKED: AtomicBool = AtomicBool::new(false);
/// Mark that a widget has consumed a click event this frame.
pub fn mark_widget_clicked() {
WIDGET_CLICKED.store(true, Ordering::SeqCst);
}
/// Check if a widget has consumed a click event this frame and reset the flag.
pub fn take_widget_clicked() -> bool {
WIDGET_CLICKED.swap(false, Ordering::SeqCst)
}
pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0); pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0);
pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0); pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0);
+13 -223
View File
@@ -1,6 +1,4 @@
use crate::MAX_LABEL_WIDTH; use crate::MAX_LABEL_WIDTH;
use crate::bar::Alignment;
use crate::config::MediaDisplayFormat;
use crate::render::RenderConfig; use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame; use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi; use crate::ui::CustomUi;
@@ -16,7 +14,6 @@ use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager; use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
use windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@@ -24,31 +21,24 @@ use windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackSt
pub struct MediaConfig { pub struct MediaConfig {
/// Enable the Media widget /// Enable the Media widget
pub enable: bool, pub enable: bool,
/// Display format of the media widget (defaults to IconAndText)
pub display: Option<MediaDisplayFormat>,
} }
impl From<MediaConfig> for Media { impl From<MediaConfig> for Media {
fn from(value: MediaConfig) -> Self { fn from(value: MediaConfig) -> Self {
Self::new( Self::new(value.enable)
value.enable,
value.display.unwrap_or(MediaDisplayFormat::IconAndText),
)
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Media { pub struct Media {
pub enable: bool, pub enable: bool,
pub display: MediaDisplayFormat,
pub session_manager: GlobalSystemMediaTransportControlsSessionManager, pub session_manager: GlobalSystemMediaTransportControlsSessionManager,
} }
impl Media { impl Media {
pub fn new(enable: bool, display: MediaDisplayFormat) -> Self { pub fn new(enable: bool) -> Self {
Self { Self {
enable, enable,
display,
session_manager: GlobalSystemMediaTransportControlsSessionManager::RequestAsync() session_manager: GlobalSystemMediaTransportControlsSessionManager::RequestAsync()
.unwrap() .unwrap()
.join() .join()
@@ -64,58 +54,6 @@ impl Media {
} }
} }
pub fn previous(&self) {
if let Ok(session) = self.session_manager.GetCurrentSession()
&& let Ok(op) = session.TrySkipPreviousAsync()
{
op.join().unwrap_or_default();
}
}
pub fn next(&self) {
if let Ok(session) = self.session_manager.GetCurrentSession()
&& let Ok(op) = session.TrySkipNextAsync()
{
op.join().unwrap_or_default();
}
}
fn is_playing(&self) -> bool {
if let Ok(session) = self.session_manager.GetCurrentSession()
&& let Ok(info) = session.GetPlaybackInfo()
&& let Ok(status) = info.PlaybackStatus()
{
return status == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing;
}
false
}
fn is_previous_enabled(&self) -> bool {
if let Ok(session) = self.session_manager.GetCurrentSession()
&& let Ok(info) = session.GetPlaybackInfo()
&& let Ok(controls) = info.Controls()
&& let Ok(enabled) = controls.IsPreviousEnabled()
{
return enabled;
}
false
}
fn is_next_enabled(&self) -> bool {
if let Ok(session) = self.session_manager.GetCurrentSession()
&& let Ok(info) = session.GetPlaybackInfo()
&& let Ok(controls) = info.Controls()
&& let Ok(enabled) = controls.IsNextEnabled()
{
return enabled;
}
false
}
fn has_session(&self) -> bool {
self.session_manager.GetCurrentSession().is_ok()
}
fn output(&mut self) -> String { fn output(&mut self) -> String {
if let Ok(session) = self.session_manager.GetCurrentSession() if let Ok(session) = self.session_manager.GetCurrentSession()
&& let Ok(operation) = session.TryGetMediaPropertiesAsync() && let Ok(operation) = session.TryGetMediaPropertiesAsync()
@@ -140,96 +78,28 @@ impl Media {
impl BarWidget for Media { impl BarWidget for Media {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable { if self.enable {
// Don't render if there's no active media session
if !self.has_session() {
return;
}
let output = self.output(); let output = self.output();
if !output.is_empty() {
let show_icon = matches!( let mut layout_job = LayoutJob::simple(
self.display,
MediaDisplayFormat::Icon
| MediaDisplayFormat::IconAndText
| MediaDisplayFormat::IconAndControls
| MediaDisplayFormat::Full
);
let show_text = matches!(
self.display,
MediaDisplayFormat::Text
| MediaDisplayFormat::IconAndText
| MediaDisplayFormat::TextAndControls
| MediaDisplayFormat::Full
);
let show_controls = matches!(
self.display,
MediaDisplayFormat::ControlsOnly
| MediaDisplayFormat::IconAndControls
| MediaDisplayFormat::TextAndControls
| MediaDisplayFormat::Full
);
// Don't render if there's no media info and we're not showing controls-only
if output.is_empty() && !show_controls {
return;
}
let icon_font_id = config.icon_font_id.clone();
let text_font_id = config.text_font_id.clone();
let icon_color = ctx.style().visuals.selection.stroke.color;
let text_color = ctx.style().visuals.text_color();
let mut layout_job = LayoutJob::default();
if show_icon {
layout_job = LayoutJob::simple(
egui_phosphor::regular::HEADPHONES.to_string(), egui_phosphor::regular::HEADPHONES.to_string(),
icon_font_id.clone(), config.icon_font_id.clone(),
icon_color, ctx.style().visuals.selection.stroke.color,
100.0, 100.0,
); );
}
if show_text {
layout_job.append( layout_job.append(
&output, &output,
if show_icon { 10.0 } else { 0.0 }, 10.0,
TextFormat { TextFormat {
font_id: text_font_id, font_id: config.text_font_id.clone(),
color: text_color, color: ctx.style().visuals.text_color(),
valign: Align::Center, valign: Align::Center,
..Default::default() ..Default::default()
}, },
); );
}
let is_playing = self.is_playing(); config.apply_on_widget(false, ui, |ui| {
let is_previous_enabled = self.is_previous_enabled(); if SelectableFrame::new(false)
let is_next_enabled = self.is_next_enabled();
let disabled_color = text_color.gamma_multiply(0.5);
let is_reversed = matches!(config.alignment, Some(Alignment::Right));
let prev_color = if is_previous_enabled {
text_color
} else {
disabled_color
};
let next_color = if is_next_enabled {
text_color
} else {
disabled_color
};
let play_pause_icon = if is_playing {
egui_phosphor::regular::PAUSE
} else {
egui_phosphor::regular::PLAY
};
let show_label = |ui: &mut Ui| {
if (show_icon || show_text)
&& SelectableFrame::new(false)
.show(ui, |ui| { .show(ui, |ui| {
let available_height = ui.available_height(); let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui); let mut custom_ui = CustomUi(ui);
@@ -239,95 +109,15 @@ impl BarWidget for Media {
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32, MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height, available_height,
), ),
Label::new(layout_job.clone()).selectable(false).truncate(), Label::new(layout_job).selectable(false).truncate(),
) )
}) })
.on_hover_text(&output)
.clicked() .clicked()
{ {
self.toggle(); self.toggle();
} }
};
let show_previous = |ui: &mut Ui| {
if SelectableFrame::new(false)
.show(ui, |ui| {
ui.add(
Label::new(LayoutJob::simple(
egui_phosphor::regular::SKIP_BACK.to_string(),
icon_font_id.clone(),
prev_color,
100.0,
))
.selectable(false),
)
})
.clicked()
&& is_previous_enabled
{
self.previous();
}
};
let show_play_pause = |ui: &mut Ui| {
if SelectableFrame::new(false)
.show(ui, |ui| {
ui.add(
Label::new(LayoutJob::simple(
play_pause_icon.to_string(),
icon_font_id.clone(),
text_color,
100.0,
))
.selectable(false),
)
})
.on_hover_text(&output)
.clicked()
{
self.toggle();
}
};
let show_next = |ui: &mut Ui| {
if SelectableFrame::new(false)
.show(ui, |ui| {
ui.add(
Label::new(LayoutJob::simple(
egui_phosphor::regular::SKIP_FORWARD.to_string(),
icon_font_id.clone(),
next_color,
100.0,
))
.selectable(false),
)
})
.clicked()
&& is_next_enabled
{
self.next();
}
};
config.apply_on_widget(false, ui, |ui| {
if is_reversed {
// Right panel renders right-to-left, so reverse order
if show_controls {
show_next(ui);
show_play_pause(ui);
show_previous(ui);
}
show_label(ui);
} else {
// Left/center panel renders left-to-right, normal order
show_label(ui);
if show_controls {
show_previous(ui);
show_play_pause(ui);
show_next(ui);
}
}
}); });
} }
} }
} }
}
+1 -11
View File
@@ -20,8 +20,6 @@ pub mod media;
pub mod memory; pub mod memory;
pub mod network; pub mod network;
pub mod storage; pub mod storage;
#[cfg(target_os = "windows")]
pub mod systray;
pub mod time; pub mod time;
pub mod update; pub mod update;
pub mod widget; pub mod widget;
@@ -94,16 +92,10 @@ impl IconsCache {
pub fn insert_image(&self, id: ImageIconId, image: Arc<ColorImage>) { pub fn insert_image(&self, id: ImageIconId, image: Arc<ColorImage>) {
self.images.write().unwrap().insert(id, image); self.images.write().unwrap().insert(id, image);
} }
/// Removes the cached image and texture for the given icon ID.
pub fn remove(&self, id: &ImageIconId) {
self.images.write().unwrap().remove(id);
self.textures.write().unwrap().1.remove(id);
}
} }
#[inline] #[inline]
pub(crate) fn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage { fn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage {
let size = [rgba_image.width() as usize, rgba_image.height() as usize]; let size = [rgba_image.width() as usize, rgba_image.height() as usize];
let pixels = rgba_image.as_flat_samples(); let pixels = rgba_image.as_flat_samples();
ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()) ColorImage::from_rgba_unmultiplied(size, pixels.as_slice())
@@ -164,8 +156,6 @@ pub enum ImageIconId {
Path(Arc<Path>), Path(Arc<Path>),
/// Windows HWND handle. /// Windows HWND handle.
Hwnd(isize), Hwnd(isize),
/// System tray icon identifier.
SystrayIcon(String),
} }
impl From<&Path> for ImageIconId { impl From<&Path> for ImageIconId {
+1 -31
View File
@@ -33,8 +33,6 @@ pub struct StorageConfig {
/// Show removable disks /// Show removable disks
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))] #[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub show_removable_disks: Option<bool>, pub show_removable_disks: Option<bool>,
/// Storage display name
pub storage_display_name: Option<StorageDisplayName>,
/// Select when the current percentage is over this value [[1-100]] /// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>, pub auto_select_over: Option<u8>,
/// Hide when the current percentage is under this value [[1-100]] /// Hide when the current percentage is under this value [[1-100]]
@@ -50,9 +48,6 @@ impl From<StorageConfig> for Storage {
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
show_read_only_disks: value.show_read_only_disks.unwrap_or(false), show_read_only_disks: value.show_read_only_disks.unwrap_or(false),
show_removable_disks: value.show_removable_disks.unwrap_or(true), show_removable_disks: value.show_removable_disks.unwrap_or(true),
storage_display_name: value
.storage_display_name
.unwrap_or(StorageDisplayName::Mount),
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)), auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)), auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)),
last_updated: Instant::now(), last_updated: Instant::now(),
@@ -60,19 +55,6 @@ impl From<StorageConfig> for Storage {
} }
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum StorageDisplayName {
/// Display label as mount point eg. C:\
Mount,
/// Display label as name eg. Local Disk
Name,
/// Display label as mount then name eg. C:\ Local Disk
MountAndName,
/// Display label as name then mount eg. Local Disk C:\
NameAndMount,
}
struct StorageDisk { struct StorageDisk {
label: String, label: String,
selected: bool, selected: bool,
@@ -85,7 +67,6 @@ pub struct Storage {
label_prefix: LabelPrefix, label_prefix: LabelPrefix,
show_read_only_disks: bool, show_read_only_disks: bool,
show_removable_disks: bool, show_removable_disks: bool,
storage_display_name: StorageDisplayName,
auto_select_over: Option<u8>, auto_select_over: Option<u8>,
auto_hide_under: Option<u8>, auto_hide_under: Option<u8>,
last_updated: Instant, last_updated: Instant,
@@ -109,17 +90,6 @@ impl Storage {
continue; continue;
} }
let mount = disk.mount_point(); let mount = disk.mount_point();
let name = disk.name();
let display_name = match self.storage_display_name {
StorageDisplayName::Mount => mount.to_string_lossy(),
StorageDisplayName::Name => name.to_string_lossy(),
StorageDisplayName::MountAndName => {
mount.to_string_lossy() + name.to_string_lossy()
}
StorageDisplayName::NameAndMount => {
name.to_string_lossy() + mount.to_string_lossy()
}
};
let total = disk.total_space(); let total = disk.total_space();
let available = disk.available_space(); let available = disk.available_space();
let used = total - available; let used = total - available;
@@ -133,7 +103,7 @@ impl Storage {
disks.push(StorageDisk { disks.push(StorageDisk {
label: match self.label_prefix { label: match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => { LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("{} {}%", display_name, percentage) format!("{} {}%", mount.to_string_lossy(), percentage)
} }
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage}%"), LabelPrefix::None | LabelPrefix::Icon => format!("{percentage}%"),
}, },
File diff suppressed because it is too large Load Diff
-12
View File
@@ -19,10 +19,6 @@ use crate::widgets::network::Network;
use crate::widgets::network::NetworkConfig; use crate::widgets::network::NetworkConfig;
use crate::widgets::storage::Storage; use crate::widgets::storage::Storage;
use crate::widgets::storage::StorageConfig; use crate::widgets::storage::StorageConfig;
#[cfg(target_os = "windows")]
use crate::widgets::systray::Systray;
#[cfg(target_os = "windows")]
use crate::widgets::systray::SystrayConfig;
use crate::widgets::time::Time; use crate::widgets::time::Time;
use crate::widgets::time::TimeConfig; use crate::widgets::time::TimeConfig;
use crate::widgets::update::Update; use crate::widgets::update::Update;
@@ -70,10 +66,6 @@ pub enum WidgetConfig {
/// Storage widget configuration /// Storage widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Storage"))] #[cfg_attr(feature = "schemars", schemars(title = "Storage"))]
Storage(StorageConfig), Storage(StorageConfig),
/// System Tray widget configuration (Windows only)
#[cfg(target_os = "windows")]
#[cfg_attr(feature = "schemars", schemars(title = "Systray"))]
Systray(SystrayConfig),
/// Time widget configuration /// Time widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Time"))] #[cfg_attr(feature = "schemars", schemars(title = "Time"))]
Time(TimeConfig), Time(TimeConfig),
@@ -95,8 +87,6 @@ impl WidgetConfig {
WidgetConfig::Memory(config) => Box::new(Memory::from(*config)), WidgetConfig::Memory(config) => Box::new(Memory::from(*config)),
WidgetConfig::Network(config) => Box::new(Network::from(*config)), WidgetConfig::Network(config) => Box::new(Network::from(*config)),
WidgetConfig::Storage(config) => Box::new(Storage::from(*config)), WidgetConfig::Storage(config) => Box::new(Storage::from(*config)),
#[cfg(target_os = "windows")]
WidgetConfig::Systray(config) => Box::new(Systray::from(config)),
WidgetConfig::Time(config) => Box::new(Time::from(config.clone())), WidgetConfig::Time(config) => Box::new(Time::from(config.clone())),
WidgetConfig::Update(config) => Box::new(Update::from(*config)), WidgetConfig::Update(config) => Box::new(Update::from(*config)),
} }
@@ -122,8 +112,6 @@ impl WidgetConfig {
WidgetConfig::Memory(config) => config.enable, WidgetConfig::Memory(config) => config.enable,
WidgetConfig::Network(config) => config.enable, WidgetConfig::Network(config) => config.enable,
WidgetConfig::Storage(config) => config.enable, WidgetConfig::Storage(config) => config.enable,
#[cfg(target_os = "windows")]
WidgetConfig::Systray(config) => config.enable,
WidgetConfig::Time(config) => config.enable, WidgetConfig::Time(config) => config.enable,
WidgetConfig::Update(config) => config.enable, WidgetConfig::Update(config) => config.enable,
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-client" name = "komorebi-client"
version = "0.1.41" version = "0.1.40"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-gui" name = "komorebi-gui"
version = "0.1.41" version = "0.1.40"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-layouts" name = "komorebi-layouts"
version = "0.1.41" version = "0.1.40"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
+684 -88
View File
@@ -141,15 +141,6 @@ impl Arrangement for DefaultLayout {
}); });
} }
// Last visible column absorbs any remainder from integer division
// so that visible columns tile the full area width without gaps
let width_remainder = area.right - column_width * visible_columns;
if width_remainder > 0 {
let last_visible_idx =
(first_visible as usize + visible_columns as usize - 1).min(len - 1);
layouts[last_visible_idx].right += width_remainder;
}
let adjustment = calculate_scrolling_adjustment(resize_dimensions); let adjustment = calculate_scrolling_adjustment(resize_dimensions);
layouts layouts
.iter_mut() .iter_mut()
@@ -669,34 +660,6 @@ impl Arrangement for DefaultLayout {
current_left += width; current_left += width;
} }
// Last column absorbs any remainder from integer division
// so that columns tile the full area width without gaps
let total_width: i32 = col_widths.iter().sum();
let width_remainder = area.right - total_width;
if width_remainder > 0
&& let Some(last) = col_widths.last_mut()
{
*last += width_remainder;
}
// Pre-calculate flipped column positions: same widths laid out
// in reverse order so that the last column sits at area.left
let flipped_col_lefts = if matches!(
layout_flip,
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
) {
let n = num_cols as usize;
let mut flipped = vec![0i32; n];
let mut fl = area.left;
for i in (0..n).rev() {
flipped[i] = fl;
fl += col_widths[i];
}
flipped
} else {
vec![]
};
let mut iter = layouts.iter_mut().enumerate().peekable(); let mut iter = layouts.iter_mut().enumerate().peekable();
for col in 0..num_cols { for col in 0..num_cols {
@@ -710,10 +673,8 @@ impl Arrangement for DefaultLayout {
remaining_windows / remaining_columns remaining_windows / remaining_columns
}; };
// Rows within each column: base height from integer division, // Rows within each column are equal height (no row_ratios support for Grid)
// last row absorbs any remainder to cover the full area height let win_height = area.bottom / num_rows_in_this_col;
let base_height = area.bottom / num_rows_in_this_col;
let height_remainder = area.bottom - base_height * num_rows_in_this_col;
let col_idx = col as usize; let col_idx = col as usize;
let win_width = col_widths[col_idx]; let win_width = col_widths[col_idx];
@@ -721,36 +682,25 @@ impl Arrangement for DefaultLayout {
for row in 0..num_rows_in_this_col { for row in 0..num_rows_in_this_col {
if let Some((_idx, win)) = iter.next() { if let Some((_idx, win)) = iter.next() {
let is_last_row = row == num_rows_in_this_col - 1;
let win_height = if is_last_row {
base_height + height_remainder
} else {
base_height
};
let mut left = col_left; let mut left = col_left;
let mut top = area.top + base_height * row; let mut top = area.top + win_height * row;
match layout_flip { match layout_flip {
Some(Axis::Horizontal) => { Some(Axis::Horizontal) => {
left = flipped_col_lefts[col_idx]; // Calculate flipped left position
let flipped_col = (num_cols - 1 - col) as usize;
left = col_lefts[flipped_col];
} }
Some(Axis::Vertical) => { Some(Axis::Vertical) => {
top = if is_last_row { // Calculate flipped top position
area.top top = area.bottom - win_height * (row + 1) + area.top;
} else {
area.top + area.bottom - base_height * (row + 1)
};
} }
Some(Axis::HorizontalAndVertical) => { Some(Axis::HorizontalAndVertical) => {
left = flipped_col_lefts[col_idx]; let flipped_col = (num_cols - 1 - col) as usize;
top = if is_last_row { left = col_lefts[flipped_col];
area.top top = area.bottom - win_height * (row + 1) + area.top;
} else {
area.top + area.bottom - base_height * (row + 1)
};
} }
None => {} None => {} // No flip
} }
win.bottom = win_height; win.bottom = win_height;
@@ -984,16 +934,6 @@ fn columns_with_ratios(
left += right; left += right;
} }
// Last column absorbs any remainder from integer division
// so that columns tile the full area width without gaps
let total_width: i32 = layouts.iter().map(|r| r.right).sum();
let remainder = area.right - total_width;
if remainder > 0
&& let Some(last) = layouts.last_mut()
{
last.right += remainder;
}
layouts layouts
} }
@@ -1065,16 +1005,6 @@ fn rows_with_ratios(
top += bottom; top += bottom;
} }
// Last row absorbs any remainder from integer division
// so that rows tile the full area height without gaps
let total_height: i32 = layouts.iter().map(|r| r.bottom).sum();
let remainder = area.bottom - total_height;
if remainder > 0
&& let Some(last) = layouts.last_mut()
{
last.bottom += remainder;
}
layouts layouts
} }
@@ -1199,33 +1129,47 @@ fn recursive_fibonacci(
*area *area
}; };
#[allow(clippy::cast_possible_truncation)]
let primary_width = (area.right as f32 * column_split_ratio) as i32;
#[allow(clippy::cast_possible_truncation)]
let primary_height = (area.bottom as f32 * row_split_ratio) as i32;
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
let primary_resized_width = (resized.right as f32 * column_split_ratio) as i32; let primary_resized_width = (resized.right as f32 * column_split_ratio) as i32;
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
let primary_resized_height = (resized.bottom as f32 * row_split_ratio) as i32; let primary_resized_height = (resized.bottom as f32 * row_split_ratio) as i32;
let secondary_width = area.right - primary_width;
let secondary_resized_width = resized.right - primary_resized_width;
let secondary_resized_height = resized.bottom - primary_resized_height;
let (main_x, alt_x, alt_y, main_y); let (main_x, alt_x, alt_y, main_y);
if let Some(flip) = layout_flip { if let Some(flip) = layout_flip {
match flip { match flip {
Axis::Horizontal => { Axis::Horizontal => {
main_x = resized.left + (area.right - primary_resized_width); main_x =
resized.left + secondary_width + (secondary_width - secondary_resized_width);
alt_x = resized.left; alt_x = resized.left;
alt_y = resized.top + primary_resized_height; alt_y = resized.top + primary_resized_height;
main_y = resized.top; main_y = resized.top;
} }
Axis::Vertical => { Axis::Vertical => {
main_y = resized.top + (area.bottom - primary_resized_height); main_y = resized.top
+ (area.bottom - primary_height)
+ ((area.bottom - primary_height) - secondary_resized_height);
alt_y = resized.top; alt_y = resized.top;
main_x = resized.left; main_x = resized.left;
alt_x = resized.left + primary_resized_width; alt_x = resized.left + primary_resized_width;
} }
Axis::HorizontalAndVertical => { Axis::HorizontalAndVertical => {
main_x = resized.left + (area.right - primary_resized_width); main_x =
resized.left + secondary_width + (secondary_width - secondary_resized_width);
alt_x = resized.left; alt_x = resized.left;
main_y = resized.top + (area.bottom - primary_resized_height); main_y = resized.top
+ (area.bottom - primary_height)
+ ((area.bottom - primary_height) - secondary_resized_height);
alt_y = resized.top; alt_y = resized.top;
} }
} }
@@ -1597,5 +1541,657 @@ fn resize_bottom(rect: &mut Rect, resize: i32) {
} }
#[cfg(test)] #[cfg(test)]
#[path = "arrangement_tests.rs"] mod tests {
mod tests; use super::*;
use std::num::NonZeroUsize;
// Helper to create a test area
fn test_area() -> Rect {
Rect {
left: 0,
top: 0,
right: 1000,
bottom: 800,
}
}
// Helper to create LayoutOptions with column ratios
fn layout_options_with_column_ratios(ratios: &[f32]) -> LayoutOptions {
let mut arr = [None; MAX_RATIOS];
for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {
arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: Some(arr),
row_ratios: None,
}
}
// Helper to create LayoutOptions with row ratios
fn layout_options_with_row_ratios(ratios: &[f32]) -> LayoutOptions {
let mut arr = [None; MAX_RATIOS];
for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {
arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: None,
row_ratios: Some(arr),
}
}
// Helper to create LayoutOptions with both column and row ratios
fn layout_options_with_ratios(column_ratios: &[f32], row_ratios: &[f32]) -> LayoutOptions {
let mut col_arr = [None; MAX_RATIOS];
for (i, &r) in column_ratios.iter().take(MAX_RATIOS).enumerate() {
col_arr[i] = Some(r);
}
let mut row_arr = [None; MAX_RATIOS];
for (i, &r) in row_ratios.iter().take(MAX_RATIOS).enumerate() {
row_arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: Some(col_arr),
row_ratios: Some(row_arr),
}
}
mod columns_with_ratios_tests {
use super::*;
#[test]
fn test_columns_equal_width_no_ratios() {
let area = test_area();
let layouts = columns_with_ratios(&area, 4, None);
assert_eq!(layouts.len(), 4);
// Each column should be 250 pixels wide (1000 / 4)
for layout in &layouts {
assert_eq!(layout.right, 250);
assert_eq!(layout.bottom, 800);
}
}
#[test]
fn test_columns_with_single_ratio() {
let area = test_area();
let opts = layout_options_with_column_ratios(&[0.3]);
let layouts = columns_with_ratios(&area, 3, opts.column_ratios);
assert_eq!(layouts.len(), 3);
// First column: 30% of 1000 = 300
assert_eq!(layouts[0].right, 300);
// Remaining 700 split between 2 columns = 350 each
assert_eq!(layouts[1].right, 350);
assert_eq!(layouts[2].right, 350);
}
#[test]
fn test_columns_with_multiple_ratios() {
let area = test_area();
let opts = layout_options_with_column_ratios(&[0.2, 0.3, 0.5]);
let layouts = columns_with_ratios(&area, 4, opts.column_ratios);
assert_eq!(layouts.len(), 4);
// First column: 20% of 1000 = 200
assert_eq!(layouts[0].right, 200);
// Second column: 30% of 1000 = 300
assert_eq!(layouts[1].right, 300);
// Third column: 50% of 1000 = 500
// But wait - cumulative is 1.0, so third might be truncated
// Let's check what actually happens
// Actually, the sum 0.2 + 0.3 = 0.5 < 1.0, and 0.5 + 0.5 = 1.0
// So 0.5 won't be included because cumulative would reach 1.0
}
#[test]
fn test_columns_positions_are_correct() {
let area = test_area();
let opts = layout_options_with_column_ratios(&[0.3, 0.4]);
let layouts = columns_with_ratios(&area, 3, opts.column_ratios);
// First column starts at 0
assert_eq!(layouts[0].left, 0);
// Second column starts where first ends
assert_eq!(layouts[1].left, layouts[0].right);
// Third column starts where second ends
assert_eq!(layouts[2].left, layouts[1].left + layouts[1].right);
}
#[test]
fn test_columns_last_column_gets_remaining_space() {
let area = test_area();
let opts = layout_options_with_column_ratios(&[0.3]);
let layouts = columns_with_ratios(&area, 2, opts.column_ratios);
assert_eq!(layouts.len(), 2);
// First column: 30% = 300
assert_eq!(layouts[0].right, 300);
// Last column gets remaining space: 700
assert_eq!(layouts[1].right, 700);
}
#[test]
fn test_columns_single_column() {
let area = test_area();
let opts = layout_options_with_column_ratios(&[0.5]);
let layouts = columns_with_ratios(&area, 1, opts.column_ratios);
assert_eq!(layouts.len(), 1);
// Single column takes full width regardless of ratio
assert_eq!(layouts[0].right, 1000);
}
#[test]
fn test_columns_more_columns_than_ratios() {
let area = test_area();
let opts = layout_options_with_column_ratios(&[0.2]);
let layouts = columns_with_ratios(&area, 5, opts.column_ratios);
assert_eq!(layouts.len(), 5);
// First column: 20% = 200
assert_eq!(layouts[0].right, 200);
// Remaining 800 split among 4 columns = 200 each
for i in 1..5 {
assert_eq!(layouts[i].right, 200);
}
}
}
mod rows_with_ratios_tests {
use super::*;
#[test]
fn test_rows_equal_height_no_ratios() {
let area = test_area();
let layouts = rows_with_ratios(&area, 4, None);
assert_eq!(layouts.len(), 4);
// Each row should be 200 pixels tall (800 / 4)
for layout in &layouts {
assert_eq!(layout.bottom, 200);
assert_eq!(layout.right, 1000);
}
}
#[test]
fn test_rows_with_single_ratio() {
let area = test_area();
let opts = layout_options_with_row_ratios(&[0.5]);
let layouts = rows_with_ratios(&area, 3, opts.row_ratios);
assert_eq!(layouts.len(), 3);
// First row: 50% of 800 = 400
assert_eq!(layouts[0].bottom, 400);
// Remaining 400 split between 2 rows = 200 each
assert_eq!(layouts[1].bottom, 200);
assert_eq!(layouts[2].bottom, 200);
}
#[test]
fn test_rows_positions_are_correct() {
let area = test_area();
let opts = layout_options_with_row_ratios(&[0.25, 0.25]);
let layouts = rows_with_ratios(&area, 3, opts.row_ratios);
// First row starts at top
assert_eq!(layouts[0].top, 0);
// Second row starts where first ends
assert_eq!(layouts[1].top, layouts[0].bottom);
// Third row starts where second ends
assert_eq!(layouts[2].top, layouts[1].top + layouts[1].bottom);
}
#[test]
fn test_rows_last_row_gets_remaining_space() {
let area = test_area();
let opts = layout_options_with_row_ratios(&[0.25]);
let layouts = rows_with_ratios(&area, 2, opts.row_ratios);
assert_eq!(layouts.len(), 2);
// First row: 25% of 800 = 200
assert_eq!(layouts[0].bottom, 200);
// Last row gets remaining: 600
assert_eq!(layouts[1].bottom, 600);
}
}
mod vertical_stack_layout_tests {
use super::*;
#[test]
fn test_vertical_stack_default_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let layouts =
DefaultLayout::VerticalStack.calculate(&area, len, None, None, &[], 0, None, &[]);
assert_eq!(layouts.len(), 3);
// Primary column should be 50% (default ratio)
assert_eq!(layouts[0].right, 500);
}
#[test]
fn test_vertical_stack_custom_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_column_ratios(&[0.7]);
let layouts = DefaultLayout::VerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 3);
// Primary column should be 70%
assert_eq!(layouts[0].right, 700);
// Stack columns should share remaining 30%
assert_eq!(layouts[1].right, 300);
assert_eq!(layouts[2].right, 300);
}
#[test]
fn test_vertical_stack_with_row_ratios() {
let area = test_area();
let len = NonZeroUsize::new(4).unwrap();
let opts = layout_options_with_ratios(&[0.6], &[0.5, 0.3]);
let layouts = DefaultLayout::VerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 4);
// Primary column: 60%
assert_eq!(layouts[0].right, 600);
// Stack rows should use row_ratios
// First stack row: 50% of 800 = 400
assert_eq!(layouts[1].bottom, 400);
// Second stack row: 30% of 800 = 240
assert_eq!(layouts[2].bottom, 240);
}
#[test]
fn test_vertical_stack_single_window() {
let area = test_area();
let len = NonZeroUsize::new(1).unwrap();
let opts = layout_options_with_column_ratios(&[0.6]);
let layouts = DefaultLayout::VerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 1);
// Single window should take full width
assert_eq!(layouts[0].right, 1000);
}
}
mod horizontal_stack_layout_tests {
use super::*;
#[test]
fn test_horizontal_stack_default_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let layouts =
DefaultLayout::HorizontalStack.calculate(&area, len, None, None, &[], 0, None, &[]);
assert_eq!(layouts.len(), 3);
// Primary row should be 50% height (default ratio)
assert_eq!(layouts[0].bottom, 400);
}
#[test]
fn test_horizontal_stack_custom_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_row_ratios(&[0.7]);
let layouts = DefaultLayout::HorizontalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 3);
// Primary row should be 70% height
assert_eq!(layouts[0].bottom, 560);
}
}
mod ultrawide_layout_tests {
use super::*;
#[test]
fn test_ultrawide_default_ratios() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let layouts = DefaultLayout::UltrawideVerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
None,
&[],
);
assert_eq!(layouts.len(), 3);
// Primary (center): 50% = 500
assert_eq!(layouts[0].right, 500);
// Secondary (left): 25% = 250
assert_eq!(layouts[1].right, 250);
// Tertiary gets remaining: 250
assert_eq!(layouts[2].right, 250);
}
#[test]
fn test_ultrawide_custom_ratios() {
let area = test_area();
let len = NonZeroUsize::new(4).unwrap();
let opts = layout_options_with_column_ratios(&[0.5, 0.2]);
let layouts = DefaultLayout::UltrawideVerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 4);
// Primary (center): 50% = 500
assert_eq!(layouts[0].right, 500);
// Secondary (left): 20% = 200
assert_eq!(layouts[1].right, 200);
// Tertiary column gets remaining: 300
assert_eq!(layouts[2].right, 300);
assert_eq!(layouts[3].right, 300);
}
#[test]
fn test_ultrawide_two_windows() {
let area = test_area();
let len = NonZeroUsize::new(2).unwrap();
let opts = layout_options_with_column_ratios(&[0.6]);
let layouts = DefaultLayout::UltrawideVerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 2);
// Primary: 60% = 600
assert_eq!(layouts[0].right, 600);
// Secondary gets remaining: 400
assert_eq!(layouts[1].right, 400);
}
}
mod bsp_layout_tests {
use super::*;
#[test]
fn test_bsp_default_ratio() {
let area = test_area();
let len = NonZeroUsize::new(2).unwrap();
let layouts = DefaultLayout::BSP.calculate(&area, len, None, None, &[], 0, None, &[]);
assert_eq!(layouts.len(), 2);
// First window should be 50% width
assert_eq!(layouts[0].right, 500);
}
#[test]
fn test_bsp_custom_column_ratio() {
let area = test_area();
let len = NonZeroUsize::new(2).unwrap();
let opts = layout_options_with_column_ratios(&[0.7]);
let layouts =
DefaultLayout::BSP.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);
assert_eq!(layouts.len(), 2);
// First window should be 70% width
assert_eq!(layouts[0].right, 700);
}
#[test]
fn test_bsp_custom_row_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_ratios(&[0.5], &[0.7]);
let layouts =
DefaultLayout::BSP.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);
assert_eq!(layouts.len(), 3);
// Second window should be 70% of remaining height
assert_eq!(layouts[1].bottom, 560);
}
}
mod right_main_vertical_stack_tests {
use super::*;
#[test]
fn test_right_main_default_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let layouts = DefaultLayout::RightMainVerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
None,
&[],
);
assert_eq!(layouts.len(), 3);
// Primary should be on the right, 50% width
assert_eq!(layouts[0].right, 500);
assert_eq!(layouts[0].left, 500); // Right side
}
#[test]
fn test_right_main_custom_ratio() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_column_ratios(&[0.6]);
let layouts = DefaultLayout::RightMainVerticalStack.calculate(
&area,
len,
None,
None,
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 3);
// Primary: 60% = 600
assert_eq!(layouts[0].right, 600);
// Should be positioned on the right
assert_eq!(layouts[0].left, 400);
}
}
mod columns_layout_tests {
use super::*;
#[test]
fn test_columns_layout_with_ratios() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_column_ratios(&[0.2, 0.5]);
let layouts =
DefaultLayout::Columns.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);
assert_eq!(layouts.len(), 3);
assert_eq!(layouts[0].right, 200); // 20%
assert_eq!(layouts[1].right, 500); // 50%
assert_eq!(layouts[2].right, 300); // remaining
}
}
mod rows_layout_tests {
use super::*;
#[test]
fn test_rows_layout_with_ratios() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_row_ratios(&[0.25, 0.5]);
let layouts =
DefaultLayout::Rows.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);
assert_eq!(layouts.len(), 3);
assert_eq!(layouts[0].bottom, 200); // 25%
assert_eq!(layouts[1].bottom, 400); // 50%
assert_eq!(layouts[2].bottom, 200); // remaining
}
}
mod grid_layout_tests {
use super::*;
#[test]
fn test_grid_with_column_ratios() {
let area = test_area();
let len = NonZeroUsize::new(4).unwrap();
let opts = layout_options_with_column_ratios(&[0.3]);
let layouts =
DefaultLayout::Grid.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);
assert_eq!(layouts.len(), 4);
// Grid with 4 windows should be 2x2
// First column: 30% = 300
assert_eq!(layouts[0].right, 300);
assert_eq!(layouts[1].right, 300);
}
#[test]
fn test_grid_without_ratios() {
let area = test_area();
let len = NonZeroUsize::new(4).unwrap();
let layouts = DefaultLayout::Grid.calculate(&area, len, None, None, &[], 0, None, &[]);
assert_eq!(layouts.len(), 4);
// 2x2 grid, equal columns = 500 each
assert_eq!(layouts[0].right, 500);
assert_eq!(layouts[2].right, 500);
}
}
mod layout_flip_tests {
use super::*;
#[test]
fn test_columns_flip_horizontal() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_column_ratios(&[0.2, 0.3]);
let layouts = DefaultLayout::Columns.calculate(
&area,
len,
None,
Some(Axis::Horizontal),
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 3);
// Columns should be reversed
// Last column (originally 50%) should now be first
assert_eq!(layouts[2].left, 0);
}
#[test]
fn test_rows_flip_vertical() {
let area = test_area();
let len = NonZeroUsize::new(3).unwrap();
let opts = layout_options_with_row_ratios(&[0.25, 0.5]);
let layouts = DefaultLayout::Rows.calculate(
&area,
len,
None,
Some(Axis::Vertical),
&[],
0,
Some(opts),
&[],
);
assert_eq!(layouts.len(), 3);
// Rows should be reversed
// Last row should now be at top
assert_eq!(layouts[2].top, 0);
}
}
mod container_padding_tests {
use super::*;
#[test]
fn test_padding_applied_to_all_layouts() {
let area = test_area();
let len = NonZeroUsize::new(2).unwrap();
let padding = 10;
let layouts = DefaultLayout::Columns.calculate(
&area,
len,
Some(padding),
None,
&[],
0,
None,
&[],
);
assert_eq!(layouts.len(), 2);
// Each layout should have padding applied
// left increases, right decreases, top increases, bottom decreases
assert_eq!(layouts[0].left, padding);
assert_eq!(layouts[0].top, padding);
assert_eq!(layouts[0].right, 500 - padding * 2);
assert_eq!(layouts[0].bottom, 800 - padding * 2);
}
}
}
File diff suppressed because it is too large Load Diff
+388 -51
View File
@@ -1,5 +1,3 @@
use std::collections::HashMap;
use clap::ValueEnum; use clap::ValueEnum;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -25,38 +23,8 @@ pub const DEFAULT_RATIO: f32 = 0.5;
/// Default secondary ratio value for UltrawideVerticalStack layout /// Default secondary ratio value for UltrawideVerticalStack layout
pub const DEFAULT_SECONDARY_RATIO: f32 = 0.25; pub const DEFAULT_SECONDARY_RATIO: f32 = 0.25;
/// Validates and converts a Vec of ratios into a fixed-size array.
/// - Clamps values to MIN_RATIO..MAX_RATIO range
/// - Truncates when cumulative sum reaches or exceeds 1.0
/// - Limits to MAX_RATIOS values
#[must_use]
pub fn validate_ratios(ratios: &[f32]) -> [Option<f32>; MAX_RATIOS] {
let mut arr = [None; MAX_RATIOS];
let mut cumulative_sum = 0.0_f32;
for (i, &val) in ratios.iter().take(MAX_RATIOS).enumerate() {
let clamped_val = val.clamp(MIN_RATIO, MAX_RATIO);
// Only add this ratio if cumulative sum stays below 1.0
if cumulative_sum + clamped_val < 1.0 {
arr[i] = Some(clamped_val);
cumulative_sum += clamped_val;
} else {
// Stop adding ratios - cumulative sum would reach or exceed 1.0
tracing::debug!(
"Truncating ratios at index {} - cumulative sum {} + {} would reach/exceed 1.0",
i,
cumulative_sum,
clamped_val
);
break;
}
}
arr
}
#[derive( #[derive(
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Display, EnumString, ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
)] )]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// A predefined komorebi layout /// A predefined komorebi layout
@@ -169,7 +137,30 @@ where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let opt: Option<Vec<f32>> = Option::deserialize(deserializer)?; let opt: Option<Vec<f32>> = Option::deserialize(deserializer)?;
Ok(opt.map(|vec| validate_ratios(&vec))) Ok(opt.map(|vec| {
let mut arr = [None; MAX_RATIOS];
let mut cumulative_sum = 0.0_f32;
for (i, &val) in vec.iter().take(MAX_RATIOS).enumerate() {
let clamped_val = val.clamp(MIN_RATIO, MAX_RATIO);
// Only add this ratio if cumulative sum stays below 1.0
if cumulative_sum + clamped_val < 1.0 {
arr[i] = Some(clamped_val);
cumulative_sum += clamped_val;
} else {
// Stop adding ratios - cumulative sum would reach or exceed 1.0
tracing::debug!(
"Truncating ratios at index {} - cumulative sum {} + {} would reach/exceed 1.0",
i,
cumulative_sum,
clamped_val
);
break;
}
}
arr
}))
} }
/// Helper to serialize [Option<f32>; MAX_RATIOS] as a compact array (without trailing nulls) /// Helper to serialize [Option<f32>; MAX_RATIOS] as a compact array (without trailing nulls)
@@ -252,21 +243,6 @@ pub struct GridLayoutOptions {
pub rows: usize, pub rows: usize,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Per-layout default options entry for the `layout_defaults` global setting.
/// Contains both base layout options and threshold-based layout options rules.
pub struct LayoutDefaultEntry {
/// Default layout options for this layout
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_options: Option<LayoutOptions>,
/// Threshold-based layout options rules in the format of threshold => options.
/// When container count >= threshold, the highest matching threshold's options
/// fully replace the base `layout_options`.
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_options_rules: Option<HashMap<usize, LayoutOptions>>,
}
impl DefaultLayout { impl DefaultLayout {
pub fn leftmost_index(&self, len: usize) -> usize { pub fn leftmost_index(&self, len: usize) -> usize {
match self { match self {
@@ -437,5 +413,366 @@ impl DefaultLayout {
} }
#[cfg(test)] #[cfg(test)]
#[path = "default_layout_tests.rs"] mod tests {
mod tests; use super::*;
// Helper to create LayoutOptions with column ratios
fn layout_options_with_column_ratios(ratios: &[f32]) -> LayoutOptions {
let mut arr = [None; MAX_RATIOS];
for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {
arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: Some(arr),
row_ratios: None,
}
}
// Helper to create LayoutOptions with row ratios
fn layout_options_with_row_ratios(ratios: &[f32]) -> LayoutOptions {
let mut arr = [None; MAX_RATIOS];
for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {
arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: None,
row_ratios: Some(arr),
}
}
// Helper to create LayoutOptions with both column and row ratios
fn layout_options_with_ratios(column_ratios: &[f32], row_ratios: &[f32]) -> LayoutOptions {
let mut col_arr = [None; MAX_RATIOS];
for (i, &r) in column_ratios.iter().take(MAX_RATIOS).enumerate() {
col_arr[i] = Some(r);
}
let mut row_arr = [None; MAX_RATIOS];
for (i, &r) in row_ratios.iter().take(MAX_RATIOS).enumerate() {
row_arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: Some(col_arr),
row_ratios: Some(row_arr),
}
}
mod deserialize_ratios_tests {
use super::*;
#[test]
fn test_deserialize_valid_ratios() {
let json = r#"{"column_ratios": [0.3, 0.4, 0.2]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
assert_eq!(ratios[0], Some(0.3));
assert_eq!(ratios[1], Some(0.4));
assert_eq!(ratios[2], Some(0.2));
assert_eq!(ratios[3], None);
assert_eq!(ratios[4], None);
}
#[test]
fn test_deserialize_clamps_values_to_min() {
// Values below MIN_RATIO should be clamped
let json = r#"{"column_ratios": [0.05]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
assert_eq!(ratios[0], Some(MIN_RATIO)); // Clamped to 0.1
}
#[test]
fn test_deserialize_clamps_values_to_max() {
// Values above MAX_RATIO should be clamped
let json = r#"{"column_ratios": [0.95]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
// 0.9 is the max, so it should be clamped
assert!(ratios[0].unwrap() <= MAX_RATIO);
}
#[test]
fn test_deserialize_truncates_when_sum_exceeds_one() {
// Sum of ratios should not reach 1.0
// [0.5, 0.4] = 0.9, then 0.3 would make it 1.2, so it should be truncated
let json = r#"{"column_ratios": [0.5, 0.4, 0.3]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
assert_eq!(ratios[0], Some(0.5));
assert_eq!(ratios[1], Some(0.4));
// Third ratio should be truncated because 0.5 + 0.4 + 0.3 >= 1.0
assert_eq!(ratios[2], None);
}
#[test]
fn test_deserialize_truncates_at_max_ratios() {
// More than MAX_RATIOS values should be truncated
let json = r#"{"column_ratios": [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
// Only MAX_RATIOS (5) values should be stored
for i in 0..MAX_RATIOS {
assert_eq!(ratios[i], Some(0.1));
}
}
#[test]
fn test_deserialize_empty_array() {
let json = r#"{"column_ratios": []}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
for i in 0..MAX_RATIOS {
assert_eq!(ratios[i], None);
}
}
#[test]
fn test_deserialize_null() {
let json = r#"{"column_ratios": null}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.column_ratios.is_none());
}
#[test]
fn test_deserialize_row_ratios() {
let json = r#"{"row_ratios": [0.3, 0.5]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.row_ratios.unwrap();
assert_eq!(ratios[0], Some(0.3));
assert_eq!(ratios[1], Some(0.5));
assert_eq!(ratios[2], None);
}
}
mod serialize_ratios_tests {
use super::*;
#[test]
fn test_serialize_ratios_compact() {
let opts = layout_options_with_column_ratios(&[0.3, 0.4]);
let json = serde_json::to_string(&opts).unwrap();
// Should serialize ratios as compact array without trailing nulls in the ratios array
assert!(json.contains("0.3") && json.contains("0.4"));
}
#[test]
fn test_serialize_none_ratios() {
let opts = LayoutOptions {
scrolling: None,
grid: None,
column_ratios: None,
row_ratios: None,
};
let json = serde_json::to_string(&opts).unwrap();
// None values should serialize as null or be omitted
assert!(!json.contains("["));
}
#[test]
fn test_roundtrip_serialization() {
let original = layout_options_with_column_ratios(&[0.3, 0.4, 0.2]);
let json = serde_json::to_string(&original).unwrap();
let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();
assert_eq!(original.column_ratios, deserialized.column_ratios);
}
#[test]
fn test_serialize_row_ratios() {
let opts = layout_options_with_row_ratios(&[0.3, 0.5]);
let json = serde_json::to_string(&opts).unwrap();
assert!(json.contains("row_ratios"));
assert!(json.contains("0.3") && json.contains("0.5"));
}
#[test]
fn test_roundtrip_row_ratios() {
let original = layout_options_with_row_ratios(&[0.4, 0.3]);
let json = serde_json::to_string(&original).unwrap();
let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();
assert_eq!(original.row_ratios, deserialized.row_ratios);
assert!(original.column_ratios.is_none());
}
#[test]
fn test_roundtrip_both_ratios() {
let original = layout_options_with_ratios(&[0.3, 0.4], &[0.5, 0.3]);
let json = serde_json::to_string(&original).unwrap();
let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();
assert_eq!(original.column_ratios, deserialized.column_ratios);
assert_eq!(original.row_ratios, deserialized.row_ratios);
}
}
mod ratio_constants_tests {
use super::*;
#[test]
fn test_constants_valid_ranges() {
assert!(MIN_RATIO > 0.0);
assert!(MIN_RATIO < MAX_RATIO);
assert!(MAX_RATIO < 1.0);
assert!(DEFAULT_RATIO >= MIN_RATIO && DEFAULT_RATIO <= MAX_RATIO);
assert!(DEFAULT_SECONDARY_RATIO >= MIN_RATIO && DEFAULT_SECONDARY_RATIO <= MAX_RATIO);
assert!(MAX_RATIOS >= 1);
}
#[test]
fn test_default_ratio_is_half() {
assert_eq!(DEFAULT_RATIO, 0.5);
}
#[test]
fn test_max_ratios_is_five() {
assert_eq!(MAX_RATIOS, 5);
}
}
mod layout_options_tests {
use super::*;
#[test]
fn test_layout_options_default_values() {
let json = r#"{}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.scrolling.is_none());
assert!(opts.grid.is_none());
assert!(opts.column_ratios.is_none());
assert!(opts.row_ratios.is_none());
}
#[test]
fn test_layout_options_with_all_fields() {
let json = r#"{
"scrolling": {"columns": 3},
"grid": {"rows": 2},
"column_ratios": [0.3, 0.4],
"row_ratios": [0.5]
}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.scrolling.is_some());
assert_eq!(opts.scrolling.unwrap().columns, 3);
assert!(opts.grid.is_some());
assert_eq!(opts.grid.unwrap().rows, 2);
assert!(opts.column_ratios.is_some());
assert!(opts.row_ratios.is_some());
}
}
mod default_layout_tests {
use super::*;
#[test]
fn test_cycle_next_covers_all_layouts() {
let start = DefaultLayout::BSP;
let mut current = start;
let mut visited = vec![current];
loop {
current = current.cycle_next();
if current == start {
break;
}
assert!(
!visited.contains(&current),
"Cycle contains duplicate: {:?}",
current
);
visited.push(current);
}
// Should have visited all layouts
assert_eq!(visited.len(), 9); // 9 layouts total
}
#[test]
fn test_cycle_previous_is_inverse_of_next() {
// Note: cycle_previous has some inconsistencies in the current implementation
// This test documents the expected behavior for most layouts
let layouts_with_correct_inverse = [
DefaultLayout::Columns,
DefaultLayout::Rows,
DefaultLayout::VerticalStack,
DefaultLayout::HorizontalStack,
DefaultLayout::UltrawideVerticalStack,
DefaultLayout::Grid,
DefaultLayout::RightMainVerticalStack,
];
for layout in layouts_with_correct_inverse {
let next = layout.cycle_next();
assert_eq!(
next.cycle_previous(),
layout,
"cycle_previous should be inverse of cycle_next for {:?}",
layout
);
}
}
#[test]
fn test_leftmost_index_standard_layouts() {
assert_eq!(DefaultLayout::BSP.leftmost_index(5), 0);
assert_eq!(DefaultLayout::Columns.leftmost_index(5), 0);
assert_eq!(DefaultLayout::Rows.leftmost_index(5), 0);
assert_eq!(DefaultLayout::VerticalStack.leftmost_index(5), 0);
assert_eq!(DefaultLayout::HorizontalStack.leftmost_index(5), 0);
assert_eq!(DefaultLayout::Grid.leftmost_index(5), 0);
}
#[test]
fn test_leftmost_index_ultrawide() {
assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(1), 0);
assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(2), 1);
assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(5), 1);
}
#[test]
fn test_leftmost_index_right_main() {
assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(1), 0);
assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(2), 1);
assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(5), 1);
}
#[test]
fn test_rightmost_index_standard_layouts() {
assert_eq!(DefaultLayout::BSP.rightmost_index(5), 4);
assert_eq!(DefaultLayout::Columns.rightmost_index(5), 4);
assert_eq!(DefaultLayout::Rows.rightmost_index(5), 4);
assert_eq!(DefaultLayout::VerticalStack.rightmost_index(5), 4);
}
#[test]
fn test_rightmost_index_right_main() {
assert_eq!(DefaultLayout::RightMainVerticalStack.rightmost_index(1), 0);
assert_eq!(DefaultLayout::RightMainVerticalStack.rightmost_index(5), 0);
}
#[test]
fn test_rightmost_index_ultrawide() {
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(1), 0);
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(2), 0);
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(3), 2);
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(5), 4);
}
}
}
@@ -1,954 +0,0 @@
use super::*;
// Helper to create LayoutOptions with column ratios
fn layout_options_with_column_ratios(ratios: &[f32]) -> LayoutOptions {
let mut arr = [None; MAX_RATIOS];
for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {
arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: Some(arr),
row_ratios: None,
}
}
// Helper to create LayoutOptions with row ratios
fn layout_options_with_row_ratios(ratios: &[f32]) -> LayoutOptions {
let mut arr = [None; MAX_RATIOS];
for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {
arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: None,
row_ratios: Some(arr),
}
}
// Helper to create LayoutOptions with both column and row ratios
fn layout_options_with_ratios(column_ratios: &[f32], row_ratios: &[f32]) -> LayoutOptions {
let mut col_arr = [None; MAX_RATIOS];
for (i, &r) in column_ratios.iter().take(MAX_RATIOS).enumerate() {
col_arr[i] = Some(r);
}
let mut row_arr = [None; MAX_RATIOS];
for (i, &r) in row_ratios.iter().take(MAX_RATIOS).enumerate() {
row_arr[i] = Some(r);
}
LayoutOptions {
scrolling: None,
grid: None,
column_ratios: Some(col_arr),
row_ratios: Some(row_arr),
}
}
mod deserialize_ratios_tests {
use super::*;
#[test]
fn test_deserialize_valid_ratios() {
let json = r#"{"column_ratios": [0.3, 0.4, 0.2]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
assert_eq!(ratios[0], Some(0.3));
assert_eq!(ratios[1], Some(0.4));
assert_eq!(ratios[2], Some(0.2));
assert_eq!(ratios[3], None);
assert_eq!(ratios[4], None);
}
#[test]
fn test_deserialize_clamps_values_to_min() {
// Values below MIN_RATIO should be clamped
let json = r#"{"column_ratios": [0.05]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
assert_eq!(ratios[0], Some(MIN_RATIO)); // Clamped to 0.1
}
#[test]
fn test_deserialize_clamps_values_to_max() {
// Values above MAX_RATIO should be clamped
let json = r#"{"column_ratios": [0.95]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
// 0.9 is the max, so it should be clamped
assert!(ratios[0].unwrap() <= MAX_RATIO);
}
#[test]
fn test_deserialize_truncates_when_sum_exceeds_one() {
// Sum of ratios should not reach 1.0
// [0.5, 0.4] = 0.9, then 0.3 would make it 1.2, so it should be truncated
let json = r#"{"column_ratios": [0.5, 0.4, 0.3]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
assert_eq!(ratios[0], Some(0.5));
assert_eq!(ratios[1], Some(0.4));
// Third ratio should be truncated because 0.5 + 0.4 + 0.3 >= 1.0
assert_eq!(ratios[2], None);
}
#[test]
fn test_deserialize_truncates_at_max_ratios() {
// More than MAX_RATIOS values should be truncated
let json = r#"{"column_ratios": [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
// Only MAX_RATIOS (5) values should be stored
for item in ratios.iter().take(MAX_RATIOS) {
assert_eq!(*item, Some(0.1));
}
}
#[test]
fn test_deserialize_empty_array() {
let json = r#"{"column_ratios": []}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.column_ratios.unwrap();
for item in ratios.iter().take(MAX_RATIOS) {
assert_eq!(*item, None);
}
}
#[test]
fn test_deserialize_null() {
let json = r#"{"column_ratios": null}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.column_ratios.is_none());
}
#[test]
fn test_deserialize_row_ratios() {
let json = r#"{"row_ratios": [0.3, 0.5]}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let ratios = opts.row_ratios.unwrap();
assert_eq!(ratios[0], Some(0.3));
assert_eq!(ratios[1], Some(0.5));
assert_eq!(ratios[2], None);
}
}
mod serialize_ratios_tests {
use super::*;
#[test]
fn test_serialize_ratios_compact() {
let opts = layout_options_with_column_ratios(&[0.3, 0.4]);
let json = serde_json::to_string(&opts).unwrap();
// Should serialize ratios as compact array without trailing nulls in the ratios array
assert!(json.contains("0.3") && json.contains("0.4"));
}
#[test]
fn test_serialize_none_ratios() {
let opts = LayoutOptions {
scrolling: None,
grid: None,
column_ratios: None,
row_ratios: None,
};
let json = serde_json::to_string(&opts).unwrap();
// None values should serialize as null or be omitted
assert!(!json.contains("["));
}
#[test]
fn test_roundtrip_serialization() {
let original = layout_options_with_column_ratios(&[0.3, 0.4, 0.2]);
let json = serde_json::to_string(&original).unwrap();
let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();
assert_eq!(original.column_ratios, deserialized.column_ratios);
}
#[test]
fn test_serialize_row_ratios() {
let opts = layout_options_with_row_ratios(&[0.3, 0.5]);
let json = serde_json::to_string(&opts).unwrap();
assert!(json.contains("row_ratios"));
assert!(json.contains("0.3") && json.contains("0.5"));
}
#[test]
fn test_roundtrip_row_ratios() {
let original = layout_options_with_row_ratios(&[0.4, 0.3]);
let json = serde_json::to_string(&original).unwrap();
let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();
assert_eq!(original.row_ratios, deserialized.row_ratios);
assert!(original.column_ratios.is_none());
}
#[test]
fn test_roundtrip_both_ratios() {
let original = layout_options_with_ratios(&[0.3, 0.4], &[0.5, 0.3]);
let json = serde_json::to_string(&original).unwrap();
let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();
assert_eq!(original.column_ratios, deserialized.column_ratios);
assert_eq!(original.row_ratios, deserialized.row_ratios);
}
}
mod ratio_constants_tests {
use super::*;
#[test]
fn test_constants_valid_ranges() {
const {
assert!(MIN_RATIO > 0.0);
assert!(MIN_RATIO < MAX_RATIO);
assert!(MAX_RATIO < 1.0);
assert!(DEFAULT_RATIO >= MIN_RATIO && DEFAULT_RATIO <= MAX_RATIO);
assert!(DEFAULT_SECONDARY_RATIO >= MIN_RATIO && DEFAULT_SECONDARY_RATIO <= MAX_RATIO);
assert!(MAX_RATIOS >= 1);
}
}
#[test]
fn test_default_ratio_is_half() {
assert_eq!(DEFAULT_RATIO, 0.5);
}
#[test]
fn test_max_ratios_is_five() {
assert_eq!(MAX_RATIOS, 5);
}
}
mod layout_options_tests {
use super::*;
#[test]
fn test_layout_options_default_values() {
let json = r#"{}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.scrolling.is_none());
assert!(opts.grid.is_none());
assert!(opts.column_ratios.is_none());
assert!(opts.row_ratios.is_none());
}
#[test]
fn test_layout_options_with_all_fields() {
let json = r#"{
"scrolling": {"columns": 3},
"grid": {"rows": 2},
"column_ratios": [0.3, 0.4],
"row_ratios": [0.5]
}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.scrolling.is_some());
assert_eq!(opts.scrolling.unwrap().columns, 3);
assert!(opts.grid.is_some());
assert_eq!(opts.grid.unwrap().rows, 2);
assert!(opts.column_ratios.is_some());
assert!(opts.row_ratios.is_some());
}
}
mod default_layout_tests {
use super::*;
#[test]
fn test_cycle_next_covers_all_layouts() {
let start = DefaultLayout::BSP;
let mut current = start;
let mut visited = vec![current];
loop {
current = current.cycle_next();
if current == start {
break;
}
assert!(
!visited.contains(&current),
"Cycle contains duplicate: {:?}",
current
);
visited.push(current);
}
// Should have visited all layouts
assert_eq!(visited.len(), 9); // 9 layouts total
}
#[test]
fn test_cycle_previous_is_inverse_of_next() {
// Note: cycle_previous has some inconsistencies in the current implementation
// This test documents the expected behavior for most layouts
let layouts_with_correct_inverse = [
DefaultLayout::Columns,
DefaultLayout::Rows,
DefaultLayout::VerticalStack,
DefaultLayout::HorizontalStack,
DefaultLayout::UltrawideVerticalStack,
DefaultLayout::Grid,
DefaultLayout::RightMainVerticalStack,
];
for layout in layouts_with_correct_inverse {
let next = layout.cycle_next();
assert_eq!(
next.cycle_previous(),
layout,
"cycle_previous should be inverse of cycle_next for {:?}",
layout
);
}
}
#[test]
fn test_leftmost_index_standard_layouts() {
assert_eq!(DefaultLayout::BSP.leftmost_index(5), 0);
assert_eq!(DefaultLayout::Columns.leftmost_index(5), 0);
assert_eq!(DefaultLayout::Rows.leftmost_index(5), 0);
assert_eq!(DefaultLayout::VerticalStack.leftmost_index(5), 0);
assert_eq!(DefaultLayout::HorizontalStack.leftmost_index(5), 0);
assert_eq!(DefaultLayout::Grid.leftmost_index(5), 0);
}
#[test]
fn test_leftmost_index_ultrawide() {
assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(1), 0);
assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(2), 1);
assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(5), 1);
}
#[test]
fn test_leftmost_index_right_main() {
assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(1), 0);
assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(2), 1);
assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(5), 1);
}
#[test]
fn test_rightmost_index_standard_layouts() {
assert_eq!(DefaultLayout::BSP.rightmost_index(5), 4);
assert_eq!(DefaultLayout::Columns.rightmost_index(5), 4);
assert_eq!(DefaultLayout::Rows.rightmost_index(5), 4);
assert_eq!(DefaultLayout::VerticalStack.rightmost_index(5), 4);
}
#[test]
fn test_rightmost_index_right_main() {
assert_eq!(DefaultLayout::RightMainVerticalStack.rightmost_index(1), 0);
assert_eq!(DefaultLayout::RightMainVerticalStack.rightmost_index(5), 0);
}
#[test]
fn test_rightmost_index_ultrawide() {
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(1), 0);
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(2), 0);
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(3), 2);
assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(5), 4);
}
}
mod layout_options_rules_tests {
use super::*;
#[test]
fn test_hashmap_deserialization_ratios_only() {
// layout_options_rules entries with only ratios
// Note: ratios must sum to < 1.0 to avoid truncation by validate_ratios
let json = r#"{
"2": {"column_ratios": [0.7]},
"3": {"column_ratios": [0.55]},
"5": {"column_ratios": [0.3, 0.3, 0.3]}
}"#;
let rules: std::collections::HashMap<usize, LayoutOptions> =
serde_json::from_str(json).unwrap();
assert_eq!(rules.len(), 3);
assert_eq!(rules[&2].column_ratios.unwrap()[0], Some(0.7));
assert_eq!(rules[&3].column_ratios.unwrap()[0], Some(0.55));
let r5 = rules[&5].column_ratios.unwrap();
assert_eq!(r5[0], Some(0.3));
assert_eq!(r5[1], Some(0.3));
assert_eq!(r5[2], Some(0.3));
// No scrolling/grid in these entries
assert!(rules[&2].scrolling.is_none());
assert!(rules[&2].grid.is_none());
}
#[test]
fn test_hashmap_deserialization_full_options() {
// layout_options_rules entries with full options including scrolling/grid
let json = r#"{
"2": {"column_ratios": [0.7], "scrolling": {"columns": 3}},
"5": {"column_ratios": [0.3, 0.3, 0.3], "grid": {"rows": 2}}
}"#;
let rules: std::collections::HashMap<usize, LayoutOptions> =
serde_json::from_str(json).unwrap();
assert_eq!(rules.len(), 2);
assert_eq!(rules[&2].scrolling.unwrap().columns, 3);
assert!(rules[&2].grid.is_none());
assert!(rules[&5].scrolling.is_none());
assert_eq!(rules[&5].grid.unwrap().rows, 2);
}
#[test]
fn test_rule_entry_with_all_fields() {
let json = r#"{
"column_ratios": [0.6, 0.3],
"scrolling": {"columns": 4, "center_focused_column": true},
"grid": {"rows": 2},
"row_ratios": [0.5]
}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
let col = opts.column_ratios.unwrap();
assert_eq!(col[0], Some(0.6));
assert_eq!(col[1], Some(0.3));
let row = opts.row_ratios.unwrap();
assert_eq!(row[0], Some(0.5));
assert_eq!(opts.scrolling.unwrap().columns, 4);
assert_eq!(opts.scrolling.unwrap().center_focused_column, Some(true));
assert_eq!(opts.grid.unwrap().rows, 2);
}
#[test]
fn test_rule_entry_empty_object_gives_defaults() {
let json = r#"{}"#;
let opts: LayoutOptions = serde_json::from_str(json).unwrap();
assert!(opts.column_ratios.is_none());
assert!(opts.row_ratios.is_none());
assert!(opts.scrolling.is_none());
assert!(opts.grid.is_none());
}
}
mod layout_default_entry_tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_default_layout_as_hashmap_key() {
let mut map: HashMap<DefaultLayout, &str> = HashMap::new();
map.insert(DefaultLayout::BSP, "bsp");
map.insert(DefaultLayout::VerticalStack, "vstack");
map.insert(DefaultLayout::Columns, "cols");
assert_eq!(map.len(), 3);
assert_eq!(map[&DefaultLayout::BSP], "bsp");
assert_eq!(map[&DefaultLayout::VerticalStack], "vstack");
assert_eq!(map[&DefaultLayout::Columns], "cols");
}
#[test]
fn test_default_layout_hash_consistency() {
// Same variant inserted twice should overwrite
let mut map: HashMap<DefaultLayout, i32> = HashMap::new();
map.insert(DefaultLayout::Grid, 1);
map.insert(DefaultLayout::Grid, 2);
assert_eq!(map.len(), 1);
assert_eq!(map[&DefaultLayout::Grid], 2);
}
#[test]
fn test_layout_default_entry_deserialize_full() {
let json = r#"{
"layout_options": {"column_ratios": [0.7]},
"layout_options_rules": {
"2": {"column_ratios": [0.7]},
"3": {"column_ratios": [0.55]},
"5": {"column_ratios": [0.3, 0.3, 0.3]}
}
}"#;
let entry: LayoutDefaultEntry = serde_json::from_str(json).unwrap();
let base = entry.layout_options.unwrap();
assert_eq!(base.column_ratios.unwrap()[0], Some(0.7));
let rules = entry.layout_options_rules.unwrap();
assert_eq!(rules.len(), 3);
assert_eq!(rules[&2].column_ratios.unwrap()[0], Some(0.7));
assert_eq!(rules[&3].column_ratios.unwrap()[0], Some(0.55));
let r5 = rules[&5].column_ratios.unwrap();
assert_eq!(r5[0], Some(0.3));
assert_eq!(r5[1], Some(0.3));
assert_eq!(r5[2], Some(0.3));
}
#[test]
fn test_layout_default_entry_deserialize_only_base() {
let json = r#"{
"layout_options": {"column_ratios": [0.6]}
}"#;
let entry: LayoutDefaultEntry = serde_json::from_str(json).unwrap();
assert!(entry.layout_options.is_some());
assert_eq!(
entry.layout_options.unwrap().column_ratios.unwrap()[0],
Some(0.6)
);
assert!(entry.layout_options_rules.is_none());
}
#[test]
fn test_layout_default_entry_deserialize_only_rules() {
let json = r#"{
"layout_options_rules": {
"3": {"column_ratios": [0.4]}
}
}"#;
let entry: LayoutDefaultEntry = serde_json::from_str(json).unwrap();
assert!(entry.layout_options.is_none());
let rules = entry.layout_options_rules.unwrap();
assert_eq!(rules.len(), 1);
assert_eq!(rules[&3].column_ratios.unwrap()[0], Some(0.4));
}
#[test]
fn test_layout_default_entry_deserialize_empty() {
let json = r#"{}"#;
let entry: LayoutDefaultEntry = serde_json::from_str(json).unwrap();
assert!(entry.layout_options.is_none());
assert!(entry.layout_options_rules.is_none());
}
#[test]
fn test_layout_default_entry_roundtrip() {
let json = r#"{
"layout_options": {"column_ratios": [0.7]},
"layout_options_rules": {
"2": {"column_ratios": [0.6]},
"5": {"column_ratios": [0.3, 0.3, 0.3]}
}
}"#;
let original: LayoutDefaultEntry = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: LayoutDefaultEntry = serde_json::from_str(&serialized).unwrap();
assert_eq!(
original.layout_options.unwrap().column_ratios,
deserialized.layout_options.unwrap().column_ratios
);
let orig_rules = original.layout_options_rules.unwrap();
let deser_rules = deserialized.layout_options_rules.unwrap();
assert_eq!(orig_rules.len(), deser_rules.len());
for (key, orig_opts) in &orig_rules {
let deser_opts = &deser_rules[key];
assert_eq!(orig_opts.column_ratios, deser_opts.column_ratios);
}
}
#[test]
fn test_layout_defaults_full_config_deserialize() {
// Simulate the top-level layout_defaults field
let json = r#"{
"VerticalStack": {
"layout_options": {"column_ratios": [0.7]},
"layout_options_rules": {
"2": {"column_ratios": [0.7]},
"3": {"column_ratios": [0.55]}
}
},
"HorizontalStack": {
"layout_options": {"column_ratios": [0.6]}
},
"Columns": {
"layout_options_rules": {
"4": {"column_ratios": [0.3, 0.3, 0.3]}
}
}
}"#;
let defaults: HashMap<DefaultLayout, LayoutDefaultEntry> =
serde_json::from_str(json).unwrap();
assert_eq!(defaults.len(), 3);
// VerticalStack: has both base and rules
let vs = &defaults[&DefaultLayout::VerticalStack];
assert!(vs.layout_options.is_some());
assert_eq!(vs.layout_options_rules.as_ref().unwrap().len(), 2);
// HorizontalStack: has only base
let hs = &defaults[&DefaultLayout::HorizontalStack];
assert!(hs.layout_options.is_some());
assert!(hs.layout_options_rules.is_none());
// Columns: has only rules
let cols = &defaults[&DefaultLayout::Columns];
assert!(cols.layout_options.is_none());
assert_eq!(cols.layout_options_rules.as_ref().unwrap().len(), 1);
}
#[test]
fn test_layout_default_entry_with_scrolling_and_grid() {
let json = r#"{
"layout_options": {
"column_ratios": [0.5],
"scrolling": {"columns": 3},
"grid": {"rows": 2}
},
"layout_options_rules": {
"4": {
"scrolling": {"columns": 5, "center_focused_column": true}
}
}
}"#;
let entry: LayoutDefaultEntry = serde_json::from_str(json).unwrap();
let base = entry.layout_options.unwrap();
assert_eq!(base.scrolling.unwrap().columns, 3);
assert_eq!(base.grid.unwrap().rows, 2);
let rules = entry.layout_options_rules.unwrap();
let r4 = &rules[&4];
assert_eq!(r4.scrolling.unwrap().columns, 5);
assert_eq!(r4.scrolling.unwrap().center_focused_column, Some(true));
// Rule doesn't inherit base fields - full replacement
assert!(r4.column_ratios.is_none());
assert!(r4.grid.is_none());
}
#[test]
fn test_layout_default_entry_skip_serializing_none() {
// When both fields are None, they should not appear in output
let entry = LayoutDefaultEntry {
layout_options: None,
layout_options_rules: None,
};
let json = serde_json::to_string(&entry).unwrap();
assert!(!json.contains("layout_options"));
assert!(!json.contains("layout_options_rules"));
assert_eq!(json, "{}");
}
}
/// Tests for the complete-replacement cascade logic.
///
/// This mirrors the resolution algorithm in workspace.rs::update():
/// - If the workspace defines EITHER layout_options OR layout_options_rules,
/// it completely replaces the global layout_defaults for this layout.
/// - Global defaults are only used when the workspace has NEITHER setting.
/// - Within the effective source (workspace or global):
/// 1. Try threshold match from rules (highest matching threshold wins)
/// 2. If a rule matches -> use it (full replacement of base)
/// 3. Else -> use the base layout_options
///
/// Since the actual cascade is in workspace.rs (which has heavy WM dependencies),
/// we test the pure algorithm here using the same data structures.
mod cascade_resolution_tests {
use super::*;
/// Simulates the cascade resolution logic from workspace.rs::update().
/// This is a pure function equivalent of the inline code in update().
fn resolve_effective_options(
container_count: usize,
workspace_base: Option<LayoutOptions>,
workspace_rules: &[(usize, LayoutOptions)], // sorted by threshold ascending
global_base: Option<LayoutOptions>,
global_rules: &[(usize, LayoutOptions)], // sorted by threshold ascending
) -> Option<LayoutOptions> {
let has_workspace_overrides = workspace_base.is_some() || !workspace_rules.is_empty();
let (effective_base, effective_rules): (Option<LayoutOptions>, &[(usize, LayoutOptions)]) =
if has_workspace_overrides {
(workspace_base, workspace_rules)
} else {
(global_base, global_rules)
};
// Try threshold match from effective rules
let mut matched = None;
for (threshold, opts) in effective_rules {
if container_count >= *threshold {
matched = Some(*opts);
}
}
// If a rule matched, use it (full replacement); otherwise use effective base
if matched.is_some() {
matched
} else {
effective_base
}
}
fn opts_with_ratio(ratio: f32) -> LayoutOptions {
layout_options_with_column_ratios(&[ratio])
}
// --- No overrides ---
#[test]
fn test_no_workspace_no_global_returns_none() {
let result = resolve_effective_options(3, None, &[], None, &[]);
assert!(result.is_none());
}
// --- Base-only scenarios ---
#[test]
fn test_workspace_base_only() {
let ws_base = opts_with_ratio(0.7);
let result = resolve_effective_options(3, Some(ws_base), &[], None, &[]);
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
#[test]
fn test_global_base_only() {
let global_base = opts_with_ratio(0.6);
let result = resolve_effective_options(3, None, &[], Some(global_base), &[]);
assert_eq!(result.unwrap().column_ratios, global_base.column_ratios);
}
#[test]
fn test_workspace_base_overrides_all_globals() {
// Workspace has base → globals (both base and rules) are ignored entirely
let ws_base = opts_with_ratio(0.7);
let global_base = opts_with_ratio(0.6);
let global_rules = vec![(2, opts_with_ratio(0.5))];
let result =
resolve_effective_options(3, Some(ws_base), &[], Some(global_base), &global_rules);
// Workspace base wins; global rules are NOT used even though they would match
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
// --- Rules-only scenarios ---
#[test]
fn test_global_rules_match() {
let global_rules = vec![(2, opts_with_ratio(0.6)), (4, opts_with_ratio(0.5))];
// 3 containers: matches threshold 2, not 4
let result = resolve_effective_options(3, None, &[], None, &global_rules);
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.6));
}
#[test]
fn test_global_rules_highest_matching_threshold_wins() {
let global_rules = vec![(2, opts_with_ratio(0.6)), (4, opts_with_ratio(0.5))];
// 5 containers: matches both thresholds 2 and 4; highest (4) wins
let result = resolve_effective_options(5, None, &[], None, &global_rules);
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.5));
}
#[test]
fn test_global_rules_no_match_falls_through_to_none() {
let global_rules = vec![(5, opts_with_ratio(0.5))];
// 3 containers: doesn't match threshold 5
let result = resolve_effective_options(3, None, &[], None, &global_rules);
assert!(result.is_none());
}
#[test]
fn test_global_rules_no_match_falls_through_to_global_base() {
let global_base = opts_with_ratio(0.6);
let global_rules = vec![(5, opts_with_ratio(0.5))];
// 3 containers: doesn't match threshold 5, falls back to global base
let result = resolve_effective_options(3, None, &[], Some(global_base), &global_rules);
assert_eq!(result.unwrap().column_ratios, global_base.column_ratios);
}
#[test]
fn test_workspace_rules_override_global_rules() {
let ws_rules = vec![(2, opts_with_ratio(0.8))];
let global_rules = vec![(2, opts_with_ratio(0.6))];
// Workspace has rules → global rules are ignored entirely
let result = resolve_effective_options(3, None, &ws_rules, None, &global_rules);
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.8));
}
// --- Complete replacement: workspace having EITHER setting disables ALL globals ---
#[test]
fn test_workspace_rules_disable_global_base() {
// Workspace has rules but no base. Global has base.
// Since workspace has a setting, globals are completely replaced.
let ws_rules = vec![(2, opts_with_ratio(0.8))];
let global_base = opts_with_ratio(0.6);
// Rule matches → use it. Global base is NOT available as fallback.
let result = resolve_effective_options(3, None, &ws_rules, Some(global_base), &[]);
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.8));
}
#[test]
fn test_workspace_rules_no_match_does_not_fall_to_global_base() {
// Workspace has rules (but they don't match). Global has base.
// Since workspace has a setting, globals are completely replaced → returns None.
let ws_rules = vec![(5, opts_with_ratio(0.8))];
let global_base = opts_with_ratio(0.6);
let result = resolve_effective_options(3, None, &ws_rules, Some(global_base), &[]);
// No workspace base, no rule match, globals ignored → None
assert!(result.is_none());
}
#[test]
fn test_workspace_base_disables_global_rules() {
// Workspace has base but no rules. Global has rules.
// Since workspace has a setting, globals are completely replaced.
let ws_base = opts_with_ratio(0.7);
let global_rules = vec![(2, opts_with_ratio(0.5))];
// No workspace rules → no rule match → use workspace base. Global rules ignored.
let result = resolve_effective_options(3, Some(ws_base), &[], None, &global_rules);
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
#[test]
fn test_workspace_base_disables_global_rules_and_base() {
// Workspace has base. Global has both rules and base.
// Since workspace has a setting, all globals are completely replaced.
let ws_base = opts_with_ratio(0.7);
let global_base = opts_with_ratio(0.6);
let global_rules = vec![(2, opts_with_ratio(0.5))];
let result =
resolve_effective_options(3, Some(ws_base), &[], Some(global_base), &global_rules);
// Only workspace base is used; global rules and base are both ignored
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
#[test]
fn test_workspace_rules_disable_global_rules_and_base() {
// Workspace has rules. Global has both rules and base.
// Since workspace has a setting, all globals are completely replaced.
let ws_rules = vec![(2, opts_with_ratio(0.8))];
let global_base = opts_with_ratio(0.6);
let global_rules = vec![(2, opts_with_ratio(0.5))];
let result =
resolve_effective_options(3, None, &ws_rules, Some(global_base), &global_rules);
// Workspace rule matches → 0.8. Global base and rules both ignored.
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.8));
}
// --- Full replacement semantics (rule match replaces base) ---
#[test]
fn test_rule_match_is_full_replacement_not_merge() {
// When a rule matches, its options FULLY REPLACE the base.
// Fields not specified in the rule default to their standard defaults.
let ws_base = layout_options_with_ratios(&[0.7], &[0.4]);
let rule_opts = layout_options_with_column_ratios(&[0.5]);
// rule_opts has column_ratios but no row_ratios
let ws_rules = vec![(2, rule_opts)];
let result = resolve_effective_options(3, Some(ws_base), &ws_rules, None, &[]);
let effective = result.unwrap();
// Column ratios come from the rule
assert_eq!(effective.column_ratios.unwrap()[0], Some(0.5));
// Row ratios are NOT inherited from ws_base - they're None (full replacement)
assert!(effective.row_ratios.is_none());
}
// --- Edge cases ---
#[test]
fn test_exact_threshold_match() {
let rules = vec![(3, opts_with_ratio(0.6))];
let result = resolve_effective_options(3, None, &rules, None, &[]);
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.6));
}
#[test]
fn test_container_count_one_below_threshold() {
let rules = vec![(3, opts_with_ratio(0.6))];
let result = resolve_effective_options(2, None, &rules, None, &[]);
assert!(result.is_none());
}
#[test]
fn test_zero_containers() {
let ws_base = opts_with_ratio(0.7);
let rules = vec![(1, opts_with_ratio(0.5))];
let result = resolve_effective_options(0, Some(ws_base), &rules, None, &[]);
// 0 containers doesn't match threshold 1 → falls back to workspace base
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
#[test]
fn test_many_thresholds_correct_match() {
let rules = vec![
(1, opts_with_ratio(0.8)),
(3, opts_with_ratio(0.6)),
(5, opts_with_ratio(0.4)),
(8, opts_with_ratio(0.3)),
];
// 6 containers: matches 1, 3, 5 but not 8. Highest match is 5.
let result = resolve_effective_options(6, None, &rules, None, &[]);
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.4));
}
#[test]
fn test_workspace_rules_disable_global_rules_even_if_ws_rules_dont_match() {
// Key behavior: if workspace has ANY setting, globals are entirely ignored.
// Even if workspace rules don't match, we don't fall back to global rules.
let ws_rules = vec![(10, opts_with_ratio(0.8))]; // threshold too high
let global_rules = vec![(2, opts_with_ratio(0.5))]; // would match
let result = resolve_effective_options(3, None, &ws_rules, None, &global_rules);
// Workspace has rules → all globals ignored. WS rules don't match → None.
assert!(result.is_none());
}
#[test]
fn test_all_four_sources_present_rules_match() {
// All four sources present: workspace base, workspace rules, global base, global rules
let ws_base = opts_with_ratio(0.7);
let ws_rules = vec![(2, opts_with_ratio(0.8))];
let global_base = opts_with_ratio(0.6);
let global_rules = vec![(2, opts_with_ratio(0.5))];
let result = resolve_effective_options(
3,
Some(ws_base),
&ws_rules,
Some(global_base),
&global_rules,
);
// Workspace has settings → uses workspace only. Rule matches → 0.8
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.8));
}
#[test]
fn test_all_four_sources_present_rules_no_match() {
// All four sources present, but workspace rules don't match
let ws_base = opts_with_ratio(0.7);
let ws_rules = vec![(10, opts_with_ratio(0.8))]; // threshold too high
let global_base = opts_with_ratio(0.6);
let global_rules = vec![(10, opts_with_ratio(0.5))]; // also too high
let result = resolve_effective_options(
3,
Some(ws_base),
&ws_rules,
Some(global_base),
&global_rules,
);
// Workspace has settings → uses workspace only. No rule match → workspace base 0.7
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
// --- Workspace with both base and rules ---
#[test]
fn test_workspace_both_rule_matches() {
let ws_base = opts_with_ratio(0.7);
let ws_rules = vec![(2, opts_with_ratio(0.5))];
let result = resolve_effective_options(3, Some(ws_base), &ws_rules, None, &[]);
// Rule matches → use rule (full replacement), not ws_base
assert_eq!(result.unwrap().column_ratios.unwrap()[0], Some(0.5));
}
#[test]
fn test_workspace_both_rule_no_match() {
let ws_base = opts_with_ratio(0.7);
let ws_rules = vec![(10, opts_with_ratio(0.5))];
let result = resolve_effective_options(3, Some(ws_base), &ws_rules, None, &[]);
// Rule doesn't match → fall back to ws_base
assert_eq!(result.unwrap().column_ratios, ws_base.column_ratios);
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
[package] [package]
name = "komorebi-themes" name = "komorebi-themes"
version = "0.1.41" version = "0.1.40"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "3f157904c641f0dc80f043449fe0214fc4182425" } base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "b9e26b31f7a0e7ed239b14e5317e95d1bdc544bd" }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui32"] } #catppuccin-egui = { version = "5", default-features = false, features = ["egui32"] }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a", default-features = false, features = [ catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a", default-features = false, features = [
"egui33", "egui33",
@@ -15,7 +15,7 @@ serde = { workspace = true }
serde_variant = "0.1" serde_variant = "0.1"
strum = { workspace = true } strum = { workspace = true }
hex_color = { version = "3", features = ["serde"] } hex_color = { version = "3", features = ["serde"] }
flavours = { git = "https://github.com/LGUG2Z/flavours", rev = "24518c129918fe3260aa559eded7657e50752cb1" } flavours = { git = "https://github.com/LGUG2Z/flavours", version = "0.7.2" }
[features] [features]
default = ["schemars"] default = ["schemars"]
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi" name = "komorebi"
version = "0.1.41" version = "0.1.40"
description = "A tiling window manager for Windows" description = "A tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi" repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024" edition = "2024"
@@ -51,7 +51,7 @@ windows-numerics = { workspace = true }
windows-implement = { workspace = true } windows-implement = { workspace = true }
windows-interface = { workspace = true } windows-interface = { workspace = true }
winput = "0.2" winput = "0.2"
winreg = "0.56" winreg = "0.55"
serde_with = { version = "3.12", features = ["schemars_1"] } serde_with = { version = "3.12", features = ["schemars_1"] }
[build-dependencies] [build-dependencies]
+6 -37
View File
@@ -58,7 +58,6 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW; use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
use windows::Win32::UI::WindowsAndMessaging::LoadCursorW; use windows::Win32::UI::WindowsAndMessaging::LoadCursorW;
use windows::Win32::UI::WindowsAndMessaging::MSG; use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN; use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
use windows::Win32::UI::WindowsAndMessaging::SetCursor; use windows::Win32::UI::WindowsAndMessaging::SetCursor;
@@ -68,16 +67,11 @@ use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR; use windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR;
use windows::Win32::UI::WindowsAndMessaging::WM_USER;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::BOOL; use windows_core::BOOL;
use windows_core::PCWSTR; use windows_core::PCWSTR;
use windows_numerics::Matrix3x2; use windows_numerics::Matrix3x2;
/// Custom WM_USER message that tells the border window thread to call update_brushes() on itself,
/// avoiding a data race between the border manager thread and the border's message loop thread.
pub const WM_UPDATE_BRUSHES: u32 = WM_USER + 1;
pub struct RenderFactory(ID2D1Factory); pub struct RenderFactory(ID2D1Factory);
unsafe impl Sync for RenderFactory {} unsafe impl Sync for RenderFactory {}
unsafe impl Send for RenderFactory {} unsafe impl Send for RenderFactory {}
@@ -324,29 +318,18 @@ impl Border {
} }
pub fn destroy(&self) -> color_eyre::Result<()> { pub fn destroy(&self) -> color_eyre::Result<()> {
// signal that we're destroying - prevents new render operations from starting // signal that we're destroying - prevents new render operations
self.is_destroying.store(true, Ordering::Release); self.is_destroying.store(true, Ordering::Release);
// small delay to allow in-flight render operations to complete // small delay to allow in-flight render operations to complete
std::thread::sleep(std::time::Duration::from_millis(10)); std::thread::sleep(std::time::Duration::from_millis(10));
// WM_DESTROY will clear GWLP_USERDATA and drop the render target before D2D // clear user data **BEFORE** closing window
// frees its internal HwndPresenter during WM_NCDESTROY // pending messages will see a null pointer and exit early
WindowsApi::close_window(self.hwnd) unsafe {
SetWindowLongPtrW(self.hwnd(), GWLP_USERDATA, 0);
} }
WindowsApi::close_window(self.hwnd)
/// Post a message to the border's own message loop thread requesting a brush update.
/// This ensures update_brushes() always runs on the window thread that owns the D2D
/// render target, preventing a data race with concurrent WndProc render operations.
pub fn request_brush_update(&self) {
let _ = unsafe {
PostMessageW(
Option::from(self.hwnd()),
WM_UPDATE_BRUSHES,
WPARAM(0),
LPARAM(0),
)
};
} }
pub fn set_position(&self, rect: &Rect, reference_hwnd: isize) -> color_eyre::Result<()> { pub fn set_position(&self, rect: &Rect, reference_hwnd: isize) -> color_eyre::Result<()> {
@@ -593,20 +576,6 @@ impl Border {
let _ = ValidateRect(Option::from(window), None); let _ = ValidateRect(Option::from(window), None);
LRESULT(0) LRESULT(0)
} }
WM_UPDATE_BRUSHES => {
let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;
if border_pointer.is_null() {
return LRESULT(0);
}
if (*border_pointer).is_destroying.load(Ordering::Acquire) {
return LRESULT(0);
}
if let Err(error) = (*border_pointer).update_brushes() {
tracing::error!("failed to update brushes: {error}");
}
(*border_pointer).invalidate();
LRESULT(0)
}
WM_DESTROY => { WM_DESTROY => {
let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _; let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;
if !border_pointer.is_null() { if !border_pointer.is_null() {
+6 -15
View File
@@ -451,11 +451,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} else if matches!(notification, Notification::ForceUpdate) { } else if matches!(notification, Notification::ForceUpdate) {
// Update the border brushes if there was a forced update // Update the border brushes if there was a forced update
// notification and this is not a new border (new border's // notification and this is not a new border (new border's
// already have their brushes updated on creation). // already have their brushes updated on creation)
// Post to the border's own thread to avoid a data race between border.update_brushes()?;
// this thread dropping the old render target and the window
// thread mid-render holding a reference to it.
border.request_brush_update();
} }
border.invalidate(); border.invalidate();
@@ -619,11 +616,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if forced_update && !new_border { if forced_update && !new_border {
// Update the border brushes if there was a forced update // Update the border brushes if there was a forced update
// notification and this is not a new border (new border's // notification and this is not a new border (new border's
// already have their brushes updated on creation). // already have their brushes updated on creation)
// Post to the border's own thread to avoid a data race between border.update_brushes()?;
// this thread dropping the old render target and the window
// thread mid-render holding a reference to it.
border.request_brush_update();
} }
border.set_position(&rect, focused_window_hwnd)?; border.set_position(&rect, focused_window_hwnd)?;
border.invalidate(); border.invalidate();
@@ -705,11 +699,8 @@ fn handle_floating_borders(
if forced_update && !new_border { if forced_update && !new_border {
// Update the border brushes if there was a forced update // Update the border brushes if there was a forced update
// notification and this is not a new border (new border's // notification and this is not a new border (new border's
// already have their brushes updated on creation). // already have their brushes updated on creation)
// Post to the border's own thread to avoid a data race between border.update_brushes()?;
// this thread dropping the old render target and the window
// thread mid-render holding a reference to it.
border.request_brush_update();
} }
border.set_position(&rect, window.hwnd)?; border.set_position(&rect, window.hwnd)?;
border.invalidate(); border.invalidate();
+1 -1
View File
@@ -74,7 +74,7 @@ pub enum AnimationStyle {
EaseInOutBounce, EaseInOutBounce,
#[cfg_attr(feature = "schemars", schemars(title = "CubicBezier"))] #[cfg_attr(feature = "schemars", schemars(title = "CubicBezier"))]
#[value(skip)] #[value(skip)]
/// Custom Cubic Bezier function /// Custom Cubic Bézier function
CubicBezier(f64, f64, f64, f64), CubicBezier(f64, f64, f64, f64),
} }
-6
View File
@@ -15,7 +15,6 @@ use strum::EnumString;
use crate::KomorebiTheme; use crate::KomorebiTheme;
use crate::animation::prefix::AnimationPrefix; use crate::animation::prefix::AnimationPrefix;
use crate::state::State;
// Re-export everything from komorebi-layouts // Re-export everything from komorebi-layouts
pub use komorebi_layouts::Arrangement; pub use komorebi_layouts::Arrangement;
@@ -32,7 +31,6 @@ pub use komorebi_layouts::DefaultLayout;
pub use komorebi_layouts::Direction; pub use komorebi_layouts::Direction;
pub use komorebi_layouts::GridLayoutOptions; pub use komorebi_layouts::GridLayoutOptions;
pub use komorebi_layouts::Layout; pub use komorebi_layouts::Layout;
pub use komorebi_layouts::LayoutDefaultEntry;
pub use komorebi_layouts::LayoutOptions; pub use komorebi_layouts::LayoutOptions;
pub use komorebi_layouts::MAX_RATIO; pub use komorebi_layouts::MAX_RATIO;
pub use komorebi_layouts::MAX_RATIOS; pub use komorebi_layouts::MAX_RATIOS;
@@ -41,7 +39,6 @@ pub use komorebi_layouts::OperationDirection;
pub use komorebi_layouts::Rect; pub use komorebi_layouts::Rect;
pub use komorebi_layouts::ScrollingLayoutOptions; pub use komorebi_layouts::ScrollingLayoutOptions;
pub use komorebi_layouts::Sizing; pub use komorebi_layouts::Sizing;
pub use komorebi_layouts::validate_ratios;
// Local modules and exports // Local modules and exports
pub use animation::AnimationStyle; pub use animation::AnimationStyle;
@@ -121,7 +118,6 @@ pub enum SocketMessage {
AdjustWorkspacePadding(Sizing, i32), AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout), ChangeLayout(DefaultLayout),
CycleLayout(CycleDirection), CycleLayout(CycleDirection),
LayoutRatios(Option<Vec<f32>>, Option<Vec<f32>>),
ScrollingLayoutColumns(NonZeroUsize), ScrollingLayoutColumns(NonZeroUsize),
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf), ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
FlipLayout(Axis), FlipLayout(Axis),
@@ -258,8 +254,6 @@ pub enum SocketMessage {
StaticConfigSchema, StaticConfigSchema,
GenerateStaticConfig, GenerateStaticConfig,
DebugWindow(isize), DebugWindow(isize),
// low level commands
ApplyState(State),
} }
impl SocketMessage { impl SocketMessage {
+1 -4
View File
@@ -238,9 +238,6 @@ lazy_static! {
static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen))); static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen)));
static ref CURRENT_VIRTUAL_DESKTOP: Arc<Mutex<Option<Vec<u8>>>> = Arc::new(Mutex::new(None)); static ref CURRENT_VIRTUAL_DESKTOP: Arc<Mutex<Option<Vec<u8>>>> = Arc::new(Mutex::new(None));
pub static ref LAYOUT_DEFAULTS: Arc<Mutex<HashMap<DefaultLayout, LayoutDefaultEntry>>> =
Arc::new(Mutex::new(HashMap::new()));
} }
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10); pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
@@ -325,7 +322,7 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
// the latter case, if the user desires this validation after initiating the task view, komorebi // the latter case, if the user desires this validation after initiating the task view, komorebi
// should be restarted, and then when this // fn runs again for the first time, it will pick up // should be restarted, and then when this // fn runs again for the first time, it will pick up
// the value of CurrentVirtualDesktop and validate against it accordingly // the value of CurrentVirtualDesktop and validate against it accordingly
current.map(|current| current.to_vec()) current
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
+10 -6
View File
@@ -307,10 +307,12 @@ impl Monitor {
DefaultLayout::RightMainVerticalStack => { DefaultLayout::RightMainVerticalStack => {
workspace.add_container_to_front(container); workspace.add_container_to_front(container);
} }
DefaultLayout::UltrawideVerticalStack DefaultLayout::UltrawideVerticalStack => {
if workspace.containers().len() == 1 => if workspace.containers().len() == 1 {
{
workspace.insert_container_at_idx(0, container); workspace.insert_container_at_idx(0, container);
} else {
workspace.add_container_to_back(container);
}
} }
_ => { _ => {
workspace.add_container_to_back(container); workspace.add_container_to_back(container);
@@ -330,10 +332,12 @@ impl Monitor {
match layout { match layout {
DefaultLayout::RightMainVerticalStack DefaultLayout::RightMainVerticalStack
| DefaultLayout::UltrawideVerticalStack | DefaultLayout::UltrawideVerticalStack => {
if workspace.containers().len() == 1 => if workspace.containers().len() == 1 {
{
workspace.add_container_to_back(container); workspace.add_container_to_back(container);
} else {
workspace.insert_container_at_idx(target_index, container);
}
} }
_ => { _ => {
workspace.insert_container_at_idx(target_index, container); workspace.insert_container_at_idx(target_index, container);
-26
View File
@@ -957,29 +957,6 @@ impl WindowManager {
} }
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?, SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?, SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
SocketMessage::LayoutRatios(ref columns, ref rows) => {
use crate::core::validate_ratios;
let focused_workspace = self.focused_workspace_mut()?;
let mut options = focused_workspace.layout_options.unwrap_or(LayoutOptions {
scrolling: None,
grid: None,
column_ratios: None,
row_ratios: None,
});
if let Some(cols) = columns {
options.column_ratios = Some(validate_ratios(cols));
}
if let Some(rws) = rows {
options.row_ratios = Some(validate_ratios(rws));
}
focused_workspace.layout_options = Some(options);
self.update_focused_workspace(false, false)?;
}
SocketMessage::ChangeLayoutCustom(ref path) => { SocketMessage::ChangeLayoutCustom(ref path) => {
self.change_workspace_custom_layout(path)?; self.change_workspace_custom_layout(path)?;
} }
@@ -2296,9 +2273,6 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
SocketMessage::Theme(ref theme) => { SocketMessage::Theme(ref theme) => {
theme_manager::send_notification(*theme.clone()); theme_manager::send_notification(*theme.clone());
} }
SocketMessage::ApplyState(ref state) => {
self.apply_state(state.clone());
}
// Deprecated commands // Deprecated commands
SocketMessage::AltFocusHack(_) SocketMessage::AltFocusHack(_)
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {} | SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
+3 -1
View File
@@ -28,12 +28,14 @@ pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
Action::Press => ignore_movement = true, Action::Press => ignore_movement = true,
Action::Release => ignore_movement = false, Action::Release => ignore_movement = false,
}, },
Event::MouseMoveRelative { .. } if !ignore_movement => { Event::MouseMoveRelative { .. } => {
if !ignore_movement {
match wm.lock().raise_window_at_cursor_pos() { match wm.lock().raise_window_at_cursor_pos() {
Ok(()) => {} Ok(()) => {}
Err(error) => tracing::error!("{}", error), Err(error) => tracing::error!("{}", error),
} }
} }
}
_ => {} _ => {}
} }
} }
-3
View File
@@ -253,9 +253,6 @@ impl From<&WindowManager> for State {
layout: workspace.layout.clone(), layout: workspace.layout.clone(),
layout_options: workspace.layout_options, layout_options: workspace.layout_options,
layout_rules: workspace.layout_rules.clone(), layout_rules: workspace.layout_rules.clone(),
layout_options_rules: workspace.layout_options_rules.clone(),
layout_defaults_cache: workspace.layout_defaults_cache.clone(),
work_area_offset_rules: workspace.work_area_offset_rules.clone(),
layout_flip: workspace.layout_flip, layout_flip: workspace.layout_flip,
workspace_padding: workspace.workspace_padding, workspace_padding: workspace.workspace_padding,
container_padding: workspace.container_padding, container_padding: workspace.container_padding,
+6 -55
View File
@@ -13,7 +13,6 @@ use crate::FloatingLayerBehaviour;
use crate::HIDING_BEHAVIOUR; use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS; use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST; use crate::LAYERED_WHITELIST;
use crate::LAYOUT_DEFAULTS;
use crate::MANAGE_IDENTIFIERS; use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES; use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR; use crate::NO_TITLEBAR;
@@ -54,7 +53,6 @@ use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation; use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour; use crate::core::HidingBehaviour;
use crate::core::Layout; use crate::core::Layout;
use crate::core::LayoutDefaultEntry;
use crate::core::LayoutOptions; use crate::core::LayoutOptions;
use crate::core::MoveBehaviour; use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour; use crate::core::OperationBehaviour;
@@ -217,12 +215,6 @@ pub struct WorkspaceConfig {
/// Layout-specific options /// Layout-specific options
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub layout_options: Option<LayoutOptions>, pub layout_options: Option<LayoutOptions>,
/// Threshold-based layout options rules in the format of threshold => options.
/// When container count >= threshold, the highest matching threshold's options
/// fully replace the base `layout_options`.
/// This follows the same threshold logic as `layout_rules`.
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_options_rules: Option<HashMap<usize, LayoutOptions>>,
/// END OF LIFE FEATURE: Custom Layout /// END OF LIFE FEATURE: Custom Layout
#[deprecated(note = "End of life feature")] #[deprecated(note = "End of life feature")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -231,9 +223,6 @@ pub struct WorkspaceConfig {
/// Layout rules in the format of threshold => layout /// Layout rules in the format of threshold => layout
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub layout_rules: Option<HashMap<usize, DefaultLayout>>, pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
/// Work area offset rules in the format of threshold => Rect (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub work_area_offset_rules: Option<HashMap<usize, Rect>>,
/// END OF LIFE FEATURE: Custom layout rules /// END OF LIFE FEATURE: Custom layout rules
#[deprecated(note = "End of life feature")] #[deprecated(note = "End of life feature")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -298,13 +287,6 @@ impl From<&Workspace> for WorkspaceConfig {
} }
let layout_rules = (!layout_rules.is_empty()).then_some(layout_rules); let layout_rules = (!layout_rules.is_empty()).then_some(layout_rules);
let mut work_area_offset_rules = HashMap::new();
for (threshold, offset) in &value.work_area_offset_rules {
work_area_offset_rules.insert(*threshold, *offset);
}
let work_area_offset_rules =
(!work_area_offset_rules.is_empty()).then_some(work_area_offset_rules);
let mut window_container_behaviour_rules = HashMap::new(); let mut window_container_behaviour_rules = HashMap::new();
for (threshold, behaviour) in value.window_container_behaviour_rules.iter().flatten() { for (threshold, behaviour) in value.window_container_behaviour_rules.iter().flatten() {
window_container_behaviour_rules.insert(*threshold, *behaviour); window_container_behaviour_rules.insert(*threshold, *behaviour);
@@ -350,11 +332,6 @@ impl From<&Workspace> for WorkspaceConfig {
); );
value.layout_options value.layout_options
}, },
layout_options_rules: if value.layout_options_rules.is_empty() {
None
} else {
Some(value.layout_options_rules.iter().copied().collect())
},
#[allow(deprecated)] #[allow(deprecated)]
custom_layout: value custom_layout: value
.workspace_config .workspace_config
@@ -376,7 +353,6 @@ impl From<&Workspace> for WorkspaceConfig {
.workspace_config .workspace_config
.as_ref() .as_ref()
.and_then(|c| c.workspace_rules.clone()), .and_then(|c| c.workspace_rules.clone()),
work_area_offset_rules,
work_area_offset: value.work_area_offset, work_area_offset: value.work_area_offset,
apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset), apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset),
window_container_behaviour: value.window_container_behaviour, window_container_behaviour: value.window_container_behaviour,
@@ -475,7 +451,7 @@ pub enum AppSpecificConfigurationPath {
#[serde_with::serde_as] #[serde_with::serde_as]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.41` /// The `komorebi.json` static configuration file reference for `v0.1.40`
pub struct StaticConfig { pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required /// DEPRECATED from v0.1.22: no longer required
#[deprecated(note = "No longer required")] #[deprecated(note = "No longer required")]
@@ -595,11 +571,6 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DEFAULT_CONTAINER_PADDING)))] #[cfg_attr(feature = "schemars", schemars(extend("default" = DEFAULT_CONTAINER_PADDING)))]
pub default_container_padding: Option<i32>, pub default_container_padding: Option<i32>,
/// Per-layout default options and rules, keyed by layout name.
/// Applied as fallback when a workspace does not define its own layout_options or layout_options_rules.
/// If a workspace defines either setting, all global defaults for that layout are completely replaced.
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_defaults: Option<HashMap<DefaultLayout, LayoutDefaultEntry>>,
/// Monitor and workspace configurations /// Monitor and workspace configurations
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub monitors: Option<Vec<MonitorConfig>>, pub monitors: Option<Vec<MonitorConfig>>,
@@ -909,14 +880,6 @@ impl From<&WindowManager> for StaticConfig {
default_container_padding: Option::from( default_container_padding: Option::from(
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst), DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
), ),
layout_defaults: {
let guard = LAYOUT_DEFAULTS.lock();
if guard.is_empty() {
None
} else {
Some(guard.clone())
}
},
monitors: Option::from(monitors), monitors: Option::from(monitors),
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()), window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
global_work_area_offset: value.work_area_offset, global_work_area_offset: value.work_area_offset,
@@ -1032,12 +995,6 @@ impl StaticConfig {
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst); DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
} }
if let Some(defaults) = &self.layout_defaults {
*LAYOUT_DEFAULTS.lock() = defaults.clone();
} else {
LAYOUT_DEFAULTS.lock().clear();
}
if let Some(border_width) = self.border_width { if let Some(border_width) = self.border_width {
border_manager::BORDER_WIDTH.store(border_width, Ordering::SeqCst); border_manager::BORDER_WIDTH.store(border_width, Ordering::SeqCst);
} }
@@ -1446,7 +1403,7 @@ impl StaticConfig {
workspace_config.layout = Some(DefaultLayout::Columns); workspace_config.layout = Some(DefaultLayout::Columns);
} }
ws.load_static_config(workspace_config, value.layout_defaults.as_ref())?; ws.load_static_config(workspace_config)?;
} }
} }
@@ -1529,10 +1486,7 @@ impl StaticConfig {
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() { for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) { if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config( ws.load_static_config(workspace_config)?;
workspace_config,
value.layout_defaults.as_ref(),
)?;
} }
} }
@@ -1614,7 +1568,7 @@ impl StaticConfig {
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() { for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) { if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config(workspace_config, value.layout_defaults.as_ref())?; ws.load_static_config(workspace_config)?;
} }
} }
@@ -1697,10 +1651,7 @@ impl StaticConfig {
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() { for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) { if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config( ws.load_static_config(workspace_config)?;
workspace_config,
value.layout_defaults.as_ref(),
)?;
} }
} }
@@ -1968,7 +1919,7 @@ mod tests {
let docs = vec![ 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.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", "0.1.35", "0.1.28", "0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33", "0.1.34", "0.1.35",
"0.1.36", "0.1.37", "0.1.38", "0.1.39", "0.1.36", "0.1.37", "0.1.38",
]; ];
let mut versions = vec![]; let mut versions = vec![];
+7 -42
View File
@@ -239,15 +239,9 @@ impl WindowManager {
let mouse_follows_focus = self.mouse_follows_focus; let mouse_follows_focus = self.mouse_follows_focus;
for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() { for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() {
let mut focused_workspace = 0; let mut focused_workspace = 0;
if let Some(state_monitor) = state.monitors.elements().get(monitor_idx) { for (workspace_idx, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
monitor if let Some(state_monitor) = state.monitors.elements().get(monitor_idx)
.workspaces_mut() && let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)
.resize(state_monitor.workspaces().len(), Workspace::default());
for (workspace_idx, workspace) in
monitor.workspaces_mut().iter_mut().enumerate()
{
if let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)
{ {
// to make sure padding and layout_options changes get applied for users after a quick restart // to make sure padding and layout_options changes get applied for users after a quick restart
let container_padding = workspace.container_padding; let container_padding = workspace.container_padding;
@@ -265,7 +259,6 @@ impl WindowManager {
} }
} }
} }
}
if let Err(error) = monitor.focus_workspace(focused_workspace) { if let Err(error) = monitor.focus_workspace(focused_workspace) {
tracing::warn!( tracing::warn!(
@@ -2110,15 +2103,8 @@ impl WindowManager {
tracing::info!("focusing container"); tracing::info!("focusing container");
if workspace.monocle_container.is_some() { let new_idx =
let cycle_direction = match direction { if workspace.maximized_window.is_some() || workspace.monocle_container.is_some() {
OperationDirection::Left | OperationDirection::Down => CycleDirection::Previous,
OperationDirection::Right | OperationDirection::Up => CycleDirection::Next,
};
return self.cycle_monocle(cycle_direction);
}
let new_idx = if workspace.maximized_window.is_some() {
None None
} else { } else {
workspace.new_idx_for_direction(direction) workspace.new_idx_for_direction(direction)
@@ -3107,27 +3093,6 @@ impl WindowManager {
workspace.reintegrate_monocle_container() workspace.reintegrate_monocle_container()
} }
#[tracing::instrument(skip(self))]
pub fn cycle_monocle(&mut self, direction: CycleDirection) -> eyre::Result<()> {
tracing::info!("cycling monocle container");
if self.focused_workspace()?.containers().is_empty() {
return Ok(());
}
self.focused_workspace_mut()?
.cycle_monocle_container(direction)?;
for container in self.focused_workspace_mut()?.containers_mut() {
container.hide(None);
}
// borders were getting funny during cycles, can't be bothered to root cause it
border_manager::destroy_all_borders()?;
self.update_focused_workspace(true, true)
}
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn toggle_maximize(&mut self) -> eyre::Result<()> { pub fn toggle_maximize(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?; self.handle_unmanaged_window_behaviour()?;
@@ -3376,7 +3341,7 @@ impl WindowManager {
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules; let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
rules.retain(|pair| pair.0 != at_container_count); rules.retain(|pair| pair.0 != at_container_count);
rules.push((at_container_count, Layout::Default(layout))); rules.push((at_container_count, Layout::Default(layout)));
rules.sort_by_key(|a| a.0); rules.sort_by(|a, b| a.0.cmp(&b.0));
// If this is the focused workspace on a non-focused screen, let's update it // If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx { if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
@@ -3419,7 +3384,7 @@ impl WindowManager {
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules; let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
rules.retain(|pair| pair.0 != at_container_count); rules.retain(|pair| pair.0 != at_container_count);
rules.push((at_container_count, Layout::Custom(layout))); rules.push((at_container_count, Layout::Custom(layout)));
rules.sort_by_key(|a| a.0); rules.sort_by(|a, b| a.0.cmp(&b.0));
// If this is the focused workspace on a non-focused screen, let's update it // If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx { if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
+8 -169
View File
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::Display; use std::fmt::Display;
@@ -26,7 +25,6 @@ use crate::core::CustomLayout;
use crate::core::CycleDirection; use crate::core::CycleDirection;
use crate::core::DefaultLayout; use crate::core::DefaultLayout;
use crate::core::Layout; use crate::core::Layout;
use crate::core::LayoutDefaultEntry;
use crate::core::LayoutOptions; use crate::core::LayoutOptions;
use crate::core::OperationDirection; use crate::core::OperationDirection;
use crate::core::Rect; use crate::core::Rect;
@@ -63,15 +61,6 @@ pub struct Workspace {
pub layout: Layout, pub layout: Layout,
pub layout_options: Option<LayoutOptions>, pub layout_options: Option<LayoutOptions>,
pub layout_rules: Vec<(usize, Layout)>, pub layout_rules: Vec<(usize, Layout)>,
/// Threshold-based layout options rules (container_count >= threshold -> use these options).
/// Sorted by threshold ascending at load time.
#[serde(default)]
pub layout_options_rules: Vec<(usize, LayoutOptions)>,
/// Cached per-layout defaults from the global `layout_defaults` config setting.
/// Pre-sorted at config load time; used as fallback when workspace has no overrides.
#[serde(skip)]
pub(crate) layout_defaults_cache: HashMap<DefaultLayout, CachedLayoutDefault>,
pub work_area_offset_rules: Vec<(usize, Rect)>,
pub layout_flip: Option<Axis>, pub layout_flip: Option<Axis>,
pub workspace_padding: Option<i32>, pub workspace_padding: Option<i32>,
pub container_padding: Option<i32>, pub container_padding: Option<i32>,
@@ -129,9 +118,6 @@ impl Default for Workspace {
layout: Layout::Default(DefaultLayout::BSP), layout: Layout::Default(DefaultLayout::BSP),
layout_options: None, layout_options: None,
layout_rules: vec![], layout_rules: vec![],
layout_options_rules: vec![],
layout_defaults_cache: HashMap::new(),
work_area_offset_rules: vec![],
layout_flip: None, layout_flip: None,
workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)), workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)),
container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)), container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)),
@@ -177,49 +163,8 @@ pub struct WorkspaceGlobals {
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>, pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Cached per-layout default options (pre-sorted rules) derived from the global `layout_defaults`.
pub(crate) struct CachedLayoutDefault {
pub layout_options: Option<LayoutOptions>,
/// Threshold-based rules, sorted by threshold ascending at load time
pub layout_options_rules: Vec<(usize, LayoutOptions)>,
}
/// Convert an optional HashMap of threshold-based layout options rules into a Vec sorted by
/// threshold ascending.
fn sorted_layout_options_rules(
rules: Option<&HashMap<usize, LayoutOptions>>,
) -> Vec<(usize, LayoutOptions)> {
match rules {
Some(rules) => {
let mut sorted: Vec<(usize, LayoutOptions)> =
rules.iter().map(|(t, o)| (*t, *o)).collect();
sorted.sort_by_key(|(t, _)| *t);
sorted
}
None => vec![],
}
}
/// Find the highest matching threshold rule for the given container count.
/// Rules must be sorted by threshold ascending.
fn resolve_threshold_match(
rules: &[(usize, LayoutOptions)],
container_count: usize,
) -> Option<LayoutOptions> {
rules
.iter()
.rev()
.find(|(threshold, _)| container_count >= *threshold)
.map(|(_, opts)| *opts)
}
impl Workspace { impl Workspace {
pub fn load_static_config( pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> eyre::Result<()> {
&mut self,
config: &WorkspaceConfig,
layout_defaults: Option<&HashMap<DefaultLayout, LayoutDefaultEntry>>,
) -> eyre::Result<()> {
self.name = Option::from(config.name.clone()); self.name = Option::from(config.name.clone());
self.container_padding = config.container_padding; self.container_padding = config.container_padding;
@@ -268,15 +213,6 @@ impl Workspace {
self.layout_rules = all_layout_rules; self.layout_rules = all_layout_rules;
} }
let mut all_work_area_offset_rules = vec![];
if let Some(work_area_offset_rules) = &config.work_area_offset_rules {
for (count, rect) in work_area_offset_rules {
all_work_area_offset_rules.push((*count, *rect));
}
all_work_area_offset_rules.sort_by_key(|(i, _)| *i);
self.work_area_offset_rules = all_work_area_offset_rules;
}
self.work_area_offset = config.work_area_offset; self.work_area_offset = config.work_area_offset;
self.apply_window_based_work_area_offset = self.apply_window_based_work_area_offset =
@@ -304,78 +240,19 @@ impl Workspace {
self.layout_flip = config.layout_flip; self.layout_flip = config.layout_flip;
self.floating_layer_behaviour = config.floating_layer_behaviour; self.floating_layer_behaviour = config.floating_layer_behaviour;
self.wallpaper = config.wallpaper.clone(); self.wallpaper = config.wallpaper.clone();
// Load layout options directly (LayoutOptions is used in both config and runtime)
self.layout_options = config.layout_options; self.layout_options = config.layout_options;
// Load threshold-based layout options rules, sorted by threshold ascending
self.layout_options_rules =
sorted_layout_options_rules(config.layout_options_rules.as_ref());
tracing::debug!( tracing::debug!(
"Workspace '{}' loaded layout_options: {:?}, layout_options_rules: {} entries", "Workspace '{}' loaded layout_options: {:?}",
self.name.as_deref().unwrap_or("unnamed"), self.name.as_deref().unwrap_or("unnamed"),
self.layout_options, self.layout_options
self.layout_options_rules.len(),
); );
// Cache per-layout defaults from global layout_defaults, pre-sorting rules
self.layout_defaults_cache = if let Some(defaults) = layout_defaults {
defaults
.iter()
.map(|(layout, entry)| {
(
*layout,
CachedLayoutDefault {
layout_options: entry.layout_options,
layout_options_rules: sorted_layout_options_rules(
entry.layout_options_rules.as_ref(),
),
},
)
})
.collect()
} else {
HashMap::new()
};
self.workspace_config = Some(config.clone()); self.workspace_config = Some(config.clone());
Ok(()) Ok(())
} }
/// Compute effective layout options using the complete-replacement cascade:
///
/// If the workspace defines EITHER `layout_options` OR `layout_options_rules`,
/// it completely replaces the global `layout_defaults` for this layout.
/// Global defaults are only used when the workspace has NEITHER setting.
///
/// Within the effective source (workspace or global):
/// 1. Try threshold match from rules (highest matching threshold wins)
/// 2. If a rule matches -> use it (full replacement of base)
/// 3. Else -> use the base `layout_options`
fn effective_layout_options(&self) -> Option<LayoutOptions> {
let container_count = self.containers().len();
let has_workspace_overrides =
self.layout_options.is_some() || !self.layout_options_rules.is_empty();
let (effective_base, effective_rules): (Option<LayoutOptions>, &[(usize, LayoutOptions)]) =
if has_workspace_overrides {
(self.layout_options, &self.layout_options_rules)
} else {
match &self.layout {
Layout::Default(dl) => match self.layout_defaults_cache.get(dl) {
Some(entry) => (entry.layout_options, &entry.layout_options_rules),
None => (None, &[]),
},
Layout::Custom(_) => (None, &[]),
}
};
resolve_threshold_match(effective_rules, container_count).or(effective_base)
}
pub fn hide(&mut self, omit: Option<isize>) { pub fn hide(&mut self, omit: Option<isize>) {
for window in self.floating_windows_mut().iter_mut().rev() { for window in self.floating_windows_mut().iter_mut().rev() {
let mut should_hide = omit.is_none(); let mut should_hide = omit.is_none();
@@ -602,27 +479,9 @@ impl Workspace {
let border_width = self.globals.border_width; let border_width = self.globals.border_width;
let border_offset = self.globals.border_offset; let border_offset = self.globals.border_offset;
let work_area = self.globals.work_area; let work_area = self.globals.work_area;
let work_area_offset = self.work_area_offset.or(self.globals.work_area_offset);
let window_based_work_area_offset = self.globals.window_based_work_area_offset; let window_based_work_area_offset = self.globals.window_based_work_area_offset;
let window_based_work_area_offset_limit = self.globals.window_based_work_area_offset_limit; let window_based_work_area_offset_limit = self.globals.window_based_work_area_offset_limit;
let mut rules_work_area_offset = None;
if !self.work_area_offset_rules.is_empty() {
let count = if self.monocle_container.is_some() {
1
} else {
self.containers().len()
};
for (threshold, work_area_offset_rule) in &self.work_area_offset_rules {
if count >= *threshold {
rules_work_area_offset = Some(*work_area_offset_rule);
}
}
};
let work_area_offset = rules_work_area_offset
.or(self.work_area_offset)
.or(self.globals.work_area_offset);
let mut adjusted_work_area = work_area_offset.map_or_else( let mut adjusted_work_area = work_area_offset.map_or_else(
|| work_area, || work_area,
@@ -636,6 +495,7 @@ impl Workspace {
with_offset with_offset
}, },
); );
if (self.containers().len() <= window_based_work_area_offset_limit as usize if (self.containers().len() <= window_based_work_area_offset_limit as usize
|| self.monocle_container.is_some() && window_based_work_area_offset_limit > 0) || self.monocle_container.is_some() && window_based_work_area_offset_limit > 0)
&& self.apply_window_based_work_area_offset && self.apply_window_based_work_area_offset
@@ -696,14 +556,10 @@ impl Workspace {
} else if let Some(window) = &mut self.maximized_window { } else if let Some(window) = &mut self.maximized_window {
window.maximize(); window.maximize();
} else if !self.containers().is_empty() { } else if !self.containers().is_empty() {
let effective_layout_options = self.effective_layout_options();
tracing::debug!( tracing::debug!(
"Workspace '{}' update() - effective_layout_options: {:?} (base: {:?}, rules: {})", "Workspace '{}' update() - self.layout_options before calculate: {:?}",
self.name.as_deref().unwrap_or("unnamed"), self.name.as_deref().unwrap_or("unnamed"),
effective_layout_options, self.layout_options
self.layout_options,
self.layout_options_rules.len(),
); );
let mut layouts = self.layout.as_boxed_arrangement().calculate( let mut layouts = self.layout.as_boxed_arrangement().calculate(
&adjusted_work_area, &adjusted_work_area,
@@ -714,7 +570,7 @@ impl Workspace {
self.layout_flip, self.layout_flip,
&self.resize_dimensions, &self.resize_dimensions,
self.focused_container_idx(), self.focused_container_idx(),
effective_layout_options, self.layout_options,
&self.latest_layout, &self.latest_layout,
); );
@@ -1659,23 +1515,6 @@ impl Workspace {
Ok(()) Ok(())
} }
pub fn cycle_monocle_container(&mut self, direction: CycleDirection) -> eyre::Result<()> {
if self.containers().is_empty() {
return Ok(());
}
self.reintegrate_monocle_container()?;
let new_idx = self
.new_idx_for_cycle_direction(direction)
.ok_or_eyre("there is no container to cycle monocle to")?;
self.focus_container(new_idx);
self.new_monocle_container()?;
Ok(())
}
pub fn new_maximized_window(&mut self) -> eyre::Result<()> { pub fn new_maximized_window(&mut self) -> eyre::Result<()> {
let focused_idx = self.focused_container_idx(); let focused_idx = self.focused_container_idx();
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebic-no-console" name = "komorebic-no-console"
version = "0.1.41" version = "0.1.40"
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows" description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi" repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024" edition = "2024"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebic" name = "komorebic"
version = "0.1.41" version = "0.1.40"
description = "The command-line interface for Komorebi, a tiling window manager for Windows" description = "The command-line interface for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi" repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024" edition = "2024"
+3 -22
View File
@@ -1001,16 +1001,6 @@ struct ScrollingLayoutColumns {
count: NonZeroUsize, count: NonZeroUsize,
} }
#[derive(Parser)]
struct LayoutRatios {
/// Column width ratios (space-separated values between 0.1 and 0.9)
#[clap(short, long, num_args = 1..)]
columns: Option<Vec<f32>>,
/// Row height ratios (space-separated values between 0.1 and 0.9)
#[clap(short, long, num_args = 1..)]
rows: Option<Vec<f32>>,
}
#[derive(Parser)] #[derive(Parser)]
struct License { struct License {
/// Email address associated with an Individual Commercial Use License /// Email address associated with an Individual Commercial Use License
@@ -1277,8 +1267,6 @@ enum SubCommand {
/// Set the number of visible columns for the Scrolling layout on the focused workspace /// Set the number of visible columns for the Scrolling layout on the focused workspace
#[clap(arg_required_else_help = true)] #[clap(arg_required_else_help = true)]
ScrollingLayoutColumns(ScrollingLayoutColumns), ScrollingLayoutColumns(ScrollingLayoutColumns),
/// Set the layout column and row ratios for the focused workspace
LayoutRatios(LayoutRatios),
/// Load a custom layout from file for the focused workspace /// Load a custom layout from file for the focused workspace
#[clap(hide = true)] #[clap(hide = true)]
#[clap(arg_required_else_help = true)] #[clap(arg_required_else_help = true)]
@@ -1924,12 +1912,14 @@ fn main() -> eyre::Result<()> {
"Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n" "Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"
); );
} }
Some(AppSpecificConfigurationPath::Single(path)) if !path.exists() => { Some(AppSpecificConfigurationPath::Single(path)) => {
if !path.exists() {
println!( println!(
"Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", "Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n",
path.display() path.display()
); );
} }
}
_ => {} _ => {}
} }
} }
@@ -2944,15 +2934,6 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::ScrollingLayoutColumns(args) => { SubCommand::ScrollingLayoutColumns(args) => {
send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?; send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?;
} }
SubCommand::LayoutRatios(args) => {
if args.columns.is_none() && args.rows.is_none() {
println!(
"No ratios provided, nothing to change. Use --columns or --rows to specify ratios."
);
} else {
send_message(&SocketMessage::LayoutRatios(args.columns, args.rows))?;
}
}
SubCommand::LoadCustomLayout(args) => { SubCommand::LoadCustomLayout(args) => {
send_message(&SocketMessage::ChangeLayoutCustom(args.path))?; send_message(&SocketMessage::ChangeLayoutCustom(args.path))?;
} }
-2
View File
@@ -81,8 +81,6 @@ nav:
- common-workflows/mouse-follows-focus.md - common-workflows/mouse-follows-focus.md
- common-workflows/dynamic-layout-switching.md - common-workflows/dynamic-layout-switching.md
- common-workflows/multiple-bar-instances.md - common-workflows/multiple-bar-instances.md
- common-workflows/bar.md
- common-workflows/bar-widgets/systray.md
- common-workflows/multi-monitor-setup.md - common-workflows/multi-monitor-setup.md
- CLI reference: - CLI reference:
- cli/quickstart.md - cli/quickstart.md
+2 -1745
View File
File diff suppressed because it is too large Load Diff
+2 -68
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "StaticConfig", "title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.41`", "description": "The `komorebi.json` static configuration file reference for `v0.1.40`",
"type": "object", "type": "object",
"properties": { "properties": {
"animation": { "animation": {
@@ -304,16 +304,6 @@
"$ref": "#/$defs/MatchingRule" "$ref": "#/$defs/MatchingRule"
} }
}, },
"layout_defaults": {
"description": "Per-layout default options and rules, keyed by layout name.\nApplied as fallback when a workspace does not define its own layout_options or layout_options_rules.\nIf a workspace defines either setting, all global defaults for that layout are completely replaced.",
"type": [
"object",
"null"
],
"additionalProperties": {
"$ref": "#/$defs/LayoutDefaultEntry"
}
},
"manage_rules": { "manage_rules": {
"description": "Individual window force-manage rules", "description": "Individual window force-manage rules",
"type": [ "type": [
@@ -713,7 +703,7 @@
}, },
{ {
"title": "CubicBezier", "title": "CubicBezier",
"description": "Custom Cubic Bezier function", "description": "Custom Cubic Bézier function",
"type": "object", "type": "object",
"properties": { "properties": {
"CubicBezier": { "CubicBezier": {
@@ -3300,36 +3290,6 @@
"colours" "colours"
] ]
}, },
"LayoutDefaultEntry": {
"description": "Per-layout default options entry for the `layout_defaults` global setting.\nContains both base layout options and threshold-based layout options rules.",
"type": "object",
"properties": {
"layout_options": {
"description": "Default layout options for this layout",
"anyOf": [
{
"$ref": "#/$defs/LayoutOptions"
},
{
"type": "null"
}
]
},
"layout_options_rules": {
"description": "Threshold-based layout options rules in the format of threshold => options.\nWhen container count >= threshold, the highest matching threshold's options\nfully replace the base `layout_options`.",
"type": [
"object",
"null"
],
"additionalProperties": false,
"patternProperties": {
"^\\d+$": {
"$ref": "#/$defs/LayoutOptions"
}
}
}
}
},
"LayoutOptions": { "LayoutOptions": {
"description": "Options for specific layouts", "description": "Options for specific layouts",
"type": "object", "type": "object",
@@ -4254,19 +4214,6 @@
} }
] ]
}, },
"layout_options_rules": {
"description": "Threshold-based layout options rules in the format of threshold => options.\nWhen container count >= threshold, the highest matching threshold's options\nfully replace the base `layout_options`.\nThis follows the same threshold logic as `layout_rules`.",
"type": [
"object",
"null"
],
"additionalProperties": false,
"patternProperties": {
"^\\d+$": {
"$ref": "#/$defs/LayoutOptions"
}
}
},
"layout_rules": { "layout_rules": {
"description": "Layout rules in the format of threshold => layout", "description": "Layout rules in the format of threshold => layout",
"type": [ "type": [
@@ -4339,19 +4286,6 @@
} }
] ]
}, },
"work_area_offset_rules": {
"description": "Work area offset rules in the format of threshold => Rect (default: None)",
"type": [
"object",
"null"
],
"additionalProperties": false,
"patternProperties": {
"^\\d+$": {
"$ref": "#/$defs/Rect"
}
}
},
"workspace_padding": { "workspace_padding": {
"description": "Workspace padding (default: global)", "description": "Workspace padding (default: global)",
"type": [ "type": [