mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-05-29 23:10:41 +02:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dbf7da249 | |||
| 95838fc896 | |||
| bdef1448e1 | |||
| 0b39551470 | |||
| ab528f799d | |||
| 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 |
@@ -55,6 +55,7 @@ body:
|
|||||||
label: Hotkey Configuration
|
label: Hotkey Configuration
|
||||||
description: >
|
description: >
|
||||||
Please provide your whkdrc or komorebi.ahk hotkey configuration file
|
Please provide your whkdrc or komorebi.ahk hotkey configuration file
|
||||||
|
render: shell
|
||||||
- type: textarea
|
- type: textarea
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@@ -62,3 +63,4 @@ body:
|
|||||||
label: Output of komorebic check
|
label: Output of komorebic check
|
||||||
description: >
|
description: >
|
||||||
Please provide the output of `komorebic check`
|
Please provide the output of `komorebic check`
|
||||||
|
render: shell
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check and close feature issues
|
- name: Check and close feature issues
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const issue = context.payload.issue;
|
const issue = context.payload.issue;
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ on:
|
|||||||
- hotfix/*
|
- hotfix/*
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
schedule:
|
# schedule:
|
||||||
- cron: "30 0 * * 0" # Every day at 00:30 UTC
|
# - cron: "30 0 * * 0" # Every day at 00:30 UTC
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
@@ -43,10 +43,11 @@ jobs:
|
|||||||
RUSTFLAGS: -Ctarget-feature=+crt-static -Dwarnings
|
RUSTFLAGS: -Ctarget-feature=+crt-static -Dwarnings
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: rustup toolchain install stable --profile minimal
|
- 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
|
- run: rustup toolchain install nightly --allow-downgrade -c rustfmt
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
@@ -64,7 +65,7 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
cargo install cargo-wix
|
cargo install cargo-wix
|
||||||
cargo wix --no-build -p komorebi --nocapture -I .\wix\main.wxs --target ${{ matrix.platform.target }}
|
cargo wix --no-build -p komorebi --nocapture -I .\wix\main.wxs --target ${{ matrix.platform.target }}
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: komorebi-${{ matrix.platform.target }}-${{ github.sha }}
|
name: komorebi-${{ matrix.platform.target }}-${{ github.sha }}
|
||||||
path: |
|
path: |
|
||||||
@@ -81,12 +82,12 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: echo "VERSION=nightly" >> $GITHUB_ENV
|
run: echo "VERSION=nightly" >> $GITHUB_ENV
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
- run: |
|
- run: |
|
||||||
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
|
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
|
||||||
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
||||||
@@ -128,14 +129,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
TAG=${{ github.event.release.tag_name }}
|
TAG=${{ github.event.release.tag_name }}
|
||||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
- run: |
|
- run: |
|
||||||
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
|
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
|
||||||
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
||||||
@@ -170,14 +171,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
TAG=${{ github.ref_name }}
|
TAG=${{ github.ref_name }}
|
||||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
- run: |
|
- run: |
|
||||||
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
|
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
|
||||||
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ dummy.go
|
|||||||
komorebic/applications.yaml
|
komorebic/applications.yaml
|
||||||
komorebic/applications.json
|
komorebic/applications.json
|
||||||
/.vs
|
/.vs
|
||||||
|
/bar-schema
|
||||||
|
/komorebi-schema
|
||||||
|
/.wrangler
|
||||||
|
/.xwin-cache
|
||||||
|
result
|
||||||
|
/.direnv
|
||||||
|
|||||||
Generated
+2259
-1528
File diff suppressed because it is too large
Load Diff
+55
-43
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"komorebi",
|
"komorebi",
|
||||||
"komorebi-client",
|
"komorebi-client",
|
||||||
"komorebi-gui",
|
"komorebi-gui",
|
||||||
"komorebic",
|
"komorebic",
|
||||||
"komorebic-no-console",
|
"komorebic-no-console",
|
||||||
"komorebi-bar",
|
"komorebi-bar",
|
||||||
"komorebi-themes"
|
"komorebi-themes",
|
||||||
|
"komorebi-shortcuts",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@@ -18,12 +19,12 @@ chrono = "0.4"
|
|||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
crossbeam-utils = "0.8"
|
crossbeam-utils = "0.8"
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
eframe = "0.31"
|
eframe = "0.33"
|
||||||
egui_extras = "0.31"
|
egui_extras = "0.33"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
dunce = "1"
|
dunce = "1"
|
||||||
hotwatch = "0.5"
|
hotwatch = "0.5"
|
||||||
schemars = "0.8"
|
schemars = "1.1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { package = "serde_json_lenient", version = "0.2" }
|
serde_json = { package = "serde_json_lenient", version = "0.2" }
|
||||||
@@ -32,44 +33,55 @@ strum = { version = "0.27", features = ["derive"] }
|
|||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-appender = "0.2"
|
tracing-appender = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
parking_lot = "0.12"
|
||||||
paste = "1"
|
paste = "1"
|
||||||
sysinfo = "0.33"
|
sysinfo = "0.37"
|
||||||
uds_windows = "1"
|
uds_windows = "1"
|
||||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "a28c6559a9de2f92c142a714947a9b081776caca" }
|
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "8c42d8db257d30fe95bc98c2e5cd8f75da861021" }
|
||||||
windows-numerics = { version = "0.2" }
|
windows-numerics = { version = "0.3" }
|
||||||
windows-implement = { version = "0.60" }
|
windows-implement = { version = "0.60" }
|
||||||
windows-interface = { version = "0.59" }
|
windows-interface = { version = "0.59" }
|
||||||
windows-core = { version = "0.61" }
|
windows-core = { version = "0.62" }
|
||||||
shadow-rs = "1"
|
shadow-rs = "1"
|
||||||
which = "7"
|
which = "8"
|
||||||
|
|
||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
version = "0.61"
|
version = "0.62"
|
||||||
features = [
|
features = [
|
||||||
"Foundation_Numerics",
|
"Foundation_Numerics",
|
||||||
"Win32_Devices",
|
"Win32_Devices",
|
||||||
"Win32_Devices_Display",
|
"Win32_Devices_Display",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
"Win32_UI_Shell_Common", # for IObjectArray
|
"Win32_UI_Shell_Common", # for IObjectArray
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
"Win32_Globalization",
|
"Win32_Globalization",
|
||||||
"Win32_Graphics_Dwm",
|
"Win32_Graphics_Dwm",
|
||||||
"Win32_Graphics_Gdi",
|
"Win32_Graphics_Gdi",
|
||||||
"Win32_Graphics_Direct2D",
|
"Win32_Graphics_Direct2D",
|
||||||
"Win32_Graphics_Direct2D_Common",
|
"Win32_Graphics_Direct2D_Common",
|
||||||
"Win32_Graphics_Dxgi_Common",
|
"Win32_Graphics_Dxgi_Common",
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_System_Power",
|
"Win32_System_Power",
|
||||||
"Win32_System_RemoteDesktop",
|
"Win32_System_RemoteDesktop",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_UI_Accessibility",
|
"Win32_UI_Accessibility",
|
||||||
"Win32_UI_HiDpi",
|
"Win32_UI_HiDpi",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_UI_Shell_Common",
|
"Win32_UI_Shell_Common",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
"Win32_System_WindowsProgramming",
|
"Win32_System_WindowsProgramming",
|
||||||
"Media",
|
"Media",
|
||||||
"Media_Control"
|
"Media_Control",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.release-opt]
|
||||||
|
inherits = "release"
|
||||||
|
lto = true
|
||||||
|
panic = "abort"
|
||||||
|
codegen-units = 1
|
||||||
|
strip = true
|
||||||
|
|
||||||
|
[workspace.metadata.crane]
|
||||||
|
name = "komorebi-workspace"
|
||||||
|
|||||||
@@ -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
|
## Overview
|
||||||
|
|
||||||
_komorebi_ is a tiling window manager that works as an extension to Microsoft's
|
_komorebi_ is a tiling window manager that works as an extension to Microsoft's
|
||||||
@@ -394,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.
|
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.36"}
|
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.39"}
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use komorebi_client::Notification;
|
use komorebi_client::Notification;
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[graph]
|
[graph]
|
||||||
targets = [
|
targets = [
|
||||||
"x86_64-pc-windows-msvc",
|
"x86_64-pc-windows-msvc",
|
||||||
"i686-pc-windows-msvc",
|
"i686-pc-windows-msvc",
|
||||||
"aarch64-pc-windows-msvc",
|
"aarch64-pc-windows-msvc",
|
||||||
]
|
]
|
||||||
all-features = false
|
all-features = false
|
||||||
no-default-features = false
|
no-default-features = false
|
||||||
@@ -12,65 +12,82 @@ feature-depth = 1
|
|||||||
|
|
||||||
[advisories]
|
[advisories]
|
||||||
ignore = [
|
ignore = [
|
||||||
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
|
{ 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-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-2024-0320", reason = "not using any yaml features from this library" },
|
||||||
|
{ id = "RUSTSEC-2025-0056", reason = "only used for colour palette generation" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[licenses]
|
[licenses]
|
||||||
allow = [
|
allow = [
|
||||||
"0BSD",
|
"0BSD",
|
||||||
"Apache-2.0",
|
"Apache-2.0",
|
||||||
"Artistic-2.0",
|
"Apache-2.0 WITH LLVM-exception",
|
||||||
"BSD-2-Clause",
|
"Artistic-2.0",
|
||||||
"BSD-3-Clause",
|
"BSD-2-Clause",
|
||||||
"BSL-1.0",
|
"BSD-3-Clause",
|
||||||
"CC0-1.0",
|
"BSL-1.0",
|
||||||
"ISC",
|
"CC0-1.0",
|
||||||
"MIT",
|
"ISC",
|
||||||
"MIT-0",
|
"MIT",
|
||||||
"MPL-2.0",
|
"MIT-0",
|
||||||
"OFL-1.1",
|
"MPL-2.0",
|
||||||
"Ubuntu-font-1.0",
|
"OFL-1.1",
|
||||||
"Unicode-3.0",
|
"Ubuntu-font-1.0",
|
||||||
"Zlib",
|
"Unicode-3.0",
|
||||||
"LicenseRef-Komorebi-1.0"
|
"Zlib",
|
||||||
|
"LicenseRef-Komorebi-2.0",
|
||||||
]
|
]
|
||||||
confidence-threshold = 0.8
|
confidence-threshold = 0.8
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebi"
|
crate = "komorebi"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
expression = "LicenseRef-Komorebi-2.0"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebi-client"
|
crate = "komorebi-client"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
expression = "LicenseRef-Komorebi-2.0"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebic"
|
crate = "komorebic"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
expression = "LicenseRef-Komorebi-2.0"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebic-no-console"
|
crate = "komorebic-no-console"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
expression = "LicenseRef-Komorebi-2.0"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebi-themes"
|
crate = "komorebi-themes"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
expression = "LicenseRef-Komorebi-2.0"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebi-gui"
|
crate = "komorebi-gui"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
expression = "LicenseRef-Komorebi-2.0"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
crate = "komorebi-bar"
|
crate = "komorebi-bar"
|
||||||
expression = "LicenseRef-Komorebi-1.0"
|
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 = []
|
license-files = []
|
||||||
|
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
@@ -78,6 +95,11 @@ crate = "base16-egui-themes"
|
|||||||
expression = "MIT"
|
expression = "MIT"
|
||||||
license-files = []
|
license-files = []
|
||||||
|
|
||||||
|
[[licenses.clarify]]
|
||||||
|
crate = "win32-display-data"
|
||||||
|
expression = "0BSD"
|
||||||
|
license-files = []
|
||||||
|
|
||||||
[bans]
|
[bans]
|
||||||
multiple-versions = "allow"
|
multiple-versions = "allow"
|
||||||
wildcards = "allow"
|
wildcards = "allow"
|
||||||
@@ -90,10 +112,12 @@ unknown-registry = "deny"
|
|||||||
unknown-git = "deny"
|
unknown-git = "deny"
|
||||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||||
allow-git = [
|
allow-git = [
|
||||||
"https://github.com/LGUG2Z/base16-egui-themes",
|
"https://github.com/LGUG2Z/base16-egui-themes",
|
||||||
"https://github.com/LGUG2Z/catppuccin-egui",
|
"https://github.com/LGUG2Z/windows-icons",
|
||||||
"https://github.com/LGUG2Z/windows-icons",
|
"https://github.com/LGUG2Z/win32-display-data",
|
||||||
"https://github.com/LGUG2Z/win32-display-data",
|
"https://github.com/LGUG2Z/flavours",
|
||||||
"https://github.com/LGUG2Z/flavours",
|
"https://github.com/LGUG2Z/base16_color_scheme",
|
||||||
"https://github.com/LGUG2Z/base16_color_scheme",
|
"https://github.com/LGUG2Z/whkd",
|
||||||
|
"https://github.com/LGUG2Z/catppuccin-egui",
|
||||||
|
"https://github.com/amPerl/egui-phosphor",
|
||||||
]
|
]
|
||||||
|
|||||||
+497
-409
File diff suppressed because it is too large
Load Diff
@@ -10,8 +10,8 @@ Options:
|
|||||||
Desired ease function for animation
|
Desired ease function for animation
|
||||||
|
|
||||||
[default: linear]
|
[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,
|
[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-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]
|
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>
|
-a, --animation-type <ANIMATION_TYPE>
|
||||||
Animation type to apply the style to. If not specified, sets global style
|
Animation type to apply the style to. If not specified, sets global style
|
||||||
|
|||||||
@@ -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:
|
Arguments:
|
||||||
<DEFAULT_LAYOUT>
|
<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:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ Options:
|
|||||||
--whkd
|
--whkd
|
||||||
Enable autostart of whkd
|
Enable autostart of whkd
|
||||||
|
|
||||||
--ahk
|
|
||||||
Enable autostart of ahk
|
|
||||||
|
|
||||||
--bar
|
--bar
|
||||||
Enable autostart of komorebi-bar
|
Enable autostart of komorebi-bar
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ Options:
|
|||||||
--whkd
|
--whkd
|
||||||
Kill whkd if it is running as a background process
|
Kill whkd if it is running as a background process
|
||||||
|
|
||||||
--ahk
|
|
||||||
Kill ahk if it is running as a background process
|
|
||||||
|
|
||||||
--bar
|
--bar
|
||||||
Kill komorebi-bar if it is running as a background process
|
Kill komorebi-bar if it is running as a background process
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
```
|
||||||
@@ -13,7 +13,7 @@ Arguments:
|
|||||||
The number of window containers on-screen required to trigger this layout rule
|
The number of window containers on-screen required to trigger this layout rule
|
||||||
|
|
||||||
<LAYOUT>
|
<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:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Arguments:
|
|||||||
Target workspace name
|
Target workspace name
|
||||||
|
|
||||||
<VALUE>
|
<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:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
```
|
||||||
@@ -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
-1
@@ -1,7 +1,7 @@
|
|||||||
# promote
|
# 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
|
Usage: komorebic.exe promote
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ Usage: komorebic.exe query <STATE_QUERY>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<STATE_QUERY>
|
<STATE_QUERY>
|
||||||
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name, focused-workspace-layout, version]
|
[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:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
```
|
||||||
@@ -18,9 +18,6 @@ Options:
|
|||||||
--whkd
|
--whkd
|
||||||
Start whkd in a background process
|
Start whkd in a background process
|
||||||
|
|
||||||
--ahk
|
|
||||||
Start autohotkey configuration file
|
|
||||||
|
|
||||||
--bar
|
--bar
|
||||||
Start komorebi-bar in a background process
|
Start komorebi-bar in a background process
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ Options:
|
|||||||
--whkd
|
--whkd
|
||||||
Stop whkd if it is running as a background process
|
Stop whkd if it is running as a background process
|
||||||
|
|
||||||
--ahk
|
|
||||||
Stop ahk if it is running as a background process
|
|
||||||
|
|
||||||
--bar
|
--bar
|
||||||
Stop komorebi-bar if it is running as a background process
|
Stop komorebi-bar if it is running as a background process
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# toggle-pause
|
# toggle-pause
|
||||||
|
|
||||||
```
|
```
|
||||||
Toggle window tiling on the focused workspace
|
Toggle the paused state for all window tiling
|
||||||
|
|
||||||
Usage: komorebic.exe toggle-pause
|
Usage: komorebic.exe toggle-pause
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# toggle-shortcuts
|
||||||
|
|
||||||
|
```
|
||||||
|
Toggle the komorebi-shortcuts helper
|
||||||
|
|
||||||
|
Usage: komorebic.exe toggle-shortcuts
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
@@ -8,7 +8,7 @@ Usage: komorebic.exe window-hiding-behaviour <HIDING_BEHAVIOUR>
|
|||||||
Arguments:
|
Arguments:
|
||||||
<HIDING_BEHAVIOUR>
|
<HIDING_BEHAVIOUR>
|
||||||
Possible values:
|
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)
|
- 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
|
- 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
|
The number of window containers on-screen required to trigger this layout rule
|
||||||
|
|
||||||
<LAYOUT>
|
<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:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Arguments:
|
|||||||
Workspace index on the specified monitor (zero-indexed)
|
Workspace index on the specified monitor (zero-indexed)
|
||||||
|
|
||||||
<VALUE>
|
<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:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -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
|
```json
|
||||||
{
|
{
|
||||||
"animation": {
|
"animation": {
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"duration": 250,
|
||||||
|
"fps": 60,
|
||||||
|
"style": "EaseOutSine"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ how to map the indices and would use default behaviour which would result in a m
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Multiple Monitors on different machines
|
# Multiple monitors on different machines
|
||||||
|
|
||||||
You can use the same `komorebi.json` to configure two different setups and then synchronize your config across 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.
|
However, if you do this it is important to be aware of a few things.
|
||||||
@@ -393,6 +393,13 @@ This is because komorebi will apply the appropriate config to the loaded monitor
|
|||||||
index (the index defined in the user config) to the actual monitor index, and the bar will use that map to know if it
|
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.
|
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
|
### 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
|
* If you are using a laptop connected to one monitor at work and a different one at home, the work monitor and the home
|
||||||
|
|||||||
@@ -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
|
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`,
|
With the example configurations downloaded, you can now start `komorebi`,
|
||||||
`komorebi-bar` and `whkd`.
|
`komorebi-bar` and `whkd`.
|
||||||
|
|
||||||
@@ -23,6 +36,9 @@ With the example configurations downloaded, you can now start `komorebi`,
|
|||||||
komorebic start --whkd --bar
|
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
|
## komorebi.json
|
||||||
|
|
||||||
The example window manager configuration sets some sane defaults and provides
|
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
|
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.
|
`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" %}
|
{% include "./whkdrc.sample" %}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ cargo +stable install --path komorebic --locked
|
|||||||
cargo +stable install --path komorebic-no-console --locked
|
cargo +stable install --path komorebic-no-console --locked
|
||||||
cargo +stable install --path komorebi-gui --locked
|
cargo +stable install --path komorebi-gui --locked
|
||||||
cargo +stable install --path komorebi-bar --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
|
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.36/schema.bar.json",
|
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.bar.json",
|
||||||
"monitor": 0,
|
|
||||||
"font_family": "JetBrains Mono",
|
"font_family": "JetBrains Mono",
|
||||||
"theme": {
|
"theme": {
|
||||||
"palette": "Base16",
|
"palette": "Base16",
|
||||||
@@ -48,8 +47,8 @@
|
|||||||
{
|
{
|
||||||
"Network": {
|
"Network": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"show_total_data_transmitted": true,
|
"show_activity": true,
|
||||||
"show_network_activity": true
|
"show_total_activity": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.36/schema.json",
|
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.json",
|
||||||
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
|
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
|
||||||
"window_hiding_behaviour": "Cloak",
|
"window_hiding_behaviour": "Cloak",
|
||||||
"cross_monitor_move_behaviour": "Insert",
|
"cross_monitor_move_behaviour": "Insert",
|
||||||
|
|||||||
@@ -138,13 +138,14 @@ running `komorebic stop` and `komorebic start`.
|
|||||||
Users with Nvidia GPUs may have issues with transparency on the Komorebi Bar.
|
Users with Nvidia GPUs may have issues with transparency on the Komorebi Bar.
|
||||||
|
|
||||||
To solve this the user can do the following:
|
To solve this the user can do the following:
|
||||||
1. Open the Nvidia Control Panel
|
|
||||||
2. On the left menu tree, under "3D Settings", select "Manage 3D Settings"
|
- Open the Nvidia Control Panel
|
||||||
3. Select the "Program Settings" tab
|
- On the left menu tree, under "3D Settings", select "Manage 3D Settings"
|
||||||
4. Press the "Add" button and select "komorebi-bar"
|
- Select the "Program Settings" tab
|
||||||
5. Under "3. Specify the settings for this program:", find the feature labelled, "OpenGL GDI compatibility"
|
- Press the "Add" button and select "komorebi-bar"
|
||||||
6. Change the setting to "Prefer compatibility"
|
- Under "3. Specify the settings for this program:", find the feature labelled, "OpenGL GDI compatibility"
|
||||||
7. At the bottom of the window select "Apply"
|
- Change the setting to "Prefer compatibility"
|
||||||
8. Restart the Komorebi Bar with "komorebic stop --bar; komorebic start --bar"
|
- 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.
|
This should resolve the issue and your Komorebi Bar should render with the proper transparency.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
alt + o : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
|
alt + o : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
|
||||||
alt + shift + o : komorebic reload-configuration
|
alt + shift + o : komorebic reload-configuration
|
||||||
|
|
||||||
|
alt + i : komorebic toggle-shortcuts
|
||||||
|
|
||||||
# App shortcuts - these require shell to be pwsh / powershell
|
# App shortcuts - these require shell to be pwsh / powershell
|
||||||
# The apps will be focused if open, or launched if not open
|
# The apps will be focused if open, or launched if not open
|
||||||
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
||||||
|
|||||||
Generated
+179
@@ -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
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ fmt:
|
|||||||
prettier --write .github/FUNDING.yml
|
prettier --write .github/FUNDING.yml
|
||||||
prettier --write .github/workflows/windows.yaml
|
prettier --write .github/workflows/windows.yaml
|
||||||
|
|
||||||
|
fix:
|
||||||
|
cargo clippy --fix --allow-dirty
|
||||||
|
|
||||||
install-targets *targets:
|
install-targets *targets:
|
||||||
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
|
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
|
||||||
|
|
||||||
@@ -28,10 +31,10 @@ install-target-with-jsonschema target:
|
|||||||
cargo +stable install --path {{ target }} --locked
|
cargo +stable install --path {{ target }} --locked
|
||||||
|
|
||||||
install:
|
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:
|
install-with-jsonschema:
|
||||||
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
|
||||||
|
|
||||||
build-targets *targets:
|
build-targets *targets:
|
||||||
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
|
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
|
||||||
@@ -40,7 +43,7 @@ build-target target:
|
|||||||
cargo +stable build --package {{ target }} --locked --release --no-default-features
|
cargo +stable build --package {{ target }} --locked --release --no-default-features
|
||||||
|
|
||||||
build:
|
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:
|
copy-target target:
|
||||||
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
||||||
@@ -52,7 +55,7 @@ wpm target:
|
|||||||
just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}
|
just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}
|
||||||
|
|
||||||
copy:
|
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:
|
run target:
|
||||||
cargo +stable run --bin {{ target }} --locked --no-default-features
|
cargo +stable run --bin {{ target }} --locked --no-default-features
|
||||||
@@ -72,19 +75,38 @@ trace target $RUST_LOG="trace":
|
|||||||
deadlock $RUST_LOG="trace":
|
deadlock $RUST_LOG="trace":
|
||||||
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
|
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
|
||||||
|
|
||||||
docgen:
|
docgen starlight:
|
||||||
cargo run --package komorebic -- docgen
|
rm {{ starlight }}/src/data/cli/windows/*.md
|
||||||
Get-ChildItem -Path "docs/cli" -Recurse -File | ForEach-Object { (Get-Content $_.FullName) -replace 'Usage: ', 'Usage: komorebic.exe ' | Set-Content $_.FullName }
|
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:
|
jsonschema:
|
||||||
cargo run --package komorebic -- static-config-schema > schema.json
|
cargo run --package komorebic -- static-config-schema > schema.json
|
||||||
cargo run --package komorebic -- application-specific-configuration-schema > schema.asc.json
|
cargo run --package komorebic -- application-specific-configuration-schema > schema.asc.json
|
||||||
cargo run --package komorebi-bar -- --schema > schema.bar.json
|
cargo run --package komorebi-bar -- --schema > schema.bar.json
|
||||||
|
|
||||||
# this part is run in a nix shell because python is a nightmare
|
version := `cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "komorebi") | .version'`
|
||||||
schemagen:
|
|
||||||
rm -rf static-config-docs bar-config-docs
|
schemapub:
|
||||||
mkdir -p static-config-docs bar-config-docs
|
rm -Force komorebi-schema
|
||||||
generate-schema-doc ./schema.json --config template_name=js_offline --config minify=false ./static-config-docs/
|
mkdir -Force komorebi-schema
|
||||||
generate-schema-doc ./schema.bar.json --config template_name=js_offline --config minify=false ./bar-config-docs/
|
cp schema.json komorebi-schema/komorebi.{{ version }}.schema.json
|
||||||
mv ./bar-config-docs/schema.bar.html ./bar-config-docs/schema.html
|
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
|
||||||
|
|
||||||
|
procdump:
|
||||||
|
cargo build --bin komorebi
|
||||||
|
.\procdump.exe -ma -e -x . .\target\debug\komorebi.exe
|
||||||
|
|||||||
+13
-7
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-bar"
|
name = "komorebi-bar"
|
||||||
version = "0.1.36"
|
version = "0.1.40"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
komorebi-client = { path = "../komorebi-client" }
|
komorebi-client = { path = "../komorebi-client", default-features = false }
|
||||||
komorebi-themes = { path = "../komorebi-themes" }
|
komorebi-themes = { path = "../komorebi-themes", default-features = false }
|
||||||
|
|
||||||
chrono-tz = { workspace = true }
|
chrono-tz = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
@@ -17,15 +17,16 @@ crossbeam-channel = { workspace = true }
|
|||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
eframe = { workspace = true }
|
eframe = { workspace = true }
|
||||||
egui-phosphor = "0.9"
|
egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" }
|
||||||
font-loader = "0.11"
|
font-loader = "0.11"
|
||||||
hotwatch = { workspace = true }
|
hotwatch = { workspace = true }
|
||||||
image = "0.25"
|
image = "0.25"
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
netdev = "0.33"
|
netdev = "0.40"
|
||||||
num = "0.4"
|
num = "0.4"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
parking_lot = { workspace = true }
|
||||||
random_word = { version = "0.5", features = ["en"] }
|
random_word = { version = "0.5", features = ["en"] }
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
@@ -35,6 +36,7 @@ starship-battery = "0.10"
|
|||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
|
which = { workspace = true }
|
||||||
windows = { workspace = true }
|
windows = { workspace = true }
|
||||||
windows-core = { workspace = true }
|
windows-core = { workspace = true }
|
||||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
|
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
|
||||||
@@ -42,4 +44,8 @@ windows-icons-fallback = { package = "windows-icons", git = "https://github.com/
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["schemars"]
|
default = ["schemars"]
|
||||||
schemars = ["dep:schemars"]
|
schemars = [
|
||||||
|
"dep:schemars",
|
||||||
|
"komorebi-client/default",
|
||||||
|
"komorebi-themes/default",
|
||||||
|
]
|
||||||
|
|||||||
+431
-117
@@ -1,27 +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::KomobarConfig;
|
||||||
use crate::config::KomobarTheme;
|
use crate::config::KomobarTheme;
|
||||||
use crate::config::MonitorConfigOrIndex;
|
use crate::config::MonitorConfigOrIndex;
|
||||||
use crate::config::Position;
|
use crate::config::Position;
|
||||||
use crate::config::PositionConfig;
|
use crate::config::PositionConfig;
|
||||||
|
use crate::config::get_individual_spacing;
|
||||||
use crate::process_hwnd;
|
use crate::process_hwnd;
|
||||||
use crate::render::Color32Ext;
|
use crate::render::Color32Ext;
|
||||||
use crate::render::Grouping;
|
use crate::render::Grouping;
|
||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::render::RenderExt;
|
use crate::render::RenderExt;
|
||||||
use crate::widgets::komorebi::Komorebi;
|
use crate::widgets::komorebi::Komorebi;
|
||||||
use crate::widgets::komorebi::KomorebiNotificationState;
|
use crate::widgets::komorebi::MonitorInfo;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use crate::widgets::widget::WidgetConfig;
|
use crate::widgets::widget::WidgetConfig;
|
||||||
use crate::KomorebiEvent;
|
use color_eyre::eyre;
|
||||||
use crate::AUTO_SELECT_FILL_COLOUR;
|
|
||||||
use crate::AUTO_SELECT_TEXT_COLOUR;
|
|
||||||
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 crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use crossbeam_channel::TryRecvError;
|
use crossbeam_channel::TryRecvError;
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
@@ -38,6 +39,7 @@ use eframe::egui::Frame;
|
|||||||
use eframe::egui::Id;
|
use eframe::egui::Id;
|
||||||
use eframe::egui::Layout;
|
use eframe::egui::Layout;
|
||||||
use eframe::egui::Margin;
|
use eframe::egui::Margin;
|
||||||
|
use eframe::egui::PointerButton;
|
||||||
use eframe::egui::Rgba;
|
use eframe::egui::Rgba;
|
||||||
use eframe::egui::Style;
|
use eframe::egui::Style;
|
||||||
use eframe::egui::TextStyle;
|
use eframe::egui::TextStyle;
|
||||||
@@ -46,22 +48,101 @@ use eframe::egui::Visuals;
|
|||||||
use font_loader::system_fonts;
|
use font_loader::system_fonts;
|
||||||
use font_loader::system_fonts::FontPropertyBuilder;
|
use font_loader::system_fonts::FontPropertyBuilder;
|
||||||
use komorebi_client::Colour;
|
use komorebi_client::Colour;
|
||||||
use komorebi_client::KomorebiTheme;
|
|
||||||
use komorebi_client::MonitorNotification;
|
use komorebi_client::MonitorNotification;
|
||||||
use komorebi_client::NotificationEvent;
|
use komorebi_client::NotificationEvent;
|
||||||
use komorebi_client::PathExt;
|
use komorebi_client::PathExt;
|
||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
use komorebi_themes::catppuccin_egui;
|
use komorebi_client::VirtualDesktopNotification;
|
||||||
use komorebi_themes::Base16Value;
|
|
||||||
use komorebi_themes::Base16Wrapper;
|
use komorebi_themes::Base16Wrapper;
|
||||||
use komorebi_themes::Catppuccin;
|
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::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
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::path::PathBuf;
|
||||||
|
use std::process::ChildStdin;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::process::Stdio;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
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 struct Komobar {
|
||||||
pub hwnd: Option<isize>,
|
pub hwnd: Option<isize>,
|
||||||
@@ -69,7 +150,7 @@ pub struct Komobar {
|
|||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
pub config: KomobarConfig,
|
pub config: KomobarConfig,
|
||||||
pub render_config: Rc<RefCell<RenderConfig>>,
|
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 left_widgets: Vec<Box<dyn BarWidget>>,
|
||||||
pub center_widgets: Vec<Box<dyn BarWidget>>,
|
pub center_widgets: Vec<Box<dyn BarWidget>>,
|
||||||
pub right_widgets: Vec<Box<dyn BarWidget>>,
|
pub right_widgets: Vec<Box<dyn BarWidget>>,
|
||||||
@@ -81,6 +162,18 @@ pub struct Komobar {
|
|||||||
pub size_rect: komorebi_client::Rect,
|
pub size_rect: komorebi_client::Rect,
|
||||||
pub work_area_offset: komorebi_client::Rect,
|
pub work_area_offset: komorebi_client::Rect,
|
||||||
applied_theme_on_first_frame: bool,
|
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(
|
pub fn apply_theme(
|
||||||
@@ -93,12 +186,12 @@ pub fn apply_theme(
|
|||||||
render_config: Rc<RefCell<RenderConfig>>,
|
render_config: Rc<RefCell<RenderConfig>>,
|
||||||
) {
|
) {
|
||||||
let (auto_select_fill, auto_select_text) = match theme {
|
let (auto_select_fill, auto_select_text) = match theme {
|
||||||
KomobarTheme::Catppuccin {
|
KomobarTheme::Catppuccin(KomobarThemeCatppuccin {
|
||||||
name: catppuccin,
|
name: catppuccin,
|
||||||
accent: catppuccin_value,
|
accent: catppuccin_value,
|
||||||
auto_select_fill: catppuccin_auto_select_fill,
|
auto_select_fill: catppuccin_auto_select_fill,
|
||||||
auto_select_text: catppuccin_auto_select_text,
|
auto_select_text: catppuccin_auto_select_text,
|
||||||
} => {
|
}) => {
|
||||||
match catppuccin {
|
match catppuccin {
|
||||||
Catppuccin::Frappe => {
|
Catppuccin::Frappe => {
|
||||||
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
|
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
|
||||||
@@ -163,12 +256,12 @@ pub fn apply_theme(
|
|||||||
catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())),
|
catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KomobarTheme::Base16 {
|
KomobarTheme::Base16(KomobarThemeBase16 {
|
||||||
name: base16,
|
name: base16,
|
||||||
accent: base16_value,
|
accent: base16_value,
|
||||||
auto_select_fill: base16_auto_select_fill,
|
auto_select_fill: base16_auto_select_fill,
|
||||||
auto_select_text: base16_auto_select_text,
|
auto_select_text: base16_auto_select_text,
|
||||||
} => {
|
}) => {
|
||||||
ctx.set_style(base16.style());
|
ctx.set_style(base16.style());
|
||||||
let base16_value = base16_value.unwrap_or_default();
|
let base16_value = base16_value.unwrap_or_default();
|
||||||
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
|
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
|
||||||
@@ -186,12 +279,12 @@ pub fn apply_theme(
|
|||||||
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))),
|
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KomobarTheme::Custom {
|
KomobarTheme::Custom(KomobarThemeCustom {
|
||||||
colours,
|
colours,
|
||||||
accent: base16_value,
|
accent: base16_value,
|
||||||
auto_select_fill: base16_auto_select_fill,
|
auto_select_fill: base16_auto_select_fill,
|
||||||
auto_select_text: base16_auto_select_text,
|
auto_select_text: base16_auto_select_text,
|
||||||
} => {
|
}) => {
|
||||||
let background = colours.background();
|
let background = colours.background();
|
||||||
ctx.set_style(colours.style());
|
ctx.set_style(colours.style());
|
||||||
let base16_value = base16_value.unwrap_or_default();
|
let base16_value = base16_value.unwrap_or_default();
|
||||||
@@ -229,16 +322,15 @@ pub fn apply_theme(
|
|||||||
// apply rounding to the widgets
|
// apply rounding to the widgets
|
||||||
if let Some(Grouping::Bar(config) | Grouping::Alignment(config) | Grouping::Widget(config)) =
|
if let Some(Grouping::Bar(config) | Grouping::Alignment(config) | Grouping::Widget(config)) =
|
||||||
&grouping
|
&grouping
|
||||||
|
&& let Some(rounding) = config.rounding
|
||||||
{
|
{
|
||||||
if let Some(rounding) = config.rounding {
|
ctx.style_mut(|style| {
|
||||||
ctx.style_mut(|style| {
|
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
|
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
style.visuals.widgets.open.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
|
// Update RenderConfig's background_color so that widgets will have the new color
|
||||||
@@ -249,7 +341,7 @@ impl Komobar {
|
|||||||
pub fn apply_config(
|
pub fn apply_config(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
previous_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
|
previous_monitor_info: Option<Rc<RefCell<MonitorInfo>>>,
|
||||||
) {
|
) {
|
||||||
MAX_LABEL_WIDTH.store(
|
MAX_LABEL_WIDTH.store(
|
||||||
self.config.max_label_width.unwrap_or(400.0) as i32,
|
self.config.max_label_width.unwrap_or(400.0) as i32,
|
||||||
@@ -278,7 +370,7 @@ impl Komobar {
|
|||||||
self.config.icon_scale,
|
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();
|
let mut komorebi_widgets = Vec::new();
|
||||||
|
|
||||||
for (idx, widget_config) in self.config.left_widgets.iter().enumerate() {
|
for (idx, widget_config) in self.config.left_widgets.iter().enumerate() {
|
||||||
@@ -330,19 +422,18 @@ impl Komobar {
|
|||||||
komorebi_widgets
|
komorebi_widgets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|(mut widget, idx, side)| {
|
.for_each(|(mut widget, idx, side)| {
|
||||||
match komorebi_notification_state {
|
match monitor_info {
|
||||||
None => {
|
None => {
|
||||||
komorebi_notification_state =
|
monitor_info = Some(widget.monitor_info.clone());
|
||||||
Some(widget.komorebi_notification_state.clone());
|
|
||||||
}
|
}
|
||||||
Some(ref previous) => {
|
Some(ref previous) => {
|
||||||
if widget.workspaces.is_some_and(|w| w.enable) {
|
if widget.workspaces.is_some() {
|
||||||
previous.borrow_mut().update_from_config(
|
previous
|
||||||
&widget.komorebi_notification_state.borrow(),
|
.borrow_mut()
|
||||||
);
|
.update_from_self(&widget.monitor_info.borrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.komorebi_notification_state = previous.clone();
|
widget.monitor_info = previous.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,20 +453,25 @@ impl Komobar {
|
|||||||
self.right_widgets = right_widgets;
|
self.right_widgets = right_widgets;
|
||||||
|
|
||||||
let (usr_monitor_index, config_work_area_offset) = match &self.config.monitor {
|
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)
|
(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
|
let mapped_info = self.monitor_info.as_ref().map(|info| {
|
||||||
.borrow()
|
let monitor = info.borrow();
|
||||||
.monitor_usr_idx_map
|
(
|
||||||
.get(&usr_monitor_index)
|
monitor.monitor_usr_idx_map.get(&usr_monitor_index).copied(),
|
||||||
.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 Some(monitor_index) = self.monitor_index {
|
||||||
if let (prev_rect, Some(new_rect)) = (&self.work_area_offset, &config_work_area_offset)
|
if let (prev_rect, Some(new_rect)) = (&self.work_area_offset, &config_work_area_offset)
|
||||||
@@ -426,17 +522,51 @@ impl Komobar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.komorebi_notification_state.is_some() && !self.disabled {
|
} 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...");
|
tracing::warn!(
|
||||||
|
"couldn't find the monitor index of this bar! Disabling the bar until the monitor connects..."
|
||||||
|
);
|
||||||
self.disabled = true;
|
self.disabled = true;
|
||||||
} else {
|
} 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;
|
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");
|
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
|
/// Updates the `size_rect` field. Returns a bool indicating if the field was changed or not
|
||||||
@@ -473,7 +603,9 @@ impl Komobar {
|
|||||||
end.x -= margin.left + margin.right;
|
end.x -= margin.left + margin.right;
|
||||||
|
|
||||||
if end.y == 0.0 {
|
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 {
|
self.size_rect = komorebi_client::Rect {
|
||||||
@@ -505,8 +637,7 @@ impl Komobar {
|
|||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
home.is_dir(),
|
home.is_dir(),
|
||||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
|
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory"
|
||||||
home_path
|
|
||||||
);
|
);
|
||||||
|
|
||||||
home
|
home
|
||||||
@@ -520,26 +651,6 @@ impl Komobar {
|
|||||||
match komorebi_client::StaticConfig::read(&config) {
|
match komorebi_client::StaticConfig::read(&config) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
if let Some(theme) = config.theme {
|
if let Some(theme) = config.theme {
|
||||||
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(Base16Wrapper::Base16(name)),
|
|
||||||
KomorebiTheme::Custom {
|
|
||||||
ref colours,
|
|
||||||
stack_border,
|
|
||||||
..
|
|
||||||
} => stack_border
|
|
||||||
.unwrap_or(Base16Value::Base0B)
|
|
||||||
.color32(Base16Wrapper::Custom(colours.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
apply_theme(
|
apply_theme(
|
||||||
ctx,
|
ctx,
|
||||||
KomobarTheme::from(theme),
|
KomobarTheme::from(theme),
|
||||||
@@ -549,10 +660,6 @@ impl Komobar {
|
|||||||
bar_grouping,
|
bar_grouping,
|
||||||
self.render_config.clone(),
|
self.render_config.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(state) = &self.komorebi_notification_state {
|
|
||||||
state.borrow_mut().stack_accent = Some(stack_accent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -565,17 +672,16 @@ impl Komobar {
|
|||||||
| Grouping::Alignment(config)
|
| Grouping::Alignment(config)
|
||||||
| Grouping::Widget(config),
|
| Grouping::Widget(config),
|
||||||
) = &bar_grouping
|
) = &bar_grouping
|
||||||
|
&& let Some(rounding) = config.rounding
|
||||||
{
|
{
|
||||||
if let Some(rounding) = config.rounding {
|
ctx.style_mut(|style| {
|
||||||
ctx.style_mut(|style| {
|
style.visuals.widgets.noninteractive.corner_radius =
|
||||||
style.visuals.widgets.noninteractive.corner_radius =
|
rounding.into();
|
||||||
rounding.into();
|
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,7 +701,7 @@ impl Komobar {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
config,
|
config,
|
||||||
render_config: Rc::new(RefCell::new(RenderConfig::new())),
|
render_config: Rc::new(RefCell::new(RenderConfig::new())),
|
||||||
komorebi_notification_state: None,
|
monitor_info: None,
|
||||||
left_widgets: vec![],
|
left_widgets: vec![],
|
||||||
center_widgets: vec![],
|
center_widgets: vec![],
|
||||||
right_widgets: vec![],
|
right_widgets: vec![],
|
||||||
@@ -607,6 +713,16 @@ impl Komobar {
|
|||||||
size_rect: komorebi_client::Rect::default(),
|
size_rect: komorebi_client::Rect::default(),
|
||||||
work_area_offset: komorebi_client::Rect::default(),
|
work_area_offset: komorebi_client::Rect::default(),
|
||||||
applied_theme_on_first_frame: false,
|
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);
|
komobar.apply_config(&cc.egui_ctx, None);
|
||||||
@@ -731,12 +847,12 @@ impl eframe::App for Komobar {
|
|||||||
|
|
||||||
if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {
|
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.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() {
|
if let Ok(updated_config) = self.rx_config.try_recv() {
|
||||||
self.config = updated_config;
|
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() {
|
match self.rx_gui.try_recv() {
|
||||||
@@ -751,13 +867,41 @@ impl eframe::App for Komobar {
|
|||||||
Ok(KomorebiEvent::Notification(notification)) => {
|
Ok(KomorebiEvent::Notification(notification)) => {
|
||||||
let state = ¬ification.state;
|
let state = ¬ification.state;
|
||||||
let usr_monitor_index = match &self.config.monitor {
|
let usr_monitor_index = match &self.config.monitor {
|
||||||
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
|
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
|
||||||
MonitorConfigOrIndex::Index(idx) => *idx,
|
monitor_config.index
|
||||||
|
}
|
||||||
|
Some(MonitorConfigOrIndex::Index(idx)) => *idx,
|
||||||
|
None => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let monitor_index = state.monitor_usr_idx_map.get(&usr_monitor_index).copied();
|
let monitor_index = state.monitor_usr_idx_map.get(&usr_monitor_index).copied();
|
||||||
self.monitor_index = monitor_index;
|
self.monitor_index = monitor_index;
|
||||||
let mut should_apply_config = false;
|
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()
|
if self.monitor_index.is_none()
|
||||||
|| self
|
|| self
|
||||||
.monitor_index
|
.monitor_index
|
||||||
@@ -802,9 +946,9 @@ impl eframe::App for Komobar {
|
|||||||
) {
|
) {
|
||||||
let monitor_index = self.monitor_index.expect("should have a monitor index");
|
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;
|
should_apply_config = true;
|
||||||
}
|
}
|
||||||
@@ -815,7 +959,7 @@ impl eframe::App for Komobar {
|
|||||||
|
|
||||||
// Check if monitor coordinates/size has changed
|
// Check if monitor coordinates/size has changed
|
||||||
if let Some(monitor_index) = self.monitor_index {
|
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 top = MONITOR_TOP.load(Ordering::SeqCst);
|
||||||
let left = MONITOR_LEFT.load(Ordering::SeqCst);
|
let left = MONITOR_LEFT.load(Ordering::SeqCst);
|
||||||
let right = MONITOR_RIGHT.load(Ordering::SeqCst);
|
let right = MONITOR_RIGHT.load(Ordering::SeqCst);
|
||||||
@@ -825,36 +969,38 @@ impl eframe::App for Komobar {
|
|||||||
bottom: monitor_size.bottom,
|
bottom: monitor_size.bottom,
|
||||||
right,
|
right,
|
||||||
};
|
};
|
||||||
if *monitor_size != rect {
|
if monitor_size != rect {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Monitor coordinates/size has changed, storing new coordinates: {:#?}",
|
"Monitor coordinates/size has changed, storing new coordinates: {:#?}",
|
||||||
monitor_size
|
monitor_size
|
||||||
);
|
);
|
||||||
|
|
||||||
self.update_monitor_coordinates(monitor_size);
|
self.update_monitor_coordinates(&monitor_size);
|
||||||
|
|
||||||
should_apply_config = true;
|
should_apply_config = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(komorebi_notification_state) = &self.komorebi_notification_state {
|
if let Some(monitor_info) = &self.monitor_info {
|
||||||
komorebi_notification_state
|
monitor_info.borrow_mut().update(
|
||||||
.borrow_mut()
|
self.monitor_index,
|
||||||
.handle_notification(
|
notification.state,
|
||||||
ctx,
|
self.render_config.borrow().show_all_icons,
|
||||||
self.monitor_index,
|
);
|
||||||
notification,
|
handle_notification(
|
||||||
self.bg_color.clone(),
|
ctx,
|
||||||
self.bg_color_with_alpha.clone(),
|
notification.event,
|
||||||
self.config.transparency_alpha,
|
self.bg_color.clone(),
|
||||||
self.config.grouping,
|
self.bg_color_with_alpha.clone(),
|
||||||
self.config.theme.clone(),
|
self.config.transparency_alpha,
|
||||||
self.render_config.clone(),
|
self.config.grouping,
|
||||||
);
|
self.config.theme.clone(),
|
||||||
|
self.render_config.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_apply_config {
|
if should_apply_config {
|
||||||
self.apply_config(ctx, self.komorebi_notification_state.clone());
|
self.apply_config(ctx, self.monitor_info.clone());
|
||||||
|
|
||||||
// Reposition the Bar
|
// Reposition the Bar
|
||||||
self.position_bar();
|
self.position_bar();
|
||||||
@@ -936,6 +1082,111 @@ impl eframe::App for Komobar {
|
|||||||
let frame = render_config.change_frame_on_bar(frame, &ctx.style());
|
let frame = render_config.change_frame_on_bar(frame, &ctx.style());
|
||||||
|
|
||||||
CentralPanel::default().frame(frame).show(ctx, |ui| {
|
CentralPanel::default().frame(frame).show(ctx, |ui| {
|
||||||
|
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
|
// Apply grouping logic for the bar as a whole
|
||||||
let area_frame = if let Some(frame) = &self.config.frame {
|
let area_frame = if let Some(frame) = &self.config.frame {
|
||||||
Frame::NONE
|
Frame::NONE
|
||||||
@@ -1075,3 +1326,66 @@ pub enum Alignment {
|
|||||||
Center,
|
Center,
|
||||||
Right,
|
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");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+199
-75
@@ -1,11 +1,13 @@
|
|||||||
|
use crate::DEFAULT_PADDING;
|
||||||
|
use crate::bar::exec_powershell;
|
||||||
use crate::render::Grouping;
|
use crate::render::Grouping;
|
||||||
use crate::widgets::widget::WidgetConfig;
|
use crate::widgets::widget::WidgetConfig;
|
||||||
use crate::DEFAULT_PADDING;
|
|
||||||
use eframe::egui::Pos2;
|
use eframe::egui::Pos2;
|
||||||
use eframe::egui::TextBuffer;
|
use eframe::egui::TextBuffer;
|
||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
use komorebi_client::KomorebiTheme;
|
use komorebi_client::PathExt;
|
||||||
use komorebi_client::Rect;
|
use komorebi_client::Rect;
|
||||||
|
use komorebi_client::SocketMessage;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -13,9 +15,10 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
/// The `komorebi.bar.json` configuration file reference for `v0.1.36`
|
/// The `komorebi.bar.json` configuration file reference for `v0.1.40`
|
||||||
pub struct KomobarConfig {
|
pub struct KomobarConfig {
|
||||||
/// Bar height (default: 50)
|
/// Bar height
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 50.0)))]
|
||||||
pub height: Option<f32>,
|
pub height: Option<f32>,
|
||||||
/// Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or
|
/// 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
|
/// 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)
|
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)
|
||||||
pub frame: Option<FrameConfig>,
|
pub frame: Option<FrameConfig>,
|
||||||
/// The monitor index or the full monitor options
|
/// 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
|
/// Font family
|
||||||
pub font_family: Option<String>,
|
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>,
|
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>,
|
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>,
|
pub max_label_width: Option<f32>,
|
||||||
/// Theme
|
/// Theme
|
||||||
pub theme: Option<KomobarTheme>,
|
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>,
|
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>,
|
pub widget_spacing: Option<f32>,
|
||||||
/// Visual grouping for widgets
|
/// Visual grouping for widgets
|
||||||
pub grouping: Option<Grouping>,
|
pub grouping: Option<Grouping>,
|
||||||
|
/// Options for mouse interaction on the bar
|
||||||
|
pub mouse: Option<MouseConfig>,
|
||||||
/// Left side widgets (ordered left-to-right)
|
/// Left side widgets (ordered left-to-right)
|
||||||
pub left_widgets: Vec<WidgetConfig>,
|
pub left_widgets: Vec<WidgetConfig>,
|
||||||
/// Center widgets (ordered left-to-right)
|
/// Center widgets (ordered left-to-right)
|
||||||
@@ -115,7 +126,9 @@ impl KomobarConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if display {
|
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 (canonical, aliases) in map {
|
||||||
for alias in aliases {
|
for alias in aliases {
|
||||||
if raw.contains(alias) {
|
if raw.contains(alias) {
|
||||||
@@ -138,6 +151,7 @@ impl KomobarConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Position configuration
|
||||||
pub struct PositionConfig {
|
pub struct PositionConfig {
|
||||||
/// The desired starting position of the bar (0,0 = top left of the screen)
|
/// The desired starting position of the bar (0,0 = top left of the screen)
|
||||||
#[serde(alias = "position")]
|
#[serde(alias = "position")]
|
||||||
@@ -149,6 +163,7 @@ pub struct PositionConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Frame configuration
|
||||||
pub struct FrameConfig {
|
pub struct FrameConfig {
|
||||||
/// Margin inside the painted frame
|
/// Margin inside the painted frame
|
||||||
pub inner_margin: Position,
|
pub inner_margin: Position,
|
||||||
@@ -157,6 +172,7 @@ pub struct FrameConfig {
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Monitor configuration or monitor index
|
||||||
pub enum MonitorConfigOrIndex {
|
pub enum MonitorConfigOrIndex {
|
||||||
/// The monitor index where you want the bar to show
|
/// The monitor index where you want the bar to show
|
||||||
Index(usize),
|
Index(usize),
|
||||||
@@ -166,6 +182,7 @@ pub enum MonitorConfigOrIndex {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Monitor configuration
|
||||||
pub struct MonitorConfig {
|
pub struct MonitorConfig {
|
||||||
/// Komorebi monitor index of the monitor on which to render the bar
|
/// Komorebi monitor index of the monitor on which to render the bar
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
@@ -183,9 +200,13 @@ pub type Margin = SpacingKind;
|
|||||||
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
|
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
|
||||||
// `GroupedSpacingConfig` with both `vertical` and `horizontal` set to `None` ignoring the
|
// `GroupedSpacingConfig` with both `vertical` and `horizontal` set to `None` ignoring the
|
||||||
// individual values.
|
// individual values.
|
||||||
|
/// Spacing kind
|
||||||
pub enum SpacingKind {
|
pub enum SpacingKind {
|
||||||
|
/// Spacing applied to all sides
|
||||||
All(f32),
|
All(f32),
|
||||||
|
/// Individual spacing applied to each side
|
||||||
Individual(IndividualSpacingConfig),
|
Individual(IndividualSpacingConfig),
|
||||||
|
/// Grouped vertical and horizontal spacing
|
||||||
Grouped(GroupedSpacingConfig),
|
Grouped(GroupedSpacingConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,25 +251,36 @@ impl SpacingKind {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Grouped vertical and horizontal spacing
|
||||||
pub struct GroupedSpacingConfig {
|
pub struct GroupedSpacingConfig {
|
||||||
|
/// Vertical grouped spacing
|
||||||
pub vertical: Option<GroupedSpacingOptions>,
|
pub vertical: Option<GroupedSpacingOptions>,
|
||||||
|
/// Horizontal grouped spacing
|
||||||
pub horizontal: Option<GroupedSpacingOptions>,
|
pub horizontal: Option<GroupedSpacingOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Grouped spacing options
|
||||||
pub enum GroupedSpacingOptions {
|
pub enum GroupedSpacingOptions {
|
||||||
|
/// Symmetrical grouped spacing
|
||||||
Symmetrical(f32),
|
Symmetrical(f32),
|
||||||
|
/// Split grouped spacing
|
||||||
Split(f32, f32),
|
Split(f32, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Individual spacing configuration
|
||||||
pub struct IndividualSpacingConfig {
|
pub struct IndividualSpacingConfig {
|
||||||
|
/// Spacing for the top
|
||||||
pub top: f32,
|
pub top: f32,
|
||||||
|
/// Spacing for the bottom
|
||||||
pub bottom: f32,
|
pub bottom: f32,
|
||||||
|
/// Spacing for the left
|
||||||
pub left: f32,
|
pub left: f32,
|
||||||
|
/// Spacing for the right
|
||||||
pub right: f32,
|
pub right: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,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 {
|
impl KomobarConfig {
|
||||||
pub fn read(path: &PathBuf) -> color_eyre::Result<Self> {
|
pub fn read(path: &PathBuf) -> color_eyre::Result<Self> {
|
||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
@@ -348,6 +527,7 @@ impl KomobarConfig {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Position
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
/// X coordinate
|
/// X coordinate
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
@@ -373,71 +553,11 @@ impl From<Position> for Pos2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
pub use komorebi_themes::KomobarTheme;
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::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>,
|
|
||||||
auto_select_fill: Option<komorebi_themes::CatppuccinValue>,
|
|
||||||
auto_select_text: 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>,
|
|
||||||
auto_select_fill: Option<komorebi_themes::Base16Value>,
|
|
||||||
auto_select_text: Option<komorebi_themes::Base16Value>,
|
|
||||||
},
|
|
||||||
/// A custom Base16 theme
|
|
||||||
Custom {
|
|
||||||
/// Colours of the custom Base16 theme palette
|
|
||||||
colours: Box<komorebi_themes::Base16ColourPalette>,
|
|
||||||
accent: Option<komorebi_themes::Base16Value>,
|
|
||||||
auto_select_fill: Option<komorebi_themes::Base16Value>,
|
|
||||||
auto_select_text: Option<komorebi_themes::Base16Value>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KomorebiTheme> for KomobarTheme {
|
|
||||||
fn from(value: KomorebiTheme) -> Self {
|
|
||||||
match value {
|
|
||||||
KomorebiTheme::Catppuccin {
|
|
||||||
name, bar_accent, ..
|
|
||||||
} => Self::Catppuccin {
|
|
||||||
name,
|
|
||||||
accent: bar_accent,
|
|
||||||
auto_select_fill: None,
|
|
||||||
auto_select_text: None,
|
|
||||||
},
|
|
||||||
KomorebiTheme::Base16 {
|
|
||||||
name, bar_accent, ..
|
|
||||||
} => Self::Base16 {
|
|
||||||
name,
|
|
||||||
accent: bar_accent,
|
|
||||||
auto_select_fill: None,
|
|
||||||
auto_select_text: None,
|
|
||||||
},
|
|
||||||
KomorebiTheme::Custom {
|
|
||||||
colours,
|
|
||||||
bar_accent,
|
|
||||||
..
|
|
||||||
} => Self::Custom {
|
|
||||||
colours,
|
|
||||||
accent: bar_accent,
|
|
||||||
auto_select_fill: None,
|
|
||||||
auto_select_text: None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Label prefix
|
||||||
pub enum LabelPrefix {
|
pub enum LabelPrefix {
|
||||||
/// Show no prefix
|
/// Show no prefix
|
||||||
None,
|
None,
|
||||||
@@ -451,6 +571,7 @@ pub enum LabelPrefix {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Display format
|
||||||
pub enum DisplayFormat {
|
pub enum DisplayFormat {
|
||||||
/// Show only icon
|
/// Show only icon
|
||||||
Icon,
|
Icon,
|
||||||
@@ -465,9 +586,10 @@ pub enum DisplayFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! extend_enum {
|
macro_rules! extend_enum {
|
||||||
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
($(#[$type_meta:meta])* $existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
||||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
$(#[$type_meta])*
|
||||||
pub enum $new_enum {
|
pub enum $new_enum {
|
||||||
// Add new variants
|
// Add new variants
|
||||||
$(
|
$(
|
||||||
@@ -488,7 +610,9 @@ macro_rules! extend_enum {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
|
extend_enum!(
|
||||||
|
/// Workspaces display format
|
||||||
|
DisplayFormat, WorkspacesDisplayFormat, {
|
||||||
/// Show all icons only
|
/// Show all icons only
|
||||||
AllIcons,
|
AllIcons,
|
||||||
/// Show both all icons and text
|
/// Show both all icons and text
|
||||||
|
|||||||
+28
-37
@@ -15,12 +15,10 @@ use eframe::egui::ViewportBuilder;
|
|||||||
use font_loader::system_fonts;
|
use font_loader::system_fonts;
|
||||||
use hotwatch::EventKind;
|
use hotwatch::EventKind;
|
||||||
use hotwatch::Hotwatch;
|
use hotwatch::Hotwatch;
|
||||||
use image::RgbaImage;
|
|
||||||
use komorebi_client::replace_env_in_path;
|
|
||||||
use komorebi_client::PathExt;
|
use komorebi_client::PathExt;
|
||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
use komorebi_client::SubscribeOptions;
|
use komorebi_client::SubscribeOptions;
|
||||||
use std::collections::HashMap;
|
use komorebi_client::replace_env_in_path;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -28,16 +26,14 @@ use std::sync::atomic::AtomicI32;
|
|||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::LazyLock;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::Foundation::LPARAM;
|
use windows::Win32::Foundation::LPARAM;
|
||||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
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::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||||
|
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
|
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||||
use windows_core::BOOL;
|
use windows_core::BOOL;
|
||||||
@@ -53,9 +49,6 @@ pub static DEFAULT_PADDING: f32 = 10.0;
|
|||||||
pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0);
|
pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0);
|
||||||
pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0);
|
pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
pub static ICON_CACHE: LazyLock<Mutex<HashMap<isize, RgbaImage>>> =
|
|
||||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(author, about, version)]
|
#[clap(author, about, version)]
|
||||||
struct Opts {
|
struct Opts {
|
||||||
@@ -110,7 +103,7 @@ fn process_hwnd() -> Option<isize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum KomorebiEvent {
|
pub enum KomorebiEvent {
|
||||||
Notification(komorebi_client::Notification),
|
Notification(Box<komorebi_client::Notification>),
|
||||||
Reconnect,
|
Reconnect,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,15 +114,8 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
if opts.schema {
|
if opts.schema {
|
||||||
let settings = schemars::gen::SchemaSettings::default().with(|s| {
|
let bar_config = schemars::schema_for!(KomobarConfig);
|
||||||
s.option_nullable = false;
|
let schema = serde_json::to_string_pretty(&bar_config)?;
|
||||||
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)?;
|
|
||||||
|
|
||||||
println!("{schema}");
|
println!("{schema}");
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
@@ -144,13 +130,17 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
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()?;
|
color_eyre::install()?;
|
||||||
|
|
||||||
if std::env::var("RUST_LOG").is_err() {
|
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(
|
tracing::subscriber::set_global_default(
|
||||||
@@ -166,8 +156,7 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
home.is_dir(),
|
home.is_dir(),
|
||||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
|
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory"
|
||||||
home_path
|
|
||||||
);
|
);
|
||||||
|
|
||||||
home
|
home
|
||||||
@@ -227,28 +216,30 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
)?)?;
|
)?)?;
|
||||||
|
|
||||||
let (usr_monitor_index, work_area_offset) = match &config.monitor {
|
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)
|
(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
|
let monitor_index = state
|
||||||
.monitor_usr_idx_map
|
.monitor_usr_idx_map
|
||||||
.get(&usr_monitor_index)
|
.get(&usr_monitor_index)
|
||||||
.map_or(usr_monitor_index, |i| *i);
|
.map_or(usr_monitor_index, |i| *i);
|
||||||
|
|
||||||
MONITOR_RIGHT.store(
|
MONITOR_RIGHT.store(
|
||||||
state.monitors.elements()[monitor_index].size().right,
|
state.monitors.elements()[monitor_index].size.right,
|
||||||
Ordering::SeqCst,
|
Ordering::SeqCst,
|
||||||
);
|
);
|
||||||
|
|
||||||
MONITOR_TOP.store(
|
MONITOR_TOP.store(
|
||||||
state.monitors.elements()[monitor_index].size().top,
|
state.monitors.elements()[monitor_index].size.top,
|
||||||
Ordering::SeqCst,
|
Ordering::SeqCst,
|
||||||
);
|
);
|
||||||
|
|
||||||
MONITOR_LEFT.store(
|
MONITOR_LEFT.store(
|
||||||
state.monitors.elements()[monitor_index].size().left,
|
state.monitors.elements()[monitor_index].size.left,
|
||||||
Ordering::SeqCst,
|
Ordering::SeqCst,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -258,11 +249,11 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
None => {
|
None => {
|
||||||
config.position = Some(PositionConfig {
|
config.position = Some(PositionConfig {
|
||||||
start: Some(Position {
|
start: Some(Position {
|
||||||
x: state.monitors.elements()[monitor_index].size().left as f32,
|
x: state.monitors.elements()[monitor_index].size.left as f32,
|
||||||
y: state.monitors.elements()[monitor_index].size().top as f32,
|
y: state.monitors.elements()[monitor_index].size.top as f32,
|
||||||
}),
|
}),
|
||||||
end: Some(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,
|
y: 50.0,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -270,14 +261,14 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
Some(ref mut position) => {
|
Some(ref mut position) => {
|
||||||
if position.start.is_none() {
|
if position.start.is_none() {
|
||||||
position.start = Some(Position {
|
position.start = Some(Position {
|
||||||
x: state.monitors.elements()[monitor_index].size().left as f32,
|
x: state.monitors.elements()[monitor_index].size.left as f32,
|
||||||
y: state.monitors.elements()[monitor_index].size().top as f32,
|
y: state.monitors.elements()[monitor_index].size.top as f32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if position.end.is_none() {
|
if position.end.is_none() {
|
||||||
position.end = Some(Position {
|
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,
|
y: 50.0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -362,7 +353,7 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
while komorebi_client::send_message(
|
while komorebi_client::send_message(
|
||||||
&SocketMessage::AddSubscriberSocket(subscriber_name.clone()),
|
&SocketMessage::AddSubscriberSocket(subscriber_name.clone()),
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
}
|
}
|
||||||
@@ -385,7 +376,7 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
Ok(notification) => {
|
Ok(notification) => {
|
||||||
tracing::debug!("received notification from komorebi");
|
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}")
|
tracing::error!("could not send komorebi notification update to gui thread: {error}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,5 +404,5 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
Ok(Box::new(Komobar::new(cc, rx_gui, rx_config, config)))
|
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,8 +1,8 @@
|
|||||||
|
use crate::AUTO_SELECT_FILL_COLOUR;
|
||||||
|
use crate::AUTO_SELECT_TEXT_COLOUR;
|
||||||
use crate::bar::Alignment;
|
use crate::bar::Alignment;
|
||||||
use crate::config::KomobarConfig;
|
use crate::config::KomobarConfig;
|
||||||
use crate::config::MonitorConfigOrIndex;
|
use crate::config::MonitorConfigOrIndex;
|
||||||
use crate::AUTO_SELECT_FILL_COLOUR;
|
|
||||||
use crate::AUTO_SELECT_TEXT_COLOUR;
|
|
||||||
use eframe::egui::Color32;
|
use eframe::egui::Color32;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::CornerRadius;
|
use eframe::egui::CornerRadius;
|
||||||
@@ -18,27 +18,33 @@ use komorebi_client::Rgb;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
|
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
|
/// Grouping
|
||||||
pub enum Grouping {
|
pub enum Grouping {
|
||||||
/// No grouping is applied
|
/// No grouping is applied
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "None"))]
|
||||||
None,
|
None,
|
||||||
/// Widgets are grouped as a whole
|
/// Widgets are grouped as a whole
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Bar"))]
|
||||||
Bar(GroupingConfig),
|
Bar(GroupingConfig),
|
||||||
/// Widgets are grouped by alignment
|
/// Widgets are grouped by alignment
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Alignment"))]
|
||||||
Alignment(GroupingConfig),
|
Alignment(GroupingConfig),
|
||||||
/// Widgets are grouped individually
|
/// Widgets are grouped individually
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Widget"))]
|
||||||
Widget(GroupingConfig),
|
Widget(GroupingConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
/// Render configuration
|
||||||
pub struct RenderConfig {
|
pub struct RenderConfig {
|
||||||
/// Komorebi monitor index of the monitor on which to render the bar
|
/// Komorebi monitor index of the monitor on which to render the bar
|
||||||
pub monitor_idx: usize,
|
pub monitor_idx: usize,
|
||||||
@@ -93,8 +99,9 @@ impl RenderExt for &KomobarConfig {
|
|||||||
icon_font_id.size *= icon_scale.unwrap_or(1.4).clamp(1.0, 2.0);
|
icon_font_id.size *= icon_scale.unwrap_or(1.4).clamp(1.0, 2.0);
|
||||||
|
|
||||||
let monitor_idx = match &self.monitor {
|
let monitor_idx = match &self.monitor {
|
||||||
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
|
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => monitor_config.index,
|
||||||
MonitorConfigOrIndex::Index(idx) => *idx,
|
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
|
// check if any of the alignments have a komorebi widget with the workspace set to show all icons
|
||||||
@@ -356,6 +363,7 @@ impl RenderConfig {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Grouping configuration
|
||||||
pub struct GroupingConfig {
|
pub struct GroupingConfig {
|
||||||
/// Styles for the grouping
|
/// Styles for the grouping
|
||||||
pub style: Option<GroupingStyle>,
|
pub style: Option<GroupingStyle>,
|
||||||
@@ -367,7 +375,9 @@ pub struct GroupingConfig {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Grouping Style
|
||||||
pub enum GroupingStyle {
|
pub enum GroupingStyle {
|
||||||
|
/// Default
|
||||||
#[serde(alias = "CtByte")]
|
#[serde(alias = "CtByte")]
|
||||||
Default,
|
Default,
|
||||||
/// A shadow is added under the default group. (blur: 4, offset: x-1 y-1, spread: 3)
|
/// A shadow is added under the default group. (blur: 4, offset: x-1 y-1, spread: 3)
|
||||||
@@ -389,8 +399,9 @@ pub enum GroupingStyle {
|
|||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Rounding configuration
|
||||||
pub enum RoundingConfig {
|
pub enum RoundingConfig {
|
||||||
/// All 4 corners are the same
|
/// All 4 corners are the same
|
||||||
Same(f32),
|
Same(f32),
|
||||||
/// All 4 corners are custom. Order: NW, NE, SW, SE
|
/// All 4 corners are custom. Order: NW, NE, SW, SE
|
||||||
Individual([f32; 4]),
|
Individual([f32; 4]),
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,29 +2,31 @@ use crate::config::LabelPrefix;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use starship_battery::units::ratio::percent;
|
|
||||||
use starship_battery::Manager;
|
use starship_battery::Manager;
|
||||||
use starship_battery::State;
|
use starship_battery::State;
|
||||||
|
use starship_battery::units::ratio::percent;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Battery widget configuration
|
||||||
pub struct BatteryConfig {
|
pub struct BatteryConfig {
|
||||||
/// Enable the Battery widget
|
/// Enable the Battery widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
/// Hide the widget if the battery is at full charge
|
/// Hide the widget if the battery is at full charge
|
||||||
pub hide_on_full_charge: Option<bool>,
|
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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
pub label_prefix: Option<LabelPrefix>,
|
||||||
@@ -87,41 +89,41 @@ impl Battery {
|
|||||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||||
output = None;
|
output = None;
|
||||||
|
|
||||||
if let Ok(mut batteries) = self.manager.batteries() {
|
if let Ok(mut batteries) = self.manager.batteries()
|
||||||
if let Some(Ok(first)) = batteries.nth(0) {
|
&& let Some(Ok(first)) = batteries.nth(0)
|
||||||
let percentage = first.state_of_charge().get::<percent>().round() as u8;
|
{
|
||||||
|
let percentage = first.state_of_charge().get::<percent>().round() as u8;
|
||||||
|
|
||||||
if percentage == 100 && self.hide_on_full_charge {
|
if percentage == 100 && self.hide_on_full_charge {
|
||||||
output = None
|
output = None
|
||||||
} else {
|
} else {
|
||||||
match first.state() {
|
match first.state() {
|
||||||
State::Charging => self.state = BatteryState::Charging,
|
State::Charging => self.state = BatteryState::Charging,
|
||||||
State::Discharging => {
|
State::Discharging => {
|
||||||
self.state = match percentage {
|
self.state = match percentage {
|
||||||
p if p > 75 => BatteryState::Discharging,
|
p if p > 75 => BatteryState::Discharging,
|
||||||
p if p > 50 => BatteryState::High,
|
p if p > 50 => BatteryState::High,
|
||||||
p if p > 25 => BatteryState::Medium,
|
p if p > 25 => BatteryState::Medium,
|
||||||
p if p > 10 => BatteryState::Low,
|
p if p > 10 => BatteryState::Low,
|
||||||
_ => BatteryState::Warning,
|
_ => BatteryState::Warning,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +178,11 @@ impl BarWidget for Battery {
|
|||||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
&& let Err(error) = Command::new("cmd.exe")
|
||||||
if let Err(error) = Command::new("cmd.exe")
|
|
||||||
.args(["/C", "start", "ms-settings:batterysaver"])
|
.args(["/C", "start", "ms-settings:batterysaver"])
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
eprintln!("{}", error)
|
eprintln!("{error}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -18,10 +18,12 @@ use sysinfo::System;
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// CPU widget configuration
|
||||||
pub struct CpuConfig {
|
pub struct CpuConfig {
|
||||||
/// Enable the Cpu widget
|
/// Enable the Cpu widget
|
||||||
pub enable: bool,
|
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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
pub label_prefix: Option<LabelPrefix>,
|
||||||
@@ -76,8 +78,8 @@ impl Cpu {
|
|||||||
|
|
||||||
CpuOutput {
|
CpuOutput {
|
||||||
label: match self.label_prefix {
|
label: match self.label_prefix {
|
||||||
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {}%", used),
|
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {used}%"),
|
||||||
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", used),
|
LabelPrefix::None | LabelPrefix::Icon => format!("{used}%"),
|
||||||
},
|
},
|
||||||
selected,
|
selected,
|
||||||
}
|
}
|
||||||
@@ -120,12 +122,10 @@ impl BarWidget for Cpu {
|
|||||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
&& let Err(error) =
|
||||||
if let Err(error) =
|
|
||||||
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
|
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
|
||||||
{
|
{
|
||||||
eprintln!("{}", error)
|
eprintln!("{error}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ use crate::selected_frame::SelectableFrame;
|
|||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
use eframe::egui::WidgetText;
|
use eframe::egui::WidgetText;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ impl CustomModifiers {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Date widget configuration
|
||||||
pub struct DateConfig {
|
pub struct DateConfig {
|
||||||
/// Enable the Date widget
|
/// Enable the Date widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
@@ -103,6 +105,7 @@ impl From<DateConfig> for Date {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Date widget format
|
||||||
pub enum DateFormat {
|
pub enum DateFormat {
|
||||||
/// Month/Date/Year format (09/08/24)
|
/// Month/Date/Year format (09/08/24)
|
||||||
MonthDateYear,
|
MonthDateYear,
|
||||||
@@ -113,8 +116,10 @@ pub enum DateFormat {
|
|||||||
/// Day Date Month Year format (8 September 2024)
|
/// Day Date Month Year format (8 September 2024)
|
||||||
DayDateMonthYear,
|
DayDateMonthYear,
|
||||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||||
Custom(String),
|
Custom(String),
|
||||||
/// Custom format with modifiers
|
/// Custom format with modifiers
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "CustomModifiers"))]
|
||||||
CustomModifiers(CustomModifiers),
|
CustomModifiers(CustomModifiers),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +171,7 @@ impl Date {
|
|||||||
.to_string()
|
.to_string()
|
||||||
.trim()
|
.trim()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
Err(_) => format!("Invalid timezone: {}", timezone),
|
Err(_) => format!("Invalid timezone: {timezone}"),
|
||||||
},
|
},
|
||||||
None => Local::now()
|
None => Local::now()
|
||||||
.format(&self.format.fmt_string())
|
.format(&self.format.fmt_string())
|
||||||
@@ -225,7 +230,7 @@ impl BarWidget for Date {
|
|||||||
if SelectableFrame::new(false)
|
if SelectableFrame::new(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.add(
|
ui.add(
|
||||||
Label::new(WidgetText::LayoutJob(layout_job.clone()))
|
Label::new(WidgetText::LayoutJob(Arc::from(layout_job.clone())))
|
||||||
.selectable(false),
|
.selectable(false),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
use crate::config::LabelPrefix;
|
use crate::config::LabelPrefix;
|
||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
use color_eyre::eyre;
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
use eframe::egui::WidgetText;
|
use eframe::egui::WidgetText;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use windows::Win32::Globalization::LCIDToLocaleName;
|
use windows::Win32::Globalization::LCIDToLocaleName;
|
||||||
@@ -19,15 +21,16 @@ use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyboardLayout;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||||
|
|
||||||
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
|
|
||||||
const ERROR_TEXT: &str = "Error";
|
const ERROR_TEXT: &str = "Error";
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Keyboard widget configuration
|
||||||
pub struct KeyboardConfig {
|
pub struct KeyboardConfig {
|
||||||
/// Enable the Input widget
|
/// Enable the Input widget
|
||||||
pub enable: bool,
|
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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
pub label_prefix: Option<LabelPrefix>,
|
||||||
@@ -35,9 +38,7 @@ pub struct KeyboardConfig {
|
|||||||
|
|
||||||
impl From<KeyboardConfig> for Keyboard {
|
impl From<KeyboardConfig> for Keyboard {
|
||||||
fn from(value: KeyboardConfig) -> Self {
|
fn from(value: KeyboardConfig) -> Self {
|
||||||
let data_refresh_interval = value
|
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
|
||||||
.data_refresh_interval
|
|
||||||
.unwrap_or(DEFAULT_DATA_REFRESH_INTERVAL);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
enable: value.enable,
|
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.
|
/// - `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
|
/// - `Err(())`: Indicates that the function failed to retrieve the locale name or encountered
|
||||||
/// invalid UTF-16 characters during conversion.
|
/// 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 foreground_window_tid = unsafe { GetWindowThreadProcessId(GetForegroundWindow(), None) };
|
||||||
let lcid = unsafe { GetKeyboardLayout(foreground_window_tid) };
|
let lcid = unsafe { GetKeyboardLayout(foreground_window_tid) };
|
||||||
|
|
||||||
@@ -169,7 +170,10 @@ impl BarWidget for Keyboard {
|
|||||||
);
|
);
|
||||||
|
|
||||||
config.apply_on_widget(true, ui, |ui| {
|
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),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ use crate::config::DisplayFormat;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::komorebi::KomorebiLayoutConfig;
|
use crate::widgets::komorebi::KomorebiLayoutConfig;
|
||||||
use eframe::egui::vec2;
|
use color_eyre::eyre;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::CornerRadius;
|
use eframe::egui::CornerRadius;
|
||||||
use eframe::egui::FontId;
|
use eframe::egui::FontId;
|
||||||
@@ -13,11 +13,12 @@ use eframe::egui::Stroke;
|
|||||||
use eframe::egui::StrokeKind;
|
use eframe::egui::StrokeKind;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
|
use eframe::egui::vec2;
|
||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
use serde::de::Error;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Deserializer;
|
use serde::Deserializer;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde::de::Error;
|
||||||
use serde_json::from_str;
|
use serde_json::from_str;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
@@ -25,24 +26,30 @@ use std::fmt::Formatter;
|
|||||||
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Komorebi layout kind
|
||||||
pub enum KomorebiLayout {
|
pub enum KomorebiLayout {
|
||||||
|
/// Predefined layout
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Default"))]
|
||||||
Default(komorebi_client::DefaultLayout),
|
Default(komorebi_client::DefaultLayout),
|
||||||
|
/// Monocle mode
|
||||||
Monocle,
|
Monocle,
|
||||||
|
/// Floating layer
|
||||||
Floating,
|
Floating,
|
||||||
|
/// Paused
|
||||||
Paused,
|
Paused,
|
||||||
|
/// Custom layout
|
||||||
Custom,
|
Custom,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for KomorebiLayout {
|
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
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let s: String = String::deserialize(deserializer)?;
|
let s: String = String::deserialize(deserializer)?;
|
||||||
|
|
||||||
// Attempt to deserialize the string as a DefaultLayout
|
// Attempt to deserialize the string as a DefaultLayout
|
||||||
if let Ok(default_layout) =
|
if let Ok(default_layout) = from_str::<komorebi_client::DefaultLayout>(&format!("\"{s}\""))
|
||||||
from_str::<komorebi_client::DefaultLayout>(&format!("\"{}\"", s))
|
|
||||||
{
|
{
|
||||||
return Ok(KomorebiLayout::Default(default_layout));
|
return Ok(KomorebiLayout::Default(default_layout));
|
||||||
}
|
}
|
||||||
@@ -53,7 +60,7 @@ impl<'de> Deserialize<'de> for KomorebiLayout {
|
|||||||
"Floating" => Ok(KomorebiLayout::Floating),
|
"Floating" => Ok(KomorebiLayout::Floating),
|
||||||
"Paused" => Ok(KomorebiLayout::Paused),
|
"Paused" => Ok(KomorebiLayout::Paused),
|
||||||
"Custom" => Ok(KomorebiLayout::Custom),
|
"Custom" => Ok(KomorebiLayout::Custom),
|
||||||
_ => Err(Error::custom(format!("Invalid layout: {}", s))),
|
_ => Err(Error::custom(format!("Invalid layout: {s}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,16 +99,15 @@ impl KomorebiLayout {
|
|||||||
fn on_click_option(&mut self, monitor_idx: usize, workspace_idx: Option<usize>) {
|
fn on_click_option(&mut self, monitor_idx: usize, workspace_idx: Option<usize>) {
|
||||||
match self {
|
match self {
|
||||||
KomorebiLayout::Default(option) => {
|
KomorebiLayout::Default(option) => {
|
||||||
if let Some(ws_idx) = workspace_idx {
|
if let Some(ws_idx) = workspace_idx
|
||||||
if komorebi_client::send_message(&SocketMessage::WorkspaceLayout(
|
&& komorebi_client::send_message(&SocketMessage::WorkspaceLayout(
|
||||||
monitor_idx,
|
monitor_idx,
|
||||||
ws_idx,
|
ws_idx,
|
||||||
*option,
|
*option,
|
||||||
))
|
))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
tracing::error!("could not send message to komorebi: WorkspaceLayout");
|
tracing::error!("could not send message to komorebi: WorkspaceLayout");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KomorebiLayout::Monocle => {
|
KomorebiLayout::Monocle => {
|
||||||
@@ -188,6 +194,12 @@ impl KomorebiLayout {
|
|||||||
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
|
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);
|
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::Monocle => {}
|
||||||
KomorebiLayout::Floating => {
|
KomorebiLayout::Floating => {
|
||||||
@@ -264,57 +276,53 @@ impl KomorebiLayout {
|
|||||||
show_options = self.on_click(&show_options, monitor_idx, workspace_idx);
|
show_options = self.on_click(&show_options, monitor_idx, workspace_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_options {
|
if show_options && let Some(workspace_idx) = workspace_idx {
|
||||||
if let Some(workspace_idx) = workspace_idx {
|
Frame::NONE.show(ui, |ui| {
|
||||||
Frame::NONE.show(ui, |ui| {
|
ui.add(
|
||||||
ui.add(
|
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
|
||||||
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
|
.selectable(false),
|
||||||
.selectable(false),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
let mut layout_options = layout_config.options.clone().unwrap_or(vec![
|
let mut layout_options = layout_config.options.clone().unwrap_or(vec![
|
||||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
|
||||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),
|
||||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),
|
||||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),
|
||||||
KomorebiLayout::Default(
|
KomorebiLayout::Default(
|
||||||
komorebi_client::DefaultLayout::RightMainVerticalStack,
|
komorebi_client::DefaultLayout::RightMainVerticalStack,
|
||||||
),
|
),
|
||||||
KomorebiLayout::Default(
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::HorizontalStack),
|
||||||
komorebi_client::DefaultLayout::HorizontalStack,
|
KomorebiLayout::Default(
|
||||||
),
|
komorebi_client::DefaultLayout::UltrawideVerticalStack,
|
||||||
KomorebiLayout::Default(
|
),
|
||||||
komorebi_client::DefaultLayout::UltrawideVerticalStack,
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),
|
||||||
),
|
//KomorebiLayout::Custom,
|
||||||
KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),
|
KomorebiLayout::Monocle,
|
||||||
//KomorebiLayout::Custom,
|
KomorebiLayout::Floating,
|
||||||
KomorebiLayout::Monocle,
|
KomorebiLayout::Paused,
|
||||||
KomorebiLayout::Floating,
|
]);
|
||||||
KomorebiLayout::Paused,
|
|
||||||
]);
|
|
||||||
|
|
||||||
for layout_option in &mut layout_options {
|
for layout_option in &mut layout_options {
|
||||||
let is_selected = self == layout_option;
|
let is_selected = self == layout_option;
|
||||||
|
|
||||||
if SelectableFrame::new(is_selected)
|
if SelectableFrame::new(is_selected)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
|
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
|
||||||
})
|
})
|
||||||
.on_hover_text(match layout_option {
|
.on_hover_text(match layout_option {
|
||||||
KomorebiLayout::Default(layout) => layout.to_string(),
|
KomorebiLayout::Default(layout) => layout.to_string(),
|
||||||
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
|
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
|
||||||
KomorebiLayout::Floating => "Toggle tiling".to_string(),
|
KomorebiLayout::Floating => "Toggle tiling".to_string(),
|
||||||
KomorebiLayout::Paused => "Toggle pause".to_string(),
|
KomorebiLayout::Paused => "Toggle pause".to_string(),
|
||||||
KomorebiLayout::Custom => "Custom".to_string(),
|
KomorebiLayout::Custom => "Custom".to_string(),
|
||||||
})
|
})
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
layout_option.on_click_option(monitor_idx, Some(workspace_idx));
|
layout_option.on_click_option(monitor_idx, Some(workspace_idx));
|
||||||
show_options = false;
|
show_options = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
use crate::MAX_LABEL_WIDTH;
|
||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::ui::CustomUi;
|
use crate::ui::CustomUi;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use crate::MAX_LABEL_WIDTH;
|
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
@@ -17,6 +17,7 @@ use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Media widget configuration
|
||||||
pub struct MediaConfig {
|
pub struct MediaConfig {
|
||||||
/// Enable the Media widget
|
/// Enable the Media widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
@@ -40,36 +41,34 @@ impl Media {
|
|||||||
enable,
|
enable,
|
||||||
session_manager: GlobalSystemMediaTransportControlsSessionManager::RequestAsync()
|
session_manager: GlobalSystemMediaTransportControlsSessionManager::RequestAsync()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get()
|
.join()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(&self) {
|
pub fn toggle(&self) {
|
||||||
if let Ok(session) = self.session_manager.GetCurrentSession() {
|
if let Ok(session) = self.session_manager.GetCurrentSession()
|
||||||
if let Ok(op) = session.TryTogglePlayPauseAsync() {
|
&& let Ok(op) = session.TryTogglePlayPauseAsync()
|
||||||
op.get().unwrap_or_default();
|
{
|
||||||
}
|
op.join().unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&mut self) -> String {
|
fn output(&mut self) -> String {
|
||||||
if let Ok(session) = self.session_manager.GetCurrentSession() {
|
if let Ok(session) = self.session_manager.GetCurrentSession()
|
||||||
if let Ok(operation) = session.TryGetMediaPropertiesAsync() {
|
&& let Ok(operation) = session.TryGetMediaPropertiesAsync()
|
||||||
if let Ok(properties) = operation.get() {
|
&& let Ok(properties) = operation.join()
|
||||||
if let (Ok(artist), Ok(title)) = (properties.Artist(), properties.Title()) {
|
&& let (Ok(artist), Ok(title)) = (properties.Artist(), properties.Title())
|
||||||
if artist.is_empty() {
|
{
|
||||||
return format!("{title}");
|
if artist.is_empty() {
|
||||||
}
|
return format!("{title}");
|
||||||
|
|
||||||
if title.is_empty() {
|
|
||||||
return format!("{artist}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return format!("{artist} - {title}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if title.is_empty() {
|
||||||
|
return format!("{artist}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return format!("{artist} - {title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
String::new()
|
String::new()
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -18,10 +18,12 @@ use sysinfo::System;
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Memory widget configuration
|
||||||
pub struct MemoryConfig {
|
pub struct MemoryConfig {
|
||||||
/// Enable the Memory widget
|
/// Enable the Memory widget
|
||||||
pub enable: bool,
|
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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
pub label_prefix: Option<LabelPrefix>,
|
||||||
@@ -79,9 +81,9 @@ impl Memory {
|
|||||||
MemoryOutput {
|
MemoryOutput {
|
||||||
label: match self.label_prefix {
|
label: match self.label_prefix {
|
||||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||||
format!("RAM: {}%", usage)
|
format!("RAM: {usage}%")
|
||||||
}
|
}
|
||||||
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", usage),
|
LabelPrefix::None | LabelPrefix::Icon => format!("{usage}%"),
|
||||||
},
|
},
|
||||||
selected,
|
selected,
|
||||||
}
|
}
|
||||||
@@ -124,12 +126,10 @@ impl BarWidget for Memory {
|
|||||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
&& let Err(error) =
|
||||||
if let Err(error) =
|
|
||||||
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
|
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
|
||||||
{
|
{
|
||||||
eprintln!("{}", error)
|
eprintln!("{error}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
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 battery;
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
pub mod date;
|
pub mod date;
|
||||||
@@ -11,3 +23,151 @@ pub mod storage;
|
|||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
pub mod widget;
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,24 +3,30 @@ use crate::config::LabelPrefix;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Color32;
|
use eframe::egui::Color32;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::process::Command;
|
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::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use sysinfo::Networks;
|
use sysinfo::Networks;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Network widget configuration
|
||||||
pub struct NetworkConfig {
|
pub struct NetworkConfig {
|
||||||
/// Enable the Network widget
|
/// Enable the Network widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
@@ -35,7 +41,8 @@ pub struct NetworkConfig {
|
|||||||
/// Characters to reserve for received and transmitted activity
|
/// Characters to reserve for received and transmitted activity
|
||||||
#[serde(alias = "network_activity_fill_characters")]
|
#[serde(alias = "network_activity_fill_characters")]
|
||||||
pub activity_left_padding: Option<usize>,
|
pub activity_left_padding: Option<usize>,
|
||||||
/// 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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
pub label_prefix: Option<LabelPrefix>,
|
||||||
@@ -45,6 +52,7 @@ pub struct NetworkConfig {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Network select configuration
|
||||||
pub struct NetworkSelectConfig {
|
pub struct NetworkSelectConfig {
|
||||||
/// Select the total received data when it's over this value
|
/// Select the total received data when it's over this value
|
||||||
pub total_received_over: Option<u64>,
|
pub total_received_over: Option<u64>,
|
||||||
@@ -58,24 +66,35 @@ pub struct NetworkSelectConfig {
|
|||||||
|
|
||||||
impl From<NetworkConfig> for Network {
|
impl From<NetworkConfig> for Network {
|
||||||
fn from(value: NetworkConfig) -> Self {
|
fn from(value: NetworkConfig) -> Self {
|
||||||
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
|
let default_refresh_interval = 10;
|
||||||
|
let data_refresh_interval = value
|
||||||
|
.data_refresh_interval
|
||||||
|
.unwrap_or(default_refresh_interval);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
enable: value.enable,
|
enable: value.enable,
|
||||||
show_total_activity: value.show_total_activity,
|
show_total_activity: value.show_total_activity,
|
||||||
show_activity: value.show_activity,
|
show_activity: value.show_activity,
|
||||||
show_default_interface: value.show_default_interface.unwrap_or(true),
|
show_default_interface: value.show_default_interface.unwrap_or(true),
|
||||||
networks_network_activity: Networks::new_with_refreshed_list(),
|
networks_network_activity: Arc::new(Mutex::new(Networks::new_with_refreshed_list())),
|
||||||
default_interface: String::new(),
|
default_interface: Arc::new(Mutex::new(String::new())),
|
||||||
|
interface_generation: Arc::new(AtomicU64::new(0)),
|
||||||
|
default_refresh_interval,
|
||||||
data_refresh_interval,
|
data_refresh_interval,
|
||||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||||
auto_select: value.auto_select,
|
auto_select: value.auto_select,
|
||||||
activity_left_padding: value.activity_left_padding.unwrap_or_default(),
|
activity_left_padding: value.activity_left_padding.unwrap_or_default(),
|
||||||
last_state_total_activity: vec![],
|
last_update_request_default_interface: Instant::now()
|
||||||
last_state_activity: vec![],
|
.checked_sub(Duration::from_secs(default_refresh_interval))
|
||||||
last_updated_network_activity: Instant::now()
|
|
||||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
|
||||||
.unwrap(),
|
.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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,84 +104,139 @@ pub struct Network {
|
|||||||
pub show_total_activity: bool,
|
pub show_total_activity: bool,
|
||||||
pub show_activity: bool,
|
pub show_activity: bool,
|
||||||
pub show_default_interface: bool,
|
pub show_default_interface: bool,
|
||||||
networks_network_activity: Networks,
|
networks_network_activity: Arc<Mutex<Networks>>,
|
||||||
|
default_refresh_interval: u64,
|
||||||
data_refresh_interval: u64,
|
data_refresh_interval: u64,
|
||||||
label_prefix: LabelPrefix,
|
label_prefix: LabelPrefix,
|
||||||
auto_select: Option<NetworkSelectConfig>,
|
auto_select: Option<NetworkSelectConfig>,
|
||||||
default_interface: String,
|
default_interface: Arc<Mutex<String>>,
|
||||||
last_state_total_activity: Vec<NetworkReading>,
|
interface_generation: Arc<AtomicU64>,
|
||||||
last_state_activity: Vec<NetworkReading>,
|
last_update_request_default_interface: Instant,
|
||||||
last_updated_network_activity: 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,
|
activity_left_padding: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
fn default_interface(&mut self) {
|
fn update_default_interface_async(&mut self) {
|
||||||
if let Ok(interface) = netdev::get_default_interface() {
|
let gen_ = self.interface_generation.fetch_add(1, Ordering::SeqCst) + 1;
|
||||||
if let Some(friendly_name) = &interface.friendly_name {
|
let gen_arc = Arc::clone(&self.interface_generation);
|
||||||
self.default_interface.clone_from(friendly_name);
|
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 network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
|
fn default_interface(&mut self) -> String {
|
||||||
let mut activity = self.last_state_activity.clone();
|
let current = self.default_interface.lock().unwrap().clone();
|
||||||
let mut total_activity = self.last_state_total_activity.clone();
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
if now.duration_since(self.last_updated_network_activity)
|
if now.duration_since(self.last_update_request_default_interface)
|
||||||
> Duration::from_secs(self.data_refresh_interval)
|
> Duration::from_secs(self.default_refresh_interval)
|
||||||
{
|
{
|
||||||
activity.clear();
|
self.last_update_request_default_interface = now;
|
||||||
total_activity.clear();
|
self.update_default_interface_async();
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(interface) = netdev::get_default_interface() {
|
current
|
||||||
if let Some(friendly_name) = &interface.friendly_name {
|
}
|
||||||
self.default_interface.clone_from(friendly_name);
|
|
||||||
|
|
||||||
self.networks_network_activity.refresh(true);
|
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);
|
||||||
|
|
||||||
for (interface_name, data) in &self.networks_network_activity {
|
thread::spawn(move || {
|
||||||
if friendly_name.eq(interface_name) {
|
let mut activity = Vec::new();
|
||||||
if self.show_activity {
|
let mut total_activity = Vec::new();
|
||||||
let received = Self::to_pretty_bytes(
|
|
||||||
data.received(),
|
|
||||||
self.data_refresh_interval,
|
|
||||||
);
|
|
||||||
let transmitted = Self::to_pretty_bytes(
|
|
||||||
data.transmitted(),
|
|
||||||
self.data_refresh_interval,
|
|
||||||
);
|
|
||||||
|
|
||||||
activity.push(NetworkReading::new(
|
if let Ok(interface) = netdev::get_default_interface()
|
||||||
NetworkReadingFormat::Speed,
|
&& let Some(friendly_name) = &interface.friendly_name
|
||||||
ReadingValue::from(received),
|
&& let Ok(mut networks) = networks_network_activity_arc.lock()
|
||||||
ReadingValue::from(transmitted),
|
{
|
||||||
));
|
networks.refresh(true);
|
||||||
}
|
|
||||||
|
|
||||||
if self.show_total_activity {
|
for (interface_name, data) in &*networks {
|
||||||
let total_received =
|
if friendly_name.eq(interface_name) {
|
||||||
Self::to_pretty_bytes(data.total_received(), 1);
|
if show_activity {
|
||||||
let total_transmitted =
|
let received =
|
||||||
Self::to_pretty_bytes(data.total_transmitted(), 1);
|
Network::to_pretty_bytes(data.received(), data_refresh_interval);
|
||||||
|
let transmitted =
|
||||||
|
Network::to_pretty_bytes(data.transmitted(), data_refresh_interval);
|
||||||
|
|
||||||
total_activity.push(NetworkReading::new(
|
activity.push(NetworkReading::new(
|
||||||
NetworkReadingFormat::Total,
|
NetworkReadingFormat::Speed,
|
||||||
ReadingValue::from(total_received),
|
ReadingValue::from(received),
|
||||||
ReadingValue::from(total_transmitted),
|
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),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_state_activity.clone_from(&activity);
|
// Only update if this is the latest request
|
||||||
self.last_state_total_activity.clone_from(&total_activity);
|
if gen_ == gen_arc.load(Ordering::SeqCst) {
|
||||||
self.last_updated_network_activity = now;
|
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)
|
(activity, total_activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,10 +386,9 @@ impl Network {
|
|||||||
if SelectableFrame::new_auto(selected, auto_focus_fill)
|
if SelectableFrame::new_auto(selected, auto_focus_fill)
|
||||||
.show(ui, add_contents)
|
.show(ui, add_contents)
|
||||||
.clicked()
|
.clicked()
|
||||||
|
&& let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn()
|
||||||
{
|
{
|
||||||
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() {
|
eprintln!("{error}");
|
||||||
eprintln!("{}", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,9 +507,9 @@ impl BarWidget for Network {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.show_default_interface {
|
if self.show_default_interface {
|
||||||
self.default_interface();
|
let mut self_default_interface = self.default_interface();
|
||||||
|
|
||||||
if !self.default_interface.is_empty() {
|
if !self_default_interface.is_empty() {
|
||||||
let mut layout_job = LayoutJob::simple(
|
let mut layout_job = LayoutJob::simple(
|
||||||
match self.label_prefix {
|
match self.label_prefix {
|
||||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||||
@@ -450,11 +523,11 @@ impl BarWidget for Network {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
||||||
self.default_interface.insert_str(0, "NET: ");
|
self_default_interface.insert_str(0, "NET: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
layout_job.append(
|
layout_job.append(
|
||||||
&self.default_interface,
|
&self_default_interface,
|
||||||
10.0,
|
10.0,
|
||||||
TextFormat {
|
TextFormat {
|
||||||
font_id: config.text_font_id.clone(),
|
font_id: config.text_font_id.clone(),
|
||||||
@@ -535,6 +608,6 @@ enum DataUnit {
|
|||||||
|
|
||||||
impl fmt::Display for DataUnit {
|
impl fmt::Display for DataUnit {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{self:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ use crate::config::LabelPrefix;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -18,13 +18,21 @@ use sysinfo::Disks;
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Storage widget configuration
|
||||||
pub struct StorageConfig {
|
pub struct StorageConfig {
|
||||||
/// Enable the Storage widget
|
/// Enable the Storage widget
|
||||||
pub enable: bool,
|
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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
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]]
|
/// Select when the current percentage is over this value [[1-100]]
|
||||||
pub auto_select_over: Option<u8>,
|
pub auto_select_over: Option<u8>,
|
||||||
/// Hide when the current percentage is under this value [[1-100]]
|
/// Hide when the current percentage is under this value [[1-100]]
|
||||||
@@ -38,6 +46,8 @@ impl From<StorageConfig> for Storage {
|
|||||||
disks: Disks::new_with_refreshed_list(),
|
disks: Disks::new_with_refreshed_list(),
|
||||||
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
|
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
|
||||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||||
|
show_read_only_disks: value.show_read_only_disks.unwrap_or(false),
|
||||||
|
show_removable_disks: value.show_removable_disks.unwrap_or(true),
|
||||||
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
|
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
|
||||||
auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)),
|
auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)),
|
||||||
last_updated: Instant::now(),
|
last_updated: Instant::now(),
|
||||||
@@ -55,6 +65,8 @@ pub struct Storage {
|
|||||||
disks: Disks,
|
disks: Disks,
|
||||||
data_refresh_interval: u64,
|
data_refresh_interval: u64,
|
||||||
label_prefix: LabelPrefix,
|
label_prefix: LabelPrefix,
|
||||||
|
show_read_only_disks: bool,
|
||||||
|
show_removable_disks: bool,
|
||||||
auto_select_over: Option<u8>,
|
auto_select_over: Option<u8>,
|
||||||
auto_hide_under: Option<u8>,
|
auto_hide_under: Option<u8>,
|
||||||
last_updated: Instant,
|
last_updated: Instant,
|
||||||
@@ -71,6 +83,12 @@ impl Storage {
|
|||||||
let mut disks = vec![];
|
let mut disks = vec![];
|
||||||
|
|
||||||
for disk in &self.disks {
|
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 mount = disk.mount_point();
|
||||||
let total = disk.total_space();
|
let total = disk.total_space();
|
||||||
let available = disk.available_space();
|
let available = disk.available_space();
|
||||||
@@ -87,7 +105,7 @@ impl Storage {
|
|||||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||||
format!("{} {}%", mount.to_string_lossy(), percentage)
|
format!("{} {}%", mount.to_string_lossy(), percentage)
|
||||||
}
|
}
|
||||||
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", percentage),
|
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage}%"),
|
||||||
},
|
},
|
||||||
selected,
|
selected,
|
||||||
})
|
})
|
||||||
@@ -142,17 +160,15 @@ impl BarWidget for Storage {
|
|||||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
&& let Err(error) = Command::new("cmd.exe")
|
||||||
if let Err(error) = Command::new("cmd.exe")
|
|
||||||
.args([
|
.args([
|
||||||
"/C",
|
"/C",
|
||||||
"explorer.exe",
|
"explorer.exe",
|
||||||
output.label.split(' ').collect::<Vec<&str>>()[0],
|
output.label.split(' ').collect::<Vec<&str>>()[0],
|
||||||
])
|
])
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
eprintln!("{}", error)
|
eprintln!("{error}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use crate::widgets::widget::BarWidget;
|
|||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use chrono::NaiveTime;
|
use chrono::NaiveTime;
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::CornerRadius;
|
use eframe::egui::CornerRadius;
|
||||||
@@ -16,6 +15,7 @@ use eframe::egui::Stroke;
|
|||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use eframe::epaint::StrokeKind;
|
use eframe::epaint::StrokeKind;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -72,6 +72,7 @@ lazy_static! {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Time widget configuration
|
||||||
pub struct TimeConfig {
|
pub struct TimeConfig {
|
||||||
/// Enable the Time widget
|
/// Enable the Time widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
@@ -92,7 +93,7 @@ pub struct TimeConfig {
|
|||||||
///}
|
///}
|
||||||
/// ```
|
/// ```
|
||||||
pub timezone: Option<String>,
|
pub timezone: Option<String>,
|
||||||
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)
|
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00
|
||||||
pub changing_icon: Option<bool>,
|
pub changing_icon: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +120,7 @@ impl From<TimeConfig> for Time {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Time format
|
||||||
pub enum TimeFormat {
|
pub enum TimeFormat {
|
||||||
/// Twelve-hour format (with seconds)
|
/// Twelve-hour format (with seconds)
|
||||||
TwelveHour,
|
TwelveHour,
|
||||||
@@ -133,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)
|
/// Twenty-four-hour format displayed as a binary clock with rectangles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)
|
||||||
BinaryRectangle,
|
BinaryRectangle,
|
||||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +212,7 @@ impl Time {
|
|||||||
Some(dt.time()),
|
Some(dt.time()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Err(_) => (format!("Invalid timezone: {:?}", timezone), None),
|
Err(_) => (format!("Invalid timezone: {timezone:?}"), None),
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let dt = Local::now();
|
let dt = Local::now();
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
use crate::selected_frame::SelectableFrame;
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::widgets::widget::BarWidget;
|
use crate::widgets::widget::BarWidget;
|
||||||
use eframe::egui::text::LayoutJob;
|
|
||||||
use eframe::egui::Align;
|
use eframe::egui::Align;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::TextFormat;
|
use eframe::egui::TextFormat;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -16,10 +16,12 @@ use std::time::Instant;
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Update widget configuration
|
||||||
pub struct UpdateConfig {
|
pub struct UpdateConfig {
|
||||||
/// Enable the Update widget
|
/// Enable the Update widget
|
||||||
pub enable: bool,
|
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>,
|
pub data_refresh_interval: Option<u64>,
|
||||||
/// Display label prefix
|
/// Display label prefix
|
||||||
pub label_prefix: Option<LabelPrefix>,
|
pub label_prefix: Option<LabelPrefix>,
|
||||||
@@ -140,16 +142,14 @@ impl BarWidget for Update {
|
|||||||
if SelectableFrame::new(false)
|
if SelectableFrame::new(false)
|
||||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
&& let Err(error) = Command::new("explorer.exe")
|
||||||
if let Err(error) = Command::new("explorer.exe")
|
|
||||||
.args([format!(
|
.args([format!(
|
||||||
"https://github.com/LGUG2Z/komorebi/releases/v{}",
|
"https://github.com/LGUG2Z/komorebi/releases/v{}",
|
||||||
self.latest_version
|
self.latest_version
|
||||||
)])
|
)])
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
eprintln!("{}", error)
|
eprintln!("{error}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
|
use crate::widgets::applications::Applications;
|
||||||
|
use crate::widgets::applications::ApplicationsConfig;
|
||||||
use crate::widgets::battery::Battery;
|
use crate::widgets::battery::Battery;
|
||||||
use crate::widgets::battery::BatteryConfig;
|
use crate::widgets::battery::BatteryConfig;
|
||||||
use crate::widgets::cpu::Cpu;
|
use crate::widgets::cpu::Cpu;
|
||||||
@@ -32,23 +34,50 @@ pub trait BarWidget {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Widget configuration
|
||||||
pub enum WidgetConfig {
|
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),
|
Battery(BatteryConfig),
|
||||||
|
/// CPU widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Cpu"))]
|
||||||
Cpu(CpuConfig),
|
Cpu(CpuConfig),
|
||||||
|
/// Date widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Date"))]
|
||||||
Date(DateConfig),
|
Date(DateConfig),
|
||||||
|
/// Keyboard widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Keyboard"))]
|
||||||
Keyboard(KeyboardConfig),
|
Keyboard(KeyboardConfig),
|
||||||
|
/// Komorebi widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Komorebi"))]
|
||||||
Komorebi(KomorebiConfig),
|
Komorebi(KomorebiConfig),
|
||||||
|
/// Media widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Media"))]
|
||||||
Media(MediaConfig),
|
Media(MediaConfig),
|
||||||
|
/// Memory widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Memory"))]
|
||||||
Memory(MemoryConfig),
|
Memory(MemoryConfig),
|
||||||
|
/// Network widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Network"))]
|
||||||
Network(NetworkConfig),
|
Network(NetworkConfig),
|
||||||
|
/// Storage widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Storage"))]
|
||||||
Storage(StorageConfig),
|
Storage(StorageConfig),
|
||||||
|
/// Time widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Time"))]
|
||||||
Time(TimeConfig),
|
Time(TimeConfig),
|
||||||
|
/// Update widget configuration
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "Update"))]
|
||||||
Update(UpdateConfig),
|
Update(UpdateConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetConfig {
|
impl WidgetConfig {
|
||||||
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
|
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
|
||||||
match self {
|
match self {
|
||||||
|
WidgetConfig::Applications(config) => Box::new(Applications::from(config)),
|
||||||
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
|
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
|
||||||
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
|
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
|
||||||
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
|
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
|
||||||
@@ -65,6 +94,7 @@ impl WidgetConfig {
|
|||||||
|
|
||||||
pub fn enabled(&self) -> bool {
|
pub fn enabled(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
WidgetConfig::Applications(config) => config.enable,
|
||||||
WidgetConfig::Battery(config) => config.enable,
|
WidgetConfig::Battery(config) => config.enable,
|
||||||
WidgetConfig::Cpu(config) => config.enable,
|
WidgetConfig::Cpu(config) => config.enable,
|
||||||
WidgetConfig::Date(config) => config.enable,
|
WidgetConfig::Date(config) => config.enable,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-client"
|
name = "komorebi-client"
|
||||||
version = "0.1.36"
|
version = "0.1.40"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
komorebi = { path = "../komorebi" }
|
komorebi = { path = "../komorebi", default-features = false }
|
||||||
|
|
||||||
uds_windows = { workspace = true }
|
uds_windows = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["schemars"]
|
default = ["schemars"]
|
||||||
schemars = ["komorebi/schemars"]
|
schemars = ["komorebi/default"]
|
||||||
|
|||||||
+40
-27
@@ -1,8 +1,36 @@
|
|||||||
#![warn(clippy::all)]
|
#![warn(clippy::all)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
#![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::PerAnimationPrefixConfig;
|
||||||
|
pub use komorebi::animation::prefix::AnimationPrefix;
|
||||||
pub use komorebi::asc::ApplicationSpecificConfiguration;
|
pub use komorebi::asc::ApplicationSpecificConfiguration;
|
||||||
pub use komorebi::border_manager::BorderInfo;
|
pub use komorebi::border_manager::BorderInfo;
|
||||||
pub use komorebi::config_generation::ApplicationConfiguration;
|
pub use komorebi::config_generation::ApplicationConfiguration;
|
||||||
@@ -11,8 +39,6 @@ pub use komorebi::config_generation::IdWithIdentifierAndComment;
|
|||||||
pub use komorebi::config_generation::MatchingRule;
|
pub use komorebi::config_generation::MatchingRule;
|
||||||
pub use komorebi::config_generation::MatchingStrategy;
|
pub use komorebi::config_generation::MatchingStrategy;
|
||||||
pub use komorebi::container::Container;
|
pub use komorebi::container::Container;
|
||||||
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
|
|
||||||
pub use komorebi::core::replace_env_in_path;
|
|
||||||
pub use komorebi::core::AnimationStyle;
|
pub use komorebi::core::AnimationStyle;
|
||||||
pub use komorebi::core::ApplicationIdentifier;
|
pub use komorebi::core::ApplicationIdentifier;
|
||||||
pub use komorebi::core::Arrangement;
|
pub use komorebi::core::Arrangement;
|
||||||
@@ -42,40 +68,24 @@ pub use komorebi::core::StackbarLabel;
|
|||||||
pub use komorebi::core::StackbarMode;
|
pub use komorebi::core::StackbarMode;
|
||||||
pub use komorebi::core::StateQuery;
|
pub use komorebi::core::StateQuery;
|
||||||
pub use komorebi::core::WindowKind;
|
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::Monitor;
|
||||||
pub use komorebi::monitor_reconciliator::MonitorNotification;
|
pub use komorebi::monitor_reconciliator::MonitorNotification;
|
||||||
pub use komorebi::ring::Ring;
|
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::win32_display_data;
|
||||||
pub use komorebi::window::Window;
|
pub use komorebi::window::Window;
|
||||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||||
pub use komorebi::workspace::Workspace;
|
pub use komorebi::workspace::Workspace;
|
||||||
pub use komorebi::workspace::WorkspaceGlobals;
|
pub use komorebi::workspace::WorkspaceGlobals;
|
||||||
pub use komorebi::workspace::WorkspaceLayer;
|
pub use komorebi::workspace::WorkspaceLayer;
|
||||||
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::GlobalState;
|
|
||||||
pub use komorebi::KomorebiTheme;
|
|
||||||
pub use komorebi::MonitorConfig;
|
|
||||||
pub use komorebi::Notification;
|
|
||||||
pub use komorebi::NotificationEvent;
|
|
||||||
pub use komorebi::PredefinedAspectRatio;
|
|
||||||
pub use komorebi::Rgb;
|
|
||||||
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 komorebi::DATA_DIR;
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@@ -93,12 +103,15 @@ pub fn send_message(message: &SocketMessage) -> std::io::Result<()> {
|
|||||||
stream.write_all(serde_json::to_string(message)?.as_bytes())
|
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 socket = DATA_DIR.join(KOMOREBI);
|
||||||
let mut stream = UnixStream::connect(socket)?;
|
let mut stream = UnixStream::connect(socket)?;
|
||||||
stream.set_write_timeout(Some(Duration::from_secs(1)))?;
|
stream.set_write_timeout(Some(Duration::from_secs(1)))?;
|
||||||
let msgs = messages.into_iter().fold(String::new(), |mut s, m| {
|
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_str(&m_str);
|
||||||
s.push('\n');
|
s.push('\n');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-gui"
|
name = "komorebi-gui"
|
||||||
version = "0.1.36"
|
version = "0.1.40"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
komorebi-client = { path = "../komorebi-client" }
|
komorebi-client = { path = "../komorebi-client", default-features = false }
|
||||||
|
|
||||||
eframe = { workspace = true }
|
eframe = { workspace = true }
|
||||||
egui_extras = { workspace = true }
|
egui_extras = { workspace = true }
|
||||||
random_word = { version = "0.5", features = ["en"] }
|
random_word = { version = "0.5", features = ["en"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
windows-core = { workspace = true }
|
windows-core = { workspace = true }
|
||||||
windows = { workspace = true }
|
windows = { workspace = true }
|
||||||
|
|||||||
+14
-14
@@ -1,9 +1,9 @@
|
|||||||
#![warn(clippy::all)]
|
#![warn(clippy::all)]
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::color_picker::Alpha;
|
|
||||||
use eframe::egui::Color32;
|
use eframe::egui::Color32;
|
||||||
use eframe::egui::ViewportBuilder;
|
use eframe::egui::ViewportBuilder;
|
||||||
|
use eframe::egui::color_picker::Alpha;
|
||||||
use komorebi_client::BorderStyle;
|
use komorebi_client::BorderStyle;
|
||||||
use komorebi_client::Colour;
|
use komorebi_client::Colour;
|
||||||
use komorebi_client::DefaultLayout;
|
use komorebi_client::DefaultLayout;
|
||||||
@@ -78,8 +78,8 @@ impl From<&komorebi_client::Monitor> for MonitorConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
size: *value.size(),
|
size: value.size,
|
||||||
work_area_offset: value.work_area_offset().unwrap_or_default(),
|
work_area_offset: value.work_area_offset.unwrap_or_default(),
|
||||||
workspaces,
|
workspaces,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,22 +95,22 @@ struct WorkspaceConfig {
|
|||||||
|
|
||||||
impl From<&komorebi_client::Workspace> for WorkspaceConfig {
|
impl From<&komorebi_client::Workspace> for WorkspaceConfig {
|
||||||
fn from(value: &komorebi_client::Workspace) -> Self {
|
fn from(value: &komorebi_client::Workspace) -> Self {
|
||||||
let layout = match value.layout() {
|
let layout = match value.layout {
|
||||||
Layout::Default(layout) => *layout,
|
Layout::Default(layout) => layout,
|
||||||
Layout::Custom(_) => DefaultLayout::BSP,
|
Layout::Custom(_) => DefaultLayout::BSP,
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = value
|
let name = value
|
||||||
.name()
|
.name
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());
|
.unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
layout,
|
layout,
|
||||||
name,
|
name,
|
||||||
tile: *value.tile(),
|
tile: value.tile,
|
||||||
workspace_padding: value.workspace_padding().unwrap_or(20),
|
workspace_padding: value.workspace_padding.unwrap_or(20),
|
||||||
container_padding: value.container_padding().unwrap_or(20),
|
container_padding: value.container_padding.unwrap_or(20),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ impl eframe::App for KomorebiGui {
|
|||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ctx.set_pixels_per_point(2.0);
|
ctx.set_pixels_per_point(2.0);
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
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("Debugging", |ui| {
|
||||||
ui.collapsing("Window Rules", |ui| {
|
ui.collapsing("Window Rules", |ui| {
|
||||||
let window = Window::from(self.debug_hwnd);
|
let window = Window::from(self.debug_hwnd);
|
||||||
@@ -437,7 +437,7 @@ impl eframe::App for KomorebiGui {
|
|||||||
BorderStyle::Square,
|
BorderStyle::Square,
|
||||||
] {
|
] {
|
||||||
if ui
|
if ui
|
||||||
.add(egui::SelectableLabel::new(
|
.add(egui::Button::selectable(
|
||||||
self.border_config.border_style == option,
|
self.border_config.border_style == option,
|
||||||
option.to_string(),
|
option.to_string(),
|
||||||
))
|
))
|
||||||
@@ -494,7 +494,7 @@ impl eframe::App for KomorebiGui {
|
|||||||
StackbarMode::Always,
|
StackbarMode::Always,
|
||||||
] {
|
] {
|
||||||
if ui
|
if ui
|
||||||
.add(egui::SelectableLabel::new(
|
.add(egui::Button::selectable(
|
||||||
self.stackbar_config.mode == option,
|
self.stackbar_config.mode == option,
|
||||||
option.to_string(),
|
option.to_string(),
|
||||||
))
|
))
|
||||||
@@ -513,7 +513,7 @@ impl eframe::App for KomorebiGui {
|
|||||||
ui.collapsing("Label", |ui| {
|
ui.collapsing("Label", |ui| {
|
||||||
for option in [StackbarLabel::Process, StackbarLabel::Title] {
|
for option in [StackbarLabel::Process, StackbarLabel::Title] {
|
||||||
if ui
|
if ui
|
||||||
.add(egui::SelectableLabel::new(
|
.add(egui::Button::selectable(
|
||||||
self.stackbar_config.label == option,
|
self.stackbar_config.label == option,
|
||||||
option.to_string(),
|
option.to_string(),
|
||||||
))
|
))
|
||||||
@@ -772,7 +772,7 @@ impl eframe::App for KomorebiGui {
|
|||||||
DefaultLayout::Grid,
|
DefaultLayout::Grid,
|
||||||
] {
|
] {
|
||||||
if ui
|
if ui
|
||||||
.add(egui::SelectableLabel::new(
|
.add(egui::Button::selectable(
|
||||||
workspace.layout == option,
|
workspace.layout == option,
|
||||||
option.to_string(),
|
option.to_string(),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -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,12 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-themes"
|
name = "komorebi-themes"
|
||||||
version = "0.1.36"
|
version = "0.1.40"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "96f26c88d83781f234d42222293ec73d23a39ad8" }
|
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "b9e26b31f7a0e7ed239b14e5317e95d1bdc544bd" }
|
||||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
|
#catppuccin-egui = { version = "5", default-features = false, features = ["egui32"] }
|
||||||
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
|
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a", default-features = false, features = [
|
||||||
|
"egui33",
|
||||||
|
] }
|
||||||
eframe = { workspace = true }
|
eframe = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
use hex_color::HexColor;
|
use hex_color::HexColor;
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
use schemars::gen::SchemaGenerator;
|
use schemars::Schema;
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
use schemars::schema::InstanceType;
|
use schemars::SchemaGenerator;
|
||||||
#[cfg(feature = "schemars")]
|
|
||||||
use schemars::schema::Schema;
|
|
||||||
#[cfg(feature = "schemars")]
|
|
||||||
use schemars::schema::SchemaObject;
|
|
||||||
|
|
||||||
use crate::Color32;
|
use crate::Color32;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -15,6 +11,7 @@ use serde::Serialize;
|
|||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Colour representation
|
||||||
pub enum Colour {
|
pub enum Colour {
|
||||||
/// Colour represented as RGB
|
/// Colour represented as RGB
|
||||||
Rgb(Rgb),
|
Rgb(Rgb),
|
||||||
@@ -56,22 +53,22 @@ impl From<Colour> for Color32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Colour represented as a Hex string
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Hex(pub HexColor);
|
pub struct Hex(pub HexColor);
|
||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
impl schemars::JsonSchema for Hex {
|
impl schemars::JsonSchema for Hex {
|
||||||
fn schema_name() -> String {
|
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||||
String::from("Hex")
|
std::borrow::Cow::Borrowed("Hex")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||||
SchemaObject {
|
schemars::json_schema!({
|
||||||
instance_type: Some(InstanceType::String.into()),
|
"type": "string",
|
||||||
format: Some("color-hex".to_string()),
|
"format": "color-hex",
|
||||||
..Default::default()
|
"description": "Colour represented as a Hex string"
|
||||||
}
|
})
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +83,7 @@ impl From<Colour> for u32 {
|
|||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Colour represented as RGB
|
||||||
pub struct Rgb {
|
pub struct Rgb {
|
||||||
/// Red
|
/// Red
|
||||||
pub r: u32,
|
pub r: u32,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use crate::Base16ColourPalette;
|
||||||
use crate::colour::Colour;
|
use crate::colour::Colour;
|
||||||
use crate::colour::Hex;
|
use crate::colour::Hex;
|
||||||
use crate::Base16ColourPalette;
|
|
||||||
use hex_color::HexColor;
|
use hex_color::HexColor;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@@ -12,9 +12,12 @@ use serde::Serialize;
|
|||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Theme variant
|
||||||
pub enum ThemeVariant {
|
pub enum ThemeVariant {
|
||||||
#[default]
|
#[default]
|
||||||
|
/// Dark variant
|
||||||
Dark,
|
Dark,
|
||||||
|
/// Light variant
|
||||||
Light,
|
Light,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+349
-7
@@ -4,8 +4,8 @@
|
|||||||
pub mod colour;
|
pub mod colour;
|
||||||
mod generator;
|
mod generator;
|
||||||
|
|
||||||
pub use generator::generate_base16_palette;
|
|
||||||
pub use generator::ThemeVariant;
|
pub use generator::ThemeVariant;
|
||||||
|
pub use generator::generate_base16_palette;
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -16,30 +16,31 @@ use strum::IntoEnumIterator;
|
|||||||
use crate::colour::Colour;
|
use crate::colour::Colour;
|
||||||
pub use base16_egui_themes::Base16;
|
pub use base16_egui_themes::Base16;
|
||||||
pub use catppuccin_egui;
|
pub use catppuccin_egui;
|
||||||
use eframe::egui::style::Selection;
|
|
||||||
use eframe::egui::style::WidgetVisuals;
|
|
||||||
use eframe::egui::style::Widgets;
|
|
||||||
pub use eframe::egui::Color32;
|
pub use eframe::egui::Color32;
|
||||||
use eframe::egui::Shadow;
|
use eframe::egui::Shadow;
|
||||||
use eframe::egui::Stroke;
|
use eframe::egui::Stroke;
|
||||||
use eframe::egui::Style;
|
use eframe::egui::Style;
|
||||||
use eframe::egui::Visuals;
|
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;
|
use serde_variant::to_variant_name;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
|
/// Theme
|
||||||
pub enum Theme {
|
pub enum Theme {
|
||||||
/// A theme from catppuccin-egui
|
/// Theme from catppuccin-egui
|
||||||
Catppuccin {
|
Catppuccin {
|
||||||
name: Catppuccin,
|
name: Catppuccin,
|
||||||
accent: Option<CatppuccinValue>,
|
accent: Option<CatppuccinValue>,
|
||||||
},
|
},
|
||||||
/// A theme from base16-egui-themes
|
/// Theme from base16-egui-themes
|
||||||
Base16 {
|
Base16 {
|
||||||
name: Base16,
|
name: Base16,
|
||||||
accent: Option<Base16Value>,
|
accent: Option<Base16Value>,
|
||||||
},
|
},
|
||||||
/// A custom base16 palette
|
/// Custom base16 palette
|
||||||
Custom {
|
Custom {
|
||||||
palette: Box<Base16ColourPalette>,
|
palette: Box<Base16ColourPalette>,
|
||||||
accent: Option<Base16Value>,
|
accent: Option<Base16Value>,
|
||||||
@@ -47,22 +48,39 @@ pub enum Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
|
/// Base16 colour palette: https://github.com/chriskempson/base16
|
||||||
pub struct Base16ColourPalette {
|
pub struct Base16ColourPalette {
|
||||||
|
/// Base00
|
||||||
pub base_00: Colour,
|
pub base_00: Colour,
|
||||||
|
/// Base01
|
||||||
pub base_01: Colour,
|
pub base_01: Colour,
|
||||||
|
/// Base02
|
||||||
pub base_02: Colour,
|
pub base_02: Colour,
|
||||||
|
/// Base03
|
||||||
pub base_03: Colour,
|
pub base_03: Colour,
|
||||||
|
/// Base04
|
||||||
pub base_04: Colour,
|
pub base_04: Colour,
|
||||||
|
/// Base05
|
||||||
pub base_05: Colour,
|
pub base_05: Colour,
|
||||||
|
/// Base06
|
||||||
pub base_06: Colour,
|
pub base_06: Colour,
|
||||||
|
/// Base07
|
||||||
pub base_07: Colour,
|
pub base_07: Colour,
|
||||||
|
/// Base08
|
||||||
pub base_08: Colour,
|
pub base_08: Colour,
|
||||||
|
/// Base09
|
||||||
pub base_09: Colour,
|
pub base_09: Colour,
|
||||||
|
/// Base0A
|
||||||
pub base_0a: Colour,
|
pub base_0a: Colour,
|
||||||
|
/// Base0B
|
||||||
pub base_0b: Colour,
|
pub base_0b: Colour,
|
||||||
|
/// Base0C
|
||||||
pub base_0c: Colour,
|
pub base_0c: Colour,
|
||||||
|
/// Base0D
|
||||||
pub base_0d: Colour,
|
pub base_0d: Colour,
|
||||||
|
/// Base0E
|
||||||
pub base_0e: Colour,
|
pub base_0e: Colour,
|
||||||
|
/// Base0F
|
||||||
pub base_0f: Colour,
|
pub base_0f: Colour,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,28 +217,48 @@ impl Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
||||||
|
/// Base16 value
|
||||||
pub enum Base16Value {
|
pub enum Base16Value {
|
||||||
|
/// Base00
|
||||||
Base00,
|
Base00,
|
||||||
|
/// Base01
|
||||||
Base01,
|
Base01,
|
||||||
|
/// Base02
|
||||||
Base02,
|
Base02,
|
||||||
|
/// Base03
|
||||||
Base03,
|
Base03,
|
||||||
|
/// Base04
|
||||||
Base04,
|
Base04,
|
||||||
|
/// Base05
|
||||||
Base05,
|
Base05,
|
||||||
|
/// Base06
|
||||||
#[default]
|
#[default]
|
||||||
Base06,
|
Base06,
|
||||||
|
/// Base07
|
||||||
Base07,
|
Base07,
|
||||||
|
/// Base08
|
||||||
Base08,
|
Base08,
|
||||||
|
/// Base09
|
||||||
Base09,
|
Base09,
|
||||||
|
/// Base0A
|
||||||
Base0A,
|
Base0A,
|
||||||
|
/// Base0B
|
||||||
Base0B,
|
Base0B,
|
||||||
|
/// Base0C
|
||||||
Base0C,
|
Base0C,
|
||||||
|
/// Base0D
|
||||||
Base0D,
|
Base0D,
|
||||||
|
/// Base0E
|
||||||
Base0E,
|
Base0E,
|
||||||
|
/// Base0F
|
||||||
Base0F,
|
Base0F,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper around a Base16 colour palette
|
||||||
pub enum Base16Wrapper {
|
pub enum Base16Wrapper {
|
||||||
|
/// Predefined Base16 colour palette
|
||||||
Base16(Base16),
|
Base16(Base16),
|
||||||
|
/// Custom Base16 colour palette
|
||||||
Custom(Box<Base16ColourPalette>),
|
Custom(Box<Base16ColourPalette>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,10 +306,15 @@ impl Base16Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
||||||
|
/// Catppuccin palette
|
||||||
pub enum Catppuccin {
|
pub enum Catppuccin {
|
||||||
|
/// Frappe (https://catppuccin.com/palette#flavor-frappe)
|
||||||
Frappe,
|
Frappe,
|
||||||
|
/// Latte (https://catppuccin.com/palette#flavor-latte)
|
||||||
Latte,
|
Latte,
|
||||||
|
/// Macchiato (https://catppuccin.com/palette#flavor-macchiato)
|
||||||
Macchiato,
|
Macchiato,
|
||||||
|
/// Mocha (https://catppuccin.com/palette#flavor-mocha)
|
||||||
Mocha,
|
Mocha,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,33 +336,60 @@ impl From<Catppuccin> for catppuccin_egui::Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
|
||||||
|
/// Catppuccin Value
|
||||||
pub enum CatppuccinValue {
|
pub enum CatppuccinValue {
|
||||||
|
/// Rosewater
|
||||||
Rosewater,
|
Rosewater,
|
||||||
|
/// Flamingo
|
||||||
Flamingo,
|
Flamingo,
|
||||||
|
/// Pink
|
||||||
Pink,
|
Pink,
|
||||||
|
/// Mauve
|
||||||
Mauve,
|
Mauve,
|
||||||
|
/// Red
|
||||||
Red,
|
Red,
|
||||||
|
/// Maroon
|
||||||
Maroon,
|
Maroon,
|
||||||
|
/// Peach
|
||||||
Peach,
|
Peach,
|
||||||
|
/// Yellow
|
||||||
Yellow,
|
Yellow,
|
||||||
|
/// Green
|
||||||
Green,
|
Green,
|
||||||
|
/// Teal
|
||||||
Teal,
|
Teal,
|
||||||
|
/// Sky
|
||||||
Sky,
|
Sky,
|
||||||
|
/// Sapphire
|
||||||
Sapphire,
|
Sapphire,
|
||||||
|
/// Blue
|
||||||
Blue,
|
Blue,
|
||||||
|
/// Lavender
|
||||||
Lavender,
|
Lavender,
|
||||||
#[default]
|
#[default]
|
||||||
|
/// Text
|
||||||
Text,
|
Text,
|
||||||
|
/// Subtext1
|
||||||
Subtext1,
|
Subtext1,
|
||||||
|
/// Subtext0
|
||||||
Subtext0,
|
Subtext0,
|
||||||
|
/// Overlay2
|
||||||
Overlay2,
|
Overlay2,
|
||||||
|
/// Overlay1
|
||||||
Overlay1,
|
Overlay1,
|
||||||
|
/// Overlay0
|
||||||
Overlay0,
|
Overlay0,
|
||||||
|
/// Surface2
|
||||||
Surface2,
|
Surface2,
|
||||||
|
/// Surface1
|
||||||
Surface1,
|
Surface1,
|
||||||
|
/// Surface0
|
||||||
Surface0,
|
Surface0,
|
||||||
|
/// Base
|
||||||
Base,
|
Base,
|
||||||
|
/// Mantle
|
||||||
Mantle,
|
Mantle,
|
||||||
|
/// Crust
|
||||||
Crust,
|
Crust,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,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,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+9
-7
@@ -1,37 +1,39 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi"
|
name = "komorebi"
|
||||||
version = "0.1.36"
|
version = "0.1.40"
|
||||||
description = "A tiling window manager for Windows"
|
description = "A tiling window manager for Windows"
|
||||||
repository = "https://github.com/LGUG2Z/komorebi"
|
repository = "https://github.com/LGUG2Z/komorebi"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
komorebi-themes = { path = "../komorebi-themes" }
|
komorebi-themes = { path = "../komorebi-themes" }
|
||||||
|
|
||||||
|
base64 = "0.22"
|
||||||
bitflags = { version = "2", features = ["serde"] }
|
bitflags = { version = "2", features = ["serde"] }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
color-eyre = { workspace = true }
|
color-eyre = { workspace = true }
|
||||||
crossbeam-channel = { workspace = true }
|
crossbeam-channel = { workspace = true }
|
||||||
crossbeam-utils = { workspace = true }
|
crossbeam-utils = { workspace = true }
|
||||||
ctrlc = { version = "3", features = ["termination"] }
|
ctrlc = { version = "3", features = ["termination"] }
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
dunce = { workspace = true }
|
ed25519-dalek = "2"
|
||||||
getset = "0.1"
|
|
||||||
hotwatch = { workspace = true }
|
hotwatch = { workspace = true }
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
miow = "0.6"
|
miow = "0.6"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
net2 = "0.2"
|
net2 = "0.2"
|
||||||
os_info = "3.10"
|
os_info = "3.10"
|
||||||
parking_lot = "0.12"
|
parking_lot = { workspace = true }
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
powershell_script = "1.0"
|
powershell_script = "1.0"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||||
serde_yaml = { workspace = true }
|
serde_yaml = { workspace = true }
|
||||||
shadow-rs = { workspace = true }
|
shadow-rs = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
@@ -49,7 +51,7 @@ windows-implement = { workspace = true }
|
|||||||
windows-interface = { workspace = true }
|
windows-interface = { workspace = true }
|
||||||
winput = "0.2"
|
winput = "0.2"
|
||||||
winreg = "0.55"
|
winreg = "0.55"
|
||||||
serde_with = { version = "3.12", features = ["schemars_0_8"] }
|
serde_with = { version = "3.12", features = ["schemars_1"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { workspace = true }
|
shadow-rs = { workspace = true }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
use super::prefix::AnimationPrefix;
|
use super::prefix::AnimationPrefix;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use color_eyre::Result;
|
use color_eyre::eyre;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -6,10 +6,10 @@ use std::sync::atomic::Ordering;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use super::RenderDispatcher;
|
|
||||||
use super::ANIMATION_DURATION_GLOBAL;
|
use super::ANIMATION_DURATION_GLOBAL;
|
||||||
use super::ANIMATION_FPS;
|
use super::ANIMATION_FPS;
|
||||||
use super::ANIMATION_MANAGER;
|
use super::ANIMATION_MANAGER;
|
||||||
|
use super::RenderDispatcher;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
@@ -55,9 +55,9 @@ impl AnimationEngine {
|
|||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
pub fn animate(
|
pub fn animate(
|
||||||
render_dispatcher: (impl RenderDispatcher + Send + 'static),
|
render_dispatcher: impl RenderDispatcher + Send + 'static,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
) -> Result<()> {
|
) -> eyre::Result<()> {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let animation_key = render_dispatcher.get_animation_key();
|
let animation_key = render_dispatcher.get_animation_key();
|
||||||
if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) {
|
if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::core::Rect;
|
|
||||||
use crate::AnimationStyle;
|
use crate::AnimationStyle;
|
||||||
|
use crate::core::Rect;
|
||||||
|
|
||||||
use super::style::apply_ease_func;
|
use super::style::apply_ease_func;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use crate::core::animation::AnimationStyle;
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prefix::AnimationPrefix;
|
use prefix::AnimationPrefix;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
@@ -25,8 +25,33 @@ use serde::Serialize;
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Animation configuration
|
||||||
|
///
|
||||||
|
/// This can be either global:
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "enabled": true,
|
||||||
|
/// "style": "EaseInSine",
|
||||||
|
/// "fps": 60,
|
||||||
|
/// "duration": 250
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or scoped by an animation kind prefix:
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "movement": {
|
||||||
|
/// "enabled": true,
|
||||||
|
/// "style": "EaseInSine",
|
||||||
|
/// "fps": 60,
|
||||||
|
/// "duration": 250
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub enum PerAnimationPrefixConfig<T> {
|
pub enum PerAnimationPrefixConfig<T> {
|
||||||
|
/// Animation configuration prefixed for a specific animation kind
|
||||||
Prefix(HashMap<AnimationPrefix, T>),
|
Prefix(HashMap<AnimationPrefix, T>),
|
||||||
|
/// Animation configuration for all animation kinds
|
||||||
Global(T),
|
Global(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ pub enum AnimationPrefix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {
|
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {
|
||||||
format!("{}:{}", prefix, key)
|
format!("{prefix}:{key}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use color_eyre::Result;
|
use color_eyre::eyre;
|
||||||
|
|
||||||
pub trait RenderDispatcher {
|
pub trait RenderDispatcher {
|
||||||
fn get_animation_key(&self) -> String;
|
fn get_animation_key(&self) -> String;
|
||||||
fn pre_render(&self) -> Result<()>;
|
fn pre_render(&self) -> eyre::Result<()>;
|
||||||
fn render(&self, delta: f64) -> Result<()>;
|
fn render(&self, delta: f64) -> eyre::Result<()>;
|
||||||
fn post_render(&self) -> Result<()>;
|
fn post_render(&self) -> eyre::Result<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,6 +355,61 @@ impl Ease for EaseInOutBounce {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CubicBezier {
|
||||||
|
pub x1: f64,
|
||||||
|
pub y1: f64,
|
||||||
|
pub x2: f64,
|
||||||
|
pub y2: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CubicBezier {
|
||||||
|
fn x(&self, s: f64) -> f64 {
|
||||||
|
3.0 * self.x1 * s * (1.0 - s).powi(2) + 3.0 * self.x2 * s.powi(2) * (1.0 - s) + s.powi(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y(&self, s: f64) -> f64 {
|
||||||
|
3.0 * self.y1 * s * (1.0 - s).powi(2) + 3.0 * self.y2 * s.powi(2) * (1.0 - s) + s.powi(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dx_ds(&self, s: f64) -> f64 {
|
||||||
|
3.0 * self.x1 * (1.0 - s) * (1.0 - 3.0 * s)
|
||||||
|
+ 3.0 * self.x2 * (2.0 * s - 3.0 * s.powi(2))
|
||||||
|
+ 3.0 * s.powi(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_s(&self, t: f64) -> f64 {
|
||||||
|
if t <= 0.0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if t >= 1.0 {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = t;
|
||||||
|
|
||||||
|
for _ in 0..8 {
|
||||||
|
let x_val = self.x(s);
|
||||||
|
let dx_val = self.dx_ds(s);
|
||||||
|
if dx_val.abs() < 1e-6 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let delta = (x_val - t) / dx_val;
|
||||||
|
s = (s - delta).clamp(0.0, 1.0);
|
||||||
|
if delta.abs() < 1e-6 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate(&self, t: f64) -> f64 {
|
||||||
|
let s = self.find_s(t.clamp(0.0, 1.0));
|
||||||
|
self.y(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
|
pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
|
||||||
match style {
|
match style {
|
||||||
AnimationStyle::Linear => Linear::evaluate(t),
|
AnimationStyle::Linear => Linear::evaluate(t),
|
||||||
@@ -365,6 +420,7 @@ pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
|
|||||||
AnimationStyle::EaseOutQuad => EaseOutQuad::evaluate(t),
|
AnimationStyle::EaseOutQuad => EaseOutQuad::evaluate(t),
|
||||||
AnimationStyle::EaseInOutQuad => EaseInOutQuad::evaluate(t),
|
AnimationStyle::EaseInOutQuad => EaseInOutQuad::evaluate(t),
|
||||||
AnimationStyle::EaseInCubic => EaseInCubic::evaluate(t),
|
AnimationStyle::EaseInCubic => EaseInCubic::evaluate(t),
|
||||||
|
AnimationStyle::EaseOutCubic => EaseOutCubic::evaluate(t),
|
||||||
AnimationStyle::EaseInOutCubic => EaseInOutCubic::evaluate(t),
|
AnimationStyle::EaseInOutCubic => EaseInOutCubic::evaluate(t),
|
||||||
AnimationStyle::EaseInQuart => EaseInQuart::evaluate(t),
|
AnimationStyle::EaseInQuart => EaseInQuart::evaluate(t),
|
||||||
AnimationStyle::EaseOutQuart => EaseOutQuart::evaluate(t),
|
AnimationStyle::EaseOutQuart => EaseOutQuart::evaluate(t),
|
||||||
@@ -387,5 +443,6 @@ pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
|
|||||||
AnimationStyle::EaseInBounce => EaseInBounce::evaluate(t),
|
AnimationStyle::EaseInBounce => EaseInBounce::evaluate(t),
|
||||||
AnimationStyle::EaseOutBounce => EaseOutBounce::evaluate(t),
|
AnimationStyle::EaseOutBounce => EaseOutBounce::evaluate(t),
|
||||||
AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t),
|
AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t),
|
||||||
|
AnimationStyle::CubicBezier(x1, y1, x2, y2) => CubicBezier { x1, y1, x2, y2 }.evaluate(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
use crate::border_manager::window_kind_colour;
|
use crate::WINDOWS_11;
|
||||||
use crate::border_manager::RenderTarget;
|
use crate::WindowsApi;
|
||||||
use crate::border_manager::WindowKind;
|
|
||||||
use crate::border_manager::BORDER_OFFSET;
|
use crate::border_manager::BORDER_OFFSET;
|
||||||
use crate::border_manager::BORDER_WIDTH;
|
use crate::border_manager::BORDER_WIDTH;
|
||||||
|
use crate::border_manager::RenderTarget;
|
||||||
use crate::border_manager::STYLE;
|
use crate::border_manager::STYLE;
|
||||||
|
use crate::border_manager::WindowKind;
|
||||||
|
use crate::border_manager::window_kind_colour;
|
||||||
use crate::core::BorderStyle;
|
use crate::core::BorderStyle;
|
||||||
use crate::core::Rect;
|
use crate::core::Rect;
|
||||||
use crate::windows_api;
|
use crate::windows_api;
|
||||||
use crate::WindowsApi;
|
|
||||||
use crate::WINDOWS_11;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::LazyLock;
|
|
||||||
use windows::Win32::Foundation::FALSE;
|
use windows::Win32::Foundation::FALSE;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::Foundation::LPARAM;
|
use windows::Win32::Foundation::LPARAM;
|
||||||
use windows::Win32::Foundation::LRESULT;
|
use windows::Win32::Foundation::LRESULT;
|
||||||
use windows::Win32::Foundation::TRUE;
|
use windows::Win32::Foundation::TRUE;
|
||||||
use windows::Win32::Foundation::WPARAM;
|
use windows::Win32::Foundation::WPARAM;
|
||||||
|
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
|
||||||
|
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
|
||||||
use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
|
use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||||
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
|
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
|
||||||
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
|
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
|
||||||
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
|
|
||||||
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
|
|
||||||
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
|
|
||||||
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
|
|
||||||
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
|
|
||||||
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
|
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
|
||||||
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
|
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
|
||||||
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
|
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
|
||||||
@@ -36,31 +33,34 @@ use windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;
|
|||||||
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
|
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
|
||||||
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
|
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
|
||||||
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
|
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
|
||||||
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
|
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
|
||||||
|
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
|
||||||
|
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
|
||||||
use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;
|
use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;
|
||||||
use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;
|
use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;
|
||||||
use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;
|
use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;
|
||||||
|
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
|
||||||
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
|
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
|
||||||
use windows::Win32::Graphics::Gdi::CreateRectRgn;
|
use windows::Win32::Graphics::Gdi::CreateRectRgn;
|
||||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
||||||
use windows::Win32::Graphics::Gdi::ValidateRect;
|
use windows::Win32::Graphics::Gdi::ValidateRect;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::LoadCursorW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SetCursor;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
|
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
|
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::LoadCursorW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
|
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SetCursor;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
|
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||||
@@ -102,10 +102,10 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|||||||
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
||||||
let hwnd = hwnd.0 as isize;
|
let hwnd = hwnd.0 as isize;
|
||||||
|
|
||||||
if let Ok(class) = WindowsApi::real_window_class_w(hwnd) {
|
if let Ok(class) = WindowsApi::real_window_class_w(hwnd)
|
||||||
if class.starts_with("komoborder") {
|
&& class.starts_with("komoborder")
|
||||||
hwnds.push(hwnd);
|
{
|
||||||
}
|
hwnds.push(hwnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
true.into()
|
true.into()
|
||||||
@@ -313,6 +313,11 @@ impl Border {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(&self) -> color_eyre::Result<()> {
|
pub fn destroy(&self) -> color_eyre::Result<()> {
|
||||||
|
// clear user data **BEFORE** closing window
|
||||||
|
// pending messages will see a null pointer and exit early
|
||||||
|
unsafe {
|
||||||
|
SetWindowLongPtrW(self.hwnd(), GWLP_USERDATA, 0);
|
||||||
|
}
|
||||||
WindowsApi::close_window(self.hwnd)
|
WindowsApi::close_window(self.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,63 +397,63 @@ impl Border {
|
|||||||
tracing::error!("failed to update border position {error}");
|
tracing::error!("failed to update border position {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rect.is_same_size_as(&old_rect) {
|
if (!rect.is_same_size_as(&old_rect) || !rect.has_same_position_as(&old_rect))
|
||||||
if let Some(render_target) = (*border_pointer).render_target.as_ref() {
|
&& let Some(render_target) = (*border_pointer).render_target.as_ref()
|
||||||
let border_width = (*border_pointer).width;
|
{
|
||||||
let border_offset = (*border_pointer).offset;
|
let border_width = (*border_pointer).width;
|
||||||
|
let border_offset = (*border_pointer).offset;
|
||||||
|
|
||||||
(*border_pointer).rounded_rect.rect = D2D_RECT_F {
|
(*border_pointer).rounded_rect.rect = D2D_RECT_F {
|
||||||
left: (border_width / 2 - border_offset) as f32,
|
left: (border_width / 2 - border_offset) as f32,
|
||||||
top: (border_width / 2 - border_offset) as f32,
|
top: (border_width / 2 - border_offset) as f32,
|
||||||
right: (rect.right - border_width / 2 + border_offset) as f32,
|
right: (rect.right - border_width / 2 + border_offset) as f32,
|
||||||
bottom: (rect.bottom - border_width / 2 + border_offset) as f32,
|
bottom: (rect.bottom - border_width / 2 + border_offset) as f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = render_target.Resize(&D2D_SIZE_U {
|
||||||
|
width: rect.right as u32,
|
||||||
|
height: rect.bottom as u32,
|
||||||
|
});
|
||||||
|
|
||||||
|
let window_kind = (*border_pointer).window_kind;
|
||||||
|
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
|
||||||
|
render_target.BeginDraw();
|
||||||
|
render_target.Clear(None);
|
||||||
|
|
||||||
|
// Calculate border radius based on style
|
||||||
|
let style = match (*border_pointer).style {
|
||||||
|
BorderStyle::System => {
|
||||||
|
if *WINDOWS_11 {
|
||||||
|
BorderStyle::Rounded
|
||||||
|
} else {
|
||||||
|
BorderStyle::Square
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BorderStyle::Rounded => BorderStyle::Rounded,
|
||||||
|
BorderStyle::Square => BorderStyle::Square,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = render_target.Resize(&D2D_SIZE_U {
|
match style {
|
||||||
width: rect.right as u32,
|
BorderStyle::Rounded => {
|
||||||
height: rect.bottom as u32,
|
render_target.DrawRoundedRectangle(
|
||||||
});
|
&(*border_pointer).rounded_rect,
|
||||||
|
brush,
|
||||||
let window_kind = (*border_pointer).window_kind;
|
border_width as f32,
|
||||||
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
|
None,
|
||||||
render_target.BeginDraw();
|
);
|
||||||
render_target.Clear(None);
|
|
||||||
|
|
||||||
// Calculate border radius based on style
|
|
||||||
let style = match (*border_pointer).style {
|
|
||||||
BorderStyle::System => {
|
|
||||||
if *WINDOWS_11 {
|
|
||||||
BorderStyle::Rounded
|
|
||||||
} else {
|
|
||||||
BorderStyle::Square
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BorderStyle::Rounded => BorderStyle::Rounded,
|
|
||||||
BorderStyle::Square => BorderStyle::Square,
|
|
||||||
};
|
|
||||||
|
|
||||||
match style {
|
|
||||||
BorderStyle::Rounded => {
|
|
||||||
render_target.DrawRoundedRectangle(
|
|
||||||
&(*border_pointer).rounded_rect,
|
|
||||||
brush,
|
|
||||||
border_width as f32,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
BorderStyle::Square => {
|
|
||||||
render_target.DrawRectangle(
|
|
||||||
&(*border_pointer).rounded_rect.rect,
|
|
||||||
brush,
|
|
||||||
border_width as f32,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
BorderStyle::Square => {
|
||||||
let _ = render_target.EndDraw(None, None);
|
render_target.DrawRectangle(
|
||||||
|
&(*border_pointer).rounded_rect.rect,
|
||||||
|
brush,
|
||||||
|
border_width as f32,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = render_target.EndDraw(None, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+177
-107
@@ -1,16 +1,17 @@
|
|||||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||||
|
|
||||||
mod border;
|
mod border;
|
||||||
|
use crate::WindowManager;
|
||||||
|
use crate::WindowsApi;
|
||||||
use crate::core::BorderImplementation;
|
use crate::core::BorderImplementation;
|
||||||
use crate::core::BorderStyle;
|
use crate::core::BorderStyle;
|
||||||
use crate::core::WindowKind;
|
use crate::core::WindowKind;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::windows_api;
|
use crate::windows_api;
|
||||||
|
use crate::workspace::Workspace;
|
||||||
use crate::workspace::WorkspaceLayer;
|
use crate::workspace::WorkspaceLayer;
|
||||||
use crate::WindowManager;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
use border::border_hwnds;
|
|
||||||
pub use border::Border;
|
pub use border::Border;
|
||||||
|
use border::border_hwnds;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossbeam_utils::atomic::AtomicCell;
|
use crossbeam_utils::atomic::AtomicCell;
|
||||||
@@ -21,15 +22,15 @@ use lazy_static::lazy_static;
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::OnceLock;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicI32;
|
use std::sync::atomic::AtomicI32;
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
|
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
|
||||||
@@ -169,13 +170,15 @@ fn window_kind_colour(focus_kind: WindowKind) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||||
std::thread::spawn(move || loop {
|
std::thread::spawn(move || {
|
||||||
match handle_notifications(wm.clone()) {
|
loop {
|
||||||
Ok(()) => {
|
match handle_notifications(wm.clone()) {
|
||||||
tracing::warn!("restarting finished thread");
|
Ok(()) => {
|
||||||
}
|
tracing::warn!("restarting finished thread");
|
||||||
Err(error) => {
|
}
|
||||||
tracing::warn!("restarting failed thread: {}", error);
|
Err(error) => {
|
||||||
|
tracing::warn!("restarting failed thread: {}", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -208,10 +211,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|w| w.hwnd)
|
.map(|w| w.hwnd)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let workspace_layer = *state.monitors.elements()[focused_monitor_idx].workspaces()
|
let workspace_layer = state.monitors.elements()[focused_monitor_idx].workspaces()
|
||||||
[focused_workspace_idx]
|
[focused_workspace_idx]
|
||||||
.layer();
|
.layer;
|
||||||
let foreground_window = WindowsApi::foreground_window().unwrap_or_default();
|
let foreground_window = WindowsApi::foreground_window().unwrap_or_default();
|
||||||
|
let layer_changed = previous_layer != workspace_layer;
|
||||||
|
let forced_update = matches!(notification, Notification::ForceUpdate);
|
||||||
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
@@ -221,7 +226,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
// Only operate on the focused workspace of each monitor
|
// Only operate on the focused workspace of each monitor
|
||||||
if let Some(ws) = m.focused_workspace() {
|
if let Some(ws) = m.focused_workspace() {
|
||||||
// Handle the monocle container separately
|
// Handle the monocle container separately
|
||||||
if let Some(monocle) = ws.monocle_container() {
|
if let Some(monocle) = &ws.monocle_container {
|
||||||
let window_kind = if monitor_idx != focused_monitor_idx {
|
let window_kind = if monitor_idx != focused_monitor_idx {
|
||||||
WindowKind::Unfocused
|
WindowKind::Unfocused
|
||||||
} else {
|
} else {
|
||||||
@@ -234,6 +239,17 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.set_accent(window_kind_colour(window_kind))?;
|
.set_accent(window_kind_colour(window_kind))?;
|
||||||
|
|
||||||
|
if ws.layer == WorkspaceLayer::Floating {
|
||||||
|
for window in ws.floating_windows() {
|
||||||
|
let mut window_kind = WindowKind::Unfocused;
|
||||||
|
|
||||||
|
if foreground_window == window.hwnd {
|
||||||
|
window_kind = WindowKind::Floating;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.set_accent(window_kind_colour(window_kind))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
continue 'monitors;
|
continue 'monitors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +257,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
let window_kind = if idx != ws.focused_container_idx()
|
let window_kind = if idx != ws.focused_container_idx()
|
||||||
|| monitor_idx != focused_monitor_idx
|
|| monitor_idx != focused_monitor_idx
|
||||||
{
|
{
|
||||||
if ws.locked_containers().contains(&idx) {
|
if c.locked {
|
||||||
WindowKind::UnfocusedLocked
|
WindowKind::UnfocusedLocked
|
||||||
} else {
|
} else {
|
||||||
WindowKind::Unfocused
|
WindowKind::Unfocused
|
||||||
@@ -325,15 +341,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
should_process_notification = true;
|
should_process_notification = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !should_process_notification {
|
if !should_process_notification
|
||||||
if let Some(Notification::Update(ref previous)) = previous_notification
|
&& let Some(Notification::Update(ref previous)) = previous_notification
|
||||||
{
|
&& previous.unwrap_or_default() != notification_hwnd.unwrap_or_default()
|
||||||
if previous.unwrap_or_default()
|
{
|
||||||
!= notification_hwnd.unwrap_or_default()
|
should_process_notification = true;
|
||||||
{
|
|
||||||
should_process_notification = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
should_process_notification
|
should_process_notification
|
||||||
@@ -342,7 +354,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !should_process_notification {
|
if !should_process_notification {
|
||||||
tracing::trace!("monitor state matches latest snapshot, skipping notification");
|
tracing::debug!("monitor state matches latest snapshot, skipping notification");
|
||||||
continue 'receiver;
|
continue 'receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +381,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
// Only operate on the focused workspace of each monitor
|
// Only operate on the focused workspace of each monitor
|
||||||
if let Some(ws) = m.focused_workspace() {
|
if let Some(ws) = m.focused_workspace() {
|
||||||
// Workspaces with tiling disabled don't have borders
|
// Workspaces with tiling disabled don't have borders
|
||||||
if !ws.tile() {
|
if !ws.tile {
|
||||||
// Remove all borders on this monitor
|
// Remove all borders on this monitor
|
||||||
remove_borders(
|
remove_borders(
|
||||||
&mut borders,
|
&mut borders,
|
||||||
@@ -382,16 +394,16 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle the monocle container separately
|
// Handle the monocle container separately
|
||||||
if let Some(monocle) = ws.monocle_container() {
|
if let Some(monocle) = &ws.monocle_container {
|
||||||
let mut new_border = false;
|
let mut new_border = false;
|
||||||
let focused_window_hwnd =
|
let focused_window_hwnd =
|
||||||
monocle.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
monocle.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
||||||
let id = monocle.id().clone();
|
let id = monocle.id.clone();
|
||||||
let border = match borders.entry(id.clone()) {
|
let border = match borders.entry(id.clone()) {
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
if let Ok(border) = Border::create(
|
if let Ok(border) = Border::create(
|
||||||
monocle.id(),
|
&monocle.id,
|
||||||
focused_window_hwnd,
|
focused_window_hwnd,
|
||||||
monitor_idx,
|
monitor_idx,
|
||||||
) {
|
) {
|
||||||
@@ -448,22 +460,48 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
windows_borders.insert(focused_window_hwnd, id);
|
windows_borders.insert(focused_window_hwnd, id);
|
||||||
|
|
||||||
let border_hwnd = border.hwnd;
|
let border_hwnd = border.hwnd;
|
||||||
// Remove all borders on this monitor except monocle
|
|
||||||
remove_borders(
|
|
||||||
&mut borders,
|
|
||||||
&mut windows_borders,
|
|
||||||
monitor_idx,
|
|
||||||
|_, b| border_hwnd != b.hwnd,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
|
if ws.layer == WorkspaceLayer::Floating {
|
||||||
|
handle_floating_borders(
|
||||||
|
&mut borders,
|
||||||
|
&mut windows_borders,
|
||||||
|
ws,
|
||||||
|
monitor_idx,
|
||||||
|
foreground_window,
|
||||||
|
layer_changed,
|
||||||
|
forced_update,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Remove all borders on this monitor except monocle and floating borders
|
||||||
|
remove_borders(
|
||||||
|
&mut borders,
|
||||||
|
&mut windows_borders,
|
||||||
|
monitor_idx,
|
||||||
|
|_, b| {
|
||||||
|
border_hwnd != b.hwnd
|
||||||
|
&& !ws
|
||||||
|
.floating_windows()
|
||||||
|
.iter()
|
||||||
|
.any(|w| w.hwnd == b.tracking_hwnd)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// Remove all borders on this monitor except monocle
|
||||||
|
remove_borders(
|
||||||
|
&mut borders,
|
||||||
|
&mut windows_borders,
|
||||||
|
monitor_idx,
|
||||||
|
|_, b| border_hwnd != b.hwnd,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
continue 'monitors;
|
continue 'monitors;
|
||||||
}
|
}
|
||||||
|
|
||||||
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();
|
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();
|
||||||
let foreground_monitor_id =
|
let foreground_monitor_id =
|
||||||
WindowsApi::monitor_from_window(foreground_hwnd);
|
WindowsApi::monitor_from_window(foreground_hwnd);
|
||||||
let is_maximized = foreground_monitor_id == m.id()
|
let is_maximized =
|
||||||
&& WindowsApi::is_zoomed(foreground_hwnd);
|
foreground_monitor_id == m.id && WindowsApi::is_zoomed(foreground_hwnd);
|
||||||
|
|
||||||
if is_maximized {
|
if is_maximized {
|
||||||
// Remove all borders on this monitor
|
// Remove all borders on this monitor
|
||||||
@@ -481,7 +519,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
let mut container_and_floating_window_ids = ws
|
let mut container_and_floating_window_ids = ws
|
||||||
.containers()
|
.containers()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.id().clone())
|
.map(|c| c.id.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for w in ws.floating_windows() {
|
for w in ws.floating_windows() {
|
||||||
@@ -499,7 +537,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
'containers: for (idx, c) in ws.containers().iter().enumerate() {
|
'containers: for (idx, c) in ws.containers().iter().enumerate() {
|
||||||
let focused_window_hwnd =
|
let focused_window_hwnd =
|
||||||
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
||||||
let id = c.id().clone();
|
let id = c.id.clone();
|
||||||
|
|
||||||
// Get the border entry for this container from the map or create one
|
// Get the border entry for this container from the map or create one
|
||||||
let mut new_border = false;
|
let mut new_border = false;
|
||||||
@@ -507,7 +545,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
if let Ok(border) =
|
if let Ok(border) =
|
||||||
Border::create(c.id(), focused_window_hwnd, monitor_idx)
|
Border::create(&c.id, focused_window_hwnd, monitor_idx)
|
||||||
{
|
{
|
||||||
new_border = true;
|
new_border = true;
|
||||||
entry.insert(border)
|
entry.insert(border)
|
||||||
@@ -523,7 +561,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
|| monitor_idx != focused_monitor_idx
|
|| monitor_idx != focused_monitor_idx
|
||||||
|| focused_window_hwnd != foreground_window
|
|| focused_window_hwnd != foreground_window
|
||||||
{
|
{
|
||||||
if ws.locked_containers().contains(&idx) {
|
if c.locked {
|
||||||
WindowKind::UnfocusedLocked
|
WindowKind::UnfocusedLocked
|
||||||
} else {
|
} else {
|
||||||
WindowKind::Unfocused
|
WindowKind::Unfocused
|
||||||
@@ -563,15 +601,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
|
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
|
||||||
Ok(rect) => rect,
|
Ok(rect) => rect,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
remove_border(c.id(), &mut borders, &mut windows_borders)?;
|
remove_border(&c.id, &mut borders, &mut windows_borders)?;
|
||||||
continue 'containers;
|
continue 'containers;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
border.window_rect = rect;
|
border.window_rect = rect;
|
||||||
|
|
||||||
let layer_changed = previous_layer != workspace_layer;
|
|
||||||
let forced_update = matches!(notification, Notification::ForceUpdate);
|
|
||||||
|
|
||||||
let should_invalidate = new_border
|
let should_invalidate = new_border
|
||||||
|| (last_focus_state != new_focus_state)
|
|| (last_focus_state != new_focus_state)
|
||||||
|| layer_changed
|
|| layer_changed
|
||||||
@@ -591,65 +626,15 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
windows_borders.insert(focused_window_hwnd, id);
|
windows_borders.insert(focused_window_hwnd, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
handle_floating_borders(
|
||||||
for window in ws.floating_windows() {
|
&mut borders,
|
||||||
let mut new_border = false;
|
&mut windows_borders,
|
||||||
let id = window.hwnd.to_string();
|
ws,
|
||||||
let border = match borders.entry(id.clone()) {
|
monitor_idx,
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
foreground_window,
|
||||||
Entry::Vacant(entry) => {
|
layer_changed,
|
||||||
if let Ok(border) = Border::create(
|
forced_update,
|
||||||
&window.hwnd.to_string(),
|
)?;
|
||||||
window.hwnd,
|
|
||||||
monitor_idx,
|
|
||||||
) {
|
|
||||||
new_border = true;
|
|
||||||
entry.insert(border)
|
|
||||||
} else {
|
|
||||||
continue 'monitors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let last_focus_state = border.window_kind;
|
|
||||||
|
|
||||||
let new_focus_state = if foreground_window == window.hwnd {
|
|
||||||
WindowKind::Floating
|
|
||||||
} else {
|
|
||||||
WindowKind::Unfocused
|
|
||||||
};
|
|
||||||
|
|
||||||
border.window_kind = new_focus_state;
|
|
||||||
|
|
||||||
// Update the border's monitor idx in case it changed
|
|
||||||
border.monitor_idx = Some(monitor_idx);
|
|
||||||
|
|
||||||
let rect = WindowsApi::window_rect(window.hwnd)?;
|
|
||||||
border.window_rect = rect;
|
|
||||||
|
|
||||||
let layer_changed = previous_layer != workspace_layer;
|
|
||||||
let forced_update =
|
|
||||||
matches!(notification, Notification::ForceUpdate);
|
|
||||||
|
|
||||||
let should_invalidate = new_border
|
|
||||||
|| (last_focus_state != new_focus_state)
|
|
||||||
|| layer_changed
|
|
||||||
|| forced_update;
|
|
||||||
|
|
||||||
if should_invalidate {
|
|
||||||
if forced_update && !new_border {
|
|
||||||
// Update the border brushes if there was a forced update
|
|
||||||
// notification and this is not a new border (new border's
|
|
||||||
// already have their brushes updated on creation)
|
|
||||||
border.update_brushes()?;
|
|
||||||
}
|
|
||||||
border.set_position(&rect, window.hwnd)?;
|
|
||||||
border.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
windows_borders.insert(window.hwnd, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -665,6 +650,68 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_floating_borders(
|
||||||
|
borders: &mut HashMap<String, Box<Border>>,
|
||||||
|
windows_borders: &mut HashMap<isize, String>,
|
||||||
|
ws: &Workspace,
|
||||||
|
monitor_idx: usize,
|
||||||
|
foreground_window: isize,
|
||||||
|
layer_changed: bool,
|
||||||
|
forced_update: bool,
|
||||||
|
) -> color_eyre::Result<()> {
|
||||||
|
for window in ws.floating_windows() {
|
||||||
|
let mut new_border = false;
|
||||||
|
let id = window.hwnd.to_string();
|
||||||
|
let border = match borders.entry(id.clone()) {
|
||||||
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
if let Ok(border) =
|
||||||
|
Border::create(&window.hwnd.to_string(), window.hwnd, monitor_idx)
|
||||||
|
{
|
||||||
|
new_border = true;
|
||||||
|
entry.insert(border)
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let last_focus_state = border.window_kind;
|
||||||
|
|
||||||
|
let new_focus_state = if foreground_window == window.hwnd {
|
||||||
|
WindowKind::Floating
|
||||||
|
} else {
|
||||||
|
WindowKind::Unfocused
|
||||||
|
};
|
||||||
|
|
||||||
|
border.window_kind = new_focus_state;
|
||||||
|
|
||||||
|
// Update the border's monitor idx in case it changed
|
||||||
|
border.monitor_idx = Some(monitor_idx);
|
||||||
|
|
||||||
|
let rect = WindowsApi::window_rect(window.hwnd)?;
|
||||||
|
border.window_rect = rect;
|
||||||
|
|
||||||
|
let should_invalidate =
|
||||||
|
new_border || (last_focus_state != new_focus_state) || layer_changed || forced_update;
|
||||||
|
|
||||||
|
if should_invalidate {
|
||||||
|
if forced_update && !new_border {
|
||||||
|
// Update the border brushes if there was a forced update
|
||||||
|
// notification and this is not a new border (new border's
|
||||||
|
// already have their brushes updated on creation)
|
||||||
|
border.update_brushes()?;
|
||||||
|
}
|
||||||
|
border.set_position(&rect, window.hwnd)?;
|
||||||
|
border.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
windows_borders.insert(window.hwnd, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes all borders from monitor with index `monitor_idx` filtered by
|
/// Removes all borders from monitor with index `monitor_idx` filtered by
|
||||||
/// `condition`. This condition is a function that will take a reference to
|
/// `condition`. This condition is a function that will take a reference to
|
||||||
/// the container id and the border and returns a bool, if true that border
|
/// the container id and the border and returns a bool, if true that border
|
||||||
@@ -720,6 +767,13 @@ fn remove_border(
|
|||||||
fn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {
|
fn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {
|
||||||
let raw_pointer = Box::into_raw(border);
|
let raw_pointer = Box::into_raw(border);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// release d2d resources **BEFORE** destroying window
|
||||||
|
// this drops render_target and brushes while HWND is still valid
|
||||||
|
// prevents EndDraw() from accessing freed HWND resources
|
||||||
|
(*raw_pointer).render_target = None;
|
||||||
|
(*raw_pointer).brushes.clear();
|
||||||
|
|
||||||
|
// Now safe to destroy window
|
||||||
(*raw_pointer).destroy()?;
|
(*raw_pointer).destroy()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -766,10 +820,26 @@ pub fn hide_border(tracking_hwnd: isize) {
|
|||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Z Order (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos)
|
||||||
pub enum ZOrder {
|
pub enum ZOrder {
|
||||||
|
/// HWND_TOP
|
||||||
|
///
|
||||||
|
/// Places the window at the top of the Z order.
|
||||||
Top,
|
Top,
|
||||||
|
/// HWND_NOTOPMOST
|
||||||
|
///
|
||||||
|
/// Places the window above all non-topmost windows (that is, behind all topmost windows).
|
||||||
|
/// This flag has no effect if the window is already a non-topmost window.
|
||||||
NoTopMost,
|
NoTopMost,
|
||||||
|
/// HWND_BOTTOM
|
||||||
|
///
|
||||||
|
/// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window,
|
||||||
|
/// the window loses its topmost status and is placed at the bottom of all other windows.
|
||||||
Bottom,
|
Bottom,
|
||||||
|
/// HWND_TOPMOST
|
||||||
|
///
|
||||||
|
/// Places the window above all non-topmost windows.
|
||||||
|
/// The window maintains its topmost position even when it is deactivated.
|
||||||
TopMost,
|
TopMost,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
|
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use windows::core::IUnknown;
|
|
||||||
use windows::core::IUnknown_Vtbl;
|
|
||||||
use windows::core::GUID;
|
|
||||||
use windows::core::HRESULT;
|
|
||||||
use windows::core::HSTRING;
|
|
||||||
use windows::core::PCWSTR;
|
|
||||||
use windows::core::PWSTR;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::Foundation::RECT;
|
use windows::Win32::Foundation::RECT;
|
||||||
use windows::Win32::Foundation::SIZE;
|
use windows::Win32::Foundation::SIZE;
|
||||||
use windows::Win32::UI::Shell::Common::IObjectArray;
|
use windows::Win32::UI::Shell::Common::IObjectArray;
|
||||||
|
use windows::core::GUID;
|
||||||
|
use windows::core::HRESULT;
|
||||||
|
use windows::core::HSTRING;
|
||||||
|
use windows::core::IUnknown;
|
||||||
|
use windows::core::IUnknown_Vtbl;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::core::PWSTR;
|
||||||
use windows_core::BOOL;
|
use windows_core::BOOL;
|
||||||
|
|
||||||
type DesktopID = GUID;
|
type DesktopID = GUID;
|
||||||
@@ -129,7 +129,7 @@ pub unsafe trait IApplicationView: IUnknown {
|
|||||||
pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17
|
pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17
|
||||||
pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;
|
pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;
|
||||||
pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)
|
pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)
|
||||||
-> HRESULT;
|
-> HRESULT;
|
||||||
|
|
||||||
/*** IApplicationView methods ***/
|
/*** IApplicationView methods ***/
|
||||||
pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20
|
pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ use interfaces::IServiceProvider;
|
|||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||||
|
use windows::Win32::System::Com::COINIT_MULTITHREADED;
|
||||||
use windows::Win32::System::Com::CoCreateInstance;
|
use windows::Win32::System::Com::CoCreateInstance;
|
||||||
use windows::Win32::System::Com::CoInitializeEx;
|
use windows::Win32::System::Com::CoInitializeEx;
|
||||||
use windows::Win32::System::Com::CoUninitialize;
|
use windows::Win32::System::Com::CoUninitialize;
|
||||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
|
||||||
use windows::Win32::System::Com::COINIT_MULTITHREADED;
|
|
||||||
use windows_core::Interface;
|
use windows_core::Interface;
|
||||||
|
|
||||||
struct ComInit();
|
struct ComInit();
|
||||||
@@ -64,7 +64,7 @@ fn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplication
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {
|
pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {
|
||||||
COM_INIT.with(|_| {
|
COM_INIT.with(|_| {
|
||||||
let provider = get_iservice_provider();
|
let provider = get_iservice_provider();
|
||||||
|
|||||||
+79
-18
@@ -1,18 +1,19 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use getset::Getters;
|
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::Lockable;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
#[getset(get = "pub")]
|
pub id: String,
|
||||||
id: String,
|
#[serde(default)]
|
||||||
|
pub locked: bool,
|
||||||
windows: Ring<Window>,
|
windows: Ring<Window>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,22 +23,45 @@ impl Default for Container {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: nanoid!(),
|
id: nanoid!(),
|
||||||
|
locked: false,
|
||||||
windows: Ring::default(),
|
windows: Ring::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Lockable for Container {
|
||||||
|
fn locked(&self) -> bool {
|
||||||
|
self.locked
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_locked(&mut self, locked: bool) -> &mut Self {
|
||||||
|
self.locked = locked;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
|
pub fn preselect() -> Self {
|
||||||
|
Self {
|
||||||
|
id: "PRESELECT".to_string(),
|
||||||
|
locked: false,
|
||||||
|
windows: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_preselect(&self) -> bool {
|
||||||
|
self.id == "PRESELECT"
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hide(&self, omit: Option<isize>) {
|
pub fn hide(&self, omit: Option<isize>) {
|
||||||
for window in self.windows().iter().rev() {
|
for window in self.windows().iter().rev() {
|
||||||
let mut should_hide = omit.is_none();
|
let mut should_hide = omit.is_none();
|
||||||
|
|
||||||
if !should_hide {
|
if !should_hide
|
||||||
if let Some(omit) = omit {
|
&& let Some(omit) = omit
|
||||||
if omit != window.hwnd {
|
&& omit != window.hwnd
|
||||||
should_hide = true
|
{
|
||||||
}
|
should_hide = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_hide {
|
if should_hide {
|
||||||
@@ -69,10 +93,10 @@ impl Container {
|
|||||||
|
|
||||||
pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {
|
pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {
|
||||||
for window in self.windows() {
|
for window in self.windows() {
|
||||||
if let Ok(window_exe) = window.exe() {
|
if let Ok(window_exe) = window.exe()
|
||||||
if exe == window_exe {
|
&& exe == window_exe
|
||||||
return Option::from(window.hwnd);
|
{
|
||||||
}
|
return Option::from(window.hwnd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,10 +105,10 @@ impl Container {
|
|||||||
|
|
||||||
pub fn idx_from_exe(&self, exe: &str) -> Option<usize> {
|
pub fn idx_from_exe(&self, exe: &str) -> Option<usize> {
|
||||||
for (idx, window) in self.windows().iter().enumerate() {
|
for (idx, window) in self.windows().iter().enumerate() {
|
||||||
if let Ok(window_exe) = window.exe() {
|
if let Ok(window_exe) = window.exe()
|
||||||
if exe == window_exe {
|
&& exe == window_exe
|
||||||
return Option::from(idx);
|
{
|
||||||
}
|
return Option::from(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +168,7 @@ impl Container {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contains_window() {
|
fn test_contains_window() {
|
||||||
@@ -250,4 +275,40 @@ mod tests {
|
|||||||
// Should return None since window 4 doesn't exist
|
// Should return None since window 4 doesn't exist
|
||||||
assert_eq!(container.idx_for_window(4), None);
|
assert_eq!(container.idx_for_window(4), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserializes_with_missing_locked_field_defaults_to_false() {
|
||||||
|
let json = r#"{
|
||||||
|
"id": "test-1",
|
||||||
|
"windows": { "elements": [], "focused": 0 }
|
||||||
|
}"#;
|
||||||
|
let container: Container = serde_json::from_str(json).expect("Should deserialize");
|
||||||
|
|
||||||
|
assert!(!container.locked);
|
||||||
|
assert_eq!(container.id, "test-1");
|
||||||
|
assert!(container.windows().is_empty());
|
||||||
|
|
||||||
|
let json = r#"{
|
||||||
|
"id": "test-2",
|
||||||
|
"windows": { "elements": [ { "hwnd": 5 }, { "hwnd": 9 } ], "focused": 1 }
|
||||||
|
}"#;
|
||||||
|
let container: Container = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(container.id, "test-2");
|
||||||
|
assert!(!container.locked);
|
||||||
|
assert_eq!(container.windows(), &[Window::from(5), Window::from(9)]);
|
||||||
|
assert_eq!(container.focused_window_idx(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serializes_and_deserializes() {
|
||||||
|
let mut container = Container::default();
|
||||||
|
container.set_locked(true);
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&container).expect("Should serialize");
|
||||||
|
let deserialized: Container =
|
||||||
|
serde_json::from_str(&serialized).expect("Should deserialize");
|
||||||
|
|
||||||
|
assert!(deserialized.locked);
|
||||||
|
assert_eq!(deserialized.id, container.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,40 +2,153 @@ use clap::ValueEnum;
|
|||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde::ser::SerializeSeq;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
#[derive(Copy, Clone, Debug, Display, EnumString, ValueEnum, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Mathematical function which describes the rate at which a value changes
|
||||||
pub enum AnimationStyle {
|
pub enum AnimationStyle {
|
||||||
|
/// Linear
|
||||||
Linear,
|
Linear,
|
||||||
|
/// Ease in sine
|
||||||
EaseInSine,
|
EaseInSine,
|
||||||
|
/// Ease out sine
|
||||||
EaseOutSine,
|
EaseOutSine,
|
||||||
|
/// Ease in out sine
|
||||||
EaseInOutSine,
|
EaseInOutSine,
|
||||||
|
/// Ease in quad
|
||||||
EaseInQuad,
|
EaseInQuad,
|
||||||
|
/// Ease out quad
|
||||||
EaseOutQuad,
|
EaseOutQuad,
|
||||||
|
/// Ease in out quad
|
||||||
EaseInOutQuad,
|
EaseInOutQuad,
|
||||||
|
/// Ease in cubic
|
||||||
EaseInCubic,
|
EaseInCubic,
|
||||||
|
/// Ease out cubic
|
||||||
|
EaseOutCubic,
|
||||||
|
/// Ease in out cubic
|
||||||
EaseInOutCubic,
|
EaseInOutCubic,
|
||||||
|
/// Ease in quart
|
||||||
EaseInQuart,
|
EaseInQuart,
|
||||||
|
/// Ease out quart
|
||||||
EaseOutQuart,
|
EaseOutQuart,
|
||||||
|
/// Ease in out quart
|
||||||
EaseInOutQuart,
|
EaseInOutQuart,
|
||||||
|
/// Ease in quint
|
||||||
EaseInQuint,
|
EaseInQuint,
|
||||||
|
/// Ease out quint
|
||||||
EaseOutQuint,
|
EaseOutQuint,
|
||||||
|
/// Ease in out quint
|
||||||
EaseInOutQuint,
|
EaseInOutQuint,
|
||||||
|
/// Ease in expo
|
||||||
EaseInExpo,
|
EaseInExpo,
|
||||||
|
/// Ease out expo
|
||||||
EaseOutExpo,
|
EaseOutExpo,
|
||||||
|
/// Ease in out expo
|
||||||
EaseInOutExpo,
|
EaseInOutExpo,
|
||||||
|
/// Ease in circ
|
||||||
EaseInCirc,
|
EaseInCirc,
|
||||||
|
/// Ease out circ
|
||||||
EaseOutCirc,
|
EaseOutCirc,
|
||||||
|
/// Ease in out circ
|
||||||
EaseInOutCirc,
|
EaseInOutCirc,
|
||||||
|
/// Ease in back
|
||||||
EaseInBack,
|
EaseInBack,
|
||||||
|
/// Ease out back
|
||||||
EaseOutBack,
|
EaseOutBack,
|
||||||
|
/// Ease in out back
|
||||||
EaseInOutBack,
|
EaseInOutBack,
|
||||||
|
/// Ease in elastic
|
||||||
EaseInElastic,
|
EaseInElastic,
|
||||||
|
/// Ease out elastic
|
||||||
EaseOutElastic,
|
EaseOutElastic,
|
||||||
|
/// Ease in out elastic
|
||||||
EaseInOutElastic,
|
EaseInOutElastic,
|
||||||
|
/// Ease in bounce
|
||||||
EaseInBounce,
|
EaseInBounce,
|
||||||
|
/// Ease out bounce
|
||||||
EaseOutBounce,
|
EaseOutBounce,
|
||||||
|
/// Ease in out bounce
|
||||||
EaseInOutBounce,
|
EaseInOutBounce,
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(title = "CubicBezier"))]
|
||||||
|
#[value(skip)]
|
||||||
|
/// Custom Cubic Bézier function
|
||||||
|
CubicBezier(f64, f64, f64, f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom serde implementation
|
||||||
|
impl<'de> Deserialize<'de> for AnimationStyle {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct AnimationStyleVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for AnimationStyleVisitor {
|
||||||
|
type Value = AnimationStyle;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a string or an array of four f64 values")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle string variants (e.g., "EaseInOutExpo")
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
value.parse().map_err(|_| E::unknown_variant(value, &[]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle CubicBezier array (e.g., [0.32, 0.72, 0.0, 1.0])
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: serde::de::SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let x1 = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
|
||||||
|
let y1 = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
|
||||||
|
let x2 = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
|
||||||
|
let y2 = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| serde::de::Error::invalid_length(3, &self))?;
|
||||||
|
|
||||||
|
// Ensure no extra elements
|
||||||
|
if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {
|
||||||
|
return Err(serde::de::Error::invalid_length(5, &self));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AnimationStyle::CubicBezier(x1, y1, x2, y2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(AnimationStyleVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for AnimationStyle {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
// Serialize CubicBezier as an array
|
||||||
|
AnimationStyle::CubicBezier(x1, y1, x2, y2) => {
|
||||||
|
let mut seq = serializer.serialize_seq(Some(4))?;
|
||||||
|
seq.serialize_element(x1)?;
|
||||||
|
seq.serialize_element(y1)?;
|
||||||
|
seq.serialize_element(x2)?;
|
||||||
|
seq.serialize_element(y2)?;
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
// Serialize all other variants as strings
|
||||||
|
_ => serializer.serialize_str(&self.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+242
-107
@@ -6,14 +6,16 @@ use serde::Serialize;
|
|||||||
use strum::Display;
|
use strum::Display;
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
|
|
||||||
use super::custom_layout::Column;
|
|
||||||
use super::custom_layout::ColumnSplit;
|
|
||||||
use super::custom_layout::ColumnSplitWithCapacity;
|
|
||||||
use super::CustomLayout;
|
use super::CustomLayout;
|
||||||
use super::DefaultLayout;
|
use super::DefaultLayout;
|
||||||
use super::Rect;
|
use super::Rect;
|
||||||
|
use super::custom_layout::Column;
|
||||||
|
use super::custom_layout::ColumnSplit;
|
||||||
|
use super::custom_layout::ColumnSplitWithCapacity;
|
||||||
|
use crate::default_layout::LayoutOptions;
|
||||||
|
|
||||||
pub trait Arrangement {
|
pub trait Arrangement {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn calculate(
|
fn calculate(
|
||||||
&self,
|
&self,
|
||||||
area: &Rect,
|
area: &Rect,
|
||||||
@@ -21,6 +23,9 @@ pub trait Arrangement {
|
|||||||
container_padding: Option<i32>,
|
container_padding: Option<i32>,
|
||||||
layout_flip: Option<Axis>,
|
layout_flip: Option<Axis>,
|
||||||
resize_dimensions: &[Option<Rect>],
|
resize_dimensions: &[Option<Rect>],
|
||||||
|
focused_idx: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
|
latest_layout: &[Rect],
|
||||||
) -> Vec<Rect>;
|
) -> Vec<Rect>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +38,99 @@ impl Arrangement for DefaultLayout {
|
|||||||
container_padding: Option<i32>,
|
container_padding: Option<i32>,
|
||||||
layout_flip: Option<Axis>,
|
layout_flip: Option<Axis>,
|
||||||
resize_dimensions: &[Option<Rect>],
|
resize_dimensions: &[Option<Rect>],
|
||||||
|
focused_idx: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
|
latest_layout: &[Rect],
|
||||||
) -> Vec<Rect> {
|
) -> Vec<Rect> {
|
||||||
let len = usize::from(len);
|
let len = usize::from(len);
|
||||||
let mut dimensions = match self {
|
let mut dimensions = match self {
|
||||||
|
Self::Scrolling => {
|
||||||
|
let column_count = layout_options
|
||||||
|
.and_then(|o| o.scrolling.map(|s| s.columns))
|
||||||
|
.unwrap_or(3);
|
||||||
|
|
||||||
|
let column_width = area.right / column_count.min(len) as i32;
|
||||||
|
let mut layouts = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
let visible_columns = area.right / column_width;
|
||||||
|
let keep_centered = layout_options
|
||||||
|
.and_then(|o| {
|
||||||
|
o.scrolling
|
||||||
|
.map(|s| s.center_focused_column.unwrap_or_default())
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let first_visible: isize = if focused_idx == 0 {
|
||||||
|
// if focused idx is 0, we are at the beginning of the scrolling strip
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let previous_first_visible = if latest_layout.is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
// previous first_visible based on the left position of the first visible window
|
||||||
|
let left_edge = area.left;
|
||||||
|
latest_layout
|
||||||
|
.iter()
|
||||||
|
.position(|rect| rect.left >= left_edge)
|
||||||
|
.unwrap_or(0) as isize
|
||||||
|
};
|
||||||
|
|
||||||
|
let focused_idx = focused_idx as isize;
|
||||||
|
|
||||||
|
// if center_focused_column is enabled, and we have an odd number of visible columns,
|
||||||
|
// center the focused window column
|
||||||
|
if keep_centered && visible_columns % 2 == 1 {
|
||||||
|
let center_offset = visible_columns as isize / 2;
|
||||||
|
(focused_idx - center_offset).max(0).min(
|
||||||
|
(len as isize)
|
||||||
|
.saturating_sub(visible_columns as isize)
|
||||||
|
.max(0),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if focused_idx < previous_first_visible {
|
||||||
|
// focused window is off the left edge, we need to scroll left
|
||||||
|
focused_idx
|
||||||
|
} else if focused_idx >= previous_first_visible + visible_columns as isize {
|
||||||
|
// focused window is off the right edge, we need to scroll right
|
||||||
|
// and make sure it's the last visible window
|
||||||
|
(focused_idx + 1 - visible_columns as isize).max(0)
|
||||||
|
} else {
|
||||||
|
// focused window is already visible, we don't need to scroll
|
||||||
|
previous_first_visible
|
||||||
|
}
|
||||||
|
.min(
|
||||||
|
(len as isize)
|
||||||
|
.saturating_sub(visible_columns as isize)
|
||||||
|
.max(0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..len {
|
||||||
|
let position = (i as isize) - first_visible;
|
||||||
|
let left = area.left + (position as i32 * column_width);
|
||||||
|
|
||||||
|
layouts.push(Rect {
|
||||||
|
left,
|
||||||
|
top: area.top,
|
||||||
|
right: column_width,
|
||||||
|
bottom: area.bottom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustment = calculate_scrolling_adjustment(resize_dimensions);
|
||||||
|
layouts
|
||||||
|
.iter_mut()
|
||||||
|
.zip(adjustment.iter())
|
||||||
|
.for_each(|(layout, adjustment)| {
|
||||||
|
layout.top += adjustment.top;
|
||||||
|
layout.bottom += adjustment.bottom;
|
||||||
|
layout.left += adjustment.left;
|
||||||
|
layout.right += adjustment.right;
|
||||||
|
});
|
||||||
|
|
||||||
|
layouts
|
||||||
|
}
|
||||||
Self::BSP => recursive_fibonacci(
|
Self::BSP => recursive_fibonacci(
|
||||||
0,
|
0,
|
||||||
len,
|
len,
|
||||||
@@ -60,10 +155,9 @@ impl Arrangement for DefaultLayout {
|
|||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 2.. = len
|
||||||
if let 2.. = len {
|
{
|
||||||
columns_reverse(&mut layouts);
|
columns_reverse(&mut layouts);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
@@ -85,10 +179,9 @@ impl Arrangement for DefaultLayout {
|
|||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 2.. = len
|
||||||
if let 2.. = len {
|
{
|
||||||
rows_reverse(&mut layouts);
|
rows_reverse(&mut layouts);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
@@ -139,25 +232,23 @@ impl Arrangement for DefaultLayout {
|
|||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 2.. = len
|
||||||
if let 2.. = len {
|
{
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
let (primary, rest) = layouts.split_at_mut(1);
|
||||||
let primary = &mut primary[0];
|
let primary = &mut primary[0];
|
||||||
|
|
||||||
for rect in rest.iter_mut() {
|
for rect in rest.iter_mut() {
|
||||||
rect.left = primary.left;
|
rect.left = primary.left;
|
||||||
}
|
|
||||||
primary.left = rest[0].left + rest[0].right;
|
|
||||||
}
|
}
|
||||||
|
primary.left = rest[0].left + rest[0].right;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 3.. = len
|
||||||
if let 3.. = len {
|
{
|
||||||
rows_reverse(&mut layouts[1..]);
|
rows_reverse(&mut layouts[1..]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
@@ -211,25 +302,23 @@ impl Arrangement for DefaultLayout {
|
|||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 2.. = len
|
||||||
if let 2.. = len {
|
{
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
let (primary, rest) = layouts.split_at_mut(1);
|
||||||
let primary = &mut primary[0];
|
let primary = &mut primary[0];
|
||||||
|
|
||||||
primary.left = rest[0].left;
|
primary.left = rest[0].left;
|
||||||
for rect in rest.iter_mut() {
|
for rect in rest.iter_mut() {
|
||||||
rect.left = primary.left + primary.right;
|
rect.left = primary.left + primary.right;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 3.. = len
|
||||||
if let 3.. = len {
|
{
|
||||||
rows_reverse(&mut layouts[1..]);
|
rows_reverse(&mut layouts[1..]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
@@ -280,25 +369,23 @@ impl Arrangement for DefaultLayout {
|
|||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 2.. = len
|
||||||
if let 2.. = len {
|
{
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
let (primary, rest) = layouts.split_at_mut(1);
|
||||||
let primary = &mut primary[0];
|
let primary = &mut primary[0];
|
||||||
|
|
||||||
for rect in rest.iter_mut() {
|
for rect in rest.iter_mut() {
|
||||||
rect.top = primary.top;
|
rect.top = primary.top;
|
||||||
}
|
|
||||||
primary.top = rest[0].top + rest[0].bottom;
|
|
||||||
}
|
}
|
||||||
|
primary.top = rest[0].top + rest[0].bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 3.. = len
|
||||||
if let 3.. = len {
|
{
|
||||||
columns_reverse(&mut layouts[1..]);
|
columns_reverse(&mut layouts[1..]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
@@ -407,10 +494,9 @@ impl Arrangement for DefaultLayout {
|
|||||||
if matches!(
|
if matches!(
|
||||||
layout_flip,
|
layout_flip,
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||||
) {
|
) && let 4.. = len
|
||||||
if let 4.. = len {
|
{
|
||||||
rows_reverse(&mut layouts[2..]);
|
rows_reverse(&mut layouts[2..]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
@@ -428,14 +514,25 @@ impl Arrangement for DefaultLayout {
|
|||||||
|
|
||||||
let len = len as i32;
|
let len = len as i32;
|
||||||
|
|
||||||
let num_cols = (len as f32).sqrt().ceil() as i32;
|
let row_constraint = layout_options.and_then(|o| o.grid.map(|g| g.rows));
|
||||||
|
let num_cols = if let Some(rows) = row_constraint {
|
||||||
|
((len as f32) / (rows as f32)).ceil() as i32
|
||||||
|
} else {
|
||||||
|
(len as f32).sqrt().ceil() as i32
|
||||||
|
};
|
||||||
|
|
||||||
let mut iter = layouts.iter_mut().enumerate().peekable();
|
let mut iter = layouts.iter_mut().enumerate().peekable();
|
||||||
|
|
||||||
for col in 0..num_cols {
|
for col in 0..num_cols {
|
||||||
let iter_peek = iter.peek().map(|x| x.0).unwrap_or_default() as i32;
|
let iter_peek = iter.peek().map(|x| x.0).unwrap_or_default() as i32;
|
||||||
let remaining_windows = len - iter_peek;
|
let remaining_windows = len - iter_peek;
|
||||||
let remaining_columns = num_cols - col;
|
let remaining_columns = num_cols - col;
|
||||||
let num_rows_in_this_col = remaining_windows / remaining_columns;
|
|
||||||
|
let num_rows_in_this_col = if let Some(rows) = row_constraint {
|
||||||
|
(remaining_windows / remaining_columns).min(rows as i32)
|
||||||
|
} else {
|
||||||
|
remaining_windows / remaining_columns
|
||||||
|
};
|
||||||
|
|
||||||
let win_height = area.bottom / num_rows_in_this_col;
|
let win_height = area.bottom / num_rows_in_this_col;
|
||||||
let win_width = area.right / num_cols;
|
let win_width = area.right / num_cols;
|
||||||
@@ -487,6 +584,9 @@ impl Arrangement for CustomLayout {
|
|||||||
container_padding: Option<i32>,
|
container_padding: Option<i32>,
|
||||||
_layout_flip: Option<Axis>,
|
_layout_flip: Option<Axis>,
|
||||||
_resize_dimensions: &[Option<Rect>],
|
_resize_dimensions: &[Option<Rect>],
|
||||||
|
_focused_idx: usize,
|
||||||
|
_layout_options: Option<LayoutOptions>,
|
||||||
|
_latest_layout: &[Rect],
|
||||||
) -> Vec<Rect> {
|
) -> Vec<Rect> {
|
||||||
let mut dimensions = vec![];
|
let mut dimensions = vec![];
|
||||||
let container_count = len.get();
|
let container_count = len.get();
|
||||||
@@ -541,7 +641,7 @@ impl Arrangement for CustomLayout {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match column {
|
match column {
|
||||||
Column::Primary(Option::Some(_)) => {
|
Column::Primary(Some(_)) => {
|
||||||
let main_column_area = if idx == 0 {
|
let main_column_area = if idx == 0 {
|
||||||
Self::main_column_area(area, primary_right, None)
|
Self::main_column_area(area, primary_right, None)
|
||||||
} else {
|
} else {
|
||||||
@@ -604,9 +704,13 @@ impl Arrangement for CustomLayout {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Axis on which to perform an operation
|
||||||
pub enum Axis {
|
pub enum Axis {
|
||||||
|
/// Horizontal axis
|
||||||
Horizontal,
|
Horizontal,
|
||||||
|
/// Vertical axis
|
||||||
Vertical,
|
Vertical,
|
||||||
|
/// Both horizontal and vertical axes
|
||||||
HorizontalAndVertical,
|
HorizontalAndVertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,67 +777,67 @@ fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Optio
|
|||||||
|
|
||||||
// This needs to be aware of layout flips
|
// This needs to be aware of layout flips
|
||||||
for (i, opt) in resize_dimensions.iter().enumerate() {
|
for (i, opt) in resize_dimensions.iter().enumerate() {
|
||||||
if let Some(resize_ref) = opt {
|
if let Some(resize_ref) = opt
|
||||||
if i > 0 {
|
&& i > 0
|
||||||
if resize_ref.left != 0 {
|
{
|
||||||
#[allow(clippy::if_not_else)]
|
if resize_ref.left != 0 {
|
||||||
let range = if i == 1 {
|
#[allow(clippy::if_not_else)]
|
||||||
0..1
|
let range = if i == 1 {
|
||||||
} else if i & 1 != 0 {
|
0..1
|
||||||
i - 1..i
|
} else if i & 1 != 0 {
|
||||||
} else {
|
i - 1..i
|
||||||
i - 2..i
|
} else {
|
||||||
};
|
i - 2..i
|
||||||
|
};
|
||||||
|
|
||||||
for n in range {
|
for n in range {
|
||||||
let should_adjust = n % 2 == 0;
|
let should_adjust = n % 2 == 0;
|
||||||
if should_adjust {
|
if should_adjust {
|
||||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||||
adjacent_resize.right += resize_ref.left;
|
adjacent_resize.right += resize_ref.left;
|
||||||
} else {
|
} else {
|
||||||
resize_adjustments[n] = Option::from(Rect {
|
resize_adjustments[n] = Option::from(Rect {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
right: resize_ref.left,
|
right: resize_ref.left,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rr) = resize_adjustments[i].as_mut() {
|
|
||||||
rr.left = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resize_ref.top != 0 {
|
if let Some(rr) = resize_adjustments[i].as_mut() {
|
||||||
let range = if i == 1 {
|
rr.left = 0;
|
||||||
0..1
|
}
|
||||||
} else if i & 1 == 0 {
|
}
|
||||||
i - 1..i
|
|
||||||
} else {
|
|
||||||
i - 2..i
|
|
||||||
};
|
|
||||||
|
|
||||||
for n in range {
|
if resize_ref.top != 0 {
|
||||||
let should_adjust = n % 2 != 0;
|
let range = if i == 1 {
|
||||||
if should_adjust {
|
0..1
|
||||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
} else if i & 1 == 0 {
|
||||||
adjacent_resize.bottom += resize_ref.top;
|
i - 1..i
|
||||||
} else {
|
} else {
|
||||||
resize_adjustments[n] = Option::from(Rect {
|
i - 2..i
|
||||||
left: 0,
|
};
|
||||||
top: 0,
|
|
||||||
right: 0,
|
for n in range {
|
||||||
bottom: resize_ref.top,
|
let should_adjust = n % 2 != 0;
|
||||||
});
|
if should_adjust {
|
||||||
}
|
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||||
|
adjacent_resize.bottom += resize_ref.top;
|
||||||
|
} else {
|
||||||
|
resize_adjustments[n] = Option::from(Rect {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: resize_ref.top,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(Some(resize)) = resize_adjustments.get_mut(i) {
|
if let Some(Some(resize)) = resize_adjustments.get_mut(i) {
|
||||||
resize.top = 0;
|
resize.top = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -818,7 +922,7 @@ fn recursive_fibonacci(
|
|||||||
right: resized.right,
|
right: resized.right,
|
||||||
bottom: resized.bottom,
|
bottom: resized.bottom,
|
||||||
}]
|
}]
|
||||||
} else if idx % 2 != 0 {
|
} else if !idx.is_multiple_of(2) {
|
||||||
let mut res = vec![Rect {
|
let mut res = vec![Rect {
|
||||||
left: resized.left,
|
left: resized.left,
|
||||||
top: main_y,
|
top: main_y,
|
||||||
@@ -1115,6 +1219,37 @@ fn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rec
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_scrolling_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
||||||
|
let len = resize_dimensions.len();
|
||||||
|
let mut result = vec![Rect::default(); len];
|
||||||
|
|
||||||
|
if len <= 1 {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, rect) in resize_dimensions.iter().enumerate() {
|
||||||
|
if let Some(rect) = rect {
|
||||||
|
let is_leftmost = i == 0;
|
||||||
|
let is_rightmost = i == len - 1;
|
||||||
|
|
||||||
|
resize_left(&mut result[i], rect.left);
|
||||||
|
resize_right(&mut result[i], rect.right);
|
||||||
|
resize_top(&mut result[i], rect.top);
|
||||||
|
resize_bottom(&mut result[i], rect.bottom);
|
||||||
|
|
||||||
|
if !is_leftmost && rect.left != 0 {
|
||||||
|
resize_right(&mut result[i - 1], rect.left);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_rightmost && rect.right != 0 {
|
||||||
|
resize_left(&mut result[i + 1], rect.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
fn resize_left(rect: &mut Rect, resize: i32) {
|
fn resize_left(rect: &mut Rect, resize: i32) {
|
||||||
rect.left += resize / 2;
|
rect.left += resize / 2;
|
||||||
rect.right += -resize / 2;
|
rect.right += -resize / 2;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::config_generation::ApplicationConfiguration;
|
use crate::config_generation::ApplicationConfiguration;
|
||||||
use crate::config_generation::ApplicationOptions;
|
use crate::config_generation::ApplicationOptions;
|
||||||
use crate::config_generation::MatchingRule;
|
use crate::config_generation::MatchingRule;
|
||||||
use color_eyre::Result;
|
use color_eyre::eyre;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@@ -36,12 +36,12 @@ impl DerefMut for ApplicationSpecificConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationSpecificConfiguration {
|
impl ApplicationSpecificConfiguration {
|
||||||
pub fn load(pathbuf: &PathBuf) -> Result<Self> {
|
pub fn load(pathbuf: &PathBuf) -> eyre::Result<Self> {
|
||||||
let content = std::fs::read_to_string(pathbuf)?;
|
let content = std::fs::read_to_string(pathbuf)?;
|
||||||
Ok(serde_json::from_str(&content)?)
|
Ok(serde_json::from_str(&content)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(pathbuf: &PathBuf) -> Result<String> {
|
pub fn format(pathbuf: &PathBuf) -> eyre::Result<String> {
|
||||||
Ok(serde_json::to_string_pretty(&Self::load(pathbuf)?)?)
|
Ok(serde_json::to_string_pretty(&Self::load(pathbuf)?)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use color_eyre::Result;
|
use color_eyre::eyre;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
@@ -53,41 +53,64 @@ impl ApplicationOptions {
|
|||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
/// Rule for matching applications
|
||||||
pub enum MatchingRule {
|
pub enum MatchingRule {
|
||||||
|
/// Simple matching rule which must evaluate to true
|
||||||
Simple(IdWithIdentifier),
|
Simple(IdWithIdentifier),
|
||||||
|
/// Composite matching rule where all conditions must evaluate to true
|
||||||
Composite(Vec<IdWithIdentifier>),
|
Composite(Vec<IdWithIdentifier>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Rule for assigning applications to a workspace
|
||||||
pub struct WorkspaceMatchingRule {
|
pub struct WorkspaceMatchingRule {
|
||||||
|
/// Target monitor index
|
||||||
pub monitor_index: usize,
|
pub monitor_index: usize,
|
||||||
|
/// Target workspace index
|
||||||
pub workspace_index: usize,
|
pub workspace_index: usize,
|
||||||
|
/// Matching rule for the application
|
||||||
pub matching_rule: MatchingRule,
|
pub matching_rule: MatchingRule,
|
||||||
|
/// Whether to apply the rule only when the application is initially launched
|
||||||
pub initial_only: bool,
|
pub initial_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Rule for matching applications
|
||||||
pub struct IdWithIdentifier {
|
pub struct IdWithIdentifier {
|
||||||
|
/// Kind of identifier to target
|
||||||
pub kind: ApplicationIdentifier,
|
pub kind: ApplicationIdentifier,
|
||||||
|
/// Target identifier
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
/// Matching strategy to use
|
||||||
pub matching_strategy: Option<MatchingStrategy>,
|
pub matching_strategy: Option<MatchingStrategy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Strategy for matching identifiers
|
||||||
pub enum MatchingStrategy {
|
pub enum MatchingStrategy {
|
||||||
|
/// Should not be used, only kept for backward compatibility
|
||||||
Legacy,
|
Legacy,
|
||||||
|
/// Equals
|
||||||
Equals,
|
Equals,
|
||||||
|
/// Starts With
|
||||||
StartsWith,
|
StartsWith,
|
||||||
|
/// Ends With
|
||||||
EndsWith,
|
EndsWith,
|
||||||
|
/// Contains
|
||||||
Contains,
|
Contains,
|
||||||
|
/// Regex
|
||||||
Regex,
|
Regex,
|
||||||
|
/// Does not end with
|
||||||
DoesNotEndWith,
|
DoesNotEndWith,
|
||||||
|
/// Does not start with
|
||||||
DoesNotStartWith,
|
DoesNotStartWith,
|
||||||
|
/// Does not equal
|
||||||
DoesNotEqual,
|
DoesNotEqual,
|
||||||
|
/// Does not contain
|
||||||
DoesNotContain,
|
DoesNotContain,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +165,11 @@ impl ApplicationConfiguration {
|
|||||||
pub struct ApplicationConfigurationGenerator;
|
pub struct ApplicationConfigurationGenerator;
|
||||||
|
|
||||||
impl ApplicationConfigurationGenerator {
|
impl ApplicationConfigurationGenerator {
|
||||||
pub fn load(content: &str) -> Result<Vec<ApplicationConfiguration>> {
|
pub fn load(content: &str) -> eyre::Result<Vec<ApplicationConfiguration>> {
|
||||||
Ok(serde_yaml::from_str(content)?)
|
Ok(serde_yaml::from_str(content)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(content: &str) -> Result<String> {
|
pub fn format(content: &str) -> eyre::Result<String> {
|
||||||
let mut cfgen = Self::load(content)?;
|
let mut cfgen = Self::load(content)?;
|
||||||
for cfg in &mut cfgen {
|
for cfg in &mut cfgen {
|
||||||
cfg.populate_default_matching_strategies();
|
cfg.populate_default_matching_strategies();
|
||||||
@@ -156,7 +179,10 @@ impl ApplicationConfigurationGenerator {
|
|||||||
Ok(serde_yaml::to_string(&cfgen)?)
|
Ok(serde_yaml::to_string(&cfgen)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge(base_content: &str, override_content: &str) -> Result<Vec<ApplicationConfiguration>> {
|
fn merge(
|
||||||
|
base_content: &str,
|
||||||
|
override_content: &str,
|
||||||
|
) -> eyre::Result<Vec<ApplicationConfiguration>> {
|
||||||
let base_cfgen = Self::load(base_content)?;
|
let base_cfgen = Self::load(base_content)?;
|
||||||
let override_cfgen = Self::load(override_content)?;
|
let override_cfgen = Self::load(override_content)?;
|
||||||
|
|
||||||
@@ -182,7 +208,7 @@ impl ApplicationConfigurationGenerator {
|
|||||||
pub fn generate_pwsh(
|
pub fn generate_pwsh(
|
||||||
base_content: &str,
|
base_content: &str,
|
||||||
override_content: Option<&str>,
|
override_content: Option<&str>,
|
||||||
) -> Result<Vec<String>> {
|
) -> eyre::Result<Vec<String>> {
|
||||||
let mut cfgen = if let Some(override_content) = override_content {
|
let mut cfgen = if let Some(override_content) = override_content {
|
||||||
Self::merge(base_content, override_content)?
|
Self::merge(base_content, override_content)?
|
||||||
} else {
|
} else {
|
||||||
@@ -233,7 +259,10 @@ impl ApplicationConfigurationGenerator {
|
|||||||
Ok(lines)
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_ahk(base_content: &str, override_content: Option<&str>) -> Result<Vec<String>> {
|
pub fn generate_ahk(
|
||||||
|
base_content: &str,
|
||||||
|
override_content: Option<&str>,
|
||||||
|
) -> eyre::Result<Vec<String>> {
|
||||||
let mut cfgen = if let Some(override_content) = override_content {
|
let mut cfgen = if let Some(override_content) = override_content {
|
||||||
Self::merge(base_content, override_content)?
|
Self::merge(base_content, override_content)?
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
use color_eyre::eyre::bail;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
@@ -5,12 +9,6 @@ use std::ops::Deref;
|
|||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use color_eyre::eyre::anyhow;
|
|
||||||
use color_eyre::eyre::bail;
|
|
||||||
use color_eyre::Result;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use super::Rect;
|
use super::Rect;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
@@ -32,7 +30,7 @@ impl DerefMut for CustomLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CustomLayout {
|
impl CustomLayout {
|
||||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
|
pub fn from_path<P: AsRef<Path>>(path: P) -> eyre::Result<Self> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let layout: Self = match path.extension() {
|
let layout: Self = match path.extension() {
|
||||||
Some(extension) if extension == "yaml" || extension == "yml" => {
|
Some(extension) if extension == "yaml" || extension == "yml" => {
|
||||||
@@ -41,7 +39,7 @@ impl CustomLayout {
|
|||||||
Some(extension) if extension == "json" => {
|
Some(extension) if extension == "json" => {
|
||||||
serde_json::from_reader(BufReader::new(File::open(path)?))?
|
serde_json::from_reader(BufReader::new(File::open(path)?))?
|
||||||
}
|
}
|
||||||
_ => return Err(anyhow!("custom layouts must be json or yaml files")),
|
_ => bail!("custom layouts must be json or yaml files"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !layout.is_valid() {
|
if !layout.is_valid() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
use core::str::FromStr;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
@@ -8,22 +9,154 @@ use super::OperationDirection;
|
|||||||
use super::Rect;
|
use super::Rect;
|
||||||
use super::Sizing;
|
use super::Sizing;
|
||||||
|
|
||||||
|
pub fn deserialize_option_none_default_layout<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Option<DefaultLayout>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
if s == "None" {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
<DefaultLayout as FromStr>::from_str(&s)
|
||||||
|
.map(Some)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
|
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// A predefined komorebi layout
|
||||||
pub enum DefaultLayout {
|
pub enum DefaultLayout {
|
||||||
|
/// BSP Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +-------+-----+
|
||||||
|
/// | | |
|
||||||
|
/// | +--+--+
|
||||||
|
/// | | |--|
|
||||||
|
/// +-------+--+--+
|
||||||
|
/// ```
|
||||||
BSP,
|
BSP,
|
||||||
|
/// Columns Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +--+--+--+--+
|
||||||
|
/// | | | | |
|
||||||
|
/// | | | | |
|
||||||
|
/// | | | | |
|
||||||
|
/// +--+--+--+--+
|
||||||
|
/// ```
|
||||||
Columns,
|
Columns,
|
||||||
|
/// Rows Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +-----------+
|
||||||
|
/// |-----------|
|
||||||
|
/// |-----------|
|
||||||
|
/// |-----------|
|
||||||
|
/// +-----------+
|
||||||
|
/// ```
|
||||||
Rows,
|
Rows,
|
||||||
|
/// Vertical Stack Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +-------+-----+
|
||||||
|
/// | | |
|
||||||
|
/// | +-----+
|
||||||
|
/// | | |
|
||||||
|
/// +-------+-----+
|
||||||
|
/// ```
|
||||||
VerticalStack,
|
VerticalStack,
|
||||||
|
/// Horizontal Stack Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +------+------+
|
||||||
|
/// | |
|
||||||
|
/// |------+------+
|
||||||
|
/// | | |
|
||||||
|
/// +------+------+
|
||||||
|
/// ```
|
||||||
HorizontalStack,
|
HorizontalStack,
|
||||||
|
/// Ultrawide Vertical Stack Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +-----+-----------+-----+
|
||||||
|
/// | | | |
|
||||||
|
/// | | +-----+
|
||||||
|
/// | | | |
|
||||||
|
/// | | +-----+
|
||||||
|
/// | | | |
|
||||||
|
/// +-----+-----------+-----+
|
||||||
|
/// ```
|
||||||
UltrawideVerticalStack,
|
UltrawideVerticalStack,
|
||||||
|
/// Grid Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
|
||||||
|
/// | | | | | | | | | | | | | | |
|
||||||
|
/// | | | | | | | | | | | | | +---+
|
||||||
|
/// +-----+-----+ | +---+---+ +---+---+---+ +---+---| |
|
||||||
|
/// | | | | | | | | | | | | | +---+
|
||||||
|
/// | | | | | | | | | | | | | | |
|
||||||
|
/// +-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
|
||||||
|
/// 4 windows 5 windows 6 windows 7 windows
|
||||||
|
/// ```
|
||||||
Grid,
|
Grid,
|
||||||
|
/// Right Main Vertical Stack Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +-----+-------+
|
||||||
|
/// | | |
|
||||||
|
/// +-----+ |
|
||||||
|
/// | | |
|
||||||
|
/// +-----+-------+
|
||||||
|
/// ```
|
||||||
RightMainVerticalStack,
|
RightMainVerticalStack,
|
||||||
|
/// Scrolling Layout
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// +--+--+--+--+--+--+
|
||||||
|
/// | | | |
|
||||||
|
/// | | | |
|
||||||
|
/// | | | |
|
||||||
|
/// +--+--+--+--+--+--+
|
||||||
|
/// ```
|
||||||
|
Scrolling,
|
||||||
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
|
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Options for specific layouts
|
||||||
|
pub struct LayoutOptions {
|
||||||
|
/// Options related to the Scrolling layout
|
||||||
|
pub scrolling: Option<ScrollingLayoutOptions>,
|
||||||
|
/// Options related to the Grid layout
|
||||||
|
pub grid: Option<GridLayoutOptions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Options for the Scrolling layout
|
||||||
|
pub struct ScrollingLayoutOptions {
|
||||||
|
/// Desired number of visible columns (default: 3)
|
||||||
|
pub columns: usize,
|
||||||
|
/// With an odd number of visible columns, keep the focused window column centered
|
||||||
|
pub center_focused_column: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Options for the Grid layout
|
||||||
|
pub struct GridLayoutOptions {
|
||||||
|
/// Maximum number of rows per grid column
|
||||||
|
pub rows: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl DefaultLayout {
|
impl DefaultLayout {
|
||||||
pub fn leftmost_index(&self, len: usize) -> usize {
|
pub fn leftmost_index(&self, len: usize) -> usize {
|
||||||
match self {
|
match self {
|
||||||
@@ -31,6 +164,7 @@ impl DefaultLayout {
|
|||||||
n if n > 1 => 1,
|
n if n > 1 => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
},
|
},
|
||||||
|
Self::Scrolling => 0,
|
||||||
DefaultLayout::BSP
|
DefaultLayout::BSP
|
||||||
| DefaultLayout::Columns
|
| DefaultLayout::Columns
|
||||||
| DefaultLayout::Rows
|
| DefaultLayout::Rows
|
||||||
@@ -53,6 +187,7 @@ impl DefaultLayout {
|
|||||||
_ => len.saturating_sub(1),
|
_ => len.saturating_sub(1),
|
||||||
},
|
},
|
||||||
DefaultLayout::RightMainVerticalStack => 0,
|
DefaultLayout::RightMainVerticalStack => 0,
|
||||||
|
DefaultLayout::Scrolling => len.saturating_sub(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +210,7 @@ impl DefaultLayout {
|
|||||||
| Self::RightMainVerticalStack
|
| Self::RightMainVerticalStack
|
||||||
| Self::HorizontalStack
|
| Self::HorizontalStack
|
||||||
| Self::UltrawideVerticalStack
|
| Self::UltrawideVerticalStack
|
||||||
|
| Self::Scrolling
|
||||||
) {
|
) {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
@@ -169,13 +305,15 @@ impl DefaultLayout {
|
|||||||
Self::HorizontalStack => Self::UltrawideVerticalStack,
|
Self::HorizontalStack => Self::UltrawideVerticalStack,
|
||||||
Self::UltrawideVerticalStack => Self::Grid,
|
Self::UltrawideVerticalStack => Self::Grid,
|
||||||
Self::Grid => Self::RightMainVerticalStack,
|
Self::Grid => Self::RightMainVerticalStack,
|
||||||
Self::RightMainVerticalStack => Self::BSP,
|
Self::RightMainVerticalStack => Self::Scrolling,
|
||||||
|
Self::Scrolling => Self::BSP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn cycle_previous(self) -> Self {
|
pub const fn cycle_previous(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Scrolling => Self::RightMainVerticalStack,
|
||||||
Self::RightMainVerticalStack => Self::Grid,
|
Self::RightMainVerticalStack => Self::Grid,
|
||||||
Self::Grid => Self::UltrawideVerticalStack,
|
Self::Grid => Self::UltrawideVerticalStack,
|
||||||
Self::UltrawideVerticalStack => Self::HorizontalStack,
|
Self::UltrawideVerticalStack => Self::HorizontalStack,
|
||||||
|
|||||||
+116
-42
@@ -1,9 +1,10 @@
|
|||||||
|
use super::DefaultLayout;
|
||||||
|
use super::OperationDirection;
|
||||||
use super::custom_layout::Column;
|
use super::custom_layout::Column;
|
||||||
use super::custom_layout::ColumnSplit;
|
use super::custom_layout::ColumnSplit;
|
||||||
use super::custom_layout::ColumnSplitWithCapacity;
|
use super::custom_layout::ColumnSplitWithCapacity;
|
||||||
use super::custom_layout::CustomLayout;
|
use super::custom_layout::CustomLayout;
|
||||||
use super::DefaultLayout;
|
use crate::default_layout::LayoutOptions;
|
||||||
use super::OperationDirection;
|
|
||||||
|
|
||||||
pub trait Direction {
|
pub trait Direction {
|
||||||
fn index_in_direction(
|
fn index_in_direction(
|
||||||
@@ -11,6 +12,7 @@ pub trait Direction {
|
|||||||
op_direction: OperationDirection,
|
op_direction: OperationDirection,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> Option<usize>;
|
) -> Option<usize>;
|
||||||
|
|
||||||
fn is_valid_direction(
|
fn is_valid_direction(
|
||||||
@@ -18,30 +20,35 @@ pub trait Direction {
|
|||||||
op_direction: OperationDirection,
|
op_direction: OperationDirection,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
fn up_index(
|
fn up_index(
|
||||||
&self,
|
&self,
|
||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize;
|
) -> usize;
|
||||||
fn down_index(
|
fn down_index(
|
||||||
&self,
|
&self,
|
||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize;
|
) -> usize;
|
||||||
fn left_index(
|
fn left_index(
|
||||||
&self,
|
&self,
|
||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize;
|
) -> usize;
|
||||||
fn right_index(
|
fn right_index(
|
||||||
&self,
|
&self,
|
||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize;
|
) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,32 +58,53 @@ impl Direction for DefaultLayout {
|
|||||||
op_direction: OperationDirection,
|
op_direction: OperationDirection,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
match op_direction {
|
match op_direction {
|
||||||
OperationDirection::Left => {
|
OperationDirection::Left => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.left_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.left_index(
|
||||||
|
Some(op_direction),
|
||||||
|
idx,
|
||||||
|
Some(count),
|
||||||
|
layout_options,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Right => {
|
OperationDirection::Right => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.right_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.right_index(
|
||||||
|
Some(op_direction),
|
||||||
|
idx,
|
||||||
|
Some(count),
|
||||||
|
layout_options,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Up => {
|
OperationDirection::Up => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.up_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.up_index(
|
||||||
|
Some(op_direction),
|
||||||
|
idx,
|
||||||
|
Some(count),
|
||||||
|
layout_options,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Down => {
|
OperationDirection::Down => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.down_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.down_index(
|
||||||
|
Some(op_direction),
|
||||||
|
idx,
|
||||||
|
Some(count),
|
||||||
|
layout_options,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -89,6 +117,7 @@ impl Direction for DefaultLayout {
|
|||||||
op_direction: OperationDirection,
|
op_direction: OperationDirection,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if count < 2 {
|
if count < 2 {
|
||||||
return false;
|
return false;
|
||||||
@@ -101,16 +130,18 @@ impl Direction for DefaultLayout {
|
|||||||
Self::Rows | Self::HorizontalStack => idx != 0,
|
Self::Rows | Self::HorizontalStack => idx != 0,
|
||||||
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
|
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
|
||||||
Self::UltrawideVerticalStack => idx > 2,
|
Self::UltrawideVerticalStack => idx > 2,
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => false,
|
||||||
},
|
},
|
||||||
OperationDirection::Down => match self {
|
OperationDirection::Down => match self {
|
||||||
Self::BSP => idx != count - 1 && idx % 2 != 0,
|
Self::BSP => idx != count - 1 && !idx.is_multiple_of(2),
|
||||||
Self::Columns => false,
|
Self::Columns => false,
|
||||||
Self::Rows => idx != count - 1,
|
Self::Rows => idx != count - 1,
|
||||||
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,
|
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,
|
||||||
Self::HorizontalStack => idx == 0,
|
Self::HorizontalStack => idx == 0,
|
||||||
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
|
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => false,
|
||||||
},
|
},
|
||||||
OperationDirection::Left => match self {
|
OperationDirection::Left => match self {
|
||||||
Self::BSP => idx != 0,
|
Self::BSP => idx != 0,
|
||||||
@@ -119,10 +150,11 @@ impl Direction for DefaultLayout {
|
|||||||
Self::Rows => false,
|
Self::Rows => false,
|
||||||
Self::HorizontalStack => idx != 0 && idx != 1,
|
Self::HorizontalStack => idx != 0 && idx != 1,
|
||||||
Self::UltrawideVerticalStack => idx != 1,
|
Self::UltrawideVerticalStack => idx != 1,
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => idx != 0,
|
||||||
},
|
},
|
||||||
OperationDirection::Right => match self {
|
OperationDirection::Right => match self {
|
||||||
Self::BSP => idx % 2 == 0 && idx != count - 1,
|
Self::BSP => idx.is_multiple_of(2) && idx != count - 1,
|
||||||
Self::Columns => idx != count - 1,
|
Self::Columns => idx != count - 1,
|
||||||
Self::Rows => false,
|
Self::Rows => false,
|
||||||
Self::VerticalStack => idx == 0,
|
Self::VerticalStack => idx == 0,
|
||||||
@@ -132,7 +164,8 @@ impl Direction for DefaultLayout {
|
|||||||
2 => idx != 0,
|
2 => idx != 0,
|
||||||
_ => idx < 2,
|
_ => idx < 2,
|
||||||
},
|
},
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => idx != count - 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,10 +175,11 @@ impl Direction for DefaultLayout {
|
|||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::BSP => {
|
Self::BSP => {
|
||||||
if idx % 2 == 0 {
|
if idx.is_multiple_of(2) {
|
||||||
idx - 1
|
idx - 1
|
||||||
} else {
|
} else {
|
||||||
idx - 2
|
idx - 2
|
||||||
@@ -157,7 +191,8 @@ impl Direction for DefaultLayout {
|
|||||||
| Self::UltrawideVerticalStack
|
| Self::UltrawideVerticalStack
|
||||||
| Self::RightMainVerticalStack => idx - 1,
|
| Self::RightMainVerticalStack => idx - 1,
|
||||||
Self::HorizontalStack => 0,
|
Self::HorizontalStack => 0,
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +201,7 @@ impl Direction for DefaultLayout {
|
|||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::BSP
|
Self::BSP
|
||||||
@@ -175,7 +211,8 @@ impl Direction for DefaultLayout {
|
|||||||
| Self::RightMainVerticalStack => idx + 1,
|
| Self::RightMainVerticalStack => idx + 1,
|
||||||
Self::Columns => unreachable!(),
|
Self::Columns => unreachable!(),
|
||||||
Self::HorizontalStack => 1,
|
Self::HorizontalStack => 1,
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,10 +221,11 @@ impl Direction for DefaultLayout {
|
|||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::BSP => {
|
Self::BSP => {
|
||||||
if idx % 2 == 0 {
|
if idx.is_multiple_of(2) {
|
||||||
idx - 2
|
idx - 2
|
||||||
} else {
|
} else {
|
||||||
idx - 1
|
idx - 1
|
||||||
@@ -202,7 +240,8 @@ impl Direction for DefaultLayout {
|
|||||||
1 => unreachable!(),
|
1 => unreachable!(),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
},
|
},
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => idx - 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +250,7 @@ impl Direction for DefaultLayout {
|
|||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
||||||
@@ -222,7 +262,8 @@ impl Direction for DefaultLayout {
|
|||||||
0 => 2,
|
0 => 2,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
|
||||||
|
Self::Scrolling => idx + 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,21 +293,32 @@ struct GridTouchingEdges {
|
|||||||
clippy::cast_precision_loss,
|
clippy::cast_precision_loss,
|
||||||
clippy::cast_sign_loss
|
clippy::cast_sign_loss
|
||||||
)]
|
)]
|
||||||
fn get_grid_item(idx: usize, count: usize) -> GridItem {
|
fn get_grid_item(idx: usize, count: usize, layout_options: Option<LayoutOptions>) -> GridItem {
|
||||||
let num_cols = (count as f32).sqrt().ceil() as usize;
|
let row_constraint = layout_options.and_then(|o| o.grid.map(|g| g.rows));
|
||||||
|
let num_cols = if let Some(rows) = row_constraint {
|
||||||
|
((count as f32) / (rows as f32)).ceil() as i32
|
||||||
|
} else {
|
||||||
|
(count as f32).sqrt().ceil() as i32
|
||||||
|
};
|
||||||
|
|
||||||
let mut iter = 0;
|
let mut iter = 0;
|
||||||
|
|
||||||
for col in 0..num_cols {
|
for col in 0..num_cols {
|
||||||
let remaining_windows = count - iter;
|
let remaining_windows = (count - iter) as i32;
|
||||||
let remaining_columns = num_cols - col;
|
let remaining_columns = num_cols - col;
|
||||||
let num_rows_in_this_col = remaining_windows / remaining_columns;
|
|
||||||
|
let num_rows_in_this_col = if let Some(rows) = row_constraint {
|
||||||
|
(remaining_windows / remaining_columns).min(rows as i32)
|
||||||
|
} else {
|
||||||
|
remaining_windows / remaining_columns
|
||||||
|
};
|
||||||
|
|
||||||
for row in 0..num_rows_in_this_col {
|
for row in 0..num_rows_in_this_col {
|
||||||
if iter == idx {
|
if iter == idx {
|
||||||
return GridItem {
|
return GridItem {
|
||||||
state: GridItemState::Valid,
|
state: GridItemState::Valid,
|
||||||
row: row + 1,
|
row: (row + 1) as usize,
|
||||||
num_rows: num_rows_in_this_col,
|
num_rows: num_rows_in_this_col as usize,
|
||||||
touching_edges: GridTouchingEdges {
|
touching_edges: GridTouchingEdges {
|
||||||
left: col == 0,
|
left: col == 0,
|
||||||
right: col == num_cols - 1,
|
right: col == num_cols - 1,
|
||||||
@@ -293,8 +345,13 @@ fn get_grid_item(idx: usize, count: usize) -> GridItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_grid_edge(op_direction: OperationDirection, idx: usize, count: usize) -> bool {
|
fn is_grid_edge(
|
||||||
let item = get_grid_item(idx, count);
|
op_direction: OperationDirection,
|
||||||
|
idx: usize,
|
||||||
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
|
) -> bool {
|
||||||
|
let item = get_grid_item(idx, count, layout_options);
|
||||||
|
|
||||||
match item.state {
|
match item.state {
|
||||||
GridItemState::Invalid => false,
|
GridItemState::Invalid => false,
|
||||||
@@ -311,6 +368,7 @@ fn grid_neighbor(
|
|||||||
op_direction: Option<OperationDirection>,
|
op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let Some(op_direction) = op_direction else {
|
let Some(op_direction) = op_direction else {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -320,11 +378,11 @@ fn grid_neighbor(
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = get_grid_item(idx, count);
|
let item = get_grid_item(idx, count, layout_options);
|
||||||
|
|
||||||
match op_direction {
|
match op_direction {
|
||||||
OperationDirection::Left => {
|
OperationDirection::Left => {
|
||||||
let item_from_prev_col = get_grid_item(idx - item.row, count);
|
let item_from_prev_col = get_grid_item(idx - item.row, count, layout_options);
|
||||||
|
|
||||||
if item.touching_edges.up && item.num_rows != item_from_prev_col.num_rows {
|
if item.touching_edges.up && item.num_rows != item_from_prev_col.num_rows {
|
||||||
return idx - (item.num_rows - 1);
|
return idx - (item.num_rows - 1);
|
||||||
@@ -348,36 +406,42 @@ impl Direction for CustomLayout {
|
|||||||
op_direction: OperationDirection,
|
op_direction: OperationDirection,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
if count <= self.len() {
|
if count <= self.len() {
|
||||||
return DefaultLayout::Columns.index_in_direction(op_direction, idx, count);
|
return DefaultLayout::Columns.index_in_direction(
|
||||||
|
op_direction,
|
||||||
|
idx,
|
||||||
|
count,
|
||||||
|
layout_options,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
match op_direction {
|
match op_direction {
|
||||||
OperationDirection::Left => {
|
OperationDirection::Left => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.left_index(None, idx, None))
|
Option::from(self.left_index(None, idx, None, layout_options))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Right => {
|
OperationDirection::Right => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.right_index(None, idx, None))
|
Option::from(self.right_index(None, idx, None, layout_options))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Up => {
|
OperationDirection::Up => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.up_index(None, idx, None))
|
Option::from(self.up_index(None, idx, None, layout_options))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Down => {
|
OperationDirection::Down => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count, layout_options) {
|
||||||
Option::from(self.down_index(None, idx, None))
|
Option::from(self.down_index(None, idx, None, layout_options))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -390,9 +454,15 @@ impl Direction for CustomLayout {
|
|||||||
op_direction: OperationDirection,
|
op_direction: OperationDirection,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if count <= self.len() {
|
if count <= self.len() {
|
||||||
return DefaultLayout::Columns.is_valid_direction(op_direction, idx, count);
|
return DefaultLayout::Columns.is_valid_direction(
|
||||||
|
op_direction,
|
||||||
|
idx,
|
||||||
|
count,
|
||||||
|
layout_options,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
match op_direction {
|
match op_direction {
|
||||||
@@ -436,6 +506,7 @@ impl Direction for CustomLayout {
|
|||||||
_op_direction: Option<OperationDirection>,
|
_op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
_count: Option<usize>,
|
_count: Option<usize>,
|
||||||
|
_layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
idx - 1
|
idx - 1
|
||||||
}
|
}
|
||||||
@@ -445,6 +516,7 @@ impl Direction for CustomLayout {
|
|||||||
_op_direction: Option<OperationDirection>,
|
_op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
_count: Option<usize>,
|
_count: Option<usize>,
|
||||||
|
_layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
idx + 1
|
idx + 1
|
||||||
}
|
}
|
||||||
@@ -454,6 +526,7 @@ impl Direction for CustomLayout {
|
|||||||
_op_direction: Option<OperationDirection>,
|
_op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
_count: Option<usize>,
|
_count: Option<usize>,
|
||||||
|
_layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let column_idx = self.column_for_container_idx(idx);
|
let column_idx = self.column_for_container_idx(idx);
|
||||||
if column_idx - 1 == 0 {
|
if column_idx - 1 == 0 {
|
||||||
@@ -468,6 +541,7 @@ impl Direction for CustomLayout {
|
|||||||
_op_direction: Option<OperationDirection>,
|
_op_direction: Option<OperationDirection>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
_count: Option<usize>,
|
_count: Option<usize>,
|
||||||
|
_layout_options: Option<LayoutOptions>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let column_idx = self.column_for_container_idx(idx);
|
let column_idx = self.column_for_container_idx(idx);
|
||||||
self.first_container_idx(column_idx + 1)
|
self.first_container_idx(column_idx + 1)
|
||||||
|
|||||||
+139
-19
@@ -1,18 +1,20 @@
|
|||||||
#![warn(clippy::all)]
|
#![warn(clippy::all)]
|
||||||
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
|
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
|
||||||
|
#![allow(deprecated)] // allow deprecated variants like HidingBehaviour::Hide to be used in derive macros
|
||||||
|
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use color_eyre::Result;
|
use color_eyre::eyre;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
|
|
||||||
use crate::animation::prefix::AnimationPrefix;
|
|
||||||
use crate::KomorebiTheme;
|
use crate::KomorebiTheme;
|
||||||
|
use crate::animation::prefix::AnimationPrefix;
|
||||||
pub use animation::AnimationStyle;
|
pub use animation::AnimationStyle;
|
||||||
pub use arrangement::Arrangement;
|
pub use arrangement::Arrangement;
|
||||||
pub use arrangement::Axis;
|
pub use arrangement::Axis;
|
||||||
@@ -22,14 +24,14 @@ pub use custom_layout::ColumnSplitWithCapacity;
|
|||||||
pub use custom_layout::ColumnWidth;
|
pub use custom_layout::ColumnWidth;
|
||||||
pub use custom_layout::CustomLayout;
|
pub use custom_layout::CustomLayout;
|
||||||
pub use cycle_direction::CycleDirection;
|
pub use cycle_direction::CycleDirection;
|
||||||
pub use default_layout::DefaultLayout;
|
pub use default_layout::*;
|
||||||
pub use direction::Direction;
|
pub use direction::Direction;
|
||||||
pub use layout::Layout;
|
pub use layout::Layout;
|
||||||
pub use operation_direction::OperationDirection;
|
pub use operation_direction::OperationDirection;
|
||||||
pub use pathext::replace_env_in_path;
|
|
||||||
pub use pathext::resolve_option_hashmap_usize_path;
|
|
||||||
pub use pathext::PathExt;
|
pub use pathext::PathExt;
|
||||||
pub use pathext::ResolvedPathBuf;
|
pub use pathext::ResolvedPathBuf;
|
||||||
|
pub use pathext::replace_env_in_path;
|
||||||
|
pub use pathext::resolve_option_hashmap_usize_path;
|
||||||
pub use rect::Rect;
|
pub use rect::Rect;
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
@@ -54,6 +56,8 @@ pub enum SocketMessage {
|
|||||||
// Window / Container Commands
|
// Window / Container Commands
|
||||||
FocusWindow(OperationDirection),
|
FocusWindow(OperationDirection),
|
||||||
MoveWindow(OperationDirection),
|
MoveWindow(OperationDirection),
|
||||||
|
PreselectDirection(OperationDirection),
|
||||||
|
CancelPreselect,
|
||||||
CycleFocusWindow(CycleDirection),
|
CycleFocusWindow(CycleDirection),
|
||||||
CycleMoveWindow(CycleDirection),
|
CycleMoveWindow(CycleDirection),
|
||||||
StackWindow(OperationDirection),
|
StackWindow(OperationDirection),
|
||||||
@@ -86,6 +90,7 @@ pub enum SocketMessage {
|
|||||||
Close,
|
Close,
|
||||||
Minimize,
|
Minimize,
|
||||||
Promote,
|
Promote,
|
||||||
|
PromoteSwap,
|
||||||
PromoteFocus,
|
PromoteFocus,
|
||||||
PromoteWindow(OperationDirection),
|
PromoteWindow(OperationDirection),
|
||||||
EagerFocus(String),
|
EagerFocus(String),
|
||||||
@@ -108,6 +113,7 @@ pub enum SocketMessage {
|
|||||||
AdjustWorkspacePadding(Sizing, i32),
|
AdjustWorkspacePadding(Sizing, i32),
|
||||||
ChangeLayout(DefaultLayout),
|
ChangeLayout(DefaultLayout),
|
||||||
CycleLayout(CycleDirection),
|
CycleLayout(CycleDirection),
|
||||||
|
ScrollingLayoutColumns(NonZeroUsize),
|
||||||
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
|
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
|
||||||
FlipLayout(Axis),
|
FlipLayout(Axis),
|
||||||
ToggleWorkspaceWindowContainerBehaviour,
|
ToggleWorkspaceWindowContainerBehaviour,
|
||||||
@@ -200,6 +206,7 @@ pub enum SocketMessage {
|
|||||||
StackbarFontFamily(Option<String>),
|
StackbarFontFamily(Option<String>),
|
||||||
WorkAreaOffset(Rect),
|
WorkAreaOffset(Rect),
|
||||||
MonitorWorkAreaOffset(usize, Rect),
|
MonitorWorkAreaOffset(usize, Rect),
|
||||||
|
WorkspaceWorkAreaOffset(usize, usize, Rect),
|
||||||
ToggleWindowBasedWorkAreaOffset,
|
ToggleWindowBasedWorkAreaOffset,
|
||||||
ResizeDelta(i32),
|
ResizeDelta(i32),
|
||||||
InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),
|
InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),
|
||||||
@@ -245,7 +252,7 @@ pub enum SocketMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SocketMessage {
|
impl SocketMessage {
|
||||||
pub fn as_bytes(&self) -> Result<Vec<u8>> {
|
pub fn as_bytes(&self) -> eyre::Result<Vec<u8>> {
|
||||||
Ok(serde_json::to_string(self)?.as_bytes().to_vec())
|
Ok(serde_json::to_string(self)?.as_bytes().to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,7 +260,7 @@ impl SocketMessage {
|
|||||||
impl FromStr for SocketMessage {
|
impl FromStr for SocketMessage {
|
||||||
type Err = serde_json::Error;
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> eyre::Result<Self, Self::Err> {
|
||||||
serde_json::from_str(s)
|
serde_json::from_str(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,17 +274,24 @@ pub struct SubscribeOptions {
|
|||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Stackbar mode
|
||||||
pub enum StackbarMode {
|
pub enum StackbarMode {
|
||||||
|
/// Always show
|
||||||
Always,
|
Always,
|
||||||
|
/// Never show
|
||||||
Never,
|
Never,
|
||||||
|
/// Show on stack
|
||||||
OnStack,
|
OnStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Starbar label
|
||||||
pub enum StackbarLabel {
|
pub enum StackbarLabel {
|
||||||
#[default]
|
#[default]
|
||||||
|
/// Process name
|
||||||
Process,
|
Process,
|
||||||
|
/// Window title
|
||||||
Title,
|
Title,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +299,7 @@ pub enum StackbarLabel {
|
|||||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
|
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Border style
|
||||||
pub enum BorderStyle {
|
pub enum BorderStyle {
|
||||||
#[default]
|
#[default]
|
||||||
/// Use the system border style
|
/// Use the system border style
|
||||||
@@ -299,6 +314,7 @@ pub enum BorderStyle {
|
|||||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
|
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Border style
|
||||||
pub enum BorderImplementation {
|
pub enum BorderImplementation {
|
||||||
#[default]
|
#[default]
|
||||||
/// Use the adjustable komorebi border implementation
|
/// Use the adjustable komorebi border implementation
|
||||||
@@ -322,13 +338,20 @@ pub enum BorderImplementation {
|
|||||||
Hash,
|
Hash,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Window kind
|
||||||
pub enum WindowKind {
|
pub enum WindowKind {
|
||||||
|
/// Single window
|
||||||
Single,
|
Single,
|
||||||
|
/// Stack container
|
||||||
Stack,
|
Stack,
|
||||||
|
/// Monocle container
|
||||||
Monocle,
|
Monocle,
|
||||||
#[default]
|
#[default]
|
||||||
|
/// Unfocused window
|
||||||
Unfocused,
|
Unfocused,
|
||||||
|
/// Unfocused locked container
|
||||||
UnfocusedLocked,
|
UnfocusedLocked,
|
||||||
|
/// Floating window
|
||||||
Floating,
|
Floating,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +364,7 @@ pub enum StateQuery {
|
|||||||
FocusedWindowIndex,
|
FocusedWindowIndex,
|
||||||
FocusedWorkspaceName,
|
FocusedWorkspaceName,
|
||||||
FocusedWorkspaceLayout,
|
FocusedWorkspaceLayout,
|
||||||
|
FocusedContainerKind,
|
||||||
Version,
|
Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,41 +372,64 @@ pub enum StateQuery {
|
|||||||
Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Application identifier
|
||||||
pub enum ApplicationIdentifier {
|
pub enum ApplicationIdentifier {
|
||||||
|
/// Executable name
|
||||||
#[serde(alias = "exe")]
|
#[serde(alias = "exe")]
|
||||||
Exe,
|
Exe,
|
||||||
|
/// Class
|
||||||
#[serde(alias = "class")]
|
#[serde(alias = "class")]
|
||||||
Class,
|
Class,
|
||||||
#[serde(alias = "title")]
|
#[serde(alias = "title")]
|
||||||
|
/// Window title
|
||||||
Title,
|
Title,
|
||||||
|
/// Executable path
|
||||||
#[serde(alias = "path")]
|
#[serde(alias = "path")]
|
||||||
Path,
|
Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Focus follows mouse implementation
|
||||||
pub enum FocusFollowsMouseImplementation {
|
pub enum FocusFollowsMouseImplementation {
|
||||||
/// A custom FFM implementation (slightly more CPU-intensive)
|
/// Custom FFM implementation (slightly more CPU-intensive)
|
||||||
Komorebi,
|
Komorebi,
|
||||||
/// The native (legacy) Windows FFM implementation
|
/// Native (legacy) Windows FFM implementation
|
||||||
Windows,
|
Windows,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Window management behaviour
|
||||||
pub struct WindowManagementBehaviour {
|
pub struct WindowManagementBehaviour {
|
||||||
/// The current WindowContainerBehaviour to be used
|
/// The current [`WindowContainerBehaviour`] to be used
|
||||||
pub current_behaviour: WindowContainerBehaviour,
|
pub current_behaviour: WindowContainerBehaviour,
|
||||||
/// Override of `current_behaviour` to open new windows as floating windows
|
/// Override of `current_behaviour` to open new windows as floating windows
|
||||||
/// that can be later toggled to tiled, when false it will default to
|
/// that can be later toggled to tiled, when false it will default to
|
||||||
/// `current_behaviour` again.
|
/// `current_behaviour` again.
|
||||||
pub float_override: bool,
|
pub float_override: bool,
|
||||||
|
/// Determines if a new window should be spawned floating when on the floating layer and the
|
||||||
|
/// floating layer behaviour is set to float. This value is always calculated when checking for
|
||||||
|
/// the management behaviour on a specific workspace.
|
||||||
|
pub floating_layer_override: bool,
|
||||||
|
/// The floating layer behaviour to be used if the float override is being used
|
||||||
|
pub floating_layer_behaviour: FloatingLayerBehaviour,
|
||||||
|
/// The `Placement` to be used when toggling a window to float
|
||||||
|
pub toggle_float_placement: Placement,
|
||||||
|
/// The `Placement` to be used when spawning a window on the floating layer with the
|
||||||
|
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`
|
||||||
|
pub floating_layer_placement: Placement,
|
||||||
|
/// The `Placement` to be used when spawning a window with float override active
|
||||||
|
pub float_override_placement: Placement,
|
||||||
|
/// The `Placement` to be used when spawning a window that matches a `floating_applications` rule
|
||||||
|
pub float_rule_placement: Placement,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Window container behaviour when a new window is opened
|
||||||
pub enum WindowContainerBehaviour {
|
pub enum WindowContainerBehaviour {
|
||||||
/// Create a new container for each new window
|
/// Create a new container for each new window
|
||||||
#[default]
|
#[default]
|
||||||
@@ -395,18 +442,63 @@ pub enum WindowContainerBehaviour {
|
|||||||
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Floating layer behaviour when a new window is opened
|
||||||
pub enum FloatingLayerBehaviour {
|
pub enum FloatingLayerBehaviour {
|
||||||
/// Tile new windows (unless they match a float rule)
|
/// Tile new windows (unless they match a float rule or float override is active)
|
||||||
#[default]
|
#[default]
|
||||||
Tile,
|
Tile,
|
||||||
/// Float new windows
|
/// Float new windows
|
||||||
Float,
|
Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||||
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Placement behaviour for floating windows
|
||||||
|
pub enum Placement {
|
||||||
|
/// Does not change the size or position of the window
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Center the window without changing the size
|
||||||
|
Center,
|
||||||
|
/// Center the window and resize it according to the `AspectRatio`
|
||||||
|
CenterAndResize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatingLayerBehaviour {
|
||||||
|
pub fn should_float(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
FloatingLayerBehaviour::Tile => false,
|
||||||
|
FloatingLayerBehaviour::Float => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Placement {
|
||||||
|
pub fn should_center(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Placement::None => false,
|
||||||
|
Placement::Center | Placement::CenterAndResize => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_resize(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Placement::None | Placement::Center => false,
|
||||||
|
Placement::CenterAndResize => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Move behaviour when the operation works across a monitor boundary
|
||||||
pub enum MoveBehaviour {
|
pub enum MoveBehaviour {
|
||||||
/// Swap the window container with the window container at the edge of the adjacent monitor
|
/// Swap the window container with the window container at the edge of the adjacent monitor
|
||||||
|
#[default]
|
||||||
Swap,
|
Swap,
|
||||||
/// Insert the window container into the focused workspace on the adjacent monitor
|
/// Insert the window container into the focused workspace on the adjacent monitor
|
||||||
Insert,
|
Insert,
|
||||||
@@ -414,39 +506,52 @@ pub enum MoveBehaviour {
|
|||||||
NoOp,
|
NoOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||||
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Behaviour when an action would cross a monitor boundary
|
||||||
pub enum CrossBoundaryBehaviour {
|
pub enum CrossBoundaryBehaviour {
|
||||||
/// Attempt to perform actions across a workspace boundary
|
/// Attempt to perform actions across a workspace boundary
|
||||||
Workspace,
|
Workspace,
|
||||||
/// Attempt to perform actions across a monitor boundary
|
/// Attempt to perform actions across a monitor boundary
|
||||||
|
#[default]
|
||||||
Monitor,
|
Monitor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Window hiding behaviour
|
||||||
pub enum HidingBehaviour {
|
pub enum HidingBehaviour {
|
||||||
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
/// END OF LIFE FEATURE: Use the `SW_HIDE` flag to hide windows when switching workspaces (has issues with Electron apps)
|
||||||
|
#[deprecated(note = "End of life feature")]
|
||||||
Hide,
|
Hide,
|
||||||
/// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
|
/// Use the `SW_MINIMIZE` flag to hide windows when switching workspaces (has issues with frequent workspace switching)
|
||||||
Minimize,
|
Minimize,
|
||||||
/// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces
|
/// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces
|
||||||
Cloak,
|
Cloak,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
||||||
|
)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Operation behaviour for temporarily unmanaged and floating windows
|
||||||
pub enum OperationBehaviour {
|
pub enum OperationBehaviour {
|
||||||
/// Process komorebic commands on temporarily unmanaged/floated windows
|
/// Process commands on temporarily unmanaged/floated windows
|
||||||
|
#[default]
|
||||||
Op,
|
Op,
|
||||||
/// Ignore komorebic commands on temporarily unmanaged/floated windows
|
/// Ignore commands on temporarily unmanaged/floated windows
|
||||||
NoOp,
|
NoOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Sizing
|
||||||
pub enum Sizing {
|
pub enum Sizing {
|
||||||
|
/// Increase
|
||||||
Increase,
|
Increase,
|
||||||
|
/// Decrease
|
||||||
Decrease,
|
Decrease,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,6 +571,19 @@ impl Sizing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Window handling behaviour
|
||||||
|
pub enum WindowHandlingBehaviour {
|
||||||
|
#[default]
|
||||||
|
/// Synchronous
|
||||||
|
Sync,
|
||||||
|
/// Asynchronous
|
||||||
|
Async,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -473,7 +591,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn deserializes() {
|
fn deserializes() {
|
||||||
// Set a variable for testing
|
// Set a variable for testing
|
||||||
std::env::set_var("VAR", "VALUE");
|
unsafe {
|
||||||
|
std::env::set_var("VAR", "VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
let json = r#"{"type":"WorkspaceLayoutCustomRule","content":[0,0,0,"/path/%VAR%/d"]}"#;
|
let json = r#"{"type":"WorkspaceLayoutCustomRule","content":[0,0,0,"/path/%VAR%/d"]}"#;
|
||||||
let message: SocketMessage = serde_json::from_str(json).unwrap();
|
let message: SocketMessage = serde_json::from_str(json).unwrap();
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use super::Axis;
|
||||||
|
use super::direction::Direction;
|
||||||
|
use crate::default_layout::LayoutOptions;
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
|
|
||||||
use super::direction::Direction;
|
|
||||||
use super::Axis;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum OperationDirection {
|
pub enum OperationDirection {
|
||||||
@@ -57,7 +57,8 @@ impl OperationDirection {
|
|||||||
layout_flip: Option<Axis>,
|
layout_flip: Option<Axis>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
len: NonZeroUsize,
|
len: NonZeroUsize,
|
||||||
|
layout_options: Option<LayoutOptions>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
layout.index_in_direction(self.flip(layout_flip), idx, len.get())
|
layout.index_in_direction(self.flip(layout_flip), idx, len.get(), layout_options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -10,9 +9,9 @@ use serde::Serialize;
|
|||||||
|
|
||||||
/// Path extension trait
|
/// Path extension trait
|
||||||
pub trait PathExt {
|
pub trait PathExt {
|
||||||
/// Resolve environment variables components in a path.
|
/// Resolve environment variable components in a path.
|
||||||
///
|
///
|
||||||
/// Resolves the follwing formats:
|
/// Resolves the following formats:
|
||||||
/// - CMD: `%variable%`
|
/// - CMD: `%variable%`
|
||||||
/// - PowerShell: `$Env:variable`
|
/// - PowerShell: `$Env:variable`
|
||||||
/// - Bash: `$variable`.
|
/// - Bash: `$variable`.
|
||||||
@@ -58,7 +57,7 @@ impl<P: AsRef<Path>> PathExt for P {
|
|||||||
// if component is a variable, get the value from the environment
|
// if component is a variable, get the value from the environment
|
||||||
if let Some(var) = var {
|
if let Some(var) = var {
|
||||||
let var = unsafe { OsStr::from_encoded_bytes_unchecked(var) };
|
let var = unsafe { OsStr::from_encoded_bytes_unchecked(var) };
|
||||||
if let Some(value) = env::var_os(var) {
|
if let Some(value) = std::env::var_os(var) {
|
||||||
out.push(value);
|
out.push(value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -122,13 +121,16 @@ impl<'de> serde_with::DeserializeAs<'de, PathBuf> for ResolvedPathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
impl serde_with::schemars_0_8::JsonSchemaAs<PathBuf> for ResolvedPathBuf {
|
impl serde_with::schemars_1::JsonSchemaAs<PathBuf> for ResolvedPathBuf {
|
||||||
fn schema_name() -> String {
|
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||||
"PathBuf".to_owned()
|
std::borrow::Cow::Borrowed("PathBuf")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||||
<PathBuf as schemars::JsonSchema>::json_schema(gen)
|
schemars::json_schema!({
|
||||||
|
"type": "string",
|
||||||
|
"description": "A file system path. Environment variables like %VAR%, $Env:VAR, or $VAR are automatically resolved."
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +167,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resolves_env_vars() {
|
fn resolves_env_vars() {
|
||||||
// Set a variable for testing
|
// Set a variable for testing
|
||||||
std::env::set_var("VAR", "VALUE");
|
unsafe {
|
||||||
|
std::env::set_var("VAR", "VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
// %VAR% format
|
// %VAR% format
|
||||||
assert_eq!(resolve("/path/%VAR%/d"), expected("/path/VALUE/d"));
|
assert_eq!(resolve("/path/%VAR%/d"), expected("/path/VALUE/d"));
|
||||||
@@ -183,7 +187,9 @@ mod tests {
|
|||||||
assert_eq!(resolve("/path/$ASD/to/d"), expected("/path/$ASD/to/d"));
|
assert_eq!(resolve("/path/$ASD/to/d"), expected("/path/$ASD/to/d"));
|
||||||
|
|
||||||
// Set a $env:USERPROFILE variable for testing
|
// Set a $env:USERPROFILE variable for testing
|
||||||
std::env::set_var("USERPROFILE", "C:\\Users\\user");
|
unsafe {
|
||||||
|
std::env::set_var("USERPROFILE", "C:\\Users\\user");
|
||||||
|
}
|
||||||
|
|
||||||
// ~ and $HOME should be replaced with $Env:USERPROFILE
|
// ~ and $HOME should be replaced with $Env:USERPROFILE
|
||||||
assert_eq!(resolve("~"), expected("C:\\Users\\user"));
|
assert_eq!(resolve("~"), expected("C:\\Users\\user"));
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ use windows::Win32::Foundation::RECT;
|
|||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
/// Rectangle dimensions
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
/// The left point in a Win32 Rect
|
/// Left point of the rectangle
|
||||||
pub left: i32,
|
pub left: i32,
|
||||||
/// The top point in a Win32 Rect
|
/// Top point of the rectangle
|
||||||
pub top: i32,
|
pub top: i32,
|
||||||
/// The right point in a Win32 Rect
|
/// Width of the recentangle (from the left point)
|
||||||
pub right: i32,
|
pub right: i32,
|
||||||
/// The bottom point in a Win32 Rect
|
/// Height of the rectangle (from the top point)
|
||||||
pub bottom: i32,
|
pub bottom: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ impl Rect {
|
|||||||
pub fn is_same_size_as(&self, rhs: &Self) -> bool {
|
pub fn is_same_size_as(&self, rhs: &Self) -> bool {
|
||||||
self.right == rhs.right && self.bottom == rhs.bottom
|
self.right == rhs.right && self.bottom == rhs.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_same_position_as(&self, rhs: &Self) -> bool {
|
||||||
|
self.left == rhs.left && self.top == rhs.top
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
|
|||||||
@@ -44,13 +44,15 @@ pub fn send_notification(hwnd: isize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||||
std::thread::spawn(move || loop {
|
std::thread::spawn(move || {
|
||||||
match handle_notifications(wm.clone()) {
|
loop {
|
||||||
Ok(()) => {
|
match handle_notifications(wm.clone()) {
|
||||||
tracing::warn!("restarting finished thread");
|
Ok(()) => {
|
||||||
}
|
tracing::warn!("restarting finished thread");
|
||||||
Err(error) => {
|
}
|
||||||
tracing::warn!("restarting failed thread: {}", error);
|
Err(error) => {
|
||||||
|
tracing::warn!("restarting failed thread: {}", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+66
-14
@@ -8,7 +8,7 @@ pub mod ring;
|
|||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod focus_manager;
|
pub mod focus_manager;
|
||||||
pub mod locked_deque;
|
pub mod lockable_sequence;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
pub mod monitor_reconciliator;
|
pub mod monitor_reconciliator;
|
||||||
pub mod process_command;
|
pub mod process_command;
|
||||||
@@ -16,7 +16,9 @@ pub mod process_event;
|
|||||||
pub mod process_movement;
|
pub mod process_movement;
|
||||||
pub mod reaper;
|
pub mod reaper;
|
||||||
pub mod set_window_position;
|
pub mod set_window_position;
|
||||||
|
pub mod splash;
|
||||||
pub mod stackbar_manager;
|
pub mod stackbar_manager;
|
||||||
|
pub mod state;
|
||||||
pub mod static_config;
|
pub mod static_config;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
pub mod theme_manager;
|
pub mod theme_manager;
|
||||||
@@ -39,12 +41,12 @@ use std::io::Write;
|
|||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicI32;
|
use std::sync::atomic::AtomicI32;
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use core::*;
|
pub use core::*;
|
||||||
pub use komorebi_themes::colour::*;
|
pub use komorebi_themes::colour::*;
|
||||||
@@ -62,17 +64,19 @@ use crate::core::config_generation::IdWithIdentifier;
|
|||||||
use crate::core::config_generation::MatchingRule;
|
use crate::core::config_generation::MatchingRule;
|
||||||
use crate::core::config_generation::MatchingStrategy;
|
use crate::core::config_generation::MatchingStrategy;
|
||||||
use crate::core::config_generation::WorkspaceMatchingRule;
|
use crate::core::config_generation::WorkspaceMatchingRule;
|
||||||
use color_eyre::Result;
|
use color_eyre::eyre;
|
||||||
|
use crossbeam_utils::atomic::AtomicCell;
|
||||||
use os_info::Version;
|
use os_info::Version;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use state::State;
|
||||||
use uds_windows::UnixStream;
|
use uds_windows::UnixStream;
|
||||||
use which::which;
|
use which::which;
|
||||||
use winreg::enums::HKEY_CURRENT_USER;
|
|
||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
|
use winreg::enums::HKEY_CURRENT_USER;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||||
@@ -159,7 +163,14 @@ lazy_static! {
|
|||||||
})
|
})
|
||||||
]));
|
]));
|
||||||
static ref SESSION_FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
|
static ref SESSION_FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
|
||||||
static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
|
static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
|
||||||
|
MatchingRule::Simple(IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("komorebi-shortcuts.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
})
|
||||||
|
|
||||||
|
]));
|
||||||
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||||
"Chrome_RenderWidgetHostHWND".to_string(),
|
"Chrome_RenderWidgetHostHWND".to_string(),
|
||||||
]));
|
]));
|
||||||
@@ -192,8 +203,7 @@ lazy_static! {
|
|||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
home.is_dir(),
|
home.is_dir(),
|
||||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
|
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory"
|
||||||
home_path
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -204,11 +214,10 @@ lazy_static! {
|
|||||||
pub static ref AHK_EXE: String = {
|
pub static ref AHK_EXE: String = {
|
||||||
let mut ahk: String = String::from("autohotkey.exe");
|
let mut ahk: String = String::from("autohotkey.exe");
|
||||||
|
|
||||||
if let Ok(komorebi_ahk_exe) = std::env::var("KOMOREBI_AHK_EXE") {
|
if let Ok(komorebi_ahk_exe) = std::env::var("KOMOREBI_AHK_EXE")
|
||||||
if which(&komorebi_ahk_exe).is_ok() {
|
&& which(&komorebi_ahk_exe).is_ok() {
|
||||||
ahk = komorebi_ahk_exe;
|
ahk = komorebi_ahk_exe;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ahk
|
ahk
|
||||||
};
|
};
|
||||||
@@ -227,11 +236,17 @@ lazy_static! {
|
|||||||
Arc::new(Mutex::new(HashMap::new()));
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen)));
|
static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen)));
|
||||||
|
|
||||||
|
static ref CURRENT_VIRTUAL_DESKTOP: Arc<Mutex<Option<Vec<u8>>>> = Arc::new(Mutex::new(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static DEFAULT_WORKSPACE_LAYOUT: AtomicCell<Option<DefaultLayout>> =
|
||||||
|
AtomicCell::new(Some(DefaultLayout::BSP));
|
||||||
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
|
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||||
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||||
|
pub static DEFAULT_RESIZE_DELTA: i32 = 50;
|
||||||
|
|
||||||
|
pub static DEFAULT_MOUSE_FOLLOWS_FOCUS: bool = true;
|
||||||
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
||||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||||
@@ -240,8 +255,34 @@ pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
|||||||
|
|
||||||
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
|
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
|
||||||
|
|
||||||
|
pub static WINDOW_HANDLING_BEHAVIOUR: AtomicCell<WindowHandlingBehaviour> =
|
||||||
|
AtomicCell::new(WindowHandlingBehaviour::Sync);
|
||||||
|
|
||||||
shadow_rs::shadow!(build);
|
shadow_rs::shadow!(build);
|
||||||
|
|
||||||
|
pub const PUBLIC_KEY: [u8; 32] = [
|
||||||
|
0x5a, 0x69, 0x4a, 0xe1, 0x3c, 0x4b, 0xc8, 0x4e, 0xc3, 0x79, 0x0f, 0xab, 0x27, 0x6b, 0x7e, 0xdd,
|
||||||
|
0x6b, 0x39, 0x6f, 0xa2, 0xc3, 0x9f, 0x3d, 0x48, 0xf2, 0x72, 0x56, 0x41, 0x1b, 0xc8, 0x08, 0xdb,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct License {
|
||||||
|
#[serde(rename = "hasValidSubscription")]
|
||||||
|
pub has_valid_subscription: bool,
|
||||||
|
pub timestamp: i64,
|
||||||
|
#[serde(rename = "currentEndPeriod")]
|
||||||
|
pub current_end_period: Option<i64>,
|
||||||
|
pub signature: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for types that can be marked as locked or unlocked.
|
||||||
|
pub trait Lockable {
|
||||||
|
/// Returns `true` if the item is locked.
|
||||||
|
fn locked(&self) -> bool;
|
||||||
|
/// Sets the locked state of the item.
|
||||||
|
fn set_locked(&mut self, locked: bool) -> &mut Self;
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
@@ -286,23 +327,34 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
|||||||
current
|
current
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum NotificationEvent {
|
pub enum NotificationEvent {
|
||||||
WindowManager(WindowManagerEvent),
|
WindowManager(WindowManagerEvent),
|
||||||
Socket(SocketMessage),
|
Socket(SocketMessage),
|
||||||
Monitor(MonitorNotification),
|
Monitor(MonitorNotification),
|
||||||
|
VirtualDesktop(VirtualDesktopNotification),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
pub enum VirtualDesktopNotification {
|
||||||
|
EnteredAssociatedVirtualDesktop,
|
||||||
|
LeftAssociatedVirtualDesktop,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
pub event: NotificationEvent,
|
pub event: NotificationEvent,
|
||||||
pub state: State,
|
pub state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_subscribers(notification: Notification, state_has_been_modified: bool) -> Result<()> {
|
pub fn notify_subscribers(
|
||||||
|
notification: Notification,
|
||||||
|
state_has_been_modified: bool,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
let is_override_event = matches!(
|
let is_override_event = matches!(
|
||||||
notification.event,
|
notification.event,
|
||||||
NotificationEvent::Socket(SocketMessage::AddSubscriberSocket(_))
|
NotificationEvent::Socket(SocketMessage::AddSubscriberSocket(_))
|
||||||
@@ -383,7 +435,7 @@ pub fn notify_subscribers(notification: Notification, state_has_been_modified: b
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_configuration() -> Result<()> {
|
pub fn load_configuration() -> eyre::Result<()> {
|
||||||
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
||||||
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user