mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-11 22:12:53 +01:00
Compare commits
286 Commits
feature/ru
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10af1f3c84 | ||
|
|
dcb1cd32fa | ||
|
|
17a8ac5810 | ||
|
|
8805fafa99 | ||
|
|
1219c3d118 | ||
|
|
b792328676 | ||
|
|
a7380db47c | ||
|
|
086993a7c0 | ||
|
|
51e1337f40 | ||
|
|
6cef8d9ef6 | ||
|
|
6e36b81669 | ||
|
|
0758c7d900 | ||
|
|
f77a303b30 | ||
|
|
3a81f7babb | ||
|
|
3d8778a7d6 | ||
|
|
a42e809ade | ||
|
|
66c5766848 | ||
|
|
ac56590791 | ||
|
|
4d67f5fed3 | ||
|
|
c91224295f | ||
|
|
4b1e3bd448 | ||
|
|
1cb8ed7f10 | ||
|
|
90271f1f99 | ||
|
|
9fde9ed6f8 | ||
|
|
ce4479eb9f | ||
|
|
f3b7f5ac42 | ||
|
|
382896dfd2 | ||
|
|
ff4187aecf | ||
|
|
8f982e45d1 | ||
|
|
309bfdfee1 | ||
|
|
a0239faea0 | ||
|
|
999dd5d20d | ||
|
|
69086299e1 | ||
|
|
4114b10b05 | ||
|
|
71402fe01b | ||
|
|
5c2767d589 | ||
|
|
ac67c41377 | ||
|
|
faed6ec535 | ||
|
|
4ebba08b7d | ||
|
|
a733fecb49 | ||
|
|
31c969fc55 | ||
|
|
df2adde13d | ||
|
|
a77b3e774a | ||
|
|
c8c4c3507c | ||
|
|
df38facf9e | ||
|
|
416dd94670 | ||
|
|
d0ae92ca3a | ||
|
|
3491dc7590 | ||
|
|
adbb6c1cb0 | ||
|
|
18ee667896 | ||
|
|
1a42c64620 | ||
|
|
b613986474 | ||
|
|
e93751aa5f | ||
|
|
9872dcd0d7 | ||
|
|
f99c82f5d4 | ||
|
|
b255058467 | ||
|
|
2abe618354 | ||
|
|
e953715fef | ||
|
|
2ac1929117 | ||
|
|
e33a5f28f0 | ||
|
|
15d069f2bf | ||
|
|
3e9947c2e2 | ||
|
|
1c23439f95 | ||
|
|
6eb2905e00 | ||
|
|
52a745d0a3 | ||
|
|
80877cc449 | ||
|
|
160cb7202d | ||
|
|
8d085df1ba | ||
|
|
5d48a5c5b4 | ||
|
|
539aeec965 | ||
|
|
bcfb058bc3 | ||
|
|
e07b464b0d | ||
|
|
ffa76ea28c | ||
|
|
2c00d79968 | ||
|
|
78177af6b8 | ||
|
|
86e0d40828 | ||
|
|
48f6ac8964 | ||
|
|
1db572f789 | ||
|
|
3dad77533b | ||
|
|
f40fb9a251 | ||
|
|
b4e16e43e9 | ||
|
|
4c2e8ff6d2 | ||
|
|
c879aae1e7 | ||
|
|
7e87e83189 | ||
|
|
aae9338f66 | ||
|
|
f68a709f1d | ||
|
|
c76846ac63 | ||
|
|
b29dd8b1d1 | ||
|
|
59c3c14731 | ||
|
|
db96f2cc5a | ||
|
|
a37a6752a8 | ||
|
|
4d0df9c5b5 | ||
|
|
f8ea62f857 | ||
|
|
93bb41737b | ||
|
|
280352eeef | ||
|
|
7619b9b4ed | ||
|
|
72a4d5276e | ||
|
|
4bb3b83d57 | ||
|
|
ccd2f3a464 | ||
|
|
7e242ada66 | ||
|
|
5b2acd0f12 | ||
|
|
ec0bbaae98 | ||
|
|
3c44f3bfdb | ||
|
|
7839980ddf | ||
|
|
6416c0b6eb | ||
|
|
f6ccec9505 | ||
|
|
21cb5e1e6f | ||
|
|
009c0dcd28 | ||
|
|
98c5ab3b9b | ||
|
|
d4eeec994f | ||
|
|
e9ed1cfd3b | ||
|
|
4a2eb391f7 | ||
|
|
41e18bccc6 | ||
|
|
3d373b3630 | ||
|
|
b4e61b079c | ||
|
|
eec6312a51 | ||
|
|
00384ce333 | ||
|
|
e75578d9ce | ||
|
|
50c850cb02 | ||
|
|
5a1af5b133 | ||
|
|
74c433185a | ||
|
|
71bb346c89 | ||
|
|
3feff1dca9 | ||
|
|
3019eaf89c | ||
|
|
ce59bd9ae4 | ||
|
|
309dd159ca | ||
|
|
6f1d6dbdc7 | ||
|
|
6a10d583a6 | ||
|
|
270ea5aa46 | ||
|
|
a9d2738733 | ||
|
|
3d8f68e559 | ||
|
|
80bb7288c4 | ||
|
|
76c833f661 | ||
|
|
80bce4be7e | ||
|
|
8c10547325 | ||
|
|
8f886b3fe4 | ||
|
|
53c38e157f | ||
|
|
70f561e6ac | ||
|
|
4ea835fa59 | ||
|
|
09137af305 | ||
|
|
ee89b344df | ||
|
|
46d5ea4a1d | ||
|
|
82c2241601 | ||
|
|
c28773b96a | ||
|
|
577364a556 | ||
|
|
17cd0308cb | ||
|
|
10424b696f | ||
|
|
6e7d8fb922 | ||
|
|
917cd9b7db | ||
|
|
bdbd665b21 | ||
|
|
f3f2098451 | ||
|
|
4ca2e8388b | ||
|
|
31752e422a | ||
|
|
5e308b9131 | ||
|
|
1bf53b89af | ||
|
|
11690c6004 | ||
|
|
3457dfc04c | ||
|
|
af1c9b5aa9 | ||
|
|
22fac5a9fb | ||
|
|
7a3990f106 | ||
|
|
74e93e5524 | ||
|
|
86e78570d6 | ||
|
|
3ee3aac806 | ||
|
|
c408c1149c | ||
|
|
a8b02f40fd | ||
|
|
6608e5a5bb | ||
|
|
8ef1bcf26e | ||
|
|
d146f35c25 | ||
|
|
fc07ba3dd9 | ||
|
|
3a8a61119d | ||
|
|
d24beb60b1 | ||
|
|
7daf3242e2 | ||
|
|
b6e261aef6 | ||
|
|
d40c304324 | ||
|
|
69d252ba12 | ||
|
|
2ac477d89f | ||
|
|
6db73151f7 | ||
|
|
2d6ff0708f | ||
|
|
13a519fb29 | ||
|
|
9f8e4b9dca | ||
|
|
5a0196ac9d | ||
|
|
46d0e340f9 | ||
|
|
371ef88ecb | ||
|
|
f5b5070436 | ||
|
|
c8320552b0 | ||
|
|
2a5a960c34 | ||
|
|
10ab43a8ae | ||
|
|
0e8ed8aa40 | ||
|
|
fa2ccad5bf | ||
|
|
3c4ccd2504 | ||
|
|
7d821cd3db | ||
|
|
f4bbee0a2e | ||
|
|
2934d011fd | ||
|
|
71762a5961 | ||
|
|
76aeefa9f7 | ||
|
|
4968b0fe37 | ||
|
|
b4b400b236 | ||
|
|
2ee0bbc0c7 | ||
|
|
d38d3c956d | ||
|
|
052eb1c763 | ||
|
|
58730b81b4 | ||
|
|
274ae43e8f | ||
|
|
2a30f09bbd | ||
|
|
8fd18048a4 | ||
|
|
5809735024 | ||
|
|
96fdbbd1fb | ||
|
|
de131e9ca5 | ||
|
|
07dba03255 | ||
|
|
acd53dec1b | ||
|
|
a98968d179 | ||
|
|
8a32219867 | ||
|
|
ce4b75cc3c | ||
|
|
e4226ce623 | ||
|
|
4bfd7febb4 | ||
|
|
5cc688dc6b | ||
|
|
d897890032 | ||
|
|
e702d93a8a | ||
|
|
a8c687d3d5 | ||
|
|
30fbc1ae73 | ||
|
|
cb60e91842 | ||
|
|
64d29d606a | ||
|
|
072a62c314 | ||
|
|
a95e6e9644 | ||
|
|
6ba19d3ea2 | ||
|
|
edf1943157 | ||
|
|
d0c847e5bc | ||
|
|
992bc2abfe | ||
|
|
fa07f2d2f8 | ||
|
|
cc4e204191 | ||
|
|
24791f0ce5 | ||
|
|
6b95bf95f9 | ||
|
|
c0e1e9366d | ||
|
|
532436fe1a | ||
|
|
532949409c | ||
|
|
ec4a5e6491 | ||
|
|
f6e99eaac1 | ||
|
|
a6cf801a6b | ||
|
|
83d11c6f0f | ||
|
|
1ba1c57ba0 | ||
|
|
9f16894a09 | ||
|
|
df9ae931cc | ||
|
|
be2af9fdcb | ||
|
|
c083484ef0 | ||
|
|
1804b21c4a | ||
|
|
b6bd191cf5 | ||
|
|
5919f88b38 | ||
|
|
ff2aa5e51a | ||
|
|
42c12d5ec3 | ||
|
|
f0ce8e8572 | ||
|
|
bdea4821c3 | ||
|
|
229aeb7ddc | ||
|
|
17cbdc8663 | ||
|
|
9f3d806f79 | ||
|
|
fe9a1416e7 | ||
|
|
3618beb366 | ||
|
|
a4de2ee841 | ||
|
|
60e1834b43 | ||
|
|
54323c4c6a | ||
|
|
6516c808ee | ||
|
|
894b6f3d96 | ||
|
|
7ccdff4986 | ||
|
|
c48e1db0ff | ||
|
|
ea9752d5e1 | ||
|
|
acf780767c | ||
|
|
8e588d0284 | ||
|
|
91ff9b8852 | ||
|
|
81a7951312 | ||
|
|
555308db5f | ||
|
|
c90769f5fa | ||
|
|
76002385ab | ||
|
|
f40e80cd61 | ||
|
|
e4f9d8af86 | ||
|
|
0c64432c25 | ||
|
|
fe20caa56a | ||
|
|
02a2796e7d | ||
|
|
a0eb025cec | ||
|
|
70a61376a8 | ||
|
|
1761919707 | ||
|
|
1325da4e81 | ||
|
|
0776ca1565 | ||
|
|
724b0b7692 | ||
|
|
b53de81754 | ||
|
|
a837fea40c | ||
|
|
dd577c0eb3 | ||
|
|
7d497c3e14 | ||
|
|
e6398c29f8 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -55,6 +55,7 @@ body:
|
||||
label: Hotkey Configuration
|
||||
description: >
|
||||
Please provide your whkdrc or komorebi.ahk hotkey configuration file
|
||||
render: shell
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
@@ -62,3 +63,4 @@ body:
|
||||
label: Output of komorebic check
|
||||
description: >
|
||||
Please provide the output of `komorebic check`
|
||||
render: shell
|
||||
|
||||
2
.github/workflows/feature-check.yaml
vendored
2
.github/workflows/feature-check.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check and close feature issues
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
|
||||
31
.github/workflows/windows.yaml
vendored
31
.github/workflows/windows.yaml
vendored
@@ -13,11 +13,19 @@ on:
|
||||
- hotfix/*
|
||||
tags:
|
||||
- v*
|
||||
schedule:
|
||||
- cron: "30 0 * * 0" # Every day at 00:30 UTC
|
||||
# schedule:
|
||||
# - cron: "30 0 * * 0" # Every day at 00:30 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -35,10 +43,11 @@ jobs:
|
||||
RUSTFLAGS: -Ctarget-feature=+crt-static -Dwarnings
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: rustup toolchain install stable --profile minimal
|
||||
- run: rustup component add --toolchain stable-x86_64-pc-windows-msvc clippy
|
||||
- run: rustup toolchain install nightly --allow-downgrade -c rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -47,7 +56,7 @@ jobs:
|
||||
key: ${{ matrix.platform.target }}
|
||||
- run: cargo +nightly fmt --check
|
||||
- run: cargo clippy
|
||||
- run: cargo test --package komorebi --test compat
|
||||
- run: cargo test
|
||||
- uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: "build"
|
||||
@@ -56,7 +65,7 @@ jobs:
|
||||
- run: |
|
||||
cargo install cargo-wix
|
||||
cargo wix --no-build -p komorebi --nocapture -I .\wix\main.wxs --target ${{ matrix.platform.target }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: komorebi-${{ matrix.platform.target }}-${{ github.sha }}
|
||||
path: |
|
||||
@@ -73,12 +82,12 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- shell: bash
|
||||
run: echo "VERSION=nightly" >> $GITHUB_ENV
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
- 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
|
||||
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
||||
@@ -120,14 +129,14 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- shell: bash
|
||||
run: |
|
||||
TAG=${{ github.event.release.tag_name }}
|
||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
- 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
|
||||
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
||||
@@ -162,14 +171,14 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- shell: bash
|
||||
run: |
|
||||
TAG=${{ github.ref_name }}
|
||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
- 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
|
||||
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -6,3 +6,9 @@ dummy.go
|
||||
komorebic/applications.yaml
|
||||
komorebic/applications.json
|
||||
/.vs
|
||||
/bar-schema
|
||||
/komorebi-schema
|
||||
/.wrangler
|
||||
/.xwin-cache
|
||||
result
|
||||
/.direnv
|
||||
|
||||
40
CODE_OF_CONDUCT.md
Normal file
40
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# The Komorebi Code of Conduct
|
||||
|
||||
This document is based on the [Rust Code of
|
||||
Conduct](https://www.rust-lang.org/policies/code-of-conduct)
|
||||
|
||||
## Conduct
|
||||
|
||||
- We are committed to providing a friendly, safe and welcoming environment for
|
||||
all, regardless of level of experience, gender identity and expression, sexual
|
||||
orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, nationality, or other similar characteristic.
|
||||
|
||||
- Please avoid using overtly sexual aliases or other nicknames that might
|
||||
detract from a friendly, safe and welcoming environment for all.
|
||||
|
||||
- Please be kind and courteous. There’s no need to be mean or rude.
|
||||
|
||||
- Respect that people have differences of opinion and that every design or
|
||||
implementation choice carries a trade-off and numerous costs. There is seldom a
|
||||
right answer.
|
||||
|
||||
- Please keep unstructured critique to a minimum. If you have solid ideas you
|
||||
want to experiment with, make a fork and see how it works.
|
||||
|
||||
- We will exclude you from interaction if you insult, demean or harass anyone.
|
||||
That is not welcome behavior. We interpret the term “harassment” as including
|
||||
the definition in the [Citizen Code of
|
||||
Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md);
|
||||
if you have any lack of clarity about what might be included in that concept,
|
||||
please read their definition. In particular, we don’t tolerate behavior that
|
||||
excludes people in socially marginalized groups.
|
||||
|
||||
- Private harassment is also unacceptable. No matter who you are, if you feel
|
||||
you have been or are being harassed or made uncomfortable by a community member,
|
||||
please contact me immediately. Whether you’re a regular contributor or a
|
||||
newcomer, we care about making this community a safe place for you and we’ve got
|
||||
your back.
|
||||
|
||||
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing
|
||||
behavior is not welcome.
|
||||
4542
Cargo.lock
generated
4542
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
108
Cargo.toml
108
Cargo.toml
@@ -2,27 +2,29 @@
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"komorebi",
|
||||
"komorebi-client",
|
||||
"komorebi-gui",
|
||||
"komorebic",
|
||||
"komorebic-no-console",
|
||||
"komorebi-bar",
|
||||
"komorebi-themes"
|
||||
"komorebi",
|
||||
"komorebi-client",
|
||||
"komorebi-gui",
|
||||
"komorebic",
|
||||
"komorebic-no-console",
|
||||
"komorebi-bar",
|
||||
"komorebi-themes",
|
||||
"komorebi-shortcuts",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
clap = { version = "4", features = ["derive", "wrap_help"] }
|
||||
chrono-tz = "0.10"
|
||||
chrono = "0.4"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
color-eyre = "0.6"
|
||||
eframe = "0.31"
|
||||
egui_extras = "0.31"
|
||||
eframe = "0.33"
|
||||
egui_extras = "0.33"
|
||||
dirs = "6"
|
||||
dunce = "1"
|
||||
hotwatch = "0.5"
|
||||
schemars = "0.8"
|
||||
schemars = "1.1"
|
||||
lazy_static = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { package = "serde_json_lenient", version = "0.2" }
|
||||
@@ -31,57 +33,55 @@ strum = { version = "0.27", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
parking_lot = "0.12"
|
||||
paste = "1"
|
||||
sysinfo = "0.33"
|
||||
sysinfo = "0.37"
|
||||
uds_windows = "1"
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
|
||||
windows-numerics = { version = "0.1" }
|
||||
windows-implement = { version = "0.59" }
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "8c42d8db257d30fe95bc98c2e5cd8f75da861021" }
|
||||
windows-numerics = { version = "0.3" }
|
||||
windows-implement = { version = "0.60" }
|
||||
windows-interface = { version = "0.59" }
|
||||
windows-core = { version = "0.60" }
|
||||
windows-core = { version = "0.62" }
|
||||
shadow-rs = "1"
|
||||
which = "7"
|
||||
which = "8"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.60"
|
||||
version = "0.62"
|
||||
features = [
|
||||
"Foundation_Numerics",
|
||||
"Win32_Devices",
|
||||
"Win32_Devices_Display",
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_Shell_Common", # for IObjectArray
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Graphics_Direct2D",
|
||||
"Win32_Graphics_Direct2D_Common",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Power",
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Media",
|
||||
"Media_Control"
|
||||
"Foundation_Numerics",
|
||||
"Win32_Devices",
|
||||
"Win32_Devices_Display",
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_Shell_Common", # for IObjectArray
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Graphics_Direct2D",
|
||||
"Win32_Graphics_Direct2D_Common",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Power",
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Media",
|
||||
"Media_Control",
|
||||
]
|
||||
|
||||
[profile.dev-jeezy]
|
||||
inherits = "dev"
|
||||
debug = false
|
||||
opt-level = 1
|
||||
|
||||
[profile.dev-jeezy.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release-jeezy]
|
||||
[profile.release-opt]
|
||||
inherits = "release"
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
lto = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "komorebi-workspace"
|
||||
|
||||
24
LICENSE.md
24
LICENSE.md
@@ -1,6 +1,6 @@
|
||||
# Komorebi License
|
||||
|
||||
Version 1.0.0
|
||||
Version 2.0.0
|
||||
|
||||
## Acceptance
|
||||
|
||||
@@ -13,9 +13,20 @@ your licenses.
|
||||
The licensor grants you a copyright license for the software
|
||||
to do everything you might do with the software that would
|
||||
otherwise infringe the licensor's copyright in it for any
|
||||
permitted purpose. However, you may only make changes according
|
||||
permitted purpose. However, you may only distribute the source
|
||||
code of the software according to the [Distribution License](
|
||||
#distribution-license), you may only make changes according
|
||||
to the [Changes License](#changes-license), and you may not
|
||||
distribute the software or new works based on the software.
|
||||
otherwise distribute the software or new works based on the
|
||||
software.
|
||||
|
||||
## Distribution License
|
||||
|
||||
The licensor grants you an additional copyright license to
|
||||
distribute copies of the source code of the software. Your
|
||||
license to distribute covers distributing the source code of
|
||||
the software with changes permitted by the [Changes License](
|
||||
#changes-license).
|
||||
|
||||
## Changes License
|
||||
|
||||
@@ -45,7 +56,7 @@ law. These terms do not limit them.
|
||||
|
||||
These terms do not allow you to sublicense or transfer any of
|
||||
your licenses to anyone else, or prevent the licensor from
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
any other licenses.
|
||||
|
||||
## Patent Defense
|
||||
@@ -63,7 +74,7 @@ violated any of these terms, or done anything with the software
|
||||
not covered by your licenses, your licenses can nonetheless
|
||||
continue if you come into full compliance with these terms,
|
||||
and take practical steps to correct past violations, within
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
end immediately.
|
||||
|
||||
## No Liability
|
||||
@@ -88,11 +99,10 @@ organizations that have control over, are under the control of,
|
||||
or are under common control with that organization. **Control**
|
||||
means ownership of substantially all the assets of an entity,
|
||||
or the power to direct its management and policies by vote,
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
|
||||
**Your licenses** are all the licenses granted to you for the
|
||||
software under these terms.
|
||||
|
||||
**Use** means anything you do with the software requiring one
|
||||
of your licenses.
|
||||
|
||||
|
||||
57
README.md
57
README.md
@@ -7,9 +7,9 @@ Tiling Window Management for Windows.
|
||||
<img alt="Tech for Palestine" src="https://badge.techforpalestine.org/default">
|
||||
</a>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LGUG2Z/komorebi/.github/workflows/windows.yaml">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LGUG2Z/komorebi">
|
||||
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/LGUG2Z/komorebi/total">
|
||||
<img alt="GitHub commits since latest release (by date) for a branch" src="https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest">
|
||||
<img alt="Active Individual Commercial Use Licenses" src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Flgug2z-ecstaticmagentacheetah.web.val.run&query=%24.&label=active%20individual%20commercial%20use%20licenses&cacheSeconds=3600&link=https%3A%2F%2Flgug2z.com%2Fsoftware%2Fkomorebi">
|
||||
<a href="https://discord.gg/mGkn66PHkx">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/898554690126630914">
|
||||
</a>
|
||||
@@ -29,6 +29,46 @@ Tiling Window Management for Windows.
|
||||
|
||||

|
||||
|
||||
## Note: Students using devices enrolled in mobile device management (MDM)
|
||||
|
||||
Your usage still falls under the [Komorebi License 2.0.0](./LICENSE.md).
|
||||
|
||||
You can email me at the address I sign my commits with (add `.patch` to the end
|
||||
of any commit URL on GitHub to find it) from the address associated with your
|
||||
institution with the subject "komorebi - student with an MDM device", and I will
|
||||
be able to remove the splash intended for corporate users, whose usage falls
|
||||
under the [Individual Commercial Use
|
||||
License](https://lgug2z.com/software/komorebi).
|
||||
|
||||
This is currently a manual process - most days this shouldn't take more than
|
||||
12h, and you will receive an email reply from me when the process is complete.
|
||||
|
||||
If you haven't had a reply to your email within 24h you can reach out to me on
|
||||
Discord.
|
||||
|
||||
## Note: Unexpected mobile device management (MDM) detection prompts
|
||||
|
||||
You have most likely unintentionally enrolled your device in "Bring Your Own
|
||||
Device" (BYOD) MDM. You can confirm if this is the case by running `dsregcmd
|
||||
/status` and then take the appropriate steps to remove the MDM profile and take
|
||||
back full control of your system.
|
||||
|
||||
If you need help doing this you can ask on Discord.
|
||||
|
||||
## Note: komorebi for Mac
|
||||
|
||||
If you made your way to this repo looking for [komorebi for
|
||||
Mac](https://github.com/KomoCorp/komorebi-for-mac), the project is currently
|
||||
being developed in private with [early access available to GitHub
|
||||
Sponsors](https://github.com/sponsors/LGUG2Z).
|
||||
|
||||
If you want to see how far along development is before signing up for early
|
||||
access (spoiler: it's very far along!) there is an overview video you can watch
|
||||
[here](https://www.youtube.com/watch?v=u3eJcsa_MJk).
|
||||
|
||||
Sponsors with early access can install komorebi for Mac either by compiling
|
||||
from source, by using Homebrew, or by using the project's Nix Flake.
|
||||
|
||||
## Overview
|
||||
|
||||
_komorebi_ is a tiling window manager that works as an extension to Microsoft's
|
||||
@@ -71,7 +111,10 @@ showcases the many awesome projects that exist in the _komorebi_ ecosystem.
|
||||
|
||||
## Licensing for Personal Use
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 1.0.0
|
||||
`komorebi` is [educational source
|
||||
software](https://lgug2z.com/articles/educational-source-software/).
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 2.0.0
|
||||
license](https://github.com/LGUG2Z/komorebi-license), which is a fork of the
|
||||
[PolyForm Strict 1.0.0
|
||||
license](https://polyformproject.org/licenses/strict/1.0.0). On a high level
|
||||
@@ -82,7 +125,7 @@ hard-forks) based on the software.
|
||||
Anyone is free to make their own fork of `komorebi` with changes intended either
|
||||
for personal use or for integration back upstream via pull requests.
|
||||
|
||||
The [Komorebi 1.0.0 License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
The [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
not permit any kind of commercial use (i.e. using `komorebi` at work).
|
||||
|
||||
## Sponsorship for Personal Use
|
||||
@@ -99,7 +142,8 @@ me on GitHub.
|
||||
[GitHub Sponsors is enabled for this
|
||||
project](https://github.com/sponsors/LGUG2Z). Sponsors can claim custom roles on
|
||||
the Discord server, get shout outs at the end of _komorebi_-related videos on
|
||||
YouTube, and gain the ability to submit feature requests on the issue tracker.
|
||||
YouTube, gain the ability to submit feature requests on the issue tracker, and
|
||||
receive releases of komorebi with "easter eggs" on physical media.
|
||||
|
||||
If you would like to tip or sponsor the project but are unable to use GitHub
|
||||
Sponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z), or
|
||||
@@ -142,7 +186,8 @@ video will answer the majority of your questions.
|
||||
|
||||
[@amnweb](https://github.com/amnweb) showing _komorebi_ `v0.1.28` running on Windows 11 with window borders,
|
||||
unfocused window transparency and animations enabled, using a custom status bar integrated using
|
||||
_komorebi_'s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).
|
||||
_komorebi_'
|
||||
s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).
|
||||
|
||||
https://github.com/LGUG2Z/komorebi/assets/13164844/21be8dc4-fa76-4f70-9b37-1d316f4b40c2
|
||||
|
||||
@@ -389,7 +434,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
|
||||
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
||||
|
||||
```rust
|
||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.34"}
|
||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.39"}
|
||||
|
||||
use anyhow::Result;
|
||||
use komorebi_client::Notification;
|
||||
|
||||
432
check_schema_docs.py
Normal file
432
check_schema_docs.py
Normal file
@@ -0,0 +1,432 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check schema.json and schema.bar.json for missing docstrings and map them to Rust source files.
|
||||
|
||||
This script analyzes the generated JSON schemas and identifies:
|
||||
1. Type definitions ($defs) missing top-level descriptions
|
||||
2. Enum variants missing descriptions (in oneOf/anyOf)
|
||||
3. Enum variants missing titles (object variants in oneOf/anyOf)
|
||||
4. Struct properties missing descriptions
|
||||
5. Top-level schema properties missing descriptions
|
||||
|
||||
For each missing docstring, it attempts to find the corresponding Rust source
|
||||
file and line number where the docstring should be added.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class MissingDoc:
|
||||
type_name: str
|
||||
kind: str # "type", "variant", "property", "variant_title"
|
||||
item_name: Optional[str] # variant or property name
|
||||
rust_file: Optional[str] = None
|
||||
rust_line: Optional[int] = None
|
||||
|
||||
def __str__(self):
|
||||
location = ""
|
||||
if self.rust_file and self.rust_line:
|
||||
location = f" -> {self.rust_file}:{self.rust_line}"
|
||||
elif self.rust_file:
|
||||
location = f" -> {self.rust_file}"
|
||||
|
||||
if self.kind == "type":
|
||||
return f"[TYPE] {self.type_name}{location}"
|
||||
elif self.kind == "variant":
|
||||
return f"[VARIANT] {self.type_name}::{self.item_name}{location}"
|
||||
elif self.kind == "variant_title":
|
||||
return f"[VARIANT_TITLE] {self.type_name}::{self.item_name}{location}"
|
||||
else:
|
||||
return f"[PROPERTY] {self.type_name}.{self.item_name}{location}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SchemaConfig:
|
||||
"""Configuration for a schema to check."""
|
||||
|
||||
schema_file: str
|
||||
search_paths: list[str]
|
||||
display_name: str
|
||||
|
||||
|
||||
def find_rust_definition(
|
||||
type_name: str, item_name: Optional[str], kind: str, search_paths: list[Path]
|
||||
) -> tuple[Optional[str], Optional[int]]:
|
||||
"""Find the Rust file and line number for a type/variant/property definition."""
|
||||
|
||||
if kind == "type":
|
||||
patterns = [
|
||||
rf"pub\s+enum\s+{type_name}\b",
|
||||
rf"pub\s+struct\s+{type_name}\b",
|
||||
]
|
||||
elif kind in ("variant", "variant_title"):
|
||||
patterns = [
|
||||
rf"^\s*{re.escape(item_name)}\s*[,\(\{{]",
|
||||
rf"^\s*{re.escape(item_name)}\s*$",
|
||||
rf"^\s*#\[.*\]\s*\n\s*{re.escape(item_name)}\b",
|
||||
]
|
||||
else: # property
|
||||
patterns = [rf"pub\s+{re.escape(item_name)}\s*:"]
|
||||
|
||||
for search_path in search_paths:
|
||||
for rust_file in search_path.rglob("*.rs"):
|
||||
try:
|
||||
content = rust_file.read_text()
|
||||
lines = content.split("\n")
|
||||
|
||||
if kind == "type":
|
||||
for pattern in patterns:
|
||||
for i, line in enumerate(lines):
|
||||
if re.search(pattern, line):
|
||||
return str(rust_file), i + 1
|
||||
|
||||
elif kind in ("variant", "variant_title", "property"):
|
||||
parent_pattern = rf"pub\s+(?:enum|struct)\s+{type_name}\b"
|
||||
in_type = False
|
||||
brace_count = 0
|
||||
found_open_brace = False
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if re.search(parent_pattern, line):
|
||||
in_type = True
|
||||
brace_count = 0
|
||||
found_open_brace = False
|
||||
|
||||
if in_type:
|
||||
if "{" in line:
|
||||
found_open_brace = True
|
||||
brace_count += line.count("{") - line.count("}")
|
||||
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
return str(rust_file), i + 1
|
||||
|
||||
if found_open_brace and brace_count <= 0:
|
||||
in_type = False
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def _get_variant_identifier(variant: dict) -> str:
|
||||
"""Extract a meaningful identifier for a variant.
|
||||
|
||||
Tries to find the best identifier by checking:
|
||||
1. A top-level const value (e.g., {"const": "Linear"})
|
||||
2. A property with a const value (e.g., {"kind": {"const": "Bar"}})
|
||||
3. The first required property name
|
||||
4. The type field
|
||||
5. Falls back to "unknown"
|
||||
"""
|
||||
# Check for top-level const value (simple enum variant)
|
||||
if "const" in variant:
|
||||
return str(variant["const"])
|
||||
|
||||
properties = variant.get("properties", {})
|
||||
|
||||
# Check for a property with a const value (common pattern for tagged enums)
|
||||
for prop_name, prop_def in properties.items():
|
||||
if isinstance(prop_def, dict) and "const" in prop_def:
|
||||
return str(prop_def["const"])
|
||||
|
||||
# Fall back to first required property name
|
||||
required = variant.get("required", [])
|
||||
if required:
|
||||
return str(required[0])
|
||||
|
||||
# Fall back to type
|
||||
if "type" in variant:
|
||||
return str(variant["type"])
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def check_type_description(type_name: str, type_def: dict) -> list[MissingDoc]:
|
||||
"""Check if a type definition has proper documentation."""
|
||||
missing = []
|
||||
has_top_description = "description" in type_def
|
||||
|
||||
# Always check for top-level type description first
|
||||
# (except for types that are purely references or have special handling)
|
||||
needs_type_description = True
|
||||
|
||||
# Check oneOf variants (tagged enums with variant descriptions)
|
||||
if "oneOf" in type_def:
|
||||
# oneOf types should have a top-level description
|
||||
if not has_top_description:
|
||||
missing.append(MissingDoc(type_name, "type", None, None, None))
|
||||
|
||||
for variant in type_def["oneOf"]:
|
||||
# Case 1: Simple const variant (e.g., {"const": "Swap", "description": "..."})
|
||||
variant_name = variant.get("const") or variant.get("title")
|
||||
if variant_name and "description" not in variant:
|
||||
missing.append(
|
||||
MissingDoc(type_name, "variant", str(variant_name), None, None)
|
||||
)
|
||||
|
||||
# Case 2: String enum inside oneOf (e.g., {"type": "string", "enum": [...]})
|
||||
# These variants don't have individual descriptions in the schema
|
||||
if "enum" in variant and variant.get("type") == "string":
|
||||
for enum_variant in variant["enum"]:
|
||||
missing.append(
|
||||
MissingDoc(type_name, "variant", str(enum_variant), None, None)
|
||||
)
|
||||
|
||||
# Case 3: Object variant with properties (e.g., CubicBezier)
|
||||
if "properties" in variant and "description" not in variant:
|
||||
for prop_name in variant.get("required", []):
|
||||
missing.append(
|
||||
MissingDoc(type_name, "variant", str(prop_name), None, None)
|
||||
)
|
||||
|
||||
# Case 4: Object variant missing title (needed for schema UI display)
|
||||
# Object variants should have a title or const for proper display in editors
|
||||
if (
|
||||
"properties" in variant
|
||||
and "title" not in variant
|
||||
and "const" not in variant
|
||||
):
|
||||
# Try to find a good identifier for the variant (for display only)
|
||||
variant_id = _get_variant_identifier(variant)
|
||||
missing.append(
|
||||
MissingDoc(type_name, "variant_title", str(variant_id), None, None)
|
||||
)
|
||||
|
||||
# Check anyOf variants - check each variant individually
|
||||
elif "anyOf" in type_def:
|
||||
# anyOf types should have a top-level description
|
||||
if not has_top_description:
|
||||
missing.append(MissingDoc(type_name, "type", None, None, None))
|
||||
|
||||
# Check each variant for description (skip pure $ref and null types)
|
||||
for variant in type_def["anyOf"]:
|
||||
# Skip null type variants (used for Option<T>)
|
||||
if variant.get("type") == "null":
|
||||
continue
|
||||
# Skip pure $ref variants (the referenced type is checked separately)
|
||||
if "$ref" in variant and len(variant) == 1:
|
||||
continue
|
||||
# Skip $ref variants that have a description
|
||||
if "$ref" in variant and "description" in variant:
|
||||
continue
|
||||
# Variant with $ref but no description
|
||||
if "$ref" in variant and "description" not in variant:
|
||||
# Extract the type name from the $ref
|
||||
ref_name = variant["$ref"].split("/")[-1]
|
||||
missing.append(MissingDoc(type_name, "variant", ref_name, None, None))
|
||||
# Non-ref variant without description
|
||||
elif "description" not in variant and "$ref" not in variant:
|
||||
# Try to identify the variant by its type or const
|
||||
variant_id = variant.get("const") or variant.get("type") or "unknown"
|
||||
missing.append(
|
||||
MissingDoc(type_name, "variant", str(variant_id), None, None)
|
||||
)
|
||||
|
||||
# Check for missing title on object variants in anyOf
|
||||
if (
|
||||
"properties" in variant
|
||||
and "title" not in variant
|
||||
and "const" not in variant
|
||||
):
|
||||
variant_id = _get_variant_identifier(variant)
|
||||
missing.append(
|
||||
MissingDoc(type_name, "variant_title", str(variant_id), None, None)
|
||||
)
|
||||
|
||||
# Check simple string enums (no oneOf means no variant descriptions possible in schema)
|
||||
elif "enum" in type_def:
|
||||
if not has_top_description:
|
||||
missing.append(MissingDoc(type_name, "type", None, None, None))
|
||||
# Each enum variant needs a docstring - these can't have descriptions in simple enum format
|
||||
for variant in type_def["enum"]:
|
||||
missing.append(MissingDoc(type_name, "variant", str(variant), None, None))
|
||||
|
||||
# Check struct properties
|
||||
elif "properties" in type_def:
|
||||
# Structs should always have a top-level description
|
||||
if not has_top_description:
|
||||
missing.append(MissingDoc(type_name, "type", None, None, None))
|
||||
|
||||
for prop_name, prop_def in type_def["properties"].items():
|
||||
if "description" not in prop_def:
|
||||
missing.append(MissingDoc(type_name, "property", prop_name, None, None))
|
||||
|
||||
# Simple type without description (like PathBuf, Hex)
|
||||
elif not has_top_description:
|
||||
# Only flag if it has a concrete type (not just a $ref)
|
||||
if type_def.get("type") is not None:
|
||||
missing.append(MissingDoc(type_name, "type", None, None, None))
|
||||
|
||||
return missing
|
||||
|
||||
|
||||
def check_top_level_properties(schema: dict, root_type_name: str) -> list[MissingDoc]:
|
||||
"""Check top-level schema properties for missing descriptions."""
|
||||
missing = []
|
||||
properties = schema.get("properties", {})
|
||||
|
||||
for prop_name, prop_def in properties.items():
|
||||
if "description" not in prop_def:
|
||||
missing.append(
|
||||
MissingDoc(root_type_name, "property", prop_name, None, None)
|
||||
)
|
||||
|
||||
return missing
|
||||
|
||||
|
||||
def check_schema(
|
||||
schema_path: Path,
|
||||
search_paths: list[Path],
|
||||
project_root: Path,
|
||||
display_name: str,
|
||||
) -> tuple[list[MissingDoc], int]:
|
||||
"""Check a single schema file and return missing docs and exit code."""
|
||||
if not schema_path.exists():
|
||||
print(f"Error: {schema_path.name} not found at {schema_path}")
|
||||
return [], 1
|
||||
|
||||
with open(schema_path) as f:
|
||||
schema = json.load(f)
|
||||
|
||||
all_missing: list[MissingDoc] = []
|
||||
|
||||
# Check top-level schema properties
|
||||
root_type_name = schema.get("title", "Root")
|
||||
all_missing.extend(check_top_level_properties(schema, root_type_name))
|
||||
|
||||
# Check all type definitions
|
||||
for type_name, type_def in sorted(schema.get("$defs", {}).items()):
|
||||
# Skip PerAnimationPrefixConfig2/3 as they're generated variants
|
||||
if (
|
||||
type_name.startswith("PerAnimationPrefixConfig")
|
||||
and type_name != "PerAnimationPrefixConfig"
|
||||
):
|
||||
continue
|
||||
all_missing.extend(check_type_description(type_name, type_def))
|
||||
|
||||
# Find Rust source locations
|
||||
print(f"Scanning Rust source files for {display_name}...", file=sys.stderr)
|
||||
for doc in all_missing:
|
||||
doc.rust_file, doc.rust_line = find_rust_definition(
|
||||
doc.type_name, doc.item_name, doc.kind, search_paths
|
||||
)
|
||||
if doc.rust_file:
|
||||
try:
|
||||
doc.rust_file = str(Path(doc.rust_file).relative_to(project_root))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return all_missing, 0
|
||||
|
||||
|
||||
def print_results(all_missing: list[MissingDoc], display_name: str) -> None:
|
||||
"""Print the results for a schema check."""
|
||||
# Group by file
|
||||
by_file: dict[str, list[MissingDoc]] = {}
|
||||
external: list[MissingDoc] = []
|
||||
|
||||
for doc in all_missing:
|
||||
if doc.rust_file:
|
||||
by_file.setdefault(doc.rust_file, []).append(doc)
|
||||
else:
|
||||
external.append(doc)
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 70)
|
||||
print(f"MISSING DOCSTRINGS IN SCHEMA ({display_name})")
|
||||
print("=" * 70)
|
||||
|
||||
type_count = sum(1 for d in all_missing if d.kind == "type")
|
||||
variant_count = sum(1 for d in all_missing if d.kind == "variant")
|
||||
variant_title_count = sum(1 for d in all_missing if d.kind == "variant_title")
|
||||
prop_count = sum(1 for d in all_missing if d.kind == "property")
|
||||
|
||||
print(f"\nTotal: {len(all_missing)} missing docstrings/titles")
|
||||
print(f" - {type_count} types")
|
||||
print(f" - {variant_count} variants")
|
||||
print(f" - {variant_title_count} variant titles")
|
||||
print(f" - {prop_count} properties")
|
||||
|
||||
# Print by file
|
||||
for rust_file in sorted(by_file.keys()):
|
||||
docs = sorted(by_file[rust_file], key=lambda d: d.rust_line or 0)
|
||||
print(f"\n{rust_file}:")
|
||||
print("-" * len(rust_file))
|
||||
for doc in docs:
|
||||
print(f" {doc}")
|
||||
|
||||
# Print external items (types not found in source)
|
||||
if external:
|
||||
print(f"\nExternal/Unknown location:")
|
||||
print("-" * 25)
|
||||
for doc in external:
|
||||
print(f" {doc}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
|
||||
|
||||
def main():
|
||||
project_root = Path.cwd()
|
||||
|
||||
# Define schemas to check with their respective search paths
|
||||
schemas = [
|
||||
SchemaConfig(
|
||||
schema_file="schema.json",
|
||||
search_paths=["komorebi/src", "komorebi-themes/src"],
|
||||
display_name="komorebi",
|
||||
),
|
||||
SchemaConfig(
|
||||
schema_file="schema.bar.json",
|
||||
search_paths=["komorebi-bar/src", "komorebi-themes/src"],
|
||||
display_name="komorebi-bar",
|
||||
),
|
||||
]
|
||||
|
||||
total_missing = 0
|
||||
has_errors = False
|
||||
|
||||
for schema_config in schemas:
|
||||
schema_path = project_root / schema_config.schema_file
|
||||
search_paths = [
|
||||
project_root / p
|
||||
for p in schema_config.search_paths
|
||||
if (project_root / p).exists()
|
||||
]
|
||||
|
||||
missing, error_code = check_schema(
|
||||
schema_path,
|
||||
search_paths,
|
||||
project_root,
|
||||
schema_config.display_name,
|
||||
)
|
||||
|
||||
if error_code != 0:
|
||||
has_errors = True
|
||||
continue
|
||||
|
||||
print_results(missing, schema_config.display_name)
|
||||
total_missing += len(missing)
|
||||
|
||||
# Print combined summary
|
||||
if len(schemas) > 1:
|
||||
print("\n" + "=" * 70)
|
||||
print("COMBINED SUMMARY")
|
||||
print("=" * 70)
|
||||
print(f"Total missing docstrings across all schemas: {total_missing}")
|
||||
print("=" * 70)
|
||||
|
||||
if has_errors:
|
||||
return 1
|
||||
|
||||
return 1 if total_missing > 0 else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
123
deny.toml
Normal file
123
deny.toml
Normal file
@@ -0,0 +1,123 @@
|
||||
[graph]
|
||||
targets = [
|
||||
"x86_64-pc-windows-msvc",
|
||||
"i686-pc-windows-msvc",
|
||||
"aarch64-pc-windows-msvc",
|
||||
]
|
||||
all-features = false
|
||||
no-default-features = false
|
||||
|
||||
[output]
|
||||
feature-depth = 1
|
||||
|
||||
[advisories]
|
||||
ignore = [
|
||||
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
|
||||
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" },
|
||||
{ id = "RUSTSEC-2024-0320", reason = "not using any yaml features from this library" },
|
||||
{ id = "RUSTSEC-2025-0056", reason = "only used for colour palette generation" },
|
||||
]
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"0BSD",
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"Artistic-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MIT-0",
|
||||
"MPL-2.0",
|
||||
"OFL-1.1",
|
||||
"Ubuntu-font-1.0",
|
||||
"Unicode-3.0",
|
||||
"Zlib",
|
||||
"LicenseRef-Komorebi-2.0",
|
||||
]
|
||||
confidence-threshold = 0.8
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-client"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebic"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebic-no-console"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-themes"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-gui"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-bar"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-shortcuts"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "whkd-core"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "whkd-parser"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "base16-egui-themes"
|
||||
expression = "MIT"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "win32-display-data"
|
||||
expression = "0BSD"
|
||||
license-files = []
|
||||
|
||||
[bans]
|
||||
multiple-versions = "allow"
|
||||
wildcards = "allow"
|
||||
highlight = "all"
|
||||
workspace-default-features = "allow"
|
||||
external-default-features = "allow"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
allow-git = [
|
||||
"https://github.com/LGUG2Z/base16-egui-themes",
|
||||
"https://github.com/LGUG2Z/windows-icons",
|
||||
"https://github.com/LGUG2Z/win32-display-data",
|
||||
"https://github.com/LGUG2Z/flavours",
|
||||
"https://github.com/LGUG2Z/base16_color_scheme",
|
||||
"https://github.com/LGUG2Z/whkd",
|
||||
"https://github.com/LGUG2Z/catppuccin-egui",
|
||||
"https://github.com/amPerl/egui-phosphor",
|
||||
]
|
||||
987
dependencies.json
Normal file
987
dependencies.json
Normal file
@@ -0,0 +1,987 @@
|
||||
{
|
||||
"licenses": [
|
||||
[
|
||||
"0BSD",
|
||||
[
|
||||
"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"win32-display-data 0.1.0 git+https://github.com/LGUG2Z/win32-display-data?rev=8c42d8db257d30fe95bc98c2e5cd8f75da861021"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Apache-2.0",
|
||||
[
|
||||
"ab_glyph 0.2.32 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ab_glyph_rasterizer 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit 0.21.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_consumer 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_windows 0.29.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_winit 0.29.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler2 2.0.1 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",
|
||||
"allocator-api2 0.2.21 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.13 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-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anyhow 1.0.100 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",
|
||||
"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",
|
||||
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"autocfg 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace 0.3.76 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme",
|
||||
"base64 0.22.1 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",
|
||||
"bitflags 1.3.2 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.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",
|
||||
"bytemuck 1.24.0 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.51 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",
|
||||
"chrono 0.4.42 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.5.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_builder 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_derive 4.5.49 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_lex 0.7.6 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",
|
||||
"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",
|
||||
"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-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crypto-common 0.1.7 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",
|
||||
"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",
|
||||
"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",
|
||||
"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 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"document-features 0.2.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dpi 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dyn-clone 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ecolor 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ed25519 2.2.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"eframe 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui-phosphor 0.10.0 git+https://github.com/amPerl/egui-phosphor?rev=d13688738478ecd12b426e3e74c59d6577a85b59",
|
||||
"egui-winit 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_extras 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_glow 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"emath 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"epaint 0.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",
|
||||
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"filetime 0.2.26 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"find-msvc-tools 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"flate2 1.1.5 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",
|
||||
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.3.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.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"git2 0.20.3 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",
|
||||
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin_egl_sys 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin_wgl_sys 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"half 2.7.1 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.15.5 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",
|
||||
"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",
|
||||
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"iana-time-zone 0.1.64 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_adapter 1.2.1 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",
|
||||
"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",
|
||||
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"indexmap 2.13.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.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_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",
|
||||
"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",
|
||||
"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",
|
||||
"lazy_static 1.5.0 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",
|
||||
"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",
|
||||
"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",
|
||||
"log 0.4.29 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miette 7.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miette-derive 7.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miniz_oxide 0.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",
|
||||
"miow 0.6.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.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ntapi 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-rational 0.3.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",
|
||||
"object 0.32.2 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",
|
||||
"owned_ttf_parser 0.25.1 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.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_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",
|
||||
"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",
|
||||
"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.18.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",
|
||||
"proc-macro2 1.0.105 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",
|
||||
"psm 0.1.28 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",
|
||||
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quote 1.0.43 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.5 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.9.0 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.9.3 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",
|
||||
"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",
|
||||
"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",
|
||||
"regex 1.12.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-automata 0.4.13 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-syntax 0.8.8 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",
|
||||
"rustc-demangle 0.1.26 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.13.2 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",
|
||||
"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_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_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json 1.0.149 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_with 3.16.1 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.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",
|
||||
"shadow-rs 1.5.0 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",
|
||||
"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",
|
||||
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"siphasher 1.0.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",
|
||||
"socket2 0.6.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.22 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"supports-hyperlinks 3.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",
|
||||
"syn 1.0.109 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",
|
||||
"tempfile 3.24.0 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.17 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror-impl 2.0.17 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.44 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"time-core 0.1.6 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",
|
||||
"typenum 1.19.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tz-rs 0.7.1 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.3 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.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-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"uom 0.37.0 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",
|
||||
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"webbrowser 1.0.6 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.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.61.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.62.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.61.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.62.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.60.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.59.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-link 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-link 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-registry 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.4.2 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.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.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.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-targets 0.53.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-threading 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-threading 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.53.1 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.53.1 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",
|
||||
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy-derive 0.8.33 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.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.5.8 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
[
|
||||
"ar_archive_writer 0.2.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Artistic-2.0",
|
||||
[
|
||||
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"BSD-2-Clause",
|
||||
[
|
||||
"av1-grain 0.2.5 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",
|
||||
"zerocopy 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy-derive 0.8.33 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"BSD-3-Clause",
|
||||
[
|
||||
"alloc-no-stdlib 2.0.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"alloc-stdlib 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"avif-serialize 0.8.6 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",
|
||||
"curve25519-dalek 4.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ed25519-dalek 2.2.0 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",
|
||||
"lebe 0.5.3 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.27 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"
|
||||
]
|
||||
],
|
||||
[
|
||||
"BSL-1.0",
|
||||
[
|
||||
"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",
|
||||
"ryu 1.0.22 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"CC0-1.0",
|
||||
[
|
||||
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"imgref 1.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-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"ISC",
|
||||
[
|
||||
"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libloading 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"starship-battery 0.10.3 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"MIT",
|
||||
[
|
||||
"accesskit 0.21.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_consumer 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_windows 0.29.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"aho-corasick 1.1.4 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",
|
||||
"allocator-api2 0.2.21 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.13 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-wincon 3.0.11 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",
|
||||
"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",
|
||||
"as-slice 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"autocfg 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"av-scenechange 0.14.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace 0.3.76 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme",
|
||||
"base64 0.22.1 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",
|
||||
"bitflags 1.3.2 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.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",
|
||||
"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",
|
||||
"built 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck 1.24.0 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-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytes 1.11.0 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",
|
||||
"catppuccin-egui 5.6.0 git+https://github.com/LGUG2Z/catppuccin-egui?rev=b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a",
|
||||
"cc 1.2.51 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_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"chrono 0.4.42 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",
|
||||
"clap 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_builder 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_derive 4.5.49 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_lex 0.7.6 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-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",
|
||||
"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",
|
||||
"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-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crypto-common 0.1.7 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",
|
||||
"curve25519-dalek-derive 0.1.1 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.21.3 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",
|
||||
"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",
|
||||
"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 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"document-features 0.2.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dpi 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dyn-clone 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ecolor 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ed25519 2.2.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"eframe 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui-phosphor 0.10.0 git+https://github.com/amPerl/egui-phosphor?rev=d13688738478ecd12b426e3e74c59d6577a85b59",
|
||||
"egui-winit 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_extras 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_glow 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"emath 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"epaint 0.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-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",
|
||||
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fax 0.2.6 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",
|
||||
"filetime 0.2.26 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"find-msvc-tools 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"flate2 1.1.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"flavours 0.7.2 git+https://github.com/LGUG2Z/flavours",
|
||||
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"form_urlencoded 1.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fs-tail 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"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.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.3.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.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"git2 0.20.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",
|
||||
"glutin-winit 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"h2 0.4.13 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"half 2.7.1 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.15.5 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",
|
||||
"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",
|
||||
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http-body 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http-body-util 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"httparse 1.10.1 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-util 0.1.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"iana-time-zone 0.1.64 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_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.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",
|
||||
"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 2.13.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.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_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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"log 0.4.29 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"loop9 0.1.5 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",
|
||||
"maybe-rayon 0.1.1 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",
|
||||
"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",
|
||||
"minimal-lexical 0.2.1 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.8.9 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",
|
||||
"nanoid 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"netdev 0.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",
|
||||
"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 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",
|
||||
"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",
|
||||
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-rational 0.3.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",
|
||||
"object 0.32.2 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",
|
||||
"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",
|
||||
"owo-colors 4.2.3 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.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_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",
|
||||
"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",
|
||||
"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.8.0 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.12.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"phf_shared 0.8.0 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.18.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",
|
||||
"ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro2 1.0.105 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",
|
||||
"psm 0.1.28 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quote 1.0.43 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.5 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.9.0 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.9.3 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",
|
||||
"raw-window-handle 0.6.2 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",
|
||||
"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",
|
||||
"regex 1.12.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-automata 0.4.13 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-syntax 0.8.8 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.52 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.26 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.13.2 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.28 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"schemars 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"schemars_derive 1.2.0 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",
|
||||
"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_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_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json 1.0.149 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_with 3.16.1 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.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",
|
||||
"shadow-rs 1.5.0 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",
|
||||
"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",
|
||||
"signature 2.2.0 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",
|
||||
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"siphasher 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"slab 0.4.11 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",
|
||||
"socket2 0.6.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.22 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"strum 0.27.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"strum_macros 0.27.2 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.114 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.37.2 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.3 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.17 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror-impl 2.0.17 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.10.3 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.44 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"time-core 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tokio 1.49.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tokio-native-tls 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tokio-util 0.7.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower-http 0.6.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower-layer 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing 0.1.44 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-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-log 0.2.0 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",
|
||||
"ttf-parser 0.25.1 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.1 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.1.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.22 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unsafe-libyaml 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"uom 0.37.0 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",
|
||||
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"want 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"webbrowser 1.0.6 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.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"win-msgbox 0.2.1 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",
|
||||
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.61.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.62.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.61.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.62.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354",
|
||||
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5",
|
||||
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.60.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.59.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-link 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-link 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-registry 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.4.2 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.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.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.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-targets 0.53.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-threading 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-threading 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.53.1 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.53.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winsafe 0.0.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"wmi 0.15.2 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",
|
||||
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy-derive 0.8.33 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.12 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.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.5.8 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"MIT-0",
|
||||
[
|
||||
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"MPL-2.0",
|
||||
[
|
||||
"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ramhorns 1.0.1 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"OFL-1.1",
|
||||
[
|
||||
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Ubuntu-font-1.0",
|
||||
[
|
||||
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Unicode-3.0",
|
||||
[
|
||||
"icu_collections 2.1.1 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.1.1 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.1.2 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.1.1 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.4 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.22 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.1 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.6 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.3 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.2 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Unlicense",
|
||||
[
|
||||
"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-lite 0.1.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",
|
||||
"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"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Zlib",
|
||||
[
|
||||
"adler32 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck 1.24.0 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.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",
|
||||
"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",
|
||||
"glow 0.16.0 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",
|
||||
"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.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.5.8 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -10,9 +10,8 @@ Options:
|
||||
Desired ease function for animation
|
||||
|
||||
[default: linear]
|
||||
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart,
|
||||
ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ,
|
||||
ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
|
||||
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo,
|
||||
ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
|
||||
|
||||
-a, --animation-type <ANIMATION_TYPE>
|
||||
Animation type to apply the style to. If not specified, sets global style
|
||||
|
||||
@@ -18,7 +18,7 @@ Arguments:
|
||||
Options:
|
||||
-w, --window-kind <WINDOW_KIND>
|
||||
[default: single]
|
||||
[possible values: single, stack, monocle, unfocused, floating]
|
||||
[possible values: single, stack, monocle, unfocused, unfocused-locked, floating]
|
||||
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
12
docs/cli/cancel-preselect.md
Normal file
12
docs/cli/cancel-preselect.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# cancel-preselect
|
||||
|
||||
```
|
||||
Cancel a workspace preselect set by the preselect-direction command, if one exists
|
||||
|
||||
Usage: komorebic.exe cancel-preselect
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe change-layout <DEFAULT_LAYOUT>
|
||||
|
||||
Arguments:
|
||||
<DEFAULT_LAYOUT>
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
12
docs/cli/clear-session-float-rules.md
Normal file
12
docs/cli/clear-session-float-rules.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# clear-session-float-rules
|
||||
|
||||
```
|
||||
Clear all session float rules
|
||||
|
||||
Usage: komorebic.exe clear-session-float-rules
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
16
docs/cli/cycle-empty-workspace.md
Normal file
16
docs/cli/cycle-empty-workspace.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# cycle-empty-workspace
|
||||
|
||||
```
|
||||
Focus the next empty workspace in the given cycle direction (if one exists)
|
||||
|
||||
Usage: komorebic.exe cycle-empty-workspace <CYCLE_DIRECTION>
|
||||
|
||||
Arguments:
|
||||
<CYCLE_DIRECTION>
|
||||
[possible values: previous, next]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/data-directory.md
Normal file
12
docs/cli/data-directory.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# data-directory
|
||||
|
||||
```
|
||||
Show the path to komorebi's data directory in %LOCALAPPDATA%
|
||||
|
||||
Usage: komorebic.exe data-directory
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -12,9 +12,6 @@ Options:
|
||||
--whkd
|
||||
Enable autostart of whkd
|
||||
|
||||
--ahk
|
||||
Enable autostart of ahk
|
||||
|
||||
--bar
|
||||
Enable autostart of komorebi-bar
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ Options:
|
||||
--whkd
|
||||
Kill whkd if it is running as a background process
|
||||
|
||||
--ahk
|
||||
Kill ahk if it is running as a background process
|
||||
|
||||
--bar
|
||||
Kill komorebi-bar if it is running as a background process
|
||||
|
||||
|
||||
16
docs/cli/license.md
Normal file
16
docs/cli/license.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# license
|
||||
|
||||
```
|
||||
Specify an email associated with an Individual Commercial Use License
|
||||
|
||||
Usage: komorebic.exe license <EMAIL>
|
||||
|
||||
Arguments:
|
||||
<EMAIL>
|
||||
Email address associated with an Individual Commercial Use License
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/move-to-last-workspace.md
Normal file
12
docs/cli/move-to-last-workspace.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# move-to-last-workspace
|
||||
|
||||
```
|
||||
Move the focused window to the last focused monitor workspace
|
||||
|
||||
Usage: komorebic.exe move-to-last-workspace
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -13,7 +13,7 @@ Arguments:
|
||||
The number of window containers on-screen required to trigger this layout rule
|
||||
|
||||
<LAYOUT>
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
@@ -10,7 +10,7 @@ Arguments:
|
||||
Target workspace name
|
||||
|
||||
<VALUE>
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
16
docs/cli/preselect-direction.md
Normal file
16
docs/cli/preselect-direction.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# preselect-direction
|
||||
|
||||
```
|
||||
Preselect the specified direction for the next window to be spawned on supported layouts
|
||||
|
||||
Usage: komorebic.exe preselect-direction <OPERATION_DIRECTION>
|
||||
|
||||
Arguments:
|
||||
<OPERATION_DIRECTION>
|
||||
[possible values: left, right, up, down]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/promote-swap.md
Normal file
12
docs/cli/promote-swap.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# promote-swap
|
||||
|
||||
```
|
||||
Promote the focused window to the largest tile by swapping container indices with the largest tile
|
||||
|
||||
Usage: komorebic.exe promote-swap
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
# promote
|
||||
|
||||
```
|
||||
Promote the focused window to the top of the tree
|
||||
Promote the focused window to the largest tile via container removal and re-insertion
|
||||
|
||||
Usage: komorebic.exe promote
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe query <STATE_QUERY>
|
||||
|
||||
Arguments:
|
||||
<STATE_QUERY>
|
||||
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name]
|
||||
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name, focused-workspace-layout, focused-container-kind, version]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
16
docs/cli/scrolling-layout-columns.md
Normal file
16
docs/cli/scrolling-layout-columns.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# scrolling-layout-columns
|
||||
|
||||
```
|
||||
Set the number of visible columns for the Scrolling layout on the focused workspace
|
||||
|
||||
Usage: komorebic.exe scrolling-layout-columns <COUNT>
|
||||
|
||||
Arguments:
|
||||
<COUNT>
|
||||
Desired number of visible columns
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/send-to-last-workspace.md
Normal file
12
docs/cli/send-to-last-workspace.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# send-to-last-workspace
|
||||
|
||||
```
|
||||
Send the focused window to the last focused monitor workspace
|
||||
|
||||
Usage: komorebic.exe send-to-last-workspace
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/session-float-rule.md
Normal file
12
docs/cli/session-float-rule.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# session-float-rule
|
||||
|
||||
```
|
||||
Add a rule to float the foreground window for the rest of this session
|
||||
|
||||
Usage: komorebic.exe session-float-rule
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/session-float-rules.md
Normal file
12
docs/cli/session-float-rules.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# session-float-rules
|
||||
|
||||
```
|
||||
Show all session float rules
|
||||
|
||||
Usage: komorebic.exe session-float-rules
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -18,9 +18,6 @@ Options:
|
||||
--whkd
|
||||
Start whkd in a background process
|
||||
|
||||
--ahk
|
||||
Start autohotkey configuration file
|
||||
|
||||
--bar
|
||||
Start komorebi-bar in a background process
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ Options:
|
||||
--whkd
|
||||
Stop whkd if it is running as a background process
|
||||
|
||||
--ahk
|
||||
Stop ahk if it is running as a background process
|
||||
|
||||
--bar
|
||||
Stop komorebi-bar if it is running as a background process
|
||||
|
||||
|
||||
12
docs/cli/toggle-lock.md
Normal file
12
docs/cli/toggle-lock.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# toggle-lock
|
||||
|
||||
```
|
||||
Toggle a lock for the focused container, ensuring it will not be displaced by any new windows
|
||||
|
||||
Usage: komorebic.exe toggle-lock
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
# toggle-pause
|
||||
|
||||
```
|
||||
Toggle window tiling on the focused workspace
|
||||
Toggle the paused state for all window tiling
|
||||
|
||||
Usage: komorebic.exe toggle-pause
|
||||
|
||||
|
||||
12
docs/cli/toggle-shortcuts.md
Normal file
12
docs/cli/toggle-shortcuts.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# toggle-shortcuts
|
||||
|
||||
```
|
||||
Toggle the komorebi-shortcuts helper
|
||||
|
||||
Usage: komorebic.exe toggle-shortcuts
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -1,8 +1,7 @@
|
||||
# toggle-workspace-float-override
|
||||
|
||||
```
|
||||
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace
|
||||
previously it takes the opposite of the global value
|
||||
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes the opposite of the global value
|
||||
|
||||
Usage: komorebic.exe toggle-workspace-float-override
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# toggle-workspace-window-container-behaviour
|
||||
|
||||
```
|
||||
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the
|
||||
global value
|
||||
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the global value
|
||||
|
||||
Usage: komorebic.exe toggle-workspace-window-container-behaviour
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Usage: komorebic.exe window-hiding-behaviour <HIDING_BEHAVIOUR>
|
||||
Arguments:
|
||||
<HIDING_BEHAVIOUR>
|
||||
Possible values:
|
||||
- hide: Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
||||
- hide: END OF LIFE FEATURE: Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
||||
- minimize: Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
|
||||
- cloak: Use the undocumented SetCloak Win32 function to hide windows when switching workspaces
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Arguments:
|
||||
The number of window containers on-screen required to trigger this layout rule
|
||||
|
||||
<LAYOUT>
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
@@ -13,7 +13,7 @@ Arguments:
|
||||
Workspace index on the specified monitor (zero-indexed)
|
||||
|
||||
<VALUE>
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
31
docs/cli/workspace-work-area-offset.md
Normal file
31
docs/cli/workspace-work-area-offset.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# workspace-work-area-offset
|
||||
|
||||
```
|
||||
Set offsets for a workspace to exclude parts of the work area from tiling
|
||||
|
||||
Usage: komorebic.exe workspace-work-area-offset <MONITOR> <WORKSPACE> <LEFT> <TOP> <RIGHT> <BOTTOM>
|
||||
|
||||
Arguments:
|
||||
<MONITOR>
|
||||
Monitor index (zero-indexed)
|
||||
|
||||
<WORKSPACE>
|
||||
Workspace index (zero-indexed)
|
||||
|
||||
<LEFT>
|
||||
Size of the left work area offset (set right to left * 2 to maintain right padding)
|
||||
|
||||
<TOP>
|
||||
Size of the top work area offset (set bottom to the same value to maintain bottom padding)
|
||||
|
||||
<RIGHT>
|
||||
Size of the right work area offset
|
||||
|
||||
<BOTTOM>
|
||||
Size of the bottom work area offset
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -6,7 +6,10 @@ defined in the `komorebi.json` configuration file.
|
||||
```json
|
||||
{
|
||||
"animation": {
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"duration": 250,
|
||||
"fps": 60,
|
||||
"style": "EaseOutSine"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
419
docs/common-workflows/multi-monitor-setup.md
Normal file
419
docs/common-workflows/multi-monitor-setup.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# Multi-Monitor Setup
|
||||
|
||||
You can set up komorebi to work with multiple monitors. To do so, first you start by setting up multiple monitor
|
||||
configurations on your `komorebi.json` config file.
|
||||
|
||||
If you've used the [`komorebic quickstart`](../cli/quickstart.md) command you'll already have a `komorebi.json` config
|
||||
file with one monitor config setup. Open this file and look for the `"monitors":` line, you should find something like
|
||||
this:
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": [
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "I",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "II",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "III",
|
||||
"layout": "HorizontalStack"
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"layout": "UltrawideVerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "V",
|
||||
"layout": "Rows"
|
||||
},
|
||||
{
|
||||
"name": "VI",
|
||||
"layout": "Grid"
|
||||
},
|
||||
{
|
||||
"name": "VII",
|
||||
"layout": "RightMainVerticalStack"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For this example we will remove some workspaces to simplify the config so it is easier to look at, but feel free to
|
||||
set up as many workspaces per monitor as you'd like. Here is the same configuration with only 3 workspaces.
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": [
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "I",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "II",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "III",
|
||||
"layout": "HorizontalStack"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Let's add another monitor:
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": [
|
||||
// monitor 1, index 0
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "I",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "II",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "III",
|
||||
"layout": "HorizontalStack"
|
||||
}
|
||||
]
|
||||
},
|
||||
// monitor 2, index 1
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "1",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "2",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "3",
|
||||
"layout": "HorizontalStack"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Now have two monitor configurations. We have the first monitor configuration, which is index 0 (*usually
|
||||
on programming languages the first item of a list starts with index 0*), this configuration has 3 workspaces with names
|
||||
"I", "II" and "III". Then the 2nd monitor configuration, which is index 1, also has 3 workspaces with names "1", "2",
|
||||
and "3" (you should always give unique names to your workspaces).
|
||||
|
||||
Now if you start komorebi with two monitors connected, the main monitor will use the configuration with index 0 and the
|
||||
secondary monitor will use the configuration with index 1.
|
||||
|
||||
---
|
||||
|
||||
Let's say you have more monitors, or you want to make sure that a certain configuration is always applied to a certain
|
||||
monitor. For this you will want to use the `display_index_preferences`.
|
||||
|
||||
Open up a terminal and type the following command: [ `komorebic monitor-info`](../cli/monitor-information.md). This
|
||||
command will give you the information about your connected monitors, you want to look up the `serial_number_id`. You
|
||||
should get something like this:
|
||||
|
||||
```
|
||||
❯ komorebic monitor-info
|
||||
[
|
||||
{
|
||||
"id": 6620935,
|
||||
"name": "DISPLAY1",
|
||||
"device": "BOE0A1C",
|
||||
"device_id": "BOE0A1C-5&a2bea0b&0&UID512",
|
||||
"serial_number_id": "0",
|
||||
"size": {
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"right": 1920,
|
||||
"bottom": 1080
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 181932057,
|
||||
"name": "DISPLAY2",
|
||||
"device": "VSC8C31",
|
||||
"device_id": "VSC8C31-5&18560b1f&0&UID4356",
|
||||
"serial_number_id": "UEP174021562",
|
||||
"size": {
|
||||
"left": 0,
|
||||
"top": -1080,
|
||||
"right": 1920,
|
||||
"bottom": 1080
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
In this case the setup is a laptop with a secondary monitor connected. You'll need to figure out which monitor is which,
|
||||
usually the display name's number should be similar to the numbers you can find on
|
||||
`Windows Settings -> System -> Display`.
|
||||
|
||||
If you have trouble with this step you can always jump on Discord and ask for help (create a `Support` thread).
|
||||
|
||||
Once you know which monitor is which, you want to look up their `serial_number_id` to use that on
|
||||
`display_index_preferences`, you can also use the `device_id`, it accepts both however there have been reported cases
|
||||
where the `device_id` changes after a restart while the `serial_number_id` doesn't.
|
||||
|
||||
So with the example above, we want the laptop to always use the configuration index 0 and the other monitor to use
|
||||
configuration index 1, so we map the configuration index number to the monitor `serial_number_id`/`device_id` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "0",
|
||||
"1": "UEP174021562"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Again you could also have used the `device_id` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "BOE0A1C-5&a2bea0b&0&UID512",
|
||||
"1": "VSC8C31-5&18560b1f&0&UID4356"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should add this `display_index_preferences` option to your `komorebi.json` file. If you find that something is
|
||||
not working as expected you can try to use the command `komorebic check`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> **When using multiple monitors it is recommended to always set the `display_index_preferences`. If you don't you might
|
||||
get some undefined behaviour.**
|
||||
|
||||
---
|
||||
|
||||
If you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so
|
||||
using the `bar_configurations` array in your `komorebi.json` configuration file. You can refer to the
|
||||
[multiple-bar-instances](multiple-bar-instances.md) documentation.
|
||||
|
||||
In this case it is important to use `display_index_preferences`, because if you don't, and you have 3 or more monitors,
|
||||
disconnecting and reconnecting monitors may result in the bars for the monitors getting shifted around.
|
||||
|
||||
Consider this setup with 3 monitors (A, B and C):
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 0
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_2_BAR.json
|
||||
{
|
||||
"monitor_index": 1
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// WORK_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 2
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "MONITOR_1_ID",
|
||||
"1": "MONITOR_2_ID",
|
||||
"2": "MONITOR_3_ID"
|
||||
},
|
||||
"bar_configurations": [
|
||||
// this bar uses "monitor_index": 0,
|
||||
"path/to/bar_config_1.json",
|
||||
// this bar uses "monitor_index": 1,
|
||||
"path/to/bar_config_2.json",
|
||||
// this bar uses "monitor_index": 2,
|
||||
"path/to/bar_config_3.json"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Komorebi uses an internal map to keep track of monitor to config indices, this map is called `monitor_usr_idx_map` it is
|
||||
an internal variable to komorebi that you don't need to do anything with, but you can see it with the [
|
||||
`komorebic state`](../cli/state.md) command (in case you need to debug something).
|
||||
|
||||
At first, komorebi will load all monitors and set the internal index map (`monitor_usr_idx_map`) as:
|
||||
|
||||
```json
|
||||
{
|
||||
// This is monitor A
|
||||
"0": 0,
|
||||
// This is monitor B
|
||||
"1": 1,
|
||||
// This is monitor C
|
||||
"2": 2
|
||||
}
|
||||
```
|
||||
|
||||
Which kind of seems unnecessary, but imagine that then you disconnect monitor B (or it goes to sleep). Then komorebi
|
||||
will only have 2 monitors with index 0 and 1, so the above map will be updated to this:
|
||||
|
||||
```jsonc
|
||||
[
|
||||
"0": 0, // This is monitor A
|
||||
"2": 1, // This is now monitor C, because monitor B disconnected
|
||||
]
|
||||
```
|
||||
|
||||
So now the bar intended to be for monitor B, which was looking for index "1" on that map, doesn't see it and knows it
|
||||
should be disabled. And the bar for monitor C looks at that map and knows that it's index "2" now maps to index 1 so it
|
||||
uses that index internally to get all the correct values about the monitor.
|
||||
|
||||
If you didn't have the `display_index_preferences` set, then when you disconnected monitor B, komorebi wouldn't know
|
||||
how to map the indices and would use default behaviour which would result in a map like this:
|
||||
|
||||
```json
|
||||
{
|
||||
// This is monitor A
|
||||
"0": 0,
|
||||
// This is monitor C, because monitor B disconnected. However the bars will think it is monitor B because it has index "1"
|
||||
"1": 1
|
||||
}
|
||||
```
|
||||
|
||||
# Multiple monitors on different machines
|
||||
|
||||
You can use the same `komorebi.json` to configure two different setups and then synchronize your config across machines.
|
||||
However, if you do this it is important to be aware of a few things.
|
||||
|
||||
Firstly, using `display_index_preferences` is required in this case.
|
||||
|
||||
You will need to get the `serial_number_id` or `device_id` of all the monitors of all your setups. With that information
|
||||
you would then set your config like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "HOME_MONITOR_1_ID",
|
||||
"1": "HOME_MONITOR_2_ID",
|
||||
"2": "WORK_MONITOR_1_ID",
|
||||
"3": "WORK_MONITOR_2_ID"
|
||||
},
|
||||
"monitors": [
|
||||
// HOME_MONITOR_1
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
},
|
||||
// HOME_MONITOR_2
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
},
|
||||
// WORK_MONITOR_1
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
},
|
||||
// WORK_MONITOR_2
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> *You can't use the same config on two different monitors, you have to make a duplicated config for each monitor!*
|
||||
|
||||
Then on the bar configs you need to set the bar's monitor index like this:
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 0
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_2_BAR.json
|
||||
{
|
||||
"monitor_index": 1
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// WORK_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 2
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// WORK_MONITOR_2_BAR.json
|
||||
{
|
||||
"monitor_index": 3
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Although you will only ever have 2 monitors connected at any one time, and they'll always have index 0 and 1, the
|
||||
above config will still work on both physical configurations.
|
||||
|
||||
This is because komorebi will apply the appropriate config to the loaded monitors and will create a map of the user
|
||||
index (the index defined in the user config) to the actual monitor index, and the bar will use that map to know if it
|
||||
should be enabled, and where it should be drawn.
|
||||
|
||||
# Windows Display Settings
|
||||
|
||||
In `Settings > System > Display > Multiple Displays`:
|
||||
|
||||
- Disable "Remember windows locations on monitor connection"
|
||||
- Enable "Minimize windows when a monitor is disconnected"
|
||||
|
||||
### Things to keep in mind
|
||||
|
||||
* If you are using a laptop connected to one monitor at work and a different one at home, the work monitor and the home
|
||||
monitor are considered different monitors by komorebi
|
||||
* When you disconnect from work, komorebi will keep the work monitor cached
|
||||
* You can still use a laptop alone without any monitor and if you need a window that was on the other monitor you can
|
||||
press the taskbar icon or use `alt + tab` to bring it to focus and that window will now be part of the laptop monitor
|
||||
* If you then reconnect the work monitor, the cached version will be applied with all its windows (except any window(s)
|
||||
you might have moved to another monitor)
|
||||
* If however, instead of reconnecting the work monitor, you connect the home monitor, then the work monitor will still
|
||||
remain cached, and komorebi will load the home monitor from the cache (if it exists)
|
||||
* Sometimes when you disconnect/reconnect a monitor the event might be missed by komorebi, meaning that Windows will
|
||||
show you both monitors but komorebi won't know about the existence of one of them
|
||||
* If you notice this type of weird behaviour, always run the [
|
||||
`komorebic monitor-info`](../cli/monitor-information.md)
|
||||
command and validate if one of the monitors is missing
|
||||
* To fix this you can try disconnecting and reconnecting the monitor again, or restarting komorebi
|
||||
@@ -8,12 +8,8 @@ configuration file.
|
||||
```json
|
||||
{
|
||||
"default_workspace_padding": 0,
|
||||
"default_container_padding": 0,
|
||||
"border_width": 0,
|
||||
"border_offset": -1
|
||||
"default_container_padding": -1,
|
||||
}
|
||||
```
|
||||
|
||||
A restart of `komorebi` is required after changing these settings.
|
||||
|
||||
[](https://www.youtube.com/watch?v=6QYLao953XE)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# Setting a Given Display to a Specific Index
|
||||
|
||||
If you would like `komorebi` to remember monitor index positions, you will need to set the `display_index_preferences`
|
||||
configuration option in the static configuration file.
|
||||
|
||||
Display IDs can be found using `komorebic monitor-information`.
|
||||
|
||||
Then, in `komorebi.json`, you simply need to specify the preferred index position for each display ID:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "DEL4310-5&1a6c0954&0&UID209155",
|
||||
"1": "<another-display_id>"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -16,6 +16,19 @@ the example files have been downloaded. For most new users this will be in the
|
||||
komorebic quickstart
|
||||
```
|
||||
|
||||
## Corporate Devices Enrolled in MDM
|
||||
|
||||
If you are using `komorebi` on a corporate device enrolled in mobile device
|
||||
management, you will receive a pop-up when you run `komorebic start` reminding
|
||||
you that the [Komorebi License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
not permit any kind of commercial use.
|
||||
|
||||
You can remove this pop-up by running `komorebic license <email>` with the email
|
||||
associated with your Individual Commercial Use License. A single HTTP request
|
||||
will be sent with the given email address to verify license validity.
|
||||
|
||||
## Starting komorebi
|
||||
|
||||
With the example configurations downloaded, you can now start `komorebi`,
|
||||
`komorebi-bar` and `whkd`.
|
||||
|
||||
@@ -23,6 +36,9 @@ With the example configurations downloaded, you can now start `komorebi`,
|
||||
komorebic start --whkd --bar
|
||||
```
|
||||
|
||||
If you don't want to use the komorebi status bar, you can remove the `--bar` option
|
||||
from the above command.
|
||||
|
||||
## komorebi.json
|
||||
|
||||
The example window manager configuration sets some sane defaults and provides
|
||||
@@ -186,6 +202,9 @@ limitations on hotkey bindings that include the `win` key. However, you will sti
|
||||
to [modify the registry](https://superuser.com/questions/1059511/how-to-disable-winl-in-windows-10) to prevent
|
||||
`win + l` from locking the operating system.
|
||||
|
||||
You can toggle an overlay of the current `whkdrc` shortcuts related to `komorebi` at any time when using the example
|
||||
configuration with `alt + i`.
|
||||
|
||||
```
|
||||
{% include "./whkdrc.sample" %}
|
||||
```
|
||||
|
||||
@@ -34,7 +34,7 @@ showcases the many awesome projects that exist in the `komorebi` ecosystem.
|
||||
|
||||
## Licensing for Personal Use
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 1.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork
|
||||
`komorebi` is licensed under the [Komorebi 2.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork
|
||||
of the [PolyForm Strict 1.0.0 license](https://polyformproject.org/licenses/strict/1.0.0). On a high level this means
|
||||
that you are free to do whatever you want with `komorebi` for personal use other than redistribution, or distribution of
|
||||
new works (i.e. hard-forks) based on the software.
|
||||
@@ -42,7 +42,7 @@ new works (i.e. hard-forks) based on the software.
|
||||
Anyone is free to make their own fork of `komorebi` with changes intended either for personal use or for integration
|
||||
back upstream via pull requests.
|
||||
|
||||
The [Komorebi 1.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (
|
||||
The [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (
|
||||
i.e. using `komorebi` at work).
|
||||
|
||||
## Sponsorship for Personal Use
|
||||
|
||||
@@ -120,6 +120,7 @@ cargo +stable install --path komorebic --locked
|
||||
cargo +stable install --path komorebic-no-console --locked
|
||||
cargo +stable install --path komorebi-gui --locked
|
||||
cargo +stable install --path komorebi-bar --locked
|
||||
cargo +stable install --path komorebi-shortcuts --locked
|
||||
```
|
||||
|
||||
If the binaries have been built and added to your `$PATH` correctly, you should
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.bar.json",
|
||||
"monitor": 0,
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.bar.json",
|
||||
"font_family": "JetBrains Mono",
|
||||
"theme": {
|
||||
"palette": "Base16",
|
||||
@@ -48,8 +47,8 @@
|
||||
{
|
||||
"Network": {
|
||||
"enable": true,
|
||||
"show_total_data_transmitted": true,
|
||||
"show_network_activity": true
|
||||
"show_activity": true,
|
||||
"show_total_activity": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.json",
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.json",
|
||||
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
|
||||
"window_hiding_behaviour": "Cloak",
|
||||
"cross_monitor_move_behaviour": "Insert",
|
||||
@@ -14,13 +14,6 @@
|
||||
"unfocused_border": "Base03",
|
||||
"bar_accent": "Base0D"
|
||||
},
|
||||
"stackbar": {
|
||||
"height": 40,
|
||||
"mode": "OnStack",
|
||||
"tabs": {
|
||||
"width": 300
|
||||
}
|
||||
},
|
||||
"monitors": [
|
||||
{
|
||||
"workspaces": [
|
||||
|
||||
@@ -132,3 +132,20 @@ running `komorebic stop` and `komorebic start`.
|
||||
We can see the _komorebi_ state is no longer associated with the previous
|
||||
device: `null`, suggesting an issue when the display resumes from a suspended
|
||||
state.
|
||||
|
||||
## Komorebi Bar does not render transparency on Nvidia GPUs
|
||||
|
||||
Users with Nvidia GPUs may have issues with transparency on the Komorebi Bar.
|
||||
|
||||
To solve this the user can do the following:
|
||||
|
||||
- Open the Nvidia Control Panel
|
||||
- On the left menu tree, under "3D Settings", select "Manage 3D Settings"
|
||||
- Select the "Program Settings" tab
|
||||
- Press the "Add" button and select "komorebi-bar"
|
||||
- Under "3. Specify the settings for this program:", find the feature labelled, "OpenGL GDI compatibility"
|
||||
- Change the setting to "Prefer compatibility"
|
||||
- At the bottom of the window select "Apply"
|
||||
- Restart the Komorebi Bar with "komorebic stop --bar; komorebic start --bar"
|
||||
|
||||
This should resolve the issue and your Komorebi Bar should render with the proper transparency.
|
||||
|
||||
46
docs/usage/focusing-windows.md
Normal file
46
docs/usage/focusing-windows.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Focusing Windows
|
||||
|
||||
Windows can be focused in a direction (left, down, up, right) using the [`komorebic focus`](../cli/focus.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + h : komorebic focus left
|
||||
alt + j : komorebic focus down
|
||||
alt + k : komorebic focus up
|
||||
alt + l : komorebic focus right
|
||||
```
|
||||
|
||||
Windows can be focused in a cycle direction (previous, next) using the [`komorebic cycle-focus`](../cli/cycle-focus.md)
|
||||
command.
|
||||
|
||||
```
|
||||
# example showing you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
|
||||
```
|
||||
|
||||
It is possible to attempt to focus the first window, on any workspace, matching an exe using the [
|
||||
`komorebic eager-focus`](../cli/eager-focus.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
win + 1 : komorebic eager-focus firefox.exe
|
||||
```
|
||||
|
||||
The window at the largest tile can be focused using the [`komorebic promote-focus`](../cli/promote-focus.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + return : komorebic promote-focus
|
||||
```
|
||||
|
||||
The behaviour when attempting to call `komorebic focus` when at the left or right edge of a monitor is determined by
|
||||
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
|
||||
|
||||
When set to `Workspace`, the next workspace on the same monitor will be focused.
|
||||
|
||||
When set to `Monitor`, the focused workspace on the next monitor in the given direction will be focused.
|
||||
59
docs/usage/focusing-workspaces.md
Normal file
59
docs/usage/focusing-workspaces.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Focusing Workspaces
|
||||
|
||||
Workspaces on the focused monitor can be focused by their index using the [
|
||||
`komorebic focus-workspace`](../cli/focus-workspace.md) command.
|
||||
|
||||
If this command is called with an index for a workspace which does not exist, that workspace, and all workspace indexes
|
||||
required to get to that workspace, will be created.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + 1 : komorebic focus-workspace 0
|
||||
alt + 2 : komorebic focus-workspace 1
|
||||
alt + 3 : komorebic focus-workspace 2
|
||||
```
|
||||
|
||||
Workspaces on the focused monitor can be focused in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-workspace`](../cli/cycle-workspace.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-workspace previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-workspace next # oem_6 is ]
|
||||
```
|
||||
|
||||
Workspaces on other monitors can be focused by both the monitor index and the workspace index using the [
|
||||
`komorebic focus-monitor-workspace`](../cli/focus-monitor-workspace.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + 1 : komorebic focus-monitor-workspace 0 0
|
||||
alt + 2 : komorebic focus-monitor-workspace 0 1
|
||||
alt + 3 : komorebic focus-monitor-workspace 1 0
|
||||
```
|
||||
|
||||
Workspaces on any monitor can be focused by their name (given that all workspace names across all monitors are unique)
|
||||
using the [`komorebic focus-named-workspace`](../cli/focus-named-workspace.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + c : komorebic focus-named-workspace coding
|
||||
```
|
||||
|
||||
Workspaces on all monitors can be set to the same index (emulating single workspaces which span across all monitors)
|
||||
using the [`komorebic focus-workspaces`](../cli/focus-workspaces.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + 1 : komorebic focus-workspaces 0
|
||||
alt + 2 : komorebic focus-workspaces 1
|
||||
alt + 3 : komorebic focus-workspaces 2
|
||||
```
|
||||
|
||||
The last focused workspace on the focused monitor can be re-focused using the [
|
||||
`komorebic focus-last-workspace`](../cli/focus-last-workspace.md) command.
|
||||
59
docs/usage/moving-windows-across-workspaces.md
Normal file
59
docs/usage/moving-windows-across-workspaces.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Moving Windows Across Workspaces
|
||||
|
||||
Windows can be moved to another workspace on the focused monitor using the [
|
||||
`komorebic move-to-workspace`](../cli/move-to-workspace.md) command. This command will also move your focus to the
|
||||
target workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + 1 : komorebic move-to-workspace 0
|
||||
alt + shift + 2 : komorebic move-to-workspace 1
|
||||
alt + shift + 3 : komorebic move-to-workspace 2
|
||||
```
|
||||
|
||||
Windows can be sent to another workspace on the focused monitor using the [
|
||||
`komorebic send-to-workspace`](../cli/send-to-workspace.md) command. This command will keep your focus on the origin
|
||||
workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + 1 : komorebic send-to-workspace 0
|
||||
alt + shift + 2 : komorebic send-to-workspace 1
|
||||
alt + shift + 3 : komorebic send-to-workspace 2
|
||||
```
|
||||
|
||||
Windows can be moved to another workspace on the focused monitor in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will also move your focus
|
||||
to the target workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-move-to-workspace previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-move-to-workspace next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows can be sent to another workspace on the focused monitor in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will keep your focus on
|
||||
the origin workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-send-to-workspace previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-send-to-workspace next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows can be moved or sent to the focused workspace on a another monitor using the [
|
||||
`komorebic move-to-monitor`](../cli/move-to-monitor.md) and [`komorebic send-to-monitor`](../cli/send-to-monitor.md)
|
||||
commands.
|
||||
|
||||
Windows can be moved or sent to the focused workspace on a monitor in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-move-to-monitor`](../cli/cycle-move-to-monitor.md) and [
|
||||
`komorebic cycle-send-to-monitor`](../cli/cycle-send-to-monitor.md) commands.
|
||||
|
||||
Windows can be moved or sent to a named workspace on any monitor (given that all workspace names across all monitors are
|
||||
unique) using the [`komorebic move-to-named-workspace`](../cli/move-to-named-workspace.md) and [
|
||||
`komorebic send-to-named-workspace`](../cli/send-to-named-workspace.md) commands
|
||||
50
docs/usage/moving-windows.md
Normal file
50
docs/usage/moving-windows.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Moving Windows
|
||||
|
||||
Windows can be moved in a direction (left, down, up, right) using the [`komorebic move`](../cli/move.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + h : komorebic move left
|
||||
alt + shift + j : komorebic move down
|
||||
alt + shift + k : komorebic move up
|
||||
alt + shift + l : komorebic move right
|
||||
```
|
||||
|
||||
Windows can be moved in a cycle direction (previous, next) using the [`komorebic cycle-move`](../cli/cycle-move.md)
|
||||
command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-move previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-move next # oem_6 is ]
|
||||
```
|
||||
|
||||
The focused window can be moved to the largest tile using the [`komorebic promote`](../cli/promote.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + return : komorebic promote
|
||||
```
|
||||
|
||||
The behaviour when attempting to call `komorebic move` when at the left or right edge of a monitor is determined by
|
||||
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
|
||||
|
||||
When set to `Workspace`, the focused window will be moved to the next workspace on the focused monitor in the given
|
||||
direction
|
||||
|
||||
When set to `Monitor`, the focused window will be moved to the focused workspace on the next monitor in the given
|
||||
direction.
|
||||
|
||||
The behaviour when calling `komorebic move` with `cross_boundary_behaviour` set to `Monitor` can be further refined with
|
||||
the [`cross_monitor_move_behaviour`](https://komorebi.lgug2z.com/schema#cross_monitor_move_behaviour) configuration
|
||||
option.
|
||||
|
||||
When set to `Swap`, the focused window will be swapped with the window at the corresponding edge of the adjacent monitor
|
||||
|
||||
When set to `Insert`, the focused window will be inserted into the focused workspace on the adjacent monitor.
|
||||
|
||||
When set to `NoOp`, the focused window will not be moved across a monitor boundary, though focusing across monitor
|
||||
boundaries will continue to function.
|
||||
52
docs/usage/stacking-windows.md
Normal file
52
docs/usage/stacking-windows.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Stacking Windows
|
||||
|
||||
Windows can be stacked in a direction (left, down, up, right) using the [`komorebic stack`](../cli/stack.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + left : komorebic stack left
|
||||
alt + down : komorebic stack down
|
||||
alt + up : komorebic stack up
|
||||
alt + right : komorebic stack right
|
||||
```
|
||||
|
||||
Windows can be popped from a stack using the [`komorebic unstack`](../cli/unstack.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + oem_1 : komorebic unstack # oem_1 is ;
|
||||
```
|
||||
|
||||
Windows in a stack can be focused in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-stack`](../cli/cycle-stack.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
|
||||
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows in a stack can have their positions in the stack moved in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-stack-index`](../cli/cycle-stack-index.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-stack-index previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-stack-index next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows in a stack can be focused by their index in the stack using the [
|
||||
`komorebic focus-stack-window`](../cli/focus-stack-window.md) command.
|
||||
|
||||
All windows on the focused workspace can be combined into a single stack using the [
|
||||
`komorebic stack-all`](../cli/stack-all.md) command.
|
||||
|
||||
All windows in a focused stack can be popped using the [`komorebic unstack-all`](../cli/unstack-all.md) command.
|
||||
|
||||
It is possible to tell the window manager to stack the next opened window on top of the currently focused window by
|
||||
using the [
|
||||
`komorebic toggle-workspace-window-container-behaviour`](../cli/toggle-workspace-window-container-behaviour.md) command.
|
||||
@@ -5,6 +5,8 @@
|
||||
alt + o : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
|
||||
alt + shift + o : komorebic reload-configuration
|
||||
|
||||
alt + i : komorebic toggle-shortcuts
|
||||
|
||||
# App shortcuts - these require shell to be pwsh / powershell
|
||||
# The apps will be focused if open, or launched if not open
|
||||
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
||||
|
||||
179
flake.lock
generated
Normal file
179
flake.lock
generated
Normal file
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1766774972,
|
||||
"narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761588595,
|
||||
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1765835352,
|
||||
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks-nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1765911976,
|
||||
"narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"git-hooks-nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1766870016,
|
||||
"narHash": "sha256-fHmxAesa6XNqnIkcS6+nIHuEmgd/iZSP/VXxweiEuQw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5c2bc52fb9f8c264ed6c93bd20afa2ff5e763dce",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1765674936,
|
||||
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-parts": "flake-parts",
|
||||
"git-hooks-nix": "git-hooks-nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766890375,
|
||||
"narHash": "sha256-0Zi7ChAtjq/efwQYmp7kOJPcSt6ya9ynSUe6ppgZhsQ=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "91e1f7a0017065360f447622d11b7ce6ed04772f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766000401,
|
||||
"narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
360
flake.nix
Normal file
360
flake.nix
Normal file
@@ -0,0 +1,360 @@
|
||||
{
|
||||
description = "komorebi for Windows";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
crane.url = "github:ipetkov/crane";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
git-hooks-nix.url = "github:cachix/git-hooks.nix";
|
||||
git-hooks-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-parts,
|
||||
crane,
|
||||
rust-overlay,
|
||||
...
|
||||
}:
|
||||
let
|
||||
windowsSdkVersion = "10.0.26100";
|
||||
windowsCrtVersion = "14.44.17.14";
|
||||
|
||||
mkWindowsSdk =
|
||||
pkgs:
|
||||
pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "windows-sdk-${windowsSdkVersion}-crt-${windowsCrtVersion}";
|
||||
|
||||
nativeBuildInputs = [ pkgs.xwin ];
|
||||
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "recursive";
|
||||
outputHash = "sha256-6cLS5q1BDRpLPScfmmKpTTEHUzsgKTKD1+mKvGX9Deo=";
|
||||
|
||||
buildCommand = ''
|
||||
export HOME=$(mktemp -d)
|
||||
xwin --accept-license \
|
||||
--sdk-version ${windowsSdkVersion} \
|
||||
--crt-version ${windowsCrtVersion} \
|
||||
splat --output $out
|
||||
'';
|
||||
};
|
||||
|
||||
mkMsvcEnv =
|
||||
{ pkgs, windowsSdk }:
|
||||
let
|
||||
clangVersion = pkgs.lib.versions.major pkgs.llvmPackages.clang.version;
|
||||
in
|
||||
{
|
||||
# linker for the windows target
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER = "lld-link";
|
||||
|
||||
# c/c++ compiler
|
||||
CC_x86_64_pc_windows_msvc = "clang-cl";
|
||||
CXX_x86_64_pc_windows_msvc = "clang-cl";
|
||||
AR_x86_64_pc_windows_msvc = "llvm-lib";
|
||||
|
||||
# IMPORTANT: libclang include path MUST come first to avoid header conflicts
|
||||
CFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [
|
||||
"--target=x86_64-pc-windows-msvc"
|
||||
"-Wno-unused-command-line-argument"
|
||||
"-fuse-ld=lld-link"
|
||||
"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include"
|
||||
"/imsvc${windowsSdk}/crt/include"
|
||||
"/imsvc${windowsSdk}/sdk/include/ucrt"
|
||||
"/imsvc${windowsSdk}/sdk/include/um"
|
||||
"/imsvc${windowsSdk}/sdk/include/shared"
|
||||
];
|
||||
|
||||
CXXFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [
|
||||
"--target=x86_64-pc-windows-msvc"
|
||||
"-Wno-unused-command-line-argument"
|
||||
"-fuse-ld=lld-link"
|
||||
"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include"
|
||||
"/imsvc${windowsSdk}/crt/include"
|
||||
"/imsvc${windowsSdk}/sdk/include/ucrt"
|
||||
"/imsvc${windowsSdk}/sdk/include/um"
|
||||
"/imsvc${windowsSdk}/sdk/include/shared"
|
||||
];
|
||||
|
||||
# target-specific rust flags with linker flavor and library search paths
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS = builtins.concatStringsSep " " [
|
||||
"-Clinker-flavor=lld-link"
|
||||
"-Lnative=${windowsSdk}/crt/lib/x86_64"
|
||||
"-Lnative=${windowsSdk}/sdk/lib/um/x86_64"
|
||||
"-Lnative=${windowsSdk}/sdk/lib/ucrt/x86_64"
|
||||
];
|
||||
|
||||
# cargo target
|
||||
CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc";
|
||||
};
|
||||
|
||||
mkKomorebiPackages =
|
||||
{ pkgs, windowsSdk }:
|
||||
let
|
||||
# toolchain with windows msvc target
|
||||
toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
|
||||
targets = [ "x86_64-pc-windows-msvc" ];
|
||||
};
|
||||
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
|
||||
version = "0.1.0";
|
||||
|
||||
msvcEnv = mkMsvcEnv { inherit pkgs windowsSdk; };
|
||||
|
||||
src = pkgs.lib.cleanSourceWith {
|
||||
src = ./.;
|
||||
filter =
|
||||
path: type:
|
||||
(craneLib.filterCargoSources path type)
|
||||
|| (pkgs.lib.hasInfix "/docs/" path)
|
||||
|| (builtins.match ".*/docs/.*" path != null);
|
||||
};
|
||||
|
||||
commonArgs = {
|
||||
inherit src version;
|
||||
strictDeps = true;
|
||||
COMMIT_HASH = self.rev or (pkgs.lib.removeSuffix "-dirty" self.dirtyRev);
|
||||
|
||||
# build inputs for cross-compilation
|
||||
nativeBuildInputs = [
|
||||
pkgs.llvmPackages.clang-unwrapped
|
||||
pkgs.llvmPackages.lld
|
||||
pkgs.llvmPackages.llvm
|
||||
];
|
||||
|
||||
# cross-compilation environment
|
||||
inherit (msvcEnv)
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER
|
||||
CC_x86_64_pc_windows_msvc
|
||||
CXX_x86_64_pc_windows_msvc
|
||||
AR_x86_64_pc_windows_msvc
|
||||
CFLAGS_x86_64_pc_windows_msvc
|
||||
CXXFLAGS_x86_64_pc_windows_msvc
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS
|
||||
CARGO_BUILD_TARGET
|
||||
;
|
||||
};
|
||||
|
||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||
|
||||
individualCrateArgs = commonArgs // {
|
||||
inherit cargoArtifacts;
|
||||
doCheck = false;
|
||||
doDoc = false;
|
||||
};
|
||||
|
||||
fullBuild = craneLib.buildPackage (
|
||||
individualCrateArgs
|
||||
// {
|
||||
pname = "komorebi-workspace";
|
||||
}
|
||||
);
|
||||
|
||||
extractBinary =
|
||||
binaryName:
|
||||
pkgs.runCommand "komorebi-${binaryName}"
|
||||
{
|
||||
meta = fullBuild.meta // { };
|
||||
}
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
cp ${fullBuild}/bin/${binaryName}.exe $out/bin/
|
||||
'';
|
||||
in
|
||||
{
|
||||
inherit
|
||||
craneLib
|
||||
src
|
||||
individualCrateArgs
|
||||
fullBuild
|
||||
msvcEnv
|
||||
;
|
||||
komorebi = extractBinary "komorebi";
|
||||
komorebic = extractBinary "komorebic";
|
||||
komorebic-no-console = extractBinary "komorebic-no-console";
|
||||
komorebi-bar = extractBinary "komorebi-bar";
|
||||
komorebi-gui = extractBinary "komorebi-gui";
|
||||
komorebi-shortcuts = extractBinary "komorebi-shortcuts";
|
||||
};
|
||||
|
||||
mkPkgs =
|
||||
system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ (import rust-overlay) ];
|
||||
};
|
||||
in
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
systems = [
|
||||
"aarch64-darwin"
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
|
||||
imports = [
|
||||
inputs.treefmt-nix.flakeModule
|
||||
inputs.git-hooks-nix.flakeModule
|
||||
];
|
||||
|
||||
perSystem =
|
||||
{ config, system, ... }:
|
||||
let
|
||||
pkgs = mkPkgs system;
|
||||
windowsSdk = mkWindowsSdk pkgs;
|
||||
build = mkKomorebiPackages { inherit pkgs windowsSdk; };
|
||||
|
||||
# toolchain with windows target and nightly rustfmt
|
||||
rustToolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
|
||||
targets = [ "x86_64-pc-windows-msvc" ];
|
||||
};
|
||||
nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt;
|
||||
rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin {
|
||||
name = "rust-toolchain-with-nightly-rustfmt";
|
||||
paths = [
|
||||
nightlyRustfmt
|
||||
rustToolchain
|
||||
];
|
||||
};
|
||||
nightlyToolchain = pkgs.rust-bin.nightly.latest.default.override {
|
||||
targets = [ "x86_64-pc-windows-msvc" ];
|
||||
};
|
||||
cargo-udeps = pkgs.writeShellScriptBin "cargo-udeps" ''
|
||||
export PATH="${nightlyToolchain}/bin:$PATH"
|
||||
exec ${pkgs.cargo-udeps}/bin/cargo-udeps "$@"
|
||||
'';
|
||||
in
|
||||
{
|
||||
treefmt = {
|
||||
projectRootFile = "flake.nix";
|
||||
programs = {
|
||||
deadnix.enable = true;
|
||||
just.enable = true;
|
||||
nixfmt.enable = true;
|
||||
taplo.enable = true;
|
||||
rustfmt = {
|
||||
enable = true;
|
||||
package = pkgs.rust-bin.nightly.latest.rustfmt;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
checks = {
|
||||
komorebi-workspace-clippy = build.craneLib.cargoClippy (
|
||||
build.individualCrateArgs
|
||||
// {
|
||||
cargoClippyExtraArgs = "--all-targets -- -D warnings";
|
||||
}
|
||||
);
|
||||
|
||||
komorebi-workspace-fmt = build.craneLib.cargoFmt {
|
||||
inherit (build) src;
|
||||
};
|
||||
|
||||
komorebi-workspace-toml-fmt = build.craneLib.taploFmt {
|
||||
src = pkgs.lib.sources.sourceFilesBySuffices build.src [ ".toml" ];
|
||||
};
|
||||
|
||||
komorebi-workspace-deny = build.craneLib.cargoDeny {
|
||||
inherit (build) src;
|
||||
};
|
||||
|
||||
komorebi-workspace-nextest = build.craneLib.cargoNextest build.individualCrateArgs;
|
||||
};
|
||||
|
||||
packages = {
|
||||
inherit (build)
|
||||
komorebi
|
||||
komorebic
|
||||
komorebic-no-console
|
||||
komorebi-bar
|
||||
komorebi-gui
|
||||
komorebi-shortcuts
|
||||
;
|
||||
inherit windowsSdk;
|
||||
komorebi-full = build.fullBuild;
|
||||
default = build.fullBuild;
|
||||
};
|
||||
|
||||
apps = {
|
||||
komorebi = {
|
||||
type = "app";
|
||||
program = "${build.komorebi}/bin/komorebi.exe";
|
||||
};
|
||||
komorebic = {
|
||||
type = "app";
|
||||
program = "${build.komorebic}/bin/komorebic.exe";
|
||||
};
|
||||
komorebic-no-console = {
|
||||
type = "app";
|
||||
program = "${build.komorebic-no-console}/bin/komorebic-no-console.exe";
|
||||
};
|
||||
komorebi-bar = {
|
||||
type = "app";
|
||||
program = "${build.komorebi-bar}/bin/komorebi-bar.exe";
|
||||
};
|
||||
komorebi-gui = {
|
||||
type = "app";
|
||||
program = "${build.komorebi-gui}/bin/komorebi-gui.exe";
|
||||
};
|
||||
komorebi-shortcuts = {
|
||||
type = "app";
|
||||
program = "${build.komorebi-shortcuts}/bin/komorebi-shortcuts.exe";
|
||||
};
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${build.fullBuild}/bin/komorebi.exe";
|
||||
};
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
name = "komorebi";
|
||||
|
||||
RUST_BACKTRACE = "full";
|
||||
|
||||
# cross-compilation environment
|
||||
inherit (build.msvcEnv)
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER
|
||||
CC_x86_64_pc_windows_msvc
|
||||
CXX_x86_64_pc_windows_msvc
|
||||
AR_x86_64_pc_windows_msvc
|
||||
CFLAGS_x86_64_pc_windows_msvc
|
||||
CXXFLAGS_x86_64_pc_windows_msvc
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS
|
||||
CARGO_BUILD_TARGET
|
||||
;
|
||||
|
||||
packages = [
|
||||
rustToolchainWithNightlyRustfmt
|
||||
cargo-udeps
|
||||
|
||||
# cross-compilation tooling
|
||||
pkgs.llvmPackages.clang-unwrapped # provides clang-cl
|
||||
pkgs.llvmPackages.lld # provides lld-link
|
||||
pkgs.llvmPackages.llvm # provides llvm-lib
|
||||
|
||||
pkgs.cargo-deny
|
||||
pkgs.cargo-nextest
|
||||
pkgs.cargo-outdated
|
||||
pkgs.jq
|
||||
pkgs.just
|
||||
pkgs.prettier
|
||||
];
|
||||
};
|
||||
|
||||
pre-commit = {
|
||||
check.enable = true;
|
||||
settings.hooks.treefmt = {
|
||||
enable = true;
|
||||
package = config.treefmt.build.wrapper;
|
||||
pass_filenames = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
61
justfile
61
justfile
@@ -15,26 +15,38 @@ fmt:
|
||||
prettier --write .github/FUNDING.yml
|
||||
prettier --write .github/workflows/windows.yaml
|
||||
|
||||
fix:
|
||||
cargo clippy --fix --allow-dirty
|
||||
|
||||
install-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
|
||||
|
||||
install-target target:
|
||||
cargo +stable install --path {{ target }} --locked --no-default-features
|
||||
|
||||
install-targets-with-jsonschema *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just install-target-with-jsonschema $_ }
|
||||
|
||||
install-target-with-jsonschema target:
|
||||
cargo +stable install --path {{ target }} --locked
|
||||
|
||||
install:
|
||||
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
|
||||
|
||||
install-with-jsonschema:
|
||||
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
|
||||
|
||||
build-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
|
||||
|
||||
build-target target:
|
||||
cargo +stable build --package {{ target }} --locked --profile release-jeezy
|
||||
cargo +stable build --package {{ target }} --locked --release --no-default-features
|
||||
|
||||
build:
|
||||
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
|
||||
|
||||
copy-target target:
|
||||
cp .\target\release-jeezy\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
||||
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
||||
|
||||
copy-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just copy-target $_ }
|
||||
@@ -43,10 +55,10 @@ wpm target:
|
||||
just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}
|
||||
|
||||
copy:
|
||||
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
|
||||
|
||||
run target:
|
||||
cargo +stable run --bin {{ target }} --locked
|
||||
cargo +stable run --bin {{ target }} --locked --no-default-features
|
||||
|
||||
warn target $RUST_LOG="warn":
|
||||
just run {{ target }}
|
||||
@@ -61,21 +73,36 @@ trace target $RUST_LOG="trace":
|
||||
just run {{ target }}
|
||||
|
||||
deadlock $RUST_LOG="trace":
|
||||
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
||||
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
|
||||
|
||||
docgen:
|
||||
cargo run --package komorebic -- docgen
|
||||
Get-ChildItem -Path "docs/cli" -Recurse -File | ForEach-Object { (Get-Content $_.FullName) -replace 'Usage: ', 'Usage: komorebic.exe ' | Set-Content $_.FullName }
|
||||
docgen starlight:
|
||||
rm {{ starlight }}/src/data/cli/windows/*.md
|
||||
cargo run --package komorebic -- docgen --output {{ starlight }}/src/data/cli/windows
|
||||
schemars-docgen ./schema.json --output {{ starlight }}/src/content/docs/reference/komorebi-windows.mdx --title "komorebi.json (Windows)" --description "komorebi for Windows configuration schema reference"
|
||||
schemars-docgen ./schema.bar.json --output {{ starlight }}/src/content/docs/reference/bar-windows.mdx --title "komorebi.bar.json (Windows)" --description "komorebi-bar for Windows configuration schema reference"
|
||||
|
||||
jsonschema:
|
||||
cargo run --package komorebic -- static-config-schema > schema.json
|
||||
cargo run --package komorebic -- application-specific-configuration-schema > schema.asc.json
|
||||
cargo run --package komorebi-bar -- --schema > schema.bar.json
|
||||
|
||||
# this part is run in a nix shell because python is a nightmare
|
||||
schemagen:
|
||||
rm -rf static-config-docs bar-config-docs
|
||||
mkdir -p static-config-docs bar-config-docs
|
||||
generate-schema-doc ./schema.json --config template_name=js_offline --config minify=false ./static-config-docs/
|
||||
generate-schema-doc ./schema.bar.json --config template_name=js_offline --config minify=false ./bar-config-docs/
|
||||
mv ./bar-config-docs/schema.bar.html ./bar-config-docs/schema.html
|
||||
version := `cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "komorebi") | .version'`
|
||||
|
||||
schemapub:
|
||||
rm -Force komorebi-schema
|
||||
mkdir -Force komorebi-schema
|
||||
cp schema.json komorebi-schema/komorebi.{{ version }}.schema.json
|
||||
cp schema.bar.json komorebi-schema/komorebi.bar.{{ version }}.schema.json
|
||||
npx wrangler pages deploy --project-name komorebi --branch main .\komorebi-schema
|
||||
|
||||
depcheck:
|
||||
cargo outdated --depth 2
|
||||
cargo +nightly udeps --quiet
|
||||
|
||||
deps:
|
||||
cargo update
|
||||
just depgen
|
||||
|
||||
depgen:
|
||||
cargo deny check
|
||||
cargo deny list --format json | jq 'del(.unlicensed)' > dependencies.json
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
[package]
|
||||
name = "komorebi-bar"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
version = "0.1.40"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi-client = { path = "../komorebi-client" }
|
||||
komorebi-themes = { path = "../komorebi-themes" }
|
||||
komorebi-client = { path = "../komorebi-client", default-features = false }
|
||||
komorebi-themes = { path = "../komorebi-themes", default-features = false }
|
||||
|
||||
chrono-tz = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
@@ -16,23 +17,35 @@ crossbeam-channel = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
eframe = { workspace = true }
|
||||
egui-phosphor = "0.9"
|
||||
egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" }
|
||||
font-loader = "0.11"
|
||||
hotwatch = { workspace = true }
|
||||
image = "0.25"
|
||||
netdev = "0.32"
|
||||
lazy_static = { workspace = true }
|
||||
netdev = "0.40"
|
||||
num = "0.4"
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
random_word = { version = "0.4", features = ["en"] }
|
||||
parking_lot = { workspace = true }
|
||||
random_word = { version = "0.5", features = ["en"] }
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
starship-battery = "0.10"
|
||||
sysinfo = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
which = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
|
||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
|
||||
windows-icons-fallback = { package = "windows-icons", git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = [
|
||||
"dep:schemars",
|
||||
"komorebi-client/default",
|
||||
"komorebi-themes/default",
|
||||
]
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
use crate::config::get_individual_spacing;
|
||||
use crate::AUTO_SELECT_FILL_COLOUR;
|
||||
use crate::AUTO_SELECT_TEXT_COLOUR;
|
||||
use crate::BAR_HEIGHT;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use crate::KomorebiEvent;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_LEFT;
|
||||
use crate::MONITOR_RIGHT;
|
||||
use crate::MONITOR_TOP;
|
||||
use crate::config::KomobarConfig;
|
||||
use crate::config::KomobarTheme;
|
||||
use crate::config::MonitorConfigOrIndex;
|
||||
use crate::config::Position;
|
||||
use crate::config::PositionConfig;
|
||||
use crate::komorebi::Komorebi;
|
||||
use crate::komorebi::KomorebiNotificationState;
|
||||
use crate::config::get_individual_spacing;
|
||||
use crate::process_hwnd;
|
||||
use crate::render::Color32Ext;
|
||||
use crate::render::Grouping;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::render::RenderExt;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widget::WidgetConfig;
|
||||
use crate::KomorebiEvent;
|
||||
use crate::BAR_HEIGHT;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_LEFT;
|
||||
use crate::MONITOR_RIGHT;
|
||||
use crate::MONITOR_TOP;
|
||||
use crate::widgets::komorebi::Komorebi;
|
||||
use crate::widgets::komorebi::MonitorInfo;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use crate::widgets::widget::WidgetConfig;
|
||||
use color_eyre::eyre;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::TryRecvError;
|
||||
use eframe::egui::Align;
|
||||
@@ -36,6 +39,7 @@ use eframe::egui::Frame;
|
||||
use eframe::egui::Id;
|
||||
use eframe::egui::Layout;
|
||||
use eframe::egui::Margin;
|
||||
use eframe::egui::PointerButton;
|
||||
use eframe::egui::Rgba;
|
||||
use eframe::egui::Style;
|
||||
use eframe::egui::TextStyle;
|
||||
@@ -43,20 +47,102 @@ use eframe::egui::Vec2;
|
||||
use eframe::egui::Visuals;
|
||||
use font_loader::system_fonts;
|
||||
use font_loader::system_fonts::FontPropertyBuilder;
|
||||
use komorebi_client::KomorebiTheme;
|
||||
use komorebi_client::Colour;
|
||||
use komorebi_client::MonitorNotification;
|
||||
use komorebi_client::NotificationEvent;
|
||||
use komorebi_client::PathExt;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_themes::catppuccin_egui;
|
||||
use komorebi_themes::Base16Value;
|
||||
use komorebi_client::VirtualDesktopNotification;
|
||||
use komorebi_themes::Base16Wrapper;
|
||||
use komorebi_themes::Catppuccin;
|
||||
use komorebi_themes::CatppuccinValue;
|
||||
use komorebi_themes::KomobarThemeBase16;
|
||||
use komorebi_themes::KomobarThemeCatppuccin;
|
||||
use komorebi_themes::KomobarThemeCustom;
|
||||
use komorebi_themes::catppuccin_egui;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Error;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::ChildStdin;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
lazy_static! {
|
||||
static ref SESSION_STDIN: Mutex<Option<ChildStdin>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
fn start_powershell() -> eyre::Result<()> {
|
||||
// found running session, do nothing
|
||||
if SESSION_STDIN.lock().as_mut().is_some() {
|
||||
tracing::debug!("PowerShell session already started");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::debug!("Starting PowerShell session");
|
||||
|
||||
let mut child = Command::new("powershell.exe")
|
||||
.args(["-NoLogo", "-NoProfile", "-Command", "-"])
|
||||
.stdin(Stdio::piped())
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.spawn()?;
|
||||
|
||||
let stdin = child.stdin.take().expect("stdin piped");
|
||||
|
||||
// Store stdin for later commands
|
||||
let mut session_stdin = SESSION_STDIN.lock();
|
||||
*session_stdin = Option::from(stdin);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop_powershell() -> eyre::Result<()> {
|
||||
tracing::debug!("Stopping PowerShell session");
|
||||
|
||||
if let Some(mut session_stdin) = SESSION_STDIN.lock().take() {
|
||||
if let Err(e) = session_stdin.write_all(b"exit\n") {
|
||||
tracing::error!(error = %e, "failed to write exit command to PowerShell stdin");
|
||||
return Err(e.into());
|
||||
}
|
||||
if let Err(e) = session_stdin.flush() {
|
||||
tracing::error!(error = %e, "failed to flush PowerShell stdin");
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
tracing::debug!("PowerShell session stopped");
|
||||
} else {
|
||||
tracing::debug!("PowerShell session already stopped");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_powershell(cmd: &str) -> eyre::Result<()> {
|
||||
if let Some(session_stdin) = SESSION_STDIN.lock().as_mut() {
|
||||
if let Err(e) = writeln!(session_stdin, "{cmd}") {
|
||||
tracing::error!(error = %e, cmd = cmd, "failed to write command to PowerShell stdin");
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
if let Err(e) = session_stdin.flush() {
|
||||
tracing::error!(error = %e, "failed to flush PowerShell stdin");
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::new(ErrorKind::NotFound, "PowerShell session not started").into())
|
||||
}
|
||||
|
||||
pub struct Komobar {
|
||||
pub hwnd: Option<isize>,
|
||||
@@ -64,7 +150,7 @@ pub struct Komobar {
|
||||
pub disabled: bool,
|
||||
pub config: KomobarConfig,
|
||||
pub render_config: Rc<RefCell<RenderConfig>>,
|
||||
pub komorebi_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
|
||||
pub monitor_info: Option<Rc<RefCell<MonitorInfo>>>,
|
||||
pub left_widgets: Vec<Box<dyn BarWidget>>,
|
||||
pub center_widgets: Vec<Box<dyn BarWidget>>,
|
||||
pub right_widgets: Vec<Box<dyn BarWidget>>,
|
||||
@@ -76,6 +162,18 @@ pub struct Komobar {
|
||||
pub size_rect: komorebi_client::Rect,
|
||||
pub work_area_offset: komorebi_client::Rect,
|
||||
applied_theme_on_first_frame: bool,
|
||||
mouse_follows_focus: bool,
|
||||
input_config: InputConfig,
|
||||
}
|
||||
|
||||
struct InputConfig {
|
||||
accumulated_scroll_delta: Vec2,
|
||||
act_on_vertical_scroll: bool,
|
||||
act_on_horizontal_scroll: bool,
|
||||
vertical_scroll_threshold: f32,
|
||||
horizontal_scroll_threshold: f32,
|
||||
vertical_scroll_max_threshold: f32,
|
||||
horizontal_scroll_max_threshold: f32,
|
||||
}
|
||||
|
||||
pub fn apply_theme(
|
||||
@@ -87,75 +185,86 @@ pub fn apply_theme(
|
||||
grouping: Option<Grouping>,
|
||||
render_config: Rc<RefCell<RenderConfig>>,
|
||||
) {
|
||||
match theme {
|
||||
KomobarTheme::Catppuccin {
|
||||
let (auto_select_fill, auto_select_text) = match theme {
|
||||
KomobarTheme::Catppuccin(KomobarThemeCatppuccin {
|
||||
name: catppuccin,
|
||||
accent: catppuccin_value,
|
||||
} => match catppuccin {
|
||||
Catppuccin::Frappe => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
auto_select_fill: catppuccin_auto_select_fill,
|
||||
auto_select_text: catppuccin_auto_select_text,
|
||||
}) => {
|
||||
match catppuccin {
|
||||
Catppuccin::Frappe => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::FRAPPE.base);
|
||||
bg_color.replace(catppuccin_egui::FRAPPE.base);
|
||||
}
|
||||
Catppuccin::Latte => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::LATTE.base);
|
||||
}
|
||||
Catppuccin::Macchiato => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::MACCHIATO.base);
|
||||
}
|
||||
Catppuccin::Mocha => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::MOCHA.base);
|
||||
}
|
||||
}
|
||||
Catppuccin::Latte => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::LATTE.base);
|
||||
}
|
||||
Catppuccin::Macchiato => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::MACCHIATO.base);
|
||||
}
|
||||
Catppuccin::Mocha => {
|
||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA);
|
||||
let catppuccin_value = catppuccin_value.unwrap_or_default();
|
||||
let accent = catppuccin_value.color32(catppuccin.as_theme());
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
style.visuals.override_text_color = None;
|
||||
});
|
||||
|
||||
bg_color.replace(catppuccin_egui::MOCHA.base);
|
||||
}
|
||||
},
|
||||
KomobarTheme::Base16 {
|
||||
(
|
||||
catppuccin_auto_select_fill.map(|c| c.color32(catppuccin.as_theme())),
|
||||
catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())),
|
||||
)
|
||||
}
|
||||
KomobarTheme::Base16(KomobarThemeBase16 {
|
||||
name: base16,
|
||||
accent: base16_value,
|
||||
} => {
|
||||
auto_select_fill: base16_auto_select_fill,
|
||||
auto_select_text: base16_auto_select_text,
|
||||
}) => {
|
||||
ctx.set_style(base16.style());
|
||||
let base16_value = base16_value.unwrap_or_default();
|
||||
let accent = base16_value.color32(base16);
|
||||
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
@@ -164,8 +273,46 @@ pub fn apply_theme(
|
||||
});
|
||||
|
||||
bg_color.replace(base16.background());
|
||||
|
||||
(
|
||||
base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Base16(base16))),
|
||||
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))),
|
||||
)
|
||||
}
|
||||
}
|
||||
KomobarTheme::Custom(KomobarThemeCustom {
|
||||
colours,
|
||||
accent: base16_value,
|
||||
auto_select_fill: base16_auto_select_fill,
|
||||
auto_select_text: base16_auto_select_text,
|
||||
}) => {
|
||||
let background = colours.background();
|
||||
ctx.set_style(colours.style());
|
||||
let base16_value = base16_value.unwrap_or_default();
|
||||
let accent = base16_value.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
});
|
||||
|
||||
bg_color.replace(background);
|
||||
|
||||
(
|
||||
base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))),
|
||||
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
AUTO_SELECT_FILL_COLOUR.store(
|
||||
auto_select_fill.map_or(0, |c| Colour::from(c).into()),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
AUTO_SELECT_TEXT_COLOUR.store(
|
||||
auto_select_text.map_or(0, |c| Colour::from(c).into()),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
// Apply transparency_alpha
|
||||
let theme_color = *bg_color.borrow();
|
||||
@@ -175,16 +322,15 @@ pub fn apply_theme(
|
||||
// apply rounding to the widgets
|
||||
if let Some(Grouping::Bar(config) | Grouping::Alignment(config) | Grouping::Widget(config)) =
|
||||
&grouping
|
||||
&& let Some(rounding) = config.rounding
|
||||
{
|
||||
if let Some(rounding) = config.rounding {
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||
});
|
||||
}
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||
});
|
||||
}
|
||||
|
||||
// Update RenderConfig's background_color so that widgets will have the new color
|
||||
@@ -195,7 +341,7 @@ impl Komobar {
|
||||
pub fn apply_config(
|
||||
&mut self,
|
||||
ctx: &Context,
|
||||
previous_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
|
||||
previous_monitor_info: Option<Rc<RefCell<MonitorInfo>>>,
|
||||
) {
|
||||
MAX_LABEL_WIDTH.store(
|
||||
self.config.max_label_width.unwrap_or(400.0) as i32,
|
||||
@@ -224,7 +370,7 @@ impl Komobar {
|
||||
self.config.icon_scale,
|
||||
));
|
||||
|
||||
let mut komorebi_notification_state = previous_notification_state;
|
||||
let mut monitor_info = previous_monitor_info;
|
||||
let mut komorebi_widgets = Vec::new();
|
||||
|
||||
for (idx, widget_config) in self.config.left_widgets.iter().enumerate() {
|
||||
@@ -276,19 +422,18 @@ impl Komobar {
|
||||
komorebi_widgets
|
||||
.into_iter()
|
||||
.for_each(|(mut widget, idx, side)| {
|
||||
match komorebi_notification_state {
|
||||
match monitor_info {
|
||||
None => {
|
||||
komorebi_notification_state =
|
||||
Some(widget.komorebi_notification_state.clone());
|
||||
monitor_info = Some(widget.monitor_info.clone());
|
||||
}
|
||||
Some(ref previous) => {
|
||||
if widget.workspaces.is_some_and(|w| w.enable) {
|
||||
previous.borrow_mut().update_from_config(
|
||||
&widget.komorebi_notification_state.borrow(),
|
||||
);
|
||||
if widget.workspaces.is_some() {
|
||||
previous
|
||||
.borrow_mut()
|
||||
.update_from_self(&widget.monitor_info.borrow());
|
||||
}
|
||||
|
||||
widget.komorebi_notification_state = previous.clone();
|
||||
widget.monitor_info = previous.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,20 +453,25 @@ impl Komobar {
|
||||
self.right_widgets = right_widgets;
|
||||
|
||||
let (usr_monitor_index, config_work_area_offset) = match &self.config.monitor {
|
||||
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
|
||||
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
|
||||
(monitor_config.index, monitor_config.work_area_offset)
|
||||
}
|
||||
MonitorConfigOrIndex::Index(idx) => (*idx, None),
|
||||
Some(MonitorConfigOrIndex::Index(idx)) => (*idx, None),
|
||||
None => (0, None),
|
||||
};
|
||||
let monitor_index = self.komorebi_notification_state.as_ref().and_then(|state| {
|
||||
state
|
||||
.borrow()
|
||||
.monitor_usr_idx_map
|
||||
.get(&usr_monitor_index)
|
||||
.copied()
|
||||
|
||||
let mapped_info = self.monitor_info.as_ref().map(|info| {
|
||||
let monitor = info.borrow();
|
||||
(
|
||||
monitor.monitor_usr_idx_map.get(&usr_monitor_index).copied(),
|
||||
monitor.mouse_follows_focus,
|
||||
)
|
||||
});
|
||||
|
||||
self.monitor_index = monitor_index;
|
||||
if let Some(info) = mapped_info {
|
||||
self.monitor_index = info.0;
|
||||
self.mouse_follows_focus = info.1;
|
||||
}
|
||||
|
||||
if let Some(monitor_index) = self.monitor_index {
|
||||
if let (prev_rect, Some(new_rect)) = (&self.work_area_offset, &config_work_area_offset)
|
||||
@@ -372,17 +522,51 @@ impl Komobar {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.komorebi_notification_state.is_some() && !self.disabled {
|
||||
tracing::warn!("couldn't find the monitor index of this bar! Disabling the bar until the monitor connects...");
|
||||
} else if self.monitor_info.is_some() && !self.disabled {
|
||||
tracing::warn!(
|
||||
"couldn't find the monitor index of this bar! Disabling the bar until the monitor connects..."
|
||||
);
|
||||
self.disabled = true;
|
||||
} else {
|
||||
tracing::warn!("couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi.");
|
||||
tracing::warn!(
|
||||
"couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi."
|
||||
);
|
||||
self.disabled = true;
|
||||
}
|
||||
|
||||
if let Some(mouse) = &self.config.mouse {
|
||||
self.input_config.act_on_vertical_scroll =
|
||||
mouse.on_scroll_up.is_some() || mouse.on_scroll_down.is_some();
|
||||
self.input_config.act_on_horizontal_scroll =
|
||||
mouse.on_scroll_left.is_some() || mouse.on_scroll_right.is_some();
|
||||
self.input_config.vertical_scroll_threshold = mouse
|
||||
.vertical_scroll_threshold
|
||||
.unwrap_or(30.0)
|
||||
.clamp(10.0, 300.0);
|
||||
self.input_config.horizontal_scroll_threshold = mouse
|
||||
.horizontal_scroll_threshold
|
||||
.unwrap_or(30.0)
|
||||
.clamp(10.0, 300.0);
|
||||
// limit how many "ticks" can be accumulated
|
||||
self.input_config.vertical_scroll_max_threshold =
|
||||
self.input_config.vertical_scroll_threshold * 3.0;
|
||||
self.input_config.horizontal_scroll_max_threshold =
|
||||
self.input_config.horizontal_scroll_threshold * 3.0;
|
||||
|
||||
if mouse.has_command() {
|
||||
start_powershell().unwrap_or_else(|_| {
|
||||
tracing::error!("failed to start powershell session");
|
||||
});
|
||||
} else {
|
||||
stop_powershell().unwrap_or_else(|_| {
|
||||
tracing::error!("failed to stop powershell session");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("widget configuration options applied");
|
||||
|
||||
self.komorebi_notification_state = komorebi_notification_state;
|
||||
self.monitor_info = monitor_info;
|
||||
}
|
||||
|
||||
/// Updates the `size_rect` field. Returns a bool indicating if the field was changed or not
|
||||
@@ -419,7 +603,9 @@ impl Komobar {
|
||||
end.x -= margin.left + margin.right;
|
||||
|
||||
if end.y == 0.0 {
|
||||
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
|
||||
tracing::warn!(
|
||||
"position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default"
|
||||
)
|
||||
}
|
||||
|
||||
self.size_rect = komorebi_client::Rect {
|
||||
@@ -431,11 +617,11 @@ impl Komobar {
|
||||
}
|
||||
|
||||
fn try_apply_theme(&mut self, ctx: &Context) {
|
||||
match self.config.theme {
|
||||
match &self.config.theme {
|
||||
Some(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
theme,
|
||||
theme.clone(),
|
||||
self.bg_color.clone(),
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
@@ -447,13 +633,15 @@ impl Komobar {
|
||||
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|
||||
|_| dirs::home_dir().expect("there is no home directory"),
|
||||
|home_path| {
|
||||
let home = PathBuf::from(&home_path);
|
||||
let home = home_path.replace_env();
|
||||
|
||||
assert!(
|
||||
home.is_dir(),
|
||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory"
|
||||
);
|
||||
|
||||
home
|
||||
|
||||
if home.as_path().is_dir() {
|
||||
home
|
||||
} else {
|
||||
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -472,21 +660,6 @@ impl Komobar {
|
||||
bar_grouping,
|
||||
self.render_config.clone(),
|
||||
);
|
||||
|
||||
let stack_accent = match theme {
|
||||
KomorebiTheme::Catppuccin {
|
||||
name, stack_border, ..
|
||||
} => stack_border
|
||||
.unwrap_or(CatppuccinValue::Green)
|
||||
.color32(name.as_theme()),
|
||||
KomorebiTheme::Base16 {
|
||||
name, stack_border, ..
|
||||
} => stack_border.unwrap_or(Base16Value::Base0B).color32(name),
|
||||
};
|
||||
|
||||
if let Some(state) = &self.komorebi_notification_state {
|
||||
state.borrow_mut().stack_accent = Some(stack_accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
@@ -499,17 +672,16 @@ impl Komobar {
|
||||
| Grouping::Alignment(config)
|
||||
| Grouping::Widget(config),
|
||||
) = &bar_grouping
|
||||
&& let Some(rounding) = config.rounding
|
||||
{
|
||||
if let Some(rounding) = config.rounding {
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.widgets.noninteractive.corner_radius =
|
||||
rounding.into();
|
||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||
});
|
||||
}
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.widgets.noninteractive.corner_radius =
|
||||
rounding.into();
|
||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +701,7 @@ impl Komobar {
|
||||
disabled: false,
|
||||
config,
|
||||
render_config: Rc::new(RefCell::new(RenderConfig::new())),
|
||||
komorebi_notification_state: None,
|
||||
monitor_info: None,
|
||||
left_widgets: vec![],
|
||||
center_widgets: vec![],
|
||||
right_widgets: vec![],
|
||||
@@ -541,6 +713,16 @@ impl Komobar {
|
||||
size_rect: komorebi_client::Rect::default(),
|
||||
work_area_offset: komorebi_client::Rect::default(),
|
||||
applied_theme_on_first_frame: false,
|
||||
mouse_follows_focus: false,
|
||||
input_config: InputConfig {
|
||||
accumulated_scroll_delta: Vec2::new(0.0, 0.0),
|
||||
act_on_vertical_scroll: false,
|
||||
act_on_horizontal_scroll: false,
|
||||
vertical_scroll_threshold: 0.0,
|
||||
horizontal_scroll_threshold: 0.0,
|
||||
vertical_scroll_max_threshold: 0.0,
|
||||
horizontal_scroll_max_threshold: 0.0,
|
||||
},
|
||||
};
|
||||
|
||||
komobar.apply_config(&cc.egui_ctx, None);
|
||||
@@ -665,12 +847,12 @@ impl eframe::App for Komobar {
|
||||
|
||||
if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {
|
||||
self.scale_factor = ctx.native_pixels_per_point().unwrap_or(1.0);
|
||||
self.apply_config(ctx, self.komorebi_notification_state.clone());
|
||||
self.apply_config(ctx, self.monitor_info.clone());
|
||||
}
|
||||
|
||||
if let Ok(updated_config) = self.rx_config.try_recv() {
|
||||
self.config = updated_config;
|
||||
self.apply_config(ctx, self.komorebi_notification_state.clone());
|
||||
self.apply_config(ctx, self.monitor_info.clone());
|
||||
}
|
||||
|
||||
match self.rx_gui.try_recv() {
|
||||
@@ -685,13 +867,41 @@ impl eframe::App for Komobar {
|
||||
Ok(KomorebiEvent::Notification(notification)) => {
|
||||
let state = ¬ification.state;
|
||||
let usr_monitor_index = match &self.config.monitor {
|
||||
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
|
||||
MonitorConfigOrIndex::Index(idx) => *idx,
|
||||
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
|
||||
monitor_config.index
|
||||
}
|
||||
Some(MonitorConfigOrIndex::Index(idx)) => *idx,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
let monitor_index = state.monitor_usr_idx_map.get(&usr_monitor_index).copied();
|
||||
self.monitor_index = monitor_index;
|
||||
let mut should_apply_config = false;
|
||||
|
||||
match notification.event {
|
||||
NotificationEvent::VirtualDesktop(
|
||||
VirtualDesktopNotification::EnteredAssociatedVirtualDesktop,
|
||||
) => {
|
||||
tracing::debug!(
|
||||
"back on komorebi's associated virtual desktop - restoring bar"
|
||||
);
|
||||
if let Some(hwnd) = self.hwnd {
|
||||
komorebi_client::WindowsApi::restore_window(hwnd);
|
||||
}
|
||||
}
|
||||
NotificationEvent::VirtualDesktop(
|
||||
VirtualDesktopNotification::LeftAssociatedVirtualDesktop,
|
||||
) => {
|
||||
tracing::debug!(
|
||||
"no longer on komorebi's associated virtual desktop - minimizing bar"
|
||||
);
|
||||
if let Some(hwnd) = self.hwnd {
|
||||
komorebi_client::WindowsApi::minimize_window(hwnd);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self.monitor_index.is_none()
|
||||
|| self
|
||||
.monitor_index
|
||||
@@ -736,9 +946,9 @@ impl eframe::App for Komobar {
|
||||
) {
|
||||
let monitor_index = self.monitor_index.expect("should have a monitor index");
|
||||
|
||||
let monitor_size = state.monitors.elements()[monitor_index].size();
|
||||
let monitor_size = state.monitors.elements()[monitor_index].size;
|
||||
|
||||
self.update_monitor_coordinates(monitor_size);
|
||||
self.update_monitor_coordinates(&monitor_size);
|
||||
|
||||
should_apply_config = true;
|
||||
}
|
||||
@@ -749,7 +959,7 @@ impl eframe::App for Komobar {
|
||||
|
||||
// Check if monitor coordinates/size has changed
|
||||
if let Some(monitor_index) = self.monitor_index {
|
||||
let monitor_size = state.monitors.elements()[monitor_index].size();
|
||||
let monitor_size = state.monitors.elements()[monitor_index].size;
|
||||
let top = MONITOR_TOP.load(Ordering::SeqCst);
|
||||
let left = MONITOR_LEFT.load(Ordering::SeqCst);
|
||||
let right = MONITOR_RIGHT.load(Ordering::SeqCst);
|
||||
@@ -759,36 +969,38 @@ impl eframe::App for Komobar {
|
||||
bottom: monitor_size.bottom,
|
||||
right,
|
||||
};
|
||||
if *monitor_size != rect {
|
||||
if monitor_size != rect {
|
||||
tracing::info!(
|
||||
"Monitor coordinates/size has changed, storing new coordinates: {:#?}",
|
||||
monitor_size
|
||||
);
|
||||
|
||||
self.update_monitor_coordinates(monitor_size);
|
||||
self.update_monitor_coordinates(&monitor_size);
|
||||
|
||||
should_apply_config = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(komorebi_notification_state) = &self.komorebi_notification_state {
|
||||
komorebi_notification_state
|
||||
.borrow_mut()
|
||||
.handle_notification(
|
||||
ctx,
|
||||
self.monitor_index,
|
||||
notification,
|
||||
self.bg_color.clone(),
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
self.config.grouping,
|
||||
self.config.theme,
|
||||
self.render_config.clone(),
|
||||
);
|
||||
if let Some(monitor_info) = &self.monitor_info {
|
||||
monitor_info.borrow_mut().update(
|
||||
self.monitor_index,
|
||||
notification.state,
|
||||
self.render_config.borrow().show_all_icons,
|
||||
);
|
||||
handle_notification(
|
||||
ctx,
|
||||
notification.event,
|
||||
self.bg_color.clone(),
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
self.config.grouping,
|
||||
self.config.theme.clone(),
|
||||
self.render_config.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
if should_apply_config {
|
||||
self.apply_config(ctx, self.komorebi_notification_state.clone());
|
||||
self.apply_config(ctx, self.monitor_info.clone());
|
||||
|
||||
// Reposition the Bar
|
||||
self.position_bar();
|
||||
@@ -870,6 +1082,111 @@ impl eframe::App for Komobar {
|
||||
let frame = render_config.change_frame_on_bar(frame, &ctx.style());
|
||||
|
||||
CentralPanel::default().frame(frame).show(ctx, |ui| {
|
||||
if let Some(mouse_config) = &self.config.mouse {
|
||||
let command = if ui
|
||||
.input(|i| i.pointer.button_double_clicked(PointerButton::Primary))
|
||||
{
|
||||
tracing::debug!("Input: primary button double clicked");
|
||||
&mouse_config.on_primary_double_click
|
||||
} else if ui.input(|i| i.pointer.button_clicked(PointerButton::Secondary)) {
|
||||
tracing::debug!("Input: secondary button clicked");
|
||||
&mouse_config.on_secondary_click
|
||||
} else if ui.input(|i| i.pointer.button_clicked(PointerButton::Middle)) {
|
||||
tracing::debug!("Input: middle button clicked");
|
||||
&mouse_config.on_middle_click
|
||||
} else if ui.input(|i| i.pointer.button_clicked(PointerButton::Extra1)) {
|
||||
tracing::debug!("Input: extra1 button clicked");
|
||||
&mouse_config.on_extra1_click
|
||||
} else if ui.input(|i| i.pointer.button_clicked(PointerButton::Extra2)) {
|
||||
tracing::debug!("Input: extra2 button clicked");
|
||||
&mouse_config.on_extra2_click
|
||||
} else if self.input_config.act_on_vertical_scroll
|
||||
|| self.input_config.act_on_horizontal_scroll
|
||||
{
|
||||
let scroll_delta = ui.input(|input| input.smooth_scroll_delta);
|
||||
|
||||
self.input_config.accumulated_scroll_delta += scroll_delta;
|
||||
|
||||
if scroll_delta.y != 0.0 && self.input_config.act_on_vertical_scroll {
|
||||
// Do not store more than the max threshold
|
||||
self.input_config.accumulated_scroll_delta.y =
|
||||
self.input_config.accumulated_scroll_delta.y.clamp(
|
||||
-self.input_config.vertical_scroll_max_threshold,
|
||||
self.input_config.vertical_scroll_max_threshold,
|
||||
);
|
||||
|
||||
// When the accumulated scroll passes the threshold, trigger a tick.
|
||||
if self.input_config.accumulated_scroll_delta.y.abs()
|
||||
>= self.input_config.vertical_scroll_threshold
|
||||
{
|
||||
let direction_command =
|
||||
if self.input_config.accumulated_scroll_delta.y > 0.0 {
|
||||
&mouse_config.on_scroll_up
|
||||
} else {
|
||||
&mouse_config.on_scroll_down
|
||||
};
|
||||
|
||||
// Remove one tick's worth of scroll from the accumulator, preserving any excess.
|
||||
self.input_config.accumulated_scroll_delta.y -=
|
||||
self.input_config.vertical_scroll_threshold
|
||||
* self.input_config.accumulated_scroll_delta.y.signum();
|
||||
|
||||
tracing::debug!(
|
||||
"Input: vertical scroll ticked. excess: {} | threshold: {}",
|
||||
self.input_config.accumulated_scroll_delta.y,
|
||||
self.input_config.vertical_scroll_threshold
|
||||
);
|
||||
|
||||
direction_command
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
} else if scroll_delta.x != 0.0 && self.input_config.act_on_horizontal_scroll {
|
||||
// Do not store more than the max threshold
|
||||
self.input_config.accumulated_scroll_delta.x =
|
||||
self.input_config.accumulated_scroll_delta.x.clamp(
|
||||
-self.input_config.horizontal_scroll_max_threshold,
|
||||
self.input_config.horizontal_scroll_max_threshold,
|
||||
);
|
||||
|
||||
// When the accumulated scroll passes the threshold, trigger a tick.
|
||||
if self.input_config.accumulated_scroll_delta.x.abs()
|
||||
>= self.input_config.horizontal_scroll_threshold
|
||||
{
|
||||
let direction_command =
|
||||
if self.input_config.accumulated_scroll_delta.x > 0.0 {
|
||||
&mouse_config.on_scroll_left
|
||||
} else {
|
||||
&mouse_config.on_scroll_right
|
||||
};
|
||||
|
||||
// Remove one tick's worth of scroll from the accumulator, preserving any excess.
|
||||
self.input_config.accumulated_scroll_delta.x -=
|
||||
self.input_config.horizontal_scroll_threshold
|
||||
* self.input_config.accumulated_scroll_delta.x.signum();
|
||||
|
||||
tracing::debug!(
|
||||
"Input: horizontal scroll ticked. excess: {} | threshold: {}",
|
||||
self.input_config.accumulated_scroll_delta.x,
|
||||
self.input_config.horizontal_scroll_threshold
|
||||
);
|
||||
|
||||
direction_command
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
} else {
|
||||
&None
|
||||
};
|
||||
|
||||
if let Some(command) = command {
|
||||
command.execute(self.mouse_follows_focus);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply grouping logic for the bar as a whole
|
||||
let area_frame = if let Some(frame) = &self.config.frame {
|
||||
Frame::NONE
|
||||
@@ -1009,3 +1326,66 @@ pub enum Alignment {
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_notification(
|
||||
ctx: &Context,
|
||||
event: komorebi_client::NotificationEvent,
|
||||
bg_color: Rc<RefCell<Color32>>,
|
||||
bg_color_with_alpha: Rc<RefCell<Color32>>,
|
||||
transparency_alpha: Option<u8>,
|
||||
grouping: Option<Grouping>,
|
||||
default_theme: Option<KomobarTheme>,
|
||||
render_config: Rc<RefCell<RenderConfig>>,
|
||||
) {
|
||||
if let NotificationEvent::Socket(message) = event {
|
||||
match message {
|
||||
SocketMessage::ReloadStaticConfiguration(path) => {
|
||||
if let Ok(config) = komorebi_client::StaticConfig::read(&path) {
|
||||
if let Some(theme) = config.theme {
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
bg_color.clone(),
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!("applied theme from updated komorebi.json");
|
||||
} else if let Some(default_theme) = default_theme {
|
||||
apply_theme(
|
||||
ctx,
|
||||
default_theme,
|
||||
bg_color.clone(),
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!(
|
||||
"removed theme from updated komorebi.json and applied default theme"
|
||||
);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"theme was removed from updated komorebi.json but there was no default theme to apply"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::Theme(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(*theme),
|
||||
bg_color,
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!("applied theme from komorebi socket message");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
use crate::render::Grouping;
|
||||
use crate::widget::WidgetConfig;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use crate::bar::exec_powershell;
|
||||
use crate::render::Grouping;
|
||||
use crate::widgets::widget::WidgetConfig;
|
||||
use eframe::egui::Pos2;
|
||||
use eframe::egui::TextBuffer;
|
||||
use eframe::egui::Vec2;
|
||||
use komorebi_client::KomorebiTheme;
|
||||
use komorebi_client::PathExt;
|
||||
use komorebi_client::Rect;
|
||||
use schemars::JsonSchema;
|
||||
use komorebi_client::SocketMessage;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
/// The `komorebi.bar.json` configuration file reference for `v0.1.35`
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// The `komorebi.bar.json` configuration file reference for `v0.1.40`
|
||||
pub struct KomobarConfig {
|
||||
/// Bar height (default: 50)
|
||||
/// Bar height
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 50.0)))]
|
||||
pub height: Option<f32>,
|
||||
/// Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or
|
||||
/// vertical definition which can each take a single value for a symmetric padding or two
|
||||
@@ -73,23 +76,31 @@ pub struct KomobarConfig {
|
||||
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)
|
||||
pub frame: Option<FrameConfig>,
|
||||
/// The monitor index or the full monitor options
|
||||
pub monitor: MonitorConfigOrIndex,
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = MonitorConfigOrIndex::Index(0))))]
|
||||
pub monitor: Option<MonitorConfigOrIndex>,
|
||||
/// Font family
|
||||
pub font_family: Option<String>,
|
||||
/// Font size (default: 12.5)
|
||||
/// Font size
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 12.5)))]
|
||||
pub font_size: Option<f32>,
|
||||
/// Scale of the icons relative to the font_size [[1.0-2.0]]. (default: 1.4)
|
||||
/// Scale of the icons relative to the font_size [[1.0-2.0]]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 1.4)))]
|
||||
pub icon_scale: Option<f32>,
|
||||
/// Max label width before text truncation (default: 400.0)
|
||||
/// Max label width before text truncation
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 400.0)))]
|
||||
pub max_label_width: Option<f32>,
|
||||
/// Theme
|
||||
pub theme: Option<KomobarTheme>,
|
||||
/// Alpha value for the color transparency [[0-255]] (default: 200)
|
||||
/// Alpha value for the color transparency [[0-255]]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 200)))]
|
||||
pub transparency_alpha: Option<u8>,
|
||||
/// Spacing between widgets (default: 10.0)
|
||||
/// Spacing between widgets
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10.0)))]
|
||||
pub widget_spacing: Option<f32>,
|
||||
/// Visual grouping for widgets
|
||||
pub grouping: Option<Grouping>,
|
||||
/// Options for mouse interaction on the bar
|
||||
pub mouse: Option<MouseConfig>,
|
||||
/// Left side widgets (ordered left-to-right)
|
||||
pub left_widgets: Vec<WidgetConfig>,
|
||||
/// Center widgets (ordered left-to-right)
|
||||
@@ -115,7 +126,9 @@ impl KomobarConfig {
|
||||
}
|
||||
|
||||
if display {
|
||||
println!("\nYour bar configuration file contains some options that have been renamed or deprecated:\n");
|
||||
println!(
|
||||
"\nYour bar configuration file contains some options that have been renamed or deprecated:\n"
|
||||
);
|
||||
for (canonical, aliases) in map {
|
||||
for alias in aliases {
|
||||
if raw.contains(alias) {
|
||||
@@ -136,7 +149,9 @@ impl KomobarConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Position configuration
|
||||
pub struct PositionConfig {
|
||||
/// The desired starting position of the bar (0,0 = top left of the screen)
|
||||
#[serde(alias = "position")]
|
||||
@@ -146,14 +161,18 @@ pub struct PositionConfig {
|
||||
pub end: Option<Position>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Frame configuration
|
||||
pub struct FrameConfig {
|
||||
/// Margin inside the painted frame
|
||||
pub inner_margin: Position,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
/// Monitor configuration or monitor index
|
||||
pub enum MonitorConfigOrIndex {
|
||||
/// The monitor index where you want the bar to show
|
||||
Index(usize),
|
||||
@@ -161,7 +180,9 @@ pub enum MonitorConfigOrIndex {
|
||||
MonitorConfig(MonitorConfig),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Monitor configuration
|
||||
pub struct MonitorConfig {
|
||||
/// Komorebi monitor index of the monitor on which to render the bar
|
||||
pub index: usize,
|
||||
@@ -172,15 +193,20 @@ pub struct MonitorConfig {
|
||||
pub type Padding = SpacingKind;
|
||||
pub type Margin = SpacingKind;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
// WARNING: To any developer messing with this code in the future: The order here matters!
|
||||
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
|
||||
// `GroupedSpacingConfig` with both `vertical` and `horizontal` set to `None` ignoring the
|
||||
// individual values.
|
||||
/// Spacing kind
|
||||
pub enum SpacingKind {
|
||||
/// Spacing applied to all sides
|
||||
All(f32),
|
||||
/// Individual spacing applied to each side
|
||||
Individual(IndividualSpacingConfig),
|
||||
/// Grouped vertical and horizontal spacing
|
||||
Grouped(GroupedSpacingConfig),
|
||||
}
|
||||
|
||||
@@ -223,24 +249,38 @@ impl SpacingKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Grouped vertical and horizontal spacing
|
||||
pub struct GroupedSpacingConfig {
|
||||
/// Vertical grouped spacing
|
||||
pub vertical: Option<GroupedSpacingOptions>,
|
||||
/// Horizontal grouped spacing
|
||||
pub horizontal: Option<GroupedSpacingOptions>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
/// Grouped spacing options
|
||||
pub enum GroupedSpacingOptions {
|
||||
/// Symmetrical grouped spacing
|
||||
Symmetrical(f32),
|
||||
/// Split grouped spacing
|
||||
Split(f32, f32),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Individual spacing configuration
|
||||
pub struct IndividualSpacingConfig {
|
||||
/// Spacing for the top
|
||||
pub top: f32,
|
||||
/// Spacing for the bottom
|
||||
pub bottom: f32,
|
||||
/// Spacing for the left
|
||||
pub left: f32,
|
||||
/// Spacing for the right
|
||||
pub right: f32,
|
||||
}
|
||||
|
||||
@@ -317,6 +357,153 @@ pub fn get_individual_spacing(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
/// Mouse message
|
||||
pub enum MouseMessage {
|
||||
/// Send a message to the komorebi client.
|
||||
/// By default, a batch of messages are sent in the following order:
|
||||
/// FocusMonitorAtCursor =>
|
||||
/// MouseFollowsFocus(false) =>
|
||||
/// {message} =>
|
||||
/// MouseFollowsFocus({original.value})
|
||||
///
|
||||
/// Example:
|
||||
/// ```json
|
||||
/// "on_extra2_click": {
|
||||
/// "message": {
|
||||
/// "type": "NewWorkspace"
|
||||
/// }
|
||||
/// },
|
||||
/// ```
|
||||
/// or:
|
||||
/// ```json
|
||||
/// "on_middle_click": {
|
||||
/// "focus_monitor_at_cursor": false,
|
||||
/// "ignore_mouse_follows_focus": false,
|
||||
/// "message": {
|
||||
/// "type": "TogglePause"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// or:
|
||||
/// ```json
|
||||
/// "on_scroll_up": {
|
||||
/// "message": {
|
||||
/// "type": "CycleFocusWorkspace",
|
||||
/// "content": "Previous"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
Komorebi(KomorebiMouseMessage),
|
||||
/// Execute a custom command.
|
||||
/// CMD (%variable%), Bash ($variable) and PowerShell ($Env:variable) variables will be resolved.
|
||||
/// Example: `komorebic toggle-pause`
|
||||
Command(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Komorebi socket mouse message
|
||||
pub struct KomorebiMouseMessage {
|
||||
/// Send the FocusMonitorAtCursor message
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
|
||||
pub focus_monitor_at_cursor: Option<bool>,
|
||||
/// Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
|
||||
pub ignore_mouse_follows_focus: Option<bool>,
|
||||
/// The message to send to the komorebi client
|
||||
pub message: komorebi_client::SocketMessage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Mouse configuration
|
||||
pub struct MouseConfig {
|
||||
/// Command to send on primary/left double button click
|
||||
pub on_primary_double_click: Option<MouseMessage>,
|
||||
/// Command to send on secondary/right button click
|
||||
pub on_secondary_click: Option<MouseMessage>,
|
||||
/// Command to send on middle button click
|
||||
pub on_middle_click: Option<MouseMessage>,
|
||||
/// Command to send on extra1/back button click
|
||||
pub on_extra1_click: Option<MouseMessage>,
|
||||
/// Command to send on extra2/forward button click
|
||||
pub on_extra2_click: Option<MouseMessage>,
|
||||
|
||||
/// Defines how many points a user needs to scroll vertically to make a "tick" on a mouse/touchpad/touchscreen
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 30.0)))]
|
||||
pub vertical_scroll_threshold: Option<f32>,
|
||||
/// Command to send on scrolling up (every tick)
|
||||
pub on_scroll_up: Option<MouseMessage>,
|
||||
/// Command to send on scrolling down (every tick)
|
||||
pub on_scroll_down: Option<MouseMessage>,
|
||||
|
||||
/// Defines how many points a user needs to scroll horizontally to make a "tick" on a mouse/touchpad/touchscreen
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 30.0)))]
|
||||
pub horizontal_scroll_threshold: Option<f32>,
|
||||
/// Command to send on scrolling left (every tick)
|
||||
pub on_scroll_left: Option<MouseMessage>,
|
||||
/// Command to send on scrolling right (every tick)
|
||||
pub on_scroll_right: Option<MouseMessage>,
|
||||
}
|
||||
|
||||
impl MouseConfig {
|
||||
pub fn has_command(&self) -> bool {
|
||||
[
|
||||
&self.on_primary_double_click,
|
||||
&self.on_secondary_click,
|
||||
&self.on_middle_click,
|
||||
&self.on_extra1_click,
|
||||
&self.on_extra2_click,
|
||||
&self.on_scroll_up,
|
||||
&self.on_scroll_down,
|
||||
&self.on_scroll_left,
|
||||
&self.on_scroll_right,
|
||||
]
|
||||
.iter()
|
||||
.any(|opt| matches!(opt, Some(MouseMessage::Command(_))))
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseMessage {
|
||||
pub fn execute(&self, mouse_follows_focus: bool) {
|
||||
match self {
|
||||
MouseMessage::Komorebi(config) => {
|
||||
let mut messages = Vec::new();
|
||||
|
||||
if config.focus_monitor_at_cursor.unwrap_or(true) {
|
||||
messages.push(SocketMessage::FocusMonitorAtCursor);
|
||||
}
|
||||
|
||||
if config.ignore_mouse_follows_focus.unwrap_or(true) {
|
||||
messages.push(SocketMessage::MouseFollowsFocus(false));
|
||||
messages.push(config.message.clone());
|
||||
messages.push(SocketMessage::MouseFollowsFocus(mouse_follows_focus));
|
||||
} else {
|
||||
messages.push(config.message.clone());
|
||||
}
|
||||
|
||||
tracing::debug!("Sending messages: {messages:?}");
|
||||
|
||||
if komorebi_client::send_batch(messages).is_err() {
|
||||
tracing::error!("could not send commands");
|
||||
}
|
||||
}
|
||||
MouseMessage::Command(cmd) => {
|
||||
tracing::debug!("Executing command: {}", cmd);
|
||||
|
||||
let cmd_no_env = cmd.replace_env();
|
||||
|
||||
if exec_powershell(cmd_no_env.to_str().expect("Invalid command")).is_err() {
|
||||
tracing::error!("Failed to execute '{}'", cmd);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl KomobarConfig {
|
||||
pub fn read(path: &PathBuf) -> color_eyre::Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
@@ -338,7 +525,9 @@ impl KomobarConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Position
|
||||
pub struct Position {
|
||||
/// X coordinate
|
||||
pub x: f32,
|
||||
@@ -364,43 +553,11 @@ impl From<Position> for Pos2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "palette")]
|
||||
pub enum KomobarTheme {
|
||||
/// A theme from catppuccin-egui
|
||||
Catppuccin {
|
||||
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
|
||||
name: komorebi_themes::Catppuccin,
|
||||
accent: Option<komorebi_themes::CatppuccinValue>,
|
||||
},
|
||||
/// A theme from base16-egui-themes
|
||||
Base16 {
|
||||
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
|
||||
name: komorebi_themes::Base16,
|
||||
accent: Option<komorebi_themes::Base16Value>,
|
||||
},
|
||||
}
|
||||
pub use komorebi_themes::KomobarTheme;
|
||||
|
||||
impl From<KomorebiTheme> for KomobarTheme {
|
||||
fn from(value: KomorebiTheme) -> Self {
|
||||
match value {
|
||||
KomorebiTheme::Catppuccin {
|
||||
name, bar_accent, ..
|
||||
} => Self::Catppuccin {
|
||||
name,
|
||||
accent: bar_accent,
|
||||
},
|
||||
KomorebiTheme::Base16 {
|
||||
name, bar_accent, ..
|
||||
} => Self::Base16 {
|
||||
name,
|
||||
accent: bar_accent,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Label prefix
|
||||
pub enum LabelPrefix {
|
||||
/// Show no prefix
|
||||
None,
|
||||
@@ -412,7 +569,9 @@ pub enum LabelPrefix {
|
||||
IconAndText,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Display format
|
||||
pub enum DisplayFormat {
|
||||
/// Show only icon
|
||||
Icon,
|
||||
@@ -427,8 +586,10 @@ pub enum DisplayFormat {
|
||||
}
|
||||
|
||||
macro_rules! extend_enum {
|
||||
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq)]
|
||||
($(#[$type_meta:meta])* $existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
$(#[$type_meta])*
|
||||
pub enum $new_enum {
|
||||
// Add new variants
|
||||
$(
|
||||
@@ -449,7 +610,9 @@ macro_rules! extend_enum {
|
||||
};
|
||||
}
|
||||
|
||||
extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
|
||||
extend_enum!(
|
||||
/// Workspaces display format
|
||||
DisplayFormat, WorkspacesDisplayFormat, {
|
||||
/// Show all icons only
|
||||
AllIcons,
|
||||
/// Show both all icons and text
|
||||
@@ -460,12 +623,12 @@ extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum OriginalDisplayFormat {
|
||||
/// Show None Of The Things
|
||||
NoneOfTheThings,
|
||||
|
||||
@@ -1,821 +0,0 @@
|
||||
use crate::bar::apply_theme;
|
||||
use crate::config::DisplayFormat;
|
||||
use crate::config::KomobarTheme;
|
||||
use crate::config::WorkspacesDisplayFormat;
|
||||
use crate::komorebi_layout::KomorebiLayout;
|
||||
use crate::render::Grouping;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::ui::CustomUi;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::ICON_CACHE;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_INDEX;
|
||||
use eframe::egui::vec2;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::ColorImage;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::Frame;
|
||||
use eframe::egui::Image;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::Margin;
|
||||
use eframe::egui::RichText;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::TextureHandle;
|
||||
use eframe::egui::TextureOptions;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use image::RgbaImage;
|
||||
use komorebi_client::Container;
|
||||
use komorebi_client::NotificationEvent;
|
||||
use komorebi_client::PathExt;
|
||||
use komorebi_client::Rect;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_client::Window;
|
||||
use komorebi_client::Workspace;
|
||||
use komorebi_client::WorkspaceLayer;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiConfig {
|
||||
/// Configure the Workspaces widget
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
/// Configure the Layout widget
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
/// Configure the Workspace Layer widget
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
/// Configure the Focused Window widget
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
/// Configure the Configuration Switcher widget
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiWorkspacesConfig {
|
||||
/// Enable the Komorebi Workspaces widget
|
||||
pub enable: bool,
|
||||
/// Hide workspaces without any windows
|
||||
pub hide_empty_workspaces: bool,
|
||||
/// Display format of the workspace
|
||||
pub display: Option<WorkspacesDisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiLayoutConfig {
|
||||
/// Enable the Komorebi Layout widget
|
||||
pub enable: bool,
|
||||
/// List of layout options
|
||||
pub options: Option<Vec<KomorebiLayout>>,
|
||||
/// Display format of the current layout
|
||||
pub display: Option<DisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiWorkspaceLayerConfig {
|
||||
/// Enable the Komorebi Workspace Layer widget
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiFocusedWindowConfig {
|
||||
/// Enable the Komorebi Focused Window widget
|
||||
pub enable: bool,
|
||||
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused window)
|
||||
pub show_icon: Option<bool>,
|
||||
/// Display format of the currently focused window
|
||||
pub display: Option<DisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiConfigurationSwitcherConfig {
|
||||
/// Enable the Komorebi Configurations widget
|
||||
pub enable: bool,
|
||||
/// A map of display friendly name => path to configuration.json
|
||||
pub configurations: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl From<&KomorebiConfig> for Komorebi {
|
||||
fn from(value: &KomorebiConfig) -> Self {
|
||||
let configuration_switcher =
|
||||
if let Some(configuration_switcher) = &value.configuration_switcher {
|
||||
let mut configuration_switcher = configuration_switcher.clone();
|
||||
for (_, location) in configuration_switcher.configurations.iter_mut() {
|
||||
*location = dunce::simplified(&PathBuf::from(location.clone()).replace_env())
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
}
|
||||
Some(configuration_switcher)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
komorebi_notification_state: Rc::new(RefCell::new(KomorebiNotificationState {
|
||||
selected_workspace: String::new(),
|
||||
layout: KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
|
||||
workspaces: vec![],
|
||||
hide_empty_workspaces: value
|
||||
.workspaces
|
||||
.map(|w| w.hide_empty_workspaces)
|
||||
.unwrap_or_default(),
|
||||
mouse_follows_focus: true,
|
||||
work_area_offset: None,
|
||||
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
|
||||
stack_accent: None,
|
||||
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
|
||||
monitor_usr_idx_map: HashMap::new(),
|
||||
})),
|
||||
workspaces: value.workspaces,
|
||||
layout: value.layout.clone(),
|
||||
focused_window: value.focused_window,
|
||||
workspace_layer: value.workspace_layer,
|
||||
configuration_switcher,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Komorebi {
|
||||
pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>,
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
|
||||
impl BarWidget for Komorebi {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
|
||||
let icon_size = Vec2::splat(config.icon_font_id.size);
|
||||
let text_size = Vec2::splat(config.text_font_id.size);
|
||||
|
||||
if let Some(workspaces) = self.workspaces {
|
||||
if workspaces.enable {
|
||||
let mut update = None;
|
||||
|
||||
if !komorebi_notification_state.workspaces.is_empty() {
|
||||
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
for (i, (ws, containers, _)) in
|
||||
komorebi_notification_state.workspaces.iter().enumerate()
|
||||
{
|
||||
let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
|
||||
|
||||
if SelectableFrame::new(
|
||||
is_selected,
|
||||
)
|
||||
.show(ui, |ui| {
|
||||
let mut has_icon = false;
|
||||
|
||||
if format == WorkspacesDisplayFormat::AllIcons
|
||||
|| format == WorkspacesDisplayFormat::AllIconsAndText
|
||||
|| format == WorkspacesDisplayFormat::AllIconsAndTextOnSelected
|
||||
|| format == DisplayFormat::Icon.into()
|
||||
|| format == DisplayFormat::IconAndText.into()
|
||||
|| format == DisplayFormat::IconAndTextOnSelected.into()
|
||||
|| (format == DisplayFormat::TextAndIconOnSelected.into() && is_selected)
|
||||
{
|
||||
has_icon = containers.iter().any(|(_, container_info)| {
|
||||
container_info.icons.iter().any(|icon| icon.is_some())
|
||||
});
|
||||
|
||||
if has_icon {
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(
|
||||
ui.style().spacing.button_padding.y as i8,
|
||||
))
|
||||
.show(ui, |ui| {
|
||||
for (is_focused, container) in containers {
|
||||
for icon in container.icons.iter().flatten().collect::<Vec<_>>() {
|
||||
ui.add(
|
||||
Image::from(&img_to_texture(ctx, icon))
|
||||
.maintain_aspect_ratio(true)
|
||||
.fit_to_exact_size(if *is_focused { icon_size } else { text_size }),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// draw a custom icon when there is no app icon or text
|
||||
if !has_icon && (matches!(format, WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon))
|
||||
|| (!is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))) {
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(icon_size, Sense::hover());
|
||||
let stroke = Stroke::new(
|
||||
1.0,
|
||||
if is_selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() },
|
||||
);
|
||||
let mut rect = response.rect;
|
||||
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
|
||||
rect = rect.shrink(stroke.width);
|
||||
let c = rect.center();
|
||||
let r = rect.width() / 2.0;
|
||||
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
|
||||
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
|
||||
|
||||
response.on_hover_text(ws.to_string())
|
||||
// add hover text when there are only icons
|
||||
} else if match format {
|
||||
WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon) => has_icon,
|
||||
_ => false,
|
||||
} {
|
||||
ui.response().on_hover_text(ws.to_string())
|
||||
// add label only
|
||||
} else if (format != WorkspacesDisplayFormat::AllIconsAndTextOnSelected && format != DisplayFormat::IconAndTextOnSelected.into())
|
||||
|| (is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))
|
||||
{
|
||||
if is_selected {
|
||||
ui.add(Label::new(RichText::new(ws.to_string()).color(ctx.style().visuals.selection.stroke.color)).selectable(false))
|
||||
}
|
||||
else {
|
||||
ui.add(Label::new(ws.to_string()).selectable(false))
|
||||
}
|
||||
} else {
|
||||
ui.response()
|
||||
}
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
update = Some(ws.to_string());
|
||||
|
||||
if komorebi_notification_state.mouse_follows_focus {
|
||||
if komorebi_client::send_batch([
|
||||
SocketMessage::MouseFollowsFocus(false),
|
||||
SocketMessage::FocusMonitorWorkspaceNumber(
|
||||
komorebi_notification_state.monitor_index,
|
||||
i,
|
||||
),
|
||||
SocketMessage::RetileWithResizeDimensions,
|
||||
SocketMessage::MouseFollowsFocus(true),
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!(
|
||||
"could not send the following batch of messages to komorebi:\n
|
||||
MouseFollowsFocus(false)\n
|
||||
FocusMonitorWorkspaceNumber({}, {})\n
|
||||
RetileWithResizeDimensions
|
||||
MouseFollowsFocus(true)\n",
|
||||
komorebi_notification_state.monitor_index,
|
||||
i,
|
||||
);
|
||||
}
|
||||
} else if komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorWorkspaceNumber(
|
||||
komorebi_notification_state.monitor_index,
|
||||
i,
|
||||
),
|
||||
SocketMessage::RetileWithResizeDimensions,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!(
|
||||
"could not send the following batch of messages to komorebi:\n
|
||||
FocusMonitorWorkspaceNumber({}, {})\n
|
||||
RetileWithResizeDimensions",
|
||||
komorebi_notification_state.monitor_index,
|
||||
i,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(update) = update {
|
||||
komorebi_notification_state.selected_workspace = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(layer_config) = &self.workspace_layer {
|
||||
if layer_config.enable {
|
||||
let layer = komorebi_notification_state
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
|
||||
.map(|(_, _, layer)| layer);
|
||||
|
||||
if let Some(layer) = layer {
|
||||
let name = layer.to_string();
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(name).selectable(false)))
|
||||
.clicked()
|
||||
&& komorebi_client::send_batch([
|
||||
SocketMessage::MouseFollowsFocus(false),
|
||||
SocketMessage::ToggleWorkspaceLayer,
|
||||
SocketMessage::MouseFollowsFocus(
|
||||
komorebi_notification_state.mouse_follows_focus,
|
||||
),
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!(
|
||||
"could not send the following batch of messages to komorebi:\n\
|
||||
MouseFollowsFocus(false),
|
||||
ToggleWorkspaceLayer,
|
||||
MouseFollowsFocus({})",
|
||||
komorebi_notification_state.mouse_follows_focus,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(layout_config) = &self.layout {
|
||||
if layout_config.enable {
|
||||
let workspace_idx: Option<usize> = komorebi_notification_state
|
||||
.workspaces
|
||||
.iter()
|
||||
.position(|o| komorebi_notification_state.selected_workspace.eq(&o.0));
|
||||
|
||||
komorebi_notification_state.layout.show(
|
||||
ctx,
|
||||
ui,
|
||||
config,
|
||||
layout_config,
|
||||
workspace_idx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(configuration_switcher) = &self.configuration_switcher {
|
||||
if configuration_switcher.enable {
|
||||
for (name, location) in configuration_switcher.configurations.iter() {
|
||||
let path = PathBuf::from(location);
|
||||
if path.is_file() {
|
||||
config.apply_on_widget(false, ui,|ui|{
|
||||
if SelectableFrame::new(false).show(ui, |ui|{
|
||||
ui.add(Label::new(name).selectable(false))
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
|
||||
let mut proceed = true;
|
||||
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
|
||||
canonicalized,
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!(
|
||||
"could not send message to komorebi: ReplaceConfiguration"
|
||||
);
|
||||
proceed = false;
|
||||
}
|
||||
|
||||
if let Some(rect) = komorebi_notification_state.work_area_offset {
|
||||
if proceed {
|
||||
match komorebi_client::send_query(&SocketMessage::Query(
|
||||
komorebi_client::StateQuery::FocusedMonitorIndex,
|
||||
)) {
|
||||
Ok(idx) => {
|
||||
if let Ok(monitor_idx) = idx.parse::<usize>() {
|
||||
if komorebi_client::send_message(
|
||||
&SocketMessage::MonitorWorkAreaOffset(
|
||||
monitor_idx,
|
||||
rect,
|
||||
),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!(
|
||||
"could not send message to komorebi: MonitorWorkAreaOffset"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::error!(
|
||||
"could not send message to komorebi: Query"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(focused_window) = self.focused_window {
|
||||
if focused_window.enable {
|
||||
let titles = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.titles;
|
||||
if !titles.is_empty() {
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
let icons = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.icons;
|
||||
let focused_window_idx = komorebi_notification_state
|
||||
.focused_container_information
|
||||
.focused_window_idx;
|
||||
|
||||
let iter = titles.iter().zip(icons.iter());
|
||||
let len = iter.len();
|
||||
|
||||
for (i, (title, icon)) in iter.enumerate() {
|
||||
let selected = i == focused_window_idx && len != 1;
|
||||
let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() };
|
||||
|
||||
if SelectableFrame::new(selected)
|
||||
.show(ui, |ui| {
|
||||
// handle legacy setting
|
||||
let format = focused_window.display.unwrap_or(
|
||||
if focused_window.show_icon.unwrap_or(false) {
|
||||
DisplayFormat::IconAndText
|
||||
} else {
|
||||
DisplayFormat::Text
|
||||
},
|
||||
);
|
||||
|
||||
if format == DisplayFormat::Icon
|
||||
|| format == DisplayFormat::IconAndText
|
||||
|| format == DisplayFormat::IconAndTextOnSelected
|
||||
|| (format == DisplayFormat::TextAndIconOnSelected
|
||||
&& i == focused_window_idx)
|
||||
{
|
||||
if let Some(img) = icon {
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(
|
||||
ui.style().spacing.button_padding.y as i8,
|
||||
))
|
||||
.show(ui, |ui| {
|
||||
let response = ui.add(
|
||||
Image::from(&img_to_texture(ctx, img))
|
||||
.maintain_aspect_ratio(true)
|
||||
.fit_to_exact_size(icon_size),
|
||||
);
|
||||
|
||||
if let DisplayFormat::Icon = format {
|
||||
response.on_hover_text(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if format == DisplayFormat::Text
|
||||
|| format == DisplayFormat::IconAndText
|
||||
|| format == DisplayFormat::TextAndIconOnSelected
|
||||
|| (format == DisplayFormat::IconAndTextOnSelected
|
||||
&& i == focused_window_idx)
|
||||
{
|
||||
let available_height = ui.available_height();
|
||||
let mut custom_ui = CustomUi(ui);
|
||||
|
||||
custom_ui.add_sized_left_to_right(
|
||||
Vec2::new(
|
||||
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
|
||||
available_height,
|
||||
),
|
||||
Label::new(RichText::new( title).color(text_color)).selectable(false).truncate(),
|
||||
);
|
||||
}
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
if selected {
|
||||
return;
|
||||
}
|
||||
|
||||
if komorebi_notification_state.mouse_follows_focus {
|
||||
if komorebi_client::send_batch([
|
||||
SocketMessage::MouseFollowsFocus(false),
|
||||
SocketMessage::FocusStackWindow(i),
|
||||
SocketMessage::MouseFollowsFocus(true),
|
||||
]).is_err() {
|
||||
tracing::error!(
|
||||
"could not send the following batch of messages to komorebi:\n
|
||||
MouseFollowsFocus(false)\n
|
||||
FocusStackWindow({})\n
|
||||
MouseFollowsFocus(true)\n",
|
||||
i,
|
||||
);
|
||||
}
|
||||
} else if komorebi_client::send_message(
|
||||
&SocketMessage::FocusStackWindow(i)
|
||||
).is_err() {
|
||||
tracing::error!(
|
||||
"could not send message to komorebi: FocusStackWindow"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
|
||||
let size = [rgba_image.width() as usize, rgba_image.height() as usize];
|
||||
let pixels = rgba_image.as_flat_samples();
|
||||
let color_image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
|
||||
ctx.load_texture("icon", color_image, TextureOptions::default())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KomorebiNotificationState {
|
||||
pub workspaces: Vec<(
|
||||
String,
|
||||
Vec<(bool, KomorebiNotificationStateContainerInformation)>,
|
||||
WorkspaceLayer,
|
||||
)>,
|
||||
pub selected_workspace: String,
|
||||
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
||||
pub layout: KomorebiLayout,
|
||||
pub hide_empty_workspaces: bool,
|
||||
pub mouse_follows_focus: bool,
|
||||
pub work_area_offset: Option<Rect>,
|
||||
pub stack_accent: Option<Color32>,
|
||||
pub monitor_index: usize,
|
||||
pub monitor_usr_idx_map: HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
impl KomorebiNotificationState {
|
||||
pub fn update_from_config(&mut self, config: &Self) {
|
||||
self.hide_empty_workspaces = config.hide_empty_workspaces;
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn handle_notification(
|
||||
&mut self,
|
||||
ctx: &Context,
|
||||
monitor_index: Option<usize>,
|
||||
notification: komorebi_client::Notification,
|
||||
bg_color: Rc<RefCell<Color32>>,
|
||||
bg_color_with_alpha: Rc<RefCell<Color32>>,
|
||||
transparency_alpha: Option<u8>,
|
||||
grouping: Option<Grouping>,
|
||||
default_theme: Option<KomobarTheme>,
|
||||
render_config: Rc<RefCell<RenderConfig>>,
|
||||
) {
|
||||
let show_all_icons = render_config.borrow().show_all_icons;
|
||||
|
||||
match notification.event {
|
||||
NotificationEvent::WindowManager(_) => {}
|
||||
NotificationEvent::Monitor(_) => {}
|
||||
NotificationEvent::Socket(message) => match message {
|
||||
SocketMessage::ReloadStaticConfiguration(path) => {
|
||||
if let Ok(config) = komorebi_client::StaticConfig::read(&path) {
|
||||
if let Some(theme) = config.theme {
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
bg_color.clone(),
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!("applied theme from updated komorebi.json");
|
||||
} else if let Some(default_theme) = default_theme {
|
||||
apply_theme(
|
||||
ctx,
|
||||
default_theme,
|
||||
bg_color.clone(),
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!("removed theme from updated komorebi.json and applied default theme");
|
||||
} else {
|
||||
tracing::warn!("theme was removed from updated komorebi.json but there was no default theme to apply");
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::Theme(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
bg_color,
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!("applied theme from komorebi socket message");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
|
||||
self.monitor_usr_idx_map = notification.state.monitor_usr_idx_map.clone();
|
||||
|
||||
if monitor_index.is_none()
|
||||
|| monitor_index.is_some_and(|idx| idx >= notification.state.monitors.elements().len())
|
||||
{
|
||||
// The bar's monitor is diconnected, so the bar is disabled no need to check anything
|
||||
// any further otherwise we'll get `OutOfBounds` panics.
|
||||
return;
|
||||
}
|
||||
let monitor_index = monitor_index.expect("should have a monitor index");
|
||||
self.monitor_index = monitor_index;
|
||||
|
||||
self.mouse_follows_focus = notification.state.mouse_follows_focus;
|
||||
|
||||
let monitor = ¬ification.state.monitors.elements()[monitor_index];
|
||||
self.work_area_offset =
|
||||
notification.state.monitors.elements()[monitor_index].work_area_offset();
|
||||
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let mut workspaces = vec![];
|
||||
|
||||
self.selected_workspace = monitor.workspaces()[focused_workspace_idx]
|
||||
.name()
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| format!("{}", focused_workspace_idx + 1));
|
||||
|
||||
for (i, ws) in monitor.workspaces().iter().enumerate() {
|
||||
let should_show = if self.hide_empty_workspaces {
|
||||
focused_workspace_idx == i || !ws.is_empty()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if should_show {
|
||||
workspaces.push((
|
||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||
if show_all_icons {
|
||||
let mut containers = vec![];
|
||||
let mut has_monocle = false;
|
||||
|
||||
// add monocle container
|
||||
if let Some(container) = ws.monocle_container() {
|
||||
containers.push((true, container.into()));
|
||||
has_monocle = true;
|
||||
}
|
||||
|
||||
// add all tiled windows
|
||||
for (i, container) in ws.containers().iter().enumerate() {
|
||||
containers.push((
|
||||
!has_monocle && i == ws.focused_container_idx(),
|
||||
container.into(),
|
||||
));
|
||||
}
|
||||
|
||||
// add all floating windows
|
||||
for floating_window in ws.floating_windows() {
|
||||
containers.push((
|
||||
!has_monocle && floating_window.is_focused(),
|
||||
floating_window.into(),
|
||||
));
|
||||
}
|
||||
|
||||
containers
|
||||
} else {
|
||||
vec![(true, ws.into())]
|
||||
},
|
||||
ws.layer().to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
self.workspaces = workspaces;
|
||||
|
||||
if monitor.workspaces()[focused_workspace_idx]
|
||||
.monocle_container()
|
||||
.is_some()
|
||||
{
|
||||
self.layout = KomorebiLayout::Monocle;
|
||||
} else if !*monitor.workspaces()[focused_workspace_idx].tile() {
|
||||
self.layout = KomorebiLayout::Floating;
|
||||
} else if notification.state.is_paused {
|
||||
self.layout = KomorebiLayout::Paused;
|
||||
} else {
|
||||
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
|
||||
komorebi_client::Layout::Default(layout) => KomorebiLayout::Default(*layout),
|
||||
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
|
||||
};
|
||||
}
|
||||
|
||||
self.focused_container_information = (&monitor.workspaces()[focused_workspace_idx]).into();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KomorebiNotificationStateContainerInformation {
|
||||
pub titles: Vec<String>,
|
||||
pub icons: Vec<Option<RgbaImage>>,
|
||||
pub focused_window_idx: usize,
|
||||
}
|
||||
|
||||
impl From<&Workspace> for KomorebiNotificationStateContainerInformation {
|
||||
fn from(value: &Workspace) -> Self {
|
||||
let mut container_info = Self::EMPTY;
|
||||
|
||||
if let Some(container) = value.monocle_container() {
|
||||
container_info = container.into();
|
||||
} else if let Some(container) = value.focused_container() {
|
||||
container_info = container.into();
|
||||
}
|
||||
|
||||
for floating_window in value.floating_windows() {
|
||||
if floating_window.is_focused() {
|
||||
container_info = floating_window.into();
|
||||
}
|
||||
}
|
||||
|
||||
container_info
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Container> for KomorebiNotificationStateContainerInformation {
|
||||
fn from(value: &Container) -> Self {
|
||||
let windows = value.windows().iter().collect::<Vec<_>>();
|
||||
let mut icons = vec![];
|
||||
|
||||
for window in windows {
|
||||
let mut icon_cache = ICON_CACHE.lock().unwrap();
|
||||
let mut update_cache = false;
|
||||
let exe = window.exe().unwrap_or_default();
|
||||
|
||||
match icon_cache.get(&exe) {
|
||||
None => {
|
||||
icons.push(windows_icons::get_icon_by_process_id(window.process_id()));
|
||||
update_cache = true;
|
||||
}
|
||||
Some(icon) => {
|
||||
icons.push(Some(icon.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if update_cache {
|
||||
if let Some(Some(icon)) = icons.last() {
|
||||
icon_cache.insert(exe, icon.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
titles: value
|
||||
.windows()
|
||||
.iter()
|
||||
.map(|w| w.title().unwrap_or_default())
|
||||
.collect::<Vec<_>>(),
|
||||
icons,
|
||||
focused_window_idx: value.focused_window_idx(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Window> for KomorebiNotificationStateContainerInformation {
|
||||
fn from(value: &Window) -> Self {
|
||||
let mut icon_cache = ICON_CACHE.lock().unwrap();
|
||||
let mut update_cache = false;
|
||||
let mut icons = vec![];
|
||||
let exe = value.exe().unwrap_or_default();
|
||||
|
||||
match icon_cache.get(&exe) {
|
||||
None => {
|
||||
icons.push(windows_icons::get_icon_by_process_id(value.process_id()));
|
||||
update_cache = true;
|
||||
}
|
||||
Some(icon) => {
|
||||
icons.push(Some(icon.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if update_cache {
|
||||
if let Some(Some(icon)) = icons.last() {
|
||||
icon_cache.insert(exe, icon.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
titles: vec![value.title().unwrap_or_default()],
|
||||
icons,
|
||||
focused_window_idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KomorebiNotificationStateContainerInformation {
|
||||
pub const EMPTY: Self = Self {
|
||||
titles: vec![],
|
||||
icons: vec![],
|
||||
focused_window_idx: 0,
|
||||
};
|
||||
}
|
||||
@@ -1,21 +1,9 @@
|
||||
mod bar;
|
||||
mod battery;
|
||||
mod config;
|
||||
mod cpu;
|
||||
mod date;
|
||||
mod keyboard;
|
||||
mod komorebi;
|
||||
mod komorebi_layout;
|
||||
mod media;
|
||||
mod memory;
|
||||
mod network;
|
||||
mod render;
|
||||
mod selected_frame;
|
||||
mod storage;
|
||||
mod time;
|
||||
mod ui;
|
||||
mod update;
|
||||
mod widget;
|
||||
mod widgets;
|
||||
|
||||
use crate::bar::Komobar;
|
||||
use crate::config::KomobarConfig;
|
||||
@@ -27,27 +15,25 @@ use eframe::egui::ViewportBuilder;
|
||||
use font_loader::system_fonts;
|
||||
use hotwatch::EventKind;
|
||||
use hotwatch::Hotwatch;
|
||||
use image::RgbaImage;
|
||||
use komorebi_client::PathExt;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_client::SubscribeOptions;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use std::collections::HashMap;
|
||||
use komorebi_client::replace_env_in_path;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
use windows_core::BOOL;
|
||||
@@ -60,8 +46,8 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
pub static BAR_HEIGHT: f32 = 50.0;
|
||||
pub static DEFAULT_PADDING: f32 = 10.0;
|
||||
|
||||
pub static ICON_CACHE: LazyLock<Mutex<HashMap<String, RgbaImage>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0);
|
||||
pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author, about, version)]
|
||||
@@ -74,6 +60,7 @@ struct Opts {
|
||||
fonts: bool,
|
||||
/// Path to a JSON or YAML configuration file
|
||||
#[clap(short, long)]
|
||||
#[clap(value_parser = replace_env_in_path)]
|
||||
config: Option<PathBuf>,
|
||||
/// Write an example komorebi.bar.json to disk
|
||||
#[clap(long)]
|
||||
@@ -116,7 +103,7 @@ fn process_hwnd() -> Option<isize> {
|
||||
}
|
||||
|
||||
pub enum KomorebiEvent {
|
||||
Notification(komorebi_client::Notification),
|
||||
Notification(Box<komorebi_client::Notification>),
|
||||
Reconnect,
|
||||
}
|
||||
|
||||
@@ -125,16 +112,10 @@ fn main() -> color_eyre::Result<()> {
|
||||
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
if opts.schema {
|
||||
let settings = SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = false;
|
||||
s.option_add_null_type = false;
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<KomobarConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
let bar_config = schemars::schema_for!(KomobarConfig);
|
||||
let schema = serde_json::to_string_pretty(&bar_config)?;
|
||||
|
||||
println!("{schema}");
|
||||
std::process::exit(0);
|
||||
@@ -149,13 +130,17 @@ fn main() -> color_eyre::Result<()> {
|
||||
}
|
||||
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
unsafe {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
}
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
unsafe {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
}
|
||||
|
||||
tracing::subscriber::set_global_default(
|
||||
@@ -167,13 +152,14 @@ fn main() -> color_eyre::Result<()> {
|
||||
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|
||||
|_| dirs::home_dir().expect("there is no home directory"),
|
||||
|home_path| {
|
||||
let home = PathBuf::from(&home_path);
|
||||
let home = home_path.replace_env();
|
||||
|
||||
if home.as_path().is_dir() {
|
||||
home
|
||||
} else {
|
||||
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
|
||||
}
|
||||
assert!(
|
||||
home.is_dir(),
|
||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory"
|
||||
);
|
||||
|
||||
home
|
||||
},
|
||||
);
|
||||
|
||||
@@ -182,7 +168,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
std::fs::write(home_dir.join("komorebi.bar.json"), komorebi_bar_json)?;
|
||||
println!(
|
||||
"Example komorebi.bar.json file written to {}",
|
||||
home_dir.as_path().display()
|
||||
home_dir.display()
|
||||
);
|
||||
|
||||
std::process::exit(0);
|
||||
@@ -190,16 +176,11 @@ fn main() -> color_eyre::Result<()> {
|
||||
|
||||
let default_config_path = home_dir.join("komorebi.bar.json");
|
||||
|
||||
let config_path = opts.config.map_or_else(
|
||||
|| {
|
||||
if !default_config_path.is_file() {
|
||||
None
|
||||
} else {
|
||||
Some(default_config_path.clone())
|
||||
}
|
||||
},
|
||||
Option::from,
|
||||
);
|
||||
let config_path = opts.config.or_else(|| {
|
||||
default_config_path
|
||||
.is_file()
|
||||
.then_some(default_config_path.clone())
|
||||
});
|
||||
|
||||
let mut config = match config_path {
|
||||
None => {
|
||||
@@ -209,17 +190,14 @@ fn main() -> color_eyre::Result<()> {
|
||||
std::fs::write(&default_config_path, komorebi_bar_json)?;
|
||||
tracing::info!(
|
||||
"created example configuration file: {}",
|
||||
default_config_path.as_path().display()
|
||||
default_config_path.display()
|
||||
);
|
||||
|
||||
KomobarConfig::read(&default_config_path)?
|
||||
}
|
||||
Some(ref config) => {
|
||||
if !opts.aliases {
|
||||
tracing::info!(
|
||||
"found configuration file: {}",
|
||||
config.as_path().to_string_lossy()
|
||||
);
|
||||
tracing::info!("found configuration file: {}", config.display());
|
||||
}
|
||||
|
||||
KomobarConfig::read(config)?
|
||||
@@ -238,28 +216,30 @@ fn main() -> color_eyre::Result<()> {
|
||||
)?)?;
|
||||
|
||||
let (usr_monitor_index, work_area_offset) = match &config.monitor {
|
||||
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
|
||||
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
|
||||
(monitor_config.index, monitor_config.work_area_offset)
|
||||
}
|
||||
MonitorConfigOrIndex::Index(idx) => (*idx, None),
|
||||
Some(MonitorConfigOrIndex::Index(idx)) => (*idx, None),
|
||||
None => (0, None),
|
||||
};
|
||||
|
||||
let monitor_index = state
|
||||
.monitor_usr_idx_map
|
||||
.get(&usr_monitor_index)
|
||||
.map_or(usr_monitor_index, |i| *i);
|
||||
|
||||
MONITOR_RIGHT.store(
|
||||
state.monitors.elements()[monitor_index].size().right,
|
||||
state.monitors.elements()[monitor_index].size.right,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
MONITOR_TOP.store(
|
||||
state.monitors.elements()[monitor_index].size().top,
|
||||
state.monitors.elements()[monitor_index].size.top,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
MONITOR_LEFT.store(
|
||||
state.monitors.elements()[monitor_index].size().left,
|
||||
state.monitors.elements()[monitor_index].size.left,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
@@ -269,11 +249,11 @@ fn main() -> color_eyre::Result<()> {
|
||||
None => {
|
||||
config.position = Some(PositionConfig {
|
||||
start: Some(Position {
|
||||
x: state.monitors.elements()[monitor_index].size().left as f32,
|
||||
y: state.monitors.elements()[monitor_index].size().top as f32,
|
||||
x: state.monitors.elements()[monitor_index].size.left as f32,
|
||||
y: state.monitors.elements()[monitor_index].size.top as f32,
|
||||
}),
|
||||
end: Some(Position {
|
||||
x: state.monitors.elements()[monitor_index].size().right as f32,
|
||||
x: state.monitors.elements()[monitor_index].size.right as f32,
|
||||
y: 50.0,
|
||||
}),
|
||||
})
|
||||
@@ -281,14 +261,14 @@ fn main() -> color_eyre::Result<()> {
|
||||
Some(ref mut position) => {
|
||||
if position.start.is_none() {
|
||||
position.start = Some(Position {
|
||||
x: state.monitors.elements()[monitor_index].size().left as f32,
|
||||
y: state.monitors.elements()[monitor_index].size().top as f32,
|
||||
x: state.monitors.elements()[monitor_index].size.left as f32,
|
||||
y: state.monitors.elements()[monitor_index].size.top as f32,
|
||||
});
|
||||
}
|
||||
|
||||
if position.end.is_none() {
|
||||
position.end = Some(Position {
|
||||
x: state.monitors.elements()[monitor_index].size().right as f32,
|
||||
x: state.monitors.elements()[monitor_index].size.right as f32,
|
||||
y: 50.0,
|
||||
})
|
||||
}
|
||||
@@ -319,10 +299,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
hotwatch.watch(config_path, move |event| match event.kind {
|
||||
EventKind::Modify(_) | EventKind::Remove(_) => match KomobarConfig::read(&config_path_cl) {
|
||||
Ok(updated) => {
|
||||
tracing::info!(
|
||||
"configuration file updated: {}",
|
||||
config_path_cl.as_path().to_string_lossy()
|
||||
);
|
||||
tracing::info!("configuration file updated: {}", config_path_cl.display());
|
||||
|
||||
if let Err(error) = tx_config.send(updated) {
|
||||
tracing::error!("could not send configuration update to gui: {error}")
|
||||
@@ -349,7 +326,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
|
||||
let ctx_komorebi = cc.egui_ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
|
||||
let subscriber_name = format!("komorebi-bar-{}", random_word::get(random_word::Lang::En));
|
||||
|
||||
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
|
||||
filter_state_changes: true,
|
||||
@@ -376,7 +353,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
while komorebi_client::send_message(
|
||||
&SocketMessage::AddSubscriberSocket(subscriber_name.clone()),
|
||||
)
|
||||
.is_err()
|
||||
.is_err()
|
||||
{
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
@@ -399,7 +376,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
Ok(notification) => {
|
||||
tracing::debug!("received notification from komorebi");
|
||||
|
||||
if let Err(error) = tx_gui.send(KomorebiEvent::Notification(notification)) {
|
||||
if let Err(error) = tx_gui.send(KomorebiEvent::Notification(Box::new(notification))) {
|
||||
tracing::error!("could not send komorebi notification update to gui thread: {error}")
|
||||
}
|
||||
|
||||
@@ -427,5 +404,5 @@ fn main() -> color_eyre::Result<()> {
|
||||
Ok(Box::new(Komobar::new(cc, rx_gui, rx_config, config)))
|
||||
}),
|
||||
)
|
||||
.map_err(|error| color_eyre::eyre::Error::msg(error.to_string()))
|
||||
.map_err(|error| color_eyre::eyre::Error::msg(error.to_string()))
|
||||
}
|
||||
|
||||
@@ -1,376 +0,0 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use num_derive::FromPrimitive;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::Networks;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NetworkConfig {
|
||||
/// Enable the Network widget
|
||||
pub enable: bool,
|
||||
/// Show total data transmitted
|
||||
pub show_total_data_transmitted: bool,
|
||||
/// Show network activity
|
||||
pub show_network_activity: bool,
|
||||
/// Show default interface
|
||||
pub show_default_interface: Option<bool>,
|
||||
/// Characters to reserve for network activity data
|
||||
pub network_activity_fill_characters: Option<usize>,
|
||||
/// Data refresh interval (default: 10 seconds)
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
}
|
||||
|
||||
impl From<NetworkConfig> for Network {
|
||||
fn from(value: NetworkConfig) -> Self {
|
||||
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
show_total_activity: value.show_total_data_transmitted,
|
||||
show_activity: value.show_network_activity,
|
||||
show_default_interface: value.show_default_interface.unwrap_or(true),
|
||||
networks_network_activity: Networks::new_with_refreshed_list(),
|
||||
default_interface: String::new(),
|
||||
data_refresh_interval,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
network_activity_fill_characters: value
|
||||
.network_activity_fill_characters
|
||||
.unwrap_or_default(),
|
||||
last_state_total_activity: vec![],
|
||||
last_state_activity: vec![],
|
||||
last_updated_network_activity: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Network {
|
||||
pub enable: bool,
|
||||
pub show_total_activity: bool,
|
||||
pub show_activity: bool,
|
||||
pub show_default_interface: bool,
|
||||
networks_network_activity: Networks,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
default_interface: String,
|
||||
last_state_total_activity: Vec<NetworkReading>,
|
||||
last_state_activity: Vec<NetworkReading>,
|
||||
last_updated_network_activity: Instant,
|
||||
network_activity_fill_characters: usize,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
fn default_interface(&mut self) {
|
||||
if let Ok(interface) = netdev::get_default_interface() {
|
||||
if let Some(friendly_name) = &interface.friendly_name {
|
||||
self.default_interface.clone_from(friendly_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
|
||||
let mut activity = self.last_state_activity.clone();
|
||||
let mut total_activity = self.last_state_total_activity.clone();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(self.last_updated_network_activity)
|
||||
> Duration::from_secs(self.data_refresh_interval)
|
||||
{
|
||||
activity.clear();
|
||||
total_activity.clear();
|
||||
|
||||
if let Ok(interface) = netdev::get_default_interface() {
|
||||
if let Some(friendly_name) = &interface.friendly_name {
|
||||
self.default_interface.clone_from(friendly_name);
|
||||
|
||||
self.networks_network_activity.refresh(true);
|
||||
|
||||
for (interface_name, data) in &self.networks_network_activity {
|
||||
if friendly_name.eq(interface_name) {
|
||||
if self.show_activity {
|
||||
activity.push(NetworkReading::new(
|
||||
NetworkReadingFormat::Speed,
|
||||
Self::to_pretty_bytes(
|
||||
data.received(),
|
||||
self.data_refresh_interval,
|
||||
),
|
||||
Self::to_pretty_bytes(
|
||||
data.transmitted(),
|
||||
self.data_refresh_interval,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if self.show_total_activity {
|
||||
total_activity.push(NetworkReading::new(
|
||||
NetworkReadingFormat::Total,
|
||||
Self::to_pretty_bytes(data.total_received(), 1),
|
||||
Self::to_pretty_bytes(data.total_transmitted(), 1),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.last_state_activity.clone_from(&activity);
|
||||
self.last_state_total_activity.clone_from(&total_activity);
|
||||
self.last_updated_network_activity = now;
|
||||
}
|
||||
|
||||
(activity, total_activity)
|
||||
}
|
||||
|
||||
fn reading_to_label(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
reading: NetworkReading,
|
||||
config: RenderConfig,
|
||||
) -> Label {
|
||||
let (text_down, text_up) = match self.label_prefix {
|
||||
LabelPrefix::None | LabelPrefix::Icon => match reading.format {
|
||||
NetworkReadingFormat::Speed => (
|
||||
format!(
|
||||
"{: >width$}/s ",
|
||||
reading.received_text,
|
||||
width = self.network_activity_fill_characters
|
||||
),
|
||||
format!(
|
||||
"{: >width$}/s",
|
||||
reading.transmitted_text,
|
||||
width = self.network_activity_fill_characters
|
||||
),
|
||||
),
|
||||
NetworkReadingFormat::Total => (
|
||||
format!("{} ", reading.received_text),
|
||||
reading.transmitted_text,
|
||||
),
|
||||
},
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {
|
||||
NetworkReadingFormat::Speed => (
|
||||
format!(
|
||||
"DOWN: {: >width$}/s ",
|
||||
reading.received_text,
|
||||
width = self.network_activity_fill_characters
|
||||
),
|
||||
format!(
|
||||
"UP: {: >width$}/s",
|
||||
reading.transmitted_text,
|
||||
width = self.network_activity_fill_characters
|
||||
),
|
||||
),
|
||||
NetworkReadingFormat::Total => (
|
||||
format!("\u{2211}DOWN: {}/s ", reading.received_text),
|
||||
format!("\u{2211}UP: {}/s", reading.transmitted_text),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
let icon_format = TextFormat::simple(
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
);
|
||||
let text_format = TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// icon
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::ARROW_FAT_DOWN.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
icon_format.font_id.clone(),
|
||||
icon_format.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
// text
|
||||
layout_job.append(
|
||||
&text_down,
|
||||
ctx.style().spacing.item_spacing.x,
|
||||
text_format.clone(),
|
||||
);
|
||||
|
||||
// icon
|
||||
layout_job.append(
|
||||
&match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::ARROW_FAT_UP.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
0.0,
|
||||
icon_format.clone(),
|
||||
);
|
||||
|
||||
// text
|
||||
layout_job.append(
|
||||
&text_up,
|
||||
ctx.style().spacing.item_spacing.x,
|
||||
text_format.clone(),
|
||||
);
|
||||
|
||||
Label::new(layout_job).selectable(false)
|
||||
}
|
||||
|
||||
fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> String {
|
||||
let input = input_in_bytes as f32 / timespan_in_s as f32;
|
||||
let mut magnitude = input.log(1024f32) as u32;
|
||||
|
||||
// let the base unit be KiB
|
||||
if magnitude < 1 {
|
||||
magnitude = 1;
|
||||
}
|
||||
|
||||
let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);
|
||||
let result = input / ((1u64) << (magnitude * 10)) as f32;
|
||||
|
||||
match base {
|
||||
Some(DataUnit::B) => format!("{result:.1} B"),
|
||||
Some(unit) => format!("{result:.1} {unit}iB"),
|
||||
None => String::from("Unknown data unit"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Network {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
// widget spacing: make sure to use the same config to call the apply_on_widget function
|
||||
let mut render_config = config.clone();
|
||||
|
||||
if self.show_total_activity || self.show_activity {
|
||||
let (activity, total_activity) = self.network_activity();
|
||||
|
||||
if self.show_total_activity {
|
||||
for reading in total_activity {
|
||||
render_config.apply_on_widget(true, ui, |ui| {
|
||||
ui.add(self.reading_to_label(ctx, reading, config.clone()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_activity {
|
||||
for reading in activity {
|
||||
render_config.apply_on_widget(true, ui, |ui| {
|
||||
ui.add(self.reading_to_label(ctx, reading, config.clone()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_default_interface {
|
||||
self.default_interface();
|
||||
|
||||
if !self.default_interface.is_empty() {
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::WIFI_HIGH.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
||||
self.default_interface.insert_str(0, "NET: ");
|
||||
}
|
||||
|
||||
layout_job.append(
|
||||
&self.default_interface,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
render_config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
{
|
||||
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// widget spacing: pass on the config that was use for calling the apply_on_widget function
|
||||
*config = render_config.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum NetworkReadingFormat {
|
||||
Speed = 0,
|
||||
Total = 1,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NetworkReading {
|
||||
pub format: NetworkReadingFormat,
|
||||
pub received_text: String,
|
||||
pub transmitted_text: String,
|
||||
}
|
||||
|
||||
impl NetworkReading {
|
||||
pub fn new(format: NetworkReadingFormat, received: String, transmitted: String) -> Self {
|
||||
NetworkReading {
|
||||
format,
|
||||
received_text: received,
|
||||
transmitted_text: transmitted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromPrimitive)]
|
||||
enum DataUnit {
|
||||
B = 0,
|
||||
K = 1,
|
||||
M = 2,
|
||||
G = 3,
|
||||
T = 4,
|
||||
P = 5,
|
||||
E = 6,
|
||||
Z = 7,
|
||||
Y = 8,
|
||||
}
|
||||
|
||||
impl fmt::Display for DataUnit {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::AUTO_SELECT_FILL_COLOUR;
|
||||
use crate::AUTO_SELECT_TEXT_COLOUR;
|
||||
use crate::bar::Alignment;
|
||||
use crate::config::KomobarConfig;
|
||||
use crate::config::MonitorConfigOrIndex;
|
||||
@@ -11,29 +13,38 @@ use eframe::egui::Margin;
|
||||
use eframe::egui::Shadow;
|
||||
use eframe::egui::TextStyle;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use komorebi_client::Colour;
|
||||
use komorebi_client::Rgb;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "kind")]
|
||||
/// Grouping
|
||||
pub enum Grouping {
|
||||
/// No grouping is applied
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "None"))]
|
||||
None,
|
||||
/// Widgets are grouped as a whole
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Bar"))]
|
||||
Bar(GroupingConfig),
|
||||
/// Widgets are grouped by alignment
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Alignment"))]
|
||||
Alignment(GroupingConfig),
|
||||
/// Widgets are grouped individually
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Widget"))]
|
||||
Widget(GroupingConfig),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Render configuration
|
||||
pub struct RenderConfig {
|
||||
/// Komorebi monitor index of the monitor on which to render the bar
|
||||
pub monitor_idx: usize,
|
||||
@@ -55,6 +66,10 @@ pub struct RenderConfig {
|
||||
pub icon_font_id: FontId,
|
||||
/// Show all icons on the workspace section of the Komorebi widget
|
||||
pub show_all_icons: bool,
|
||||
/// Background color of the selected frame
|
||||
pub auto_select_fill: Option<Color32>,
|
||||
/// Text color of the selected frame
|
||||
pub auto_select_text: Option<Color32>,
|
||||
}
|
||||
|
||||
pub trait RenderExt {
|
||||
@@ -84,8 +99,9 @@ impl RenderExt for &KomobarConfig {
|
||||
icon_font_id.size *= icon_scale.unwrap_or(1.4).clamp(1.0, 2.0);
|
||||
|
||||
let monitor_idx = match &self.monitor {
|
||||
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
|
||||
MonitorConfigOrIndex::Index(idx) => *idx,
|
||||
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => monitor_config.index,
|
||||
Some(MonitorConfigOrIndex::Index(idx)) => *idx,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// check if any of the alignments have a komorebi widget with the workspace set to show all icons
|
||||
@@ -108,6 +124,10 @@ impl RenderExt for &KomobarConfig {
|
||||
text_font_id,
|
||||
icon_font_id,
|
||||
show_all_icons,
|
||||
auto_select_fill: NonZeroU32::new(AUTO_SELECT_FILL_COLOUR.load(Ordering::SeqCst))
|
||||
.map(|c| Colour::Rgb(Rgb::from(c.get())).into()),
|
||||
auto_select_text: NonZeroU32::new(AUTO_SELECT_TEXT_COLOUR.load(Ordering::SeqCst))
|
||||
.map(|c| Colour::Rgb(Rgb::from(c.get())).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +153,8 @@ impl RenderConfig {
|
||||
text_font_id: FontId::default(),
|
||||
icon_font_id: FontId::default(),
|
||||
show_all_icons: false,
|
||||
auto_select_fill: None,
|
||||
auto_select_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +361,9 @@ impl RenderConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Grouping configuration
|
||||
pub struct GroupingConfig {
|
||||
/// Styles for the grouping
|
||||
pub style: Option<GroupingStyle>,
|
||||
@@ -349,8 +373,11 @@ pub struct GroupingConfig {
|
||||
pub rounding: Option<RoundingConfig>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Grouping Style
|
||||
pub enum GroupingStyle {
|
||||
/// Default
|
||||
#[serde(alias = "CtByte")]
|
||||
Default,
|
||||
/// A shadow is added under the default group. (blur: 4, offset: x-1 y-1, spread: 3)
|
||||
@@ -369,10 +396,12 @@ pub enum GroupingStyle {
|
||||
DefaultWithGlowB0O1S2,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
/// Rounding configuration
|
||||
pub enum RoundingConfig {
|
||||
/// All 4 corners are the same
|
||||
/// All 4 corners are the same
|
||||
Same(f32),
|
||||
/// All 4 corners are custom. Order: NW, NE, SW, SE
|
||||
Individual([f32; 4]),
|
||||
|
||||
@@ -1,48 +1,81 @@
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::CursorIcon;
|
||||
use eframe::egui::Frame;
|
||||
use eframe::egui::Margin;
|
||||
use eframe::egui::Response;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::Ui;
|
||||
|
||||
/// Same as SelectableLabel, but supports all content
|
||||
pub struct SelectableFrame {
|
||||
selected: bool,
|
||||
selected_fill: Option<Color32>,
|
||||
}
|
||||
|
||||
impl SelectableFrame {
|
||||
pub fn new(selected: bool) -> Self {
|
||||
Self { selected }
|
||||
Self {
|
||||
selected,
|
||||
selected_fill: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_auto(selected: bool, selected_fill: Option<Color32>) -> Self {
|
||||
Self {
|
||||
selected,
|
||||
selected_fill,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
|
||||
let Self { selected } = self;
|
||||
let Self {
|
||||
selected,
|
||||
selected_fill,
|
||||
} = self;
|
||||
|
||||
Frame::NONE
|
||||
.show(ui, |ui| {
|
||||
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
|
||||
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
// take into account the stroke width
|
||||
let inner_margin = Margin::symmetric(
|
||||
ui.style().spacing.button_padding.x as i8,
|
||||
ui.style().spacing.button_padding.y as i8,
|
||||
ui.style().spacing.button_padding.x as i8 - 1,
|
||||
ui.style().spacing.button_padding.y as i8 - 1,
|
||||
);
|
||||
|
||||
if selected
|
||||
|| response.hovered()
|
||||
|| response.highlighted()
|
||||
|| response.has_focus()
|
||||
{
|
||||
// since the stroke is drawn inside the frame, we always reserve space for it
|
||||
if selected && response.hovered() {
|
||||
let visuals = ui.style().interact_selectable(&response, selected);
|
||||
|
||||
Frame::NONE
|
||||
.stroke(visuals.bg_stroke)
|
||||
.stroke(Stroke::new(1.0, visuals.bg_stroke.color))
|
||||
.corner_radius(visuals.corner_radius)
|
||||
.fill(selected_fill.unwrap_or(visuals.bg_fill))
|
||||
.inner_margin(inner_margin)
|
||||
.show(ui, add_contents);
|
||||
} else if response.hovered() || response.highlighted() || response.has_focus() {
|
||||
let visuals = ui.style().interact_selectable(&response, selected);
|
||||
|
||||
Frame::NONE
|
||||
.stroke(Stroke::new(1.0, visuals.bg_stroke.color))
|
||||
.corner_radius(visuals.corner_radius)
|
||||
.fill(visuals.bg_fill)
|
||||
.inner_margin(inner_margin)
|
||||
.show(ui, add_contents);
|
||||
} else if selected {
|
||||
let visuals = ui.style().interact_selectable(&response, selected);
|
||||
|
||||
Frame::NONE
|
||||
.stroke(Stroke::new(1.0, visuals.bg_fill))
|
||||
.corner_radius(visuals.corner_radius)
|
||||
.fill(selected_fill.unwrap_or(visuals.bg_fill))
|
||||
.inner_margin(inner_margin)
|
||||
.show(ui, add_contents);
|
||||
} else {
|
||||
Frame::NONE
|
||||
.stroke(Stroke::new(1.0, Color32::TRANSPARENT))
|
||||
.inner_margin(inner_margin)
|
||||
.show(ui, add_contents);
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::Disks;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct StorageConfig {
|
||||
/// Enable the Storage widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval (default: 10 seconds)
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
}
|
||||
|
||||
impl From<StorageConfig> for Storage {
|
||||
fn from(value: StorageConfig) -> Self {
|
||||
Self {
|
||||
enable: value.enable,
|
||||
disks: Disks::new_with_refreshed_list(),
|
||||
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||
last_updated: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Storage {
|
||||
pub enable: bool,
|
||||
disks: Disks,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
fn output(&mut self) -> Vec<String> {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
self.disks.refresh(true);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
let mut disks = vec![];
|
||||
|
||||
for disk in &self.disks {
|
||||
let mount = disk.mount_point();
|
||||
let total = disk.total_space();
|
||||
let available = disk.available_space();
|
||||
let used = total - available;
|
||||
|
||||
disks.push(match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("{} {}%", mount.to_string_lossy(), (used * 100) / total)
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total),
|
||||
})
|
||||
}
|
||||
|
||||
disks.sort();
|
||||
disks.reverse();
|
||||
|
||||
disks
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Storage {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
for output in self.output() {
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::HARD_DRIVES.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
{
|
||||
if let Err(error) = Command::new("cmd.exe")
|
||||
.args([
|
||||
"/C",
|
||||
"explorer.exe",
|
||||
output.split(' ').collect::<Vec<&str>>()[0],
|
||||
])
|
||||
.spawn()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use crate::battery::Battery;
|
||||
use crate::battery::BatteryConfig;
|
||||
use crate::cpu::Cpu;
|
||||
use crate::cpu::CpuConfig;
|
||||
use crate::date::Date;
|
||||
use crate::date::DateConfig;
|
||||
use crate::keyboard::Keyboard;
|
||||
use crate::keyboard::KeyboardConfig;
|
||||
use crate::komorebi::Komorebi;
|
||||
use crate::komorebi::KomorebiConfig;
|
||||
use crate::media::Media;
|
||||
use crate::media::MediaConfig;
|
||||
use crate::memory::Memory;
|
||||
use crate::memory::MemoryConfig;
|
||||
use crate::network::Network;
|
||||
use crate::network::NetworkConfig;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::storage::Storage;
|
||||
use crate::storage::StorageConfig;
|
||||
use crate::time::Time;
|
||||
use crate::time::TimeConfig;
|
||||
use crate::update::Update;
|
||||
use crate::update::UpdateConfig;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub trait BarWidget {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum WidgetConfig {
|
||||
Battery(BatteryConfig),
|
||||
Cpu(CpuConfig),
|
||||
Date(DateConfig),
|
||||
Keyboard(KeyboardConfig),
|
||||
Komorebi(KomorebiConfig),
|
||||
Media(MediaConfig),
|
||||
Memory(MemoryConfig),
|
||||
Network(NetworkConfig),
|
||||
Storage(StorageConfig),
|
||||
Time(TimeConfig),
|
||||
Update(UpdateConfig),
|
||||
}
|
||||
|
||||
impl WidgetConfig {
|
||||
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
|
||||
match self {
|
||||
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
|
||||
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
|
||||
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
|
||||
WidgetConfig::Keyboard(config) => Box::new(Keyboard::from(*config)),
|
||||
WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)),
|
||||
WidgetConfig::Media(config) => Box::new(Media::from(*config)),
|
||||
WidgetConfig::Memory(config) => Box::new(Memory::from(*config)),
|
||||
WidgetConfig::Network(config) => Box::new(Network::from(*config)),
|
||||
WidgetConfig::Storage(config) => Box::new(Storage::from(*config)),
|
||||
WidgetConfig::Time(config) => Box::new(Time::from(config.clone())),
|
||||
WidgetConfig::Update(config) => Box::new(Update::from(*config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
match self {
|
||||
WidgetConfig::Battery(config) => config.enable,
|
||||
WidgetConfig::Cpu(config) => config.enable,
|
||||
WidgetConfig::Date(config) => config.enable,
|
||||
WidgetConfig::Keyboard(config) => config.enable,
|
||||
WidgetConfig::Komorebi(config) => {
|
||||
config.workspaces.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.layout.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.focused_window.as_ref().is_some_and(|w| w.enable)
|
||||
|| config
|
||||
.configuration_switcher
|
||||
.as_ref()
|
||||
.is_some_and(|w| w.enable)
|
||||
}
|
||||
WidgetConfig::Media(config) => config.enable,
|
||||
WidgetConfig::Memory(config) => config.enable,
|
||||
WidgetConfig::Network(config) => config.enable,
|
||||
WidgetConfig::Storage(config) => config.enable,
|
||||
WidgetConfig::Time(config) => config.enable,
|
||||
WidgetConfig::Update(config) => config.enable,
|
||||
}
|
||||
}
|
||||
}
|
||||
409
komorebi-bar/src/widgets/applications.rs
Normal file
409
komorebi-bar/src/widgets/applications.rs
Normal file
@@ -0,0 +1,409 @@
|
||||
use super::ImageIcon;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::FontId;
|
||||
use eframe::egui::Frame;
|
||||
use eframe::egui::Image;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::Margin;
|
||||
use eframe::egui::RichText;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::vec2;
|
||||
use komorebi_client::PathExt;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use tracing;
|
||||
use which::which;
|
||||
|
||||
/// Minimum interval between consecutive application launches to prevent accidental spamming.
|
||||
const MIN_LAUNCH_INTERVAL: Duration = Duration::from_millis(800);
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Applications widget configuration
|
||||
pub struct ApplicationsConfig {
|
||||
/// Enables or disables the applications widget.
|
||||
pub enable: bool,
|
||||
/// Whether to show the launch command on hover (optional).
|
||||
/// Could be overridden per application. Defaults to `false` if not set.
|
||||
pub show_command_on_hover: Option<bool>,
|
||||
/// Horizontal spacing between application buttons.
|
||||
pub spacing: Option<f32>,
|
||||
/// Default display format for all applications (optional).
|
||||
/// Could be overridden per application. Defaults to `Icon`.
|
||||
pub display: Option<ApplicationsDisplayFormat>,
|
||||
/// List of configured applications to display.
|
||||
pub items: Vec<AppConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Application button configuration
|
||||
pub struct AppConfig {
|
||||
/// Whether to enable this application button (optional).
|
||||
/// Inherits from the global `Applications` setting if omitted.
|
||||
pub enable: Option<bool>,
|
||||
/// Whether to show the launch command on hover (optional).
|
||||
/// Inherits from the global `Applications` setting if omitted.
|
||||
pub show_command_on_hover: Option<bool>,
|
||||
/// Display name of the application.
|
||||
pub name: String,
|
||||
/// Optional icon: a path to an image or a text-based glyph (e.g., from Nerd Fonts).
|
||||
/// If not set, and if the `command` is a path to an executable, an icon might be extracted from it.
|
||||
/// Note: glyphs require a compatible `font_family`.
|
||||
pub icon: Option<String>,
|
||||
/// Command to execute (e.g. path to the application or shell command).
|
||||
pub command: String,
|
||||
/// Display format for this application button (optional). Overrides global format if set.
|
||||
pub display: Option<ApplicationsDisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Applications widget display format
|
||||
pub enum ApplicationsDisplayFormat {
|
||||
/// Show only the application icon.
|
||||
#[default]
|
||||
Icon,
|
||||
/// Show only the application name as text.
|
||||
Text,
|
||||
/// Show both the application icon and name.
|
||||
IconAndText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Applications {
|
||||
/// Whether the applications widget is enabled.
|
||||
pub enable: bool,
|
||||
/// Horizontal spacing between application buttons.
|
||||
pub spacing: Option<f32>,
|
||||
/// Applications to be rendered in the UI.
|
||||
pub items: Vec<App>,
|
||||
}
|
||||
|
||||
impl BarWidget for Applications {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if !self.enable {
|
||||
return;
|
||||
}
|
||||
|
||||
let icon_config = IconConfig {
|
||||
font_id: config.icon_font_id.clone(),
|
||||
size: config.icon_font_id.size,
|
||||
color: ctx.style().visuals.selection.stroke.color,
|
||||
};
|
||||
|
||||
if let Some(spacing) = self.spacing {
|
||||
ui.spacing_mut().item_spacing.x = spacing;
|
||||
}
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
for app in &mut self.items {
|
||||
app.render(ctx, ui, &icon_config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ApplicationsConfig> for Applications {
|
||||
fn from(applications_config: &ApplicationsConfig) -> Self {
|
||||
let items = applications_config
|
||||
.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, config)| {
|
||||
let command = UserCommand::new(&config.command);
|
||||
|
||||
App {
|
||||
enable: config.enable.unwrap_or(applications_config.enable),
|
||||
#[allow(clippy::obfuscated_if_else)]
|
||||
name: config
|
||||
.name
|
||||
.is_empty()
|
||||
.then(|| format!("App {}", index + 1))
|
||||
.unwrap_or_else(|| config.name.clone()),
|
||||
icon: Icon::try_from_path(config.icon.as_deref())
|
||||
.or_else(|| Icon::try_from_command(&command)),
|
||||
command,
|
||||
display: config
|
||||
.display
|
||||
.or(applications_config.display)
|
||||
.unwrap_or_default(),
|
||||
show_command_on_hover: config
|
||||
.show_command_on_hover
|
||||
.or(applications_config.show_command_on_hover)
|
||||
.unwrap_or(false),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
enable: applications_config.enable,
|
||||
items,
|
||||
spacing: applications_config.spacing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single resolved application entry used at runtime.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct App {
|
||||
/// Whether this application is enabled.
|
||||
pub enable: bool,
|
||||
/// Display name of the application. Defaults to "App N" if not set.
|
||||
pub name: String,
|
||||
/// Icon to display for this application, if available.
|
||||
pub icon: Option<Icon>,
|
||||
/// Command to execute when the application is launched.
|
||||
pub command: UserCommand,
|
||||
/// Display format (icon, text, or both).
|
||||
pub display: ApplicationsDisplayFormat,
|
||||
/// Whether to show the launch command on hover.
|
||||
pub show_command_on_hover: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Renders the application button in the provided `Ui` context with a given icon size.
|
||||
#[inline]
|
||||
pub fn render(&mut self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {
|
||||
if self.enable
|
||||
&& SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing = Vec2::splat(4.0);
|
||||
|
||||
match self.display {
|
||||
ApplicationsDisplayFormat::Icon => self.draw_icon(ctx, ui, icon_config),
|
||||
ApplicationsDisplayFormat::Text => self.draw_name(ui),
|
||||
ApplicationsDisplayFormat::IconAndText => {
|
||||
self.draw_icon(ctx, ui, icon_config);
|
||||
self.draw_name(ui);
|
||||
}
|
||||
}
|
||||
|
||||
// Add hover text with command information
|
||||
let response = ui.response();
|
||||
if self.show_command_on_hover {
|
||||
response.on_hover_text(format!("Launch: {}", self.command.as_ref()));
|
||||
}
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
// Launch the application when clicked
|
||||
self.command.launch_if_ready();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the application's icon within the UI if available,
|
||||
/// or falls back to a default placeholder icon.
|
||||
#[inline]
|
||||
fn draw_icon(&self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {
|
||||
if let Some(icon) = &self.icon {
|
||||
icon.draw(ctx, ui, icon_config);
|
||||
} else {
|
||||
Icon::draw_fallback(ui, Vec2::splat(icon_config.size));
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays the application's name as a non-selectable label within the UI.
|
||||
#[inline]
|
||||
fn draw_name(&self, ui: &mut Ui) {
|
||||
ui.add(Label::new(&self.name).selectable(false));
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds image/text data to be used as an icon in the UI.
|
||||
/// This represents source icon data before rendering.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Icon {
|
||||
/// RGBA image used for rendering the icon.
|
||||
Image(ImageIcon),
|
||||
/// Text-based icon, e.g. from a font like Nerd Fonts.
|
||||
Text(String),
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Attempts to create an [`Icon`] from a string path or text glyph/glyphs.
|
||||
///
|
||||
/// - Environment variables in the path are resolved using [`PathExt::replace_env`].
|
||||
/// - Uses [`ImageIcon::try_load`] to load and cache the icon image based on the resolved path.
|
||||
/// - If the path is invalid but the string is non-empty, it is interpreted as a text-based icon and
|
||||
/// returned as [`Icon::Text`].
|
||||
/// - Returns `None` if the input is empty, `None`, or image loading fails.
|
||||
#[inline]
|
||||
pub fn try_from_path(icon: Option<&str>) -> Option<Self> {
|
||||
let icon = icon.map(str::trim)?;
|
||||
if icon.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = icon.replace_env();
|
||||
if !path.is_file() {
|
||||
return Some(Icon::Text(icon.to_owned()));
|
||||
}
|
||||
|
||||
let image_icon = ImageIcon::try_load(path.as_ref(), || match image::open(&path) {
|
||||
Ok(img) => Some(img),
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to load icon from {:?}, error: {}", path, err);
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
Some(Icon::Image(image_icon))
|
||||
}
|
||||
|
||||
/// Attempts to create an [`Icon`] by extracting an image from the executable path of a [`UserCommand`].
|
||||
///
|
||||
/// - Uses [`ImageIcon::try_load`] to load and cache the icon image based on the resolved executable path.
|
||||
/// - Returns [`Icon::Image`] if an icon is successfully extracted.
|
||||
/// - Returns `None` if the executable path is unavailable or icon extraction fails.
|
||||
#[inline]
|
||||
pub fn try_from_command(command: &UserCommand) -> Option<Self> {
|
||||
let path = command.get_executable()?;
|
||||
let image_icon = ImageIcon::try_load(path.as_ref(), || {
|
||||
let path_str = path.to_str()?;
|
||||
windows_icons::get_icon_by_path(path_str)
|
||||
.or_else(|| windows_icons_fallback::get_icon_by_path(path_str))
|
||||
})?;
|
||||
Some(Icon::Image(image_icon))
|
||||
}
|
||||
|
||||
/// Renders the icon in the given [`Ui`] using the provided [`IconConfig`].
|
||||
#[inline]
|
||||
pub fn draw(&self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {
|
||||
match self {
|
||||
Icon::Image(image_icon) => {
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(ui.style().spacing.button_padding.y as i8))
|
||||
.show(ui, |ui| {
|
||||
ui.add(
|
||||
Image::from_texture(&image_icon.texture(ctx))
|
||||
.maintain_aspect_ratio(true)
|
||||
.fit_to_exact_size(Vec2::splat(icon_config.size)),
|
||||
);
|
||||
});
|
||||
}
|
||||
Icon::Text(icon) => {
|
||||
let rich_text = RichText::new(icon)
|
||||
.font(icon_config.font_id.clone())
|
||||
.size(icon_config.size)
|
||||
.color(icon_config.color);
|
||||
ui.add(Label::new(rich_text).selectable(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a fallback icon when the specified icon cannot be loaded.
|
||||
/// Displays a simple crossed-out rectangle as a placeholder.
|
||||
#[inline]
|
||||
pub fn draw_fallback(ui: &mut Ui, icon_size: Vec2) {
|
||||
let (response, painter) = ui.allocate_painter(icon_size, Sense::hover());
|
||||
let stroke = Stroke::new(1.0, ui.style().visuals.text_color());
|
||||
let mut rect = response.rect;
|
||||
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
|
||||
rect = rect.shrink(stroke.width);
|
||||
let c = rect.center();
|
||||
let r = rect.width() / 2.0;
|
||||
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
|
||||
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration structure for icon rendering
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IconConfig {
|
||||
/// Font used for text-based icons
|
||||
pub font_id: FontId,
|
||||
/// Size of the icon
|
||||
pub size: f32,
|
||||
/// Color of the icon used for text-based icons
|
||||
pub color: Color32,
|
||||
}
|
||||
|
||||
/// A structure to manage command execution with cooldown prevention.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserCommand {
|
||||
/// The command string to execute
|
||||
pub command: Arc<str>,
|
||||
/// Last time this command was executed (used for cooldown control)
|
||||
pub last_launch: Instant,
|
||||
}
|
||||
|
||||
impl AsRef<str> for UserCommand {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.command
|
||||
}
|
||||
}
|
||||
|
||||
impl UserCommand {
|
||||
/// Creates a new [`UserCommand`] with environment variables in the command path
|
||||
/// resolved using [`PathExt::replace_env`].
|
||||
#[inline]
|
||||
pub fn new(command: &str) -> Self {
|
||||
// Allow immediate launch by initializing last_launch in the past
|
||||
let last_launch = Instant::now() - 2 * MIN_LAUNCH_INTERVAL;
|
||||
|
||||
Self {
|
||||
command: Arc::from(command.replace_env().to_str().unwrap_or_default()),
|
||||
last_launch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to resolve the executable path from the command string.
|
||||
///
|
||||
/// Resolution logic:
|
||||
/// - Splits the command by ".exe" and checks if the first part is an existing file.
|
||||
/// - If not, attempts to locate the binary using [`which`] on this name.
|
||||
/// - If still unresolved, takes the first word (separated by whitespace) and attempts
|
||||
/// to find it in the system `PATH` using [`which`].
|
||||
///
|
||||
/// Returns `None` if no executable path can be determined.
|
||||
#[inline]
|
||||
pub fn get_executable(&self) -> Option<Cow<'_, Path>> {
|
||||
if let Some(binary) = self.command.split(".exe").next().map(Path::new) {
|
||||
if binary.is_file() {
|
||||
return Some(Cow::Borrowed(binary));
|
||||
} else if let Ok(binary) = which(binary) {
|
||||
return Some(Cow::Owned(binary));
|
||||
}
|
||||
}
|
||||
|
||||
which(self.command.split(' ').next()?).ok().map(Cow::Owned)
|
||||
}
|
||||
|
||||
/// Attempts to launch the specified command in a separate thread if enough time has passed
|
||||
/// since the last launch. This prevents repeated launches from rapid consecutive clicks.
|
||||
///
|
||||
/// Errors during launch are logged using the `tracing` crate.
|
||||
pub fn launch_if_ready(&mut self) {
|
||||
let now = Instant::now();
|
||||
// Check if enough time has passed since the last launch
|
||||
if now.duration_since(self.last_launch) < MIN_LAUNCH_INTERVAL {
|
||||
return;
|
||||
}
|
||||
|
||||
self.last_launch = now;
|
||||
let command_string = self.command.clone();
|
||||
// Launch the application in a separate thread to avoid blocking the UI
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = Command::new("cmd").args(["/C", &command_string]).spawn() {
|
||||
tracing::error!("Failed to launch command '{}': {}", command_string, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,37 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use starship_battery::units::ratio::percent;
|
||||
use starship_battery::Manager;
|
||||
use starship_battery::State;
|
||||
use starship_battery::units::ratio::percent;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Battery widget configuration
|
||||
pub struct BatteryConfig {
|
||||
/// Enable the Battery widget
|
||||
pub enable: bool,
|
||||
/// Hide the widget if the battery is at full charge
|
||||
pub hide_on_full_charge: Option<bool>,
|
||||
/// Data refresh interval (default: 10 seconds)
|
||||
/// Data refresh interval in seconds
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// Select when the current percentage is under this value [[1-100]]
|
||||
pub auto_select_under: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<BatteryConfig> for Battery {
|
||||
@@ -38,9 +42,10 @@ impl From<BatteryConfig> for Battery {
|
||||
enable: value.enable,
|
||||
hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false),
|
||||
manager: Manager::new().unwrap(),
|
||||
last_state: String::new(),
|
||||
last_state: None,
|
||||
data_refresh_interval,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
auto_select_under: value.auto_select_under.map(|u| u.clamp(1, 100)),
|
||||
state: BatteryState::Discharging,
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
@@ -52,6 +57,16 @@ impl From<BatteryConfig> for Battery {
|
||||
pub enum BatteryState {
|
||||
Charging,
|
||||
Discharging,
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct BatteryOutput {
|
||||
label: String,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
pub struct Battery {
|
||||
@@ -61,38 +76,54 @@ pub struct Battery {
|
||||
pub state: BatteryState,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
last_state: String,
|
||||
auto_select_under: Option<u8>,
|
||||
last_state: Option<BatteryOutput>,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Battery {
|
||||
fn output(&mut self) -> String {
|
||||
fn output(&mut self) -> Option<BatteryOutput> {
|
||||
let mut output = self.last_state.clone();
|
||||
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
output.clear();
|
||||
output = None;
|
||||
|
||||
if let Ok(mut batteries) = self.manager.batteries() {
|
||||
if let Some(Ok(first)) = batteries.nth(0) {
|
||||
let percentage = first.state_of_charge().get::<percent>();
|
||||
if let Ok(mut batteries) = self.manager.batteries()
|
||||
&& let Some(Ok(first)) = batteries.nth(0)
|
||||
{
|
||||
let percentage = first.state_of_charge().get::<percent>().round() as u8;
|
||||
|
||||
if percentage == 100.0 && self.hide_on_full_charge {
|
||||
output = String::new()
|
||||
} else {
|
||||
match first.state() {
|
||||
State::Charging => self.state = BatteryState::Charging,
|
||||
State::Discharging => self.state = BatteryState::Discharging,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
output = match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("BAT: {percentage:.0}%")
|
||||
if percentage == 100 && self.hide_on_full_charge {
|
||||
output = None
|
||||
} else {
|
||||
match first.state() {
|
||||
State::Charging => self.state = BatteryState::Charging,
|
||||
State::Discharging => {
|
||||
self.state = match percentage {
|
||||
p if p > 75 => BatteryState::Discharging,
|
||||
p if p > 50 => BatteryState::High,
|
||||
p if p > 25 => BatteryState::Medium,
|
||||
p if p > 10 => BatteryState::Low,
|
||||
_ => BatteryState::Warning,
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"),
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let selected = self.auto_select_under.is_some_and(|u| percentage <= u);
|
||||
|
||||
output = Some(BatteryOutput {
|
||||
label: match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("BAT: {percentage}%")
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => {
|
||||
format!("{percentage}%")
|
||||
}
|
||||
},
|
||||
selected,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,44 +139,50 @@ impl BarWidget for Battery {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let output = self.output();
|
||||
if !output.is_empty() {
|
||||
if let Some(output) = output {
|
||||
let emoji = match self.state {
|
||||
BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING,
|
||||
BatteryState::Discharging => egui_phosphor::regular::BATTERY_FULL,
|
||||
BatteryState::High => egui_phosphor::regular::BATTERY_HIGH,
|
||||
BatteryState::Medium => egui_phosphor::regular::BATTERY_MEDIUM,
|
||||
BatteryState::Low => egui_phosphor::regular::BATTERY_LOW,
|
||||
BatteryState::Warning => egui_phosphor::regular::BATTERY_WARNING,
|
||||
};
|
||||
|
||||
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(),
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output,
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let auto_focus_fill = config.auto_select_fill;
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
{
|
||||
if let Err(error) = Command::new("cmd.exe")
|
||||
&& let Err(error) = Command::new("cmd.exe")
|
||||
.args(["/C", "start", "ms-settings:batterysaver"])
|
||||
.spawn()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
@@ -17,14 +16,19 @@ use std::time::Instant;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// CPU widget configuration
|
||||
pub struct CpuConfig {
|
||||
/// Enable the Cpu widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval (default: 10 seconds)
|
||||
/// Data refresh interval in seconds
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// Select when the current percentage is over this value [[1-100]]
|
||||
pub auto_select_over: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<CpuConfig> for Cpu {
|
||||
@@ -38,6 +42,7 @@ impl From<CpuConfig> for Cpu {
|
||||
),
|
||||
data_refresh_interval,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
@@ -45,26 +50,38 @@ impl From<CpuConfig> for Cpu {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CpuOutput {
|
||||
label: String,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
pub struct Cpu {
|
||||
pub enable: bool,
|
||||
system: System,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
auto_select_over: Option<u8>,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
fn output(&mut self) -> String {
|
||||
fn output(&mut self) -> CpuOutput {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
self.system.refresh_cpu_usage();
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
let used = self.system.global_cpu_usage();
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {:.0}%", used),
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{:.0}%", used),
|
||||
let used = self.system.global_cpu_usage() as u8;
|
||||
let selected = self.auto_select_over.is_some_and(|o| used >= o);
|
||||
|
||||
CpuOutput {
|
||||
label: match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {used}%"),
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{used}%"),
|
||||
},
|
||||
selected,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +90,9 @@ impl BarWidget for Cpu {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let output = self.output();
|
||||
if !output.is_empty() {
|
||||
if !output.label.is_empty() {
|
||||
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
@@ -82,31 +101,31 @@ impl BarWidget for Cpu {
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output,
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let auto_focus_fill = config.auto_select_fill;
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
{
|
||||
if let Err(error) =
|
||||
&& let Err(error) =
|
||||
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,20 +1,25 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use chrono::Local;
|
||||
use chrono_tz::Tz;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Custom format with additive modifiers for integer format specifiers
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CustomModifiers {
|
||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||
format: String,
|
||||
@@ -38,7 +43,7 @@ impl CustomModifiers {
|
||||
}
|
||||
|
||||
// get the strftime value of modifier
|
||||
let formatted_modifier = chrono::Local::now().format(modifier).to_string();
|
||||
let formatted_modifier = Local::now().format(modifier).to_string();
|
||||
|
||||
// find the gotten value in the original output
|
||||
if let Some(pos) = modified_output.find(&formatted_modifier) {
|
||||
@@ -55,7 +60,9 @@ impl CustomModifiers {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Date widget configuration
|
||||
pub struct DateConfig {
|
||||
/// Enable the Date widget
|
||||
pub enable: bool,
|
||||
@@ -63,19 +70,42 @@ pub struct DateConfig {
|
||||
pub format: DateFormat,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
///
|
||||
/// Use a custom format to display additional information, i.e.:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Date": {
|
||||
/// "enable": true,
|
||||
/// "format": { "Custom": "%D %Z (Tokyo)" },
|
||||
/// "timezone": "Asia/Tokyo"
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
pub timezone: Option<String>,
|
||||
}
|
||||
|
||||
impl From<DateConfig> for Date {
|
||||
fn from(value: DateConfig) -> Self {
|
||||
let data_refresh_interval = 1;
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
format: value.format,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
timezone: value.timezone,
|
||||
data_refresh_interval,
|
||||
last_state: String::new(),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Date widget format
|
||||
pub enum DateFormat {
|
||||
/// Month/Date/Year format (09/08/24)
|
||||
MonthDateYear,
|
||||
@@ -86,8 +116,10 @@ pub enum DateFormat {
|
||||
/// Day Date Month Year format (8 September 2024)
|
||||
DayDateMonthYear,
|
||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||
Custom(String),
|
||||
/// Custom format with modifiers
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "CustomModifiers"))]
|
||||
CustomModifiers(CustomModifiers),
|
||||
}
|
||||
|
||||
@@ -119,19 +151,46 @@ pub struct Date {
|
||||
pub enable: bool,
|
||||
pub format: DateFormat,
|
||||
label_prefix: LabelPrefix,
|
||||
timezone: Option<String>,
|
||||
data_refresh_interval: u64,
|
||||
last_state: String,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
fn output(&mut self) -> String {
|
||||
let formatted = chrono::Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string();
|
||||
let mut output = self.last_state.clone();
|
||||
let now = Instant::now();
|
||||
|
||||
// if custom modifiers are used, apply them
|
||||
match &self.format {
|
||||
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
|
||||
_ => formatted,
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
let formatted = match &self.timezone {
|
||||
Some(timezone) => match timezone.parse::<Tz>() {
|
||||
Ok(tz) => Local::now()
|
||||
.with_timezone(&tz)
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Err(_) => format!("Invalid timezone: {timezone}"),
|
||||
},
|
||||
None => Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
// if custom modifiers are used, apply them
|
||||
output = match &self.format {
|
||||
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
|
||||
_ => formatted,
|
||||
};
|
||||
|
||||
self.last_state.clone_from(&output);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +230,7 @@ impl BarWidget for Date {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
ui.add(
|
||||
Label::new(WidgetText::LayoutJob(layout_job.clone()))
|
||||
Label::new(WidgetText::LayoutJob(Arc::from(layout_job.clone())))
|
||||
.selectable(false),
|
||||
)
|
||||
})
|
||||
26
komorebi-bar/src/keyboard.rs → komorebi-bar/src/widgets/keyboard.rs
Executable file → Normal file
26
komorebi-bar/src/keyboard.rs → komorebi-bar/src/widgets/keyboard.rs
Executable file → Normal file
@@ -1,16 +1,17 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use color_eyre::eyre;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use windows::Win32::Globalization::LCIDToLocaleName;
|
||||
@@ -20,14 +21,16 @@ use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyboardLayout;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
|
||||
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
|
||||
const ERROR_TEXT: &str = "Error";
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Keyboard widget configuration
|
||||
pub struct KeyboardConfig {
|
||||
/// Enable the Input widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval (default: 1 second)
|
||||
/// Data refresh interval
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
@@ -35,9 +38,7 @@ pub struct KeyboardConfig {
|
||||
|
||||
impl From<KeyboardConfig> for Keyboard {
|
||||
fn from(value: KeyboardConfig) -> Self {
|
||||
let data_refresh_interval = value
|
||||
.data_refresh_interval
|
||||
.unwrap_or(DEFAULT_DATA_REFRESH_INTERVAL);
|
||||
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
@@ -80,7 +81,7 @@ pub struct Keyboard {
|
||||
/// - `Ok(String)`: The name of the active keyboard layout as a valid UTF-8 string.
|
||||
/// - `Err(())`: Indicates that the function failed to retrieve the locale name or encountered
|
||||
/// invalid UTF-16 characters during conversion.
|
||||
fn get_active_keyboard_layout() -> Result<String, ()> {
|
||||
fn get_active_keyboard_layout() -> eyre::Result<String, ()> {
|
||||
let foreground_window_tid = unsafe { GetWindowThreadProcessId(GetForegroundWindow(), None) };
|
||||
let lcid = unsafe { GetKeyboardLayout(foreground_window_tid) };
|
||||
|
||||
@@ -169,7 +170,10 @@ impl BarWidget for Keyboard {
|
||||
);
|
||||
|
||||
config.apply_on_widget(true, ui, |ui| {
|
||||
ui.add(Label::new(WidgetText::LayoutJob(layout_job.clone())).selectable(false))
|
||||
ui.add(
|
||||
Label::new(WidgetText::LayoutJob(Arc::from(layout_job.clone())))
|
||||
.selectable(false),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
1025
komorebi-bar/src/widgets/komorebi.rs
Normal file
1025
komorebi-bar/src/widgets/komorebi.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
use crate::config::DisplayFormat;
|
||||
use crate::komorebi::KomorebiLayoutConfig;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use eframe::egui::vec2;
|
||||
use crate::widgets::komorebi::KomorebiLayoutConfig;
|
||||
use color_eyre::eyre;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::FontId;
|
||||
@@ -13,36 +13,43 @@ use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::vec2;
|
||||
use komorebi_client::SocketMessage;
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use serde::de::Error;
|
||||
use serde_json::from_str;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
/// Komorebi layout kind
|
||||
pub enum KomorebiLayout {
|
||||
/// Predefined layout
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Default"))]
|
||||
Default(komorebi_client::DefaultLayout),
|
||||
/// Monocle mode
|
||||
Monocle,
|
||||
/// Floating layer
|
||||
Floating,
|
||||
/// Paused
|
||||
Paused,
|
||||
/// Custom layout
|
||||
Custom,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KomorebiLayout {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
fn deserialize<D>(deserializer: D) -> eyre::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = String::deserialize(deserializer)?;
|
||||
|
||||
// Attempt to deserialize the string as a DefaultLayout
|
||||
if let Ok(default_layout) =
|
||||
from_str::<komorebi_client::DefaultLayout>(&format!("\"{}\"", s))
|
||||
if let Ok(default_layout) = from_str::<komorebi_client::DefaultLayout>(&format!("\"{s}\""))
|
||||
{
|
||||
return Ok(KomorebiLayout::Default(default_layout));
|
||||
}
|
||||
@@ -53,7 +60,7 @@ impl<'de> Deserialize<'de> for KomorebiLayout {
|
||||
"Floating" => Ok(KomorebiLayout::Floating),
|
||||
"Paused" => Ok(KomorebiLayout::Paused),
|
||||
"Custom" => Ok(KomorebiLayout::Custom),
|
||||
_ => Err(Error::custom(format!("Invalid layout: {}", s))),
|
||||
_ => Err(Error::custom(format!("Invalid layout: {s}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,25 +99,34 @@ impl KomorebiLayout {
|
||||
fn on_click_option(&mut self, monitor_idx: usize, workspace_idx: Option<usize>) {
|
||||
match self {
|
||||
KomorebiLayout::Default(option) => {
|
||||
if let Some(ws_idx) = workspace_idx {
|
||||
if komorebi_client::send_message(&SocketMessage::WorkspaceLayout(
|
||||
if let Some(ws_idx) = workspace_idx
|
||||
&& komorebi_client::send_message(&SocketMessage::WorkspaceLayout(
|
||||
monitor_idx,
|
||||
ws_idx,
|
||||
*option,
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send message to komorebi: WorkspaceLayout");
|
||||
}
|
||||
{
|
||||
tracing::error!("could not send message to komorebi: WorkspaceLayout");
|
||||
}
|
||||
}
|
||||
KomorebiLayout::Monocle => {
|
||||
if komorebi_client::send_message(&SocketMessage::ToggleMonocle).is_err() {
|
||||
if komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::ToggleMonocle,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send message to komorebi: ToggleMonocle");
|
||||
}
|
||||
}
|
||||
KomorebiLayout::Floating => {
|
||||
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err() {
|
||||
if komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::ToggleTiling,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send message to komorebi: ToggleTiling");
|
||||
}
|
||||
}
|
||||
@@ -178,6 +194,12 @@ impl KomorebiLayout {
|
||||
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
|
||||
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||
}
|
||||
// TODO: @CtByte can you think of a nice icon to draw here?
|
||||
komorebi_client::DefaultLayout::Scrolling => {
|
||||
painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);
|
||||
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||
painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);
|
||||
}
|
||||
},
|
||||
KomorebiLayout::Monocle => {}
|
||||
KomorebiLayout::Floating => {
|
||||
@@ -241,7 +263,7 @@ impl KomorebiLayout {
|
||||
let layout_frame = SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
|
||||
self.show_icon(false, font_id.clone(), ctx, ui);
|
||||
self.show_icon(true, font_id.clone(), ctx, ui);
|
||||
}
|
||||
|
||||
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
|
||||
@@ -254,57 +276,53 @@ impl KomorebiLayout {
|
||||
show_options = self.on_click(&show_options, monitor_idx, workspace_idx);
|
||||
}
|
||||
|
||||
if show_options {
|
||||
if let Some(workspace_idx) = workspace_idx {
|
||||
Frame::NONE.show(ui, |ui| {
|
||||
ui.add(
|
||||
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
|
||||
.selectable(false),
|
||||
);
|
||||
if show_options && let Some(workspace_idx) = workspace_idx {
|
||||
Frame::NONE.show(ui, |ui| {
|
||||
ui.add(
|
||||
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
|
||||
.selectable(false),
|
||||
);
|
||||
|
||||
let mut layout_options = layout_config.options.clone().unwrap_or(vec![
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),
|
||||
KomorebiLayout::Default(
|
||||
komorebi_client::DefaultLayout::RightMainVerticalStack,
|
||||
),
|
||||
KomorebiLayout::Default(
|
||||
komorebi_client::DefaultLayout::HorizontalStack,
|
||||
),
|
||||
KomorebiLayout::Default(
|
||||
komorebi_client::DefaultLayout::UltrawideVerticalStack,
|
||||
),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),
|
||||
//KomorebiLayout::Custom,
|
||||
KomorebiLayout::Monocle,
|
||||
KomorebiLayout::Floating,
|
||||
KomorebiLayout::Paused,
|
||||
]);
|
||||
let mut layout_options = layout_config.options.clone().unwrap_or(vec![
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),
|
||||
KomorebiLayout::Default(
|
||||
komorebi_client::DefaultLayout::RightMainVerticalStack,
|
||||
),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::HorizontalStack),
|
||||
KomorebiLayout::Default(
|
||||
komorebi_client::DefaultLayout::UltrawideVerticalStack,
|
||||
),
|
||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),
|
||||
//KomorebiLayout::Custom,
|
||||
KomorebiLayout::Monocle,
|
||||
KomorebiLayout::Floating,
|
||||
KomorebiLayout::Paused,
|
||||
]);
|
||||
|
||||
for layout_option in &mut layout_options {
|
||||
let is_selected = self == layout_option;
|
||||
for layout_option in &mut layout_options {
|
||||
let is_selected = self == layout_option;
|
||||
|
||||
if SelectableFrame::new(is_selected)
|
||||
.show(ui, |ui| {
|
||||
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
|
||||
})
|
||||
.on_hover_text(match layout_option {
|
||||
KomorebiLayout::Default(layout) => layout.to_string(),
|
||||
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
|
||||
KomorebiLayout::Floating => "Toggle tiling".to_string(),
|
||||
KomorebiLayout::Paused => "Toggle pause".to_string(),
|
||||
KomorebiLayout::Custom => "Custom".to_string(),
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
layout_option.on_click_option(monitor_idx, Some(workspace_idx));
|
||||
show_options = false;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
if SelectableFrame::new(is_selected)
|
||||
.show(ui, |ui| {
|
||||
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
|
||||
})
|
||||
.on_hover_text(match layout_option {
|
||||
KomorebiLayout::Default(layout) => layout.to_string(),
|
||||
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
|
||||
KomorebiLayout::Floating => "Toggle tiling".to_string(),
|
||||
KomorebiLayout::Paused => "Toggle pause".to_string(),
|
||||
KomorebiLayout::Custom => "Custom".to_string(),
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
layout_option.on_click_option(monitor_idx, Some(workspace_idx));
|
||||
show_options = false;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::ui::CustomUi;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Media widget configuration
|
||||
pub struct MediaConfig {
|
||||
/// Enable the Media widget
|
||||
pub enable: bool,
|
||||
@@ -40,36 +41,34 @@ impl Media {
|
||||
enable,
|
||||
session_manager: GlobalSystemMediaTransportControlsSessionManager::RequestAsync()
|
||||
.unwrap()
|
||||
.get()
|
||||
.join()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(&self) {
|
||||
if let Ok(session) = self.session_manager.GetCurrentSession() {
|
||||
if let Ok(op) = session.TryTogglePlayPauseAsync() {
|
||||
op.get().unwrap_or_default();
|
||||
}
|
||||
if let Ok(session) = self.session_manager.GetCurrentSession()
|
||||
&& let Ok(op) = session.TryTogglePlayPauseAsync()
|
||||
{
|
||||
op.join().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
fn output(&mut self) -> String {
|
||||
if let Ok(session) = self.session_manager.GetCurrentSession() {
|
||||
if let Ok(operation) = session.TryGetMediaPropertiesAsync() {
|
||||
if let Ok(properties) = operation.get() {
|
||||
if let (Ok(artist), Ok(title)) = (properties.Artist(), properties.Title()) {
|
||||
if artist.is_empty() {
|
||||
return format!("{title}");
|
||||
}
|
||||
|
||||
if title.is_empty() {
|
||||
return format!("{artist}");
|
||||
}
|
||||
|
||||
return format!("{artist} - {title}");
|
||||
}
|
||||
}
|
||||
if let Ok(session) = self.session_manager.GetCurrentSession()
|
||||
&& let Ok(operation) = session.TryGetMediaPropertiesAsync()
|
||||
&& let Ok(properties) = operation.join()
|
||||
&& let (Ok(artist), Ok(title)) = (properties.Artist(), properties.Title())
|
||||
{
|
||||
if artist.is_empty() {
|
||||
return format!("{title}");
|
||||
}
|
||||
|
||||
if title.is_empty() {
|
||||
return format!("{artist}");
|
||||
}
|
||||
|
||||
return format!("{artist} - {title}");
|
||||
}
|
||||
|
||||
String::new()
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
@@ -17,14 +16,19 @@ use std::time::Instant;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Memory widget configuration
|
||||
pub struct MemoryConfig {
|
||||
/// Enable the Memory widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval (default: 10 seconds)
|
||||
/// Data refresh interval in seconds
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// Select when the current percentage is over this value [[1-100]]
|
||||
pub auto_select_over: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<MemoryConfig> for Memory {
|
||||
@@ -38,6 +42,7 @@ impl From<MemoryConfig> for Memory {
|
||||
),
|
||||
data_refresh_interval,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
@@ -45,16 +50,23 @@ impl From<MemoryConfig> for Memory {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct MemoryOutput {
|
||||
label: String,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
pub struct Memory {
|
||||
pub enable: bool,
|
||||
system: System,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
auto_select_over: Option<u8>,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn output(&mut self) -> String {
|
||||
fn output(&mut self) -> MemoryOutput {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
self.system.refresh_memory();
|
||||
@@ -63,11 +75,17 @@ impl Memory {
|
||||
|
||||
let used = self.system.used_memory();
|
||||
let total = self.system.total_memory();
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("RAM: {}%", (used * 100) / total)
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total),
|
||||
let usage = ((used * 100) / total) as u8;
|
||||
let selected = self.auto_select_over.is_some_and(|o| usage >= o);
|
||||
|
||||
MemoryOutput {
|
||||
label: match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("RAM: {usage}%")
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{usage}%"),
|
||||
},
|
||||
selected,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +94,9 @@ impl BarWidget for Memory {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let output = self.output();
|
||||
if !output.is_empty() {
|
||||
if !output.label.is_empty() {
|
||||
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
@@ -85,31 +105,31 @@ impl BarWidget for Memory {
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output,
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let auto_focus_fill = config.auto_select_fill;
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
{
|
||||
if let Err(error) =
|
||||
&& let Err(error) =
|
||||
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
173
komorebi-bar/src/widgets/mod.rs
Normal file
173
komorebi-bar/src/widgets/mod.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use eframe::egui::ColorImage;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::TextureHandle;
|
||||
use eframe::egui::TextureOptions;
|
||||
use image::RgbaImage;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub mod applications;
|
||||
pub mod battery;
|
||||
pub mod cpu;
|
||||
pub mod date;
|
||||
pub mod keyboard;
|
||||
pub mod komorebi;
|
||||
mod komorebi_layout;
|
||||
pub mod media;
|
||||
pub mod memory;
|
||||
pub mod network;
|
||||
pub mod storage;
|
||||
pub mod time;
|
||||
pub mod update;
|
||||
pub mod widget;
|
||||
|
||||
/// Global cache for icon images and their associated GPU textures.
|
||||
pub static ICONS_CACHE: IconsCache = IconsCache::new();
|
||||
|
||||
/// In-memory cache for icon images and their associated GPU textures.
|
||||
///
|
||||
/// Stores raw [`ColorImage`]s and [`TextureHandle`]s keyed by [`ImageIconId`].
|
||||
/// Texture entries are context-dependent and automatically invalidated when the [`Context`] changes.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct IconsCache {
|
||||
textures: LazyLock<RwLock<(Option<Context>, HashMap<ImageIconId, TextureHandle>)>>,
|
||||
images: LazyLock<RwLock<HashMap<ImageIconId, Arc<ColorImage>>>>,
|
||||
}
|
||||
|
||||
impl IconsCache {
|
||||
/// Creates a new empty IconsCache instance.
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
textures: LazyLock::new(|| RwLock::new((None, HashMap::new()))),
|
||||
images: LazyLock::new(|| RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves or creates a texture handle for the given icon ID and image.
|
||||
///
|
||||
/// If a texture for the given ID already exists for the current [`Context`], it is reused.
|
||||
/// Otherwise, a new texture is created, inserted into the cache, and returned.
|
||||
/// The cache is reset if the [`Context`] has changed.
|
||||
#[inline]
|
||||
pub fn texture(&self, ctx: &Context, id: &ImageIconId, img: &Arc<ColorImage>) -> TextureHandle {
|
||||
if let Some(texture) = self.get_texture(ctx, id) {
|
||||
return texture;
|
||||
}
|
||||
let texture_handle = ctx.load_texture("icon", img.clone(), TextureOptions::default());
|
||||
self.insert_texture(ctx, id.clone(), texture_handle.clone());
|
||||
texture_handle
|
||||
}
|
||||
|
||||
/// Returns the cached texture for the given icon ID if it exists and matches the current [`Context`].
|
||||
pub fn get_texture(&self, ctx: &Context, id: &ImageIconId) -> Option<TextureHandle> {
|
||||
let textures_lock = self.textures.read().unwrap();
|
||||
if textures_lock.0.as_ref() == Some(ctx) {
|
||||
return textures_lock.1.get(id).cloned();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Inserts a texture handle, resetting the cache if the [`Context`] has changed.
|
||||
pub fn insert_texture(&self, ctx: &Context, id: ImageIconId, texture: TextureHandle) {
|
||||
let mut textures_lock = self.textures.write().unwrap();
|
||||
|
||||
if textures_lock.0.as_ref() != Some(ctx) {
|
||||
textures_lock.0 = Some(ctx.clone());
|
||||
textures_lock.1.clear();
|
||||
}
|
||||
|
||||
textures_lock.1.insert(id, texture);
|
||||
}
|
||||
|
||||
/// Returns the cached image for the given icon ID, if available.
|
||||
pub fn get_image(&self, id: &ImageIconId) -> Option<Arc<ColorImage>> {
|
||||
self.images.read().unwrap().get(id).cloned()
|
||||
}
|
||||
|
||||
/// Caches a raw [`ColorImage`] associated with the given icon ID.
|
||||
pub fn insert_image(&self, id: ImageIconId, image: Arc<ColorImage>) {
|
||||
self.images.write().unwrap().insert(id, image);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage {
|
||||
let size = [rgba_image.width() as usize, rgba_image.height() as usize];
|
||||
let pixels = rgba_image.as_flat_samples();
|
||||
ColorImage::from_rgba_unmultiplied(size, pixels.as_slice())
|
||||
}
|
||||
|
||||
/// Represents an image-based icon with a unique ID and pixel data.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageIcon {
|
||||
/// Unique identifier for the image icon, used for texture caching.
|
||||
pub id: ImageIconId,
|
||||
/// Shared pixel data of the icon in `ColorImage` format.
|
||||
pub image: Arc<ColorImage>,
|
||||
}
|
||||
|
||||
impl ImageIcon {
|
||||
/// Creates a new [`ImageIcon`] from the given ID and image data.
|
||||
#[inline]
|
||||
pub fn new(id: ImageIconId, image: Arc<ColorImage>) -> Self {
|
||||
Self { id, image }
|
||||
}
|
||||
|
||||
/// Loads an [`ImageIcon`] from [`ICONS_CACHE`] or calls `loader` if not cached.
|
||||
/// The loaded image is converted to a [`ColorImage`], cached, and returned.
|
||||
#[inline]
|
||||
pub fn try_load<F, I>(id: impl Into<ImageIconId>, loader: F) -> Option<Self>
|
||||
where
|
||||
F: FnOnce() -> Option<I>,
|
||||
I: Into<RgbaImage>,
|
||||
{
|
||||
let id = id.into();
|
||||
let image = ICONS_CACHE.get_image(&id).or_else(|| {
|
||||
let img = loader()?;
|
||||
let img = Arc::new(rgba_to_color_image(&img.into()));
|
||||
ICONS_CACHE.insert_image(id.clone(), img.clone());
|
||||
Some(img)
|
||||
})?;
|
||||
|
||||
Some(ImageIcon::new(id, image))
|
||||
}
|
||||
|
||||
/// Returns a texture handle for the icon, using the given [`Context`].
|
||||
///
|
||||
/// If the texture is already cached in [`ICONS_CACHE`], it is reused.
|
||||
/// Otherwise, a new texture is created from the [`ColorImage`] and cached.
|
||||
#[inline]
|
||||
pub fn texture(&self, ctx: &Context) -> TextureHandle {
|
||||
ICONS_CACHE.texture(ctx, &self.id, &self.image)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique identifier for an image-based icon.
|
||||
///
|
||||
/// Used to distinguish cached images and textures by either a file path
|
||||
/// or a Windows window handle.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum ImageIconId {
|
||||
/// Identifier based on a file system path.
|
||||
Path(Arc<Path>),
|
||||
/// Windows HWND handle.
|
||||
Hwnd(isize),
|
||||
}
|
||||
|
||||
impl From<&Path> for ImageIconId {
|
||||
#[inline]
|
||||
fn from(value: &Path) -> Self {
|
||||
Self::Path(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for ImageIconId {
|
||||
#[inline]
|
||||
fn from(value: isize) -> Self {
|
||||
Self::Hwnd(value)
|
||||
}
|
||||
}
|
||||
613
komorebi-bar/src/widgets/network.rs
Normal file
613
komorebi-bar/src/widgets/network.rs
Normal file
@@ -0,0 +1,613 @@
|
||||
use crate::bar::Alignment;
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use num_derive::FromPrimitive;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::Networks;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Network widget configuration
|
||||
pub struct NetworkConfig {
|
||||
/// Enable the Network widget
|
||||
pub enable: bool,
|
||||
/// Show total received and transmitted activity
|
||||
#[serde(alias = "show_total_data_transmitted")]
|
||||
pub show_total_activity: bool,
|
||||
/// Show received and transmitted activity
|
||||
#[serde(alias = "show_network_activity")]
|
||||
pub show_activity: bool,
|
||||
/// Show default interface
|
||||
pub show_default_interface: Option<bool>,
|
||||
/// Characters to reserve for received and transmitted activity
|
||||
#[serde(alias = "network_activity_fill_characters")]
|
||||
pub activity_left_padding: Option<usize>,
|
||||
/// Data refresh interval in seconds
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))
|
||||
pub auto_select: Option<NetworkSelectConfig>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Network select configuration
|
||||
pub struct NetworkSelectConfig {
|
||||
/// Select the total received data when it's over this value
|
||||
pub total_received_over: Option<u64>,
|
||||
/// Select the total transmitted data when it's over this value
|
||||
pub total_transmitted_over: Option<u64>,
|
||||
/// Select the received data when it's over this value
|
||||
pub received_over: Option<u64>,
|
||||
/// Select the transmitted data when it's over this value
|
||||
pub transmitted_over: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<NetworkConfig> for Network {
|
||||
fn from(value: NetworkConfig) -> Self {
|
||||
let default_refresh_interval = 10;
|
||||
let data_refresh_interval = value
|
||||
.data_refresh_interval
|
||||
.unwrap_or(default_refresh_interval);
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
show_total_activity: value.show_total_activity,
|
||||
show_activity: value.show_activity,
|
||||
show_default_interface: value.show_default_interface.unwrap_or(true),
|
||||
networks_network_activity: Arc::new(Mutex::new(Networks::new_with_refreshed_list())),
|
||||
default_interface: Arc::new(Mutex::new(String::new())),
|
||||
interface_generation: Arc::new(AtomicU64::new(0)),
|
||||
default_refresh_interval,
|
||||
data_refresh_interval,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
auto_select: value.auto_select,
|
||||
activity_left_padding: value.activity_left_padding.unwrap_or_default(),
|
||||
last_update_request_default_interface: Instant::now()
|
||||
.checked_sub(Duration::from_secs(default_refresh_interval))
|
||||
.unwrap(),
|
||||
last_state_total_activity: Arc::new(Mutex::new(vec![])),
|
||||
last_state_activity: Arc::new(Mutex::new(vec![])),
|
||||
last_update_request_network_activity: Arc::new(Mutex::new(
|
||||
Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
)),
|
||||
activity_generation: Arc::new(AtomicU64::new(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Network {
|
||||
pub enable: bool,
|
||||
pub show_total_activity: bool,
|
||||
pub show_activity: bool,
|
||||
pub show_default_interface: bool,
|
||||
networks_network_activity: Arc<Mutex<Networks>>,
|
||||
default_refresh_interval: u64,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
auto_select: Option<NetworkSelectConfig>,
|
||||
default_interface: Arc<Mutex<String>>,
|
||||
interface_generation: Arc<AtomicU64>,
|
||||
last_update_request_default_interface: Instant,
|
||||
activity_generation: Arc<AtomicU64>,
|
||||
last_state_activity: Arc<Mutex<Vec<NetworkReading>>>,
|
||||
last_state_total_activity: Arc<Mutex<Vec<NetworkReading>>>,
|
||||
last_update_request_network_activity: Arc<Mutex<Instant>>,
|
||||
activity_left_padding: usize,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
fn update_default_interface_async(&mut self) {
|
||||
let gen_ = self.interface_generation.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
let gen_arc = Arc::clone(&self.interface_generation);
|
||||
let iface_arc = Arc::clone(&self.default_interface);
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Ok(interface) = netdev::get_default_interface()
|
||||
&& let Some(friendly_name) = &interface.friendly_name
|
||||
{
|
||||
// Only update if this is the latest request
|
||||
if gen_ == gen_arc.load(Ordering::SeqCst)
|
||||
&& let Ok(mut iface) = iface_arc.lock()
|
||||
{
|
||||
*iface = friendly_name.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn default_interface(&mut self) -> String {
|
||||
let current = self.default_interface.lock().unwrap().clone();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(self.last_update_request_default_interface)
|
||||
> Duration::from_secs(self.default_refresh_interval)
|
||||
{
|
||||
self.last_update_request_default_interface = now;
|
||||
self.update_default_interface_async();
|
||||
}
|
||||
|
||||
current
|
||||
}
|
||||
|
||||
fn update_network_activity_async(&mut self) {
|
||||
let gen_ = self.activity_generation.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
let gen_arc = Arc::clone(&self.activity_generation);
|
||||
let activity_arc = Arc::clone(&self.last_state_activity);
|
||||
let total_activity_arc = Arc::clone(&self.last_state_total_activity);
|
||||
let data_refresh_interval = self.data_refresh_interval;
|
||||
let show_activity = self.show_activity;
|
||||
let show_total_activity = self.show_total_activity;
|
||||
let networks_network_activity_arc = Arc::clone(&self.networks_network_activity);
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut activity = Vec::new();
|
||||
let mut total_activity = Vec::new();
|
||||
|
||||
if let Ok(interface) = netdev::get_default_interface()
|
||||
&& let Some(friendly_name) = &interface.friendly_name
|
||||
&& let Ok(mut networks) = networks_network_activity_arc.lock()
|
||||
{
|
||||
networks.refresh(true);
|
||||
|
||||
for (interface_name, data) in &*networks {
|
||||
if friendly_name.eq(interface_name) {
|
||||
if show_activity {
|
||||
let received =
|
||||
Network::to_pretty_bytes(data.received(), data_refresh_interval);
|
||||
let transmitted =
|
||||
Network::to_pretty_bytes(data.transmitted(), data_refresh_interval);
|
||||
|
||||
activity.push(NetworkReading::new(
|
||||
NetworkReadingFormat::Speed,
|
||||
ReadingValue::from(received),
|
||||
ReadingValue::from(transmitted),
|
||||
));
|
||||
}
|
||||
|
||||
if show_total_activity {
|
||||
let total_received = Network::to_pretty_bytes(data.total_received(), 1);
|
||||
let total_transmitted =
|
||||
Network::to_pretty_bytes(data.total_transmitted(), 1);
|
||||
|
||||
total_activity.push(NetworkReading::new(
|
||||
NetworkReadingFormat::Total,
|
||||
ReadingValue::from(total_received),
|
||||
ReadingValue::from(total_transmitted),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only update if this is the latest request
|
||||
if gen_ == gen_arc.load(Ordering::SeqCst) {
|
||||
if let Ok(mut act) = activity_arc.lock() {
|
||||
*act = activity;
|
||||
}
|
||||
if let Ok(mut tot) = total_activity_arc.lock() {
|
||||
*tot = total_activity;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
|
||||
let now = Instant::now();
|
||||
let should_update = {
|
||||
let last_update_request = self.last_update_request_network_activity.lock().unwrap();
|
||||
now.duration_since(*last_update_request)
|
||||
> Duration::from_secs(self.data_refresh_interval)
|
||||
};
|
||||
|
||||
if should_update {
|
||||
{
|
||||
let mut last_updated = self.last_update_request_network_activity.lock().unwrap();
|
||||
*last_updated = now;
|
||||
}
|
||||
self.update_network_activity_async();
|
||||
}
|
||||
|
||||
self.get_network_activity()
|
||||
}
|
||||
|
||||
fn get_network_activity(&self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
|
||||
let activity = self.last_state_activity.lock().unwrap().clone();
|
||||
let total_activity = self.last_state_total_activity.lock().unwrap().clone();
|
||||
(activity, total_activity)
|
||||
}
|
||||
|
||||
fn reading_to_labels(
|
||||
&self,
|
||||
select_received: bool,
|
||||
select_transmitted: bool,
|
||||
ctx: &Context,
|
||||
reading: &NetworkReading,
|
||||
config: RenderConfig,
|
||||
) -> (Label, Label) {
|
||||
let (text_down, text_up) = match self.label_prefix {
|
||||
LabelPrefix::None | LabelPrefix::Icon => match reading.format {
|
||||
NetworkReadingFormat::Speed => (
|
||||
format!(
|
||||
"{: >width$}/s ",
|
||||
reading.received.pretty,
|
||||
width = self.activity_left_padding
|
||||
),
|
||||
format!(
|
||||
"{: >width$}/s",
|
||||
reading.transmitted.pretty,
|
||||
width = self.activity_left_padding
|
||||
),
|
||||
),
|
||||
NetworkReadingFormat::Total => (
|
||||
format!("{} ", reading.received.pretty),
|
||||
reading.transmitted.pretty.clone(),
|
||||
),
|
||||
},
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {
|
||||
NetworkReadingFormat::Speed => (
|
||||
format!(
|
||||
"DOWN: {: >width$}/s ",
|
||||
reading.received.pretty,
|
||||
width = self.activity_left_padding
|
||||
),
|
||||
format!(
|
||||
"UP: {: >width$}/s",
|
||||
reading.transmitted.pretty,
|
||||
width = self.activity_left_padding
|
||||
),
|
||||
),
|
||||
NetworkReadingFormat::Total => (
|
||||
format!("\u{2211}DOWN: {}/s ", reading.received.pretty),
|
||||
format!("\u{2211}UP: {}/s", reading.transmitted.pretty),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
let auto_text_color_received = config.auto_select_text.filter(|_| select_received);
|
||||
let auto_text_color_transmitted = config.auto_select_text.filter(|_| select_transmitted);
|
||||
|
||||
// icon
|
||||
let mut layout_job_down = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
if select_received {
|
||||
egui_phosphor::regular::ARROW_FAT_LINES_DOWN.to_string()
|
||||
} else {
|
||||
egui_phosphor::regular::ARROW_FAT_DOWN.to_string()
|
||||
}
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
auto_text_color_received.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
// text
|
||||
layout_job_down.append(
|
||||
&text_down,
|
||||
ctx.style().spacing.item_spacing.x,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: auto_text_color_received.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// icon
|
||||
let mut layout_job_up = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
if select_transmitted {
|
||||
egui_phosphor::regular::ARROW_FAT_LINES_UP.to_string()
|
||||
} else {
|
||||
egui_phosphor::regular::ARROW_FAT_UP.to_string()
|
||||
}
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
auto_text_color_transmitted.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
// text
|
||||
layout_job_up.append(
|
||||
&text_up,
|
||||
ctx.style().spacing.item_spacing.x,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: auto_text_color_transmitted.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
(
|
||||
Label::new(layout_job_down).selectable(false),
|
||||
Label::new(layout_job_up).selectable(false),
|
||||
)
|
||||
}
|
||||
|
||||
fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> (u64, String) {
|
||||
let input = input_in_bytes as f32 / timespan_in_s as f32;
|
||||
let mut magnitude = input.log(1024f32) as u32;
|
||||
|
||||
// let the base unit be KiB
|
||||
if magnitude < 1 {
|
||||
magnitude = 1;
|
||||
}
|
||||
|
||||
let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);
|
||||
let result = input / ((1u64) << (magnitude * 10)) as f32;
|
||||
|
||||
(
|
||||
input as u64,
|
||||
match base {
|
||||
Some(DataUnit::B) => format!("{result:.1} B"),
|
||||
Some(unit) => format!("{result:.1} {unit}iB"),
|
||||
None => String::from("Unknown data unit"),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn show_frame<R>(
|
||||
&self,
|
||||
selected: bool,
|
||||
auto_focus_fill: Option<Color32>,
|
||||
ui: &mut Ui,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) {
|
||||
if SelectableFrame::new_auto(selected, auto_focus_fill)
|
||||
.show(ui, add_contents)
|
||||
.clicked()
|
||||
&& let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn()
|
||||
{
|
||||
eprintln!("{error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Network {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let is_reversed = matches!(config.alignment, Some(Alignment::Right));
|
||||
|
||||
// widget spacing: make sure to use the same config to call the apply_on_widget function
|
||||
let mut render_config = config.clone();
|
||||
|
||||
if self.show_total_activity || self.show_activity {
|
||||
let (activity, total_activity) = self.network_activity();
|
||||
|
||||
if self.show_total_activity {
|
||||
for reading in &total_activity {
|
||||
render_config.apply_on_widget(false, ui, |ui| {
|
||||
let select_received = self.auto_select.is_some_and(|f| {
|
||||
f.total_received_over
|
||||
.is_some_and(|o| reading.received.value > o)
|
||||
});
|
||||
let select_transmitted = self.auto_select.is_some_and(|f| {
|
||||
f.total_transmitted_over
|
||||
.is_some_and(|o| reading.transmitted.value > o)
|
||||
});
|
||||
|
||||
let labels = self.reading_to_labels(
|
||||
select_received,
|
||||
select_transmitted,
|
||||
ctx,
|
||||
reading,
|
||||
config.clone(),
|
||||
);
|
||||
|
||||
if is_reversed {
|
||||
self.show_frame(
|
||||
select_transmitted,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.1),
|
||||
);
|
||||
self.show_frame(
|
||||
select_received,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.0),
|
||||
);
|
||||
} else {
|
||||
self.show_frame(
|
||||
select_received,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.0),
|
||||
);
|
||||
self.show_frame(
|
||||
select_transmitted,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.1),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_activity {
|
||||
for reading in &activity {
|
||||
render_config.apply_on_widget(false, ui, |ui| {
|
||||
let select_received = self.auto_select.is_some_and(|f| {
|
||||
f.received_over.is_some_and(|o| reading.received.value > o)
|
||||
});
|
||||
let select_transmitted = self.auto_select.is_some_and(|f| {
|
||||
f.transmitted_over
|
||||
.is_some_and(|o| reading.transmitted.value > o)
|
||||
});
|
||||
|
||||
let labels = self.reading_to_labels(
|
||||
select_received,
|
||||
select_transmitted,
|
||||
ctx,
|
||||
reading,
|
||||
config.clone(),
|
||||
);
|
||||
|
||||
if is_reversed {
|
||||
self.show_frame(
|
||||
select_transmitted,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.1),
|
||||
);
|
||||
self.show_frame(
|
||||
select_received,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.0),
|
||||
);
|
||||
} else {
|
||||
self.show_frame(
|
||||
select_received,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.0),
|
||||
);
|
||||
self.show_frame(
|
||||
select_transmitted,
|
||||
config.auto_select_fill,
|
||||
ui,
|
||||
|ui| ui.add(labels.1),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_default_interface {
|
||||
let mut self_default_interface = self.default_interface();
|
||||
|
||||
if !self_default_interface.is_empty() {
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::WIFI_HIGH.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
||||
self_default_interface.insert_str(0, "NET: ");
|
||||
}
|
||||
|
||||
layout_job.append(
|
||||
&self_default_interface,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
render_config.apply_on_widget(false, ui, |ui| {
|
||||
self.show_frame(false, None, ui, |ui| {
|
||||
ui.add(Label::new(layout_job).selectable(false))
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// widget spacing: pass on the config that was use for calling the apply_on_widget function
|
||||
*config = render_config.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum NetworkReadingFormat {
|
||||
Speed = 0,
|
||||
Total = 1,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ReadingValue {
|
||||
value: u64,
|
||||
pretty: String,
|
||||
}
|
||||
|
||||
impl From<(u64, String)> for ReadingValue {
|
||||
fn from(value: (u64, String)) -> Self {
|
||||
Self {
|
||||
value: value.0,
|
||||
pretty: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NetworkReading {
|
||||
format: NetworkReadingFormat,
|
||||
received: ReadingValue,
|
||||
transmitted: ReadingValue,
|
||||
}
|
||||
|
||||
impl NetworkReading {
|
||||
fn new(
|
||||
format: NetworkReadingFormat,
|
||||
received: ReadingValue,
|
||||
transmitted: ReadingValue,
|
||||
) -> Self {
|
||||
Self {
|
||||
format,
|
||||
received,
|
||||
transmitted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromPrimitive)]
|
||||
enum DataUnit {
|
||||
B = 0,
|
||||
K = 1,
|
||||
M = 2,
|
||||
G = 3,
|
||||
T = 4,
|
||||
P = 5,
|
||||
E = 6,
|
||||
Z = 7,
|
||||
Y = 8,
|
||||
}
|
||||
|
||||
impl fmt::Display for DataUnit {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
177
komorebi-bar/src/widgets/storage.rs
Normal file
177
komorebi-bar/src/widgets/storage.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use crate::bar::Alignment;
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::Disks;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Storage widget configuration
|
||||
pub struct StorageConfig {
|
||||
/// Enable the Storage widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval in seconds
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// Show disks that are read only
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = false)))]
|
||||
pub show_read_only_disks: Option<bool>,
|
||||
/// Show removable disks
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
|
||||
pub show_removable_disks: Option<bool>,
|
||||
/// Select when the current percentage is over this value [[1-100]]
|
||||
pub auto_select_over: Option<u8>,
|
||||
/// Hide when the current percentage is under this value [[1-100]]
|
||||
pub auto_hide_under: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<StorageConfig> for Storage {
|
||||
fn from(value: StorageConfig) -> Self {
|
||||
Self {
|
||||
enable: value.enable,
|
||||
disks: Disks::new_with_refreshed_list(),
|
||||
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||
show_read_only_disks: value.show_read_only_disks.unwrap_or(false),
|
||||
show_removable_disks: value.show_removable_disks.unwrap_or(true),
|
||||
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)),
|
||||
last_updated: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StorageDisk {
|
||||
label: String,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
pub struct Storage {
|
||||
pub enable: bool,
|
||||
disks: Disks,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
show_read_only_disks: bool,
|
||||
show_removable_disks: bool,
|
||||
auto_select_over: Option<u8>,
|
||||
auto_hide_under: Option<u8>,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
fn output(&mut self) -> Vec<StorageDisk> {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
self.disks.refresh(true);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
let mut disks = vec![];
|
||||
|
||||
for disk in &self.disks {
|
||||
if disk.is_read_only() && !self.show_read_only_disks {
|
||||
continue;
|
||||
}
|
||||
if disk.is_removable() && !self.show_removable_disks {
|
||||
continue;
|
||||
}
|
||||
let mount = disk.mount_point();
|
||||
let total = disk.total_space();
|
||||
let available = disk.available_space();
|
||||
let used = total - available;
|
||||
let percentage = ((used * 100) / total) as u8;
|
||||
|
||||
let hide = self.auto_hide_under.is_some_and(|u| percentage <= u);
|
||||
|
||||
if !hide {
|
||||
let selected = self.auto_select_over.is_some_and(|o| percentage >= o);
|
||||
|
||||
disks.push(StorageDisk {
|
||||
label: match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("{} {}%", mount.to_string_lossy(), percentage)
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage}%"),
|
||||
},
|
||||
selected,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
disks.sort_by(|a, b| a.label.cmp(&b.label));
|
||||
|
||||
disks
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Storage {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let mut output = self.output();
|
||||
let is_reversed = matches!(config.alignment, Some(Alignment::Right));
|
||||
|
||||
if is_reversed {
|
||||
output.reverse();
|
||||
}
|
||||
|
||||
for output in output {
|
||||
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::HARD_DRIVES.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let auto_focus_fill = config.auto_select_fill;
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
&& let Err(error) = Command::new("cmd.exe")
|
||||
.args([
|
||||
"/C",
|
||||
"explorer.exe",
|
||||
output.label.split(' ').collect::<Vec<&str>>()[0],
|
||||
])
|
||||
.spawn()
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ use crate::bar::Alignment;
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use chrono::Local;
|
||||
use chrono::NaiveTime;
|
||||
use chrono_tz::Tz;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
@@ -13,12 +15,64 @@ use eframe::egui::Stroke;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use schemars::JsonSchema;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
lazy_static! {
|
||||
static ref TIME_RANGES: Vec<(&'static str, NaiveTime)> = {
|
||||
vec![
|
||||
(
|
||||
egui_phosphor::regular::MOON,
|
||||
NaiveTime::from_hms_opt(0, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::ALARM,
|
||||
NaiveTime::from_hms_opt(6, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::BREAD,
|
||||
NaiveTime::from_hms_opt(6, 1, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::BARBELL,
|
||||
NaiveTime::from_hms_opt(6, 30, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::COFFEE,
|
||||
NaiveTime::from_hms_opt(8, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::CLOCK,
|
||||
NaiveTime::from_hms_opt(8, 30, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::HAMBURGER,
|
||||
NaiveTime::from_hms_opt(12, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::CLOCK_AFTERNOON,
|
||||
NaiveTime::from_hms_opt(12, 30, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::FORK_KNIFE,
|
||||
NaiveTime::from_hms_opt(18, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::MOON_STARS,
|
||||
NaiveTime::from_hms_opt(18, 30, 0).expect("invalid"),
|
||||
),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Time widget configuration
|
||||
pub struct TimeConfig {
|
||||
/// Enable the Time widget
|
||||
pub enable: bool,
|
||||
@@ -26,19 +80,47 @@ pub struct TimeConfig {
|
||||
pub format: TimeFormat,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
///
|
||||
/// Use a custom format to display additional information, i.e.:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Time": {
|
||||
/// "enable": true,
|
||||
/// "format": { "Custom": "%T %Z (Tokyo)" },
|
||||
/// "timezone": "Asia/Tokyo"
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
pub timezone: Option<String>,
|
||||
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00
|
||||
pub changing_icon: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<TimeConfig> for Time {
|
||||
fn from(value: TimeConfig) -> Self {
|
||||
// using 1 second made the widget look "less accurate" and lagging (especially having multiple with seconds).
|
||||
// This is still better than getting an update every frame
|
||||
let data_refresh_interval = 500;
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
format: value.format,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
timezone: value.timezone,
|
||||
changing_icon: value.changing_icon.unwrap_or_default(),
|
||||
data_refresh_interval_millis: data_refresh_interval,
|
||||
last_state: TimeOutput::new(),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_millis(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Time format
|
||||
pub enum TimeFormat {
|
||||
/// Twelve-hour format (with seconds)
|
||||
TwelveHour,
|
||||
@@ -53,6 +135,7 @@ pub enum TimeFormat {
|
||||
/// Twenty-four-hour format displayed as a binary clock with rectangles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)
|
||||
BinaryRectangle,
|
||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
@@ -82,20 +165,94 @@ impl TimeFormat {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TimeOutput {
|
||||
label: String,
|
||||
icon: String,
|
||||
}
|
||||
|
||||
impl TimeOutput {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
label: String::new(),
|
||||
icon: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Time {
|
||||
pub enable: bool,
|
||||
pub format: TimeFormat,
|
||||
label_prefix: LabelPrefix,
|
||||
timezone: Option<String>,
|
||||
changing_icon: bool,
|
||||
data_refresh_interval_millis: u64,
|
||||
last_state: TimeOutput,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
fn output(&mut self) -> String {
|
||||
chrono::Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string()
|
||||
fn output(&mut self) -> TimeOutput {
|
||||
let mut output = self.last_state.clone();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(self.last_updated)
|
||||
> Duration::from_millis(self.data_refresh_interval_millis)
|
||||
{
|
||||
let (formatted, current_time) = match &self.timezone {
|
||||
Some(timezone) => match timezone.parse::<Tz>() {
|
||||
Ok(tz) => {
|
||||
let dt = Local::now().with_timezone(&tz);
|
||||
(
|
||||
dt.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Some(dt.time()),
|
||||
)
|
||||
}
|
||||
Err(_) => (format!("Invalid timezone: {timezone:?}"), None),
|
||||
},
|
||||
None => {
|
||||
let dt = Local::now();
|
||||
(
|
||||
dt.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Some(dt.time()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if current_time.is_none() {
|
||||
return TimeOutput {
|
||||
label: formatted,
|
||||
icon: egui_phosphor::regular::WARNING_CIRCLE.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let current_range = match &self.changing_icon {
|
||||
true => TIME_RANGES
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|&(_, start)| current_time.unwrap() > *start)
|
||||
.cloned(),
|
||||
false => None,
|
||||
}
|
||||
.unwrap_or((egui_phosphor::regular::CLOCK, NaiveTime::default()));
|
||||
|
||||
output = TimeOutput {
|
||||
label: formatted,
|
||||
icon: current_range.0.to_string(),
|
||||
};
|
||||
|
||||
self.last_state.clone_from(&output);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn paint_binary_circle(
|
||||
@@ -109,36 +266,37 @@ impl Time {
|
||||
let full_height = size;
|
||||
let height = full_height / 4.0;
|
||||
let width = height;
|
||||
let offset = height / 2.0 - height / 8.0;
|
||||
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
|
||||
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
|
||||
let color = ctx.style().visuals.text_color();
|
||||
|
||||
let c = response.rect.center();
|
||||
let r = height / 2.0 - 0.5;
|
||||
|
||||
if number == 1 || number == 3 || number == 5 || number == 7 || number == 9 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r, color);
|
||||
} else {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r / 2.5, color);
|
||||
}
|
||||
|
||||
if number == 2 || number == 3 || number == 6 || number == 7 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r, color);
|
||||
} else {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r / 2.5, color);
|
||||
}
|
||||
|
||||
if number == 4 || number == 5 || number == 6 || number == 7 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r, color);
|
||||
} else if max_power > 2 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r / 2.5, color);
|
||||
}
|
||||
|
||||
if number == 8 || number == 9 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r, color);
|
||||
} else if max_power > 3 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r / 2.5, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +311,10 @@ impl Time {
|
||||
let full_height = size;
|
||||
let height = full_height / 4.0;
|
||||
let width = height * 1.5;
|
||||
let offset = height / 2.0 - height / 8.0;
|
||||
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
|
||||
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
|
||||
let color = ctx.style().visuals.text_color();
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
|
||||
@@ -173,22 +332,25 @@ impl Time {
|
||||
};
|
||||
|
||||
if max_power == 2 {
|
||||
let mut rect = response.rect.shrink(stroke.width);
|
||||
let mut rect = response
|
||||
.rect
|
||||
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
|
||||
rect.set_height(rect.height() - height * 2.0);
|
||||
rect = rect.translate(Vec2::new(0.0, height * 2.0));
|
||||
rect = rect.translate(Vec2::new(0.0, height * 2.0 + offset));
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
} else if max_power == 3 {
|
||||
let mut rect = response.rect.shrink(stroke.width);
|
||||
let mut rect = response
|
||||
.rect
|
||||
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
|
||||
rect.set_height(rect.height() - height);
|
||||
rect = rect.translate(Vec2::new(0.0, height));
|
||||
rect = rect.translate(Vec2::new(0.0, height + offset));
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
} else {
|
||||
painter.rect_stroke(
|
||||
response.rect.shrink(stroke.width),
|
||||
round_all,
|
||||
stroke,
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
let mut rect = response
|
||||
.rect
|
||||
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
|
||||
rect = rect.translate(Vec2::new(0.0, 0.0 + offset));
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
}
|
||||
|
||||
let mut rect_bin = response.rect;
|
||||
@@ -197,7 +359,7 @@ impl Time {
|
||||
if number == 1 || number == 5 || number == 9 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 3.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 3.0 + offset * 2.0)),
|
||||
round_bottom,
|
||||
color,
|
||||
);
|
||||
@@ -205,7 +367,7 @@ impl Time {
|
||||
if number == 2 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
|
||||
if max_power == 2 {
|
||||
round_top
|
||||
} else {
|
||||
@@ -217,7 +379,7 @@ impl Time {
|
||||
if number == 3 {
|
||||
rect_bin.set_height(height * 2.0);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
|
||||
round_bottom,
|
||||
color,
|
||||
);
|
||||
@@ -225,7 +387,7 @@ impl Time {
|
||||
if number == 4 || number == 5 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
|
||||
if max_power == 3 {
|
||||
round_top
|
||||
} else {
|
||||
@@ -237,7 +399,7 @@ impl Time {
|
||||
if number == 6 {
|
||||
rect_bin.set_height(height * 2.0);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
|
||||
if max_power == 3 {
|
||||
round_top
|
||||
} else {
|
||||
@@ -249,7 +411,7 @@ impl Time {
|
||||
if number == 7 {
|
||||
rect_bin.set_height(height * 3.0);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height + offset * 2.0)),
|
||||
if max_power == 3 {
|
||||
round_all
|
||||
} else {
|
||||
@@ -260,7 +422,11 @@ impl Time {
|
||||
}
|
||||
if number == 8 || number == 9 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(rect_bin.translate(Vec2::new(0.0, 0.0)), round_top, color);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(stroke.width, 0.0 + offset * 2.0)),
|
||||
round_top,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,15 +435,13 @@ impl BarWidget for Time {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let mut output = self.output();
|
||||
if !output.is_empty() {
|
||||
let use_binary_circle = output.starts_with('c');
|
||||
let use_binary_rectangle = output.starts_with('r');
|
||||
if !output.label.is_empty() {
|
||||
let use_binary_circle = output.label.starts_with('c');
|
||||
let use_binary_rectangle = output.label.starts_with('r');
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::CLOCK.to_string()
|
||||
}
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => output.icon,
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
@@ -286,12 +450,12 @@ impl BarWidget for Time {
|
||||
);
|
||||
|
||||
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
||||
output.insert_str(0, "TIME: ");
|
||||
output.label.insert_str(0, "TIME: ");
|
||||
}
|
||||
|
||||
if !use_binary_circle && !use_binary_rectangle {
|
||||
layout_job.append(
|
||||
&output,
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
@@ -314,9 +478,9 @@ impl BarWidget for Time {
|
||||
|
||||
if use_binary_circle || use_binary_rectangle {
|
||||
let ordered_output = if is_reversed {
|
||||
output.chars().rev().collect()
|
||||
output.label.chars().rev().collect()
|
||||
} else {
|
||||
output
|
||||
output.label
|
||||
};
|
||||
|
||||
for (section_index, section) in
|
||||
@@ -1,25 +1,27 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Update widget configuration
|
||||
pub struct UpdateConfig {
|
||||
/// Enable the Update widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval (default: 12 hours)
|
||||
/// Data refresh interval in hours
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 12)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
@@ -140,16 +142,14 @@ impl BarWidget for Update {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
{
|
||||
if let Err(error) = Command::new("explorer.exe")
|
||||
&& let Err(error) = Command::new("explorer.exe")
|
||||
.args([format!(
|
||||
"https://github.com/LGUG2Z/komorebi/releases/v{}",
|
||||
self.latest_version
|
||||
)])
|
||||
.spawn()
|
||||
{
|
||||
eprintln!("{}", error)
|
||||
}
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
119
komorebi-bar/src/widgets/widget.rs
Normal file
119
komorebi-bar/src/widgets/widget.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use crate::render::RenderConfig;
|
||||
use crate::widgets::applications::Applications;
|
||||
use crate::widgets::applications::ApplicationsConfig;
|
||||
use crate::widgets::battery::Battery;
|
||||
use crate::widgets::battery::BatteryConfig;
|
||||
use crate::widgets::cpu::Cpu;
|
||||
use crate::widgets::cpu::CpuConfig;
|
||||
use crate::widgets::date::Date;
|
||||
use crate::widgets::date::DateConfig;
|
||||
use crate::widgets::keyboard::Keyboard;
|
||||
use crate::widgets::keyboard::KeyboardConfig;
|
||||
use crate::widgets::komorebi::Komorebi;
|
||||
use crate::widgets::komorebi::KomorebiConfig;
|
||||
use crate::widgets::media::Media;
|
||||
use crate::widgets::media::MediaConfig;
|
||||
use crate::widgets::memory::Memory;
|
||||
use crate::widgets::memory::MemoryConfig;
|
||||
use crate::widgets::network::Network;
|
||||
use crate::widgets::network::NetworkConfig;
|
||||
use crate::widgets::storage::Storage;
|
||||
use crate::widgets::storage::StorageConfig;
|
||||
use crate::widgets::time::Time;
|
||||
use crate::widgets::time::TimeConfig;
|
||||
use crate::widgets::update::Update;
|
||||
use crate::widgets::update::UpdateConfig;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Ui;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub trait BarWidget {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Widget configuration
|
||||
pub enum WidgetConfig {
|
||||
/// Applications widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Applications"))]
|
||||
Applications(ApplicationsConfig),
|
||||
/// Battery widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Battery"))]
|
||||
Battery(BatteryConfig),
|
||||
/// CPU widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Cpu"))]
|
||||
Cpu(CpuConfig),
|
||||
/// Date widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Date"))]
|
||||
Date(DateConfig),
|
||||
/// Keyboard widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Keyboard"))]
|
||||
Keyboard(KeyboardConfig),
|
||||
/// Komorebi widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Komorebi"))]
|
||||
Komorebi(KomorebiConfig),
|
||||
/// Media widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Media"))]
|
||||
Media(MediaConfig),
|
||||
/// Memory widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Memory"))]
|
||||
Memory(MemoryConfig),
|
||||
/// Network widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Network"))]
|
||||
Network(NetworkConfig),
|
||||
/// Storage widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Storage"))]
|
||||
Storage(StorageConfig),
|
||||
/// Time widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Time"))]
|
||||
Time(TimeConfig),
|
||||
/// Update widget configuration
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Update"))]
|
||||
Update(UpdateConfig),
|
||||
}
|
||||
|
||||
impl WidgetConfig {
|
||||
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
|
||||
match self {
|
||||
WidgetConfig::Applications(config) => Box::new(Applications::from(config)),
|
||||
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
|
||||
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
|
||||
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
|
||||
WidgetConfig::Keyboard(config) => Box::new(Keyboard::from(*config)),
|
||||
WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)),
|
||||
WidgetConfig::Media(config) => Box::new(Media::from(*config)),
|
||||
WidgetConfig::Memory(config) => Box::new(Memory::from(*config)),
|
||||
WidgetConfig::Network(config) => Box::new(Network::from(*config)),
|
||||
WidgetConfig::Storage(config) => Box::new(Storage::from(*config)),
|
||||
WidgetConfig::Time(config) => Box::new(Time::from(config.clone())),
|
||||
WidgetConfig::Update(config) => Box::new(Update::from(*config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
match self {
|
||||
WidgetConfig::Applications(config) => config.enable,
|
||||
WidgetConfig::Battery(config) => config.enable,
|
||||
WidgetConfig::Cpu(config) => config.enable,
|
||||
WidgetConfig::Date(config) => config.enable,
|
||||
WidgetConfig::Keyboard(config) => config.enable,
|
||||
WidgetConfig::Komorebi(config) => {
|
||||
config.workspaces.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.layout.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.focused_container.as_ref().is_some_and(|w| w.enable)
|
||||
|| config
|
||||
.configuration_switcher
|
||||
.as_ref()
|
||||
.is_some_and(|w| w.enable)
|
||||
}
|
||||
WidgetConfig::Media(config) => config.enable,
|
||||
WidgetConfig::Memory(config) => config.enable,
|
||||
WidgetConfig::Network(config) => config.enable,
|
||||
WidgetConfig::Storage(config) => config.enable,
|
||||
WidgetConfig::Time(config) => config.enable,
|
||||
WidgetConfig::Update(config) => config.enable,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
[package]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
version = "0.1.40"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi = { path = "../komorebi" }
|
||||
komorebi = { path = "../komorebi", default-features = false }
|
||||
|
||||
uds_windows = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["komorebi/default"]
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
pub use komorebi::animation::prefix::AnimationPrefix;
|
||||
pub use komorebi::AnimationsConfig;
|
||||
pub use komorebi::AppSpecificConfigurationPath;
|
||||
pub use komorebi::AspectRatio;
|
||||
pub use komorebi::BorderColours;
|
||||
pub use komorebi::Colour;
|
||||
pub use komorebi::CrossBoundaryBehaviour;
|
||||
pub use komorebi::GridLayoutOptions;
|
||||
pub use komorebi::KomorebiTheme;
|
||||
pub use komorebi::LayoutOptions;
|
||||
pub use komorebi::MonitorConfig;
|
||||
pub use komorebi::Notification;
|
||||
pub use komorebi::NotificationEvent;
|
||||
pub use komorebi::Placement;
|
||||
pub use komorebi::PredefinedAspectRatio;
|
||||
pub use komorebi::Rgb;
|
||||
pub use komorebi::RuleDebug;
|
||||
pub use komorebi::ScrollingLayoutOptions;
|
||||
pub use komorebi::StackbarConfig;
|
||||
pub use komorebi::StaticConfig;
|
||||
pub use komorebi::SubscribeOptions;
|
||||
pub use komorebi::TabsConfig;
|
||||
pub use komorebi::ThemeOptions;
|
||||
pub use komorebi::VirtualDesktopNotification;
|
||||
pub use komorebi::Wallpaper;
|
||||
pub use komorebi::WindowContainerBehaviour;
|
||||
pub use komorebi::WindowHandlingBehaviour;
|
||||
pub use komorebi::WindowsApi;
|
||||
pub use komorebi::WorkspaceConfig;
|
||||
pub use komorebi::animation::PerAnimationPrefixConfig;
|
||||
pub use komorebi::animation::prefix::AnimationPrefix;
|
||||
pub use komorebi::asc::ApplicationSpecificConfiguration;
|
||||
pub use komorebi::colour::Colour;
|
||||
pub use komorebi::colour::Rgb;
|
||||
pub use komorebi::border_manager::BorderInfo;
|
||||
pub use komorebi::config_generation::ApplicationConfiguration;
|
||||
pub use komorebi::config_generation::IdWithIdentifier;
|
||||
pub use komorebi::config_generation::IdWithIdentifierAndComment;
|
||||
pub use komorebi::config_generation::MatchingRule;
|
||||
pub use komorebi::config_generation::MatchingStrategy;
|
||||
pub use komorebi::container::Container;
|
||||
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
|
||||
pub use komorebi::core::resolve_home_path;
|
||||
pub use komorebi::core::AnimationStyle;
|
||||
pub use komorebi::core::ApplicationIdentifier;
|
||||
pub use komorebi::core::Arrangement;
|
||||
@@ -28,6 +53,7 @@ pub use komorebi::core::CustomLayout;
|
||||
pub use komorebi::core::CycleDirection;
|
||||
pub use komorebi::core::DefaultLayout;
|
||||
pub use komorebi::core::Direction;
|
||||
pub use komorebi::core::FloatingLayerBehaviour;
|
||||
pub use komorebi::core::FocusFollowsMouseImplementation;
|
||||
pub use komorebi::core::HidingBehaviour;
|
||||
pub use komorebi::core::Layout;
|
||||
@@ -42,36 +68,24 @@ pub use komorebi::core::StackbarLabel;
|
||||
pub use komorebi::core::StackbarMode;
|
||||
pub use komorebi::core::StateQuery;
|
||||
pub use komorebi::core::WindowKind;
|
||||
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
|
||||
pub use komorebi::core::replace_env_in_path;
|
||||
pub use komorebi::monitor::Monitor;
|
||||
pub use komorebi::monitor_reconciliator::MonitorNotification;
|
||||
pub use komorebi::ring::Ring;
|
||||
pub use komorebi::splash;
|
||||
pub use komorebi::state::GlobalState;
|
||||
pub use komorebi::state::State;
|
||||
pub use komorebi::win32_display_data;
|
||||
pub use komorebi::window::Window;
|
||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||
pub use komorebi::workspace::Workspace;
|
||||
pub use komorebi::workspace::WorkspaceGlobals;
|
||||
pub use komorebi::workspace::WorkspaceLayer;
|
||||
pub use komorebi::AnimationsConfig;
|
||||
pub use komorebi::AspectRatio;
|
||||
pub use komorebi::BorderColours;
|
||||
pub use komorebi::CrossBoundaryBehaviour;
|
||||
pub use komorebi::GlobalState;
|
||||
pub use komorebi::KomorebiTheme;
|
||||
pub use komorebi::MonitorConfig;
|
||||
pub use komorebi::Notification;
|
||||
pub use komorebi::NotificationEvent;
|
||||
pub use komorebi::PredefinedAspectRatio;
|
||||
pub use komorebi::RuleDebug;
|
||||
pub use komorebi::StackbarConfig;
|
||||
pub use komorebi::State;
|
||||
pub use komorebi::StaticConfig;
|
||||
pub use komorebi::SubscribeOptions;
|
||||
pub use komorebi::TabsConfig;
|
||||
pub use komorebi::WindowContainerBehaviour;
|
||||
pub use komorebi::WindowsApi;
|
||||
pub use komorebi::WorkspaceConfig;
|
||||
|
||||
use komorebi::DATA_DIR;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
@@ -89,12 +103,15 @@ pub fn send_message(message: &SocketMessage) -> std::io::Result<()> {
|
||||
stream.write_all(serde_json::to_string(message)?.as_bytes())
|
||||
}
|
||||
|
||||
pub fn send_batch(messages: impl IntoIterator<Item = SocketMessage>) -> std::io::Result<()> {
|
||||
pub fn send_batch<Q>(messages: impl IntoIterator<Item = Q>) -> std::io::Result<()>
|
||||
where
|
||||
Q: Borrow<SocketMessage>,
|
||||
{
|
||||
let socket = DATA_DIR.join(KOMOREBI);
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.set_write_timeout(Some(Duration::from_secs(1)))?;
|
||||
let msgs = messages.into_iter().fold(String::new(), |mut s, m| {
|
||||
if let Ok(m_str) = serde_json::to_string(&m) {
|
||||
if let Ok(m_str) = serde_json::to_string(m.borrow()) {
|
||||
s.push_str(&m_str);
|
||||
s.push('\n');
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
[package]
|
||||
name = "komorebi-gui"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
version = "0.1.40"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi-client = { path = "../komorebi-client" }
|
||||
komorebi-client = { path = "../komorebi-client", default-features = false }
|
||||
|
||||
eframe = { workspace = true }
|
||||
egui_extras = { workspace = true }
|
||||
random_word = { version = "0.4", features = ["en"] }
|
||||
random_word = { version = "0.5", features = ["en"] }
|
||||
serde_json = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![warn(clippy::all)]
|
||||
|
||||
use eframe::egui;
|
||||
use eframe::egui::color_picker::Alpha;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::ViewportBuilder;
|
||||
use eframe::egui::color_picker::Alpha;
|
||||
use komorebi_client::BorderStyle;
|
||||
use komorebi_client::Colour;
|
||||
use komorebi_client::DefaultLayout;
|
||||
@@ -41,7 +41,9 @@ struct BorderColours {
|
||||
single: Color32,
|
||||
stack: Color32,
|
||||
monocle: Color32,
|
||||
floating: Color32,
|
||||
unfocused: Color32,
|
||||
unfocused_locked: Color32,
|
||||
}
|
||||
|
||||
struct BorderConfig {
|
||||
@@ -76,8 +78,8 @@ impl From<&komorebi_client::Monitor> for MonitorConfig {
|
||||
}
|
||||
|
||||
Self {
|
||||
size: *value.size(),
|
||||
work_area_offset: value.work_area_offset().unwrap_or_default(),
|
||||
size: value.size,
|
||||
work_area_offset: value.work_area_offset.unwrap_or_default(),
|
||||
workspaces,
|
||||
}
|
||||
}
|
||||
@@ -93,22 +95,22 @@ struct WorkspaceConfig {
|
||||
|
||||
impl From<&komorebi_client::Workspace> for WorkspaceConfig {
|
||||
fn from(value: &komorebi_client::Workspace) -> Self {
|
||||
let layout = match value.layout() {
|
||||
Layout::Default(layout) => *layout,
|
||||
let layout = match value.layout {
|
||||
Layout::Default(layout) => layout,
|
||||
Layout::Custom(_) => DefaultLayout::BSP,
|
||||
};
|
||||
|
||||
let name = value
|
||||
.name()
|
||||
.name
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
|
||||
.unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());
|
||||
|
||||
Self {
|
||||
layout,
|
||||
name,
|
||||
tile: *value.tile(),
|
||||
workspace_padding: value.workspace_padding().unwrap_or(20),
|
||||
container_padding: value.container_padding().unwrap_or(20),
|
||||
tile: value.tile,
|
||||
workspace_padding: value.workspace_padding.unwrap_or(20),
|
||||
container_padding: value.container_padding.unwrap_or(20),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +156,9 @@ impl KomorebiGui {
|
||||
single: colour32(global_state.border_colours.single),
|
||||
stack: colour32(global_state.border_colours.stack),
|
||||
monocle: colour32(global_state.border_colours.monocle),
|
||||
floating: colour32(global_state.border_colours.floating),
|
||||
unfocused: colour32(global_state.border_colours.unfocused),
|
||||
unfocused_locked: colour32(global_state.border_colours.unfocused_locked),
|
||||
};
|
||||
|
||||
let border_config = BorderConfig {
|
||||
@@ -243,7 +247,7 @@ impl eframe::App for KomorebiGui {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ctx.set_pixels_per_point(2.0);
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.set_width(ctx.screen_rect().width());
|
||||
ui.set_width(ctx.content_rect().width());
|
||||
ui.collapsing("Debugging", |ui| {
|
||||
ui.collapsing("Window Rules", |ui| {
|
||||
let window = Window::from(self.debug_hwnd);
|
||||
@@ -377,6 +381,22 @@ impl eframe::App for KomorebiGui {
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Floating", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.border_config.border_colours.floating,
|
||||
Alpha::Opaque,
|
||||
) {
|
||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
||||
WindowKind::Floating,
|
||||
self.border_config.border_colours.floating.r() as u32,
|
||||
self.border_config.border_colours.floating.g() as u32,
|
||||
self.border_config.border_colours.floating.b() as u32,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Unfocused", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
@@ -391,6 +411,22 @@ impl eframe::App for KomorebiGui {
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Unfocused Locked", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.border_config.border_colours.unfocused_locked,
|
||||
Alpha::Opaque,
|
||||
) {
|
||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
||||
WindowKind::UnfocusedLocked,
|
||||
self.border_config.border_colours.unfocused_locked.r() as u32,
|
||||
self.border_config.border_colours.unfocused_locked.g() as u32,
|
||||
self.border_config.border_colours.unfocused_locked.b() as u32,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -401,7 +437,7 @@ impl eframe::App for KomorebiGui {
|
||||
BorderStyle::Square,
|
||||
] {
|
||||
if ui
|
||||
.add(egui::SelectableLabel::new(
|
||||
.add(egui::Button::selectable(
|
||||
self.border_config.border_style == option,
|
||||
option.to_string(),
|
||||
))
|
||||
@@ -458,7 +494,7 @@ impl eframe::App for KomorebiGui {
|
||||
StackbarMode::Always,
|
||||
] {
|
||||
if ui
|
||||
.add(egui::SelectableLabel::new(
|
||||
.add(egui::Button::selectable(
|
||||
self.stackbar_config.mode == option,
|
||||
option.to_string(),
|
||||
))
|
||||
@@ -477,7 +513,7 @@ impl eframe::App for KomorebiGui {
|
||||
ui.collapsing("Label", |ui| {
|
||||
for option in [StackbarLabel::Process, StackbarLabel::Title] {
|
||||
if ui
|
||||
.add(egui::SelectableLabel::new(
|
||||
.add(egui::Button::selectable(
|
||||
self.stackbar_config.label == option,
|
||||
option.to_string(),
|
||||
))
|
||||
@@ -736,7 +772,7 @@ impl eframe::App for KomorebiGui {
|
||||
DefaultLayout::Grid,
|
||||
] {
|
||||
if ui
|
||||
.add(egui::SelectableLabel::new(
|
||||
.add(egui::Button::selectable(
|
||||
workspace.layout == option,
|
||||
option.to_string(),
|
||||
))
|
||||
|
||||
11
komorebi-shortcuts/Cargo.toml
Normal file
11
komorebi-shortcuts/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "komorebi-shortcuts"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
whkd-parser = { git = "https://github.com/LGUG2Z/whkd", rev = "v0.2.10" }
|
||||
whkd-core = { git = "https://github.com/LGUG2Z/whkd", rev = "v0.2.10" }
|
||||
|
||||
eframe = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
98
komorebi-shortcuts/src/main.rs
Normal file
98
komorebi-shortcuts/src/main.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use eframe::egui::ViewportBuilder;
|
||||
use std::path::PathBuf;
|
||||
use whkd_core::Whkdrc;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Quicklook {
|
||||
whkdrc: Option<Whkdrc>,
|
||||
filter: String,
|
||||
}
|
||||
|
||||
impl Quicklook {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
// Restore app state using cc.storage (requires the "persistence" feature).
|
||||
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
// for e.g. egui::PaintCallback.
|
||||
let mut home = std::env::var("WHKD_CONFIG_HOME").map_or_else(
|
||||
|_| {
|
||||
dirs::home_dir()
|
||||
.expect("no home directory found")
|
||||
.join(".config")
|
||||
},
|
||||
|home_path| {
|
||||
let home = PathBuf::from(&home_path);
|
||||
|
||||
if home.as_path().is_dir() {
|
||||
home
|
||||
} else {
|
||||
panic!(
|
||||
"$Env:WHKD_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
home.push("whkdrc");
|
||||
|
||||
Self {
|
||||
whkdrc: whkd_parser::load(&home).ok(),
|
||||
filter: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Quicklook {
|
||||
fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {
|
||||
eframe::egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.set_max_width(ui.available_width());
|
||||
ui.set_max_height(ui.available_height());
|
||||
eframe::egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
eframe::egui::Grid::new("grid")
|
||||
.num_columns(2)
|
||||
.striped(true)
|
||||
.spacing([40.0, 4.0])
|
||||
.min_col_width(ui.available_width() / 2.0 - 20.0)
|
||||
.show(ui, |ui| {
|
||||
if let Some(whkdrc) = &self.whkdrc {
|
||||
ui.label("Filter");
|
||||
ui.add(
|
||||
eframe::egui::text_edit::TextEdit::singleline(&mut self.filter)
|
||||
.hint_text("Filter by command...")
|
||||
.background_color(ctx.style().visuals.faint_bg_color),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
for binding in &whkdrc.bindings {
|
||||
let keys = binding.keys.join(" + ");
|
||||
if self.filter.is_empty() || binding.command.contains(&self.filter)
|
||||
{
|
||||
ui.label(keys);
|
||||
ui.label(&binding.command);
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let viewport_builder = ViewportBuilder::default()
|
||||
.with_resizable(true)
|
||||
.with_decorations(false);
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: viewport_builder,
|
||||
centered: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"komorebi-shortcuts",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(Quicklook::new(cc)))),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
[package]
|
||||
name = "komorebi-themes"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
version = "0.1.40"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "96f26c88d83781f234d42222293ec73d23a39ad8" }
|
||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
|
||||
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
|
||||
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "b9e26b31f7a0e7ed239b14e5317e95d1bdc544bd" }
|
||||
#catppuccin-egui = { version = "5", default-features = false, features = ["egui32"] }
|
||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a", default-features = false, features = [
|
||||
"egui33",
|
||||
] }
|
||||
eframe = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_variant = "0.1"
|
||||
strum = { workspace = true }
|
||||
hex_color = { version = "3", features = ["serde"] }
|
||||
flavours = { git = "https://github.com/LGUG2Z/flavours", version = "0.7.2" }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use hex_color::HexColor;
|
||||
use komorebi_themes::Color32;
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::schema::InstanceType;
|
||||
use schemars::schema::Schema;
|
||||
use schemars::schema::SchemaObject;
|
||||
use schemars::JsonSchema;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::Schema;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::SchemaGenerator;
|
||||
|
||||
use crate::Color32;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
/// Colour representation
|
||||
pub enum Colour {
|
||||
/// Colour represented as RGB
|
||||
Rgb(Rgb),
|
||||
@@ -51,21 +53,22 @@ impl From<Colour> for Color32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Colour represented as a Hex string
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Hex(HexColor);
|
||||
pub struct Hex(pub HexColor);
|
||||
|
||||
impl JsonSchema for Hex {
|
||||
fn schema_name() -> String {
|
||||
String::from("Hex")
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for Hex {
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("Hex")
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
format: Some("color-hex".to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
schemars::json_schema!({
|
||||
"type": "string",
|
||||
"format": "color-hex",
|
||||
"description": "Colour represented as a Hex string"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +81,9 @@ impl From<Colour> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Colour represented as RGB
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u32,
|
||||
80
komorebi-themes/src/generator.rs
Normal file
80
komorebi-themes/src/generator.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::Base16ColourPalette;
|
||||
use crate::colour::Colour;
|
||||
use crate::colour::Hex;
|
||||
use hex_color::HexColor;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Theme variant
|
||||
pub enum ThemeVariant {
|
||||
#[default]
|
||||
/// Dark variant
|
||||
Dark,
|
||||
/// Light variant
|
||||
Light,
|
||||
}
|
||||
|
||||
impl Display for ThemeVariant {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ThemeVariant::Dark => write!(f, "dark"),
|
||||
ThemeVariant::Light => write!(f, "light"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThemeVariant> for flavours::operations::generate::Mode {
|
||||
fn from(value: ThemeVariant) -> Self {
|
||||
match value {
|
||||
ThemeVariant::Dark => Self::Dark,
|
||||
ThemeVariant::Light => Self::Light,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_base16_palette(
|
||||
image_path: &Path,
|
||||
variant: ThemeVariant,
|
||||
) -> Result<Base16ColourPalette, hex_color::ParseHexColorError> {
|
||||
Base16ColourPalette::try_from(
|
||||
&flavours::operations::generate::generate(image_path, variant.into(), false)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
impl TryFrom<&VecDeque<String>> for Base16ColourPalette {
|
||||
type Error = hex_color::ParseHexColorError;
|
||||
|
||||
fn try_from(value: &VecDeque<String>) -> Result<Self, Self::Error> {
|
||||
let fixed = value.iter().map(|s| format!("#{s}")).collect::<Vec<_>>();
|
||||
if fixed.len() != 16 {
|
||||
return Err(hex_color::ParseHexColorError::Empty);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
base_00: Colour::Hex(Hex(HexColor::parse(&fixed[0])?)),
|
||||
base_01: Colour::Hex(Hex(HexColor::parse(&fixed[1])?)),
|
||||
base_02: Colour::Hex(Hex(HexColor::parse(&fixed[2])?)),
|
||||
base_03: Colour::Hex(Hex(HexColor::parse(&fixed[3])?)),
|
||||
base_04: Colour::Hex(Hex(HexColor::parse(&fixed[4])?)),
|
||||
base_05: Colour::Hex(Hex(HexColor::parse(&fixed[5])?)),
|
||||
base_06: Colour::Hex(Hex(HexColor::parse(&fixed[6])?)),
|
||||
base_07: Colour::Hex(Hex(HexColor::parse(&fixed[7])?)),
|
||||
base_08: Colour::Hex(Hex(HexColor::parse(&fixed[8])?)),
|
||||
base_09: Colour::Hex(Hex(HexColor::parse(&fixed[9])?)),
|
||||
base_0a: Colour::Hex(Hex(HexColor::parse(&fixed[10])?)),
|
||||
base_0b: Colour::Hex(Hex(HexColor::parse(&fixed[11])?)),
|
||||
base_0c: Colour::Hex(Hex(HexColor::parse(&fixed[12])?)),
|
||||
base_0d: Colour::Hex(Hex(HexColor::parse(&fixed[13])?)),
|
||||
base_0e: Colour::Hex(Hex(HexColor::parse(&fixed[14])?)),
|
||||
base_0f: Colour::Hex(Hex(HexColor::parse(&fixed[15])?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,196 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
pub mod colour;
|
||||
mod generator;
|
||||
|
||||
pub use generator::ThemeVariant;
|
||||
pub use generator::generate_base16_palette;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::colour::Colour;
|
||||
pub use base16_egui_themes::Base16;
|
||||
pub use catppuccin_egui;
|
||||
pub use eframe::egui::Color32;
|
||||
use eframe::egui::Shadow;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::Style;
|
||||
use eframe::egui::Visuals;
|
||||
use eframe::egui::style::Selection;
|
||||
use eframe::egui::style::WidgetVisuals;
|
||||
use eframe::egui::style::Widgets;
|
||||
use serde_variant::to_variant_name;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
/// Theme
|
||||
pub enum Theme {
|
||||
/// A theme from catppuccin-egui
|
||||
/// Theme from catppuccin-egui
|
||||
Catppuccin {
|
||||
name: Catppuccin,
|
||||
accent: Option<CatppuccinValue>,
|
||||
},
|
||||
/// A theme from base16-egui-themes
|
||||
/// Theme from base16-egui-themes
|
||||
Base16 {
|
||||
name: Base16,
|
||||
accent: Option<Base16Value>,
|
||||
},
|
||||
/// Custom base16 palette
|
||||
Custom {
|
||||
palette: Box<Base16ColourPalette>,
|
||||
accent: Option<Base16Value>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
/// Base16 colour palette: https://github.com/chriskempson/base16
|
||||
pub struct Base16ColourPalette {
|
||||
/// Base00
|
||||
pub base_00: Colour,
|
||||
/// Base01
|
||||
pub base_01: Colour,
|
||||
/// Base02
|
||||
pub base_02: Colour,
|
||||
/// Base03
|
||||
pub base_03: Colour,
|
||||
/// Base04
|
||||
pub base_04: Colour,
|
||||
/// Base05
|
||||
pub base_05: Colour,
|
||||
/// Base06
|
||||
pub base_06: Colour,
|
||||
/// Base07
|
||||
pub base_07: Colour,
|
||||
/// Base08
|
||||
pub base_08: Colour,
|
||||
/// Base09
|
||||
pub base_09: Colour,
|
||||
/// Base0A
|
||||
pub base_0a: Colour,
|
||||
/// Base0B
|
||||
pub base_0b: Colour,
|
||||
/// Base0C
|
||||
pub base_0c: Colour,
|
||||
/// Base0D
|
||||
pub base_0d: Colour,
|
||||
/// Base0E
|
||||
pub base_0e: Colour,
|
||||
/// Base0F
|
||||
pub base_0f: Colour,
|
||||
}
|
||||
|
||||
impl Base16ColourPalette {
|
||||
pub fn background(self) -> Color32 {
|
||||
self.base_01.into()
|
||||
}
|
||||
pub fn style(self) -> Style {
|
||||
let original = Style::default();
|
||||
Style {
|
||||
visuals: Visuals {
|
||||
widgets: Widgets {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: self.base_01.into(),
|
||||
weak_bg_fill: self.base_01.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.widgets.noninteractive.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_05.into(),
|
||||
..original.visuals.widgets.noninteractive.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.noninteractive
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
|
||||
..original.visuals.widgets.inactive.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_05.into(),
|
||||
..original.visuals.widgets.inactive.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.inactive
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_03.into(),
|
||||
..original.visuals.widgets.hovered.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.hovered.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.hovered
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_03.into(),
|
||||
..original.visuals.widgets.hovered.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.hovered.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.active
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: self.base_01.into(),
|
||||
weak_bg_fill: self.base_01.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.widgets.open.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.open.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.open
|
||||
},
|
||||
},
|
||||
selection: Selection {
|
||||
bg_fill: self.base_02.into(),
|
||||
stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.selection.stroke
|
||||
},
|
||||
},
|
||||
hyperlink_color: self.base_08.into(),
|
||||
faint_bg_color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
|
||||
extreme_bg_color: self.base_00.into(),
|
||||
code_bg_color: self.base_02.into(),
|
||||
warn_fg_color: self.base_0c.into(),
|
||||
error_fg_color: self.base_0b.into(),
|
||||
window_shadow: Shadow {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
|
||||
..original.visuals.window_shadow
|
||||
},
|
||||
window_fill: self.base_01.into(),
|
||||
window_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.window_stroke
|
||||
},
|
||||
panel_fill: self.base_01.into(),
|
||||
popup_shadow: Shadow {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
|
||||
..original.visuals.popup_shadow
|
||||
},
|
||||
..original.visuals
|
||||
},
|
||||
..original
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
@@ -45,59 +211,110 @@ impl Theme {
|
||||
.to_string()
|
||||
})
|
||||
.collect(),
|
||||
Theme::Custom { .. } => vec!["Custom".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
||||
/// Base16 value
|
||||
pub enum Base16Value {
|
||||
/// Base00
|
||||
Base00,
|
||||
/// Base01
|
||||
Base01,
|
||||
/// Base02
|
||||
Base02,
|
||||
/// Base03
|
||||
Base03,
|
||||
/// Base04
|
||||
Base04,
|
||||
/// Base05
|
||||
Base05,
|
||||
/// Base06
|
||||
#[default]
|
||||
Base06,
|
||||
/// Base07
|
||||
Base07,
|
||||
/// Base08
|
||||
Base08,
|
||||
/// Base09
|
||||
Base09,
|
||||
/// Base0A
|
||||
Base0A,
|
||||
/// Base0B
|
||||
Base0B,
|
||||
/// Base0C
|
||||
Base0C,
|
||||
/// Base0D
|
||||
Base0D,
|
||||
/// Base0E
|
||||
Base0E,
|
||||
/// Base0F
|
||||
Base0F,
|
||||
}
|
||||
|
||||
/// Wrapper around a Base16 colour palette
|
||||
pub enum Base16Wrapper {
|
||||
/// Predefined Base16 colour palette
|
||||
Base16(Base16),
|
||||
/// Custom Base16 colour palette
|
||||
Custom(Box<Base16ColourPalette>),
|
||||
}
|
||||
|
||||
impl Base16Value {
|
||||
pub fn color32(&self, theme: Base16) -> Color32 {
|
||||
match self {
|
||||
Base16Value::Base00 => theme.base00(),
|
||||
Base16Value::Base01 => theme.base01(),
|
||||
Base16Value::Base02 => theme.base02(),
|
||||
Base16Value::Base03 => theme.base03(),
|
||||
Base16Value::Base04 => theme.base04(),
|
||||
Base16Value::Base05 => theme.base05(),
|
||||
Base16Value::Base06 => theme.base06(),
|
||||
Base16Value::Base07 => theme.base07(),
|
||||
Base16Value::Base08 => theme.base08(),
|
||||
Base16Value::Base09 => theme.base09(),
|
||||
Base16Value::Base0A => theme.base0a(),
|
||||
Base16Value::Base0B => theme.base0b(),
|
||||
Base16Value::Base0C => theme.base0c(),
|
||||
Base16Value::Base0D => theme.base0d(),
|
||||
Base16Value::Base0E => theme.base0e(),
|
||||
Base16Value::Base0F => theme.base0f(),
|
||||
pub fn color32(&self, theme: Base16Wrapper) -> Color32 {
|
||||
match theme {
|
||||
Base16Wrapper::Base16(theme) => match self {
|
||||
Base16Value::Base00 => theme.base00(),
|
||||
Base16Value::Base01 => theme.base01(),
|
||||
Base16Value::Base02 => theme.base02(),
|
||||
Base16Value::Base03 => theme.base03(),
|
||||
Base16Value::Base04 => theme.base04(),
|
||||
Base16Value::Base05 => theme.base05(),
|
||||
Base16Value::Base06 => theme.base06(),
|
||||
Base16Value::Base07 => theme.base07(),
|
||||
Base16Value::Base08 => theme.base08(),
|
||||
Base16Value::Base09 => theme.base09(),
|
||||
Base16Value::Base0A => theme.base0a(),
|
||||
Base16Value::Base0B => theme.base0b(),
|
||||
Base16Value::Base0C => theme.base0c(),
|
||||
Base16Value::Base0D => theme.base0d(),
|
||||
Base16Value::Base0E => theme.base0e(),
|
||||
Base16Value::Base0F => theme.base0f(),
|
||||
},
|
||||
Base16Wrapper::Custom(colours) => match self {
|
||||
Base16Value::Base00 => colours.base_00.into(),
|
||||
Base16Value::Base01 => colours.base_01.into(),
|
||||
Base16Value::Base02 => colours.base_02.into(),
|
||||
Base16Value::Base03 => colours.base_03.into(),
|
||||
Base16Value::Base04 => colours.base_04.into(),
|
||||
Base16Value::Base05 => colours.base_05.into(),
|
||||
Base16Value::Base06 => colours.base_06.into(),
|
||||
Base16Value::Base07 => colours.base_07.into(),
|
||||
Base16Value::Base08 => colours.base_08.into(),
|
||||
Base16Value::Base09 => colours.base_09.into(),
|
||||
Base16Value::Base0A => colours.base_0a.into(),
|
||||
Base16Value::Base0B => colours.base_0b.into(),
|
||||
Base16Value::Base0C => colours.base_0c.into(),
|
||||
Base16Value::Base0D => colours.base_0d.into(),
|
||||
Base16Value::Base0E => colours.base_0e.into(),
|
||||
Base16Value::Base0F => colours.base_0f.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
||||
/// Catppuccin palette
|
||||
pub enum Catppuccin {
|
||||
/// Frappe (https://catppuccin.com/palette#flavor-frappe)
|
||||
Frappe,
|
||||
/// Latte (https://catppuccin.com/palette#flavor-latte)
|
||||
Latte,
|
||||
/// Macchiato (https://catppuccin.com/palette#flavor-macchiato)
|
||||
Macchiato,
|
||||
/// Mocha (https://catppuccin.com/palette#flavor-mocha)
|
||||
Mocha,
|
||||
}
|
||||
|
||||
@@ -119,33 +336,60 @@ impl From<Catppuccin> for catppuccin_egui::Theme {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
||||
/// Catppuccin Value
|
||||
pub enum CatppuccinValue {
|
||||
/// Rosewater
|
||||
Rosewater,
|
||||
/// Flamingo
|
||||
Flamingo,
|
||||
/// Pink
|
||||
Pink,
|
||||
/// Mauve
|
||||
Mauve,
|
||||
/// Red
|
||||
Red,
|
||||
/// Maroon
|
||||
Maroon,
|
||||
/// Peach
|
||||
Peach,
|
||||
/// Yellow
|
||||
Yellow,
|
||||
/// Green
|
||||
Green,
|
||||
/// Teal
|
||||
Teal,
|
||||
/// Sky
|
||||
Sky,
|
||||
/// Sapphire
|
||||
Sapphire,
|
||||
/// Blue
|
||||
Blue,
|
||||
/// Lavender
|
||||
Lavender,
|
||||
#[default]
|
||||
/// Text
|
||||
Text,
|
||||
/// Subtext1
|
||||
Subtext1,
|
||||
/// Subtext0
|
||||
Subtext0,
|
||||
/// Overlay2
|
||||
Overlay2,
|
||||
/// Overlay1
|
||||
Overlay1,
|
||||
/// Overlay0
|
||||
Overlay0,
|
||||
/// Surface2
|
||||
Surface2,
|
||||
/// Surface1
|
||||
Surface1,
|
||||
/// Surface0
|
||||
Surface0,
|
||||
/// Base
|
||||
Base,
|
||||
/// Mantle
|
||||
Mantle,
|
||||
/// Crust
|
||||
Crust,
|
||||
}
|
||||
|
||||
@@ -185,3 +429,275 @@ impl CatppuccinValue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
/// Theme from catppuccin-egui
|
||||
pub struct KomorebiThemeCatppuccin {
|
||||
/// Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)
|
||||
pub name: Catppuccin,
|
||||
/// Single window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
|
||||
pub single_border: Option<CatppuccinValue>,
|
||||
/// Stack window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Green)))]
|
||||
pub stack_border: Option<CatppuccinValue>,
|
||||
/// Monocle window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Pink)))]
|
||||
pub monocle_border: Option<CatppuccinValue>,
|
||||
/// Floating window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Yellow)))]
|
||||
pub floating_border: Option<CatppuccinValue>,
|
||||
/// Unfocused window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Base)))]
|
||||
pub unfocused_border: Option<CatppuccinValue>,
|
||||
/// Unfocused locked window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Red)))]
|
||||
pub unfocused_locked_border: Option<CatppuccinValue>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar focused text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Green)))]
|
||||
pub stackbar_focused_text: Option<CatppuccinValue>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar unfocused text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Text)))]
|
||||
pub stackbar_unfocused_text: Option<CatppuccinValue>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar background colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Base)))]
|
||||
pub stackbar_background: Option<CatppuccinValue>,
|
||||
/// Bar accent colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
|
||||
pub bar_accent: Option<CatppuccinValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
/// Theme from base16-egui-themes
|
||||
pub struct KomorebiThemeBase16 {
|
||||
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
|
||||
pub name: Base16,
|
||||
/// Single window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
|
||||
pub single_border: Option<Base16Value>,
|
||||
/// Stack window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
|
||||
pub stack_border: Option<Base16Value>,
|
||||
/// Monocle window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0F)))]
|
||||
pub monocle_border: Option<Base16Value>,
|
||||
/// Floating window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base09)))]
|
||||
pub floating_border: Option<Base16Value>,
|
||||
/// Unfocused window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
|
||||
pub unfocused_border: Option<Base16Value>,
|
||||
/// Unfocused locked window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base08)))]
|
||||
pub unfocused_locked_border: Option<Base16Value>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar focused text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
|
||||
pub stackbar_focused_text: Option<Base16Value>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar unfocused text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base05)))]
|
||||
pub stackbar_unfocused_text: Option<Base16Value>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar background colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
|
||||
pub stackbar_background: Option<Base16Value>,
|
||||
/// Bar accent colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
|
||||
pub bar_accent: Option<Base16Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
/// Custom Base16 theme
|
||||
pub struct KomorebiThemeCustom {
|
||||
/// Colours of the custom Base16 theme palette
|
||||
pub colours: Box<Base16ColourPalette>,
|
||||
/// Single window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
|
||||
pub single_border: Option<Base16Value>,
|
||||
/// Stack window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
|
||||
pub stack_border: Option<Base16Value>,
|
||||
/// Monocle window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0F)))]
|
||||
pub monocle_border: Option<Base16Value>,
|
||||
/// Floating window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base09)))]
|
||||
pub floating_border: Option<Base16Value>,
|
||||
/// Unfocused window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
|
||||
pub unfocused_border: Option<Base16Value>,
|
||||
/// Unfocused locked window border colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base08)))]
|
||||
pub unfocused_locked_border: Option<Base16Value>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar focused text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
|
||||
pub stackbar_focused_text: Option<Base16Value>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar unfocused text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base05)))]
|
||||
pub stackbar_unfocused_text: Option<Base16Value>,
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Stackbar background colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
|
||||
pub stackbar_background: Option<Base16Value>,
|
||||
/// Bar accent colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
|
||||
pub bar_accent: Option<Base16Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
#[serde(tag = "palette")]
|
||||
/// Komorebi theme
|
||||
pub enum KomorebiTheme {
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Catppuccin"))]
|
||||
/// Theme from catppuccin-egui
|
||||
Catppuccin(KomorebiThemeCatppuccin),
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Base16"))]
|
||||
/// Theme from base16-egui-themes
|
||||
Base16(KomorebiThemeBase16),
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||
/// Custom Base16 theme
|
||||
Custom(KomorebiThemeCustom),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
/// Theme from catppuccin-egui
|
||||
pub struct KomobarThemeCatppuccin {
|
||||
/// Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)
|
||||
pub name: Catppuccin,
|
||||
/// Accent colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
|
||||
pub accent: Option<CatppuccinValue>,
|
||||
/// Auto select fill colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_select_fill: Option<CatppuccinValue>,
|
||||
/// Auto select text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_select_text: Option<CatppuccinValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
/// Theme from base16-egui-themes
|
||||
pub struct KomobarThemeBase16 {
|
||||
/// Name of the Base16 theme (previews: https://tinted-theming.github.io/tinted-gallery/)
|
||||
pub name: Base16,
|
||||
/// Accent colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
|
||||
pub accent: Option<Base16Value>,
|
||||
/// Auto select fill colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_select_fill: Option<Base16Value>,
|
||||
/// Auto select text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_select_text: Option<Base16Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
/// Theme from base16-egui-themes
|
||||
pub struct KomobarThemeCustom {
|
||||
/// Colours of the custom Base16 theme palette
|
||||
pub colours: Box<Base16ColourPalette>,
|
||||
/// Accent colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
|
||||
pub accent: Option<Base16Value>,
|
||||
/// Auto select fill colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_select_fill: Option<Base16Value>,
|
||||
/// Auto select text colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_select_text: Option<Base16Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
|
||||
#[serde(tag = "palette")]
|
||||
/// Komorebi bar theme
|
||||
pub enum KomobarTheme {
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Catppuccin"))]
|
||||
/// Theme from catppuccin-egui
|
||||
Catppuccin(KomobarThemeCatppuccin),
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Base16"))]
|
||||
/// Theme from base16-egui-themes
|
||||
Base16(KomobarThemeBase16),
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||
/// Custom Base16 theme
|
||||
Custom(KomobarThemeCustom),
|
||||
}
|
||||
|
||||
impl From<KomorebiTheme> for KomobarTheme {
|
||||
fn from(value: KomorebiTheme) -> Self {
|
||||
match value {
|
||||
KomorebiTheme::Catppuccin(KomorebiThemeCatppuccin {
|
||||
name, bar_accent, ..
|
||||
}) => Self::Catppuccin(KomobarThemeCatppuccin {
|
||||
name,
|
||||
accent: bar_accent,
|
||||
auto_select_fill: None,
|
||||
auto_select_text: None,
|
||||
}),
|
||||
KomorebiTheme::Base16(KomorebiThemeBase16 {
|
||||
name, bar_accent, ..
|
||||
}) => Self::Base16(KomobarThemeBase16 {
|
||||
name,
|
||||
accent: bar_accent,
|
||||
auto_select_fill: None,
|
||||
auto_select_text: None,
|
||||
}),
|
||||
KomorebiTheme::Custom(KomorebiThemeCustom {
|
||||
colours,
|
||||
bar_accent,
|
||||
..
|
||||
}) => Self::Custom(KomobarThemeCustom {
|
||||
colours,
|
||||
accent: bar_accent,
|
||||
auto_select_fill: None,
|
||||
auto_select_text: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.35"
|
||||
version = "0.1.40"
|
||||
description = "A tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
komorebi-themes = { path = "../komorebi-themes" }
|
||||
|
||||
base64 = "0.22"
|
||||
bitflags = { version = "2", features = ["serde"] }
|
||||
clap = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
crossbeam-channel = { workspace = true }
|
||||
crossbeam-utils = { workspace = true }
|
||||
ctrlc = { version = "3", features = ["termination"] }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
getset = "0.1"
|
||||
hex_color = { version = "3", features = ["serde"] }
|
||||
ed25519-dalek = "2"
|
||||
hotwatch = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
miow = "0.6"
|
||||
nanoid = "0.4"
|
||||
net2 = "0.2"
|
||||
os_info = "3.10"
|
||||
parking_lot = "0.12"
|
||||
parking_lot = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
powershell_script = "1.0"
|
||||
regex = "1"
|
||||
schemars = { workspace = true }
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||
serde_yaml = { workspace = true }
|
||||
shadow-rs = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
@@ -49,12 +51,16 @@ windows-implement = { workspace = true }
|
||||
windows-interface = { workspace = true }
|
||||
winput = "0.2"
|
||||
winreg = "0.55"
|
||||
serde_with = { version = "3.12", features = ["schemars_1"] }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
deadlock_detection = ["parking_lot/deadlock_detection"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user