mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-13 14:23:24 +01:00
Compare commits
87 Commits
feature/de
...
feature/cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4968b0fe37 | ||
|
|
b4b400b236 | ||
|
|
2ee0bbc0c7 | ||
|
|
d38d3c956d | ||
|
|
052eb1c763 | ||
|
|
58730b81b4 | ||
|
|
274ae43e8f | ||
|
|
2a30f09bbd | ||
|
|
8fd18048a4 | ||
|
|
5809735024 | ||
|
|
96fdbbd1fb | ||
|
|
de131e9ca5 | ||
|
|
07dba03255 | ||
|
|
acd53dec1b | ||
|
|
a98968d179 | ||
|
|
8a32219867 | ||
|
|
ce4b75cc3c | ||
|
|
e4226ce623 | ||
|
|
4bfd7febb4 | ||
|
|
5cc688dc6b | ||
|
|
d897890032 | ||
|
|
e702d93a8a | ||
|
|
a8c687d3d5 | ||
|
|
30fbc1ae73 | ||
|
|
cb60e91842 | ||
|
|
64d29d606a | ||
|
|
072a62c314 | ||
|
|
a95e6e9644 | ||
|
|
6ba19d3ea2 | ||
|
|
edf1943157 | ||
|
|
d0c847e5bc | ||
|
|
992bc2abfe | ||
|
|
fa07f2d2f8 | ||
|
|
cc4e204191 | ||
|
|
24791f0ce5 | ||
|
|
6b95bf95f9 | ||
|
|
c0e1e9366d | ||
|
|
532436fe1a | ||
|
|
532949409c | ||
|
|
ec4a5e6491 | ||
|
|
f6e99eaac1 | ||
|
|
a6cf801a6b | ||
|
|
83d11c6f0f | ||
|
|
1ba1c57ba0 | ||
|
|
9f16894a09 | ||
|
|
df9ae931cc | ||
|
|
be2af9fdcb | ||
|
|
c083484ef0 | ||
|
|
1804b21c4a | ||
|
|
b6bd191cf5 | ||
|
|
5919f88b38 | ||
|
|
ff2aa5e51a | ||
|
|
42c12d5ec3 | ||
|
|
f0ce8e8572 | ||
|
|
bdea4821c3 | ||
|
|
229aeb7ddc | ||
|
|
17cbdc8663 | ||
|
|
9f3d806f79 | ||
|
|
fe9a1416e7 | ||
|
|
3618beb366 | ||
|
|
a4de2ee841 | ||
|
|
60e1834b43 | ||
|
|
54323c4c6a | ||
|
|
6516c808ee | ||
|
|
894b6f3d96 | ||
|
|
7ccdff4986 | ||
|
|
c48e1db0ff | ||
|
|
ea9752d5e1 | ||
|
|
acf780767c | ||
|
|
8e588d0284 | ||
|
|
91ff9b8852 | ||
|
|
81a7951312 | ||
|
|
555308db5f | ||
|
|
c90769f5fa | ||
|
|
76002385ab | ||
|
|
f40e80cd61 | ||
|
|
e4f9d8af86 | ||
|
|
0c64432c25 | ||
|
|
fe20caa56a | ||
|
|
02a2796e7d | ||
|
|
a0eb025cec | ||
|
|
70a61376a8 | ||
|
|
1761919707 | ||
|
|
1325da4e81 | ||
|
|
0776ca1565 | ||
|
|
724b0b7692 | ||
|
|
b53de81754 |
10
.github/workflows/windows.yaml
vendored
10
.github/workflows/windows.yaml
vendored
@@ -18,6 +18,14 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -47,7 +55,7 @@ jobs:
|
||||
key: ${{ matrix.platform.target }}
|
||||
- run: cargo +nightly fmt --check
|
||||
- run: cargo clippy
|
||||
- run: cargo test --package komorebi --test compat
|
||||
- run: cargo test
|
||||
- uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: "build"
|
||||
|
||||
40
CODE_OF_CONDUCT.md
Normal file
40
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# The Komorebi Code of Conduct
|
||||
|
||||
This document is based on the [Rust Code of
|
||||
Conduct](https://www.rust-lang.org/policies/code-of-conduct)
|
||||
|
||||
## Conduct
|
||||
|
||||
- We are committed to providing a friendly, safe and welcoming environment for
|
||||
all, regardless of level of experience, gender identity and expression, sexual
|
||||
orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, nationality, or other similar characteristic.
|
||||
|
||||
- Please avoid using overtly sexual aliases or other nicknames that might
|
||||
detract from a friendly, safe and welcoming environment for all.
|
||||
|
||||
- Please be kind and courteous. There’s no need to be mean or rude.
|
||||
|
||||
- Respect that people have differences of opinion and that every design or
|
||||
implementation choice carries a trade-off and numerous costs. There is seldom a
|
||||
right answer.
|
||||
|
||||
- Please keep unstructured critique to a minimum. If you have solid ideas you
|
||||
want to experiment with, make a fork and see how it works.
|
||||
|
||||
- We will exclude you from interaction if you insult, demean or harass anyone.
|
||||
That is not welcome behavior. We interpret the term “harassment” as including
|
||||
the definition in the [Citizen Code of
|
||||
Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md);
|
||||
if you have any lack of clarity about what might be included in that concept,
|
||||
please read their definition. In particular, we don’t tolerate behavior that
|
||||
excludes people in socially marginalized groups.
|
||||
|
||||
- Private harassment is also unacceptable. No matter who you are, if you feel
|
||||
you have been or are being harassed or made uncomfortable by a community member,
|
||||
please contact me immediately. Whether you’re a regular contributor or a
|
||||
newcomer, we care about making this community a safe place for you and we’ve got
|
||||
your back.
|
||||
|
||||
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing
|
||||
behavior is not welcome.
|
||||
1676
Cargo.lock
generated
1676
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
26
Cargo.toml
@@ -13,6 +13,7 @@ members = [
|
||||
|
||||
[workspace.dependencies]
|
||||
clap = { version = "4", features = ["derive", "wrap_help"] }
|
||||
chrono-tz = "0.10"
|
||||
chrono = "0.4"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
@@ -34,16 +35,16 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
paste = "1"
|
||||
sysinfo = "0.33"
|
||||
uds_windows = "1"
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
|
||||
windows-numerics = { version = "0.1" }
|
||||
windows-implement = { version = "0.59" }
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "a28c6559a9de2f92c142a714947a9b081776caca" }
|
||||
windows-numerics = { version = "0.2" }
|
||||
windows-implement = { version = "0.60" }
|
||||
windows-interface = { version = "0.59" }
|
||||
windows-core = { version = "0.60" }
|
||||
windows-core = { version = "0.61" }
|
||||
shadow-rs = "1"
|
||||
which = "7"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.60"
|
||||
version = "0.61"
|
||||
features = [
|
||||
"Foundation_Numerics",
|
||||
"Win32_Devices",
|
||||
@@ -71,17 +72,4 @@ features = [
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Media",
|
||||
"Media_Control"
|
||||
]
|
||||
|
||||
[profile.dev-jeezy]
|
||||
inherits = "dev"
|
||||
debug = false
|
||||
opt-level = 1
|
||||
|
||||
[profile.dev-jeezy.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release-jeezy]
|
||||
inherits = "release"
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
]
|
||||
24
LICENSE.md
24
LICENSE.md
@@ -1,6 +1,6 @@
|
||||
# Komorebi License
|
||||
|
||||
Version 1.0.0
|
||||
Version 2.0.0
|
||||
|
||||
## Acceptance
|
||||
|
||||
@@ -13,9 +13,20 @@ your licenses.
|
||||
The licensor grants you a copyright license for the software
|
||||
to do everything you might do with the software that would
|
||||
otherwise infringe the licensor's copyright in it for any
|
||||
permitted purpose. However, you may only make changes according
|
||||
permitted purpose. However, you may only distribute the source
|
||||
code of the software according to the [Distribution License](
|
||||
#distribution-license), you may only make changes according
|
||||
to the [Changes License](#changes-license), and you may not
|
||||
distribute the software or new works based on the software.
|
||||
otherwise distribute the software or new works based on the
|
||||
software.
|
||||
|
||||
## Distribution License
|
||||
|
||||
The licensor grants you an additional copyright license to
|
||||
distribute copies of the source code of the software. Your
|
||||
license to distribute covers distributing the source code of
|
||||
the software with changes permitted by the [Changes License](
|
||||
#changes-license).
|
||||
|
||||
## Changes License
|
||||
|
||||
@@ -45,7 +56,7 @@ law. These terms do not limit them.
|
||||
|
||||
These terms do not allow you to sublicense or transfer any of
|
||||
your licenses to anyone else, or prevent the licensor from
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
any other licenses.
|
||||
|
||||
## Patent Defense
|
||||
@@ -63,7 +74,7 @@ violated any of these terms, or done anything with the software
|
||||
not covered by your licenses, your licenses can nonetheless
|
||||
continue if you come into full compliance with these terms,
|
||||
and take practical steps to correct past violations, within
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
end immediately.
|
||||
|
||||
## No Liability
|
||||
@@ -88,11 +99,10 @@ organizations that have control over, are under the control of,
|
||||
or are under common control with that organization. **Control**
|
||||
means ownership of substantially all the assets of an entity,
|
||||
or the power to direct its management and policies by vote,
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
|
||||
**Your licenses** are all the licenses granted to you for the
|
||||
software under these terms.
|
||||
|
||||
**Use** means anything you do with the software requiring one
|
||||
of your licenses.
|
||||
|
||||
|
||||
15
README.md
15
README.md
@@ -71,7 +71,10 @@ showcases the many awesome projects that exist in the _komorebi_ ecosystem.
|
||||
|
||||
## Licensing for Personal Use
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 1.0.0
|
||||
`komorebi` is [educational source
|
||||
software](https://lgug2z.com/articles/educational-source-software/).
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 2.0.0
|
||||
license](https://github.com/LGUG2Z/komorebi-license), which is a fork of the
|
||||
[PolyForm Strict 1.0.0
|
||||
license](https://polyformproject.org/licenses/strict/1.0.0). On a high level
|
||||
@@ -82,7 +85,7 @@ hard-forks) based on the software.
|
||||
Anyone is free to make their own fork of `komorebi` with changes intended either
|
||||
for personal use or for integration back upstream via pull requests.
|
||||
|
||||
The [Komorebi 1.0.0 License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
The [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
not permit any kind of commercial use (i.e. using `komorebi` at work).
|
||||
|
||||
## Sponsorship for Personal Use
|
||||
@@ -99,7 +102,8 @@ me on GitHub.
|
||||
[GitHub Sponsors is enabled for this
|
||||
project](https://github.com/sponsors/LGUG2Z). Sponsors can claim custom roles on
|
||||
the Discord server, get shout outs at the end of _komorebi_-related videos on
|
||||
YouTube, and gain the ability to submit feature requests on the issue tracker.
|
||||
YouTube, gain the ability to submit feature requests on the issue tracker, and
|
||||
receive releases of komorebi with "easter eggs" on physical media.
|
||||
|
||||
If you would like to tip or sponsor the project but are unable to use GitHub
|
||||
Sponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z), or
|
||||
@@ -142,7 +146,8 @@ video will answer the majority of your questions.
|
||||
|
||||
[@amnweb](https://github.com/amnweb) showing _komorebi_ `v0.1.28` running on Windows 11 with window borders,
|
||||
unfocused window transparency and animations enabled, using a custom status bar integrated using
|
||||
_komorebi_'s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).
|
||||
_komorebi_'
|
||||
s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).
|
||||
|
||||
https://github.com/LGUG2Z/komorebi/assets/13164844/21be8dc4-fa76-4f70-9b37-1d316f4b40c2
|
||||
|
||||
@@ -389,7 +394,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
|
||||
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
||||
|
||||
```rust
|
||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.34"}
|
||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.35"}
|
||||
|
||||
use anyhow::Result;
|
||||
use komorebi_client::Notification;
|
||||
|
||||
99
deny.toml
Normal file
99
deny.toml
Normal file
@@ -0,0 +1,99 @@
|
||||
[graph]
|
||||
targets = [
|
||||
"x86_64-pc-windows-msvc",
|
||||
"i686-pc-windows-msvc",
|
||||
"aarch64-pc-windows-msvc",
|
||||
]
|
||||
all-features = false
|
||||
no-default-features = false
|
||||
|
||||
[output]
|
||||
feature-depth = 1
|
||||
|
||||
[advisories]
|
||||
ignore = [
|
||||
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
|
||||
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" },
|
||||
{ id = "RUSTSEC-2024-0320", reason = "not using any yaml features from this library" }
|
||||
]
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"0BSD",
|
||||
"Apache-2.0",
|
||||
"Artistic-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MIT-0",
|
||||
"MPL-2.0",
|
||||
"OFL-1.1",
|
||||
"Ubuntu-font-1.0",
|
||||
"Unicode-3.0",
|
||||
"Zlib",
|
||||
"LicenseRef-Komorebi-1.0"
|
||||
]
|
||||
confidence-threshold = 0.8
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-client"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebic"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebic-no-console"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-themes"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-gui"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-bar"
|
||||
expression = "LicenseRef-Komorebi-1.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "base16-egui-themes"
|
||||
expression = "MIT"
|
||||
license-files = []
|
||||
|
||||
[bans]
|
||||
multiple-versions = "allow"
|
||||
wildcards = "allow"
|
||||
highlight = "all"
|
||||
workspace-default-features = "allow"
|
||||
external-default-features = "allow"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
allow-git = [
|
||||
"https://github.com/LGUG2Z/base16-egui-themes",
|
||||
"https://github.com/LGUG2Z/catppuccin-egui",
|
||||
"https://github.com/LGUG2Z/windows-icons",
|
||||
"https://github.com/LGUG2Z/win32-display-data",
|
||||
"https://github.com/LGUG2Z/flavours",
|
||||
"https://github.com/LGUG2Z/base16_color_scheme",
|
||||
]
|
||||
771
dependencies.json
Normal file
771
dependencies.json
Normal file
@@ -0,0 +1,771 @@
|
||||
{
|
||||
"licenses": [
|
||||
[
|
||||
"0BSD",
|
||||
[
|
||||
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"win32-display-data 0.1.0 git+https://github.com/LGUG2Z/win32-display-data?rev=55cebdebfbd68dbd14945a1ba90f6b05b7be2893"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Apache-2.0",
|
||||
[
|
||||
"ab_glyph 0.2.29 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ab_glyph_rasterizer 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_winit 0.23.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle-wincon 3.0.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anyhow 1.0.97 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"arboard 3.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"autocfg 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace 0.3.71 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bit_field 0.10.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cc 1.2.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cfg-if 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"chrono 0.4.40 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_builder 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_lex 0.7.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"color-eyre 0.6.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"color-spantrace 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"colorchoice 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crc32fast 1.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-channel 0.5.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"deranged 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dpi 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dyn-clone 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ecolor 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"eframe 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui-phosphor 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui-winit 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_extras 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_glow 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"either 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"emath 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"epaint 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"filetime 0.2.25 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"flate2 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin 0.32.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin_egl_sys 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin_wgl_sys 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"half 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hashbrown 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"iana-time-zone 0.1.61 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"idna 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"idna_adapter 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"image 0.25.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"image-webp 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"imgref 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"immutable-chunkmap 2.0.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"indenter 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"indexmap 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"is_terminal_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"itertools 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"jobserver 0.1.32 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"jpeg-decoder 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"khronos_api 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libgit2-sys 0.18.0+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libz-sys 1.1.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"litrs 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"lock_api 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"log 0.4.26 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miette 7.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miette-derive 7.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miow 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ntapi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"owned_ttf_parser 0.25.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"parking_lot 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"parking_lot_core 0.9.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"percent-encoding 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"png 0.17.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ppv-lite86 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro-error-attr2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro-error2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro2 1.0.94 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quote 1.0.39 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rayon 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex 1.11.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-syntax 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"reqwest 0.12.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustc-demangle 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustversion 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json 1.0.140 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
|
||||
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"socket2 0.5.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"supports-hyperlinks 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"supports-unicode 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"syn 2.0.99 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"sync_wrapper 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tempfile 3.17.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"terminal_size 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror-impl 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror-impl 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thread_local 1.1.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"time 0.3.37 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"time-core 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"typenum 1.18.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tzdb 0.7.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-linebreak 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"uom 0.36.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"url 2.5.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"utf16_iter 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"webbrowser 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"weezl 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-link 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winit 0.30.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"wmi 0.15.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Artistic-2.0",
|
||||
[
|
||||
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"BSD-2-Clause",
|
||||
[
|
||||
"av1-grain 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rav1e 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"v_frame 0.3.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"BSD-3-Clause",
|
||||
[
|
||||
"alloc-no-stdlib 2.0.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"alloc-stdlib 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"avif-serialize 0.8.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"brotli 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"exr 1.73.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"lebe 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ravif 0.11.11 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"BSL-1.0",
|
||||
[
|
||||
"clipboard-win 5.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"error-code 3.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"CC0-1.0",
|
||||
[
|
||||
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"imgref 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"notify 6.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"ISC",
|
||||
[
|
||||
"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libloading 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"starship-battery 0.10.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"MIT",
|
||||
[
|
||||
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"aligned-vec 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anstyle-wincon 3.0.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"anyhow 1.0.97 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"arboard 3.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"arg_enum_proc_macro 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"autocfg 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace 0.3.71 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bit_field 0.10.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"brotli 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"built 0.7.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytes 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"catppuccin-egui 5.3.1 git+https://github.com/LGUG2Z/catppuccin-egui?rev=bdaff30959512c4f7ee7304117076a48633d777f",
|
||||
"cc 1.2.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cfg-if 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cfg_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"chrono 0.4.40 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_builder 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"clap_lex 0.7.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"color-eyre 0.6.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"color-spantrace 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"color_quant 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"colorchoice 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crc32fast 1.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-channel 0.5.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"deranged 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"dyn-clone 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ecolor 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"eframe 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui-phosphor 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui-winit 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_extras 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"egui_glow 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"either 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"emath 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"epaint 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"filetime 0.2.25 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"flate2 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"fs-tail 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"getset 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glutin-winit 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"h2 0.4.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"half 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hashbrown 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http-body 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"http-body-util 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hyper 1.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"hyper-util 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"iana-time-zone 0.1.61 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"idna 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"idna_adapter 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"image 0.25.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"image-webp 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"immutable-chunkmap 2.0.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"indenter 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"indexmap 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"is_terminal_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"itertools 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"jobserver 0.1.32 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"jpeg-decoder 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libgit2-sys 0.18.0+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"libz-sys 1.1.21 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"litrs 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"lock_api 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"log 0.4.26 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"loop9 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"matchers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"maybe-rayon 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"memchr 2.7.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"memoffset 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"mime_guess2 2.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"mio 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miow 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"nanoid 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"netdev 0.32.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"noop_proc_macro 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ntapi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"nu-ansi-term 0.46.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"os_info 3.10.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"overload 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"owo-colors 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"owo-colors 4.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"parking_lot 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"parking_lot_core 0.9.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"percent-encoding 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"png 0.17.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ppv-lite86 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro-error-attr2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro-error2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"proc-macro2 1.0.94 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"quote 1.0.39 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"random_word 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rayon 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex 1.11.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-automata 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-syntax 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"reqwest 0.12.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rgb 0.8.50 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustc-demangle 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"rustversion 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"schannel 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"schemars 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"schemars_derive 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json 1.0.140 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
|
||||
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"simd-adler32 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"slab 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"socket2 0.5.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"strum 0.27.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"strum_macros 0.27.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"syn 2.0.99 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"synstructure 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tempfile 3.17.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"terminal_size 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"textwrap 0.16.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror-impl 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thiserror-impl 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"thread_local 1.1.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tiff 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"time 0.3.37 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"time-core 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tokio 1.43.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tokio-native-tls 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tokio-util 0.7.13 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower-layer 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing 0.1.41 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing-appender 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing-attributes 0.1.28 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing-core 0.1.33 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing-error 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing-log 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tracing-subscriber 0.3.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"try-lock 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"typenum 1.18.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"uds_windows 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-width 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unsafe-libyaml 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"uom 0.36.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"url 2.5.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"utf16_iter 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"want 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"webbrowser 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"weezl 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"which 7.0.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winapi-util 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5",
|
||||
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-interface 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-link 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-result 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winsafe 0.0.19 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"wmi 0.15.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"xml-rs 0.8.25 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"MIT-0",
|
||||
[
|
||||
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tzdb_data 0.2.1 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"MPL-2.0",
|
||||
[
|
||||
"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"OFL-1.1",
|
||||
[
|
||||
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Ubuntu-font-1.0",
|
||||
[
|
||||
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Unicode-3.0",
|
||||
[
|
||||
"icu_collections 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_locid 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_locid_transform 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_locid_transform_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_normalizer 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_normalizer_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_properties 1.5.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_properties_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_provider 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"icu_provider_macros 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"litemap 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"tinystr 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"writeable 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"yoke 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"yoke-derive 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerofrom 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerofrom-derive 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerovec 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zerovec-derive 0.10.3 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Unlicense",
|
||||
[
|
||||
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"memchr 2.7.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"regex-automata 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"winapi-util 0.1.9 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
],
|
||||
[
|
||||
"Zlib",
|
||||
[
|
||||
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"const_format 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"const_format_proc_macros 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"foldhash 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
|
||||
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -10,9 +10,9 @@ Options:
|
||||
Desired ease function for animation
|
||||
|
||||
[default: linear]
|
||||
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart,
|
||||
ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ,
|
||||
ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
|
||||
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart,
|
||||
ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back,
|
||||
ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
|
||||
|
||||
-a, --animation-type <ANIMATION_TYPE>
|
||||
Animation type to apply the style to. If not specified, sets global style
|
||||
|
||||
@@ -18,7 +18,7 @@ Arguments:
|
||||
Options:
|
||||
-w, --window-kind <WINDOW_KIND>
|
||||
[default: single]
|
||||
[possible values: single, stack, monocle, unfocused, floating]
|
||||
[possible values: single, stack, monocle, unfocused, unfocused-locked, floating]
|
||||
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
16
docs/cli/cycle-empty-workspace.md
Normal file
16
docs/cli/cycle-empty-workspace.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# cycle-empty-workspace
|
||||
|
||||
```
|
||||
Focus the next empty workspace in the given cycle direction (if one exists)
|
||||
|
||||
Usage: komorebic.exe cycle-empty-workspace <CYCLE_DIRECTION>
|
||||
|
||||
Arguments:
|
||||
<CYCLE_DIRECTION>
|
||||
[possible values: previous, next]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/move-to-last-workspace.md
Normal file
12
docs/cli/move-to-last-workspace.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# move-to-last-workspace
|
||||
|
||||
```
|
||||
Move the focused window to the last focused monitor workspace
|
||||
|
||||
Usage: komorebic.exe move-to-last-workspace
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/send-to-last-workspace.md
Normal file
12
docs/cli/send-to-last-workspace.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# send-to-last-workspace
|
||||
|
||||
```
|
||||
Send the focused window to the last focused monitor workspace
|
||||
|
||||
Usage: komorebic.exe send-to-last-workspace
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
12
docs/cli/toggle-lock.md
Normal file
12
docs/cli/toggle-lock.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# toggle-lock
|
||||
|
||||
```
|
||||
Toggle a lock for the focused container, ensuring it will not be displaced by any new windows
|
||||
|
||||
Usage: komorebic.exe toggle-lock
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
@@ -1,8 +1,8 @@
|
||||
# toggle-workspace-float-override
|
||||
|
||||
```
|
||||
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace
|
||||
previously it takes the opposite of the global value
|
||||
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes
|
||||
the opposite of the global value
|
||||
|
||||
Usage: komorebic.exe toggle-workspace-float-override
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# toggle-workspace-window-container-behaviour
|
||||
|
||||
```
|
||||
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the
|
||||
global value
|
||||
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the global value
|
||||
|
||||
Usage: komorebic.exe toggle-workspace-window-container-behaviour
|
||||
|
||||
|
||||
412
docs/common-workflows/multi-monitor-setup.md
Normal file
412
docs/common-workflows/multi-monitor-setup.md
Normal file
@@ -0,0 +1,412 @@
|
||||
# Multi-Monitor Setup
|
||||
|
||||
You can set up komorebi to work with multiple monitors. To do so, first you start by setting up multiple monitor
|
||||
configurations on your `komorebi.json` config file.
|
||||
|
||||
If you've used the [`komorebic quickstart`](../cli/quickstart.md) command you'll already have a `komorebi.json` config
|
||||
file with one monitor config setup. Open this file and look for the `"monitors":` line, you should find something like
|
||||
this:
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": [
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "I",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "II",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "III",
|
||||
"layout": "HorizontalStack"
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"layout": "UltrawideVerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "V",
|
||||
"layout": "Rows"
|
||||
},
|
||||
{
|
||||
"name": "VI",
|
||||
"layout": "Grid"
|
||||
},
|
||||
{
|
||||
"name": "VII",
|
||||
"layout": "RightMainVerticalStack"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For this example we will remove some workspaces to simplify the config so it is easier to look at, but feel free to
|
||||
set up as many workspaces per monitor as you'd like. Here is the same configuration with only 3 workspaces.
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": [
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "I",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "II",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "III",
|
||||
"layout": "HorizontalStack"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Let's add another monitor:
|
||||
|
||||
```json
|
||||
{
|
||||
"monitors": [
|
||||
// monitor 1, index 0
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "I",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "II",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "III",
|
||||
"layout": "HorizontalStack"
|
||||
}
|
||||
]
|
||||
},
|
||||
// monitor 2, index 1
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"name": "1",
|
||||
"layout": "BSP"
|
||||
},
|
||||
{
|
||||
"name": "2",
|
||||
"layout": "VerticalStack"
|
||||
},
|
||||
{
|
||||
"name": "3",
|
||||
"layout": "HorizontalStack"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Now have two monitor configurations. We have the first monitor configuration, which is index 0 (*usually
|
||||
on programming languages the first item of a list starts with index 0*), this configuration has 3 workspaces with names
|
||||
"I", "II" and "III". Then the 2nd monitor configuration, which is index 1, also has 3 workspaces with names "1", "2",
|
||||
and "3" (you should always give unique names to your workspaces).
|
||||
|
||||
Now if you start komorebi with two monitors connected, the main monitor will use the configuration with index 0 and the
|
||||
secondary monitor will use the configuration with index 1.
|
||||
|
||||
---
|
||||
|
||||
Let's say you have more monitors, or you want to make sure that a certain configuration is always applied to a certain
|
||||
monitor. For this you will want to use the `display_index_preferences`.
|
||||
|
||||
Open up a terminal and type the following command: [ `komorebic monitor-info`](../cli/monitor-information.md). This
|
||||
command will give you the information about your connected monitors, you want to look up the `serial_number_id`. You
|
||||
should get something like this:
|
||||
|
||||
```
|
||||
❯ komorebic monitor-info
|
||||
[
|
||||
{
|
||||
"id": 6620935,
|
||||
"name": "DISPLAY1",
|
||||
"device": "BOE0A1C",
|
||||
"device_id": "BOE0A1C-5&a2bea0b&0&UID512",
|
||||
"serial_number_id": "0",
|
||||
"size": {
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"right": 1920,
|
||||
"bottom": 1080
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 181932057,
|
||||
"name": "DISPLAY2",
|
||||
"device": "VSC8C31",
|
||||
"device_id": "VSC8C31-5&18560b1f&0&UID4356",
|
||||
"serial_number_id": "UEP174021562",
|
||||
"size": {
|
||||
"left": 0,
|
||||
"top": -1080,
|
||||
"right": 1920,
|
||||
"bottom": 1080
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
In this case the setup is a laptop with a secondary monitor connected. You'll need to figure out which monitor is which,
|
||||
usually the display name's number should be similar to the numbers you can find on
|
||||
`Windows Settings -> System -> Display`.
|
||||
|
||||
If you have trouble with this step you can always jump on Discord and ask for help (create a `Support` thread).
|
||||
|
||||
Once you know which monitor is which, you want to look up their `serial_number_id` to use that on
|
||||
`display_index_preferences`, you can also use the `device_id`, it accepts both however there have been reported cases
|
||||
where the `device_id` changes after a restart while the `serial_number_id` doesn't.
|
||||
|
||||
So with the example above, we want the laptop to always use the configuration index 0 and the other monitor to use
|
||||
configuration index 1, so we map the configuration index number to the monitor `serial_number_id`/`device_id` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "0",
|
||||
"1": "UEP174021562"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Again you could also have used the `device_id` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "BOE0A1C-5&a2bea0b&0&UID512",
|
||||
"1": "VSC8C31-5&18560b1f&0&UID4356"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should add this `display_index_preferences` option to your `komorebi.json` file. If you find that something is
|
||||
not working as expected you can try to use the command `komorebic check`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> **When using multiple monitors it is recommended to always set the `display_index_preferences`. If you don't you might
|
||||
get some undefined behaviour.**
|
||||
|
||||
---
|
||||
|
||||
If you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so
|
||||
using the `bar_configurations` array in your `komorebi.json` configuration file. You can refer to the
|
||||
[multiple-bar-instances](multiple-bar-instances.md) documentation.
|
||||
|
||||
In this case it is important to use `display_index_preferences`, because if you don't, and you have 3 or more monitors,
|
||||
disconnecting and reconnecting monitors may result in the bars for the monitors getting shifted around.
|
||||
|
||||
Consider this setup with 3 monitors (A, B and C):
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 0
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_2_BAR.json
|
||||
{
|
||||
"monitor_index": 1
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// WORK_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 2
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "MONITOR_1_ID",
|
||||
"1": "MONITOR_2_ID",
|
||||
"2": "MONITOR_3_ID"
|
||||
},
|
||||
"bar_configurations": [
|
||||
// this bar uses "monitor_index": 0,
|
||||
"path/to/bar_config_1.json",
|
||||
// this bar uses "monitor_index": 1,
|
||||
"path/to/bar_config_2.json",
|
||||
// this bar uses "monitor_index": 2,
|
||||
"path/to/bar_config_3.json"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Komorebi uses an internal map to keep track of monitor to config indices, this map is called `monitor_usr_idx_map` it is
|
||||
an internal variable to komorebi that you don't need to do anything with, but you can see it with the [
|
||||
`komorebic state`](../cli/state.md) command (in case you need to debug something).
|
||||
|
||||
At first, komorebi will load all monitors and set the internal index map (`monitor_usr_idx_map`) as:
|
||||
|
||||
```json
|
||||
{
|
||||
// This is monitor A
|
||||
"0": 0,
|
||||
// This is monitor B
|
||||
"1": 1,
|
||||
// This is monitor C
|
||||
"2": 2
|
||||
}
|
||||
```
|
||||
|
||||
Which kind of seems unnecessary, but imagine that then you disconnect monitor B (or it goes to sleep). Then komorebi
|
||||
will only have 2 monitors with index 0 and 1, so the above map will be updated to this:
|
||||
|
||||
```jsonc
|
||||
[
|
||||
"0": 0, // This is monitor A
|
||||
"2": 1, // This is now monitor C, because monitor B disconnected
|
||||
]
|
||||
```
|
||||
|
||||
So now the bar intended to be for monitor B, which was looking for index "1" on that map, doesn't see it and knows it
|
||||
should be disabled. And the bar for monitor C looks at that map and knows that it's index "2" now maps to index 1 so it
|
||||
uses that index internally to get all the correct values about the monitor.
|
||||
|
||||
If you didn't have the `display_index_preferences` set, then when you disconnected monitor B, komorebi wouldn't know
|
||||
how to map the indices and would use default behaviour which would result in a map like this:
|
||||
|
||||
```json
|
||||
{
|
||||
// This is monitor A
|
||||
"0": 0,
|
||||
// This is monitor C, because monitor B disconnected. However the bars will think it is monitor B because it has index "1"
|
||||
"1": 1
|
||||
}
|
||||
```
|
||||
|
||||
# Multiple Monitors on different machines
|
||||
|
||||
You can use the same `komorebi.json` to configure two different setups and then synchronize your config across machines.
|
||||
However, if you do this it is important to be aware of a few things.
|
||||
|
||||
Firstly, using `display_index_preferences` is required in this case.
|
||||
|
||||
You will need to get the `serial_number_id` or `device_id` of all the monitors of all your setups. With that information
|
||||
you would then set your config like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_index_preferences": {
|
||||
"0": "HOME_MONITOR_1_ID",
|
||||
"1": "HOME_MONITOR_2_ID",
|
||||
"2": "WORK_MONITOR_1_ID",
|
||||
"3": "WORK_MONITOR_2_ID"
|
||||
},
|
||||
"monitors": [
|
||||
// HOME_MONITOR_1
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
},
|
||||
// HOME_MONITOR_2
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
},
|
||||
// WORK_MONITOR_1
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
},
|
||||
// WORK_MONITOR_2
|
||||
{
|
||||
"workspaces": [
|
||||
// ...
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> *You can't use the same config on two different monitors, you have to make a duplicated config for each monitor!*
|
||||
|
||||
Then on the bar configs you need to set the bar's monitor index like this:
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 0
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// HOME_MONITOR_2_BAR.json
|
||||
{
|
||||
"monitor_index": 1
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// WORK_MONITOR_1_BAR.json
|
||||
{
|
||||
"monitor_index": 2
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// WORK_MONITOR_2_BAR.json
|
||||
{
|
||||
"monitor_index": 3
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Although you will only ever have 2 monitors connected at any one time, and they'll always have index 0 and 1, the
|
||||
above config will still work on both physical configurations.
|
||||
|
||||
This is because komorebi will apply the appropriate config to the loaded monitors and will create a map of the user
|
||||
index (the index defined in the user config) to the actual monitor index, and the bar will use that map to know if it
|
||||
should be enabled, and where it should be drawn.
|
||||
|
||||
### Things to keep in mind
|
||||
|
||||
* If you are using a laptop connected to one monitor at work and a different one at home, the work monitor and the home
|
||||
monitor are considered different monitors by komorebi
|
||||
* When you disconnect from work, komorebi will keep the work monitor cached
|
||||
* You can still use a laptop alone without any monitor and if you need a window that was on the other monitor you can
|
||||
press the taskbar icon or use `alt + tab` to bring it to focus and that window will now be part of the laptop monitor
|
||||
* If you then reconnect the work monitor, the cached version will be applied with all its windows (except any window(s)
|
||||
you might have moved to another monitor)
|
||||
* If however, instead of reconnecting the work monitor, you connect the home monitor, then the work monitor will still
|
||||
remain cached, and komorebi will load the home monitor from the cache (if it exists)
|
||||
* Sometimes when you disconnect/reconnect a monitor the event might be missed by komorebi, meaning that Windows will
|
||||
show you both monitors but komorebi won't know about the existence of one of them
|
||||
* If you notice this type of weird behaviour, always run the [
|
||||
`komorebic monitor-info`](../cli/monitor-information.md)
|
||||
command and validate if one of the monitors is missing
|
||||
* To fix this you can try disconnecting and reconnecting the monitor again, or restarting komorebi
|
||||
@@ -34,7 +34,7 @@ showcases the many awesome projects that exist in the `komorebi` ecosystem.
|
||||
|
||||
## Licensing for Personal Use
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 1.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork
|
||||
`komorebi` is licensed under the [Komorebi 2.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork
|
||||
of the [PolyForm Strict 1.0.0 license](https://polyformproject.org/licenses/strict/1.0.0). On a high level this means
|
||||
that you are free to do whatever you want with `komorebi` for personal use other than redistribution, or distribution of
|
||||
new works (i.e. hard-forks) based on the software.
|
||||
@@ -42,7 +42,7 @@ new works (i.e. hard-forks) based on the software.
|
||||
Anyone is free to make their own fork of `komorebi` with changes intended either for personal use or for integration
|
||||
back upstream via pull requests.
|
||||
|
||||
The [Komorebi 1.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (
|
||||
The [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (
|
||||
i.e. using `komorebi` at work).
|
||||
|
||||
## Sponsorship for Personal Use
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.bar.json",
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.bar.json",
|
||||
"monitor": 0,
|
||||
"font_family": "JetBrains Mono",
|
||||
"theme": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.json",
|
||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.json",
|
||||
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
|
||||
"window_hiding_behaviour": "Cloak",
|
||||
"cross_monitor_move_behaviour": "Insert",
|
||||
|
||||
46
docs/usage/focusing-windows.md
Normal file
46
docs/usage/focusing-windows.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Focusing Windows
|
||||
|
||||
Windows can be focused in a direction (left, down, up, right) using the [`komorebic focus`](../cli/focus.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + h : komorebic focus left
|
||||
alt + j : komorebic focus down
|
||||
alt + k : komorebic focus up
|
||||
alt + l : komorebic focus right
|
||||
```
|
||||
|
||||
Windows can be focused in a cycle direction (previous, next) using the [`komorebic cycle-focus`](../cli/cycle-focus.md)
|
||||
command.
|
||||
|
||||
```
|
||||
# example showing you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
|
||||
```
|
||||
|
||||
It is possible to attempt to focus the first window, on any workspace, matching an exe using the [
|
||||
`komorebic eager-focus`](../cli/eager-focus.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
win + 1 : komorebic eager-focus firefox.exe
|
||||
```
|
||||
|
||||
The window at the largest tile can be focused using the [`komorebic promote-focus`](../cli/promote-focus.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + return : komorebic promote-focus
|
||||
```
|
||||
|
||||
The behaviour when attempting to call `komorebic focus` when at the left or right edge of a monitor is determined by
|
||||
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
|
||||
|
||||
When set to `Workspace`, the next workspace on the same monitor will be focused.
|
||||
|
||||
When set to `Monitor`, the focused workspace on the next monitor in the given direction will be focused.
|
||||
59
docs/usage/focusing-workspaces.md
Normal file
59
docs/usage/focusing-workspaces.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Focusing Workspaces
|
||||
|
||||
Workspaces on the focused monitor can be focused by their index using the [
|
||||
`komorebic focus-workspace`](../cli/focus-workspace.md) command.
|
||||
|
||||
If this command is called with an index for a workspace which does not exist, that workspace, and all workspace indexes
|
||||
required to get to that workspace, will be created.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + 1 : komorebic focus-workspace 0
|
||||
alt + 2 : komorebic focus-workspace 1
|
||||
alt + 3 : komorebic focus-workspace 2
|
||||
```
|
||||
|
||||
Workspaces on the focused monitor can be focused in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-workspace`](../cli/cycle-workspace.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-workspace previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-workspace next # oem_6 is ]
|
||||
```
|
||||
|
||||
Workspaces on other monitors can be focused by both the monitor index and the workspace index using the [
|
||||
`komorebic focus-monitor-workspace`](../cli/focus-monitor-workspace.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + 1 : komorebic focus-monitor-workspace 0 0
|
||||
alt + 2 : komorebic focus-monitor-workspace 0 1
|
||||
alt + 3 : komorebic focus-monitor-workspace 1 0
|
||||
```
|
||||
|
||||
Workspaces on any monitor can be focused by their name (given that all workspace names across all monitors are unique)
|
||||
using the [`komorebic focus-named-workspace`](../cli/focus-named-workspace.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + c : komorebic focus-named-workspace coding
|
||||
```
|
||||
|
||||
Workspaces on all monitors can be set to the same index (emulating single workspaces which span across all monitors)
|
||||
using the [`komorebic focus-workspaces`](../cli/focus-workspaces.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + 1 : komorebic focus-workspaces 0
|
||||
alt + 2 : komorebic focus-workspaces 1
|
||||
alt + 3 : komorebic focus-workspaces 2
|
||||
```
|
||||
|
||||
The last focused workspace on the focused monitor can be re-focused using the [
|
||||
`komorebic focus-last-workspace`](../cli/focus-last-workspace.md) command.
|
||||
59
docs/usage/moving-windows-across-workspaces.md
Normal file
59
docs/usage/moving-windows-across-workspaces.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Moving Windows Across Workspaces
|
||||
|
||||
Windows can be moved to another workspace on the focused monitor using the [
|
||||
`komorebic move-to-workspace`](../cli/move-to-workspace.md) command. This command will also move your focus to the
|
||||
target workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + 1 : komorebic move-to-workspace 0
|
||||
alt + shift + 2 : komorebic move-to-workspace 1
|
||||
alt + shift + 3 : komorebic move-to-workspace 2
|
||||
```
|
||||
|
||||
Windows can be sent to another workspace on the focused monitor using the [
|
||||
`komorebic send-to-workspace`](../cli/send-to-workspace.md) command. This command will keep your focus on the origin
|
||||
workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + 1 : komorebic send-to-workspace 0
|
||||
alt + shift + 2 : komorebic send-to-workspace 1
|
||||
alt + shift + 3 : komorebic send-to-workspace 2
|
||||
```
|
||||
|
||||
Windows can be moved to another workspace on the focused monitor in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will also move your focus
|
||||
to the target workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-move-to-workspace previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-move-to-workspace next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows can be sent to another workspace on the focused monitor in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will keep your focus on
|
||||
the origin workspace.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-send-to-workspace previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-send-to-workspace next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows can be moved or sent to the focused workspace on a another monitor using the [
|
||||
`komorebic move-to-monitor`](../cli/move-to-monitor.md) and [`komorebic send-to-monitor`](../cli/send-to-monitor.md)
|
||||
commands.
|
||||
|
||||
Windows can be moved or sent to the focused workspace on a monitor in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-move-to-monitor`](../cli/cycle-move-to-monitor.md) and [
|
||||
`komorebic cycle-send-to-monitor`](../cli/cycle-send-to-monitor.md) commands.
|
||||
|
||||
Windows can be moved or sent to a named workspace on any monitor (given that all workspace names across all monitors are
|
||||
unique) using the [`komorebic move-to-named-workspace`](../cli/move-to-named-workspace.md) and [
|
||||
`komorebic send-to-named-workspace`](../cli/send-to-named-workspace.md) commands
|
||||
50
docs/usage/moving-windows.md
Normal file
50
docs/usage/moving-windows.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Moving Windows
|
||||
|
||||
Windows can be moved in a direction (left, down, up, right) using the [`komorebic move`](../cli/move.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + h : komorebic move left
|
||||
alt + shift + j : komorebic move down
|
||||
alt + shift + k : komorebic move up
|
||||
alt + shift + l : komorebic move right
|
||||
```
|
||||
|
||||
Windows can be moved in a cycle direction (previous, next) using the [`komorebic cycle-move`](../cli/cycle-move.md)
|
||||
command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-move previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-move next # oem_6 is ]
|
||||
```
|
||||
|
||||
The focused window can be moved to the largest tile using the [`komorebic promote`](../cli/promote.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + return : komorebic promote
|
||||
```
|
||||
|
||||
The behaviour when attempting to call `komorebic move` when at the left or right edge of a monitor is determined by
|
||||
the [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.
|
||||
|
||||
When set to `Workspace`, the focused window will be moved to the next workspace on the focused monitor in the given
|
||||
direction
|
||||
|
||||
When set to `Monitor`, the focused window will be moved to the focused workspace on the next monitor in the given
|
||||
direction.
|
||||
|
||||
The behaviour when calling `komorebic move` with `cross_boundary_behaviour` set to `Monitor` can be further refined with
|
||||
the [`cross_monitor_move_behaviour`](https://komorebi.lgug2z.com/schema#cross_monitor_move_behaviour) configuration
|
||||
option.
|
||||
|
||||
When set to `Swap`, the focused window will be swapped with the window at the corresponding edge of the adjacent monitor
|
||||
|
||||
When set to `Insert`, the focused window will be inserted into the focused workspace on the adjacent monitor.
|
||||
|
||||
When set to `NoOp`, the focused window will not be moved across a monitor boundary, though focusing across monitor
|
||||
boundaries will continue to function.
|
||||
52
docs/usage/stacking-windows.md
Normal file
52
docs/usage/stacking-windows.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Stacking Windows
|
||||
|
||||
Windows can be stacked in a direction (left, down, up, right) using the [`komorebic stack`](../cli/stack.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + left : komorebic stack left
|
||||
alt + down : komorebic stack down
|
||||
alt + up : komorebic stack up
|
||||
alt + right : komorebic stack right
|
||||
```
|
||||
|
||||
Windows can be popped from a stack using the [`komorebic unstack`](../cli/unstack.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + oem_1 : komorebic unstack # oem_1 is ;
|
||||
```
|
||||
|
||||
Windows in a stack can be focused in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-stack`](../cli/cycle-stack.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
|
||||
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows in a stack can have their positions in the stack moved in a cycle direction (previous, next) using the [
|
||||
`komorebic cycle-stack-index`](../cli/cycle-stack-index.md) command.
|
||||
|
||||
```
|
||||
# example showing how you might bind this command
|
||||
|
||||
alt + shift + oem_4 : komorebic cycle-stack-index previous # oem_4 is [
|
||||
alt + shift + oem_6 : komorebic cycle-stack-index next # oem_6 is ]
|
||||
```
|
||||
|
||||
Windows in a stack can be focused by their index in the stack using the [
|
||||
`komorebic focus-stack-window`](../cli/focus-stack-window.md) command.
|
||||
|
||||
All windows on the focused workspace can be combined into a single stack using the [
|
||||
`komorebic stack-all`](../cli/stack-all.md) command.
|
||||
|
||||
All windows in a focused stack can be popped using the [`komorebic unstack-all`](../cli/unstack-all.md) command.
|
||||
|
||||
It is possible to tell the window manager to stack the next opened window on top of the currently focused window by
|
||||
using the [
|
||||
`komorebic toggle-workspace-window-container-behaviour`](../cli/toggle-workspace-window-container-behaviour.md) command.
|
||||
17
justfile
17
justfile
@@ -19,22 +19,31 @@ install-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
|
||||
|
||||
install-target target:
|
||||
cargo +stable install --path {{ target }} --locked --no-default-features
|
||||
|
||||
install-targets-with-jsonschema *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just install-target-with-jsonschema $_ }
|
||||
|
||||
install-target-with-jsonschema target:
|
||||
cargo +stable install --path {{ target }} --locked
|
||||
|
||||
install:
|
||||
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
|
||||
install-with-jsonschema:
|
||||
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
|
||||
build-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
|
||||
|
||||
build-target target:
|
||||
cargo +stable build --package {{ target }} --locked --profile release-jeezy
|
||||
cargo +stable build --package {{ target }} --locked --release --no-default-features
|
||||
|
||||
build:
|
||||
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
|
||||
copy-target target:
|
||||
cp .\target\release-jeezy\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
||||
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
||||
|
||||
copy-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just copy-target $_ }
|
||||
@@ -46,7 +55,7 @@ copy:
|
||||
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
|
||||
run target:
|
||||
cargo +stable run --bin {{ target }} --locked
|
||||
cargo +stable run --bin {{ target }} --locked --no-default-features
|
||||
|
||||
warn target $RUST_LOG="warn":
|
||||
just run {{ target }}
|
||||
@@ -61,7 +70,7 @@ trace target $RUST_LOG="trace":
|
||||
just run {{ target }}
|
||||
|
||||
deadlock $RUST_LOG="trace":
|
||||
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
||||
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
|
||||
|
||||
docgen:
|
||||
cargo run --package komorebic -- docgen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-bar"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -9,6 +9,7 @@ edition = "2021"
|
||||
komorebi-client = { path = "../komorebi-client" }
|
||||
komorebi-themes = { path = "../komorebi-themes" }
|
||||
|
||||
chrono-tz = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
@@ -20,13 +21,14 @@ egui-phosphor = "0.9"
|
||||
font-loader = "0.11"
|
||||
hotwatch = { workspace = true }
|
||||
image = "0.25"
|
||||
netdev = "0.32"
|
||||
lazy_static = { workspace = true }
|
||||
netdev = "0.33"
|
||||
num = "0.4"
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
random_word = { version = "0.4", features = ["en"] }
|
||||
random_word = { version = "0.5", features = ["en"] }
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
starship-battery = "0.10"
|
||||
@@ -35,4 +37,9 @@ tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
|
||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
|
||||
windows-icons-fallback = { package = "windows-icons", git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
@@ -4,15 +4,15 @@ use crate::config::KomobarTheme;
|
||||
use crate::config::MonitorConfigOrIndex;
|
||||
use crate::config::Position;
|
||||
use crate::config::PositionConfig;
|
||||
use crate::komorebi::Komorebi;
|
||||
use crate::komorebi::KomorebiNotificationState;
|
||||
use crate::process_hwnd;
|
||||
use crate::render::Color32Ext;
|
||||
use crate::render::Grouping;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::render::RenderExt;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widget::WidgetConfig;
|
||||
use crate::widgets::komorebi::Komorebi;
|
||||
use crate::widgets::komorebi::KomorebiNotificationState;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use crate::widgets::widget::WidgetConfig;
|
||||
use crate::KomorebiEvent;
|
||||
use crate::BAR_HEIGHT;
|
||||
use crate::DEFAULT_PADDING;
|
||||
@@ -49,6 +49,7 @@ use komorebi_client::NotificationEvent;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_themes::catppuccin_egui;
|
||||
use komorebi_themes::Base16Value;
|
||||
use komorebi_themes::Base16Wrapper;
|
||||
use komorebi_themes::Catppuccin;
|
||||
use komorebi_themes::CatppuccinValue;
|
||||
use std::cell::RefCell;
|
||||
@@ -155,7 +156,7 @@ pub fn apply_theme(
|
||||
} => {
|
||||
ctx.set_style(base16.style());
|
||||
let base16_value = base16_value.unwrap_or_default();
|
||||
let accent = base16_value.color32(base16);
|
||||
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
@@ -165,6 +166,23 @@ pub fn apply_theme(
|
||||
|
||||
bg_color.replace(base16.background());
|
||||
}
|
||||
KomobarTheme::Custom {
|
||||
colours,
|
||||
accent: base16_value,
|
||||
} => {
|
||||
let background = colours.background();
|
||||
ctx.set_style(colours.style());
|
||||
let base16_value = base16_value.unwrap_or_default();
|
||||
let accent = base16_value.color32(Base16Wrapper::Custom(colours));
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
});
|
||||
|
||||
bg_color.replace(background);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply transparency_alpha
|
||||
@@ -431,11 +449,11 @@ impl Komobar {
|
||||
}
|
||||
|
||||
fn try_apply_theme(&mut self, ctx: &Context) {
|
||||
match self.config.theme {
|
||||
match &self.config.theme {
|
||||
Some(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
theme,
|
||||
theme.clone(),
|
||||
self.bg_color.clone(),
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
@@ -463,6 +481,26 @@ impl Komobar {
|
||||
match komorebi_client::StaticConfig::read(&config) {
|
||||
Ok(config) => {
|
||||
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(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
@@ -473,17 +511,6 @@ impl Komobar {
|
||||
self.render_config.clone(),
|
||||
);
|
||||
|
||||
let stack_accent = match theme {
|
||||
KomorebiTheme::Catppuccin {
|
||||
name, stack_border, ..
|
||||
} => stack_border
|
||||
.unwrap_or(CatppuccinValue::Green)
|
||||
.color32(name.as_theme()),
|
||||
KomorebiTheme::Base16 {
|
||||
name, stack_border, ..
|
||||
} => stack_border.unwrap_or(Base16Value::Base0B).color32(name),
|
||||
};
|
||||
|
||||
if let Some(state) = &self.komorebi_notification_state {
|
||||
state.borrow_mut().stack_accent = Some(stack_accent);
|
||||
}
|
||||
@@ -782,7 +809,7 @@ impl eframe::App for Komobar {
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
self.config.grouping,
|
||||
self.config.theme,
|
||||
self.config.theme.clone(),
|
||||
self.render_config.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use crate::render::Grouping;
|
||||
use crate::widget::WidgetConfig;
|
||||
use crate::widgets::widget::WidgetConfig;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use eframe::egui::Pos2;
|
||||
use eframe::egui::TextBuffer;
|
||||
use eframe::egui::Vec2;
|
||||
use komorebi_client::KomorebiTheme;
|
||||
use komorebi_client::Rect;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
/// The `komorebi.bar.json` configuration file reference for `v0.1.35`
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// The `komorebi.bar.json` configuration file reference for `v0.1.36`
|
||||
pub struct KomobarConfig {
|
||||
/// Bar height (default: 50)
|
||||
pub height: Option<f32>,
|
||||
@@ -136,7 +136,8 @@ impl KomobarConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PositionConfig {
|
||||
/// The desired starting position of the bar (0,0 = top left of the screen)
|
||||
#[serde(alias = "position")]
|
||||
@@ -146,13 +147,15 @@ pub struct PositionConfig {
|
||||
pub end: Option<Position>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct FrameConfig {
|
||||
/// Margin inside the painted frame
|
||||
pub inner_margin: Position,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum MonitorConfigOrIndex {
|
||||
/// The monitor index where you want the bar to show
|
||||
@@ -161,7 +164,8 @@ pub enum MonitorConfigOrIndex {
|
||||
MonitorConfig(MonitorConfig),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct MonitorConfig {
|
||||
/// Komorebi monitor index of the monitor on which to render the bar
|
||||
pub index: usize,
|
||||
@@ -172,7 +176,8 @@ pub struct MonitorConfig {
|
||||
pub type Padding = SpacingKind;
|
||||
pub type Margin = SpacingKind;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
// WARNING: To any developer messing with this code in the future: The order here matters!
|
||||
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
|
||||
@@ -223,20 +228,23 @@ impl SpacingKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct GroupedSpacingConfig {
|
||||
pub vertical: Option<GroupedSpacingOptions>,
|
||||
pub horizontal: Option<GroupedSpacingOptions>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum GroupedSpacingOptions {
|
||||
Symmetrical(f32),
|
||||
Split(f32, f32),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct IndividualSpacingConfig {
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
@@ -338,7 +346,8 @@ impl KomobarConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Position {
|
||||
/// X coordinate
|
||||
pub x: f32,
|
||||
@@ -364,7 +373,8 @@ impl From<Position> for Pos2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "palette")]
|
||||
pub enum KomobarTheme {
|
||||
/// A theme from catppuccin-egui
|
||||
@@ -379,6 +389,12 @@ pub enum KomobarTheme {
|
||||
name: komorebi_themes::Base16,
|
||||
accent: 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>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<KomorebiTheme> for KomobarTheme {
|
||||
@@ -396,11 +412,20 @@ impl From<KomorebiTheme> for KomobarTheme {
|
||||
name,
|
||||
accent: bar_accent,
|
||||
},
|
||||
KomorebiTheme::Custom {
|
||||
colours,
|
||||
bar_accent,
|
||||
..
|
||||
} => Self::Custom {
|
||||
colours,
|
||||
accent: bar_accent,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum LabelPrefix {
|
||||
/// Show no prefix
|
||||
None,
|
||||
@@ -412,7 +437,8 @@ pub enum LabelPrefix {
|
||||
IconAndText,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum DisplayFormat {
|
||||
/// Show only icon
|
||||
Icon,
|
||||
@@ -428,7 +454,8 @@ pub enum DisplayFormat {
|
||||
|
||||
macro_rules! extend_enum {
|
||||
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum $new_enum {
|
||||
// Add new variants
|
||||
$(
|
||||
@@ -460,12 +487,12 @@ extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum OriginalDisplayFormat {
|
||||
/// Show None Of The Things
|
||||
NoneOfTheThings,
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
mod bar;
|
||||
mod battery;
|
||||
mod config;
|
||||
mod cpu;
|
||||
mod date;
|
||||
mod keyboard;
|
||||
mod komorebi;
|
||||
mod komorebi_layout;
|
||||
mod media;
|
||||
mod memory;
|
||||
mod network;
|
||||
mod render;
|
||||
mod selected_frame;
|
||||
mod storage;
|
||||
mod time;
|
||||
mod ui;
|
||||
mod update;
|
||||
mod widget;
|
||||
mod widgets;
|
||||
|
||||
use crate::bar::Komobar;
|
||||
use crate::config::KomobarConfig;
|
||||
@@ -30,7 +18,6 @@ use hotwatch::Hotwatch;
|
||||
use image::RgbaImage;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_client::SubscribeOptions;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
@@ -60,7 +47,7 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
pub static BAR_HEIGHT: f32 = 50.0;
|
||||
pub static DEFAULT_PADDING: f32 = 10.0;
|
||||
|
||||
pub static ICON_CACHE: LazyLock<Mutex<HashMap<String, RgbaImage>>> =
|
||||
pub static ICON_CACHE: LazyLock<Mutex<HashMap<isize, RgbaImage>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -125,8 +112,9 @@ fn main() -> color_eyre::Result<()> {
|
||||
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
if opts.schema {
|
||||
let settings = SchemaSettings::default().with(|s| {
|
||||
let settings = schemars::gen::SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = false;
|
||||
s.option_add_null_type = false;
|
||||
s.inline_subschemas = true;
|
||||
@@ -349,7 +337,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
|
||||
let ctx_komorebi = cc.egui_ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
|
||||
let subscriber_name = format!("komorebi-bar-{}", random_word::get(random_word::Lang::En));
|
||||
|
||||
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
|
||||
filter_state_changes: true,
|
||||
|
||||
@@ -11,7 +11,6 @@ use eframe::egui::Margin;
|
||||
use eframe::egui::Shadow;
|
||||
use eframe::egui::TextStyle;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
@@ -20,7 +19,8 @@ use std::sync::Arc;
|
||||
|
||||
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum Grouping {
|
||||
/// No grouping is applied
|
||||
@@ -339,7 +339,8 @@ impl RenderConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct GroupingConfig {
|
||||
/// Styles for the grouping
|
||||
pub style: Option<GroupingStyle>,
|
||||
@@ -349,7 +350,8 @@ pub struct GroupingConfig {
|
||||
pub rounding: Option<RoundingConfig>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum GroupingStyle {
|
||||
#[serde(alias = "CtByte")]
|
||||
Default,
|
||||
@@ -369,7 +371,8 @@ pub enum GroupingStyle {
|
||||
DefaultWithGlowB0O1S2,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum RoundingConfig {
|
||||
/// All 4 corners are the same
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use starship_battery::units::ratio::percent;
|
||||
@@ -18,7 +17,8 @@ use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct BatteryConfig {
|
||||
/// Enable the Battery widget
|
||||
pub enable: bool,
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
@@ -17,7 +16,8 @@ use std::time::Instant;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CpuConfig {
|
||||
/// Enable the Cpu widget
|
||||
pub enable: bool,
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use chrono::Local;
|
||||
use chrono_tz::Tz;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
@@ -9,12 +11,14 @@ use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Custom format with additive modifiers for integer format specifiers
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CustomModifiers {
|
||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||
format: String,
|
||||
@@ -38,7 +42,7 @@ impl CustomModifiers {
|
||||
}
|
||||
|
||||
// get the strftime value of modifier
|
||||
let formatted_modifier = chrono::Local::now().format(modifier).to_string();
|
||||
let formatted_modifier = Local::now().format(modifier).to_string();
|
||||
|
||||
// find the gotten value in the original output
|
||||
if let Some(pos) = modified_output.find(&formatted_modifier) {
|
||||
@@ -55,7 +59,8 @@ impl CustomModifiers {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct DateConfig {
|
||||
/// Enable the Date widget
|
||||
pub enable: bool,
|
||||
@@ -63,19 +68,41 @@ pub struct DateConfig {
|
||||
pub format: DateFormat,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
///
|
||||
/// Use a custom format to display additional information, i.e.:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Date": {
|
||||
/// "enable": true,
|
||||
/// "format": { "Custom": "%D %Z (Tokyo)" },
|
||||
/// "timezone": "Asia/Tokyo"
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
pub timezone: Option<String>,
|
||||
}
|
||||
|
||||
impl From<DateConfig> for Date {
|
||||
fn from(value: DateConfig) -> Self {
|
||||
let data_refresh_interval = 1;
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
format: value.format,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
timezone: value.timezone,
|
||||
data_refresh_interval,
|
||||
last_state: String::new(),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum DateFormat {
|
||||
/// Month/Date/Year format (09/08/24)
|
||||
MonthDateYear,
|
||||
@@ -119,19 +146,46 @@ pub struct Date {
|
||||
pub enable: bool,
|
||||
pub format: DateFormat,
|
||||
label_prefix: LabelPrefix,
|
||||
timezone: Option<String>,
|
||||
data_refresh_interval: u64,
|
||||
last_state: String,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
fn output(&mut self) -> String {
|
||||
let formatted = chrono::Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string();
|
||||
let mut output = self.last_state.clone();
|
||||
let now = Instant::now();
|
||||
|
||||
// if custom modifiers are used, apply them
|
||||
match &self.format {
|
||||
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
|
||||
_ => formatted,
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
let formatted = match &self.timezone {
|
||||
Some(timezone) => match timezone.parse::<Tz>() {
|
||||
Ok(tz) => Local::now()
|
||||
.with_timezone(&tz)
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Err(_) => format!("Invalid timezone: {}", timezone),
|
||||
},
|
||||
None => Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
// if custom modifiers are used, apply them
|
||||
output = match &self.format {
|
||||
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
|
||||
_ => formatted,
|
||||
};
|
||||
|
||||
self.last_state.clone_from(&output);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
6
komorebi-bar/src/keyboard.rs → komorebi-bar/src/widgets/keyboard.rs
Executable file → Normal file
6
komorebi-bar/src/keyboard.rs → komorebi-bar/src/widgets/keyboard.rs
Executable file → Normal file
@@ -1,6 +1,6 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
@@ -8,7 +8,6 @@ use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
@@ -23,7 +22,8 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
|
||||
const ERROR_TEXT: &str = "Error";
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KeyboardConfig {
|
||||
/// Enable the Input widget
|
||||
pub enable: bool,
|
||||
@@ -2,16 +2,18 @@ use crate::bar::apply_theme;
|
||||
use crate::config::DisplayFormat;
|
||||
use crate::config::KomobarTheme;
|
||||
use crate::config::WorkspacesDisplayFormat;
|
||||
use crate::komorebi_layout::KomorebiLayout;
|
||||
use crate::render::Grouping;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::ui::CustomUi;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::komorebi_layout::KomorebiLayout;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use crate::ICON_CACHE;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_INDEX;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::vec2;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::ColorImage;
|
||||
use eframe::egui::Context;
|
||||
@@ -24,6 +26,7 @@ use eframe::egui::RichText;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::TextureHandle;
|
||||
use eframe::egui::TextureOptions;
|
||||
use eframe::egui::Ui;
|
||||
@@ -37,7 +40,6 @@ use komorebi_client::SocketMessage;
|
||||
use komorebi_client::Window;
|
||||
use komorebi_client::Workspace;
|
||||
use komorebi_client::WorkspaceLayer;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
@@ -47,7 +49,8 @@ use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiConfig {
|
||||
/// Configure the Workspaces widget
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
@@ -55,13 +58,17 @@ pub struct KomorebiConfig {
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
/// Configure the Workspace Layer widget
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
/// Configure the Focused Window widget
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
/// Configure the Focused Container widget
|
||||
#[serde(alias = "focused_window")]
|
||||
pub focused_container: Option<KomorebiFocusedContainerConfig>,
|
||||
/// Configure the Locked Container widget
|
||||
pub locked_container: Option<KomorebiLockedContainerConfig>,
|
||||
/// Configure the Configuration Switcher widget
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiWorkspacesConfig {
|
||||
/// Enable the Komorebi Workspaces widget
|
||||
pub enable: bool,
|
||||
@@ -71,7 +78,8 @@ pub struct KomorebiWorkspacesConfig {
|
||||
pub display: Option<WorkspacesDisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiLayoutConfig {
|
||||
/// Enable the Komorebi Layout widget
|
||||
pub enable: bool,
|
||||
@@ -81,7 +89,8 @@ pub struct KomorebiLayoutConfig {
|
||||
pub display: Option<DisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiWorkspaceLayerConfig {
|
||||
/// Enable the Komorebi Workspace Layer widget
|
||||
pub enable: bool,
|
||||
@@ -91,17 +100,30 @@ pub struct KomorebiWorkspaceLayerConfig {
|
||||
pub show_when_tiling: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiFocusedWindowConfig {
|
||||
/// Enable the Komorebi Focused Window widget
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiFocusedContainerConfig {
|
||||
/// Enable the Komorebi Focused Container widget
|
||||
pub enable: bool,
|
||||
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused window)
|
||||
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused container)
|
||||
pub show_icon: Option<bool>,
|
||||
/// Display format of the currently focused window
|
||||
/// Display format of the currently focused container
|
||||
pub display: Option<DisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiLockedContainerConfig {
|
||||
/// Enable the Komorebi Locked Container widget
|
||||
pub enable: bool,
|
||||
/// Display format of the current locked state
|
||||
pub display: Option<DisplayFormat>,
|
||||
/// Show the widget event if the layer is unlocked
|
||||
pub show_when_unlocked: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiConfigurationSwitcherConfig {
|
||||
/// Enable the Komorebi Configurations widget
|
||||
pub enable: bool,
|
||||
@@ -135,15 +157,19 @@ impl From<&KomorebiConfig> for Komorebi {
|
||||
.unwrap_or_default(),
|
||||
mouse_follows_focus: true,
|
||||
work_area_offset: None,
|
||||
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
|
||||
focused_container_information: (
|
||||
false,
|
||||
KomorebiNotificationStateContainerInformation::EMPTY,
|
||||
),
|
||||
stack_accent: None,
|
||||
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
|
||||
monitor_usr_idx_map: HashMap::new(),
|
||||
})),
|
||||
workspaces: value.workspaces,
|
||||
layout: value.layout.clone(),
|
||||
focused_window: value.focused_window,
|
||||
focused_container: value.focused_container,
|
||||
workspace_layer: value.workspace_layer,
|
||||
locked_container: value.locked_container,
|
||||
configuration_switcher,
|
||||
}
|
||||
}
|
||||
@@ -154,8 +180,9 @@ pub struct Komorebi {
|
||||
pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>,
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
pub focused_container: Option<KomorebiFocusedContainerConfig>,
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
pub locked_container: Option<KomorebiLockedContainerConfig>,
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
|
||||
@@ -173,9 +200,10 @@ impl BarWidget for Komorebi {
|
||||
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
for (i, (ws, containers, _)) in
|
||||
for (i, (ws, containers, _, should_show)) in
|
||||
komorebi_notification_state.workspaces.iter().enumerate()
|
||||
{
|
||||
if *should_show {
|
||||
let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
|
||||
|
||||
if SelectableFrame::new(
|
||||
@@ -297,6 +325,7 @@ impl BarWidget for Komorebi {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -313,7 +342,7 @@ impl BarWidget for Komorebi {
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
|
||||
.map(|(_, _, layer)| layer);
|
||||
.map(|(_, _, layer, _)| layer);
|
||||
|
||||
if let Some(layer) = layer {
|
||||
if (layer_config.show_when_tiling.unwrap_or_default()
|
||||
@@ -330,7 +359,7 @@ impl BarWidget for Komorebi {
|
||||
if matches!(layer, WorkspaceLayer::Tiling) {
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(size, Sense::hover());
|
||||
let color = ui.style().visuals.text_color();
|
||||
let color = ctx.style().visuals.selection.stroke.color;
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
let mut rect = response.rect;
|
||||
let corner =
|
||||
@@ -360,7 +389,7 @@ impl BarWidget for Komorebi {
|
||||
} else {
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(size, Sense::hover());
|
||||
let color = ui.style().visuals.text_color();
|
||||
let color = ctx.style().visuals.selection.stroke.color;
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
let mut rect = response.rect;
|
||||
let corner =
|
||||
@@ -398,6 +427,7 @@ impl BarWidget for Komorebi {
|
||||
|
||||
if layer_frame.clicked()
|
||||
&& komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::MouseFollowsFocus(false),
|
||||
SocketMessage::ToggleWorkspaceLayer,
|
||||
SocketMessage::MouseFollowsFocus(
|
||||
@@ -496,18 +526,85 @@ impl BarWidget for Komorebi {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(focused_window) = self.focused_window {
|
||||
if focused_window.enable {
|
||||
if let Some(locked_container_config) = self.locked_container {
|
||||
if locked_container_config.enable {
|
||||
let is_locked = komorebi_notification_state.focused_container_information.0;
|
||||
|
||||
if locked_container_config
|
||||
.show_when_unlocked
|
||||
.unwrap_or_default()
|
||||
|| is_locked
|
||||
{
|
||||
let titles = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.1
|
||||
.titles;
|
||||
|
||||
if !titles.is_empty() {
|
||||
let display_format = locked_container_config
|
||||
.display
|
||||
.unwrap_or(DisplayFormat::Text);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
if display_format != DisplayFormat::Text {
|
||||
if is_locked {
|
||||
egui_phosphor::regular::LOCK_KEY.to_string()
|
||||
} else {
|
||||
egui_phosphor::regular::LOCK_SIMPLE_OPEN.to_string()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
if display_format != DisplayFormat::Icon {
|
||||
layout_job.append(
|
||||
if is_locked { "Locked" } else { "Unlocked" },
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
&& komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::ToggleLock,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send ToggleLock");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(focused_container_config) = self.focused_container {
|
||||
if focused_container_config.enable {
|
||||
let titles = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.1
|
||||
.titles;
|
||||
|
||||
if !titles.is_empty() {
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
let icons = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.focused_container_information.1
|
||||
.icons;
|
||||
let focused_window_idx = komorebi_notification_state
|
||||
.focused_container_information
|
||||
.focused_container_information.1
|
||||
.focused_window_idx;
|
||||
|
||||
let iter = titles.iter().zip(icons.iter());
|
||||
@@ -515,13 +612,13 @@ impl BarWidget for Komorebi {
|
||||
|
||||
for (i, (title, icon)) in iter.enumerate() {
|
||||
let selected = i == focused_window_idx && len != 1;
|
||||
let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() };
|
||||
let text_color = if selected { ctx.style().visuals.selection.stroke.color } else { ui.style().visuals.text_color() };
|
||||
|
||||
if SelectableFrame::new(selected)
|
||||
.show(ui, |ui| {
|
||||
// handle legacy setting
|
||||
let format = focused_window.display.unwrap_or(
|
||||
if focused_window.show_icon.unwrap_or(false) {
|
||||
let format = focused_container_config.display.unwrap_or(
|
||||
if focused_container_config.show_icon.unwrap_or(false) {
|
||||
DisplayFormat::IconAndText
|
||||
} else {
|
||||
DisplayFormat::Text
|
||||
@@ -621,9 +718,10 @@ pub struct KomorebiNotificationState {
|
||||
String,
|
||||
Vec<(bool, KomorebiNotificationStateContainerInformation)>,
|
||||
WorkspaceLayer,
|
||||
bool,
|
||||
)>,
|
||||
pub selected_workspace: String,
|
||||
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
||||
pub focused_container_information: (bool, KomorebiNotificationStateContainerInformation),
|
||||
pub layout: KomorebiLayout,
|
||||
pub hide_empty_workspaces: bool,
|
||||
pub mouse_follows_focus: bool,
|
||||
@@ -689,7 +787,7 @@ impl KomorebiNotificationState {
|
||||
SocketMessage::Theme(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
KomobarTheme::from(*theme),
|
||||
bg_color,
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
@@ -736,42 +834,41 @@ impl KomorebiNotificationState {
|
||||
true
|
||||
};
|
||||
|
||||
if should_show {
|
||||
workspaces.push((
|
||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||
if show_all_icons {
|
||||
let mut containers = vec![];
|
||||
let mut has_monocle = false;
|
||||
workspaces.push((
|
||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||
if show_all_icons {
|
||||
let mut containers = vec![];
|
||||
let mut has_monocle = false;
|
||||
|
||||
// add monocle container
|
||||
if let Some(container) = ws.monocle_container() {
|
||||
containers.push((true, container.into()));
|
||||
has_monocle = true;
|
||||
}
|
||||
// add monocle container
|
||||
if let Some(container) = ws.monocle_container() {
|
||||
containers.push((true, container.into()));
|
||||
has_monocle = true;
|
||||
}
|
||||
|
||||
// add all tiled windows
|
||||
for (i, container) in ws.containers().iter().enumerate() {
|
||||
containers.push((
|
||||
!has_monocle && i == ws.focused_container_idx(),
|
||||
container.into(),
|
||||
));
|
||||
}
|
||||
// add all tiled windows
|
||||
for (i, container) in ws.containers().iter().enumerate() {
|
||||
containers.push((
|
||||
!has_monocle && i == ws.focused_container_idx(),
|
||||
container.into(),
|
||||
));
|
||||
}
|
||||
|
||||
// add all floating windows
|
||||
for floating_window in ws.floating_windows() {
|
||||
containers.push((
|
||||
!has_monocle && floating_window.is_focused(),
|
||||
floating_window.into(),
|
||||
));
|
||||
}
|
||||
// add all floating windows
|
||||
for floating_window in ws.floating_windows() {
|
||||
containers.push((
|
||||
!has_monocle && floating_window.is_focused(),
|
||||
floating_window.into(),
|
||||
));
|
||||
}
|
||||
|
||||
containers
|
||||
} else {
|
||||
vec![(true, ws.into())]
|
||||
},
|
||||
ws.layer().to_owned(),
|
||||
));
|
||||
}
|
||||
containers
|
||||
} else {
|
||||
vec![(true, ws.into())]
|
||||
},
|
||||
ws.layer().to_owned(),
|
||||
should_show,
|
||||
));
|
||||
}
|
||||
|
||||
self.workspaces = workspaces;
|
||||
@@ -792,7 +889,12 @@ impl KomorebiNotificationState {
|
||||
};
|
||||
}
|
||||
|
||||
self.focused_container_information = (&monitor.workspaces()[focused_workspace_idx]).into();
|
||||
let focused_workspace = &monitor.workspaces()[focused_workspace_idx];
|
||||
let is_focused = focused_workspace
|
||||
.locked_containers()
|
||||
.contains(&focused_workspace.focused_container_idx());
|
||||
|
||||
self.focused_container_information = (is_focused, focused_workspace.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,11 +933,16 @@ impl From<&Container> for KomorebiNotificationStateContainerInformation {
|
||||
for window in windows {
|
||||
let mut icon_cache = ICON_CACHE.lock().unwrap();
|
||||
let mut update_cache = false;
|
||||
let exe = window.exe().unwrap_or_default();
|
||||
let hwnd = window.hwnd;
|
||||
|
||||
match icon_cache.get(&exe) {
|
||||
match icon_cache.get(&hwnd) {
|
||||
None => {
|
||||
icons.push(windows_icons::get_icon_by_process_id(window.process_id()));
|
||||
let icon = match windows_icons::get_icon_by_hwnd(window.hwnd) {
|
||||
None => windows_icons_fallback::get_icon_by_process_id(window.process_id()),
|
||||
Some(icon) => Some(icon),
|
||||
};
|
||||
|
||||
icons.push(icon);
|
||||
update_cache = true;
|
||||
}
|
||||
Some(icon) => {
|
||||
@@ -845,7 +952,7 @@ impl From<&Container> for KomorebiNotificationStateContainerInformation {
|
||||
|
||||
if update_cache {
|
||||
if let Some(Some(icon)) = icons.last() {
|
||||
icon_cache.insert(exe, icon.clone());
|
||||
icon_cache.insert(hwnd, icon.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,11 +974,16 @@ impl From<&Window> for KomorebiNotificationStateContainerInformation {
|
||||
let mut icon_cache = ICON_CACHE.lock().unwrap();
|
||||
let mut update_cache = false;
|
||||
let mut icons = vec![];
|
||||
let exe = value.exe().unwrap_or_default();
|
||||
let hwnd = value.hwnd;
|
||||
|
||||
match icon_cache.get(&exe) {
|
||||
match icon_cache.get(&hwnd) {
|
||||
None => {
|
||||
icons.push(windows_icons::get_icon_by_process_id(value.process_id()));
|
||||
let icon = match windows_icons::get_icon_by_hwnd(hwnd) {
|
||||
None => windows_icons_fallback::get_icon_by_process_id(value.process_id()),
|
||||
Some(icon) => Some(icon),
|
||||
};
|
||||
|
||||
icons.push(icon);
|
||||
update_cache = true;
|
||||
}
|
||||
Some(icon) => {
|
||||
@@ -881,7 +993,7 @@ impl From<&Window> for KomorebiNotificationStateContainerInformation {
|
||||
|
||||
if update_cache {
|
||||
if let Some(Some(icon)) = icons.last() {
|
||||
icon_cache.insert(exe, icon.clone());
|
||||
icon_cache.insert(hwnd, icon.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::config::DisplayFormat;
|
||||
use crate::komorebi::KomorebiLayoutConfig;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::komorebi::KomorebiLayoutConfig;
|
||||
use eframe::egui::vec2;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
@@ -14,7 +14,6 @@ use eframe::egui::StrokeKind;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use komorebi_client::SocketMessage;
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
@@ -23,7 +22,8 @@ use serde_json::from_str;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum KomorebiLayout {
|
||||
Default(komorebi_client::DefaultLayout),
|
||||
@@ -105,12 +105,22 @@ impl KomorebiLayout {
|
||||
}
|
||||
}
|
||||
KomorebiLayout::Monocle => {
|
||||
if komorebi_client::send_message(&SocketMessage::ToggleMonocle).is_err() {
|
||||
if komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::ToggleMonocle,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send message to komorebi: ToggleMonocle");
|
||||
}
|
||||
}
|
||||
KomorebiLayout::Floating => {
|
||||
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err() {
|
||||
if komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::ToggleTiling,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send message to komorebi: ToggleTiling");
|
||||
}
|
||||
}
|
||||
@@ -241,7 +251,7 @@ impl KomorebiLayout {
|
||||
let layout_frame = SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
|
||||
self.show_icon(false, font_id.clone(), ctx, ui);
|
||||
self.show_icon(true, font_id.clone(), ctx, ui);
|
||||
}
|
||||
|
||||
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::ui::CustomUi;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
@@ -10,13 +10,13 @@ use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct MediaConfig {
|
||||
/// Enable the Media widget
|
||||
pub enable: bool,
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
@@ -17,7 +16,8 @@ use std::time::Instant;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct MemoryConfig {
|
||||
/// Enable the Memory widget
|
||||
pub enable: bool,
|
||||
13
komorebi-bar/src/widgets/mod.rs
Normal file
13
komorebi-bar/src/widgets/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub mod battery;
|
||||
pub mod cpu;
|
||||
pub mod date;
|
||||
pub mod keyboard;
|
||||
pub mod komorebi;
|
||||
mod komorebi_layout;
|
||||
pub mod media;
|
||||
pub mod memory;
|
||||
pub mod network;
|
||||
pub mod storage;
|
||||
pub mod time;
|
||||
pub mod update;
|
||||
pub mod widget;
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
@@ -9,7 +9,6 @@ use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use num_derive::FromPrimitive;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
@@ -18,7 +17,8 @@ use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::Networks;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct NetworkConfig {
|
||||
/// Enable the Network widget
|
||||
pub enable: bool,
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
@@ -16,7 +15,8 @@ use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::Disks;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct StorageConfig {
|
||||
/// Enable the Storage widget
|
||||
pub enable: bool,
|
||||
@@ -2,7 +2,10 @@ use crate::bar::Alignment;
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use chrono::Local;
|
||||
use chrono::NaiveTime;
|
||||
use chrono_tz::Tz;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
@@ -14,11 +17,61 @@ use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use schemars::JsonSchema;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
lazy_static! {
|
||||
static ref TIME_RANGES: Vec<(&'static str, NaiveTime)> = {
|
||||
vec![
|
||||
(
|
||||
egui_phosphor::regular::MOON,
|
||||
NaiveTime::from_hms_opt(0, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::ALARM,
|
||||
NaiveTime::from_hms_opt(6, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::BREAD,
|
||||
NaiveTime::from_hms_opt(6, 1, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::BARBELL,
|
||||
NaiveTime::from_hms_opt(6, 30, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::COFFEE,
|
||||
NaiveTime::from_hms_opt(8, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::CLOCK,
|
||||
NaiveTime::from_hms_opt(8, 30, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::HAMBURGER,
|
||||
NaiveTime::from_hms_opt(12, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::CLOCK_AFTERNOON,
|
||||
NaiveTime::from_hms_opt(12, 30, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::FORK_KNIFE,
|
||||
NaiveTime::from_hms_opt(18, 0, 0).expect("invalid"),
|
||||
),
|
||||
(
|
||||
egui_phosphor::regular::MOON_STARS,
|
||||
NaiveTime::from_hms_opt(18, 30, 0).expect("invalid"),
|
||||
),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct TimeConfig {
|
||||
/// Enable the Time widget
|
||||
pub enable: bool,
|
||||
@@ -26,19 +79,46 @@ pub struct TimeConfig {
|
||||
pub format: TimeFormat,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
///
|
||||
/// Use a custom format to display additional information, i.e.:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Time": {
|
||||
/// "enable": true,
|
||||
/// "format": { "Custom": "%T %Z (Tokyo)" },
|
||||
/// "timezone": "Asia/Tokyo"
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
pub timezone: Option<String>,
|
||||
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)
|
||||
pub changing_icon: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<TimeConfig> for Time {
|
||||
fn from(value: TimeConfig) -> Self {
|
||||
// using 1 second made the widget look "less accurate" and lagging (especially having multiple with seconds).
|
||||
// This is still better than getting an update every frame
|
||||
let data_refresh_interval = 500;
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
format: value.format,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
timezone: value.timezone,
|
||||
changing_icon: value.changing_icon.unwrap_or_default(),
|
||||
data_refresh_interval_millis: data_refresh_interval,
|
||||
last_state: TimeOutput::new(),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_millis(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum TimeFormat {
|
||||
/// Twelve-hour format (with seconds)
|
||||
TwelveHour,
|
||||
@@ -82,20 +162,94 @@ impl TimeFormat {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TimeOutput {
|
||||
label: String,
|
||||
icon: String,
|
||||
}
|
||||
|
||||
impl TimeOutput {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
label: String::new(),
|
||||
icon: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Time {
|
||||
pub enable: bool,
|
||||
pub format: TimeFormat,
|
||||
label_prefix: LabelPrefix,
|
||||
timezone: Option<String>,
|
||||
changing_icon: bool,
|
||||
data_refresh_interval_millis: u64,
|
||||
last_state: TimeOutput,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
fn output(&mut self) -> String {
|
||||
chrono::Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string()
|
||||
fn output(&mut self) -> TimeOutput {
|
||||
let mut output = self.last_state.clone();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(self.last_updated)
|
||||
> Duration::from_millis(self.data_refresh_interval_millis)
|
||||
{
|
||||
let (formatted, current_time) = match &self.timezone {
|
||||
Some(timezone) => match timezone.parse::<Tz>() {
|
||||
Ok(tz) => {
|
||||
let dt = Local::now().with_timezone(&tz);
|
||||
(
|
||||
dt.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Some(dt.time()),
|
||||
)
|
||||
}
|
||||
Err(_) => (format!("Invalid timezone: {:?}", timezone), None),
|
||||
},
|
||||
None => {
|
||||
let dt = Local::now();
|
||||
(
|
||||
dt.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Some(dt.time()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if current_time.is_none() {
|
||||
return TimeOutput {
|
||||
label: formatted,
|
||||
icon: egui_phosphor::regular::WARNING_CIRCLE.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let current_range = match &self.changing_icon {
|
||||
true => TIME_RANGES
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|&(_, start)| current_time.unwrap() > *start)
|
||||
.cloned(),
|
||||
false => None,
|
||||
}
|
||||
.unwrap_or((egui_phosphor::regular::CLOCK, NaiveTime::default()));
|
||||
|
||||
output = TimeOutput {
|
||||
label: formatted,
|
||||
icon: current_range.0.to_string(),
|
||||
};
|
||||
|
||||
self.last_state.clone_from(&output);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn paint_binary_circle(
|
||||
@@ -109,36 +263,37 @@ impl Time {
|
||||
let full_height = size;
|
||||
let height = full_height / 4.0;
|
||||
let width = height;
|
||||
let offset = height / 2.0 - height / 8.0;
|
||||
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
|
||||
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
|
||||
let color = ctx.style().visuals.text_color();
|
||||
|
||||
let c = response.rect.center();
|
||||
let r = height / 2.0 - 0.5;
|
||||
|
||||
if number == 1 || number == 3 || number == 5 || number == 7 || number == 9 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r, color);
|
||||
} else {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r / 2.5, color);
|
||||
}
|
||||
|
||||
if number == 2 || number == 3 || number == 6 || number == 7 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r, color);
|
||||
} else {
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r / 2.5, color);
|
||||
}
|
||||
|
||||
if number == 4 || number == 5 || number == 6 || number == 7 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r, color);
|
||||
} else if max_power > 2 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r / 2.5, color);
|
||||
}
|
||||
|
||||
if number == 8 || number == 9 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r, color);
|
||||
} else if max_power > 3 {
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r / 2.5, color);
|
||||
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r / 2.5, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +308,10 @@ impl Time {
|
||||
let full_height = size;
|
||||
let height = full_height / 4.0;
|
||||
let width = height * 1.5;
|
||||
let offset = height / 2.0 - height / 8.0;
|
||||
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
|
||||
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
|
||||
let color = ctx.style().visuals.text_color();
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
|
||||
@@ -173,22 +329,25 @@ impl Time {
|
||||
};
|
||||
|
||||
if max_power == 2 {
|
||||
let mut rect = response.rect.shrink(stroke.width);
|
||||
let mut rect = response
|
||||
.rect
|
||||
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
|
||||
rect.set_height(rect.height() - height * 2.0);
|
||||
rect = rect.translate(Vec2::new(0.0, height * 2.0));
|
||||
rect = rect.translate(Vec2::new(0.0, height * 2.0 + offset));
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
} else if max_power == 3 {
|
||||
let mut rect = response.rect.shrink(stroke.width);
|
||||
let mut rect = response
|
||||
.rect
|
||||
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
|
||||
rect.set_height(rect.height() - height);
|
||||
rect = rect.translate(Vec2::new(0.0, height));
|
||||
rect = rect.translate(Vec2::new(0.0, height + offset));
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
} else {
|
||||
painter.rect_stroke(
|
||||
response.rect.shrink(stroke.width),
|
||||
round_all,
|
||||
stroke,
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
let mut rect = response
|
||||
.rect
|
||||
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
|
||||
rect = rect.translate(Vec2::new(0.0, 0.0 + offset));
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
}
|
||||
|
||||
let mut rect_bin = response.rect;
|
||||
@@ -197,7 +356,7 @@ impl Time {
|
||||
if number == 1 || number == 5 || number == 9 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 3.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 3.0 + offset * 2.0)),
|
||||
round_bottom,
|
||||
color,
|
||||
);
|
||||
@@ -205,7 +364,7 @@ impl Time {
|
||||
if number == 2 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
|
||||
if max_power == 2 {
|
||||
round_top
|
||||
} else {
|
||||
@@ -217,7 +376,7 @@ impl Time {
|
||||
if number == 3 {
|
||||
rect_bin.set_height(height * 2.0);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
|
||||
round_bottom,
|
||||
color,
|
||||
);
|
||||
@@ -225,7 +384,7 @@ impl Time {
|
||||
if number == 4 || number == 5 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
|
||||
if max_power == 3 {
|
||||
round_top
|
||||
} else {
|
||||
@@ -237,7 +396,7 @@ impl Time {
|
||||
if number == 6 {
|
||||
rect_bin.set_height(height * 2.0);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
|
||||
if max_power == 3 {
|
||||
round_top
|
||||
} else {
|
||||
@@ -249,7 +408,7 @@ impl Time {
|
||||
if number == 7 {
|
||||
rect_bin.set_height(height * 3.0);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(0.0, height)),
|
||||
rect_bin.translate(Vec2::new(stroke.width, height + offset * 2.0)),
|
||||
if max_power == 3 {
|
||||
round_all
|
||||
} else {
|
||||
@@ -260,7 +419,11 @@ impl Time {
|
||||
}
|
||||
if number == 8 || number == 9 {
|
||||
rect_bin.set_height(height);
|
||||
painter.rect_filled(rect_bin.translate(Vec2::new(0.0, 0.0)), round_top, color);
|
||||
painter.rect_filled(
|
||||
rect_bin.translate(Vec2::new(stroke.width, 0.0 + offset * 2.0)),
|
||||
round_top,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,15 +432,13 @@ impl BarWidget for Time {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let mut output = self.output();
|
||||
if !output.is_empty() {
|
||||
let use_binary_circle = output.starts_with('c');
|
||||
let use_binary_rectangle = output.starts_with('r');
|
||||
if !output.label.is_empty() {
|
||||
let use_binary_circle = output.label.starts_with('c');
|
||||
let use_binary_rectangle = output.label.starts_with('r');
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::CLOCK.to_string()
|
||||
}
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => output.icon,
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
@@ -286,12 +447,12 @@ impl BarWidget for Time {
|
||||
);
|
||||
|
||||
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
||||
output.insert_str(0, "TIME: ");
|
||||
output.label.insert_str(0, "TIME: ");
|
||||
}
|
||||
|
||||
if !use_binary_circle && !use_binary_rectangle {
|
||||
layout_job.append(
|
||||
&output,
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
@@ -314,9 +475,9 @@ impl BarWidget for Time {
|
||||
|
||||
if use_binary_circle || use_binary_rectangle {
|
||||
let ordered_output = if is_reversed {
|
||||
output.chars().rev().collect()
|
||||
output.label.chars().rev().collect()
|
||||
} else {
|
||||
output
|
||||
output.label
|
||||
};
|
||||
|
||||
for (section_index, section) in
|
||||
@@ -1,21 +1,21 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct UpdateConfig {
|
||||
/// Enable the Update widget
|
||||
pub enable: bool,
|
||||
@@ -1,29 +1,28 @@
|
||||
use crate::battery::Battery;
|
||||
use crate::battery::BatteryConfig;
|
||||
use crate::cpu::Cpu;
|
||||
use crate::cpu::CpuConfig;
|
||||
use crate::date::Date;
|
||||
use crate::date::DateConfig;
|
||||
use crate::keyboard::Keyboard;
|
||||
use crate::keyboard::KeyboardConfig;
|
||||
use crate::komorebi::Komorebi;
|
||||
use crate::komorebi::KomorebiConfig;
|
||||
use crate::media::Media;
|
||||
use crate::media::MediaConfig;
|
||||
use crate::memory::Memory;
|
||||
use crate::memory::MemoryConfig;
|
||||
use crate::network::Network;
|
||||
use crate::network::NetworkConfig;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::storage::Storage;
|
||||
use crate::storage::StorageConfig;
|
||||
use crate::time::Time;
|
||||
use crate::time::TimeConfig;
|
||||
use crate::update::Update;
|
||||
use crate::update::UpdateConfig;
|
||||
use crate::widgets::battery::Battery;
|
||||
use crate::widgets::battery::BatteryConfig;
|
||||
use crate::widgets::cpu::Cpu;
|
||||
use crate::widgets::cpu::CpuConfig;
|
||||
use crate::widgets::date::Date;
|
||||
use crate::widgets::date::DateConfig;
|
||||
use crate::widgets::keyboard::Keyboard;
|
||||
use crate::widgets::keyboard::KeyboardConfig;
|
||||
use crate::widgets::komorebi::Komorebi;
|
||||
use crate::widgets::komorebi::KomorebiConfig;
|
||||
use crate::widgets::media::Media;
|
||||
use crate::widgets::media::MediaConfig;
|
||||
use crate::widgets::memory::Memory;
|
||||
use crate::widgets::memory::MemoryConfig;
|
||||
use crate::widgets::network::Network;
|
||||
use crate::widgets::network::NetworkConfig;
|
||||
use crate::widgets::storage::Storage;
|
||||
use crate::widgets::storage::StorageConfig;
|
||||
use crate::widgets::time::Time;
|
||||
use crate::widgets::time::TimeConfig;
|
||||
use crate::widgets::update::Update;
|
||||
use crate::widgets::update::UpdateConfig;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Ui;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -31,7 +30,8 @@ pub trait BarWidget {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum WidgetConfig {
|
||||
Battery(BatteryConfig),
|
||||
Cpu(CpuConfig),
|
||||
@@ -72,7 +72,7 @@ impl WidgetConfig {
|
||||
WidgetConfig::Komorebi(config) => {
|
||||
config.workspaces.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.layout.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.focused_window.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.focused_container.as_ref().is_some_and(|w| w.enable)
|
||||
|| config
|
||||
.configuration_switcher
|
||||
.as_ref()
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -10,3 +10,7 @@ komorebi = { path = "../komorebi" }
|
||||
|
||||
uds_windows = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["komorebi/schemars"]
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
pub use komorebi::animation::prefix::AnimationPrefix;
|
||||
pub use komorebi::animation::PerAnimationPrefixConfig;
|
||||
pub use komorebi::asc::ApplicationSpecificConfiguration;
|
||||
pub use komorebi::colour::Colour;
|
||||
pub use komorebi::colour::Rgb;
|
||||
pub use komorebi::border_manager::BorderInfo;
|
||||
pub use komorebi::config_generation::ApplicationConfiguration;
|
||||
pub use komorebi::config_generation::IdWithIdentifier;
|
||||
pub use komorebi::config_generation::IdWithIdentifierAndComment;
|
||||
@@ -28,6 +27,7 @@ pub use komorebi::core::CustomLayout;
|
||||
pub use komorebi::core::CycleDirection;
|
||||
pub use komorebi::core::DefaultLayout;
|
||||
pub use komorebi::core::Direction;
|
||||
pub use komorebi::core::FloatingLayerBehaviour;
|
||||
pub use komorebi::core::FocusFollowsMouseImplementation;
|
||||
pub use komorebi::core::HidingBehaviour;
|
||||
pub use komorebi::core::Layout;
|
||||
@@ -45,14 +45,17 @@ pub use komorebi::core::WindowKind;
|
||||
pub use komorebi::monitor::Monitor;
|
||||
pub use komorebi::monitor_reconciliator::MonitorNotification;
|
||||
pub use komorebi::ring::Ring;
|
||||
pub use komorebi::win32_display_data;
|
||||
pub use komorebi::window::Window;
|
||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||
pub use komorebi::workspace::Workspace;
|
||||
pub use komorebi::workspace::WorkspaceGlobals;
|
||||
pub use komorebi::workspace::WorkspaceLayer;
|
||||
pub use komorebi::AnimationsConfig;
|
||||
pub use komorebi::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;
|
||||
@@ -60,6 +63,7 @@ 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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-gui"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -10,7 +10,7 @@ komorebi-client = { path = "../komorebi-client" }
|
||||
|
||||
eframe = { workspace = true }
|
||||
egui_extras = { workspace = true }
|
||||
random_word = { version = "0.4", features = ["en"] }
|
||||
random_word = { version = "0.5", features = ["en"] }
|
||||
serde_json = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
@@ -41,7 +41,9 @@ struct BorderColours {
|
||||
single: Color32,
|
||||
stack: Color32,
|
||||
monocle: Color32,
|
||||
floating: Color32,
|
||||
unfocused: Color32,
|
||||
unfocused_locked: Color32,
|
||||
}
|
||||
|
||||
struct BorderConfig {
|
||||
@@ -101,7 +103,7 @@ impl From<&komorebi_client::Workspace> for WorkspaceConfig {
|
||||
let name = value
|
||||
.name()
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
|
||||
.unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());
|
||||
|
||||
Self {
|
||||
layout,
|
||||
@@ -154,7 +156,9 @@ impl KomorebiGui {
|
||||
single: colour32(global_state.border_colours.single),
|
||||
stack: colour32(global_state.border_colours.stack),
|
||||
monocle: colour32(global_state.border_colours.monocle),
|
||||
floating: colour32(global_state.border_colours.floating),
|
||||
unfocused: colour32(global_state.border_colours.unfocused),
|
||||
unfocused_locked: colour32(global_state.border_colours.unfocused_locked),
|
||||
};
|
||||
|
||||
let border_config = BorderConfig {
|
||||
@@ -377,6 +381,22 @@ impl eframe::App for KomorebiGui {
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Floating", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.border_config.border_colours.floating,
|
||||
Alpha::Opaque,
|
||||
) {
|
||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
||||
WindowKind::Floating,
|
||||
self.border_config.border_colours.floating.r() as u32,
|
||||
self.border_config.border_colours.floating.g() as u32,
|
||||
self.border_config.border_colours.floating.b() as u32,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Unfocused", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
@@ -391,6 +411,22 @@ impl eframe::App for KomorebiGui {
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Unfocused Locked", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.border_config.border_colours.unfocused_locked,
|
||||
Alpha::Opaque,
|
||||
) {
|
||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
||||
WindowKind::UnfocusedLocked,
|
||||
self.border_config.border_colours.unfocused_locked.r() as u32,
|
||||
self.border_config.border_colours.unfocused_locked.g() as u32,
|
||||
self.border_config.border_colours.unfocused_locked.b() as u32,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi-themes"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -8,7 +8,13 @@ base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev
|
||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
|
||||
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
|
||||
eframe = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_variant = "0.1"
|
||||
strum = { workspace = true }
|
||||
hex_color = { version = "3", features = ["serde"] }
|
||||
flavours = { git = "https://github.com/LGUG2Z/flavours", version = "0.7.2" }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
use hex_color::HexColor;
|
||||
use komorebi_themes::Color32;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::gen::SchemaGenerator;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::schema::InstanceType;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::schema::Schema;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::schema::SchemaObject;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::Color32;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum Colour {
|
||||
/// Colour represented as RGB
|
||||
@@ -52,9 +57,10 @@ impl From<Colour> for Color32 {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Hex(HexColor);
|
||||
pub struct Hex(pub HexColor);
|
||||
|
||||
impl JsonSchema for Hex {
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for Hex {
|
||||
fn schema_name() -> String {
|
||||
String::from("Hex")
|
||||
}
|
||||
@@ -78,7 +84,8 @@ impl From<Colour> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u32,
|
||||
77
komorebi-themes/src/generator.rs
Normal file
77
komorebi-themes/src/generator.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use crate::colour::Colour;
|
||||
use crate::colour::Hex;
|
||||
use crate::Base16ColourPalette;
|
||||
use hex_color::HexColor;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ThemeVariant {
|
||||
#[default]
|
||||
Dark,
|
||||
Light,
|
||||
}
|
||||
|
||||
impl Display for ThemeVariant {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ThemeVariant::Dark => write!(f, "dark"),
|
||||
ThemeVariant::Light => write!(f, "light"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThemeVariant> for flavours::operations::generate::Mode {
|
||||
fn from(value: ThemeVariant) -> Self {
|
||||
match value {
|
||||
ThemeVariant::Dark => Self::Dark,
|
||||
ThemeVariant::Light => Self::Light,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_base16_palette(
|
||||
image_path: &Path,
|
||||
variant: ThemeVariant,
|
||||
) -> Result<Base16ColourPalette, hex_color::ParseHexColorError> {
|
||||
Base16ColourPalette::try_from(
|
||||
&flavours::operations::generate::generate(image_path, variant.into(), false)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
impl TryFrom<&VecDeque<String>> for Base16ColourPalette {
|
||||
type Error = hex_color::ParseHexColorError;
|
||||
|
||||
fn try_from(value: &VecDeque<String>) -> Result<Self, Self::Error> {
|
||||
let fixed = value.iter().map(|s| format!("#{s}")).collect::<Vec<_>>();
|
||||
if fixed.len() != 16 {
|
||||
return Err(hex_color::ParseHexColorError::Empty);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
base_00: Colour::Hex(Hex(HexColor::parse(&fixed[0])?)),
|
||||
base_01: Colour::Hex(Hex(HexColor::parse(&fixed[1])?)),
|
||||
base_02: Colour::Hex(Hex(HexColor::parse(&fixed[2])?)),
|
||||
base_03: Colour::Hex(Hex(HexColor::parse(&fixed[3])?)),
|
||||
base_04: Colour::Hex(Hex(HexColor::parse(&fixed[4])?)),
|
||||
base_05: Colour::Hex(Hex(HexColor::parse(&fixed[5])?)),
|
||||
base_06: Colour::Hex(Hex(HexColor::parse(&fixed[6])?)),
|
||||
base_07: Colour::Hex(Hex(HexColor::parse(&fixed[7])?)),
|
||||
base_08: Colour::Hex(Hex(HexColor::parse(&fixed[8])?)),
|
||||
base_09: Colour::Hex(Hex(HexColor::parse(&fixed[9])?)),
|
||||
base_0a: Colour::Hex(Hex(HexColor::parse(&fixed[10])?)),
|
||||
base_0b: Colour::Hex(Hex(HexColor::parse(&fixed[11])?)),
|
||||
base_0c: Colour::Hex(Hex(HexColor::parse(&fixed[12])?)),
|
||||
base_0d: Colour::Hex(Hex(HexColor::parse(&fixed[13])?)),
|
||||
base_0e: Colour::Hex(Hex(HexColor::parse(&fixed[14])?)),
|
||||
base_0f: Colour::Hex(Hex(HexColor::parse(&fixed[15])?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,32 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
pub mod colour;
|
||||
mod generator;
|
||||
|
||||
pub use generator::generate_base16_palette;
|
||||
pub use generator::ThemeVariant;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::colour::Colour;
|
||||
pub use base16_egui_themes::Base16;
|
||||
pub use catppuccin_egui;
|
||||
use eframe::egui::style::Selection;
|
||||
use eframe::egui::style::WidgetVisuals;
|
||||
use eframe::egui::style::Widgets;
|
||||
pub use eframe::egui::Color32;
|
||||
use eframe::egui::Shadow;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::Style;
|
||||
use eframe::egui::Visuals;
|
||||
use serde_variant::to_variant_name;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Theme {
|
||||
/// A theme from catppuccin-egui
|
||||
@@ -25,6 +39,140 @@ pub enum Theme {
|
||||
name: Base16,
|
||||
accent: Option<Base16Value>,
|
||||
},
|
||||
/// A custom base16 palette
|
||||
Custom {
|
||||
palette: Box<Base16ColourPalette>,
|
||||
accent: Option<Base16Value>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct Base16ColourPalette {
|
||||
pub base_00: Colour,
|
||||
pub base_01: Colour,
|
||||
pub base_02: Colour,
|
||||
pub base_03: Colour,
|
||||
pub base_04: Colour,
|
||||
pub base_05: Colour,
|
||||
pub base_06: Colour,
|
||||
pub base_07: Colour,
|
||||
pub base_08: Colour,
|
||||
pub base_09: Colour,
|
||||
pub base_0a: Colour,
|
||||
pub base_0b: Colour,
|
||||
pub base_0c: Colour,
|
||||
pub base_0d: Colour,
|
||||
pub base_0e: Colour,
|
||||
pub base_0f: Colour,
|
||||
}
|
||||
|
||||
impl Base16ColourPalette {
|
||||
pub fn background(self) -> Color32 {
|
||||
self.base_01.into()
|
||||
}
|
||||
pub fn style(self) -> Style {
|
||||
let original = Style::default();
|
||||
Style {
|
||||
visuals: Visuals {
|
||||
widgets: Widgets {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: self.base_01.into(),
|
||||
weak_bg_fill: self.base_01.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.widgets.noninteractive.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_05.into(),
|
||||
..original.visuals.widgets.noninteractive.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.noninteractive
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
|
||||
..original.visuals.widgets.inactive.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_05.into(),
|
||||
..original.visuals.widgets.inactive.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.inactive
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_03.into(),
|
||||
..original.visuals.widgets.hovered.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.hovered.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.hovered
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_03.into(),
|
||||
..original.visuals.widgets.hovered.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.hovered.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.active
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: self.base_01.into(),
|
||||
weak_bg_fill: self.base_01.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.widgets.open.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.open.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.open
|
||||
},
|
||||
},
|
||||
selection: Selection {
|
||||
bg_fill: self.base_02.into(),
|
||||
stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.selection.stroke
|
||||
},
|
||||
},
|
||||
hyperlink_color: self.base_08.into(),
|
||||
faint_bg_color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
|
||||
extreme_bg_color: self.base_00.into(),
|
||||
code_bg_color: self.base_02.into(),
|
||||
warn_fg_color: self.base_0c.into(),
|
||||
error_fg_color: self.base_0b.into(),
|
||||
window_shadow: Shadow {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
|
||||
..original.visuals.window_shadow
|
||||
},
|
||||
window_fill: self.base_01.into(),
|
||||
window_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.window_stroke
|
||||
},
|
||||
panel_fill: self.base_01.into(),
|
||||
popup_shadow: Shadow {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
|
||||
..original.visuals.popup_shadow
|
||||
},
|
||||
..original.visuals
|
||||
},
|
||||
..original
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
@@ -45,6 +193,7 @@ impl Theme {
|
||||
.to_string()
|
||||
})
|
||||
.collect(),
|
||||
Theme::Custom { .. } => vec!["Custom".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,25 +219,50 @@ pub enum Base16Value {
|
||||
Base0F,
|
||||
}
|
||||
|
||||
pub enum Base16Wrapper {
|
||||
Base16(Base16),
|
||||
Custom(Box<Base16ColourPalette>),
|
||||
}
|
||||
|
||||
impl Base16Value {
|
||||
pub fn color32(&self, theme: Base16) -> Color32 {
|
||||
match self {
|
||||
Base16Value::Base00 => theme.base00(),
|
||||
Base16Value::Base01 => theme.base01(),
|
||||
Base16Value::Base02 => theme.base02(),
|
||||
Base16Value::Base03 => theme.base03(),
|
||||
Base16Value::Base04 => theme.base04(),
|
||||
Base16Value::Base05 => theme.base05(),
|
||||
Base16Value::Base06 => theme.base06(),
|
||||
Base16Value::Base07 => theme.base07(),
|
||||
Base16Value::Base08 => theme.base08(),
|
||||
Base16Value::Base09 => theme.base09(),
|
||||
Base16Value::Base0A => theme.base0a(),
|
||||
Base16Value::Base0B => theme.base0b(),
|
||||
Base16Value::Base0C => theme.base0c(),
|
||||
Base16Value::Base0D => theme.base0d(),
|
||||
Base16Value::Base0E => theme.base0e(),
|
||||
Base16Value::Base0F => theme.base0f(),
|
||||
pub fn color32(&self, theme: Base16Wrapper) -> Color32 {
|
||||
match theme {
|
||||
Base16Wrapper::Base16(theme) => match self {
|
||||
Base16Value::Base00 => theme.base00(),
|
||||
Base16Value::Base01 => theme.base01(),
|
||||
Base16Value::Base02 => theme.base02(),
|
||||
Base16Value::Base03 => theme.base03(),
|
||||
Base16Value::Base04 => theme.base04(),
|
||||
Base16Value::Base05 => theme.base05(),
|
||||
Base16Value::Base06 => theme.base06(),
|
||||
Base16Value::Base07 => theme.base07(),
|
||||
Base16Value::Base08 => theme.base08(),
|
||||
Base16Value::Base09 => theme.base09(),
|
||||
Base16Value::Base0A => theme.base0a(),
|
||||
Base16Value::Base0B => theme.base0b(),
|
||||
Base16Value::Base0C => theme.base0c(),
|
||||
Base16Value::Base0D => theme.base0d(),
|
||||
Base16Value::Base0E => theme.base0e(),
|
||||
Base16Value::Base0F => theme.base0f(),
|
||||
},
|
||||
Base16Wrapper::Custom(colours) => match self {
|
||||
Base16Value::Base00 => colours.base_00.into(),
|
||||
Base16Value::Base01 => colours.base_01.into(),
|
||||
Base16Value::Base02 => colours.base_02.into(),
|
||||
Base16Value::Base03 => colours.base_03.into(),
|
||||
Base16Value::Base04 => colours.base_04.into(),
|
||||
Base16Value::Base05 => colours.base_05.into(),
|
||||
Base16Value::Base06 => colours.base_06.into(),
|
||||
Base16Value::Base07 => colours.base_07.into(),
|
||||
Base16Value::Base08 => colours.base_08.into(),
|
||||
Base16Value::Base09 => colours.base_09.into(),
|
||||
Base16Value::Base0A => colours.base_0a.into(),
|
||||
Base16Value::Base0B => colours.base_0b.into(),
|
||||
Base16Value::Base0C => colours.base_0c.into(),
|
||||
Base16Value::Base0D => colours.base_0d.into(),
|
||||
Base16Value::Base0E => colours.base_0e.into(),
|
||||
Base16Value::Base0F => colours.base_0f.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebi"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
description = "A tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
@@ -19,7 +19,6 @@ ctrlc = { version = "3", features = ["termination"] }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
getset = "0.1"
|
||||
hex_color = { version = "3", features = ["serde"] }
|
||||
hotwatch = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
miow = "0.6"
|
||||
@@ -29,7 +28,7 @@ os_info = "3.10"
|
||||
parking_lot = "0.12"
|
||||
paste = { workspace = true }
|
||||
regex = "1"
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
@@ -55,6 +54,9 @@ shadow-rs = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
deadlock_detection = ["parking_lot/deadlock_detection"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use color_eyre::Result;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -13,7 +11,8 @@ use super::ANIMATION_DURATION_GLOBAL;
|
||||
use super::ANIMATION_FPS;
|
||||
use super::ANIMATION_MANAGER;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct AnimationEngine;
|
||||
|
||||
impl AnimationEngine {
|
||||
|
||||
@@ -18,11 +18,12 @@ pub mod prefix;
|
||||
pub mod render_dispatcher;
|
||||
pub use render_dispatcher::RenderDispatcher;
|
||||
pub mod style;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum PerAnimationPrefixConfig<T> {
|
||||
Prefix(HashMap<AnimationPrefix, T>),
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AnimationPrefix {
|
||||
|
||||
@@ -3,8 +3,6 @@ use crate::border_manager::RenderTarget;
|
||||
use crate::border_manager::WindowKind;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::border_manager::FOCUS_STATE;
|
||||
use crate::border_manager::RENDER_TARGETS;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::Rect;
|
||||
@@ -53,18 +51,22 @@ use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::LoadCursorW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetCursor;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
use windows_core::BOOL;
|
||||
use windows_core::PCWSTR;
|
||||
@@ -114,6 +116,8 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Border {
|
||||
pub hwnd: isize,
|
||||
pub id: String,
|
||||
pub monitor_idx: Option<usize>,
|
||||
pub render_target: OnceLock<RenderTarget>,
|
||||
pub tracking_hwnd: isize,
|
||||
pub window_rect: Rect,
|
||||
@@ -130,6 +134,8 @@ impl From<isize> for Border {
|
||||
fn from(value: isize) -> Self {
|
||||
Self {
|
||||
hwnd: value,
|
||||
id: String::new(),
|
||||
monitor_idx: None,
|
||||
render_target: OnceLock::new(),
|
||||
tracking_hwnd: 0,
|
||||
window_rect: Rect::default(),
|
||||
@@ -149,7 +155,11 @@ impl Border {
|
||||
HWND(windows_api::as_ptr!(self.hwnd))
|
||||
}
|
||||
|
||||
pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result<Self> {
|
||||
pub fn create(
|
||||
id: &str,
|
||||
tracking_hwnd: isize,
|
||||
monitor_idx: usize,
|
||||
) -> color_eyre::Result<Box<Self>> {
|
||||
let name: Vec<u16> = format!("komoborder-{id}\0").encode_utf16().collect();
|
||||
let class_name = PCWSTR(name.as_ptr());
|
||||
|
||||
@@ -168,9 +178,12 @@ impl Border {
|
||||
let (border_sender, border_receiver) = mpsc::channel();
|
||||
|
||||
let instance = h_module.0 as isize;
|
||||
let container_id = id.to_owned();
|
||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
||||
let mut border = Self {
|
||||
hwnd: 0,
|
||||
id: container_id,
|
||||
monitor_idx: Some(monitor_idx),
|
||||
render_target: OnceLock::new(),
|
||||
tracking_hwnd,
|
||||
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
|
||||
@@ -183,12 +196,15 @@ impl Border {
|
||||
brushes: HashMap::new(),
|
||||
};
|
||||
|
||||
let border_pointer = std::ptr::addr_of!(border);
|
||||
let border_pointer = &raw mut border;
|
||||
let hwnd =
|
||||
WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance, border_pointer)?;
|
||||
|
||||
border.hwnd = hwnd;
|
||||
border_sender.send(border_pointer as isize)?;
|
||||
let boxed = unsafe {
|
||||
(*border_pointer).hwnd = hwnd;
|
||||
Box::from_raw(border_pointer)
|
||||
};
|
||||
border_sender.send(boxed)?;
|
||||
|
||||
let mut msg: MSG = MSG::default();
|
||||
|
||||
@@ -207,8 +223,7 @@ impl Border {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let border_ref = border_receiver.recv()?;
|
||||
let border = unsafe { &mut *(border_ref as *mut Border) };
|
||||
let mut border = border_receiver.recv()?;
|
||||
|
||||
// I have literally no idea, apparently this is to get rid of the black pixels
|
||||
// around the edges of rounded corners? @lukeyou05 borrowed this from PowerToys
|
||||
@@ -257,6 +272,7 @@ impl Border {
|
||||
WindowKind::Monocle,
|
||||
WindowKind::Unfocused,
|
||||
WindowKind::Floating,
|
||||
WindowKind::UnfocusedLocked,
|
||||
] {
|
||||
let color = window_kind_colour(window_kind);
|
||||
let color = D2D1_COLOR_F {
|
||||
@@ -292,17 +308,13 @@ impl Border {
|
||||
}
|
||||
};
|
||||
|
||||
let mut render_targets = RENDER_TARGETS.lock();
|
||||
render_targets.insert(border.hwnd, RenderTarget(render_target));
|
||||
Ok(border.clone())
|
||||
Ok(border)
|
||||
},
|
||||
Err(error) => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy(&self) -> color_eyre::Result<()> {
|
||||
let mut render_targets = RENDER_TARGETS.lock();
|
||||
render_targets.remove(&self.hwnd);
|
||||
WindowsApi::close_window(self.hwnd)
|
||||
}
|
||||
|
||||
@@ -329,6 +341,16 @@ impl Border {
|
||||
) -> LRESULT {
|
||||
unsafe {
|
||||
match message {
|
||||
WM_SETCURSOR => match LoadCursorW(None, IDC_ARROW) {
|
||||
Ok(cursor) => {
|
||||
SetCursor(Some(cursor));
|
||||
LRESULT(0)
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!("{error}");
|
||||
LRESULT(1)
|
||||
}
|
||||
},
|
||||
WM_CREATE => {
|
||||
let mut border_pointer: *mut Border =
|
||||
GetWindowLongPtrW(window, GWLP_USERDATA) as _;
|
||||
@@ -361,7 +383,7 @@ impl Border {
|
||||
return LRESULT(0);
|
||||
}
|
||||
|
||||
let reference_hwnd = lparam.0;
|
||||
let reference_hwnd = (*border_pointer).tracking_hwnd;
|
||||
|
||||
let old_rect = (*border_pointer).window_rect;
|
||||
let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default();
|
||||
@@ -475,11 +497,6 @@ impl Border {
|
||||
});
|
||||
|
||||
// Get window kind and color
|
||||
(*border_pointer).window_kind = FOCUS_STATE
|
||||
.lock()
|
||||
.get(&(window.0 as isize))
|
||||
.copied()
|
||||
.unwrap_or(WindowKind::Unfocused);
|
||||
let window_kind = (*border_pointer).window_kind;
|
||||
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
|
||||
render_target.BeginDraw();
|
||||
|
||||
@@ -5,10 +5,8 @@ use crate::core::BorderImplementation;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::WindowKind;
|
||||
use crate::ring::Ring;
|
||||
use crate::windows_api;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::Colour;
|
||||
use crate::Rgb;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use border::border_hwnds;
|
||||
@@ -17,9 +15,10 @@ use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use komorebi_themes::colour::Colour;
|
||||
use komorebi_themes::colour::Rgb;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::hash_map::Entry;
|
||||
@@ -32,6 +31,7 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use strum::Display;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
|
||||
|
||||
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
|
||||
@@ -47,6 +47,8 @@ lazy_static! {
|
||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
|
||||
pub static ref UNFOCUSED: AtomicU32 =
|
||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(128, 128, 128))));
|
||||
pub static ref UNFOCUSED_LOCKED: AtomicU32 =
|
||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(158, 8, 8))));
|
||||
pub static ref MONOCLE: AtomicU32 =
|
||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153))));
|
||||
pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66))));
|
||||
@@ -55,11 +57,8 @@ lazy_static! {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref BORDERS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
|
||||
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
|
||||
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, Border>> = Mutex::new(HashMap::new());
|
||||
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
|
||||
static ref RENDER_TARGETS: Mutex<HashMap<isize, RenderTarget>> = Mutex::new(HashMap::new());
|
||||
static ref BORDER_STATE: Mutex<HashMap<String, Box<Border>>> = Mutex::new(HashMap::new());
|
||||
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, String>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -76,6 +75,18 @@ impl Deref for RenderTarget {
|
||||
|
||||
pub struct Notification(pub Option<isize>);
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BorderInfo {
|
||||
pub border_hwnd: isize,
|
||||
pub window_kind: WindowKind,
|
||||
}
|
||||
|
||||
impl BorderInfo {
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
HWND(windows_api::as_ptr!(self.border_hwnd))
|
||||
}
|
||||
}
|
||||
|
||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||
|
||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||
@@ -90,8 +101,13 @@ fn event_rx() -> Receiver<Notification> {
|
||||
channel().1.clone()
|
||||
}
|
||||
|
||||
pub fn window_border(hwnd: isize) -> Option<Border> {
|
||||
WINDOWS_BORDERS.lock().get(&hwnd).cloned()
|
||||
pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
|
||||
WINDOWS_BORDERS.lock().get(&hwnd).and_then(|id| {
|
||||
BORDER_STATE.lock().get(id).map(|b| BorderInfo {
|
||||
border_hwnd: b.hwnd,
|
||||
window_kind: b.window_kind,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_notification(hwnd: Option<isize>) {
|
||||
@@ -107,15 +123,11 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
for (_, border) in borders.iter() {
|
||||
let _ = border.destroy();
|
||||
for (_, border) in borders.drain() {
|
||||
let _ = destroy_border(border);
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
BORDERS_MONITORS.lock().clear();
|
||||
WINDOWS_BORDERS.lock().clear();
|
||||
FOCUS_STATE.lock().clear();
|
||||
RENDER_TARGETS.lock().clear();
|
||||
|
||||
let mut remaining_hwnds = vec![];
|
||||
|
||||
@@ -128,7 +140,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
|
||||
|
||||
for hwnd in remaining_hwnds {
|
||||
let _ = Border::from(hwnd).destroy();
|
||||
let _ = destroy_border(Box::new(Border::from(hwnd)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +150,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
fn window_kind_colour(focus_kind: WindowKind) -> u32 {
|
||||
match focus_kind {
|
||||
WindowKind::Unfocused => UNFOCUSED.load(Ordering::Relaxed),
|
||||
WindowKind::UnfocusedLocked => UNFOCUSED_LOCKED.load(Ordering::Relaxed),
|
||||
WindowKind::Single => FOCUSED.load(Ordering::Relaxed),
|
||||
WindowKind::Stack => STACK.load(Ordering::Relaxed),
|
||||
WindowKind::Monocle => MONOCLE.load(Ordering::Relaxed),
|
||||
@@ -218,7 +231,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let window_kind = if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
if ws.locked_containers().contains(&idx) {
|
||||
WindowKind::UnfocusedLocked
|
||||
} else {
|
||||
WindowKind::Unfocused
|
||||
}
|
||||
} else if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
@@ -277,10 +294,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
// border.
|
||||
(fw != &foreground_window
|
||||
&& window_border(*fw)
|
||||
.map(|b| b.window_kind == WindowKind::Floating)
|
||||
.unwrap_or_default())
|
||||
.is_some_and(|b| b.window_kind == WindowKind::Floating))
|
||||
});
|
||||
|
||||
// when the focused window has an `Unfocused` border kind, usually this happens if
|
||||
// we focus an admin window and then refocus the previously focused window. For
|
||||
// komorebi it will have the same state has before, however the previously focused
|
||||
// window changed its border to unfocused so now we need to update it again.
|
||||
if !should_process_notification
|
||||
&& window_border(notification.0.unwrap_or_default())
|
||||
.is_some_and(|b| b.window_kind == WindowKind::Unfocused)
|
||||
{
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
if !should_process_notification && switch_focus_to_from_floating_window {
|
||||
should_process_notification = true;
|
||||
}
|
||||
@@ -299,26 +326,19 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||
let mut windows_borders = WINDOWS_BORDERS.lock();
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
|
||||
// If borders are disabled
|
||||
if !BORDER_ENABLED.load_consume()
|
||||
// Or if the wm is paused
|
||||
|| is_paused
|
||||
// Or if we are handling an alt-tab across workspaces
|
||||
|| ALT_TAB_HWND.load().is_some()
|
||||
{
|
||||
// Destroy the borders we know about
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
for (_, border) in borders.drain() {
|
||||
destroy_border(border)?;
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
borders_monitors.clear();
|
||||
windows_borders.clear();
|
||||
focus_state.clear();
|
||||
|
||||
previous_is_paused = is_paused;
|
||||
continue 'receiver;
|
||||
@@ -333,8 +353,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, _| true,
|
||||
)?;
|
||||
@@ -345,12 +363,16 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
let mut new_border = false;
|
||||
let border = match borders.entry(monocle.id().clone()) {
|
||||
let focused_window_hwnd =
|
||||
monocle.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
||||
let id = monocle.id().clone();
|
||||
let border = match borders.entry(id.clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(
|
||||
monocle.id(),
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd,
|
||||
focused_window_hwnd,
|
||||
monitor_idx,
|
||||
) {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
@@ -366,32 +388,44 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
WindowKind::Monocle
|
||||
};
|
||||
border.window_kind = new_focus_state;
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
|
||||
let reference_hwnd =
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd;
|
||||
// Update the borders tracking_hwnd in case it changed and remove the
|
||||
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
|
||||
if border.tracking_hwnd != focused_window_hwnd {
|
||||
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {
|
||||
// Only remove the border from `windows_borders` if it
|
||||
// still corresponds to the same border, if doesn't then
|
||||
// it means it was already updated by another border for
|
||||
// that window and in that case we don't want to remove it.
|
||||
if previous == &id {
|
||||
windows_borders.remove(&border.tracking_hwnd);
|
||||
}
|
||||
}
|
||||
border.tracking_hwnd = focused_window_hwnd;
|
||||
if !WindowsApi::is_window_visible(border.hwnd) {
|
||||
WindowsApi::restore_window(border.hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(reference_hwnd)?;
|
||||
// Update the border's monitor idx in case it changed
|
||||
border.monitor_idx = Some(monitor_idx);
|
||||
|
||||
let rect = WindowsApi::window_rect(focused_window_hwnd)?;
|
||||
border.window_rect = rect;
|
||||
|
||||
if new_border {
|
||||
border.set_position(&rect, reference_hwnd)?;
|
||||
border.set_position(&rect, focused_window_hwnd)?;
|
||||
}
|
||||
|
||||
border.invalidate();
|
||||
|
||||
borders_monitors.insert(monocle.id().clone(), monitor_idx);
|
||||
windows_borders.insert(
|
||||
monocle.focused_window().cloned().unwrap_or_default().hwnd,
|
||||
border.clone(),
|
||||
);
|
||||
windows_borders.insert(focused_window_hwnd, id);
|
||||
|
||||
let border_hwnd = border.hwnd;
|
||||
// Remove all borders on this monitor except monocle
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, b| border_hwnd != b.hwnd,
|
||||
)?;
|
||||
@@ -410,8 +444,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, _| true,
|
||||
)?;
|
||||
@@ -434,56 +466,22 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|id, _| !container_and_floating_window_ids.contains(id),
|
||||
)?;
|
||||
|
||||
'containers: for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// In case this container is a stack we need to check it's
|
||||
// unfocused windows to remove any attached border
|
||||
let is_stack = c.windows().len() > 1;
|
||||
if is_stack {
|
||||
let focused_window_idx = c.focused_window_idx();
|
||||
let potential_stacked_border_handles = c
|
||||
.windows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(i, w)| {
|
||||
if i != focused_window_idx {
|
||||
windows_borders.get(&w.hwnd).map(|b| b.hwnd)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !potential_stacked_border_handles.is_empty() {
|
||||
tracing::debug!(
|
||||
"purging stacked borders: {:?}",
|
||||
potential_stacked_border_handles
|
||||
);
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, b| potential_stacked_border_handles.contains(&b.hwnd),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let focused_window_hwnd =
|
||||
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
||||
let id = c.id().clone();
|
||||
|
||||
// Get the border entry for this container from the map or create one
|
||||
let mut new_border = false;
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
let border = match borders.entry(id.clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(c.id(), focused_window_hwnd)
|
||||
if let Ok(border) =
|
||||
Border::create(c.id(), focused_window_hwnd, monitor_idx)
|
||||
{
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
@@ -493,107 +491,84 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
let mut last_focus_state = None;
|
||||
let last_focus_state = border.window_kind;
|
||||
|
||||
let new_focus_state = if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
|| focused_window_hwnd != foreground_window
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
if ws.locked_containers().contains(&idx) {
|
||||
WindowKind::UnfocusedLocked
|
||||
} else {
|
||||
WindowKind::Unfocused
|
||||
}
|
||||
} else if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
WindowKind::Single
|
||||
};
|
||||
|
||||
border.window_kind = new_focus_state;
|
||||
|
||||
last_focus_state = focus_state.get(&border.hwnd).copied();
|
||||
|
||||
// If this container's border was previously tracking a different
|
||||
// window, then we need to destroy that border and create a new one
|
||||
// tracking the correct window.
|
||||
// Update the borders `tracking_hwnd` in case it changed and remove the
|
||||
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
|
||||
if border.tracking_hwnd != focused_window_hwnd {
|
||||
// Create new border
|
||||
if let Ok(b) = Border::create(
|
||||
c.id(),
|
||||
c.focused_window().copied().unwrap_or_default().hwnd,
|
||||
) {
|
||||
// Destroy previously stacked border window and remove its hwnd
|
||||
// and tracking_hwnd.
|
||||
border.destroy()?;
|
||||
focus_state.remove(&border.hwnd);
|
||||
if let Some(previous) =
|
||||
windows_borders.get(&border.tracking_hwnd)
|
||||
{
|
||||
// Only remove the border from `windows_borders` if it
|
||||
// still is the same border, if it isn't then it means it
|
||||
// was already updated by another border for that window
|
||||
// and in that case we don't want to remove it.
|
||||
if previous.hwnd == border.hwnd {
|
||||
windows_borders.remove(&border.tracking_hwnd);
|
||||
}
|
||||
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {
|
||||
// Only remove the border from `windows_borders` if it
|
||||
// still corresponds to the same border, if doesn't then
|
||||
// it means it was already updated by another border for
|
||||
// that window and in that case we don't want to remove it.
|
||||
if previous == &id {
|
||||
windows_borders.remove(&border.tracking_hwnd);
|
||||
}
|
||||
|
||||
// Replace with new border
|
||||
new_border = true;
|
||||
*border = b;
|
||||
} else {
|
||||
continue 'monitors;
|
||||
}
|
||||
border.tracking_hwnd = focused_window_hwnd;
|
||||
if !WindowsApi::is_window_visible(border.hwnd) {
|
||||
WindowsApi::restore_window(border.hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the border's monitor idx in case it changed
|
||||
border.monitor_idx = Some(monitor_idx);
|
||||
|
||||
// avoid getting into a thread restart loop if we try to look up
|
||||
// rect info for a window that has been destroyed by the time
|
||||
// we get here
|
||||
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
|
||||
Ok(rect) => rect,
|
||||
Err(_) => {
|
||||
remove_border(
|
||||
c.id(),
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
)?;
|
||||
remove_border(c.id(), &mut borders, &mut windows_borders)?;
|
||||
continue 'containers;
|
||||
}
|
||||
};
|
||||
border.window_rect = rect;
|
||||
|
||||
let layer_changed = previous_layer != workspace_layer;
|
||||
|
||||
let should_invalidate = match last_focus_state {
|
||||
None => true,
|
||||
Some(last_focus_state) => {
|
||||
(last_focus_state != new_focus_state) || layer_changed
|
||||
}
|
||||
};
|
||||
|
||||
if new_border || should_invalidate {
|
||||
border.set_position(&rect, focused_window_hwnd)?;
|
||||
}
|
||||
let should_invalidate = new_border
|
||||
|| (last_focus_state != new_focus_state)
|
||||
|| layer_changed;
|
||||
|
||||
if should_invalidate {
|
||||
border.set_position(&rect, focused_window_hwnd)?;
|
||||
border.invalidate();
|
||||
}
|
||||
|
||||
borders_monitors.insert(c.id().clone(), monitor_idx);
|
||||
windows_borders.insert(
|
||||
c.focused_window().cloned().unwrap_or_default().hwnd,
|
||||
border.clone(),
|
||||
);
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
windows_borders.insert(focused_window_hwnd, id);
|
||||
}
|
||||
|
||||
{
|
||||
for window in ws.floating_windows() {
|
||||
let mut new_border = false;
|
||||
let border = match borders.entry(window.hwnd.to_string()) {
|
||||
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)
|
||||
{
|
||||
if let Ok(border) = Border::create(
|
||||
&window.hwnd.to_string(),
|
||||
window.hwnd,
|
||||
monitor_idx,
|
||||
) {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
} else {
|
||||
@@ -602,39 +577,34 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
let mut last_focus_state = None;
|
||||
let mut new_focus_state = WindowKind::Unfocused;
|
||||
let last_focus_state = border.window_kind;
|
||||
|
||||
if foreground_window == window.hwnd {
|
||||
new_focus_state = WindowKind::Floating;
|
||||
}
|
||||
let new_focus_state = if foreground_window == window.hwnd {
|
||||
WindowKind::Floating
|
||||
} else {
|
||||
WindowKind::Unfocused
|
||||
};
|
||||
|
||||
border.window_kind = new_focus_state;
|
||||
last_focus_state = focus_state.get(&border.hwnd).copied();
|
||||
|
||||
// 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 should_invalidate = match last_focus_state {
|
||||
None => true,
|
||||
Some(last_focus_state) => {
|
||||
last_focus_state != new_focus_state || layer_changed
|
||||
}
|
||||
};
|
||||
|
||||
if new_border {
|
||||
border.set_position(&rect, window.hwnd)?;
|
||||
}
|
||||
let should_invalidate = new_border
|
||||
|| (last_focus_state != new_focus_state)
|
||||
|| layer_changed;
|
||||
|
||||
if should_invalidate {
|
||||
border.set_position(&rect, window.hwnd)?;
|
||||
border.invalidate();
|
||||
}
|
||||
|
||||
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
|
||||
windows_borders.insert(window.hwnd, border.clone());
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
windows_borders.insert(window.hwnd, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,24 +627,27 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
/// the container id and the border and returns a bool, if true that border
|
||||
/// will be removed.
|
||||
fn remove_borders(
|
||||
borders: &mut HashMap<String, Border>,
|
||||
windows_borders: &mut HashMap<isize, Border>,
|
||||
focus_state: &mut HashMap<isize, WindowKind>,
|
||||
borders_monitors: &mut HashMap<String, usize>,
|
||||
borders: &mut HashMap<String, Box<Border>>,
|
||||
windows_borders: &mut HashMap<isize, String>,
|
||||
monitor_idx: usize,
|
||||
condition: impl Fn(&String, &Border) -> bool,
|
||||
) -> color_eyre::Result<()> {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||
// if border is on this monitor
|
||||
if border.monitor_idx.is_some_and(|idx| idx == monitor_idx)
|
||||
// and the condition applies
|
||||
&& condition(id, border)
|
||||
// and the border is visible (we don't remove hidden borders)
|
||||
&& WindowsApi::is_window_visible(border.hwnd)
|
||||
{
|
||||
// we mark it to be removed
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
remove_border(id, borders, windows_borders, focus_state, borders_monitors)?;
|
||||
remove_border(id, borders, windows_borders)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -683,22 +656,73 @@ fn remove_borders(
|
||||
/// Removes the border with `id` and all its related info from all maps
|
||||
fn remove_border(
|
||||
id: &str,
|
||||
borders: &mut HashMap<String, Border>,
|
||||
windows_borders: &mut HashMap<isize, Border>,
|
||||
focus_state: &mut HashMap<isize, WindowKind>,
|
||||
borders_monitors: &mut HashMap<String, usize>,
|
||||
borders: &mut HashMap<String, Box<Border>>,
|
||||
windows_borders: &mut HashMap<isize, String>,
|
||||
) -> color_eyre::Result<()> {
|
||||
if let Some(removed_border) = borders.remove(id) {
|
||||
removed_border.destroy()?;
|
||||
windows_borders.remove(&removed_border.tracking_hwnd);
|
||||
focus_state.remove(&removed_border.hwnd);
|
||||
destroy_border(removed_border)?;
|
||||
}
|
||||
borders_monitors.remove(id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
/// IMPORTANT: BEWARE when changing this function. We need to make sure that we don't let the
|
||||
/// `Box<Border>` be dropped normally. We need to turn the `Box` into the raw pointer and use that
|
||||
/// pointer to call the `.destroy()` funtion of the border so it closes the window. This way the
|
||||
/// `Box` is consumed and the pointer is dropped like a normal `Copy` number instead of trying to
|
||||
/// drop the struct it points to. The actual border is owned by the thread that created the window
|
||||
/// and once the window closes that thread gets out of its loop, finishes and properly disposes of
|
||||
/// the border.
|
||||
fn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {
|
||||
let raw_pointer = Box::into_raw(border);
|
||||
unsafe {
|
||||
(*raw_pointer).destroy()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the border around window with `tracking_hwnd` if it exists
|
||||
pub fn delete_border(tracking_hwnd: isize) {
|
||||
std::thread::spawn(move || {
|
||||
let id = {
|
||||
WINDOWS_BORDERS
|
||||
.lock()
|
||||
.get(&tracking_hwnd)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut windows_borders = WINDOWS_BORDERS.lock();
|
||||
|
||||
if let Err(error) = remove_border(&id, &mut borders, &mut windows_borders) {
|
||||
tracing::error!("Failed to delete border: {}", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Shows the border around window with `tracking_hwnd` if it exists
|
||||
pub fn show_border(tracking_hwnd: isize) {
|
||||
std::thread::spawn(move || {
|
||||
if let Some(border_info) = window_border(tracking_hwnd) {
|
||||
WindowsApi::restore_window(border_info.border_hwnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Hides the border around window with `tracking_hwnd` if it exists, unless the border kind is a
|
||||
/// `Stack` border.
|
||||
pub fn hide_border(tracking_hwnd: isize) {
|
||||
std::thread::spawn(move || {
|
||||
if let Some(border_info) = window_border(tracking_hwnd) {
|
||||
WindowsApi::hide_window(border_info.border_hwnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ZOrder {
|
||||
Top,
|
||||
NoTopMost,
|
||||
|
||||
@@ -2,14 +2,14 @@ use std::collections::VecDeque;
|
||||
|
||||
use getset::Getters;
|
||||
use nanoid::nanoid;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ring::Ring;
|
||||
use crate::window::Window;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, JsonSchema)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Container {
|
||||
#[getset(get = "pub")]
|
||||
id: String,
|
||||
@@ -52,13 +52,17 @@ impl Container {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hides the unfocused windows of the container and restores the focused one. This function
|
||||
/// is used to make sure we update the window that should be shown on a stack. If the container
|
||||
/// isn't a stack this function won't change anything.
|
||||
pub fn load_focused_window(&mut self) {
|
||||
let focused_idx = self.focused_window_idx();
|
||||
|
||||
for (i, window) in self.windows_mut().iter_mut().enumerate() {
|
||||
if i == focused_idx {
|
||||
window.restore();
|
||||
window.restore_with_border(false);
|
||||
} else {
|
||||
window.hide();
|
||||
window.hide_with_border(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,14 +102,13 @@ impl Container {
|
||||
}
|
||||
|
||||
pub fn idx_for_window(&self, hwnd: isize) -> Option<usize> {
|
||||
let mut idx = None;
|
||||
for (i, window) in self.windows().iter().enumerate() {
|
||||
if window.hwnd == hwnd {
|
||||
idx = Option::from(i);
|
||||
return Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
idx
|
||||
None
|
||||
}
|
||||
|
||||
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
|
||||
@@ -137,3 +140,114 @@ impl Container {
|
||||
self.windows.focus(idx);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_contains_window() {
|
||||
let mut container = Container::default();
|
||||
|
||||
for i in 0..3 {
|
||||
container.add_window(Window::from(i));
|
||||
}
|
||||
|
||||
// Should return true for existing windows
|
||||
assert!(container.contains_window(1));
|
||||
assert_eq!(container.idx_for_window(1), Some(1));
|
||||
|
||||
// Should return false since window 4 doesn't exist
|
||||
assert!(!container.contains_window(4));
|
||||
assert_eq!(container.idx_for_window(4), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_window_by_idx() {
|
||||
let mut container = Container::default();
|
||||
|
||||
for i in 0..3 {
|
||||
container.add_window(Window::from(i));
|
||||
}
|
||||
|
||||
// Remove window 1
|
||||
container.remove_window_by_idx(1);
|
||||
|
||||
// Should only have 2 windows left
|
||||
assert_eq!(container.windows().len(), 2);
|
||||
|
||||
// Should return false since window 1 was removed
|
||||
assert!(!container.contains_window(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_focused_window() {
|
||||
let mut container = Container::default();
|
||||
|
||||
for i in 0..3 {
|
||||
container.add_window(Window::from(i));
|
||||
}
|
||||
|
||||
// Should be focused on the last created window
|
||||
assert_eq!(container.focused_window_idx(), 2);
|
||||
|
||||
// Remove the focused window
|
||||
container.remove_focused_window();
|
||||
|
||||
// Should be focused on the window before the removed one
|
||||
assert_eq!(container.focused_window_idx(), 1);
|
||||
|
||||
// Should only have 2 windows left
|
||||
assert_eq!(container.windows().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_window() {
|
||||
let mut container = Container::default();
|
||||
|
||||
container.add_window(Window::from(1));
|
||||
|
||||
assert_eq!(container.windows().len(), 1);
|
||||
assert_eq!(container.focused_window_idx(), 0);
|
||||
assert!(container.contains_window(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_focus_window() {
|
||||
let mut container = Container::default();
|
||||
|
||||
for i in 0..3 {
|
||||
container.add_window(Window::from(i));
|
||||
}
|
||||
|
||||
// Should focus on the last created window
|
||||
assert_eq!(container.focused_window_idx(), 2);
|
||||
|
||||
// focus on the window at index 1
|
||||
container.focus_window(1);
|
||||
|
||||
// Should be focused on window 1
|
||||
assert_eq!(container.focused_window_idx(), 1);
|
||||
|
||||
// focus on the window at index 0
|
||||
container.focus_window(0);
|
||||
|
||||
// Should be focused on window 0
|
||||
assert_eq!(container.focused_window_idx(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idx_for_window() {
|
||||
let mut container = Container::default();
|
||||
|
||||
for i in 0..3 {
|
||||
container.add_window(Window::from(i));
|
||||
}
|
||||
|
||||
// Should return the index of the window
|
||||
assert_eq!(container.idx_for_window(1), Some(1));
|
||||
|
||||
// Should return None since window 4 doesn't exist
|
||||
assert_eq!(container.idx_for_window(4), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum AnimationStyle {
|
||||
Linear,
|
||||
EaseInSine,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -603,18 +602,8 @@ impl Arrangement for CustomLayout {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum Axis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::config_generation::ApplicationConfiguration;
|
||||
use crate::config_generation::ApplicationOptions;
|
||||
use crate::config_generation::MatchingRule;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -10,10 +9,12 @@ use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ApplicationSpecificConfiguration(pub BTreeMap<String, AscApplicationRulesOrSchema>);
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum AscApplicationRulesOrSchema {
|
||||
AscApplicationRules(AscApplicationRules),
|
||||
@@ -46,7 +47,8 @@ impl ApplicationSpecificConfiguration {
|
||||
}
|
||||
|
||||
/// Rules that determine how an application is handled
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct AscApplicationRules {
|
||||
/// Rules to ignore specific windows
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -8,9 +7,8 @@ use strum::EnumString;
|
||||
|
||||
use super::ApplicationIdentifier;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApplicationOptions {
|
||||
@@ -52,14 +50,16 @@ impl ApplicationOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum MatchingRule {
|
||||
Simple(IdWithIdentifier),
|
||||
Composite(Vec<IdWithIdentifier>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct WorkspaceMatchingRule {
|
||||
pub monitor_index: usize,
|
||||
pub workspace_index: usize,
|
||||
@@ -67,7 +67,8 @@ pub struct WorkspaceMatchingRule {
|
||||
pub initial_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct IdWithIdentifier {
|
||||
pub kind: ApplicationIdentifier,
|
||||
pub id: String,
|
||||
@@ -75,7 +76,8 @@ pub struct IdWithIdentifier {
|
||||
pub matching_strategy: Option<MatchingStrategy>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, JsonSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum MatchingStrategy {
|
||||
Legacy,
|
||||
Equals,
|
||||
@@ -89,7 +91,8 @@ pub enum MatchingStrategy {
|
||||
DoesNotContain,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct IdWithIdentifierAndComment {
|
||||
pub kind: ApplicationIdentifier,
|
||||
pub id: String,
|
||||
@@ -109,7 +112,8 @@ impl From<IdWithIdentifierAndComment> for IdWithIdentifier {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ApplicationConfiguration {
|
||||
pub name: String,
|
||||
pub identifier: IdWithIdentifier,
|
||||
@@ -133,7 +137,8 @@ impl ApplicationConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ApplicationConfigurationGenerator;
|
||||
|
||||
impl ApplicationConfigurationGenerator {
|
||||
|
||||
@@ -8,13 +8,13 @@ use std::path::Path;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Rect;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CustomLayout(Vec<Column>);
|
||||
|
||||
impl Deref for CustomLayout {
|
||||
@@ -250,7 +250,8 @@ impl CustomLayout {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "column", content = "configuration")]
|
||||
pub enum Column {
|
||||
Primary(Option<ColumnWidth>),
|
||||
@@ -258,18 +259,21 @@ pub enum Column {
|
||||
Tertiary(ColumnSplit),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ColumnWidth {
|
||||
WidthPercentage(f32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ColumnSplit {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ColumnSplitWithCapacity {
|
||||
Horizontal(usize),
|
||||
Vertical(usize),
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum CycleDirection {
|
||||
Previous,
|
||||
Next,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -10,18 +9,9 @@ use super::Rect;
|
||||
use super::Sizing;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum DefaultLayout {
|
||||
BSP,
|
||||
Columns,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -7,7 +6,8 @@ use super::CustomLayout;
|
||||
use super::DefaultLayout;
|
||||
use super::Direction;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum Layout {
|
||||
Default(DefaultLayout),
|
||||
Custom(CustomLayout),
|
||||
|
||||
@@ -8,7 +8,6 @@ use std::str::FromStr;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -45,7 +44,8 @@ pub mod operation_direction;
|
||||
pub mod pathext;
|
||||
pub mod rect;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum SocketMessage {
|
||||
// Window / Container Commands
|
||||
@@ -62,6 +62,8 @@ pub enum SocketMessage {
|
||||
UnstackAll,
|
||||
ResizeWindowEdge(OperationDirection, Sizing),
|
||||
ResizeWindowAxis(Axis, Sizing),
|
||||
MoveContainerToLastWorkspace,
|
||||
SendContainerToLastWorkspace,
|
||||
MoveContainerToMonitorNumber(usize),
|
||||
CycleMoveContainerToMonitor(CycleDirection),
|
||||
MoveContainerToWorkspaceNumber(usize),
|
||||
@@ -84,6 +86,9 @@ pub enum SocketMessage {
|
||||
PromoteFocus,
|
||||
PromoteWindow(OperationDirection),
|
||||
EagerFocus(String),
|
||||
LockMonitorWorkspaceContainer(usize, usize, usize),
|
||||
UnlockMonitorWorkspaceContainer(usize, usize, usize),
|
||||
ToggleLock,
|
||||
ToggleFloat,
|
||||
ToggleMonocle,
|
||||
ToggleMaximize,
|
||||
@@ -122,6 +127,7 @@ pub enum SocketMessage {
|
||||
Load(PathBuf),
|
||||
CycleFocusMonitor(CycleDirection),
|
||||
CycleFocusWorkspace(CycleDirection),
|
||||
CycleFocusEmptyWorkspace(CycleDirection),
|
||||
FocusMonitorNumber(usize),
|
||||
FocusMonitorAtCursor,
|
||||
FocusLastWorkspace,
|
||||
@@ -157,7 +163,7 @@ pub enum SocketMessage {
|
||||
WatchConfiguration(bool),
|
||||
CompleteConfiguration,
|
||||
AltFocusHack(bool),
|
||||
Theme(KomorebiTheme),
|
||||
Theme(Box<KomorebiTheme>),
|
||||
Animation(bool, Option<AnimationPrefix>),
|
||||
AnimationDuration(u64, Option<AnimationPrefix>),
|
||||
AnimationFps(u64),
|
||||
@@ -241,24 +247,23 @@ impl FromStr for SocketMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct SubscribeOptions {
|
||||
/// Only emit notifications when the window manager state has changed
|
||||
pub filter_state_changes: bool,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema, ValueEnum,
|
||||
)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum StackbarMode {
|
||||
Always,
|
||||
Never,
|
||||
OnStack,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
||||
)]
|
||||
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum StackbarLabel {
|
||||
#[default]
|
||||
Process,
|
||||
@@ -266,18 +271,9 @@ pub enum StackbarLabel {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Display,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
ValueEnum,
|
||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum BorderStyle {
|
||||
#[default]
|
||||
/// Use the system border style
|
||||
@@ -289,18 +285,9 @@ pub enum BorderStyle {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Display,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
ValueEnum,
|
||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum BorderImplementation {
|
||||
#[default]
|
||||
/// Use the adjustable komorebi border implementation
|
||||
@@ -313,48 +300,42 @@ pub enum BorderImplementation {
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum WindowKind {
|
||||
Single,
|
||||
Stack,
|
||||
Monocle,
|
||||
#[default]
|
||||
Unfocused,
|
||||
UnfocusedLocked,
|
||||
Floating,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum StateQuery {
|
||||
FocusedMonitorIndex,
|
||||
FocusedWorkspaceIndex,
|
||||
FocusedContainerIndex,
|
||||
FocusedWindowIndex,
|
||||
FocusedWorkspaceName,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ApplicationIdentifier {
|
||||
#[serde(alias = "exe")]
|
||||
Exe,
|
||||
@@ -366,18 +347,8 @@ pub enum ApplicationIdentifier {
|
||||
Path,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum FocusFollowsMouseImplementation {
|
||||
/// A custom FFM implementation (slightly more CPU-intensive)
|
||||
Komorebi,
|
||||
@@ -385,7 +356,8 @@ pub enum FocusFollowsMouseImplementation {
|
||||
Windows,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct WindowManagementBehaviour {
|
||||
/// The current WindowContainerBehaviour to be used
|
||||
pub current_behaviour: WindowContainerBehaviour,
|
||||
@@ -396,18 +368,9 @@ pub struct WindowManagementBehaviour {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum WindowContainerBehaviour {
|
||||
/// Create a new container for each new window
|
||||
#[default]
|
||||
@@ -417,17 +380,19 @@ pub enum WindowContainerBehaviour {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum FloatingLayerBehaviour {
|
||||
/// Tile new windows (unless they match a float rule)
|
||||
#[default]
|
||||
Tile,
|
||||
/// Float new windows
|
||||
Float,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum MoveBehaviour {
|
||||
/// Swap the window container with the window container at the edge of the adjacent monitor
|
||||
Swap,
|
||||
@@ -437,18 +402,8 @@ pub enum MoveBehaviour {
|
||||
NoOp,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum CrossBoundaryBehaviour {
|
||||
/// Attempt to perform actions across a workspace boundary
|
||||
Workspace,
|
||||
@@ -456,18 +411,8 @@ pub enum CrossBoundaryBehaviour {
|
||||
Monitor,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum HidingBehaviour {
|
||||
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
||||
Hide,
|
||||
@@ -477,18 +422,8 @@ pub enum HidingBehaviour {
|
||||
Cloak,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
ValueEnum,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum OperationBehaviour {
|
||||
/// Process komorebic commands on temporarily unmanaged/floated windows
|
||||
Op,
|
||||
@@ -496,9 +431,8 @@ pub enum OperationBehaviour {
|
||||
NoOp,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum Sizing {
|
||||
Increase,
|
||||
Decrease,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -10,9 +9,8 @@ use strum::EnumString;
|
||||
use super::direction::Direction;
|
||||
use super::Axis;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum OperationDirection {
|
||||
Left,
|
||||
Right,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Rect {
|
||||
/// The left point in a Win32 Rect
|
||||
pub left: i32,
|
||||
|
||||
@@ -5,10 +5,10 @@ pub mod border_manager;
|
||||
pub mod com;
|
||||
#[macro_use]
|
||||
pub mod ring;
|
||||
pub mod colour;
|
||||
pub mod container;
|
||||
pub mod core;
|
||||
pub mod focus_manager;
|
||||
pub mod locked_deque;
|
||||
pub mod monitor;
|
||||
pub mod monitor_reconciliator;
|
||||
pub mod process_command;
|
||||
@@ -29,7 +29,6 @@ pub mod windows_callbacks;
|
||||
pub mod winevent;
|
||||
pub mod winevent_listener;
|
||||
pub mod workspace;
|
||||
pub mod workspace_reconciliator;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use monitor_reconciliator::MonitorNotification;
|
||||
@@ -47,11 +46,12 @@ use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use colour::*;
|
||||
pub use core::*;
|
||||
pub use komorebi_themes::colour::*;
|
||||
pub use process_command::*;
|
||||
pub use process_event::*;
|
||||
pub use static_config::*;
|
||||
pub use win32_display_data;
|
||||
pub use window::*;
|
||||
pub use window_manager::*;
|
||||
pub use window_manager_event::*;
|
||||
@@ -65,8 +65,8 @@ use crate::core::config_generation::WorkspaceMatchingRule;
|
||||
use color_eyre::Result;
|
||||
use os_info::Version;
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::RwLock;
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixStream;
|
||||
@@ -132,8 +132,8 @@ lazy_static! {
|
||||
static ref TRANSPARENCY_BLACKLIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref DISPLAY_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, String>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref DISPLAY_INDEX_PREFERENCES: Arc<RwLock<HashMap<usize, String>>> =
|
||||
Arc::new(RwLock::new(HashMap::new()));
|
||||
static ref WORKSPACE_MATCHING_RULES: Arc<Mutex<Vec<WorkspaceMatchingRule>>> =
|
||||
Arc::new(Mutex::new(Vec::new()));
|
||||
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
|
||||
@@ -173,6 +173,8 @@ lazy_static! {
|
||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||
}),
|
||||
]));
|
||||
static ref DUPLICATE_MONITOR_SERIAL_IDS: Arc<RwLock<Vec<String>>> =
|
||||
Arc::new(RwLock::new(Vec::new()));
|
||||
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
|
||||
@@ -236,6 +238,8 @@ pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
|
||||
|
||||
shadow_rs::shadow!(build);
|
||||
|
||||
#[must_use]
|
||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
@@ -280,7 +284,8 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
current
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum NotificationEvent {
|
||||
WindowManager(WindowManagerEvent),
|
||||
@@ -288,7 +293,8 @@ pub enum NotificationEvent {
|
||||
Monitor(MonitorNotification),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Notification {
|
||||
pub event: NotificationEvent,
|
||||
pub state: State,
|
||||
|
||||
316
komorebi/src/locked_deque.rs
Normal file
316
komorebi/src/locked_deque.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct LockedDeque<'a, T> {
|
||||
deque: &'a mut VecDeque<T>,
|
||||
locked_indices: &'a mut BTreeSet<usize>,
|
||||
}
|
||||
|
||||
impl<'a, T: PartialEq> LockedDeque<'a, T> {
|
||||
pub fn new(deque: &'a mut VecDeque<T>, locked_indices: &'a mut BTreeSet<usize>) -> Self {
|
||||
Self {
|
||||
deque,
|
||||
locked_indices,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, index: usize, value: T) -> usize {
|
||||
insert_respecting_locks(self.deque, self.locked_indices, index, value)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, index: usize) -> Option<T> {
|
||||
remove_respecting_locks(self.deque, self.locked_indices, index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_respecting_locks<T>(
|
||||
deque: &mut VecDeque<T>,
|
||||
locked_idx: &mut BTreeSet<usize>,
|
||||
idx: usize,
|
||||
value: T,
|
||||
) -> usize {
|
||||
if idx == deque.len() {
|
||||
deque.push_back(value);
|
||||
return idx;
|
||||
}
|
||||
|
||||
let mut new_deque = VecDeque::with_capacity(deque.len() + 1);
|
||||
let mut temp_locked_deque = VecDeque::new();
|
||||
let mut j = 0;
|
||||
let mut corrected_idx = idx;
|
||||
|
||||
for (i, el) in deque.drain(..).enumerate() {
|
||||
if i == idx {
|
||||
corrected_idx = j;
|
||||
}
|
||||
if locked_idx.contains(&i) {
|
||||
temp_locked_deque.push_back(el);
|
||||
} else {
|
||||
new_deque.push_back(el);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
new_deque.insert(corrected_idx, value);
|
||||
|
||||
for (locked_el, locked_idx) in temp_locked_deque.into_iter().zip(locked_idx.iter()) {
|
||||
new_deque.insert(*locked_idx, locked_el);
|
||||
if *locked_idx <= corrected_idx {
|
||||
corrected_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
*deque = new_deque;
|
||||
|
||||
corrected_idx
|
||||
}
|
||||
|
||||
pub fn remove_respecting_locks<T>(
|
||||
deque: &mut VecDeque<T>,
|
||||
locked_idx: &mut BTreeSet<usize>,
|
||||
idx: usize,
|
||||
) -> Option<T> {
|
||||
if idx >= deque.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let final_size = deque.len() - 1;
|
||||
|
||||
let mut new_deque = VecDeque::with_capacity(final_size);
|
||||
let mut temp_locked_deque = VecDeque::new();
|
||||
let mut removed = None;
|
||||
let mut removed_locked_idx = None;
|
||||
|
||||
for (i, el) in deque.drain(..).enumerate() {
|
||||
if i == idx {
|
||||
removed = Some(el);
|
||||
removed_locked_idx = locked_idx.contains(&i).then_some(i);
|
||||
} else if locked_idx.contains(&i) {
|
||||
temp_locked_deque.push_back(el);
|
||||
} else {
|
||||
new_deque.push_back(el);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(i) = removed_locked_idx {
|
||||
let mut above = locked_idx.split_off(&i);
|
||||
above.pop_first();
|
||||
locked_idx.extend(above.into_iter().map(|i| i - 1));
|
||||
}
|
||||
|
||||
while locked_idx.last().is_some_and(|i| *i >= final_size) {
|
||||
locked_idx.pop_last();
|
||||
}
|
||||
|
||||
let extra_invalid_idx = (new_deque.len()
|
||||
..(new_deque.len() + temp_locked_deque.len() - locked_idx.len()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (locked_el, locked_idx) in temp_locked_deque
|
||||
.into_iter()
|
||||
.zip(locked_idx.iter().chain(extra_invalid_idx.iter()))
|
||||
{
|
||||
new_deque.insert(*locked_idx, locked_el);
|
||||
}
|
||||
|
||||
*deque = new_deque;
|
||||
|
||||
removed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[test]
|
||||
fn test_insert_respecting_locks() {
|
||||
// Test case 1: Basic insertion with locked index
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
// Insert at index 0, should shift elements while keeping index 2 locked
|
||||
insert_respecting_locks(&mut deque, &mut locked, 0, 99);
|
||||
assert_eq!(deque, VecDeque::from(vec![99, 0, 2, 1, 3, 4]));
|
||||
// Element '2' remains at index 2, element '1' that was at index 1 is now at index 3
|
||||
}
|
||||
|
||||
// Test case 2: Insert at a locked index
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
// Try to insert at locked index 2, should insert at index 3 instead
|
||||
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 2, 99);
|
||||
assert_eq!(actual_index, 3);
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 99, 3, 4]));
|
||||
}
|
||||
|
||||
// Test case 3: Multiple locked indices
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(1); // Lock index 1
|
||||
locked.insert(3); // Lock index 3
|
||||
|
||||
// Insert at index 0, should maintain locked indices
|
||||
insert_respecting_locks(&mut deque, &mut locked, 0, 99);
|
||||
assert_eq!(deque, VecDeque::from(vec![99, 1, 0, 3, 2, 4]));
|
||||
// Elements '1' and '3' remain at indices 1 and 3
|
||||
}
|
||||
|
||||
// Test case 4: Insert at end
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
// Insert at end of deque
|
||||
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 5, 99);
|
||||
assert_eq!(actual_index, 5);
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4, 99]));
|
||||
}
|
||||
|
||||
// Test case 5: Empty deque
|
||||
{
|
||||
let mut deque = VecDeque::new();
|
||||
let mut locked = BTreeSet::new();
|
||||
|
||||
// Insert into empty deque
|
||||
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 0, 99);
|
||||
assert_eq!(actual_index, 0);
|
||||
assert_eq!(deque, VecDeque::from(vec![99]));
|
||||
}
|
||||
|
||||
// Test case 6: All indices locked
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
for i in 0..5 {
|
||||
locked.insert(i); // Lock all indices
|
||||
}
|
||||
|
||||
// Try to insert at index 2, should insert at the end
|
||||
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 2, 99);
|
||||
assert_eq!(actual_index, 5);
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4, 99]));
|
||||
}
|
||||
|
||||
// Test case 7: Consecutive locked indices
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
locked.insert(3); // Lock index 3
|
||||
|
||||
// Insert at index 1, should maintain consecutive locked indices
|
||||
insert_respecting_locks(&mut deque, &mut locked, 1, 99);
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 99, 2, 3, 1, 4]));
|
||||
// Elements '2' and '3' remain at indices 2 and 3
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_respecting_locks() {
|
||||
// Test case 1: Remove a non-locked index before a locked index
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 0);
|
||||
assert_eq!(removed, Some(0));
|
||||
assert_eq!(deque, VecDeque::from(vec![1, 3, 2, 4]));
|
||||
assert!(locked.contains(&2)); // Index 2 should still be locked
|
||||
}
|
||||
|
||||
// Test case 2: Remove a locked index
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 2);
|
||||
assert_eq!(removed, Some(2));
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 3, 4]));
|
||||
assert!(!locked.contains(&2)); // Index 2 should be unlocked
|
||||
}
|
||||
|
||||
// Test case 3: Remove an index after a locked index
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(1); // Lock index 1
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 3);
|
||||
assert_eq!(removed, Some(3));
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 4]));
|
||||
assert!(locked.contains(&1)); // Index 1 should still be locked
|
||||
}
|
||||
|
||||
// Test case 4: Multiple locked indices
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(1); // Lock index 1
|
||||
locked.insert(3); // Lock index 3
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 0);
|
||||
assert_eq!(removed, Some(0));
|
||||
assert_eq!(deque, VecDeque::from(vec![2, 1, 4, 3]));
|
||||
assert!(locked.contains(&1) && locked.contains(&3)); // Both indices should still be locked
|
||||
}
|
||||
|
||||
// Test case 5: Remove the last element
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 4);
|
||||
assert_eq!(removed, Some(4));
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3]));
|
||||
assert!(locked.contains(&2)); // Index 2 should still be locked
|
||||
}
|
||||
|
||||
// Test case 6: Invalid index
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 10);
|
||||
assert_eq!(removed, None);
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4])); // Deque unchanged
|
||||
assert!(locked.contains(&2)); // Lock unchanged
|
||||
}
|
||||
|
||||
// Test case 7: Remove enough elements to make a locked index invalid
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
|
||||
remove_respecting_locks(&mut deque, &mut locked, 0);
|
||||
assert_eq!(deque, VecDeque::from(vec![1, 2]));
|
||||
assert!(!locked.contains(&2)); // Index 2 should now be invalid
|
||||
}
|
||||
|
||||
// Test case 8: Removing an element before multiple locked indices
|
||||
{
|
||||
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4, 5]);
|
||||
let mut locked = BTreeSet::new();
|
||||
locked.insert(2); // Lock index 2
|
||||
locked.insert(4); // Lock index 4
|
||||
|
||||
let removed = remove_respecting_locks(&mut deque, &mut locked, 1);
|
||||
assert_eq!(removed, Some(1));
|
||||
assert_eq!(deque, VecDeque::from(vec![0, 3, 2, 5, 4]));
|
||||
assert!(locked.contains(&2) && locked.contains(&4)); // Both indices should still be locked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_utils::Backoff;
|
||||
use komorebi::animation::AnimationEngine;
|
||||
@@ -24,6 +25,7 @@ use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use parking_lot::deadlock;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use sysinfo::Process;
|
||||
use sysinfo::ProcessesToUpdate;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
@@ -48,16 +50,13 @@ use komorebi::window_manager::State;
|
||||
use komorebi::window_manager::WindowManager;
|
||||
use komorebi::windows_api::WindowsApi;
|
||||
use komorebi::winevent_listener;
|
||||
use komorebi::workspace_reconciliator;
|
||||
use komorebi::CUSTOM_FFM;
|
||||
use komorebi::DATA_DIR;
|
||||
use komorebi::HOME_DIR;
|
||||
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
||||
use komorebi::SESSION_ID;
|
||||
|
||||
shadow_rs::shadow!(build);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
fn setup(log_level: LogLevel) -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
}
|
||||
@@ -65,7 +64,16 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
std::env::set_var(
|
||||
"RUST_LOG",
|
||||
match log_level {
|
||||
LogLevel::Error => "error",
|
||||
LogLevel::Warn => "warn",
|
||||
LogLevel::Info => "info",
|
||||
LogLevel::Debug => "debug",
|
||||
LogLevel::Trace => "trace",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
|
||||
@@ -143,8 +151,19 @@ fn detect_deadlocks() {
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, ValueEnum, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
#[default]
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author, about, version = build::CLAP_LONG_VERSION)]
|
||||
#[clap(author, about, version = komorebi::build::CLAP_LONG_VERSION)]
|
||||
struct Opts {
|
||||
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
||||
#[clap(short, long = "ffm")]
|
||||
@@ -161,6 +180,9 @@ struct Opts {
|
||||
/// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi
|
||||
#[clap(long)]
|
||||
clean_state: bool,
|
||||
/// Level of log output verbosity
|
||||
#[clap(long, value_enum, default_value_t=LogLevel::Info)]
|
||||
log_level: LogLevel,
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -198,7 +220,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
let (_guard, _color_guard) = setup(opts.log_level)?;
|
||||
|
||||
WindowsApi::foreground_lock_timeout()?;
|
||||
|
||||
@@ -235,6 +257,7 @@ fn main() -> Result<()> {
|
||||
} else {
|
||||
Arc::new(Mutex::new(WindowManager::new(
|
||||
winevent_listener::event_rx(),
|
||||
None,
|
||||
)?))
|
||||
};
|
||||
|
||||
@@ -277,7 +300,6 @@ fn main() -> Result<()> {
|
||||
border_manager::listen_for_notifications(wm.clone());
|
||||
stackbar_manager::listen_for_notifications(wm.clone());
|
||||
transparency_manager::listen_for_notifications(wm.clone());
|
||||
workspace_reconciliator::listen_for_notifications(wm.clone());
|
||||
monitor_reconciliator::listen_for_notifications(wm.clone())?;
|
||||
reaper::listen_for_notifications(wm.clone(), wm.lock().known_hwnds.clone());
|
||||
focus_manager::listen_for_notifications(wm.clone());
|
||||
|
||||
@@ -9,7 +9,6 @@ use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -18,6 +17,7 @@ use crate::core::Rect;
|
||||
use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::DefaultLayout;
|
||||
use crate::Layout;
|
||||
use crate::OperationDirection;
|
||||
@@ -26,17 +26,9 @@ use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Getters,
|
||||
CopyGetters,
|
||||
MutGetters,
|
||||
Setters,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, PartialEq,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Monitor {
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
pub id: isize,
|
||||
@@ -378,19 +370,20 @@ impl Monitor {
|
||||
.position(|w| w.hwnd == foreground_hwnd);
|
||||
|
||||
if let Some(idx) = floating_window_index {
|
||||
let window = workspace.floating_windows_mut().remove(idx);
|
||||
if let Some(window) = workspace.floating_windows_mut().remove(idx) {
|
||||
let workspaces = self.workspaces_mut();
|
||||
#[allow(clippy::option_if_let_else)]
|
||||
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
||||
None => {
|
||||
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
||||
workspaces.get_mut(target_workspace_idx).unwrap()
|
||||
}
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
let workspaces = self.workspaces_mut();
|
||||
#[allow(clippy::option_if_let_else)]
|
||||
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
||||
None => {
|
||||
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
||||
workspaces.get_mut(target_workspace_idx).unwrap()
|
||||
}
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
target_workspace.floating_windows_mut().push(window);
|
||||
target_workspace.floating_windows_mut().push_back(window);
|
||||
target_workspace.set_layer(WorkspaceLayer::Floating);
|
||||
}
|
||||
} else {
|
||||
let container = workspace
|
||||
.remove_focused_container()
|
||||
@@ -407,6 +400,8 @@ impl Monitor {
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
target_workspace.set_layer(WorkspaceLayer::Tiling);
|
||||
|
||||
if let Some(direction) = direction {
|
||||
self.add_container_with_direction(
|
||||
container,
|
||||
@@ -473,3 +468,139 @@ impl Monitor {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add_container() {
|
||||
let mut m = Monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"TestMonitor".to_string(),
|
||||
"TestDevice".to_string(),
|
||||
"TestDeviceID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
// Add container to the default workspace
|
||||
m.add_container(Container::default(), Some(0)).unwrap();
|
||||
|
||||
// Should contain a container in the current focused workspace
|
||||
let workspace = m.focused_workspace_mut().unwrap();
|
||||
assert_eq!(workspace.containers().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_workspace_by_idx() {
|
||||
let mut m = Monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"TestMonitor".to_string(),
|
||||
"TestDevice".to_string(),
|
||||
"TestDeviceID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
let new_workspace_index = m.new_workspace_idx();
|
||||
assert_eq!(new_workspace_index, 1);
|
||||
|
||||
// Create workspace 2
|
||||
m.focus_workspace(new_workspace_index).unwrap();
|
||||
|
||||
// Should have 2 workspaces
|
||||
assert_eq!(m.workspaces().len(), 2);
|
||||
|
||||
// Create workspace 3
|
||||
m.focus_workspace(new_workspace_index + 1).unwrap();
|
||||
|
||||
// Should have 3 workspaces
|
||||
assert_eq!(m.workspaces().len(), 3);
|
||||
|
||||
// Remove workspace 1
|
||||
m.remove_workspace_by_idx(1);
|
||||
|
||||
// Should have only 2 workspaces
|
||||
assert_eq!(m.workspaces().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_workspaces() {
|
||||
let mut m = Monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"TestMonitor".to_string(),
|
||||
"TestDevice".to_string(),
|
||||
"TestDeviceID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
let new_workspace_index = m.new_workspace_idx();
|
||||
assert_eq!(new_workspace_index, 1);
|
||||
|
||||
// Create workspace 2
|
||||
m.focus_workspace(new_workspace_index).unwrap();
|
||||
|
||||
// Should have 2 workspaces
|
||||
assert_eq!(m.workspaces().len(), 2);
|
||||
|
||||
// Create workspace 3
|
||||
m.focus_workspace(new_workspace_index + 1).unwrap();
|
||||
|
||||
// Should have 3 workspaces
|
||||
assert_eq!(m.workspaces().len(), 3);
|
||||
|
||||
// Remove all workspaces
|
||||
m.remove_workspaces();
|
||||
|
||||
// All workspaces should be removed
|
||||
assert_eq!(m.workspaces().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_focus_workspace() {
|
||||
let mut m = Monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"TestMonitor".to_string(),
|
||||
"TestDevice".to_string(),
|
||||
"TestDeviceID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
let new_workspace_index = m.new_workspace_idx();
|
||||
assert_eq!(new_workspace_index, 1);
|
||||
|
||||
// Focus workspace 2
|
||||
m.focus_workspace(new_workspace_index).unwrap();
|
||||
|
||||
// Should have 2 workspaces
|
||||
assert_eq!(m.workspaces().len(), 2);
|
||||
|
||||
// Should be focused on workspace 2
|
||||
assert_eq!(m.focused_workspace_idx(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_workspace_idx() {
|
||||
let m = Monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"TestMonitor".to_string(),
|
||||
"TestDevice".to_string(),
|
||||
"TestDeviceID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
let new_workspace_index = m.new_workspace_idx();
|
||||
|
||||
// Should be the last workspace index: 1
|
||||
assert_eq!(new_workspace_index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -28,7 +29,8 @@ use std::sync::OnceLock;
|
||||
|
||||
pub mod hidden;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum MonitorNotification {
|
||||
ResolutionScalingChanged,
|
||||
@@ -66,17 +68,51 @@ pub fn send_notification(notification: MonitorNotification) {
|
||||
}
|
||||
|
||||
pub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) {
|
||||
let dip = DISPLAY_INDEX_PREFERENCES.read();
|
||||
let mut dip_ids = dip.values();
|
||||
let preferred_id = if dip_ids.any(|id| id == monitor.device_id()) {
|
||||
monitor.device_id().clone()
|
||||
} else if dip_ids.any(|id| Some(id) == monitor.serial_number_id().as_ref()) {
|
||||
monitor.serial_number_id().clone().unwrap_or_default()
|
||||
} else {
|
||||
serial_or_device_id.to_string()
|
||||
};
|
||||
let mut monitor_cache = MONITOR_CACHE
|
||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
.lock();
|
||||
|
||||
monitor_cache.insert(serial_or_device_id.to_string(), monitor);
|
||||
monitor_cache.insert(preferred_id, monitor);
|
||||
}
|
||||
|
||||
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
|
||||
Ok(win32_display_data::connected_displays_all()
|
||||
.flatten()
|
||||
.map(|display| {
|
||||
pub fn attached_display_devices<F, I>(display_provider: F) -> color_eyre::Result<Vec<Monitor>>
|
||||
where
|
||||
F: Fn() -> I + Copy,
|
||||
I: Iterator<Item = Result<win32_display_data::Device, win32_display_data::Error>>,
|
||||
{
|
||||
let all_displays = display_provider().flatten().collect::<Vec<_>>();
|
||||
|
||||
let mut serial_id_map = HashMap::new();
|
||||
|
||||
for d in &all_displays {
|
||||
if let Some(id) = &d.serial_number_id {
|
||||
*serial_id_map.entry(id.clone()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for d in &all_displays {
|
||||
if let Some(id) = &d.serial_number_id {
|
||||
if serial_id_map.get(id).copied().unwrap_or_default() > 1 {
|
||||
let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();
|
||||
if !dupes.contains(id) {
|
||||
(*dupes).push(id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(all_displays
|
||||
.into_iter()
|
||||
.map(|mut display| {
|
||||
let path = display.device_path;
|
||||
|
||||
let (device, device_id) = if path.is_empty() {
|
||||
@@ -93,6 +129,13 @@ pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
|
||||
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
||||
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
||||
|
||||
if let Some(id) = &display.serial_number_id {
|
||||
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
|
||||
if dupes.contains(id) {
|
||||
display.serial_number_id = None;
|
||||
}
|
||||
}
|
||||
|
||||
monitor::new(
|
||||
display.hmonitor,
|
||||
display.size.into(),
|
||||
@@ -113,7 +156,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
|
||||
tracing::info!("created hidden window to listen for monitor-related events");
|
||||
|
||||
std::thread::spawn(move || loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
match handle_notifications(wm.clone(), win32_display_data::connected_displays_all) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
@@ -130,7 +173,14 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
pub fn handle_notifications<F, I>(
|
||||
wm: Arc<Mutex<WindowManager>>,
|
||||
display_provider: F,
|
||||
) -> color_eyre::Result<()>
|
||||
where
|
||||
F: Fn() -> I + Copy,
|
||||
I: Iterator<Item = Result<win32_display_data::Device, win32_display_data::Error>>,
|
||||
{
|
||||
tracing::info!("listening");
|
||||
|
||||
let receiver = event_rx();
|
||||
@@ -255,14 +305,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let initial_monitor_count = wm.monitors().len();
|
||||
|
||||
// Get the currently attached display devices
|
||||
let attached_devices = attached_display_devices()?;
|
||||
let attached_devices = attached_display_devices(display_provider)?;
|
||||
|
||||
// Make sure that in our state any attached displays have the latest Win32 data
|
||||
for monitor in wm.monitors_mut() {
|
||||
for attached in &attached_devices {
|
||||
if attached.serial_number_id().eq(monitor.serial_number_id())
|
||||
|| attached.device_id().eq(monitor.device_id())
|
||||
let serial_number_ids_match = if let (Some(attached_snid), Some(m_snid)) =
|
||||
(attached.serial_number_id(), monitor.serial_number_id())
|
||||
{
|
||||
attached_snid.eq(m_snid)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if serial_number_ids_match || attached.device_id().eq(monitor.device_id()) {
|
||||
monitor.set_id(attached.id());
|
||||
monitor.set_device(attached.device().clone());
|
||||
monitor.set_device_id(attached.device_id().clone());
|
||||
@@ -380,8 +436,18 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
workspace_rules.remove(i);
|
||||
}
|
||||
|
||||
// Let's add their state to the cache for later
|
||||
monitor_cache.insert(id, m.clone());
|
||||
// Let's add their state to the cache for later, make sure to use what
|
||||
// the user set as preference as the id.
|
||||
let dip = DISPLAY_INDEX_PREFERENCES.read();
|
||||
let mut dip_ids = dip.values();
|
||||
let preferred_id = if dip_ids.any(|id| id == m.device_id()) {
|
||||
m.device_id().clone()
|
||||
} else if dip_ids.any(|id| Some(id) == m.serial_number_id().as_ref()) {
|
||||
m.serial_number_id().clone().unwrap_or_default()
|
||||
} else {
|
||||
id
|
||||
};
|
||||
monitor_cache.insert(preferred_id, m.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,3 +727,304 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crossbeam_channel::bounded;
|
||||
use crossbeam_channel::Sender;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
use windows::Win32::Devices::Display::DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;
|
||||
// NOTE: Using RECT instead of RECT since I get a mismatched type error. Can be updated if
|
||||
// needed.
|
||||
use windows::Win32::Foundation::RECT;
|
||||
|
||||
// Creating a Mock Display Provider
|
||||
#[derive(Clone)]
|
||||
struct MockDevice {
|
||||
hmonitor: isize,
|
||||
device_path: String,
|
||||
device_name: String,
|
||||
device_description: String,
|
||||
serial_number_id: Option<String>,
|
||||
size: RECT,
|
||||
work_area_size: RECT,
|
||||
device_key: String,
|
||||
output_technology: Option<DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY>,
|
||||
}
|
||||
|
||||
impl From<MockDevice> for win32_display_data::Device {
|
||||
fn from(mock: MockDevice) -> Self {
|
||||
win32_display_data::Device {
|
||||
hmonitor: mock.hmonitor,
|
||||
device_path: mock.device_path,
|
||||
device_name: mock.device_name,
|
||||
device_description: mock.device_description,
|
||||
serial_number_id: mock.serial_number_id,
|
||||
size: mock.size,
|
||||
work_area_size: mock.work_area_size,
|
||||
device_key: mock.device_key,
|
||||
output_technology: mock.output_technology,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a Window Manager Instance
|
||||
struct TestContext {
|
||||
socket_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Drop for TestContext {
|
||||
fn drop(&mut self) {
|
||||
if let Some(socket_path) = &self.socket_path {
|
||||
// Clean up the socket file
|
||||
if let Err(e) = std::fs::remove_file(socket_path) {
|
||||
tracing::warn!("Failed to remove socket file: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_window_manager() -> (WindowManager, TestContext) {
|
||||
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
bounded(1);
|
||||
|
||||
// Temporary socket path for testing
|
||||
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
|
||||
let socket_path = PathBuf::from(socket_name);
|
||||
|
||||
// Create a new WindowManager instance
|
||||
let wm = match WindowManager::new(receiver, Some(socket_path.clone())) {
|
||||
Ok(manager) => manager,
|
||||
Err(e) => {
|
||||
panic!("Failed to create WindowManager: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
wm,
|
||||
TestContext {
|
||||
socket_path: Some(socket_path),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_notification() {
|
||||
// Create a monitor notification
|
||||
let notification = MonitorNotification::ResolutionScalingChanged;
|
||||
|
||||
// Use the send_notification function to send the notification
|
||||
send_notification(notification);
|
||||
|
||||
// Receive the notification from the channel
|
||||
let received = event_rx().try_recv();
|
||||
|
||||
// Check if we received the notification and if it matches what we sent
|
||||
match received {
|
||||
Ok(notification) => {
|
||||
assert_eq!(notification, MonitorNotification::ResolutionScalingChanged);
|
||||
}
|
||||
Err(e) => panic!("Failed to receive MonitorNotification: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_channel_bounded_capacity() {
|
||||
let (_, receiver) = channel();
|
||||
|
||||
// Fill the channel to its capacity (20 messages)
|
||||
for _ in 0..20 {
|
||||
send_notification(MonitorNotification::WorkAreaChanged);
|
||||
}
|
||||
|
||||
// Attempt to send another message (should be dropped)
|
||||
send_notification(MonitorNotification::ResolutionScalingChanged);
|
||||
|
||||
// Verify the channel contains only the first 20 messages
|
||||
for _ in 0..20 {
|
||||
let notification = match receiver.try_recv() {
|
||||
Ok(notification) => notification,
|
||||
Err(e) => panic!("Failed to receive MonitorNotification: {}", e),
|
||||
};
|
||||
assert_eq!(
|
||||
notification,
|
||||
MonitorNotification::WorkAreaChanged,
|
||||
"Unexpected notification in the channel"
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that no additional messages are in the channel
|
||||
assert!(
|
||||
receiver.try_recv().is_err(),
|
||||
"Channel should be empty after consuming all messages"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_in_monitor_cache() {
|
||||
let m = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"Test Monitor".to_string(),
|
||||
"Test Device".to_string(),
|
||||
"Test Device ID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
// Insert the monitor into the cache
|
||||
insert_in_monitor_cache("TestMonitorID", m.clone());
|
||||
|
||||
// Retrieve the monitor from the cache
|
||||
let cache = MONITOR_CACHE
|
||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
.lock();
|
||||
let retrieved_monitor = cache.get("TestMonitorID");
|
||||
|
||||
// Check that the monitor was inserted correctly and matches the expected value
|
||||
assert_eq!(retrieved_monitor, Some(&m));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_two_monitors_cache() {
|
||||
let m1 = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"Test Monitor".to_string(),
|
||||
"Test Device".to_string(),
|
||||
"Test Device ID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
let m2 = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"Test Monitor 2".to_string(),
|
||||
"Test Device 2".to_string(),
|
||||
"Test Device ID 2".to_string(),
|
||||
Some("TestMonitorID2".to_string()),
|
||||
);
|
||||
|
||||
// Insert the first monitor into the cache
|
||||
insert_in_monitor_cache("TestMonitorID", m1.clone());
|
||||
|
||||
// Insert the second monitor into the cache
|
||||
insert_in_monitor_cache("TestMonitorID2", m2.clone());
|
||||
|
||||
// Retrieve the cache to check if the first and second monitors are present
|
||||
let cache = MONITOR_CACHE
|
||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
.lock();
|
||||
|
||||
// Check if Monitor 1 was found in the cache
|
||||
assert_eq!(
|
||||
cache.get("TestMonitorID"),
|
||||
Some(&m1),
|
||||
"Monitor cache should contain monitor 1"
|
||||
);
|
||||
|
||||
// Check if Monitor 2 was found in the cache
|
||||
assert_eq!(
|
||||
cache.get("TestMonitorID2"),
|
||||
Some(&m2),
|
||||
"Monitor cache should contain monitor 2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_listen_for_notifications() {
|
||||
// Create a WindowManager instance for testing
|
||||
let (wm, _test_context) = setup_window_manager();
|
||||
|
||||
// Start the notification listener
|
||||
let result = listen_for_notifications(Arc::new(Mutex::new(wm)));
|
||||
|
||||
// Check if the listener started successfully
|
||||
assert!(result.is_ok(), "Failed to start notification listener");
|
||||
|
||||
// Test sending a notification
|
||||
send_notification(MonitorNotification::DisplayConnectionChange);
|
||||
|
||||
// Receive the notification from the channel
|
||||
let received = event_rx().try_recv();
|
||||
|
||||
// Check if we received the notification and if it matches what we sent
|
||||
match received {
|
||||
Ok(notification) => {
|
||||
assert_eq!(notification, MonitorNotification::DisplayConnectionChange);
|
||||
}
|
||||
Err(e) => panic!("Failed to receive MonitorNotification: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attached_display_devices() {
|
||||
// Define mock display data
|
||||
let mock_monitor = MockDevice {
|
||||
hmonitor: 1,
|
||||
device_path: String::from(
|
||||
"\\\\?\\DISPLAY#ABC123#4&123456&0&UID0#{saucepackets-4321-5678-2468-abc123456789}",
|
||||
),
|
||||
device_name: String::from("\\\\.\\DISPLAY1"),
|
||||
device_description: String::from("Display description"),
|
||||
serial_number_id: Some(String::from("SaucePackets123")),
|
||||
device_key: String::from("Mock Key"),
|
||||
size: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 1920,
|
||||
bottom: 1080,
|
||||
},
|
||||
work_area_size: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 1920,
|
||||
bottom: 1080,
|
||||
},
|
||||
output_technology: Some(DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY(0)),
|
||||
};
|
||||
|
||||
// Create a closure to simulate the display provider
|
||||
let display_provider = || {
|
||||
vec![Ok::<win32_display_data::Device, win32_display_data::Error>(
|
||||
win32_display_data::Device::from(mock_monitor.clone()),
|
||||
)]
|
||||
.into_iter()
|
||||
};
|
||||
|
||||
// Should contain the mock monitor
|
||||
let result = attached_display_devices(display_provider).ok();
|
||||
if let Some(monitors) = result {
|
||||
// Check Number of monitors
|
||||
assert_eq!(monitors.len(), 1, "Expected one monitor");
|
||||
|
||||
// hmonitor
|
||||
assert_eq!(monitors[0].id(), 1);
|
||||
|
||||
// device name
|
||||
assert_eq!(monitors[0].name(), &String::from("DISPLAY1"));
|
||||
|
||||
// Device
|
||||
assert_eq!(monitors[0].device(), &String::from("ABC123"));
|
||||
|
||||
// Device ID
|
||||
assert_eq!(
|
||||
monitors[0].device_id(),
|
||||
&String::from("ABC123-4&123456&0&UID0")
|
||||
);
|
||||
|
||||
// Check monitor serial number id
|
||||
assert_eq!(
|
||||
monitors[0].serial_number_id,
|
||||
Some(String::from("SaucePackets123")),
|
||||
);
|
||||
} else {
|
||||
panic!("No monitors found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use color_eyre::Result;
|
||||
use komorebi_themes::colour::Rgb;
|
||||
use miow::pipe::connect;
|
||||
use net2::TcpStreamExt;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -11,20 +18,20 @@ use std::str::FromStr;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use miow::pipe::connect;
|
||||
use net2::TcpStreamExt;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use schemars::schema_for;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_FPS;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::build;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
@@ -41,16 +48,6 @@ use crate::core::SocketMessage;
|
||||
use crate::core::StateQuery;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
use crate::core::WindowKind;
|
||||
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
use crate::animation::ANIMATION_FPS;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::colour::Rgb;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::current_virtual_desktop;
|
||||
use crate::monitor::MonitorInformation;
|
||||
use crate::notify_subscribers;
|
||||
@@ -209,7 +206,9 @@ impl WindowManager {
|
||||
let initial_state = State::from(self.as_ref());
|
||||
|
||||
match message {
|
||||
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
|
||||
SocketMessage::CycleFocusEmptyWorkspace(_)
|
||||
| SocketMessage::CycleFocusWorkspace(_)
|
||||
| SocketMessage::FocusWorkspaceNumber(_) => {
|
||||
if let Some(monitor) = self.focused_monitor_mut() {
|
||||
let idx = monitor.focused_workspace_idx();
|
||||
monitor.set_last_focused_workspace(Option::from(idx));
|
||||
@@ -252,9 +251,11 @@ impl WindowManager {
|
||||
if let Some((monitor_idx, workspace_idx)) = monitor_workspace_indices {
|
||||
if monitor_idx != focused_monitor_idx {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
|
||||
if workspace_idx != focused_workspace_idx {
|
||||
let focused_ws_idx = self.focused_workspace_idx()?;
|
||||
if focused_ws_idx != workspace_idx {
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
} else if workspace_idx != focused_workspace_idx {
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
}
|
||||
@@ -333,11 +334,9 @@ impl WindowManager {
|
||||
SocketMessage::UnstackAll => self.unstack_all()?,
|
||||
SocketMessage::CycleStack(direction) => {
|
||||
self.cycle_container_window_in_direction(direction)?;
|
||||
self.focused_window()?.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
SocketMessage::CycleStackIndex(direction) => {
|
||||
self.cycle_container_window_index_in_direction(direction)?;
|
||||
self.focused_window()?.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
SocketMessage::FocusStackWindow(idx) => {
|
||||
// In case you are using this command on a bar on a monitor
|
||||
@@ -348,7 +347,6 @@ impl WindowManager {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
self.focus_container_window(idx)?;
|
||||
self.focused_window()?.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
SocketMessage::ForceFocus => {
|
||||
let focused_window = self.focused_window()?;
|
||||
@@ -362,6 +360,41 @@ impl WindowManager {
|
||||
SocketMessage::Minimize => {
|
||||
Window::from(WindowsApi::foreground_window()?).minimize();
|
||||
}
|
||||
SocketMessage::LockMonitorWorkspaceContainer(
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
container_idx,
|
||||
) => {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.ok_or_eyre("no monitor at the given index")?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.ok_or_eyre("no workspace at the given index")?;
|
||||
|
||||
workspace.locked_containers.insert(container_idx);
|
||||
}
|
||||
SocketMessage::UnlockMonitorWorkspaceContainer(
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
container_idx,
|
||||
) => {
|
||||
let monitor = self
|
||||
.monitors_mut()
|
||||
.get_mut(monitor_idx)
|
||||
.ok_or_eyre("no monitor at the given index")?;
|
||||
|
||||
let workspace = monitor
|
||||
.workspaces_mut()
|
||||
.get_mut(workspace_idx)
|
||||
.ok_or_eyre("no workspace at the given index")?;
|
||||
|
||||
workspace.locked_containers.remove(&container_idx);
|
||||
}
|
||||
SocketMessage::ToggleLock => self.toggle_lock()?,
|
||||
SocketMessage::ToggleFloat => self.toggle_float()?,
|
||||
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
|
||||
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
|
||||
@@ -607,6 +640,67 @@ impl WindowManager {
|
||||
SocketMessage::AdjustWorkspacePadding(sizing, adjustment) => {
|
||||
self.adjust_workspace_padding(sizing, adjustment)?;
|
||||
}
|
||||
SocketMessage::MoveContainerToLastWorkspace => {
|
||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||
// secondary monitor where the cursor is focused will be used as the target for
|
||||
// the workspace switch op
|
||||
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
|
||||
if monitor_idx != self.focused_monitor_idx() {
|
||||
if let Some(monitor) = self.monitors().get(monitor_idx) {
|
||||
if let Some(workspace) = monitor.focused_workspace() {
|
||||
if workspace.is_empty() {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let idx = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx();
|
||||
|
||||
if let Some(monitor) = self.focused_monitor_mut() {
|
||||
if let Some(last_focused_workspace) = monitor.last_focused_workspace() {
|
||||
self.move_container_to_workspace(last_focused_workspace, true, None)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.set_last_focused_workspace(Option::from(idx));
|
||||
}
|
||||
SocketMessage::SendContainerToLastWorkspace => {
|
||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||
// secondary monitor where the cursor is focused will be used as the target for
|
||||
// the workspace switch op
|
||||
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
|
||||
if monitor_idx != self.focused_monitor_idx() {
|
||||
if let Some(monitor) = self.monitors().get(monitor_idx) {
|
||||
if let Some(workspace) = monitor.focused_workspace() {
|
||||
if workspace.is_empty() {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let idx = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx();
|
||||
|
||||
if let Some(monitor) = self.focused_monitor_mut() {
|
||||
if let Some(last_focused_workspace) = monitor.last_focused_workspace() {
|
||||
self.move_container_to_workspace(last_focused_workspace, false, None)?;
|
||||
}
|
||||
}
|
||||
self.focused_monitor_mut()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.set_last_focused_workspace(Option::from(idx));
|
||||
}
|
||||
SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => {
|
||||
self.move_container_to_workspace(workspace_idx, true, None)?;
|
||||
}
|
||||
@@ -904,6 +998,55 @@ impl WindowManager {
|
||||
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
SocketMessage::CycleFocusEmptyWorkspace(direction) => {
|
||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||
// secondary monitor where the cursor is focused will be used as the target for
|
||||
// the workspace switch op
|
||||
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
|
||||
if monitor_idx != self.focused_monitor_idx() {
|
||||
if let Some(monitor) = self.monitors().get(monitor_idx) {
|
||||
if let Some(workspace) = monitor.focused_workspace() {
|
||||
if workspace.is_empty() {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let focused_monitor = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
||||
|
||||
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
|
||||
let workspaces = focused_monitor.workspaces().len();
|
||||
|
||||
let mut empty_workspaces = vec![];
|
||||
|
||||
for (idx, w) in focused_monitor.workspaces().iter().enumerate() {
|
||||
if w.is_empty() {
|
||||
empty_workspaces.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if !empty_workspaces.is_empty() {
|
||||
let mut workspace_idx = direction.next_idx(
|
||||
focused_workspace_idx,
|
||||
NonZeroUsize::new(workspaces)
|
||||
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
|
||||
);
|
||||
|
||||
while !empty_workspaces.contains(&workspace_idx) {
|
||||
workspace_idx = direction.next_idx(
|
||||
workspace_idx,
|
||||
NonZeroUsize::new(workspaces)
|
||||
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
|
||||
);
|
||||
}
|
||||
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::CloseWorkspace => {
|
||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||
// secondary monitor where the cursor is focused will be used as the target for
|
||||
@@ -1054,11 +1197,33 @@ impl WindowManager {
|
||||
WorkspaceLayer::Tiling => {
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
|
||||
for (i, window) in workspace.floating_windows().iter().enumerate() {
|
||||
if i == 0 {
|
||||
let focused_idx = workspace.focused_floating_window_idx();
|
||||
let mut window_idx_pairs = workspace
|
||||
.floating_windows_mut()
|
||||
.make_contiguous()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by window area
|
||||
window_idx_pairs.sort_by_key(|(_, w)| {
|
||||
let rect = WindowsApi::window_rect(w.hwnd).unwrap_or_default();
|
||||
rect.right * rect.bottom
|
||||
});
|
||||
window_idx_pairs.reverse();
|
||||
|
||||
for (i, window) in window_idx_pairs {
|
||||
if i == focused_idx {
|
||||
to_focus = Some(*window);
|
||||
} else {
|
||||
window.raise()?;
|
||||
}
|
||||
window.raise()?;
|
||||
}
|
||||
|
||||
if let Some(focused_window) = &to_focus {
|
||||
// The focused window should be the last one raised to make sure it is
|
||||
// on top
|
||||
focused_window.raise()?;
|
||||
}
|
||||
|
||||
for container in workspace.containers() {
|
||||
@@ -1080,7 +1245,19 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
for window in workspace.floating_windows() {
|
||||
let mut window_idx_pairs = workspace
|
||||
.floating_windows_mut()
|
||||
.make_contiguous()
|
||||
.iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by window area
|
||||
window_idx_pairs.sort_by_key(|w| {
|
||||
let rect = WindowsApi::window_rect(w.hwnd).unwrap_or_default();
|
||||
rect.right * rect.bottom
|
||||
});
|
||||
|
||||
for window in window_idx_pairs {
|
||||
window.lower()?;
|
||||
}
|
||||
}
|
||||
@@ -1108,7 +1285,7 @@ impl WindowManager {
|
||||
);
|
||||
}
|
||||
SocketMessage::DisplayIndexPreference(index_preference, ref display) => {
|
||||
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.write();
|
||||
display_index_preferences.insert(index_preference, display.clone());
|
||||
}
|
||||
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {
|
||||
@@ -1200,6 +1377,7 @@ impl WindowManager {
|
||||
.focused_workspace_name()
|
||||
.unwrap_or_else(|| focused_monitor.focused_workspace_idx().to_string())
|
||||
}
|
||||
StateQuery::Version => build::RUST_VERSION.to_string(),
|
||||
};
|
||||
|
||||
reply.write_all(response.as_bytes())?;
|
||||
@@ -1737,6 +1915,10 @@ impl WindowManager {
|
||||
WindowKind::Unfocused => {
|
||||
border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
}
|
||||
WindowKind::UnfocusedLocked => {
|
||||
border_manager::UNFOCUSED_LOCKED
|
||||
.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
}
|
||||
WindowKind::Floating => {
|
||||
border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
}
|
||||
@@ -1828,35 +2010,49 @@ impl WindowManager {
|
||||
*STACKBAR_FONT_FAMILY.lock() = font_family.clone();
|
||||
}
|
||||
SocketMessage::ApplicationSpecificConfigurationSchema => {
|
||||
let asc = schema_for!(Vec<ApplicationConfiguration>);
|
||||
let schema = serde_json::to_string_pretty(&asc)?;
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let asc = schemars::schema_for!(
|
||||
Vec<crate::core::config_generation::ApplicationConfiguration>
|
||||
);
|
||||
let schema = serde_json::to_string_pretty(&asc)?;
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
}
|
||||
}
|
||||
SocketMessage::NotificationSchema => {
|
||||
let notification = schema_for!(Notification);
|
||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let notification = schemars::schema_for!(Notification);
|
||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
}
|
||||
}
|
||||
SocketMessage::SocketSchema => {
|
||||
let socket_message = schema_for!(SocketMessage);
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let socket_message = schemars::schema_for!(SocketMessage);
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
}
|
||||
}
|
||||
SocketMessage::StaticConfigSchema => {
|
||||
let settings = SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = false;
|
||||
s.option_add_null_type = false;
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let settings = schemars::gen::SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = false;
|
||||
s.option_add_null_type = false;
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
}
|
||||
}
|
||||
SocketMessage::GenerateStaticConfig => {
|
||||
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
|
||||
@@ -1896,8 +2092,8 @@ impl WindowManager {
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
}
|
||||
SocketMessage::Theme(theme) => {
|
||||
theme_manager::send_notification(theme);
|
||||
SocketMessage::Theme(ref theme) => {
|
||||
theme_manager::send_notification(*theme.clone());
|
||||
}
|
||||
// Deprecated commands
|
||||
SocketMessage::AltFocusHack(_)
|
||||
@@ -2008,3 +2204,73 @@ pub fn read_commands_tcp(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::monitor;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::Rect;
|
||||
use crate::SocketMessage;
|
||||
use crate::WindowManagerEvent;
|
||||
use crossbeam_channel::bounded;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use uds_windows::UnixStream;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn send_socket_message(socket: &PathBuf, message: SocketMessage) {
|
||||
let mut stream = UnixStream::connect(socket).unwrap();
|
||||
stream
|
||||
.set_write_timeout(Some(Duration::from_secs(1)))
|
||||
.unwrap();
|
||||
stream
|
||||
.write_all(serde_json::to_string(&message).unwrap().as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_receive_socket_message() {
|
||||
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
bounded(1);
|
||||
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
|
||||
let socket_path = PathBuf::from(&socket_name);
|
||||
let mut wm = WindowManager::new(receiver, Some(socket_path.clone())).unwrap();
|
||||
let m = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"TestMonitor".to_string(),
|
||||
"TestDevice".to_string(),
|
||||
"TestDeviceID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
wm.monitors_mut().push_back(m);
|
||||
|
||||
// send a message
|
||||
send_socket_message(&socket_path, SocketMessage::FocusWorkspaceNumber(5));
|
||||
|
||||
let (stream, _) = wm.command_listener.accept().unwrap();
|
||||
let reader = BufReader::new(stream.try_clone().unwrap());
|
||||
let next = reader.lines().next();
|
||||
|
||||
// read and deserialize the message
|
||||
let message_string = next.unwrap().unwrap();
|
||||
let message = SocketMessage::from_str(&message_string).unwrap();
|
||||
assert!(matches!(message, SocketMessage::FocusWorkspaceNumber(5)));
|
||||
|
||||
// process the message
|
||||
wm.process_command(message, stream).unwrap();
|
||||
|
||||
// check the updated window manager state
|
||||
assert_eq!(wm.focused_workspace_idx().unwrap(), 5);
|
||||
|
||||
std::fs::remove_file(socket_path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
@@ -27,12 +25,10 @@ use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace_reconciliator;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::Window;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
@@ -304,34 +300,7 @@ impl WindowManager {
|
||||
let focused_workspace_idx =
|
||||
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
|
||||
let mut needs_reconciliation = false;
|
||||
|
||||
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if focused_pair != (*m_idx, *w_idx) {
|
||||
// At this point we know we are going to send a notification to the workspace reconciliator
|
||||
// So we get the topmost window returned by EnumWindows, which is almost always the window
|
||||
// that has been selected by alt-tab
|
||||
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
|
||||
if let Some(first) =
|
||||
alt_tab_windows.iter().find(|w| w.title().is_ok())
|
||||
{
|
||||
// If our record of this HWND hasn't been updated in over a minute
|
||||
let mut instant = ALT_TAB_HWND_INSTANT.lock();
|
||||
if instant.elapsed().gt(&Duration::from_secs(1)) {
|
||||
// Update our record with the HWND we just found
|
||||
ALT_TAB_HWND.store(Some(first.hwnd));
|
||||
// Update the timestamp of our record
|
||||
*instant = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workspace_reconciliator::send_notification(*m_idx, *w_idx);
|
||||
needs_reconciliation = true;
|
||||
}
|
||||
}
|
||||
let mut needs_reconciliation = None;
|
||||
|
||||
// There are some applications such as Firefox where, if they are focused when a
|
||||
// workspace switch takes place, it will fire an additional Show event, which will
|
||||
@@ -340,6 +309,23 @@ impl WindowManager {
|
||||
// duplicates across multiple workspaces, as it results in ghost layout tiles.
|
||||
let mut proceed = true;
|
||||
|
||||
// Check for potential `alt-tab` event
|
||||
if matches!(
|
||||
event,
|
||||
WindowManagerEvent::Uncloak(_, _) | WindowManagerEvent::Show(_, _)
|
||||
) {
|
||||
needs_reconciliation = self.needs_reconciliation(window)?;
|
||||
|
||||
if let Some((m_idx, ws_idx)) = needs_reconciliation {
|
||||
self.perform_reconciliation(window, (m_idx, ws_idx))?;
|
||||
|
||||
// Since there was a reconciliation after an `alt-tab`, that means this
|
||||
// window is already handled by komorebi so we shouldn't proceed with
|
||||
// adding it as a new window.
|
||||
proceed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if let Some(focused_workspace_idx) = self
|
||||
.monitors()
|
||||
@@ -368,7 +354,7 @@ impl WindowManager {
|
||||
let workspace_contains_window = workspace.contains_window(window.hwnd);
|
||||
let monocle_container = workspace.monocle_container().clone();
|
||||
|
||||
if !workspace_contains_window && !needs_reconciliation {
|
||||
if !workspace_contains_window && needs_reconciliation.is_none() {
|
||||
let floating_applications = FLOATING_APPLICATIONS.lock();
|
||||
let mut should_float = false;
|
||||
|
||||
@@ -395,8 +381,19 @@ impl WindowManager {
|
||||
&& !matches!(event, WindowManagerEvent::Manage(_)));
|
||||
|
||||
if behaviour.float_override {
|
||||
workspace.floating_windows_mut().push(window);
|
||||
// Center floating windows if we are already on the `Floating`
|
||||
// layer and the window doesn't match a `floating_windows` rule and
|
||||
// the workspace is not a floating workspace
|
||||
let center_spawned_floats =
|
||||
matches!(workspace.layer, WorkspaceLayer::Floating)
|
||||
&& !should_float
|
||||
&& workspace.tile;
|
||||
workspace.floating_windows_mut().push_back(window);
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
if center_spawned_floats {
|
||||
let mut floating_window = window;
|
||||
floating_window.center(&workspace.globals().work_area)?;
|
||||
}
|
||||
self.update_focused_workspace(false, false)?;
|
||||
} else {
|
||||
match behaviour.current_behaviour {
|
||||
@@ -619,7 +616,7 @@ impl WindowManager {
|
||||
window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
} else if window_management_behaviour.float_override {
|
||||
workspace.floating_windows_mut().push(window);
|
||||
workspace.floating_windows_mut().push_back(window);
|
||||
self.update_focused_workspace(false, false)?;
|
||||
} else {
|
||||
match window_management_behaviour.current_behaviour {
|
||||
@@ -742,4 +739,119 @@ impl WindowManager {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if this window is from another unfocused workspace or is an unfocused window on a
|
||||
/// stack container. If it is it will return the monitor/workspace index pair of this window so
|
||||
/// that a reconciliation of that monitor/workspace can be done.
|
||||
fn needs_reconciliation(&self, window: Window) -> color_eyre::Result<Option<(usize, usize)>> {
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
let focused_workspace_idx =
|
||||
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
|
||||
let mut needs_reconciliation = None;
|
||||
|
||||
if let Some((m_idx, ws_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if (*m_idx, *ws_idx) == focused_pair {
|
||||
if let Some(target_workspace) = self
|
||||
.monitors()
|
||||
.get(*m_idx)
|
||||
.and_then(|m| m.workspaces().get(*ws_idx))
|
||||
{
|
||||
if let Some(monocle_with_window) = target_workspace
|
||||
.monocle_container()
|
||||
.as_ref()
|
||||
.and_then(|m| m.contains_window(window.hwnd).then_some(m))
|
||||
{
|
||||
if monocle_with_window.focused_window() != Some(&window) {
|
||||
tracing::debug!("Needs reconciliation within a monocled stack");
|
||||
needs_reconciliation = Some((*m_idx, *ws_idx));
|
||||
}
|
||||
} else {
|
||||
let c_idx = target_workspace.container_idx_for_window(window.hwnd);
|
||||
|
||||
if let Some(target_container) =
|
||||
c_idx.and_then(|c_idx| target_workspace.containers().get(c_idx))
|
||||
{
|
||||
if target_container.focused_window() != Some(&window) {
|
||||
tracing::debug!(
|
||||
"Needs reconciliation within a stack on the focused workspace"
|
||||
);
|
||||
needs_reconciliation = Some((*m_idx, *ws_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("Needs reconciliation for a different monitor/workspace pair");
|
||||
needs_reconciliation = Some((*m_idx, *ws_idx));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(needs_reconciliation)
|
||||
}
|
||||
|
||||
/// When there was an `alt-tab` to a hidden window we need to perform a reconciliation, meaning
|
||||
/// we need to update the focused monitor, workspace, container and window indices to the ones
|
||||
/// corresponding to the window the user just alt-tabbed into.
|
||||
fn perform_reconciliation(
|
||||
&mut self,
|
||||
window: Window,
|
||||
reconciliation_pair: (usize, usize),
|
||||
) -> color_eyre::Result<()> {
|
||||
let (m_idx, ws_idx) = reconciliation_pair;
|
||||
|
||||
tracing::debug!("performing reconciliation");
|
||||
self.focus_monitor(m_idx)?;
|
||||
let mouse_follows_focus = self.mouse_follows_focus;
|
||||
let offset = self.work_area_offset;
|
||||
|
||||
if let Some(monitor) = self.focused_monitor_mut() {
|
||||
if ws_idx != monitor.focused_workspace_idx() {
|
||||
let previous_idx = monitor.focused_workspace_idx();
|
||||
monitor.set_last_focused_workspace(Option::from(previous_idx));
|
||||
monitor.focus_workspace(ws_idx)?;
|
||||
}
|
||||
if let Some(workspace) = monitor.focused_workspace_mut() {
|
||||
let mut layer = WorkspaceLayer::Tiling;
|
||||
if let Some((monocle, idx)) = workspace
|
||||
.monocle_container_mut()
|
||||
.as_mut()
|
||||
.and_then(|m| m.idx_for_window(window.hwnd).map(|i| (m, i)))
|
||||
{
|
||||
monocle.focus_window(idx);
|
||||
} else if workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.any(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
layer = WorkspaceLayer::Floating;
|
||||
} else if !workspace
|
||||
.maximized_window()
|
||||
.is_some_and(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
// If the window is the maximized window do nothing, else we
|
||||
// reintegrate the monocle if it exists and then focus the
|
||||
// container
|
||||
if workspace.monocle_container().is_some() {
|
||||
tracing::info!("disabling monocle");
|
||||
for container in workspace.containers_mut() {
|
||||
container.restore();
|
||||
}
|
||||
for window in workspace.floating_windows_mut() {
|
||||
window.restore();
|
||||
}
|
||||
workspace.reintegrate_monocle_container()?;
|
||||
}
|
||||
workspace.focus_container_by_window(window.hwnd)?;
|
||||
}
|
||||
workspace.set_layer(layer);
|
||||
}
|
||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
monitor.update_focused_workspace(offset)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Ring<T> {
|
||||
elements: VecDeque<T>,
|
||||
focused: usize,
|
||||
@@ -76,4 +76,36 @@ macro_rules! impl_ring_elements {
|
||||
}
|
||||
}
|
||||
};
|
||||
// This allows passing a different name to be used for the functions. For instance, the
|
||||
// `floating_windows` ring calls this as:
|
||||
// ```rust
|
||||
// impl_ring_elements!(Workspace, Window, "floating_window");
|
||||
// ```
|
||||
// Which allows using the `Window` element but name the functions as `floating_window`
|
||||
($name:ty, $element:ident, $el_name:literal) => {
|
||||
paste::paste! {
|
||||
impl $name {
|
||||
pub const fn [<$el_name:lower s>](&self) -> &VecDeque<$element> {
|
||||
self.[<$el_name:lower s>].elements()
|
||||
}
|
||||
|
||||
pub fn [<$el_name:lower s_mut>](&mut self) -> &mut VecDeque<$element> {
|
||||
self.[<$el_name:lower s>].elements_mut()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn [<focused_ $el_name:lower>](&self) -> Option<&$element> {
|
||||
self.[<$el_name:lower s>].focused()
|
||||
}
|
||||
|
||||
pub const fn [<focused_ $el_name:lower _idx>](&self) -> usize {
|
||||
self.[<$el_name:lower s>].focused_idx()
|
||||
}
|
||||
|
||||
pub fn [<focused_ $el_name:lower _mut>](&mut self) -> Option<&mut $element> {
|
||||
self.[<$el_name:lower s>].focused_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ impl Stackbar {
|
||||
}
|
||||
|
||||
// Restore the window corresponding to the tab we have clicked
|
||||
window.restore();
|
||||
window.restore_with_border(false);
|
||||
if let Err(err) = window.focus(false) {
|
||||
tracing::error!(
|
||||
"stackbar WMLBUTTONDOWN focus error: hwnd {} ({})",
|
||||
@@ -361,7 +361,7 @@ impl Stackbar {
|
||||
} else {
|
||||
// Hide any windows in the stack that don't correspond to the window
|
||||
// we have clicked
|
||||
window.hide();
|
||||
window.hide_with_border(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,34 @@ use crate::animation::ANIMATION_FPS;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::animation::DEFAULT_ANIMATION_FPS;
|
||||
use crate::asc::ApplicationSpecificConfiguration;
|
||||
use crate::asc::AscApplicationRulesOrSchema;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::ZOrder;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::colour::Colour;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::ApplicationConfigurationGenerator;
|
||||
use crate::core::config_generation::ApplicationOptions;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use crate::core::resolve_home_path;
|
||||
use crate::core::AnimationStyle;
|
||||
use crate::core::BorderImplementation;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::DefaultLayout;
|
||||
use crate::core::FocusFollowsMouseImplementation;
|
||||
use crate::core::HidingBehaviour;
|
||||
use crate::core::Layout;
|
||||
use crate::core::MoveBehaviour;
|
||||
use crate::core::OperationBehaviour;
|
||||
use crate::core::Rect;
|
||||
use crate::core::SocketMessage;
|
||||
use crate::core::StackbarLabel;
|
||||
use crate::core::StackbarMode;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
use crate::core::WindowManagementBehaviour;
|
||||
use crate::current_virtual_desktop;
|
||||
use crate::monitor;
|
||||
use crate::monitor::Monitor;
|
||||
@@ -39,6 +59,7 @@ use crate::workspace::Workspace;
|
||||
use crate::AspectRatio;
|
||||
use crate::Axis;
|
||||
use crate::CrossBoundaryBehaviour;
|
||||
use crate::FloatingLayerBehaviour;
|
||||
use crate::PredefinedAspectRatio;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
@@ -61,35 +82,13 @@ use crate::TRANSPARENCY_BLACKLIST;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
|
||||
use crate::asc::ApplicationSpecificConfiguration;
|
||||
use crate::asc::AscApplicationRulesOrSchema;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::ApplicationConfigurationGenerator;
|
||||
use crate::core::config_generation::ApplicationOptions;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use crate::core::resolve_home_path;
|
||||
use crate::core::AnimationStyle;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::DefaultLayout;
|
||||
use crate::core::FocusFollowsMouseImplementation;
|
||||
use crate::core::HidingBehaviour;
|
||||
use crate::core::Layout;
|
||||
use crate::core::MoveBehaviour;
|
||||
use crate::core::OperationBehaviour;
|
||||
use crate::core::Rect;
|
||||
use crate::core::SocketMessage;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
use crate::core::WindowManagementBehaviour;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use hotwatch::EventKind;
|
||||
use hotwatch::Hotwatch;
|
||||
use komorebi_themes::colour::Colour;
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -102,7 +101,8 @@ use std::sync::Arc;
|
||||
use uds_windows::UnixListener;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct BorderColours {
|
||||
/// Border colour when the container contains a single window
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -119,9 +119,64 @@ pub struct BorderColours {
|
||||
/// Border colour when the container is unfocused
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused: Option<Colour>,
|
||||
/// Border colour when the container is unfocused and locked
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused_locked: Option<Colour>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ThemeOptions {
|
||||
/// Specify Light or Dark variant for theme generation (default: Dark)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub theme_variant: Option<komorebi_themes::ThemeVariant>,
|
||||
/// Border colour when the container contains a single window (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub single_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container contains multiple windows (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stack_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is in monocle mode (default: Base0F)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub monocle_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the window is floating (default: Base09)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub floating_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused and locked (default: Base08)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused_locked_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar focused tab text colour (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stackbar_focused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar unfocused tab text colour (default: Base05)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar tab background colour (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stackbar_background: Option<komorebi_themes::Base16Value>,
|
||||
/// Komorebi status bar accent (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bar_accent: Option<komorebi_themes::Base16Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Wallpaper {
|
||||
/// Path to the wallpaper image file
|
||||
pub path: PathBuf,
|
||||
/// Generate and apply Base16 theme for this wallpaper (default: true)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub generate_theme: Option<bool>,
|
||||
/// Specify Light or Dark variant for theme generation (default: Dark)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub theme_options: Option<ThemeOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct WorkspaceConfig {
|
||||
/// Name
|
||||
pub name: String,
|
||||
@@ -164,6 +219,12 @@ pub struct WorkspaceConfig {
|
||||
/// Specify an axis on which to flip the selected layout (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub layout_flip: Option<Axis>,
|
||||
/// Determine what happens to a new window when the Floating workspace layer is active (default: Tile)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
|
||||
/// Specify a wallpaper for this workspace
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub wallpaper: Option<Wallpaper>,
|
||||
}
|
||||
|
||||
impl From<&Workspace> for WorkspaceConfig {
|
||||
@@ -239,11 +300,14 @@ impl From<&Workspace> for WorkspaceConfig {
|
||||
window_container_behaviour_rules: Option::from(window_container_behaviour_rules),
|
||||
float_override: *value.float_override(),
|
||||
layout_flip: value.layout_flip(),
|
||||
floating_layer_behaviour: Option::from(*value.floating_layer_behaviour()),
|
||||
wallpaper: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct MonitorConfig {
|
||||
/// Workspace configurations
|
||||
pub workspaces: Vec<WorkspaceConfig>,
|
||||
@@ -301,8 +365,19 @@ impl From<&Monitor> for MonitorConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
/// The `komorebi.json` static configuration file reference for `v0.1.35`
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum AppSpecificConfigurationPath {
|
||||
/// A single applications.json file
|
||||
Single(PathBuf),
|
||||
/// Multiple applications.json files
|
||||
Multiple(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// The `komorebi.json` static configuration file reference for `v0.1.36`
|
||||
pub struct StaticConfig {
|
||||
/// DEPRECATED from v0.1.22: no longer required
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -340,7 +415,7 @@ pub struct StaticConfig {
|
||||
pub mouse_follows_focus: Option<bool>,
|
||||
/// Path to applications.json from komorebi-application-specific-configurations (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub app_specific_configuration_path: Option<PathBuf>,
|
||||
pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,
|
||||
/// Width of the window border (default: 8)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "active_window_border_width")]
|
||||
@@ -449,7 +524,8 @@ pub struct StaticConfig {
|
||||
pub floating_window_aspect_ratio: Option<AspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct AnimationsConfig {
|
||||
/// Enable or disable animations (default: false)
|
||||
pub enabled: PerAnimationPrefixConfig<bool>,
|
||||
@@ -464,7 +540,8 @@ pub struct AnimationsConfig {
|
||||
pub fps: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "palette")]
|
||||
pub enum KomorebiTheme {
|
||||
/// A theme from catppuccin-egui
|
||||
@@ -486,6 +563,9 @@ pub enum KomorebiTheme {
|
||||
/// Border colour when the container is unfocused (default: Base)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_border: Option<komorebi_themes::CatppuccinValue>,
|
||||
/// Border colour when the container is unfocused and locked (default: Red)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_locked_border: Option<komorebi_themes::CatppuccinValue>,
|
||||
/// Stackbar focused tab text colour (default: Green)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_focused_text: Option<komorebi_themes::CatppuccinValue>,
|
||||
@@ -518,6 +598,44 @@ pub enum KomorebiTheme {
|
||||
/// Border colour when the container is unfocused (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused and locked (default: Base08)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_locked_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar focused tab text colour (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar unfocused tab text colour (default: Base05)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar tab background colour (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_background: Option<komorebi_themes::Base16Value>,
|
||||
/// Komorebi status bar accent (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
bar_accent: Option<komorebi_themes::Base16Value>,
|
||||
},
|
||||
/// A custom Base16 theme
|
||||
Custom {
|
||||
/// Colours of the custom Base16 theme palette
|
||||
colours: Box<komorebi_themes::Base16ColourPalette>,
|
||||
/// Border colour when the container contains a single window (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
single_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container contains multiple windows (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stack_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is in monocle mode (default: Base0F)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
monocle_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the window is floating (default: Base09)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
floating_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused and locked (default: Base08)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_locked_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar focused tab text colour (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
|
||||
@@ -614,7 +732,8 @@ impl StaticConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct TabsConfig {
|
||||
/// Width of a stackbar tab
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -636,7 +755,8 @@ pub struct TabsConfig {
|
||||
pub font_size: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct StackbarConfig {
|
||||
/// Stackbar height
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -673,6 +793,9 @@ impl From<&WindowManager> for StaticConfig {
|
||||
unfocused: Option::from(Colour::from(
|
||||
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
||||
)),
|
||||
unfocused_locked: Option::from(Colour::from(
|
||||
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
|
||||
)),
|
||||
})
|
||||
};
|
||||
|
||||
@@ -734,7 +857,7 @@ impl From<&WindowManager> for StaticConfig {
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
|
||||
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()),
|
||||
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.read().clone()),
|
||||
stackbar: None,
|
||||
animation: None,
|
||||
theme: None,
|
||||
@@ -762,7 +885,7 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
if let Some(display_index_preferences) = &self.display_index_preferences {
|
||||
let mut preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let mut preferences = DISPLAY_INDEX_PREFERENCES.write();
|
||||
preferences.clone_from(display_index_preferences);
|
||||
}
|
||||
|
||||
@@ -854,6 +977,11 @@ impl StaticConfig {
|
||||
if let Some(unfocused) = colours.unfocused {
|
||||
border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if let Some(unfocused_locked) = colours.unfocused_locked {
|
||||
border_manager::UNFOCUSED_LOCKED
|
||||
.store(u32::from(unfocused_locked), Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
STYLE.store(self.border_style.unwrap_or_default());
|
||||
@@ -994,144 +1122,39 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
if let Some(theme) = &self.theme {
|
||||
theme_manager::send_notification(*theme);
|
||||
theme_manager::send_notification(theme.clone());
|
||||
}
|
||||
|
||||
if let Some(path) = &self.app_specific_configuration_path {
|
||||
match path.extension() {
|
||||
None => {}
|
||||
Some(ext) => match ext.to_string_lossy().to_string().as_str() {
|
||||
"yaml" => {
|
||||
tracing::info!("loading applications.yaml from: {}", path.display());
|
||||
let path = resolve_home_path(path)?;
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let asc = ApplicationConfigurationGenerator::load(&content)?;
|
||||
|
||||
for mut entry in asc {
|
||||
if let Some(rules) = &mut entry.ignore_identifiers {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut ignore_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(ref options) = entry.options {
|
||||
let options = options.clone();
|
||||
for o in options {
|
||||
match o {
|
||||
ApplicationOptions::ObjectNameChange => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
&mut object_name_change_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::Layered => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
&mut layered_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::TrayAndMultiWindow => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
&mut tray_and_multi_window_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::Force => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
&mut manage_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::BorderOverflow => {} // deprecated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match path {
|
||||
AppSpecificConfigurationPath::Single(path) => handle_asc_file(
|
||||
path,
|
||||
&mut ignore_identifiers,
|
||||
&mut object_name_change_identifiers,
|
||||
&mut layered_identifiers,
|
||||
&mut tray_and_multi_window_identifiers,
|
||||
&mut manage_identifiers,
|
||||
&mut floating_applications,
|
||||
&mut transparency_blacklist,
|
||||
&mut slow_application_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?,
|
||||
AppSpecificConfigurationPath::Multiple(paths) => {
|
||||
for path in paths {
|
||||
handle_asc_file(
|
||||
path,
|
||||
&mut ignore_identifiers,
|
||||
&mut object_name_change_identifiers,
|
||||
&mut layered_identifiers,
|
||||
&mut tray_and_multi_window_identifiers,
|
||||
&mut manage_identifiers,
|
||||
&mut floating_applications,
|
||||
&mut transparency_blacklist,
|
||||
&mut slow_application_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?
|
||||
}
|
||||
"json" => {
|
||||
tracing::info!("loading applications.json from: {}", path.display());
|
||||
let path = resolve_home_path(path)?;
|
||||
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
|
||||
|
||||
for entry in asc.values_mut() {
|
||||
match entry {
|
||||
AscApplicationRulesOrSchema::Schema(_) => {}
|
||||
AscApplicationRulesOrSchema::AscApplicationRules(entry) => {
|
||||
if let Some(rules) = &mut entry.ignore {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut ignore_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.manage {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut manage_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.floating {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut floating_applications,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.transparency_ignore {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut transparency_blacklist,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.tray_and_multi_window {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut tray_and_multi_window_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.layered {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut layered_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.object_name_change {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut object_name_change_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.slow_application {
|
||||
populate_rules(
|
||||
rules,
|
||||
&mut slow_application_identifiers,
|
||||
&mut regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1147,7 +1170,16 @@ impl StaticConfig {
|
||||
let mut value: Self = serde_json::from_str(&content)?;
|
||||
|
||||
if let Some(path) = &mut value.app_specific_configuration_path {
|
||||
*path = resolve_home_path(&*path)?;
|
||||
match path {
|
||||
AppSpecificConfigurationPath::Single(path) => {
|
||||
*path = resolve_home_path(&*path)?;
|
||||
}
|
||||
AppSpecificConfigurationPath::Multiple(paths) => {
|
||||
for path in paths {
|
||||
*path = resolve_home_path(&*path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(monitors) = &mut value.monitors {
|
||||
@@ -1270,7 +1302,7 @@ impl StaticConfig {
|
||||
let mut wm = wm.lock();
|
||||
|
||||
let configs_with_preference: Vec<_> =
|
||||
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
|
||||
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
|
||||
let mut configs_used = Vec::new();
|
||||
|
||||
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
||||
@@ -1280,7 +1312,7 @@ impl StaticConfig {
|
||||
let offset = wm.work_area_offset;
|
||||
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
||||
let preferred_config_idx = {
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
||||
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
|
||||
(monitor
|
||||
.serial_number_id()
|
||||
@@ -1379,7 +1411,7 @@ impl StaticConfig {
|
||||
.filter(|i| !configs_used.contains(i))
|
||||
{
|
||||
let id = {
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
||||
display_index_preferences.get(i).cloned()
|
||||
};
|
||||
if let (Some(id), Some(monitor_config)) =
|
||||
@@ -1439,7 +1471,7 @@ impl StaticConfig {
|
||||
value.apply_globals()?;
|
||||
|
||||
let configs_with_preference: Vec<_> =
|
||||
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
|
||||
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
|
||||
let mut configs_used = Vec::new();
|
||||
|
||||
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
||||
@@ -1449,7 +1481,7 @@ impl StaticConfig {
|
||||
let offset = wm.work_area_offset;
|
||||
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
||||
let preferred_config_idx = {
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
||||
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
|
||||
(monitor
|
||||
.serial_number_id()
|
||||
@@ -1551,7 +1583,7 @@ impl StaticConfig {
|
||||
.filter(|i| !configs_used.contains(i))
|
||||
{
|
||||
let id = {
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
||||
display_index_preferences.get(i).cloned()
|
||||
};
|
||||
if let (Some(id), Some(monitor_config)) =
|
||||
@@ -1715,3 +1747,164 @@ fn populate_rules(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_asc_file(
|
||||
path: &PathBuf,
|
||||
ignore_identifiers: &mut Vec<MatchingRule>,
|
||||
object_name_change_identifiers: &mut Vec<MatchingRule>,
|
||||
layered_identifiers: &mut Vec<MatchingRule>,
|
||||
tray_and_multi_window_identifiers: &mut Vec<MatchingRule>,
|
||||
manage_identifiers: &mut Vec<MatchingRule>,
|
||||
floating_applications: &mut Vec<MatchingRule>,
|
||||
transparency_blacklist: &mut Vec<MatchingRule>,
|
||||
slow_application_identifiers: &mut Vec<MatchingRule>,
|
||||
regex_identifiers: &mut HashMap<String, Regex>,
|
||||
) -> Result<()> {
|
||||
match path.extension() {
|
||||
None => {}
|
||||
Some(ext) => match ext.to_string_lossy().to_string().as_str() {
|
||||
"yaml" => {
|
||||
tracing::info!("loading applications.yaml from: {}", path.display());
|
||||
let path = resolve_home_path(path)?;
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let asc = ApplicationConfigurationGenerator::load(&content)?;
|
||||
|
||||
for mut entry in asc {
|
||||
if let Some(rules) = &mut entry.ignore_identifiers {
|
||||
populate_rules(rules, ignore_identifiers, regex_identifiers)?;
|
||||
}
|
||||
|
||||
if let Some(ref options) = entry.options {
|
||||
let options = options.clone();
|
||||
for o in options {
|
||||
match o {
|
||||
ApplicationOptions::ObjectNameChange => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
object_name_change_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::Layered => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
layered_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::TrayAndMultiWindow => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
tray_and_multi_window_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::Force => {
|
||||
populate_option(
|
||||
&mut entry,
|
||||
manage_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
ApplicationOptions::BorderOverflow => {} // deprecated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"json" => {
|
||||
tracing::info!("loading applications.json from: {}", path.display());
|
||||
let path = resolve_home_path(path)?;
|
||||
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
|
||||
|
||||
for entry in asc.values_mut() {
|
||||
match entry {
|
||||
AscApplicationRulesOrSchema::Schema(_) => {}
|
||||
AscApplicationRulesOrSchema::AscApplicationRules(entry) => {
|
||||
if let Some(rules) = &mut entry.ignore {
|
||||
populate_rules(rules, ignore_identifiers, regex_identifiers)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.manage {
|
||||
populate_rules(rules, manage_identifiers, regex_identifiers)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.floating {
|
||||
populate_rules(rules, floating_applications, regex_identifiers)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.transparency_ignore {
|
||||
populate_rules(rules, transparency_blacklist, regex_identifiers)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.tray_and_multi_window {
|
||||
populate_rules(
|
||||
rules,
|
||||
tray_and_multi_window_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.layered {
|
||||
populate_rules(rules, layered_identifiers, regex_identifiers)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.object_name_change {
|
||||
populate_rules(
|
||||
rules,
|
||||
object_name_change_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rules) = &mut entry.slow_application {
|
||||
populate_rules(
|
||||
rules,
|
||||
slow_application_identifiers,
|
||||
regex_identifiers,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::StaticConfig;
|
||||
|
||||
#[test]
|
||||
fn backwards_compat() {
|
||||
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
|
||||
let docs = vec![
|
||||
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27",
|
||||
"0.1.28", "0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33", "0.1.34",
|
||||
];
|
||||
|
||||
let mut versions = vec![];
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
for version in root {
|
||||
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
|
||||
versions.push((version, client.execute(request).unwrap().text().unwrap()));
|
||||
}
|
||||
|
||||
for version in docs {
|
||||
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
|
||||
versions.push((version, client.execute(request).unwrap().text().unwrap()));
|
||||
}
|
||||
|
||||
for (version, config) in versions {
|
||||
println!("{version}");
|
||||
StaticConfig::read_raw(&config).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ use crate::stackbar_manager;
|
||||
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||
use crate::Colour;
|
||||
use crate::KomorebiTheme;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use komorebi_themes::colour::Colour;
|
||||
use komorebi_themes::Base16Wrapper;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::OnceLock;
|
||||
@@ -76,6 +77,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
@@ -87,6 +89,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
@@ -112,6 +115,10 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
|
||||
.color32(name.as_theme());
|
||||
|
||||
let unfocused_locked_border = unfocused_locked_border
|
||||
.unwrap_or(komorebi_themes::CatppuccinValue::Red)
|
||||
.color32(name.as_theme());
|
||||
|
||||
let stackbar_focused_text = stackbar_focused_text
|
||||
.unwrap_or(komorebi_themes::CatppuccinValue::Green)
|
||||
.color32(name.as_theme());
|
||||
@@ -130,6 +137,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
@@ -142,6 +150,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
@@ -149,35 +158,39 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
} => {
|
||||
let single_border = single_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0D)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stack_border = stack_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let monocle_border = monocle_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0F)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let unfocused_border = unfocused_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let unfocused_locked_border = unfocused_locked_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base08)
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let floating_border = floating_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base09)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stackbar_focused_text = stackbar_focused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stackbar_unfocused_text = stackbar_unfocused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base05)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stackbar_background = stackbar_background
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
(
|
||||
single_border,
|
||||
@@ -185,6 +198,68 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
)
|
||||
}
|
||||
KomorebiTheme::Custom {
|
||||
colours,
|
||||
single_border,
|
||||
stack_border,
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
..
|
||||
} => {
|
||||
let single_border = single_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0D)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stack_border = stack_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let monocle_border = monocle_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0F)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let unfocused_border = unfocused_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let unfocused_locked_border = unfocused_locked_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base08)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let floating_border = floating_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base09)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stackbar_focused_text = stackbar_focused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stackbar_unfocused_text = stackbar_unfocused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base05)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stackbar_background = stackbar_background
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
(
|
||||
single_border,
|
||||
stack_border,
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
@@ -198,6 +273,10 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
border_manager::FLOATING.store(u32::from(Colour::from(floating_border)), Ordering::SeqCst);
|
||||
border_manager::UNFOCUSED
|
||||
.store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst);
|
||||
border_manager::UNFOCUSED_LOCKED.store(
|
||||
u32::from(Colour::from(unfocused_locked_border)),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
STACKBAR_TAB_BACKGROUND_COLOUR.store(
|
||||
u32::from(Colour::from(stackbar_background)),
|
||||
|
||||
@@ -10,14 +10,44 @@ use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_MANAGER;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::border_manager;
|
||||
use crate::com::SetCloak;
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use crate::core::ApplicationIdentifier;
|
||||
use crate::core::HidingBehaviour;
|
||||
use crate::core::Rect;
|
||||
use crate::focus_manager;
|
||||
use crate::stackbar_manager;
|
||||
use crate::styles::ExtendedWindowStyle;
|
||||
use crate::styles::WindowStyle;
|
||||
use crate::transparency_manager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::AnimationStyle;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::IGNORE_IDENTIFIERS;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::PERMAIGNORE_CLASSES;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
||||
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
||||
use crate::WSL2_UI_PROCESSES;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use regex::Regex;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
@@ -27,47 +57,15 @@ use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
|
||||
use crate::core::ApplicationIdentifier;
|
||||
use crate::core::HidingBehaviour;
|
||||
use crate::core::Rect;
|
||||
|
||||
use crate::styles::ExtendedWindowStyle;
|
||||
use crate::styles::WindowStyle;
|
||||
use crate::transparency_manager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::IGNORE_IDENTIFIERS;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::PERMAIGNORE_CLASSES;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::WSL2_UI_PROCESSES;
|
||||
|
||||
pub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);
|
||||
pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Window {
|
||||
pub hwnd: isize,
|
||||
}
|
||||
@@ -87,7 +85,8 @@ impl From<HWND> for Window {
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct WindowDetails {
|
||||
pub title: String,
|
||||
pub exe: String,
|
||||
@@ -299,9 +298,8 @@ impl RenderDispatcher for TransparencyRenderDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum AspectRatio {
|
||||
/// A predefined aspect ratio
|
||||
@@ -316,9 +314,8 @@ impl Default for AspectRatio {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PredefinedAspectRatio {
|
||||
/// 21:9
|
||||
Ultrawide,
|
||||
@@ -480,7 +477,7 @@ impl Window {
|
||||
WindowsApi::is_window_visible(self.hwnd)
|
||||
}
|
||||
|
||||
pub fn hide(self) {
|
||||
pub fn hide_with_border(self, hide_border: bool) {
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
|
||||
programmatically_hidden_hwnds.push(self.hwnd);
|
||||
@@ -492,9 +489,16 @@ impl Window {
|
||||
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd),
|
||||
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2),
|
||||
}
|
||||
if hide_border {
|
||||
border_manager::hide_border(self.hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(self) {
|
||||
pub fn hide(self) {
|
||||
self.hide_with_border(true);
|
||||
}
|
||||
|
||||
pub fn restore_with_border(self, restore_border: bool) {
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if let Some(idx) = programmatically_hidden_hwnds
|
||||
.iter()
|
||||
@@ -510,6 +514,13 @@ impl Window {
|
||||
}
|
||||
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0),
|
||||
}
|
||||
if restore_border {
|
||||
border_manager::show_border(self.hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(self) {
|
||||
self.restore_with_border(true);
|
||||
}
|
||||
|
||||
pub fn minimize(self) {
|
||||
@@ -742,8 +753,8 @@ impl Window {
|
||||
/// it raises it as well.
|
||||
pub fn raise(self) -> Result<()> {
|
||||
WindowsApi::raise_window(self.hwnd)?;
|
||||
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
|
||||
WindowsApi::raise_window(border.hwnd)?;
|
||||
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
|
||||
WindowsApi::raise_window(border_info.border_hwnd)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -754,8 +765,8 @@ impl Window {
|
||||
/// it lowers it as well.
|
||||
pub fn lower(self) -> Result<()> {
|
||||
WindowsApi::lower_window(self.hwnd)?;
|
||||
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
|
||||
WindowsApi::lower_window(border.hwnd)?;
|
||||
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
|
||||
WindowsApi::lower_window(border_info.border_hwnd)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -12,7 +11,8 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum WindowManagerEvent {
|
||||
Destroy(WinEvent, Window),
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use core::ffi::c_void;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::mem::size_of;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use std::path::Path;
|
||||
use windows::core::Result as WindowsCrateResult;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
@@ -47,6 +47,8 @@ use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use windows::Win32::System::Com::CoCreateInstance;
|
||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::Power::RegisterPowerSettingNotification;
|
||||
use windows::Win32::System::Power::HPOWERNOTIFY;
|
||||
@@ -72,6 +74,9 @@ use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
|
||||
use windows::Win32::UI::Shell::DesktopWallpaper;
|
||||
use windows::Win32::UI::Shell::IDesktopWallpaper;
|
||||
use windows::Win32::UI::Shell::DWPOS_FILL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
||||
@@ -141,6 +146,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
use windows_core::BOOL;
|
||||
use windows_core::HSTRING;
|
||||
|
||||
use crate::core::Rect;
|
||||
|
||||
@@ -153,6 +159,7 @@ use crate::windows_callbacks;
|
||||
use crate::Window;
|
||||
use crate::WindowManager;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
|
||||
macro_rules! as_ptr {
|
||||
@@ -258,7 +265,30 @@ impl WindowsApi {
|
||||
let monitors = &mut wm.monitors;
|
||||
let monitor_usr_idx_map = &mut wm.monitor_usr_idx_map;
|
||||
|
||||
'read: for display in win32_display_data::connected_displays_all().flatten() {
|
||||
let all_displays = win32_display_data::connected_displays_all()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut serial_id_map = HashMap::new();
|
||||
|
||||
for d in &all_displays {
|
||||
if let Some(id) = &d.serial_number_id {
|
||||
*serial_id_map.entry(id.clone()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for d in &all_displays {
|
||||
if let Some(id) = &d.serial_number_id {
|
||||
if serial_id_map.get(id).copied().unwrap_or_default() > 1 {
|
||||
let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();
|
||||
if !dupes.contains(id) {
|
||||
(*dupes).push(id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
'read: for mut display in all_displays {
|
||||
let path = display.device_path.clone();
|
||||
|
||||
let (device, device_id) = if path.is_empty() {
|
||||
@@ -281,6 +311,13 @@ impl WindowsApi {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(id) = &display.serial_number_id {
|
||||
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
|
||||
if dupes.contains(id) {
|
||||
display.serial_number_id = None;
|
||||
}
|
||||
}
|
||||
|
||||
let m = monitor::new(
|
||||
display.hmonitor,
|
||||
display.size.into(),
|
||||
@@ -299,7 +336,7 @@ impl WindowsApi {
|
||||
}
|
||||
}
|
||||
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
||||
for (index, id) in &*display_index_preferences {
|
||||
if m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || id.eq(m.device_id())
|
||||
{
|
||||
@@ -334,7 +371,7 @@ impl WindowsApi {
|
||||
// Rebuild monitor index map
|
||||
*monitor_usr_idx_map = HashMap::new();
|
||||
let mut added_monitor_idxs = Vec::new();
|
||||
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.lock() {
|
||||
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.read() {
|
||||
if let Some(m_idx) = monitors.elements().iter().position(|m| {
|
||||
m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || m.device_id() == id
|
||||
}) {
|
||||
@@ -972,7 +1009,7 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
|
||||
for display in win32_display_data::connected_displays_all().flatten() {
|
||||
for mut display in win32_display_data::connected_displays_all().flatten() {
|
||||
if display.hmonitor == hmonitor {
|
||||
let path = display.device_path;
|
||||
|
||||
@@ -990,6 +1027,13 @@ impl WindowsApi {
|
||||
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
||||
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
||||
|
||||
if let Some(id) = &display.serial_number_id {
|
||||
let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();
|
||||
if dupes.contains(id) {
|
||||
display.serial_number_id = None;
|
||||
}
|
||||
}
|
||||
|
||||
let monitor = monitor::new(
|
||||
hmonitor,
|
||||
display.size.into(),
|
||||
@@ -1160,7 +1204,7 @@ impl WindowsApi {
|
||||
pub fn create_border_window(
|
||||
name: PCWSTR,
|
||||
instance: isize,
|
||||
border: *const Border,
|
||||
border: *mut Border,
|
||||
) -> Result<isize> {
|
||||
unsafe {
|
||||
CreateWindowExW(
|
||||
@@ -1307,4 +1351,22 @@ impl WindowsApi {
|
||||
pub fn wts_register_session_notification(hwnd: isize) -> Result<()> {
|
||||
unsafe { WTSRegisterSessionNotification(HWND(as_ptr!(hwnd)), 1) }.process()
|
||||
}
|
||||
|
||||
pub fn set_wallpaper(path: &Path) -> Result<()> {
|
||||
let path = path.canonicalize()?;
|
||||
|
||||
let wallpaper: IDesktopWallpaper =
|
||||
unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL)? };
|
||||
|
||||
let wallpaper_path = HSTRING::from(path.to_str().unwrap_or_default());
|
||||
unsafe {
|
||||
wallpaper.SetPosition(DWPOS_FILL)?;
|
||||
}
|
||||
|
||||
// Set the wallpaper
|
||||
unsafe {
|
||||
wallpaper.SetWallpaper(PCWSTR::null(), PCWSTR::from_raw(wallpaper_path.as_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +105,16 @@ pub extern "system" fn win_event_hook(
|
||||
WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy
|
||||
) && !has_filtered_style(hwnd)
|
||||
{
|
||||
let border_window = border_manager::window_border(hwnd.0 as isize);
|
||||
let border_info = border_manager::window_border(hwnd.0 as isize);
|
||||
|
||||
if let Some(border) = border_window {
|
||||
if let Some(border_info) = border_info {
|
||||
unsafe {
|
||||
let _ =
|
||||
SendNotifyMessageW(border.hwnd(), event, WPARAM(0), LPARAM(hwnd.0 as isize));
|
||||
let _ = SendNotifyMessageW(
|
||||
border_info.hwnd(),
|
||||
event,
|
||||
WPARAM(0),
|
||||
LPARAM(hwnd.0 as isize),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![allow(clippy::use_self)]
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
@@ -89,7 +88,8 @@ use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display, JsonSchema)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
pub enum WinEvent {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,130 +0,0 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::WindowManager;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Notification {
|
||||
pub monitor_idx: usize,
|
||||
pub workspace_idx: usize,
|
||||
}
|
||||
|
||||
pub static ALT_TAB_HWND: AtomicCell<Option<isize>> = AtomicCell::new(None);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ALT_TAB_HWND_INSTANT: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
||||
}
|
||||
|
||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||
|
||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
|
||||
}
|
||||
|
||||
fn event_tx() -> Sender<Notification> {
|
||||
channel().0.clone()
|
||||
}
|
||||
|
||||
fn event_rx() -> Receiver<Notification> {
|
||||
channel().1.clone()
|
||||
}
|
||||
|
||||
pub fn send_notification(monitor_idx: usize, workspace_idx: usize) {
|
||||
if event_tx()
|
||||
.try_send(Notification {
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
tracing::warn!("channel is full; dropping notification")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
if cfg!(debug_assertions) {
|
||||
tracing::error!("restarting failed thread: {:?}", error)
|
||||
} else {
|
||||
tracing::error!("restarting failed thread: {}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("listening");
|
||||
|
||||
let receiver = event_rx();
|
||||
let arc = wm.clone();
|
||||
|
||||
for notification in receiver {
|
||||
tracing::info!("running reconciliation");
|
||||
|
||||
let mut wm = wm.lock();
|
||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
||||
let focused_workspace_idx =
|
||||
wm.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
let updated_pair = (notification.monitor_idx, notification.workspace_idx);
|
||||
|
||||
if focused_pair != updated_pair {
|
||||
wm.focus_monitor(notification.monitor_idx)?;
|
||||
let mouse_follows_focus = wm.mouse_follows_focus;
|
||||
|
||||
if let Some(monitor) = wm.focused_monitor_mut() {
|
||||
let previous_idx = monitor.focused_workspace_idx();
|
||||
monitor.set_last_focused_workspace(Option::from(previous_idx));
|
||||
monitor.focus_workspace(notification.workspace_idx)?;
|
||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
}
|
||||
|
||||
// Drop our lock on the window manager state here to not slow down updates
|
||||
drop(wm);
|
||||
|
||||
// Check if there was an alt-tab across workspaces in the last second
|
||||
if let Some(hwnd) = ALT_TAB_HWND.load() {
|
||||
if ALT_TAB_HWND_INSTANT
|
||||
.lock()
|
||||
.elapsed()
|
||||
.lt(&Duration::from_secs(1))
|
||||
{
|
||||
// Sleep for 100 millis to let other events pass
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
tracing::info!("focusing alt-tabbed window");
|
||||
|
||||
// Take a new lock on the wm and try to focus the container with
|
||||
// the recorded HWND from the alt-tab
|
||||
let mut wm = arc.lock();
|
||||
if let Ok(workspace) = wm.focused_workspace_mut() {
|
||||
// Regardless of if this fails, we need to get past this part
|
||||
// to unblock the border manager below
|
||||
let _ = workspace.focus_container_by_window(hwnd);
|
||||
}
|
||||
|
||||
// Unblock the border manager
|
||||
ALT_TAB_HWND.store(None);
|
||||
// Send a notification to the border manager to update the borders
|
||||
border_manager::send_notification(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use komorebi::StaticConfig;
|
||||
|
||||
#[test]
|
||||
fn backwards_compat() {
|
||||
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
|
||||
let docs = vec![
|
||||
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27", "0.1.28",
|
||||
"0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33",
|
||||
];
|
||||
|
||||
let mut versions = vec![];
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
for version in root {
|
||||
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
|
||||
versions.push((version, client.execute(request).unwrap().text().unwrap()));
|
||||
}
|
||||
|
||||
for version in docs {
|
||||
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
|
||||
versions.push((version, client.execute(request).unwrap().text().unwrap()));
|
||||
}
|
||||
|
||||
for (version, config) in versions {
|
||||
println!("{version}");
|
||||
StaticConfig::read_raw(&config).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic-no-console"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "komorebic"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
@@ -21,7 +21,7 @@ miette = { version = "7", features = ["fancy"] }
|
||||
paste = { workspace = true }
|
||||
powershell_script = "1.0"
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
shadow-rs = { workspace = true }
|
||||
@@ -34,5 +34,9 @@ windows = { workspace = true }
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
shadow-rs = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] }
|
||||
@@ -25,16 +25,14 @@ use fs_tail::TailedFile;
|
||||
use komorebi_client::resolve_home_path;
|
||||
use komorebi_client::send_message;
|
||||
use komorebi_client::send_query;
|
||||
use komorebi_client::AppSpecificConfigurationPath;
|
||||
use komorebi_client::ApplicationSpecificConfiguration;
|
||||
use komorebi_client::Notification;
|
||||
use lazy_static::lazy_static;
|
||||
use miette::NamedSource;
|
||||
use miette::Report;
|
||||
use miette::SourceOffset;
|
||||
use miette::SourceSpan;
|
||||
use paste::paste;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use schemars::schema_for;
|
||||
use serde::Deserialize;
|
||||
use sysinfo::ProcessesToUpdate;
|
||||
use which::which;
|
||||
@@ -158,6 +156,7 @@ gen_enum_subcommand_args! {
|
||||
CycleMoveToMonitor: CycleDirection,
|
||||
CycleMonitor: CycleDirection,
|
||||
CycleWorkspace: CycleDirection,
|
||||
CycleEmptyWorkspace: CycleDirection,
|
||||
CycleMoveWorkspaceToMonitor: CycleDirection,
|
||||
Stack: OperationDirection,
|
||||
CycleStack: CycleDirection,
|
||||
@@ -1105,6 +1104,10 @@ enum SubCommand {
|
||||
/// Move the focused window to the specified monitor workspace
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MoveToMonitorWorkspace(MoveToMonitorWorkspace),
|
||||
/// Send the focused window to the last focused monitor workspace
|
||||
SendToLastWorkspace,
|
||||
/// Move the focused window to the last focused monitor workspace
|
||||
MoveToLastWorkspace,
|
||||
/// Focus the specified monitor
|
||||
#[clap(arg_required_else_help = true)]
|
||||
FocusMonitor(FocusMonitor),
|
||||
@@ -1132,6 +1135,9 @@ enum SubCommand {
|
||||
/// Focus the workspace in the given cycle direction
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleWorkspace(CycleWorkspace),
|
||||
/// Focus the next empty workspace in the given cycle direction (if one exists)
|
||||
#[clap(arg_required_else_help = true)]
|
||||
CycleEmptyWorkspace(CycleWorkspace),
|
||||
/// Move the focused workspace to the specified monitor
|
||||
#[clap(arg_required_else_help = true)]
|
||||
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
||||
@@ -1281,6 +1287,8 @@ enum SubCommand {
|
||||
ToggleMonocle,
|
||||
/// Toggle native maximization for the focused window
|
||||
ToggleMaximize,
|
||||
/// Toggle a lock for the focused container, ensuring it will not be displaced by any new windows
|
||||
ToggleLock,
|
||||
/// Restore all hidden windows (debugging command)
|
||||
RestoreWindows,
|
||||
/// Force komorebi to manage the focused window
|
||||
@@ -1659,11 +1667,12 @@ fn main() -> Result<()> {
|
||||
None => {
|
||||
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
|
||||
}
|
||||
Some(path) => {
|
||||
Some(AppSpecificConfigurationPath::Single(path)) => {
|
||||
if !Path::exists(Path::new(&path)) {
|
||||
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1852,6 +1861,12 @@ fn main() -> Result<()> {
|
||||
arg.cycle_direction,
|
||||
))?;
|
||||
}
|
||||
SubCommand::MoveToLastWorkspace => {
|
||||
send_message(&SocketMessage::MoveContainerToLastWorkspace)?;
|
||||
}
|
||||
SubCommand::SendToLastWorkspace => {
|
||||
send_message(&SocketMessage::SendContainerToLastWorkspace)?;
|
||||
}
|
||||
SubCommand::SwapWorkspacesWithMonitor(arg) => {
|
||||
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(arg.target))?;
|
||||
}
|
||||
@@ -1944,6 +1959,9 @@ fn main() -> Result<()> {
|
||||
SubCommand::ToggleMaximize => {
|
||||
send_message(&SocketMessage::ToggleMaximize)?;
|
||||
}
|
||||
SubCommand::ToggleLock => {
|
||||
send_message(&SocketMessage::ToggleLock)?;
|
||||
}
|
||||
SubCommand::WorkspaceLayout(arg) => {
|
||||
send_message(&SocketMessage::WorkspaceLayout(
|
||||
arg.monitor,
|
||||
@@ -2627,6 +2645,11 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
||||
SubCommand::CycleWorkspace(arg) => {
|
||||
send_message(&SocketMessage::CycleFocusWorkspace(arg.cycle_direction))?;
|
||||
}
|
||||
SubCommand::CycleEmptyWorkspace(arg) => {
|
||||
send_message(&SocketMessage::CycleFocusEmptyWorkspace(
|
||||
arg.cycle_direction,
|
||||
))?;
|
||||
}
|
||||
SubCommand::NewWorkspace => {
|
||||
send_message(&SocketMessage::NewWorkspace)?;
|
||||
}
|
||||
@@ -2973,31 +2996,43 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
||||
);
|
||||
}
|
||||
SubCommand::ApplicationSpecificConfigurationSchema => {
|
||||
let asc = schema_for!(ApplicationSpecificConfiguration);
|
||||
let schema = serde_json::to_string_pretty(&asc)?;
|
||||
println!("{schema}");
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let asc = schemars::schema_for!(ApplicationSpecificConfiguration);
|
||||
let schema = serde_json::to_string_pretty(&asc)?;
|
||||
println!("{schema}");
|
||||
}
|
||||
}
|
||||
SubCommand::NotificationSchema => {
|
||||
let notification = schema_for!(Notification);
|
||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||
println!("{schema}");
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let notification = schemars::schema_for!(komorebi_client::Notification);
|
||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||
println!("{schema}");
|
||||
}
|
||||
}
|
||||
SubCommand::SocketSchema => {
|
||||
let socket_message = schema_for!(SocketMessage);
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
println!("{schema}");
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let socket_message = schemars::schema_for!(SocketMessage);
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
println!("{schema}");
|
||||
}
|
||||
}
|
||||
SubCommand::StaticConfigSchema => {
|
||||
let settings = SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = false;
|
||||
s.option_add_null_type = false;
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
#[cfg(feature = "schemars")]
|
||||
{
|
||||
let settings = schemars::gen::SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = false;
|
||||
s.option_add_null_type = false;
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
println!("{schema}");
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
println!("{schema}");
|
||||
}
|
||||
}
|
||||
SubCommand::GenerateStaticConfig => {
|
||||
print_query(&SocketMessage::GenerateStaticConfig);
|
||||
|
||||
11
mkdocs.yml
11
mkdocs.yml
@@ -55,6 +55,12 @@ nav:
|
||||
- Installation: installation.md
|
||||
- Example configurations: example-configurations.md
|
||||
- Troubleshooting: troubleshooting.md
|
||||
- Usage:
|
||||
- usage/focusing-windows.md
|
||||
- usage/moving-windows.md
|
||||
- usage/stacking-windows.md
|
||||
- usage/focusing-workspaces.md
|
||||
- usage/moving-windows-across-workspaces.md
|
||||
- Common workflows:
|
||||
- common-workflows/komorebi-config-home.md
|
||||
- common-workflows/autostart.md
|
||||
@@ -71,6 +77,7 @@ nav:
|
||||
- common-workflows/dynamic-layout-switching.md
|
||||
- common-workflows/set-display-index.md
|
||||
- common-workflows/multiple-bar-instances.md
|
||||
- common-workflows/multi-monitor-setup.md
|
||||
- Configuration reference: https://komorebi.lgug2z.com/schema
|
||||
- Bar reference: https://komorebi-bar.lgug2z.com/schema
|
||||
- CLI reference:
|
||||
@@ -126,6 +133,8 @@ nav:
|
||||
- cli/cycle-send-to-workspace.md
|
||||
- cli/send-to-monitor-workspace.md
|
||||
- cli/move-to-monitor-workspace.md
|
||||
- cli/send-to-last-workspace.md
|
||||
- cli/move-to-last-workspace.md
|
||||
- cli/focus-monitor.md
|
||||
- cli/focus-monitor-at-cursor.md
|
||||
- cli/focus-last-workspace.md
|
||||
@@ -136,6 +145,7 @@ nav:
|
||||
- cli/close-workspace.md
|
||||
- cli/cycle-monitor.md
|
||||
- cli/cycle-workspace.md
|
||||
- cli/cycle-empty-workspace.md
|
||||
- cli/move-workspace-to-monitor.md
|
||||
- cli/cycle-move-workspace-to-monitor.md
|
||||
- cli/swap-workspaces-with-monitor.md
|
||||
@@ -183,6 +193,7 @@ nav:
|
||||
- cli/toggle-float.md
|
||||
- cli/toggle-monocle.md
|
||||
- cli/toggle-maximize.md
|
||||
- cli/toggle-lock.md
|
||||
- cli/restore-windows.md
|
||||
- cli/manage.md
|
||||
- cli/unmanage.md
|
||||
|
||||
908
schema.bar.json
908
schema.bar.json
File diff suppressed because it is too large
Load Diff
1247
schema.json
1247
schema.json
File diff suppressed because it is too large
Load Diff
BIN
wix/License.rtf
BIN
wix/License.rtf
Binary file not shown.
Reference in New Issue
Block a user