Compare commits

..

37 Commits

Author SHA1 Message Date
LGUG2Z
8ef1bcf26e chore(release): v0.1.36 2025-04-12 10:52:31 -07:00
LGUG2Z
d146f35c25 chore(deps): cargo update 2025-04-12 10:52:31 -07:00
dependabot[bot]
fc07ba3dd9 chore(deps): bump crossbeam-channel from 0.5.14 to 0.5.15
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.14 to 0.5.15.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.14...crossbeam-channel-0.5.15)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-version: 0.5.15
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 09:54:12 -07:00
LGUG2Z
3a8a61119d fix(config): update monitor wallpaper on reload 2025-04-09 16:09:17 -07:00
LGUG2Z
d24beb60b1 feat(config): add per-monitor floating layer behaviour opt
This commit adds an option to set the floating layer behaviour for all
workspaces on a monitor.

Overrides set on an individual workspace level will take precedence over
the option set at the monitor level.
2025-04-09 09:42:46 -07:00
Jerry Kingsbury
7daf3242e2 test(wm): add float and lock toggle tests
Created a test for the toggle_lock function. The test creates a
workspace with multiple containers and checks to see whether the
container was added or removed from the locked containers list when
calling the toggle_lock function.

Created a test for the float_window function. The test checks to ensure
that when calling the float_window function, the window is added to the
floating_windows list and removed from the containers window list. The
test will also check the count and ensure the expected window was added.
2025-04-09 08:55:10 -07:00
LGUG2Z
b6e261aef6 fix(bar): avoid retile messages on ws switch
This commit removes RetileWithResizeDimensions messages from the batches
that are sent to komorebi when changing workspace.

I'm not really sure why this was added in the first place, but removing
it doesn't seem to impact layouts on workspace switch, and more
important, by removing this message I'm no longer able to reproduce the
sudden exits of komorebi.exe under sustained workspace switching calls
made via the bar.
2025-04-08 17:20:02 -07:00
LGUG2Z
d40c304324 fix(borders): avoid deadlock on state hashmaps
This commit addresses a deadlock on WINDOW_BORDERS which can occur under
load, particularly when switching workspaces.

When switching workspaces using the komorebi-bar,
RetileWithResizeDimensions is also called, which calls
border_manager::destroy_all_borders.

At the same time, border_manager::window_border is called when hiding
borders from the previous workspace. Both of these functions try to take
locks on WINDOWS_BORDERS and BORDER_STATE.

border_manager::window_border is called in a new thread by hide_border,
so if this has not finished with the lock by the time
destroy_all_borders requests it, we get stuck in a deadlock.

The changes in this commit ensure that the BORDER_STATE lock is dropped
as early as possible in destroy_all_borders, and that we are not holding
the WINDOWS_BORDERS lock in window_border while trying to get the
BORDER_STATE lock to look up BorderInfo.

The logs which show the initial deadlock being detected:

2025-04-08T22:49:37.641888Z  WARN komorebi::process_command: could not acquire window manager lock, not processing message: FocusWorkspaceNumber
2025-04-08T22:49:38.294952Z  WARN komorebi::process_command: could not acquire window manager lock, not processing message: FocusWorkspaceNumber
2025-04-08T22:49:39.225645Z ERROR komorebi: 1 deadlocks detected
2025-04-08T22:49:39.225826Z ERROR komorebi: deadlock #0
2025-04-08T22:49:39.225950Z ERROR komorebi: thread id: 9896
2025-04-08T22:49:39.226072Z ERROR komorebi:    0:     0x7ff68fd33dec - backtrace::backtrace::dbghelp64::trace
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\backtrace\dbghelp64.rs:99
                           backtrace::backtrace::trace_unsynchronized<backtrace::capture::impl$1::create::closure_env$0>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\backtrace\mod.rs:66
   1:     0x7ff68fd33c87 - backtrace::backtrace::trace<backtrace::capture::impl$1::create::closure_env$0>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\backtrace\mod.rs:53
   2:     0x7ff68fd3c03e - backtrace::capture::Backtrace::create
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\capture.rs:193
   3:     0x7ff68fd3bfae - backtrace::capture::Backtrace::new
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\capture.rs:158
   4:     0x7ff68f937b54 - parking_lot_core::parking_lot::deadlock_impl::on_unpark
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:1211
   5:     0x7ff68f921f0e - parking_lot_core::parking_lot::deadlock::on_unpark
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:1144
   6:     0x7ff68f92bf88 - parking_lot_core::parking_lot::park::closure$0<parking_lot::raw_mutex::impl$3::lock_slow::closure_env$0,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$1,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$2>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:637
   7:     0x7ff68f92a777 - parking_lot_core::parking_lot::with_thread_data
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:207
                           parking_lot_core::parking_lot::park<parking_lot::raw_mutex::impl$3::lock_slow::closure_env$0,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$1,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$2>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:600
   8:     0x7ff68f926fa0 - parking_lot::raw_mutex::RawMutex::lock_slow
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot-0.12.3\src\raw_mutex.rs:262
   9:     0x7ff68f1daac6 - parking_lot::raw_mutex::impl$0::lock
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot-0.12.3\src\raw_mutex.rs:72
  10:     0x7ff68ef05eb3 - lock_api::mutex::Mutex<parking_lot::raw_mutex::RawMutex,std::collections::hash::map::HashMap<isize,alloc::string::String,std::hash::random::RandomState> >::lock<parking_lot::raw_mutex::RawMutex,std::collections::hash::map::HashMap<isize,alloc::string::Str
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\lock_api-0.4.12\src\mutex.rs:223
  11:     0x7ff68ef0713d - komorebi::border_manager::destroy_all_borders
                               at komorebi\src\border_manager\mod.rs:139
  12:     0x7ff68f194e61 - komorebi::window_manager::WindowManager::process_command<ref_mut$<uds_windows::stdnet::net::UnixStream> >
                               at komorebi\src\process_command.rs:918
  13:     0x7ff68ef1fdcf - komorebi::process_command::read_commands_uds
                               at komorebi\src\process_command.rs:2292
  14:     0x7ff68f231eb5 - komorebi::process_command::listen_for_commands::closure$0::closure$0::closure$0
                               at komorebi\src\process_command.rs:123
  15:     0x7ff68f32bcf3 - core::hint::black_box
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\hint.rs:477
                           std::sys::backtrace::__rust_begin_short_backtrace<komorebi::process_command::listen_for_commands::closure$0::closure$0::closure_env$0,tuple$<> >
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\sys\backtrace.rs:152
  16:     0x7ff68f00d1b4 - std::thread::impl$0::spawn_unchecked_::closure$1::closure$0<komorebi::process_command::listen_for_commands::closure$0::closure$0::closure_env$0,tuple$<> >
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\thread\mod.rs:559
  17:     0x7ff68f356ea1 - core::panic::unwind_safe::impl$25::call_once<tuple$<>,std::thread::impl$0::spawn_unchecked_::closure$1::closure_env$0<komorebi::process_command::listen_for_commands::closure$0::closure$0::closure_env$0,tuple$<> > >
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\panic\unwind_safe.rs:272
  18:     0x7ff68f1d1e00 - std::panicking::try::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::impl$0::spawn_unchecked_::closure$1::closure_env$0<komorebi::process_command::listen_for_commands::closure$0::closure$0::closure_env$0,tuple$<> > >,tuple$<> >
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:587
  19:     0x7ff68f0102b3 - std::thread::impl$7::drop::closure$0<enum2$<core::result::Result<tuple$<>,eyre::Report> > >
  20:     0x7ff68f007be7 - std::panicking::try
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:550
                           std::panic::catch_unwind
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panic.rs:358
                           std::thread::impl$0::spawn_unchecked_::closure$1<komorebi::process_command::listen_for_commands::closure$0::closure$0::closure_env$0,tuple$<> >
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\thread\mod.rs:557
  21:     0x7ff68ecfeb8e - core::ops::function::FnOnce::call_once<std::thread::impl$0::spawn_unchecked_::closure_env$1<komorebi::process_command::listen_for_commands::closure$0::closure$0::closure_env$0,tuple$<> >,tuple$<> >
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:250
  22:     0x7ff68fe84f1d - alloc::boxed::impl$28::call_once
                               at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library\alloc\src\boxed.rs:1976
                           alloc::boxed::impl$28::call_once
                               at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library\alloc\src\boxed.rs:1976
                           std::sys::pal::windows::thread::impl$0::new::thread_start
                               at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library\std\src\sys\pal\windows\thread.rs:56
  23:     0x7ff8ef27257d - BaseThreadInitThunk
  24:     0x7ff8f042af28 - RtlUserThreadStart

2025-04-08T22:49:39.228617Z ERROR komorebi: thread id: 62364
2025-04-08T22:49:39.228731Z ERROR komorebi:    0:     0x7ff68fd33dec - backtrace::backtrace::dbghelp64::trace
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\backtrace\dbghelp64.rs:99
                           backtrace::backtrace::trace_unsynchronized<backtrace::capture::impl$1::create::closure_env$0>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\backtrace\mod.rs:66
   1:     0x7ff68fd33c87 - backtrace::backtrace::trace<backtrace::capture::impl$1::create::closure_env$0>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\backtrace\mod.rs:53
   2:     0x7ff68fd3c03e - backtrace::capture::Backtrace::create
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\capture.rs:193
   3:     0x7ff68fd3bfae - backtrace::capture::Backtrace::new
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\backtrace-0.3.71\src\capture.rs:158
   4:     0x7ff68f937b54 - parking_lot_core::parking_lot::deadlock_impl::on_unpark
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:1211
   5:     0x7ff68f921f0e - parking_lot_core::parking_lot::deadlock::on_unpark
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:1144
   6:     0x7ff68f92bf88 - parking_lot_core::parking_lot::park::closure$0<parking_lot::raw_mutex::impl$3::lock_slow::closure_env$0,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$1,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$2>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:637
   7:     0x7ff68f92a777 - parking_lot_core::parking_lot::with_thread_data
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:207
                           parking_lot_core::parking_lot::park<parking_lot::raw_mutex::impl$3::lock_slow::closure_env$0,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$1,parking_lot::raw_mutex::impl$3::lock_slow::closure_env$2>
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot_core-0.9.10\src\parking_lot.rs:600
   8:     0x7ff68f926fa0 - parking_lot::raw_mutex::RawMutex::lock_slow
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot-0.12.3\src\raw_mutex.rs:262
   9:     0x7ff68f1daac6 - parking_lot::raw_mutex::impl$0::lock
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\parking_lot-0.12.3\src\raw_mutex.rs:72
  10:     0x7ff68ef05d93 - lock_api::mutex::Mutex<parking_lot::raw_mutex::RawMutex,std::collections::hash::map::HashMap<alloc::string::String,alloc::boxed::Box<komorebi::border_manager::border::Border,alloc::alloc::Global>,std::hash::random::RandomState> >::lock<parking_lot::raw_mu
                               at C:\Users\LGUG2Z\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\lock_api-0.4.12\src\mutex.rs:223
  11:     0x7ff68f2064de - komorebi::border_manager::window_border::closure$0
                               at komorebi\src\border_manager\mod.rs:109
  12:     0x7ff68eddeca9 - enum2$<core::option::Option<ref$<alloc::string::String> > >::and_then<ref$<alloc::string::String>,komorebi::border_manager::BorderInfo,komorebi::border_manager::window_border::closure_env$0>
                               at C:\Users\LGUG2Z\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\option.rs:1452
  13:     0x7ff68ef06339 - komorebi::border_manager::window_border
                               at komorebi\src\border_manager\mod.rs:108
2025-04-08 16:14:41 -07:00
LGUG2Z
69d252ba12 feat(wm): drop empty containers on ws update
We shouldn't ever have empty containers, but never say never because
someone on the Discord has an empty container with no Windows that
continues to take up a tile. This commit adds a call to drop all
containers without any windows whenever Workspace::update is called.
2025-04-07 15:28:29 -07:00
LGUG2Z
2ac477d89f docs(mkdocs): reduce number of tabs to improve nav 2025-04-07 11:02:57 -07:00
alex-ds13
6db73151f7 fix(wm): remove border width/offset when disabled
This commit makes sure the border's width and offset is removed when you
disable the borders.

If you want to still have that size on your gaps when borders are
disabled then you should add it to the `default_container_padding` or to
the per-monitor or per-workspace `container_padding`.
2025-04-07 10:12:18 -07:00
alex-ds13
2d6ff0708f fix(docs): borders are enabled by default 2025-04-07 10:12:18 -07:00
Csaba
13a519fb29 fix(cli): restart bar on replace-configuration
This commit ensures that the replace-configuration command also replaces
bars.

Already running bars are stopped and new bars are started using the new
configuration.
2025-04-06 21:19:41 -07:00
amrbashir
9f8e4b9dca feat(core): use PathExt to unify env var resolution
This new implementation allows for expanding any environment variable so
it is not limited to just `~`, `$HOME`, `$Env:USERPROFILE` and
`$Env:KOMOREBI_CONFIG_HOME`.

It expands the follwing formats:
- CMD: `%variable%`
- PowerShell: `$Env:variable`
- Bash: `$variable`

I searched throughout the code base for path and migrate any code that
might need to PathExt::replace_env.

It is possible that I might have missed a few places due to my
unfamiliarity with the code base, so if you find any, please let me
know.

Most of the paths that needed this trait, are in:

- Clap arguments, and that was handled by #[value_parse] attribute and a
  helper function.
- SocketMessage and that was handled by custom deserialization with the
  help of serde_with crate
2025-04-06 21:17:35 -07:00
Jerry Kingsbury
5a0196ac9d test(monitor.rs): ensure workspace count test
Created tests for the ensure_workspace_count function. The function is
tested by calling the function with only the default workspace, after
creating a workspace, and after we already have at least the number of
monitors passed into the funtion.
2025-04-05 12:14:10 -07:00
Jerry Kingsbury
46d0e340f9 test(monitor.rs): move container to workspace test
Created a test for the move_container_to_workspace funtion. The test
creates a workspace with 3 containers and an empty workspace, and
ensuresthat the container is moved to the correct workspace and that if
the workspace focus has changed when setting follow focus to true.
2025-04-05 12:14:10 -07:00
Jerry Kingsbury
371ef88ecb test(workspace): visible windows test
Created a test for the visible_windows function. The test creates two
windows in a workspace and checks to see if the the first created window
is the visible window. The test then maximizes the second window and
checks to ensure both windows are visible.

Expanded visible window test to ensure that a visible window will
display when adding another container.
2025-04-05 12:14:02 -07:00
dependabot[bot]
f5b5070436 chore(deps): bump openssl from 0.10.71 to 0.10.72
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.71 to 0.10.72.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.72
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 16:29:12 -07:00
LGUG2Z
c8320552b0 feat(cli): add session float rule cmds
This commit introduces three new commands, session-float-rule,
session-float-rules, and clear-session-float-rules, which add a
composite float rule for the currently focused window for the duration
of the komorebi session, print the float rules scoped to the current
komorebi session, and clear any float rules scoped to the current
komorebi session respectively.

The composite rule created is fairly strict, using
MatchingStrategy::Equals on the Exe, Class and Title.

Users can run session-float-rule as they are working to avoid having to
break their workflow and edit their configuration file, and when they
are ready, they can run session-float-rules to print out the composite
rules which have been generated and added to the current session to
further refine before adding them to their configuration files.

re #1402
2025-04-04 11:56:25 -07:00
alex-ds13
2a5a960c34 feat(wm): allow setting wallpaper per monitor
This commit adds the option to set `Wallpaper` per monitor. When
changing workspaces it will first check for a workspace wallpaper, if
there is none it then checks for a monitor wallpaper.
2025-04-04 11:12:47 -07:00
alex-ds13
10ab43a8ae fix(wm): properly update border colors
This commit changes the border manager notification to an enum that can
be a `Notification::Update(Option<isize>)` which should work like before
or a `Notification::ForceUpdate` which will always update the borders
and it will also update the border's brushes. To do so, this commit
moved the brush creation from the `create()` function to a new
`update_brushes` function on the `Border` itself and it changed the
`Border` `render_target` from a `OnceLock` to an `Option<RenderTarget>`
so that we can always change it when we need to.
This commit also adds a new function called `send_force_update()` to the
border manager to make it easier to send this notification.

This commit also added a check for `force_update_borders` on the
`process_command` function so that any command that reloads the
configuration, changes the theme or changes any border config can set it
to `true`. When this is true we send the `ForceUpdate` notification, if
false we send the normal `Update` notification as before.

The theme manager now always sends the `ForceUpdate` notification to the
border manager.

This commit adds a new function to the window manager called
`apply_wallpaper_for_monitor_workspace` which in turn calls the
`apply_wallpaper` function for the workspace specified with the
`monitor_idx` and `workspace_idx` given (if the workspace in question
doesn't have a wallpaper defined this won't do anything).

All these changes make it so the wallpaper theme generation colors are
properly applied in all situations.
2025-04-04 11:08:45 -07:00
alex-ds13
0e8ed8aa40 feat(wm): apply wallpapers per monitor 2025-04-04 11:08:45 -07:00
alex-ds13
fa2ccad5bf fix(wm): skip serde on WorkspaceGlobals 2025-04-04 11:08:45 -07:00
Csaba
3c4ccd2504 feat(bar): changing battery icons
This commit is changing the icon on the battery widget based on the
current level.

level | icon
------------
100 - 75: discharging
75 - 50: high
50 - 25: medium
25 - 10: low
10 - 0: warning

PR: #1398
2025-04-03 15:04:23 -07:00
LGUG2Z
7d821cd3db refactor(clippy): apply new rust lint fixes 2025-04-03 08:41:55 -07:00
Csaba
f4bbee0a2e feat(bar): auto select/hide widget based on value
This commit adds new settings to some widgets that allows to auto
select/hide them based on their current values.

The cpu/memory/network/storage widgets get a setting that auto selects
the widget if the current value/percentage is over a value.

The battery widget gets a setting that auto selects the widget if the
current percentage is under a value.

The storage widget gets a setting that auto hides the disk widget if the
percentage is under a value.

Also added 2 new settings (auto_select_fill and auto_select_text) to the
theme, in order to select the fill and text colors of an auto selected
widget.

(Easter egg: the network icons change if the value is over the limit)

PR: #1353
2025-04-03 07:42:27 -07:00
Lenus Walker
2934d011fd docs(mkdocs): add nvidia bar transparency workaround
Updated troubleshooting.md with the workaround for an issue where the
Komorebi Bar does not render transparency properly with Nvidia GPUs.
2025-04-02 17:22:06 -07:00
LGUG2Z
71762a5961 feat(cli): add datadir cmd
Because I'm tired of trying to find this stupid directory in
explorer.exe all the time.
2025-04-02 17:20:35 -07:00
zepocas
76aeefa9f7 feat(cli): add focused-workspace-layout query
This commit adds StateQuery::FocusedWorkspaceLayout variant to allow
queries for focused workspace layout via komorebic query.

This handles floating workspaces returning "None".
2025-04-02 15:33:46 -07:00
LGUG2Z
4968b0fe37 feat(themes): generate base16 palette from wallpaper
This commit adds a new Wallpaper configuration option to the
WorkspaceConfig, allowing the user to specify a path to a wallpaper
image file, and to specify whether to generate a base16 palette from the
colours of that image file.

Theme generation is enabled by default when a wallpaper is selected.

A set of theme options can be given to customize the colours of various
borders and accents.

The themes generated are also plumbed through to the komorebi-bar.

The palette generation algorithm from "flavours" (which has been forked
and updated) is quite slow, so the outputs are cached to file in
DATA_DIR, and keyed by ThemeVariant (Light or Dark).

The Win32 COM API to set the desktop wallpaper is also quite slow,
however this calls is async so it doesn't block komorebi's main thread.
2025-04-02 13:24:24 -07:00
LGUG2Z
b4b400b236 feat(themes): add custom base16 theme variant
This commit adds a custom Base16 theme variant and plumbs it throughout
the komorebi and komorebi-bar packages.
2025-04-02 13:23:31 -07:00
LGUG2Z
2ee0bbc0c7 refactor(themes): move colour.rs to komorebi-themes 2025-04-02 13:23:31 -07:00
Csaba
d38d3c956d feat(bar): add locked container widget, use accent colour for icons
This commit adds a new komorebi widget to indicate whether or not the
focused container is locked.

This commit also includes an icon colour change on the layer and layout
widgets to the accent colour.

The commit also renames the locked_window widget to locked_container as
it is more suitable.

PR: #1394
2025-04-02 13:23:02 -07:00
Csaba
052eb1c763 fix(bar): re-introduce retain exact workspace indices
This commit re-introduces a commit that was lost

original: 36e3eaad36

blame: bb31e7155d

fixes: https://github.com/LGUG2Z/komorebi/issues/1388
2025-04-01 10:08:22 -07:00
alex-ds13
58730b81b4 feat(gui): add floating and locked border colours 2025-04-01 05:53:33 -07:00
alex-ds13
274ae43e8f feat(wm): add unfocused_locked to border_colours
This commit adds the `unfocused_locked` color to the border_colours so
that users that don't use themes can still customize this color like
they do the others.
2025-04-01 05:53:26 -07:00
LGUG2Z
2a30f09bbd feat(cli): add version as state query variant
This commit adds a new StateQuery::Version, which allows integrators to
make decisions about how to handle different versions of komorebi's
state schema based on the version of komorebi that is running on a
user's machine.
2025-03-31 17:41:36 -07:00
54 changed files with 6524 additions and 1269 deletions

1066
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -394,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.35"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.36"}
use anyhow::Result;
use komorebi_client::Notification;

View File

@@ -13,7 +13,8 @@ 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-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]
@@ -93,4 +94,6 @@ allow-git = [
"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",
]

View File

@@ -3,8 +3,9 @@
[
"0BSD",
[
"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"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"
"win32-display-data 0.1.0 git+https://github.com/LGUG2Z/win32-display-data?rev=a28c6559a9de2f92c142a714947a9b081776caca"
]
],
[
@@ -16,6 +17,7 @@
"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",
"adler 1.0.2 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",
@@ -24,66 +26,75 @@
"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",
"approx 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.5.0 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",
"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme",
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.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",
"bytemuck_derive 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.19 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",
"chrono-tz 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"chrono-tz-build 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.36 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.36 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.32 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-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.6 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",
"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.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",
"ecolor 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.31.1 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",
"egui-winit 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.31.1 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",
"epaint 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.31.1 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",
"flate2 1.1.1 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",
@@ -95,53 +106,66 @@
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.1 registry+https://github.com/rust-lang/crates.io-index",
"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"glob 0.3.2 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",
"half 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.12.3 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 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.3.1 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",
"iana-time-zone 0.1.63 registry+https://github.com/rust-lang/crates.io-index",
"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.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 0.25.6 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",
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.9.0 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",
"jobserver 0.1.33 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.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",
"libc 0.2.171 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.1+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.22 registry+https://github.com/rust-lang/crates.io-index",
"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index",
"litrs 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",
"log 0.4.27 registry+https://github.com/rust-lang/crates.io-index",
"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"miette 7.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",
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.8 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",
@@ -154,10 +178,13 @@
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.21.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",
"palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.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",
@@ -165,9 +192,10 @@
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
"png 0.16.8 registry+https://github.com/rust-lang/crates.io-index",
"png 0.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",
"ppv-lite86 0.2.21 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",
@@ -175,10 +203,14 @@
"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",
"quote 1.0.40 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"rand_pcg 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.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",
@@ -186,42 +218,52 @@
"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",
"reqwest 0.12.15 registry+https://github.com/rust-lang/crates.io-index",
"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.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 1.0.219 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.219 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_with 3.12.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.12.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.15.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",
"socket2 0.5.9 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",
"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.100 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",
"tempfile 3.19.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.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",
"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",
"time 0.3.41 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.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",
@@ -241,48 +283,59 @@
"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",
"webbrowser 1.0.4 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 0.61.1 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-collections 0.2.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-core 0.61.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.60.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-interface 0.59.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.4.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-result 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.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-targets 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.53.0 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",
"wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.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",
"zerocopy 0.8.24 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"
@@ -302,7 +355,7 @@
"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"
"zerocopy 0.8.24 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -343,7 +396,7 @@
"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"
"starship-battery 0.10.1 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -352,6 +405,7 @@
"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",
"adler 1.0.2 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",
@@ -362,14 +416,16 @@
"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",
"arboard 3.5.0 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",
"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme",
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.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",
@@ -378,58 +434,71 @@
"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",
"bytemuck_derive 1.9.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",
"bytes 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"bytes 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"calm_io 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"calmio_filters 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"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",
"cc 1.2.19 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",
"chrono-tz 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"chrono-tz-build 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.36 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.36 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.32 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-thief 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"color_quant 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.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-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.6 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",
"darling 0.20.11 registry+https://github.com/rust-lang/crates.io-index",
"darling_core 0.20.11 registry+https://github.com/rust-lang/crates.io-index",
"darling_macro 0.20.11 registry+https://github.com/rust-lang/crates.io-index",
"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.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",
"ecolor 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.31.1 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",
"egui-winit 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.31.1 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",
"epaint 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.31.1 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",
"flate2 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"flavours 0.7.2 git+https://github.com/LGUG2Z/flavours",
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
@@ -443,64 +512,79 @@
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"getset 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.11.4 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",
"git2 0.20.1 registry+https://github.com/rust-lang/crates.io-index",
"glob 0.3.2 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",
"half 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.12.3 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 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.3.1 registry+https://github.com/rust-lang/crates.io-index",
"http-body 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"http-body-util 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"http-body-util 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper 1.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",
"hyper-util 0.1.11 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.63 registry+https://github.com/rust-lang/crates.io-index",
"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.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 0.23.14 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.6 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",
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.9.0 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",
"jobserver 0.1.33 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.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",
"libc 0.2.171 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.1+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.22 registry+https://github.com/rust-lang/crates.io-index",
"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index",
"litrs 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",
"log 0.4.27 registry+https://github.com/rust-lang/crates.io-index",
"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"loop9 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"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",
"mime_guess2 2.3.0 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",
"miniz_oxide 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.8 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",
"netdev 0.33.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",
@@ -514,24 +598,37 @@
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.21.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",
"palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.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",
"parse-zoneinfo 0.3.1 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",
"phf 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"phf_codegen 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf_codegen 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"phf_generator 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf_generator 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"phf_shared 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf_shared 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
"png 0.16.8 registry+https://github.com/rust-lang/crates.io-index",
"png 0.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",
"ppv-lite86 0.2.21 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",
@@ -539,11 +636,15 @@
"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",
"quote 1.0.40 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.5.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"random_word 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"rand_pcg 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"random_word 0.5.0 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",
@@ -552,8 +653,9 @@
"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",
"reqwest 0.12.15 registry+https://github.com/rust-lang/crates.io-index",
"rgb 0.8.50 registry+https://github.com/rust-lang/crates.io-index",
"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.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",
@@ -562,46 +664,57 @@
"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",
"scoped_threadpool 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"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 1.0.219 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.219 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_with 3.12.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.12.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"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",
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"slab 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.15.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",
"socket2 0.5.9 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",
"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.100 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",
"tempfile 3.19.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.2 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.6.1 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",
"time 0.3.41 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"tokio 1.44.2 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",
"tokio-util 0.7.14 registry+https://github.com/rust-lang/crates.io-index",
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
"tower 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"tower-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",
@@ -634,54 +747,66 @@
"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",
"webbrowser 1.0.4 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",
"which 7.0.3 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 0.61.1 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-collections 0.2.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-core 0.61.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354",
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5",
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.60.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-interface 0.59.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.4.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-result 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.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-targets 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.53.0 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",
"wmi 0.15.2 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",
"xml-rs 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.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",
"zerocopy 0.8.24 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"
@@ -697,19 +822,20 @@
[
"MPL-2.0",
[
"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index"
"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"ramhorns 1.0.1 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"OFL-1.1",
[
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
"epaint_default_fonts 0.31.1 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"
"epaint_default_fonts 0.31.1 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -718,11 +844,11 @@
"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_locid_transform_data 1.5.1 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_normalizer_data 1.5.1 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_properties_data 1.5.1 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",
@@ -753,14 +879,16 @@
[
"Zlib",
[
"adler32 1.2.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",
"bytemuck_derive 1.9.3 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",
"foldhash 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.8 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",

View File

@@ -10,9 +10,8 @@ Options:
Desired ease function for animation
[default: linear]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart,
ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back,
ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint,
ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
-a, --animation-type <ANIMATION_TYPE>
Animation type to apply the style to. If not specified, sets global style

View File

@@ -0,0 +1,12 @@
# clear-session-float-rules
```
Clear all session float rules
Usage: komorebic.exe clear-session-float-rules
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,12 @@
# data-directory
```
Show the path to komorebi's data directory in %LOCALAPPDATA%
Usage: komorebic.exe data-directory
Options:
-h, --help
Print help
```

View File

@@ -7,7 +7,7 @@ Usage: komorebic.exe query <STATE_QUERY>
Arguments:
<STATE_QUERY>
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name]
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name, focused-workspace-layout, version]
Options:
-h, --help

View File

@@ -0,0 +1,12 @@
# session-float-rule
```
Add a rule to float the foreground window for the rest of this session
Usage: komorebic.exe session-float-rule
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,12 @@
# session-float-rules
```
Show all session float rules
Usage: komorebic.exe session-float-rules
Options:
-h, --help
Print help
```

View File

@@ -1,8 +1,7 @@
# toggle-workspace-float-override
```
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes
the opposite of the global value
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes the opposite of the global value
Usage: komorebic.exe toggle-workspace-float-override

View File

@@ -8,12 +8,8 @@ configuration file.
```json
{
"default_workspace_padding": 0,
"default_container_padding": 0,
"border_width": 0,
"border_offset": -1
"default_container_padding": -1,
}
```
A restart of `komorebi` is required after changing these settings.
[![Watch the tutorial video](https://img.youtube.com/vi/6QYLao953XE/hqdefault.jpg)](https://www.youtube.com/watch?v=6QYLao953XE)

View File

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

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.35/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.36/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",
@@ -14,13 +14,6 @@
"unfocused_border": "Base03",
"bar_accent": "Base0D"
},
"stackbar": {
"height": 40,
"mode": "OnStack",
"tabs": {
"width": 300
}
},
"monitors": [
{
"workspaces": [

View File

@@ -132,3 +132,19 @@ running `komorebic stop` and `komorebic start`.
We can see the _komorebi_ state is no longer associated with the previous
device: `null`, suggesting an issue when the display resumes from a suspended
state.
## Komorebi Bar does not render transparency on Nvidia GPUs
Users with Nvidia GPUs may have issues with transparency on the Komorebi Bar.
To solve this the user can do the following:
1. Open the Nvidia Control Panel
2. On the left menu tree, under "3D Settings", select "Manage 3D Settings"
3. Select the "Program Settings" tab
4. Press the "Add" button and select "komorebi-bar"
5. Under "3. Specify the settings for this program:", find the feature labelled, "OpenGL GDI compatibility"
6. Change the setting to "Prefer compatibility"
7. At the bottom of the window select "Apply"
8. Restart the Komorebi Bar with "komorebic stop --bar; komorebic start --bar"
This should resolve the issue and your Komorebi Bar should render with the proper transparency.

View File

@@ -14,6 +14,8 @@ use crate::widgets::komorebi::KomorebiNotificationState;
use crate::widgets::widget::BarWidget;
use crate::widgets::widget::WidgetConfig;
use crate::KomorebiEvent;
use crate::AUTO_SELECT_FILL_COLOUR;
use crate::AUTO_SELECT_TEXT_COLOUR;
use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING;
use crate::MAX_LABEL_WIDTH;
@@ -43,12 +45,15 @@ use eframe::egui::Vec2;
use eframe::egui::Visuals;
use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder;
use komorebi_client::Colour;
use komorebi_client::KomorebiTheme;
use komorebi_client::MonitorNotification;
use komorebi_client::NotificationEvent;
use komorebi_client::PathExt;
use komorebi_client::SocketMessage;
use komorebi_themes::catppuccin_egui;
use komorebi_themes::Base16Value;
use komorebi_themes::Base16Wrapper;
use komorebi_themes::Catppuccin;
use komorebi_themes::CatppuccinValue;
use std::cell::RefCell;
@@ -87,75 +92,86 @@ pub fn apply_theme(
grouping: Option<Grouping>,
render_config: Rc<RefCell<RenderConfig>>,
) {
match theme {
let (auto_select_fill, auto_select_text) = match theme {
KomobarTheme::Catppuccin {
name: catppuccin,
accent: catppuccin_value,
} => match catppuccin {
Catppuccin::Frappe => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
auto_select_fill: catppuccin_auto_select_fill,
auto_select_text: catppuccin_auto_select_text,
} => {
match catppuccin {
Catppuccin::Frappe => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::FRAPPE.base);
bg_color.replace(catppuccin_egui::FRAPPE.base);
}
Catppuccin::Latte => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::LATTE.base);
}
Catppuccin::Macchiato => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::MACCHIATO.base);
}
Catppuccin::Mocha => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::MOCHA.base);
}
}
Catppuccin::Latte => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::LATTE.base);
}
Catppuccin::Macchiato => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::MACCHIATO.base);
}
Catppuccin::Mocha => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA);
let catppuccin_value = catppuccin_value.unwrap_or_default();
let accent = catppuccin_value.color32(catppuccin.as_theme());
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
style.visuals.override_text_color = None;
});
bg_color.replace(catppuccin_egui::MOCHA.base);
}
},
(
catppuccin_auto_select_fill.map(|c| c.color32(catppuccin.as_theme())),
catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())),
)
}
KomobarTheme::Base16 {
name: base16,
accent: base16_value,
auto_select_fill: base16_auto_select_fill,
auto_select_text: base16_auto_select_text,
} => {
ctx.set_style(base16.style());
let base16_value = base16_value.unwrap_or_default();
let accent = base16_value.color32(base16);
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
@@ -164,8 +180,46 @@ pub fn apply_theme(
});
bg_color.replace(base16.background());
(
base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Base16(base16))),
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))),
)
}
}
KomobarTheme::Custom {
colours,
accent: base16_value,
auto_select_fill: base16_auto_select_fill,
auto_select_text: base16_auto_select_text,
} => {
let background = colours.background();
ctx.set_style(colours.style());
let base16_value = base16_value.unwrap_or_default();
let accent = base16_value.color32(Base16Wrapper::Custom(colours.clone()));
ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent;
style.visuals.widgets.hovered.fg_stroke.color = accent;
style.visuals.widgets.active.fg_stroke.color = accent;
});
bg_color.replace(background);
(
base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))),
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))),
)
}
};
AUTO_SELECT_FILL_COLOUR.store(
auto_select_fill.map_or(0, |c| Colour::from(c).into()),
Ordering::SeqCst,
);
AUTO_SELECT_TEXT_COLOUR.store(
auto_select_text.map_or(0, |c| Colour::from(c).into()),
Ordering::SeqCst,
);
// Apply transparency_alpha
let theme_color = *bg_color.borrow();
@@ -431,11 +485,11 @@ impl Komobar {
}
fn try_apply_theme(&mut self, ctx: &Context) {
match self.config.theme {
match &self.config.theme {
Some(theme) => {
apply_theme(
ctx,
theme,
theme.clone(),
self.bg_color.clone(),
self.bg_color_with_alpha.clone(),
self.config.transparency_alpha,
@@ -447,13 +501,16 @@ impl Komobar {
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|_| dirs::home_dir().expect("there is no home directory"),
|home_path| {
let home = PathBuf::from(&home_path);
let home = home_path.replace_env();
assert!(
home.is_dir(),
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
home
if home.as_path().is_dir() {
home
} else {
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
}
},
);
@@ -463,6 +520,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 +550,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 +848,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(),
);
}

View File

@@ -373,7 +373,7 @@ impl From<Position> for Pos2 {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")]
pub enum KomobarTheme {
@@ -382,12 +382,24 @@ pub enum KomobarTheme {
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
name: komorebi_themes::Catppuccin,
accent: Option<komorebi_themes::CatppuccinValue>,
auto_select_fill: Option<komorebi_themes::CatppuccinValue>,
auto_select_text: Option<komorebi_themes::CatppuccinValue>,
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
name: komorebi_themes::Base16,
accent: Option<komorebi_themes::Base16Value>,
auto_select_fill: Option<komorebi_themes::Base16Value>,
auto_select_text: Option<komorebi_themes::Base16Value>,
},
/// A custom Base16 theme
Custom {
/// Colours of the custom Base16 theme palette
colours: Box<komorebi_themes::Base16ColourPalette>,
accent: Option<komorebi_themes::Base16Value>,
auto_select_fill: Option<komorebi_themes::Base16Value>,
auto_select_text: Option<komorebi_themes::Base16Value>,
},
}
@@ -399,12 +411,26 @@ impl From<KomorebiTheme> for KomobarTheme {
} => Self::Catppuccin {
name,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
},
KomorebiTheme::Base16 {
name, bar_accent, ..
} => Self::Base16 {
name,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
},
KomorebiTheme::Custom {
colours,
bar_accent,
..
} => Self::Custom {
colours,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
},
}
}

View File

@@ -16,6 +16,8 @@ use font_loader::system_fonts;
use hotwatch::EventKind;
use hotwatch::Hotwatch;
use image::RgbaImage;
use komorebi_client::replace_env_in_path;
use komorebi_client::PathExt;
use komorebi_client::SocketMessage;
use komorebi_client::SubscribeOptions;
use std::collections::HashMap;
@@ -23,6 +25,7 @@ use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::LazyLock;
@@ -47,6 +50,9 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
pub static BAR_HEIGHT: f32 = 50.0;
pub static DEFAULT_PADDING: f32 = 10.0;
pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0);
pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0);
pub static ICON_CACHE: LazyLock<Mutex<HashMap<isize, RgbaImage>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
@@ -61,6 +67,7 @@ struct Opts {
fonts: bool,
/// Path to a JSON or YAML configuration file
#[clap(short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>,
/// Write an example komorebi.bar.json to disk
#[clap(long)]
@@ -155,13 +162,15 @@ fn main() -> color_eyre::Result<()> {
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|_| dirs::home_dir().expect("there is no home directory"),
|home_path| {
let home = PathBuf::from(&home_path);
let home = home_path.replace_env();
if home.as_path().is_dir() {
home
} else {
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
}
assert!(
home.is_dir(),
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
home
},
);
@@ -170,7 +179,7 @@ fn main() -> color_eyre::Result<()> {
std::fs::write(home_dir.join("komorebi.bar.json"), komorebi_bar_json)?;
println!(
"Example komorebi.bar.json file written to {}",
home_dir.as_path().display()
home_dir.display()
);
std::process::exit(0);
@@ -178,16 +187,11 @@ fn main() -> color_eyre::Result<()> {
let default_config_path = home_dir.join("komorebi.bar.json");
let config_path = opts.config.map_or_else(
|| {
if !default_config_path.is_file() {
None
} else {
Some(default_config_path.clone())
}
},
Option::from,
);
let config_path = opts.config.or_else(|| {
default_config_path
.is_file()
.then_some(default_config_path.clone())
});
let mut config = match config_path {
None => {
@@ -197,17 +201,14 @@ fn main() -> color_eyre::Result<()> {
std::fs::write(&default_config_path, komorebi_bar_json)?;
tracing::info!(
"created example configuration file: {}",
default_config_path.as_path().display()
default_config_path.display()
);
KomobarConfig::read(&default_config_path)?
}
Some(ref config) => {
if !opts.aliases {
tracing::info!(
"found configuration file: {}",
config.as_path().to_string_lossy()
);
tracing::info!("found configuration file: {}", config.display());
}
KomobarConfig::read(config)?
@@ -307,10 +308,7 @@ fn main() -> color_eyre::Result<()> {
hotwatch.watch(config_path, move |event| match event.kind {
EventKind::Modify(_) | EventKind::Remove(_) => match KomobarConfig::read(&config_path_cl) {
Ok(updated) => {
tracing::info!(
"configuration file updated: {}",
config_path_cl.as_path().to_string_lossy()
);
tracing::info!("configuration file updated: {}", config_path_cl.display());
if let Err(error) = tx_config.send(updated) {
tracing::error!("could not send configuration update to gui: {error}")

View File

@@ -1,6 +1,8 @@
use crate::bar::Alignment;
use crate::config::KomobarConfig;
use crate::config::MonitorConfigOrIndex;
use crate::AUTO_SELECT_FILL_COLOUR;
use crate::AUTO_SELECT_TEXT_COLOUR;
use eframe::egui::Color32;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
@@ -11,8 +13,11 @@ use eframe::egui::Margin;
use eframe::egui::Shadow;
use eframe::egui::TextStyle;
use eframe::egui::Ui;
use komorebi_client::Colour;
use komorebi_client::Rgb;
use serde::Deserialize;
use serde::Serialize;
use std::num::NonZeroU32;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -55,6 +60,10 @@ pub struct RenderConfig {
pub icon_font_id: FontId,
/// Show all icons on the workspace section of the Komorebi widget
pub show_all_icons: bool,
/// Background color of the selected frame
pub auto_select_fill: Option<Color32>,
/// Text color of the selected frame
pub auto_select_text: Option<Color32>,
}
pub trait RenderExt {
@@ -108,6 +117,10 @@ impl RenderExt for &KomobarConfig {
text_font_id,
icon_font_id,
show_all_icons,
auto_select_fill: NonZeroU32::new(AUTO_SELECT_FILL_COLOUR.load(Ordering::SeqCst))
.map(|c| Colour::Rgb(Rgb::from(c.get())).into()),
auto_select_text: NonZeroU32::new(AUTO_SELECT_TEXT_COLOUR.load(Ordering::SeqCst))
.map(|c| Colour::Rgb(Rgb::from(c.get())).into()),
}
}
}
@@ -133,6 +146,8 @@ impl RenderConfig {
text_font_id: FontId::default(),
icon_font_id: FontId::default(),
show_all_icons: false,
auto_select_fill: None,
auto_select_text: None,
}
}

View File

@@ -10,15 +10,29 @@ use eframe::egui::Ui;
/// Same as SelectableLabel, but supports all content
pub struct SelectableFrame {
selected: bool,
selected_fill: Option<Color32>,
}
impl SelectableFrame {
pub fn new(selected: bool) -> Self {
Self { selected }
Self {
selected,
selected_fill: None,
}
}
pub fn new_auto(selected: bool, selected_fill: Option<Color32>) -> Self {
Self {
selected,
selected_fill,
}
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
let Self { selected } = self;
let Self {
selected,
selected_fill,
} = self;
Frame::NONE
.show(ui, |ui| {
@@ -32,7 +46,16 @@ impl SelectableFrame {
);
// since the stroke is drawn inside the frame, we always reserve space for it
if response.hovered() || response.highlighted() || response.has_focus() {
if selected && response.hovered() {
let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_stroke.color))
.corner_radius(visuals.corner_radius)
.fill(selected_fill.unwrap_or(visuals.bg_fill))
.inner_margin(inner_margin)
.show(ui, add_contents);
} else if response.hovered() || response.highlighted() || response.has_focus() {
let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE
@@ -47,7 +70,7 @@ impl SelectableFrame {
Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_fill))
.corner_radius(visuals.corner_radius)
.fill(visuals.bg_fill)
.fill(selected_fill.unwrap_or(visuals.bg_fill))
.inner_margin(inner_margin)
.show(ui, add_contents);
} else {

View File

@@ -28,6 +28,8 @@ pub struct BatteryConfig {
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is under this value [[1-100]]
pub auto_select_under: Option<u8>,
}
impl From<BatteryConfig> for Battery {
@@ -38,9 +40,10 @@ impl From<BatteryConfig> for Battery {
enable: value.enable,
hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false),
manager: Manager::new().unwrap(),
last_state: String::new(),
last_state: None,
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
auto_select_under: value.auto_select_under.map(|u| u.clamp(1, 100)),
state: BatteryState::Discharging,
last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
@@ -52,6 +55,16 @@ impl From<BatteryConfig> for Battery {
pub enum BatteryState {
Charging,
Discharging,
High,
Medium,
Low,
Warning,
}
#[derive(Clone, Debug)]
struct BatteryOutput {
label: String,
selected: bool,
}
pub struct Battery {
@@ -61,37 +74,53 @@ pub struct Battery {
pub state: BatteryState,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
last_state: String,
auto_select_under: Option<u8>,
last_state: Option<BatteryOutput>,
last_updated: Instant,
}
impl Battery {
fn output(&mut self) -> String {
fn output(&mut self) -> Option<BatteryOutput> {
let mut output = self.last_state.clone();
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
output.clear();
output = None;
if let Ok(mut batteries) = self.manager.batteries() {
if let Some(Ok(first)) = batteries.nth(0) {
let percentage = first.state_of_charge().get::<percent>();
let percentage = first.state_of_charge().get::<percent>().round() as u8;
if percentage == 100.0 && self.hide_on_full_charge {
output = String::new()
if percentage == 100 && self.hide_on_full_charge {
output = None
} else {
match first.state() {
State::Charging => self.state = BatteryState::Charging,
State::Discharging => self.state = BatteryState::Discharging,
State::Discharging => {
self.state = match percentage {
p if p > 75 => BatteryState::Discharging,
p if p > 50 => BatteryState::High,
p if p > 25 => BatteryState::Medium,
p if p > 10 => BatteryState::Low,
_ => BatteryState::Warning,
}
}
_ => {}
}
output = match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage:.0}%")
}
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"),
}
let selected = self.auto_select_under.is_some_and(|u| percentage <= u);
output = Some(BatteryOutput {
label: match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage}%")
}
LabelPrefix::None | LabelPrefix::Icon => {
format!("{percentage}%")
}
},
selected,
})
}
}
}
@@ -108,35 +137,43 @@ impl BarWidget for Battery {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
if let Some(output) = output {
let emoji = match self.state {
BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING,
BatteryState::Discharging => egui_phosphor::regular::BATTERY_FULL,
BatteryState::High => egui_phosphor::regular::BATTERY_HIGH,
BatteryState::Medium => egui_phosphor::regular::BATTERY_MEDIUM,
BatteryState::Low => egui_phosphor::regular::BATTERY_LOW,
BatteryState::Warning => egui_phosphor::regular::BATTERY_WARNING,
};
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(),
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
layout_job.append(
&output,
&output.label,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
valign: Align::Center,
..Default::default()
},
);
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{

View File

@@ -25,6 +25,8 @@ pub struct CpuConfig {
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>,
}
impl From<CpuConfig> for Cpu {
@@ -38,6 +40,7 @@ impl From<CpuConfig> for Cpu {
),
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
@@ -45,26 +48,38 @@ impl From<CpuConfig> for Cpu {
}
}
#[derive(Clone, Debug)]
struct CpuOutput {
label: String,
selected: bool,
}
pub struct Cpu {
pub enable: bool,
system: System,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
auto_select_over: Option<u8>,
last_updated: Instant,
}
impl Cpu {
fn output(&mut self) -> String {
fn output(&mut self) -> CpuOutput {
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.system.refresh_cpu_usage();
self.last_updated = now;
}
let used = self.system.global_cpu_usage();
match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {:.0}%", used),
LabelPrefix::None | LabelPrefix::Icon => format!("{:.0}%", used),
let used = self.system.global_cpu_usage() as u8;
let selected = self.auto_select_over.is_some_and(|o| used >= o);
CpuOutput {
label: match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {}%", used),
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", used),
},
selected,
}
}
}
@@ -73,7 +88,9 @@ impl BarWidget for Cpu {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
if !output.label.is_empty() {
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -82,23 +99,25 @@ impl BarWidget for Cpu {
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
layout_job.append(
&output,
&output.label,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
valign: Align::Center,
..Default::default()
},
);
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{

View File

@@ -11,7 +11,9 @@ 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;
@@ -55,8 +58,11 @@ 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>,
}
@@ -96,15 +102,26 @@ pub struct KomorebiWorkspaceLayerConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiFocusedWindowConfig {
/// Enable the Komorebi Focused Window widget
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(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 {
@@ -140,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,
}
}
@@ -159,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>,
}
@@ -178,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(
@@ -269,7 +292,6 @@ impl BarWidget for Komorebi {
komorebi_notification_state.monitor_index,
i,
),
SocketMessage::RetileWithResizeDimensions,
SocketMessage::MouseFollowsFocus(true),
])
.is_err()
@@ -278,7 +300,6 @@ impl BarWidget for Komorebi {
"could not send the following batch of messages to komorebi:\n
MouseFollowsFocus(false)\n
FocusMonitorWorkspaceNumber({}, {})\n
RetileWithResizeDimensions
MouseFollowsFocus(true)\n",
komorebi_notification_state.monitor_index,
i,
@@ -289,19 +310,18 @@ impl BarWidget for Komorebi {
komorebi_notification_state.monitor_index,
i,
),
SocketMessage::RetileWithResizeDimensions,
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n
FocusMonitorWorkspaceNumber({}, {})\n
RetileWithResizeDimensions",
FocusMonitorWorkspaceNumber({}, {})\n",
komorebi_notification_state.monitor_index,
i,
);
}
}
}
}
});
}
@@ -318,7 +338,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()
@@ -335,7 +355,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 =
@@ -365,7 +385,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 =
@@ -448,72 +468,109 @@ impl BarWidget for Komorebi {
for (name, location) in configuration_switcher.configurations.iter() {
let path = PathBuf::from(location);
if path.is_file() {
config.apply_on_widget(false, ui,|ui|{
if SelectableFrame::new(false).show(ui, |ui|{
ui.add(Label::new(name).selectable(false))
})
.clicked()
{
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized,
))
.is_err()
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
.show(ui, |ui| ui.add(Label::new(name).selectable(false)))
.clicked()
{
tracing::error!(
"could not send message to komorebi: ReplaceConfiguration"
);
proceed = false;
}
let canonicalized =
dunce::canonicalize(path.clone()).unwrap_or(path);
if let Some(rect) = komorebi_notification_state.work_area_offset {
if proceed {
match komorebi_client::send_query(&SocketMessage::Query(
komorebi_client::StateQuery::FocusedMonitorIndex,
)) {
Ok(idx) => {
if let Ok(monitor_idx) = idx.parse::<usize>() {
if komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
rect,
),
)
.is_err()
{
tracing::error!(
"could not send message to komorebi: MonitorWorkAreaOffset"
);
}
}
}
Err(_) => {
tracing::error!(
"could not send message to komorebi: Query"
);
}
}
if komorebi_client::send_message(
&SocketMessage::ReplaceConfiguration(canonicalized),
)
.is_err()
{
tracing::error!(
"could not send message to komorebi: ReplaceConfiguration"
);
}
}
}});
});
}
}
}
}
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());
@@ -521,13 +578,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
@@ -627,9 +684,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,
@@ -695,7 +753,7 @@ impl KomorebiNotificationState {
SocketMessage::Theme(theme) => {
apply_theme(
ctx,
KomobarTheme::from(theme),
KomobarTheme::from(*theme),
bg_color,
bg_color_with_alpha.clone(),
transparency_alpha,
@@ -742,42 +800,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;
@@ -798,7 +855,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());
}
}

View File

@@ -251,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 {

View File

@@ -25,6 +25,8 @@ pub struct MemoryConfig {
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>,
}
impl From<MemoryConfig> for Memory {
@@ -38,6 +40,7 @@ impl From<MemoryConfig> for Memory {
),
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
@@ -45,16 +48,23 @@ impl From<MemoryConfig> for Memory {
}
}
#[derive(Clone, Debug)]
struct MemoryOutput {
label: String,
selected: bool,
}
pub struct Memory {
pub enable: bool,
system: System,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
auto_select_over: Option<u8>,
last_updated: Instant,
}
impl Memory {
fn output(&mut self) -> String {
fn output(&mut self) -> MemoryOutput {
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.system.refresh_memory();
@@ -63,11 +73,17 @@ impl Memory {
let used = self.system.used_memory();
let total = self.system.total_memory();
match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("RAM: {}%", (used * 100) / total)
}
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total),
let usage = ((used * 100) / total) as u8;
let selected = self.auto_select_over.is_some_and(|o| usage >= o);
MemoryOutput {
label: match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("RAM: {}%", usage)
}
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", usage),
},
selected,
}
}
}
@@ -76,7 +92,9 @@ impl BarWidget for Memory {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
if !output.label.is_empty() {
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -85,23 +103,25 @@ impl BarWidget for Memory {
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
layout_job.append(
&output,
&output.label,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
valign: Align::Center,
..Default::default()
},
);
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{

View File

@@ -1,9 +1,11 @@
use crate::bar::Alignment;
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Color32;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
@@ -22,18 +24,36 @@ use sysinfo::Networks;
pub struct NetworkConfig {
/// Enable the Network widget
pub enable: bool,
/// Show total data transmitted
pub show_total_data_transmitted: bool,
/// Show network activity
pub show_network_activity: bool,
/// Show total received and transmitted activity
#[serde(alias = "show_total_data_transmitted")]
pub show_total_activity: bool,
/// Show received and transmitted activity
#[serde(alias = "show_network_activity")]
pub show_activity: bool,
/// Show default interface
pub show_default_interface: Option<bool>,
/// Characters to reserve for network activity data
pub network_activity_fill_characters: Option<usize>,
/// Characters to reserve for received and transmitted activity
#[serde(alias = "network_activity_fill_characters")]
pub activity_left_padding: Option<usize>,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))
pub auto_select: Option<NetworkSelectConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct NetworkSelectConfig {
/// Select the total received data when it's over this value
pub total_received_over: Option<u64>,
/// Select the total transmitted data when it's over this value
pub total_transmitted_over: Option<u64>,
/// Select the received data when it's over this value
pub received_over: Option<u64>,
/// Select the transmitted data when it's over this value
pub transmitted_over: Option<u64>,
}
impl From<NetworkConfig> for Network {
@@ -42,16 +62,15 @@ impl From<NetworkConfig> for Network {
Self {
enable: value.enable,
show_total_activity: value.show_total_data_transmitted,
show_activity: value.show_network_activity,
show_total_activity: value.show_total_activity,
show_activity: value.show_activity,
show_default_interface: value.show_default_interface.unwrap_or(true),
networks_network_activity: Networks::new_with_refreshed_list(),
default_interface: String::new(),
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
network_activity_fill_characters: value
.network_activity_fill_characters
.unwrap_or_default(),
auto_select: value.auto_select,
activity_left_padding: value.activity_left_padding.unwrap_or_default(),
last_state_total_activity: vec![],
last_state_activity: vec![],
last_updated_network_activity: Instant::now()
@@ -69,11 +88,12 @@ pub struct Network {
networks_network_activity: Networks,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
auto_select: Option<NetworkSelectConfig>,
default_interface: String,
last_state_total_activity: Vec<NetworkReading>,
last_state_activity: Vec<NetworkReading>,
last_updated_network_activity: Instant,
network_activity_fill_characters: usize,
activity_left_padding: usize,
}
impl Network {
@@ -105,24 +125,32 @@ impl Network {
for (interface_name, data) in &self.networks_network_activity {
if friendly_name.eq(interface_name) {
if self.show_activity {
let received = Self::to_pretty_bytes(
data.received(),
self.data_refresh_interval,
);
let transmitted = Self::to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval,
);
activity.push(NetworkReading::new(
NetworkReadingFormat::Speed,
Self::to_pretty_bytes(
data.received(),
self.data_refresh_interval,
),
Self::to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval,
),
ReadingValue::from(received),
ReadingValue::from(transmitted),
));
}
if self.show_total_activity {
let total_received =
Self::to_pretty_bytes(data.total_received(), 1);
let total_transmitted =
Self::to_pretty_bytes(data.total_transmitted(), 1);
total_activity.push(NetworkReading::new(
NetworkReadingFormat::Total,
Self::to_pretty_bytes(data.total_received(), 1),
Self::to_pretty_bytes(data.total_transmitted(), 1),
ReadingValue::from(total_received),
ReadingValue::from(total_transmitted),
))
}
}
@@ -138,105 +166,121 @@ impl Network {
(activity, total_activity)
}
fn reading_to_label(
fn reading_to_labels(
&self,
select_received: bool,
select_transmitted: bool,
ctx: &Context,
reading: NetworkReading,
reading: &NetworkReading,
config: RenderConfig,
) -> Label {
) -> (Label, Label) {
let (text_down, text_up) = match self.label_prefix {
LabelPrefix::None | LabelPrefix::Icon => match reading.format {
NetworkReadingFormat::Speed => (
format!(
"{: >width$}/s ",
reading.received_text,
width = self.network_activity_fill_characters
reading.received.pretty,
width = self.activity_left_padding
),
format!(
"{: >width$}/s",
reading.transmitted_text,
width = self.network_activity_fill_characters
reading.transmitted.pretty,
width = self.activity_left_padding
),
),
NetworkReadingFormat::Total => (
format!("{} ", reading.received_text),
reading.transmitted_text,
format!("{} ", reading.received.pretty),
reading.transmitted.pretty.clone(),
),
},
LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {
NetworkReadingFormat::Speed => (
format!(
"DOWN: {: >width$}/s ",
reading.received_text,
width = self.network_activity_fill_characters
reading.received.pretty,
width = self.activity_left_padding
),
format!(
"UP: {: >width$}/s",
reading.transmitted_text,
width = self.network_activity_fill_characters
reading.transmitted.pretty,
width = self.activity_left_padding
),
),
NetworkReadingFormat::Total => (
format!("\u{2211}DOWN: {}/s ", reading.received_text),
format!("\u{2211}UP: {}/s", reading.transmitted_text),
format!("\u{2211}DOWN: {}/s ", reading.received.pretty),
format!("\u{2211}UP: {}/s", reading.transmitted.pretty),
),
},
};
let icon_format = TextFormat::simple(
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
);
let text_format = TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
valign: Align::Center,
..Default::default()
};
let auto_text_color_received = config.auto_select_text.filter(|_| select_received);
let auto_text_color_transmitted = config.auto_select_text.filter(|_| select_transmitted);
// icon
let mut layout_job = LayoutJob::simple(
let mut layout_job_down = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::ARROW_FAT_DOWN.to_string()
if select_received {
egui_phosphor::regular::ARROW_FAT_LINES_DOWN.to_string()
} else {
egui_phosphor::regular::ARROW_FAT_DOWN.to_string()
}
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
icon_format.font_id.clone(),
icon_format.color,
config.icon_font_id.clone(),
auto_text_color_received.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
// text
layout_job.append(
layout_job_down.append(
&text_down,
ctx.style().spacing.item_spacing.x,
text_format.clone(),
TextFormat {
font_id: config.text_font_id.clone(),
color: auto_text_color_received.unwrap_or(ctx.style().visuals.text_color()),
valign: Align::Center,
..Default::default()
},
);
// icon
layout_job.append(
&match self.label_prefix {
let mut layout_job_up = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::ARROW_FAT_UP.to_string()
if select_transmitted {
egui_phosphor::regular::ARROW_FAT_LINES_UP.to_string()
} else {
egui_phosphor::regular::ARROW_FAT_UP.to_string()
}
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
0.0,
icon_format.clone(),
config.icon_font_id.clone(),
auto_text_color_transmitted.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
// text
layout_job.append(
layout_job_up.append(
&text_up,
ctx.style().spacing.item_spacing.x,
text_format.clone(),
TextFormat {
font_id: config.text_font_id.clone(),
color: auto_text_color_transmitted.unwrap_or(ctx.style().visuals.text_color()),
valign: Align::Center,
..Default::default()
},
);
Label::new(layout_job).selectable(false)
(
Label::new(layout_job_down).selectable(false),
Label::new(layout_job_up).selectable(false),
)
}
fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> String {
fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> (u64, String) {
let input = input_in_bytes as f32 / timespan_in_s as f32;
let mut magnitude = input.log(1024f32) as u32;
@@ -248,10 +292,30 @@ impl Network {
let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);
let result = input / ((1u64) << (magnitude * 10)) as f32;
match base {
Some(DataUnit::B) => format!("{result:.1} B"),
Some(unit) => format!("{result:.1} {unit}iB"),
None => String::from("Unknown data unit"),
(
input as u64,
match base {
Some(DataUnit::B) => format!("{result:.1} B"),
Some(unit) => format!("{result:.1} {unit}iB"),
None => String::from("Unknown data unit"),
},
)
}
fn show_frame<R>(
&self,
selected: bool,
auto_focus_fill: Option<Color32>,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) {
if SelectableFrame::new_auto(selected, auto_focus_fill)
.show(ui, add_contents)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() {
eprintln!("{}", error);
}
}
}
}
@@ -259,6 +323,8 @@ impl Network {
impl BarWidget for Network {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let is_reversed = matches!(config.alignment, Some(Alignment::Right));
// widget spacing: make sure to use the same config to call the apply_on_widget function
let mut render_config = config.clone();
@@ -266,17 +332,102 @@ impl BarWidget for Network {
let (activity, total_activity) = self.network_activity();
if self.show_total_activity {
for reading in total_activity {
render_config.apply_on_widget(true, ui, |ui| {
ui.add(self.reading_to_label(ctx, reading, config.clone()));
for reading in &total_activity {
render_config.apply_on_widget(false, ui, |ui| {
let select_received = self.auto_select.is_some_and(|f| {
f.total_received_over
.is_some_and(|o| reading.received.value > o)
});
let select_transmitted = self.auto_select.is_some_and(|f| {
f.total_transmitted_over
.is_some_and(|o| reading.transmitted.value > o)
});
let labels = self.reading_to_labels(
select_received,
select_transmitted,
ctx,
reading,
config.clone(),
);
if is_reversed {
self.show_frame(
select_transmitted,
config.auto_select_fill,
ui,
|ui| ui.add(labels.1),
);
self.show_frame(
select_received,
config.auto_select_fill,
ui,
|ui| ui.add(labels.0),
);
} else {
self.show_frame(
select_received,
config.auto_select_fill,
ui,
|ui| ui.add(labels.0),
);
self.show_frame(
select_transmitted,
config.auto_select_fill,
ui,
|ui| ui.add(labels.1),
);
}
});
}
}
if self.show_activity {
for reading in activity {
render_config.apply_on_widget(true, ui, |ui| {
ui.add(self.reading_to_label(ctx, reading, config.clone()));
for reading in &activity {
render_config.apply_on_widget(false, ui, |ui| {
let select_received = self.auto_select.is_some_and(|f| {
f.received_over.is_some_and(|o| reading.received.value > o)
});
let select_transmitted = self.auto_select.is_some_and(|f| {
f.transmitted_over
.is_some_and(|o| reading.transmitted.value > o)
});
let labels = self.reading_to_labels(
select_received,
select_transmitted,
ctx,
reading,
config.clone(),
);
if is_reversed {
self.show_frame(
select_transmitted,
config.auto_select_fill,
ui,
|ui| ui.add(labels.1),
);
self.show_frame(
select_received,
config.auto_select_fill,
ui,
|ui| ui.add(labels.0),
);
} else {
self.show_frame(
select_received,
config.auto_select_fill,
ui,
|ui| ui.add(labels.0),
);
self.show_frame(
select_transmitted,
config.auto_select_fill,
ui,
|ui| ui.add(labels.1),
);
}
});
}
}
@@ -314,15 +465,9 @@ impl BarWidget for Network {
);
render_config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn()
{
eprintln!("{}", error)
}
}
self.show_frame(false, None, ui, |ui| {
ui.add(Label::new(layout_job).selectable(false))
});
});
}
}
@@ -339,19 +484,38 @@ enum NetworkReadingFormat {
Total = 1,
}
#[derive(Clone)]
struct ReadingValue {
value: u64,
pretty: String,
}
impl From<(u64, String)> for ReadingValue {
fn from(value: (u64, String)) -> Self {
Self {
value: value.0,
pretty: value.1,
}
}
}
#[derive(Clone)]
struct NetworkReading {
pub format: NetworkReadingFormat,
pub received_text: String,
pub transmitted_text: String,
format: NetworkReadingFormat,
received: ReadingValue,
transmitted: ReadingValue,
}
impl NetworkReading {
pub fn new(format: NetworkReadingFormat, received: String, transmitted: String) -> Self {
NetworkReading {
fn new(
format: NetworkReadingFormat,
received: ReadingValue,
transmitted: ReadingValue,
) -> Self {
Self {
format,
received_text: received,
transmitted_text: transmitted,
received,
transmitted,
}
}
}

View File

@@ -1,3 +1,4 @@
use crate::bar::Alignment;
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
@@ -24,6 +25,10 @@ pub struct StorageConfig {
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>,
/// Hide when the current percentage is under this value [[1-100]]
pub auto_hide_under: Option<u8>,
}
impl From<StorageConfig> for Storage {
@@ -33,21 +38,30 @@ impl From<StorageConfig> for Storage {
disks: Disks::new_with_refreshed_list(),
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)),
last_updated: Instant::now(),
}
}
}
struct StorageDisk {
label: String,
selected: bool,
}
pub struct Storage {
pub enable: bool,
disks: Disks,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
auto_select_over: Option<u8>,
auto_hide_under: Option<u8>,
last_updated: Instant,
}
impl Storage {
fn output(&mut self) -> Vec<String> {
fn output(&mut self) -> Vec<StorageDisk> {
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.disks.refresh(true);
@@ -61,17 +75,26 @@ impl Storage {
let total = disk.total_space();
let available = disk.available_space();
let used = total - available;
let percentage = ((used * 100) / total) as u8;
disks.push(match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("{} {}%", mount.to_string_lossy(), (used * 100) / total)
}
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total),
})
let hide = self.auto_hide_under.is_some_and(|u| percentage <= u);
if !hide {
let selected = self.auto_select_over.is_some_and(|o| percentage >= o);
disks.push(StorageDisk {
label: match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("{} {}%", mount.to_string_lossy(), percentage)
}
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", percentage),
},
selected,
})
}
}
disks.sort();
disks.reverse();
disks.sort_by(|a, b| a.label.cmp(&b.label));
disks
}
@@ -80,7 +103,16 @@ impl Storage {
impl BarWidget for Storage {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
for output in self.output() {
let mut output = self.output();
let is_reversed = matches!(config.alignment, Some(Alignment::Right));
if is_reversed {
output.reverse();
}
for output in output {
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -89,23 +121,25 @@ impl BarWidget for Storage {
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
layout_job.append(
&output,
&output.label,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
valign: Align::Center,
..Default::default()
},
);
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked()
{
@@ -113,7 +147,7 @@ impl BarWidget for Storage {
.args([
"/C",
"explorer.exe",
output.split(' ').collect::<Vec<&str>>()[0],
output.label.split(' ').collect::<Vec<&str>>()[0],
])
.spawn()
{

View File

@@ -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()

View File

@@ -5,8 +5,6 @@ pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::animation::PerAnimationPrefixConfig;
pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::border_manager::BorderInfo;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
pub use komorebi::config_generation::ApplicationConfiguration;
pub use komorebi::config_generation::IdWithIdentifier;
pub use komorebi::config_generation::IdWithIdentifierAndComment;
@@ -14,7 +12,7 @@ pub use komorebi::config_generation::MatchingRule;
pub use komorebi::config_generation::MatchingStrategy;
pub use komorebi::container::Container;
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
pub use komorebi::core::resolve_home_path;
pub use komorebi::core::replace_env_in_path;
pub use komorebi::core::AnimationStyle;
pub use komorebi::core::ApplicationIdentifier;
pub use komorebi::core::Arrangement;
@@ -57,6 +55,7 @@ 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;
@@ -64,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;

View File

@@ -41,7 +41,9 @@ struct BorderColours {
single: Color32,
stack: Color32,
monocle: Color32,
floating: Color32,
unfocused: Color32,
unfocused_locked: Color32,
}
struct BorderConfig {
@@ -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();
}
})
});

View File

@@ -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"]

View File

@@ -1,5 +1,4 @@
use hex_color::HexColor;
use komorebi_themes::Color32;
#[cfg(feature = "schemars")]
use schemars::gen::SchemaGenerator;
#[cfg(feature = "schemars")]
@@ -9,6 +8,7 @@ use schemars::schema::Schema;
#[cfg(feature = "schemars")]
use schemars::schema::SchemaObject;
use crate::Color32;
use serde::Deserialize;
use serde::Serialize;
@@ -57,7 +57,7 @@ impl From<Colour> for Color32 {
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub struct Hex(HexColor);
pub struct Hex(pub HexColor);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Hex {

View 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])?)),
})
}
}

View File

@@ -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(),
},
}
}
}

View File

@@ -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"
@@ -28,6 +27,7 @@ net2 = "0.2"
os_info = "3.10"
parking_lot = "0.12"
paste = { workspace = true }
powershell_script = "1.0"
regex = "1"
schemars = { workspace = true, optional = true }
serde = { workspace = true }
@@ -49,6 +49,7 @@ windows-implement = { workspace = true }
windows-interface = { workspace = true }
winput = "0.2"
winreg = "0.55"
serde_with = { version = "3.12", features = ["schemars_0_8"] }
[build-dependencies]
shadow-rs = { workspace = true }

View File

@@ -9,13 +9,11 @@ use crate::core::Rect;
use crate::windows_api;
use crate::WindowsApi;
use crate::WINDOWS_11;
use color_eyre::eyre::anyhow;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::LazyLock;
use std::sync::OnceLock;
use windows::Win32::Foundation::FALSE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
@@ -118,7 +116,7 @@ pub struct Border {
pub hwnd: isize,
pub id: String,
pub monitor_idx: Option<usize>,
pub render_target: OnceLock<RenderTarget>,
pub render_target: Option<RenderTarget>,
pub tracking_hwnd: isize,
pub window_rect: Rect,
pub window_kind: WindowKind,
@@ -136,7 +134,7 @@ impl From<isize> for Border {
hwnd: value,
id: String::new(),
monitor_idx: None,
render_target: OnceLock::new(),
render_target: None,
tracking_hwnd: 0,
window_rect: Rect::default(),
window_kind: WindowKind::Unfocused,
@@ -184,7 +182,7 @@ impl Border {
hwnd: 0,
id: container_id,
monitor_idx: Some(monitor_idx),
render_target: OnceLock::new(),
render_target: None,
tracking_hwnd,
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
window_kind: WindowKind::Unfocused,
@@ -243,8 +241,14 @@ impl Border {
let _ = DwmEnableBlurBehindWindow(border.hwnd(), &bh);
}
border.update_brushes()?;
Ok(border)
}
pub fn update_brushes(&mut self) -> color_eyre::Result<()> {
let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES {
hwnd: HWND(windows_api::as_ptr!(border.hwnd)),
hwnd: HWND(windows_api::as_ptr!(self.hwnd)),
pixelSize: Default::default(),
presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,
};
@@ -265,7 +269,7 @@ impl Border {
.CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties)
} {
Ok(render_target) => unsafe {
border.brush_properties = *BRUSH_PROPERTIES.deref();
self.brush_properties = *BRUSH_PROPERTIES.deref();
for window_kind in [
WindowKind::Single,
WindowKind::Stack,
@@ -283,24 +287,18 @@ impl Border {
};
if let Ok(brush) =
render_target.CreateSolidColorBrush(&color, Some(&border.brush_properties))
render_target.CreateSolidColorBrush(&color, Some(&self.brush_properties))
{
border.brushes.insert(window_kind, brush);
self.brushes.insert(window_kind, brush);
}
}
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
if border
.render_target
.set(RenderTarget(render_target.clone()))
.is_err()
{
return Err(anyhow!("could not store border render target"));
}
self.render_target = Some(RenderTarget(render_target));
border.rounded_rect = {
let radius = 8.0 + border.width as f32 / 2.0;
self.rounded_rect = {
let radius = 8.0 + self.width as f32 / 2.0;
D2D1_ROUNDED_RECT {
rect: Default::default(),
radiusX: radius,
@@ -308,7 +306,7 @@ impl Border {
}
};
Ok(border)
Ok(())
},
Err(error) => Err(error.into()),
}
@@ -395,7 +393,7 @@ impl Border {
}
if !rect.is_same_size_as(&old_rect) {
if let Some(render_target) = (*border_pointer).render_target.get() {
if let Some(render_target) = (*border_pointer).render_target.as_ref() {
let border_width = (*border_pointer).width;
let border_offset = (*border_pointer).offset;
@@ -477,7 +475,7 @@ impl Border {
tracing::error!("failed to update border position {error}");
}
if let Some(render_target) = (*border_pointer).render_target.get() {
if let Some(render_target) = (*border_pointer).render_target.as_ref() {
(*border_pointer).width = BORDER_WIDTH.load(Ordering::Relaxed);
(*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed);

View File

@@ -7,8 +7,6 @@ use crate::core::WindowKind;
use crate::ring::Ring;
use crate::windows_api;
use crate::workspace::WorkspaceLayer;
use crate::Colour;
use crate::Rgb;
use crate::WindowManager;
use crate::WindowsApi;
use border::border_hwnds;
@@ -17,6 +15,8 @@ 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 serde::Deserialize;
@@ -73,7 +73,10 @@ impl Deref for RenderTarget {
}
}
pub struct Notification(pub Option<isize>);
pub enum Notification {
Update(Option<isize>),
ForceUpdate,
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct BorderInfo {
@@ -102,16 +105,21 @@ fn event_rx() -> Receiver<Notification> {
}
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,
})
let id = WINDOWS_BORDERS.lock().get(&hwnd)?.clone();
BORDER_STATE.lock().get(&id).map(|b| BorderInfo {
border_hwnd: b.hwnd,
window_kind: b.window_kind,
})
}
pub fn send_notification(hwnd: Option<isize>) {
if event_tx().try_send(Notification(hwnd)).is_err() {
if event_tx().try_send(Notification::Update(hwnd)).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn send_force_update() {
if event_tx().try_send(Notification::ForceUpdate).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
@@ -127,6 +135,8 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
let _ = destroy_border(border);
}
drop(borders);
WINDOWS_BORDERS.lock().clear();
let mut remaining_hwnds = vec![];
@@ -175,7 +185,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
tracing::info!("listening");
let receiver = event_rx();
event_tx().send(Notification(None))?;
event_tx().send(Notification::Update(None))?;
let mut previous_snapshot = Ring::default();
let mut previous_pending_move_op = None;
@@ -261,64 +271,75 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
}
BorderImplementation::Komorebi => {
let mut should_process_notification = true;
let should_process_notification = match notification {
Notification::Update(notification_hwnd) => {
let mut should_process_notification = true;
if monitors == previous_snapshot
// handle the window dragging edge case
&& pending_move_op == previous_pending_move_op
{
should_process_notification = false;
}
if monitors == previous_snapshot
// handle the window dragging edge case
&& pending_move_op == previous_pending_move_op
{
should_process_notification = false;
}
// handle the pause edge case
if is_paused && !previous_is_paused {
should_process_notification = true;
}
// handle the unpause edge case
if previous_is_paused && !is_paused {
should_process_notification = true;
}
// handle the retile edge case
if !should_process_notification && BORDER_STATE.lock().is_empty() {
should_process_notification = true;
}
// when we switch focus to/from a floating window
let switch_focus_to_from_floating_window = floating_window_hwnds.iter().any(|fw| {
// if we switch focus to a floating window
fw == &notification.0.unwrap_or_default() ||
// if there is any floating window with a `WindowKind::Floating` border
// that no longer is the foreground window then we need to update that
// border.
(fw != &foreground_window
&& window_border(*fw)
.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;
}
if !should_process_notification {
if let Some(ref previous) = previous_notification {
if previous.0.unwrap_or_default() != notification.0.unwrap_or_default() {
// handle the pause edge case
if is_paused && !previous_is_paused {
should_process_notification = true;
}
// handle the unpause edge case
if previous_is_paused && !is_paused {
should_process_notification = true;
}
// handle the retile edge case
if !should_process_notification && BORDER_STATE.lock().is_empty() {
should_process_notification = true;
}
// when we switch focus to/from a floating window
let switch_focus_to_from_floating_window =
floating_window_hwnds.iter().any(|fw| {
// if we switch focus to a floating window
fw == &notification_hwnd.unwrap_or_default() ||
// if there is any floating window with a `WindowKind::Floating` border
// that no longer is the foreground window then we need to update that
// border.
(fw != &foreground_window
&& window_border(*fw)
.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_hwnd.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;
}
if !should_process_notification {
if let Some(Notification::Update(ref previous)) = previous_notification
{
if previous.unwrap_or_default()
!= notification_hwnd.unwrap_or_default()
{
should_process_notification = true;
}
}
}
should_process_notification
}
}
Notification::ForceUpdate => true,
};
if !should_process_notification {
tracing::trace!("monitor state matches latest snapshot, skipping notification");
@@ -415,6 +436,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if new_border {
border.set_position(&rect, focused_window_hwnd)?;
} else if matches!(notification, Notification::ForceUpdate) {
// Update the border brushes if there was a forced update
// notification and this is not a new border (new border's
// already have their brushes updated on creation)
border.update_brushes()?;
}
border.invalidate();
@@ -544,12 +570,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer;
let forced_update = matches!(notification, Notification::ForceUpdate);
let should_invalidate = new_border
|| (last_focus_state != new_focus_state)
|| layer_changed;
|| layer_changed
|| forced_update;
if should_invalidate {
if forced_update && !new_border {
// Update the border brushes if there was a forced update
// notification and this is not a new border (new border's
// already have their brushes updated on creation)
border.update_brushes()?;
}
border.set_position(&rect, focused_window_hwnd)?;
border.invalidate();
}
@@ -594,12 +628,21 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer;
let forced_update =
matches!(notification, Notification::ForceUpdate);
let should_invalidate = new_border
|| (last_focus_state != new_focus_state)
|| layer_changed;
|| layer_changed
|| forced_update;
if should_invalidate {
if forced_update && !new_border {
// Update the border brushes if there was a forced update
// notification and this is not a new border (new border's
// already have their brushes updated on creation)
border.update_brushes()?;
}
border.set_position(&rect, window.hwnd)?;
border.invalidate();
}

View File

@@ -1,12 +1,10 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use serde::Deserialize;
use serde::Serialize;
@@ -28,7 +26,10 @@ pub use default_layout::DefaultLayout;
pub use direction::Direction;
pub use layout::Layout;
pub use operation_direction::OperationDirection;
pub use pathext::replace_env_in_path;
pub use pathext::resolve_option_hashmap_usize_path;
pub use pathext::PathExt;
pub use pathext::ResolvedPathBuf;
pub use rect::Rect;
pub mod animation;
@@ -44,6 +45,8 @@ pub mod operation_direction;
pub mod pathext;
pub mod rect;
// serde_as must be before derive
#[serde_with::serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "type", content = "content")]
@@ -105,7 +108,7 @@ pub enum SocketMessage {
AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout),
CycleLayout(CycleDirection),
ChangeLayoutCustom(PathBuf),
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
FlipLayout(Axis),
ToggleWorkspaceWindowContainerBehaviour,
ToggleWorkspaceFloatOverride,
@@ -123,8 +126,8 @@ pub enum SocketMessage {
RetileWithResizeDimensions,
QuickSave,
QuickLoad,
Save(PathBuf),
Load(PathBuf),
Save(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
Load(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
CycleFocusEmptyWorkspace(CycleDirection),
@@ -147,23 +150,28 @@ pub enum SocketMessage {
WorkspaceName(usize, usize, String),
WorkspaceLayout(usize, usize, DefaultLayout),
NamedWorkspaceLayout(String, DefaultLayout),
WorkspaceLayoutCustom(usize, usize, PathBuf),
NamedWorkspaceLayoutCustom(String, PathBuf),
WorkspaceLayoutCustom(usize, usize, #[serde_as(as = "ResolvedPathBuf")] PathBuf),
NamedWorkspaceLayoutCustom(String, #[serde_as(as = "ResolvedPathBuf")] PathBuf),
WorkspaceLayoutRule(usize, usize, usize, DefaultLayout),
NamedWorkspaceLayoutRule(String, usize, DefaultLayout),
WorkspaceLayoutCustomRule(usize, usize, usize, PathBuf),
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
WorkspaceLayoutCustomRule(
usize,
usize,
usize,
#[serde_as(as = "ResolvedPathBuf")] PathBuf,
),
NamedWorkspaceLayoutCustomRule(String, usize, #[serde_as(as = "ResolvedPathBuf")] PathBuf),
ClearWorkspaceLayoutRules(usize, usize),
ClearNamedWorkspaceLayoutRules(String),
ToggleWorkspaceLayer,
// Configuration
ReloadConfiguration,
ReplaceConfiguration(PathBuf),
ReloadStaticConfiguration(PathBuf),
ReplaceConfiguration(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
ReloadStaticConfiguration(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
WatchConfiguration(bool),
CompleteConfiguration,
AltFocusHack(bool),
Theme(KomorebiTheme),
Theme(Box<KomorebiTheme>),
Animation(bool, Option<AnimationPrefix>),
AnimationDuration(u64, Option<AnimationPrefix>),
AnimationFps(u64),
@@ -202,6 +210,9 @@ pub enum SocketMessage {
ClearNamedWorkspaceRules(String),
ClearAllWorkspaceRules,
EnforceWorkspaceRules,
SessionFloatRule,
SessionFloatRules,
ClearSessionFloatRules,
#[serde(alias = "FloatRule")]
IgnoreRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),
@@ -329,6 +340,8 @@ pub enum StateQuery {
FocusedContainerIndex,
FocusedWindowIndex,
FocusedWorkspaceName,
FocusedWorkspaceLayout,
Version,
}
#[derive(
@@ -453,45 +466,28 @@ impl Sizing {
}
}
pub fn resolve_home_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let mut resolved_path = PathBuf::new();
let mut resolved = false;
for c in path.as_ref().components() {
match c {
std::path::Component::Normal(c)
if (c == "~" || c == "$Env:USERPROFILE" || c == "$HOME") && !resolved =>
{
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
#[cfg(test)]
mod tests {
use super::*;
resolved_path.extend(home.components());
resolved = true;
}
#[test]
fn deserializes() {
// Set a variable for testing
std::env::set_var("VAR", "VALUE");
std::path::Component::Normal(c) if (c == "$Env:KOMOREBI_CONFIG_HOME") && !resolved => {
let komorebi_config_home =
PathBuf::from(std::env::var("KOMOREBI_CONFIG_HOME").ok().ok_or_else(|| {
anyhow!("there is no KOMOREBI_CONFIG_HOME environment variable set")
})?);
let json = r#"{"type":"WorkspaceLayoutCustomRule","content":[0,0,0,"/path/%VAR%/d"]}"#;
let message: SocketMessage = serde_json::from_str(json).unwrap();
resolved_path.extend(komorebi_config_home.components());
resolved = true;
}
let SocketMessage::WorkspaceLayoutCustomRule(
_workspace_index,
_workspace_number,
_monitor_index,
path,
) = message
else {
panic!("Expected WorkspaceLayoutCustomRule");
};
_ => resolved_path.push(c),
}
assert_eq!(path, PathBuf::from("/path/VALUE/d"));
}
let parent = resolved_path
.parent()
.ok_or_else(|| anyhow!("cannot parse parent directory"))?;
Ok(if parent.is_dir() {
let file = resolved_path
.components()
.last()
.ok_or_else(|| anyhow!("cannot parse filename"))?;
dunce::canonicalize(parent)?.join(file)
} else {
resolved_path
})
}

View File

@@ -1,48 +1,192 @@
use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
/// Path extension trait
pub trait PathExt {
/// Resolve environment variables components in a path.
///
/// Resolves the follwing formats:
/// - CMD: `%variable%`
/// - PowerShell: `$Env:variable`
/// - Bash: `$variable`.
fn replace_env(&self) -> PathBuf;
}
impl PathExt for PathBuf {
/// Blanket implementation for all types that can be converted to a `Path`.
impl<P: AsRef<Path>> PathExt for P {
fn replace_env(&self) -> PathBuf {
let mut result = PathBuf::new();
let mut out = PathBuf::new();
for component in self.components() {
match component {
Component::Normal(segment) => {
// Check if it starts with `$` or `$Env:`
if let Some(stripped_segment) = segment.to_string_lossy().strip_prefix('$') {
let var_name = if let Some(env_name) = stripped_segment.strip_prefix("Env:")
{
// Extract the variable name after `$Env:`
env_name
} else if stripped_segment == "HOME" {
// Special case for `$HOME`
"USERPROFILE"
} else {
// Extract the variable name after `$`
stripped_segment
};
if let Ok(value) = env::var(var_name) {
result.push(&value); // Replace with the value
} else {
result.push(segment); // Keep as-is if variable is not found
}
} else {
result.push(segment); // Keep as-is if not an environment variable
for c in self.as_ref().components() {
match c {
Component::Normal(mut c) => {
// Special case for ~ and $HOME, replace with $Env:USERPROFILE
if c == OsStr::new("~") || c.eq_ignore_ascii_case("$HOME") {
c = OsStr::new("$Env:USERPROFILE");
}
let bytes = c.as_encoded_bytes();
// %LOCALAPPDATA%
let var = if bytes[0] == b'%' && bytes[bytes.len() - 1] == b'%' {
Some(&bytes[1..bytes.len() - 1])
} else {
// prefix length is 5 for $Env: and 1 for $
// so we take the minimum of 5 and the length of the bytes
let prefix = &bytes[..5.min(bytes.len())];
let prefix = unsafe { OsStr::from_encoded_bytes_unchecked(prefix) };
// $Env:LOCALAPPDATA
if prefix.eq_ignore_ascii_case("$Env:") {
Some(&bytes[5..])
} else if bytes[0] == b'$' {
// $LOCALAPPDATA
Some(&bytes[1..])
} else {
// not a variable
None
}
};
// if component is a variable, get the value from the environment
if let Some(var) = var {
let var = unsafe { OsStr::from_encoded_bytes_unchecked(var) };
if let Some(value) = env::var_os(var) {
out.push(value);
continue;
}
}
// if not a variable, or a value couldn't be obtained from environemnt
// then push the component as is
out.push(c);
}
_ => {
// Add other components (e.g., root, parent) as-is
result.push(component.as_os_str());
}
// other components are pushed as is
_ => out.push(c),
}
}
result
out
}
}
/// Replace environment variables in a path. This is a wrapper around
/// [`PathExt::replace_env`] to be used in Clap arguments parsing.
pub fn replace_env_in_path(input: &str) -> Result<PathBuf, std::convert::Infallible> {
Ok(input.replace_env())
}
/// A wrapper around [`PathBuf`] that has a custom [Deserialize] implementation
/// that uses [`PathExt::replace_env`] to resolve environment variables
#[derive(Clone, Debug)]
pub struct ResolvedPathBuf(PathBuf);
impl ResolvedPathBuf {
/// Create a new [`ResolvedPathBuf`] from a [`PathBuf`]
pub fn new(path: PathBuf) -> Self {
Self(path.replace_env())
}
}
impl From<ResolvedPathBuf> for PathBuf {
fn from(path: ResolvedPathBuf) -> Self {
path.0
}
}
impl serde_with::SerializeAs<PathBuf> for ResolvedPathBuf {
fn serialize_as<S>(path: &PathBuf, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
path.serialize(serializer)
}
}
impl<'de> serde_with::DeserializeAs<'de, PathBuf> for ResolvedPathBuf {
fn deserialize_as<D>(deserializer: D) -> Result<PathBuf, D::Error>
where
D: serde::Deserializer<'de>,
{
let path = PathBuf::deserialize(deserializer)?;
Ok(path.replace_env())
}
}
#[cfg(feature = "schemars")]
impl serde_with::schemars_0_8::JsonSchemaAs<PathBuf> for ResolvedPathBuf {
fn schema_name() -> String {
"PathBuf".to_owned()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<PathBuf as schemars::JsonSchema>::json_schema(gen)
}
}
/// Custom deserializer for [`Option<HashMap<usize, PathBuf>>`] that uses
/// [`PathExt::replace_env`] to resolve environment variables in the paths.
///
/// This is used in `WorkspaceConfig` struct because we can't use
/// #[serde_with::serde_as] as it doesn't handle [`Option<HashMap<usize, ResolvedPathBuf>>`]
/// quite well and generated compiler errors that can't be fixed because of Rust's orphan rule.
pub fn resolve_option_hashmap_usize_path<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<usize, PathBuf>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let map = Option::<HashMap<usize, PathBuf>>::deserialize(deserializer)?;
Ok(map.map(|map| map.into_iter().map(|(k, v)| (k, v.replace_env())).collect()))
}
#[cfg(test)]
mod tests {
use super::*;
// helper functions
fn expected<P: AsRef<Path>>(p: P) -> PathBuf {
// Ensure that the path is using the correct path separator for the OS.
p.as_ref().components().collect::<PathBuf>()
}
fn resolve<P: AsRef<Path>>(p: P) -> PathBuf {
p.replace_env()
}
#[test]
fn resolves_env_vars() {
// Set a variable for testing
std::env::set_var("VAR", "VALUE");
// %VAR% format
assert_eq!(resolve("/path/%VAR%/d"), expected("/path/VALUE/d"));
// $env:VAR format
assert_eq!(resolve("/path/$env:VAR/d"), expected("/path/VALUE/d"));
// $VAR format
assert_eq!(resolve("/path/$VAR/d"), expected("/path/VALUE/d"));
// non-existent variable
assert_eq!(resolve("/path/%ASD%/to/d"), expected("/path/%ASD%/to/d"));
assert_eq!(
resolve("/path/$env:ASD/to/d"),
expected("/path/$env:ASD/to/d")
);
assert_eq!(resolve("/path/$ASD/to/d"), expected("/path/$ASD/to/d"));
// Set a $env:USERPROFILE variable for testing
std::env::set_var("USERPROFILE", "C:\\Users\\user");
// ~ and $HOME should be replaced with $Env:USERPROFILE
assert_eq!(resolve("~"), expected("C:\\Users\\user"));
assert_eq!(resolve("$HOME"), expected("C:\\Users\\user"));
}
}

View File

@@ -5,7 +5,6 @@ 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;
@@ -47,8 +46,8 @@ 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::*;
@@ -159,6 +158,7 @@ lazy_static! {
matching_strategy: Option::from(MatchingStrategy::Equals),
})
]));
static ref SESSION_FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"Chrome_RenderWidgetHostHWND".to_string(),
@@ -188,15 +188,16 @@ lazy_static! {
Arc::new(Mutex::new(HidingBehaviour::Cloak));
pub static ref HOME_DIR: PathBuf = {
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(|_| dirs::home_dir().expect("there is no home directory"), |home_path| {
let home = PathBuf::from(&home_path);
let home = home_path.replace_env();
if home.as_path().is_dir() {
home
} else {
panic!(
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
);
}
assert!(
home.is_dir(),
"$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
home_path
);
home
})
};
pub static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
@@ -239,6 +240,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);

View File

@@ -22,6 +22,7 @@ use crossbeam_utils::Backoff;
use komorebi::animation::AnimationEngine;
use komorebi::animation::ANIMATION_ENABLED_GLOBAL;
use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
use komorebi::replace_env_in_path;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
@@ -56,8 +57,6 @@ use komorebi::HOME_DIR;
use komorebi::INITIAL_CONFIGURATION_LOADED;
use komorebi::SESSION_ID;
shadow_rs::shadow!(build);
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");
@@ -165,7 +164,7 @@ enum LogLevel {
}
#[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")]
@@ -178,6 +177,7 @@ struct Opts {
tcp_port: Option<usize>,
/// Path to a static configuration JSON file
#[clap(short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>,
/// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi
#[clap(long)]

View File

@@ -12,15 +12,21 @@ use getset::Setters;
use serde::Deserialize;
use serde::Serialize;
use crate::border_manager::BORDER_ENABLED;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::core::Rect;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
use crate::workspace::WorkspaceGlobals;
use crate::workspace::WorkspaceLayer;
use crate::DefaultLayout;
use crate::FloatingLayerBehaviour;
use crate::Layout;
use crate::OperationDirection;
use crate::Wallpaper;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
@@ -60,6 +66,10 @@ pub struct Monitor {
pub container_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
pub workspace_padding: Option<i32>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub wallpaper: Option<Wallpaper>,
#[getset(get_copy = "pub", set = "pub")]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
}
impl_ring_elements!(Monitor, Workspace);
@@ -115,6 +125,8 @@ pub fn new(
workspace_names: HashMap::default(),
container_padding: None,
workspace_padding: None,
wallpaper: None,
floating_layer_behaviour: None,
}
}
@@ -156,6 +168,8 @@ impl Monitor {
workspace_names: Default::default(),
container_padding: None,
workspace_padding: None,
wallpaper: None,
floating_layer_behaviour: None,
}
}
@@ -165,11 +179,23 @@ impl Monitor {
.unwrap_or(None)
}
pub fn focused_workspace_layout(&self) -> Option<Layout> {
self.focused_workspace().and_then(|workspace| {
if *workspace.tile() {
Some(workspace.layout().clone())
} else {
None
}
})
}
pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
let focused_idx = self.focused_workspace_idx();
let hmonitor = self.id();
let monitor_wp = self.wallpaper.clone();
for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
if i == focused_idx {
workspace.restore(mouse_follows_focus)?;
workspace.restore(mouse_follows_focus, hmonitor, &monitor_wp)?;
} else {
workspace.hide(None);
}
@@ -186,18 +212,34 @@ impl Monitor {
let workspace_padding = self
.workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
let (border_width, border_offset) = {
let border_enabled = BORDER_ENABLED.load(Ordering::SeqCst);
if border_enabled {
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
(border_width, border_offset)
} else {
(0, 0)
}
};
let work_area = *self.work_area_size();
let offset = self.work_area_offset.or(offset);
let work_area_offset = self.work_area_offset.or(offset);
let window_based_work_area_offset = self.window_based_work_area_offset();
let limit = self.window_based_work_area_offset_limit();
let window_based_work_area_offset_limit = self.window_based_work_area_offset_limit();
let floating_layer_behaviour = self.floating_layer_behaviour();
for workspace in self.workspaces_mut() {
workspace.globals_mut().container_padding = container_padding;
workspace.globals_mut().workspace_padding = workspace_padding;
workspace.globals_mut().work_area = work_area;
workspace.globals_mut().work_area_offset = offset;
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
workspace.globals_mut().window_based_work_area_offset_limit = limit;
workspace.globals = WorkspaceGlobals {
container_padding,
workspace_padding,
border_width,
border_offset,
work_area,
work_area_offset,
window_based_work_area_offset,
window_based_work_area_offset_limit,
floating_layer_behaviour,
}
}
}
@@ -209,18 +251,34 @@ impl Monitor {
let workspace_padding = self
.workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
let (border_width, border_offset) = {
let border_enabled = BORDER_ENABLED.load(Ordering::SeqCst);
if border_enabled {
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
(border_width, border_offset)
} else {
(0, 0)
}
};
let work_area = *self.work_area_size();
let offset = self.work_area_offset.or(offset);
let work_area_offset = self.work_area_offset.or(offset);
let window_based_work_area_offset = self.window_based_work_area_offset();
let limit = self.window_based_work_area_offset_limit();
let window_based_work_area_offset_limit = self.window_based_work_area_offset_limit();
let floating_layer_behaviour = self.floating_layer_behaviour();
if let Some(workspace) = self.workspaces_mut().get_mut(workspace_idx) {
workspace.globals_mut().container_padding = container_padding;
workspace.globals_mut().workspace_padding = workspace_padding;
workspace.globals_mut().work_area = work_area;
workspace.globals_mut().work_area_offset = offset;
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
workspace.globals_mut().window_based_work_area_offset_limit = limit;
workspace.globals = WorkspaceGlobals {
container_padding,
workspace_padding,
border_width,
border_offset,
work_area,
work_area_offset,
window_based_work_area_offset,
window_based_work_area_offset_limit,
floating_layer_behaviour,
}
}
}
@@ -603,4 +661,117 @@ mod tests {
// Should be the last workspace index: 1
assert_eq!(new_workspace_index, 1);
}
#[test]
fn test_move_container_to_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);
{
// Create workspace 1 and add 3 containers
let workspace = m.focused_workspace_mut().unwrap();
for _ in 0..3 {
let container = Container::default();
workspace.add_container_to_back(container);
}
// Should have 3 containers in workspace 1
assert_eq!(m.focused_workspace().unwrap().containers().len(), 3);
}
// Create and focus workspace 2
m.focus_workspace(new_workspace_index).unwrap();
// Focus workspace 1
m.focus_workspace(0).unwrap();
// Move container to workspace 2
m.move_container_to_workspace(1, true, None).unwrap();
// Should be focused on workspace 2
assert_eq!(m.focused_workspace_idx(), 1);
// Workspace 2 should have 1 container now
assert_eq!(m.focused_workspace().unwrap().containers().len(), 1);
// Move to workspace 1
m.focus_workspace(0).unwrap();
// Workspace 1 should have 2 containers
assert_eq!(m.focused_workspace().unwrap().containers().len(), 2);
// Move a another container from workspace 1 to workspace 2 without following
m.move_container_to_workspace(1, false, None).unwrap();
// Should have 1 container
assert_eq!(m.focused_workspace().unwrap().containers().len(), 1);
// Should still be focused on workspace 1
assert_eq!(m.focused_workspace_idx(), 0);
// Switch to workspace 2
m.focus_workspace(1).unwrap();
// Workspace 2 should now have 2 containers
assert_eq!(m.focused_workspace().unwrap().containers().len(), 2);
}
#[test]
fn test_ensure_workspace_count_workspace_contains_two_workspaces() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create and focus another workspace
let new_workspace_index = m.new_workspace_idx();
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces now
assert_eq!(m.workspaces().len(), 2, "Monitor should have 2 workspaces");
// Ensure the monitor has at least 5 workspaces
m.ensure_workspace_count(5);
// Monitor should have 5 workspaces
assert_eq!(m.workspaces().len(), 5, "Monitor should have 5 workspaces");
}
#[test]
fn test_ensure_workspace_count_only_default_workspace() {
let mut m = Monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Ensure the monitor has at least 5 workspaces
m.ensure_workspace_count(5);
// Monitor should have 5 workspaces
assert_eq!(m.workspaces().len(), 5, "Monitor should have 5 workspaces");
// Try to call the ensure workspace count again to ensure it doesn't change
m.ensure_workspace_count(3);
assert_eq!(m.workspaces().len(), 5, "Monitor should have 5 workspaces");
}
}

View File

@@ -553,6 +553,8 @@ where
workspace_names: cached.workspace_names.clone(),
container_padding: cached.container_padding,
workspace_padding: cached.workspace_padding,
wallpaper: cached.wallpaper.clone(),
floating_layer_behaviour: cached.floating_layer_behaviour,
};
let focused_workspace_idx = m.focused_workspace_idx();

View File

@@ -1,6 +1,7 @@
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;
@@ -19,9 +20,18 @@ use std::sync::Arc;
use std::time::Duration;
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::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;
@@ -38,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;
@@ -72,6 +72,7 @@ use crate::State;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
@@ -81,6 +82,7 @@ use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SESSION_FLOATING_APPLICATIONS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKETS;
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
@@ -224,6 +226,7 @@ impl WindowManager {
_ => {}
};
let mut force_update_borders = false;
match message {
SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
@@ -395,7 +398,7 @@ impl WindowManager {
workspace.locked_containers.remove(&container_idx);
}
SocketMessage::ToggleLock => self.toggle_lock()?,
SocketMessage::ToggleFloat => self.toggle_float()?,
SocketMessage::ToggleFloat => self.toggle_float(false)?,
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {
@@ -542,6 +545,53 @@ impl WindowManager {
}));
}
}
SocketMessage::SessionFloatRule => {
let foreground_window = WindowsApi::foreground_window()?;
let window = Window::from(foreground_window);
if let (Ok(exe), Ok(title), Ok(class)) =
(window.exe(), window.title(), window.class())
{
let rule = MatchingRule::Composite(vec![
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: exe,
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Title,
id: title,
matching_strategy: Option::from(MatchingStrategy::Equals),
},
IdWithIdentifier {
kind: ApplicationIdentifier::Class,
id: class,
matching_strategy: Option::from(MatchingStrategy::Equals),
},
]);
let mut floating_applications = FLOATING_APPLICATIONS.lock();
floating_applications.push(rule.clone());
let mut session_floating_applications = SESSION_FLOATING_APPLICATIONS.lock();
session_floating_applications.push(rule.clone());
self.toggle_float(true)?;
}
}
SocketMessage::SessionFloatRules => {
let session_floating_applications = SESSION_FLOATING_APPLICATIONS.lock();
let rules = match serde_json::to_string_pretty(&*session_floating_applications) {
Ok(rules) => rules,
Err(error) => error.to_string(),
};
reply.write_all(rules.as_bytes())?;
}
SocketMessage::ClearSessionFloatRules => {
let mut floating_applications = FLOATING_APPLICATIONS.lock();
let mut session_floating_applications = SESSION_FLOATING_APPLICATIONS.lock();
floating_applications.retain(|r| !session_floating_applications.contains(r));
session_floating_applications.clear()
}
SocketMessage::IgnoreRule(identifier, ref id) => {
let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();
@@ -861,10 +911,12 @@ impl WindowManager {
}
SocketMessage::Retile => {
border_manager::destroy_all_borders()?;
force_update_borders = true;
self.retile_all(false)?
}
SocketMessage::RetileWithResizeDimensions => {
border_manager::destroy_all_borders()?;
force_update_borders = true;
self.retile_all(true)?
}
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
@@ -1377,6 +1429,20 @@ impl WindowManager {
.focused_workspace_name()
.unwrap_or_else(|| focused_monitor.focused_workspace_idx().to_string())
}
StateQuery::Version => build::RUST_VERSION.to_string(),
StateQuery::FocusedWorkspaceLayout => {
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
focused_monitor.focused_workspace_layout().map_or_else(
|| "None".to_string(),
|layout| match layout {
Layout::Default(default_layout) => default_layout.to_string(),
Layout::Custom(_) => "Custom".to_string(),
},
)
}
};
reply.write_all(response.as_bytes())?;
@@ -1586,6 +1652,7 @@ impl WindowManager {
}
SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
force_update_borders = true;
}
SocketMessage::ReplaceConfiguration(ref config) => {
// Check that this is a valid static config file first
@@ -1614,15 +1681,78 @@ impl WindowManager {
// Set self to the new wm instance
*self = wm;
// check if there are any bars
let mut system = sysinfo::System::new_all();
system.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
let has_bar = system
.processes_by_name("komorebi-bar.exe".as_ref())
.next()
.is_some();
// stop bar(s)
if has_bar {
let script = r"
Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
// start new bar(s)
let mut config = StaticConfig::read(config)?;
if let Some(display_bar_configurations) =
&mut config.bar_configurations
{
for config_file_path in &mut *display_bar_configurations {
let script = r#"Start-Process "komorebi-bar" '"--config" "CONFIGFILE"' -WindowStyle hidden"#
.replace("CONFIGFILE", &config_file_path.to_string_lossy());
match powershell_script::run(&script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
} else {
let script = r"
if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
{
Start-Process komorebi-bar -WindowStyle hidden
}
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
}
Err(error) => {
println!("Error: {error}");
}
}
}
force_update_borders = true;
}
}
SocketMessage::ReloadStaticConfiguration(ref pathbuf) => {
self.reload_static_configuration(pathbuf)?;
force_update_borders = true;
}
SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
self.update_focused_workspace(false, false)?;
force_update_borders = true;
}
}
SocketMessage::WatchConfiguration(enable) => {
@@ -1880,6 +2010,8 @@ impl WindowManager {
self.remove_all_accents()?;
}
}
} else if matches!(IMPLEMENTATION.load(), BorderImplementation::Komorebi) {
force_update_borders = true;
}
}
SocketMessage::BorderImplementation(implementation) => {
@@ -1892,44 +2024,49 @@ impl WindowManager {
match IMPLEMENTATION.load() {
BorderImplementation::Komorebi => {
self.remove_all_accents()?;
force_update_borders = true;
}
BorderImplementation::Windows => {
border_manager::destroy_all_borders()?;
}
}
border_manager::send_notification(None);
}
}
SocketMessage::BorderColour(kind, r, g, b) => match kind {
WindowKind::Single => {
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
SocketMessage::BorderColour(kind, r, g, b) => {
match kind {
WindowKind::Single => {
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Stack => {
border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Monocle => {
border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
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);
}
}
WindowKind::Stack => {
border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Monocle => {
border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
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);
}
},
force_update_borders = true;
}
SocketMessage::BorderStyle(style) => {
STYLE.store(style);
force_update_borders = true;
}
SocketMessage::BorderWidth(width) => {
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
force_update_borders = true;
}
SocketMessage::BorderOffset(offset) => {
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
force_update_borders = true;
}
SocketMessage::Animation(enable, prefix) => match prefix {
Some(prefix) => {
@@ -2091,8 +2228,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(_)
@@ -2110,7 +2247,11 @@ impl WindowManager {
initial_state.has_been_modified(self.as_ref()),
)?;
border_manager::send_notification(None);
if force_update_borders {
border_manager::send_force_update();
} else {
border_manager::send_notification(None);
}
transparency_manager::send_notification();
stackbar_manager::send_notification();

View File

@@ -296,8 +296,8 @@ impl WindowManager {
tracing::info!("ignoring uncloak after monocle move by mouse across monitors");
self.uncloack_to_ignore = self.uncloack_to_ignore.saturating_sub(1);
} else {
let mut focused_monitor_idx = self.focused_monitor_idx();
let mut focused_workspace_idx =
let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx =
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
let mut needs_reconciliation = None;
@@ -346,18 +346,6 @@ impl WindowManager {
}
if proceed {
if matches!(event, WindowManagerEvent::Show(_, _)) {
let initial_monitor_idx = initial_state.monitors.focused_idx();
if focused_monitor_idx != initial_monitor_idx {
tracing::info!("assuming focused monitor index should be {initial_monitor_idx} for WindowManagerEvent::Show");
self.focus_monitor(initial_monitor_idx)?;
}
focused_monitor_idx = self.focused_monitor_idx();
focused_workspace_idx =
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
}
let mut behaviour = self.window_management_behaviour(
focused_monitor_idx,
focused_workspace_idx,

View File

@@ -13,14 +13,12 @@ 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;
@@ -40,6 +38,7 @@ use crate::current_virtual_desktop;
use crate::monitor;
use crate::monitor::Monitor;
use crate::monitor_reconciliator;
use crate::resolve_option_hashmap_usize_path;
use crate::ring::Ring;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
@@ -62,6 +61,7 @@ use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::FloatingLayerBehaviour;
use crate::PredefinedAspectRatio;
use crate::ResolvedPathBuf;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
@@ -87,6 +87,7 @@ 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 serde::Deserialize;
@@ -119,8 +120,66 @@ 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(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>,
}
#[serde_with::serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Wallpaper {
/// Path to the wallpaper image file
#[serde_as(as = "ResolvedPathBuf")]
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>,
}
// serde_as must be before derive
#[serde_with::serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WorkspaceConfig {
@@ -131,12 +190,14 @@ pub struct WorkspaceConfig {
pub layout: Option<DefaultLayout>,
/// END OF LIFE FEATURE: Custom Layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<ResolvedPathBuf>")]
pub custom_layout: Option<PathBuf>,
/// Layout rules in the format of threshold => layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
/// END OF LIFE FEATURE: Custom layout rules (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "resolve_option_hashmap_usize_path", default)]
pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
@@ -168,6 +229,9 @@ pub struct WorkspaceConfig {
/// 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 {
@@ -243,7 +307,8 @@ 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()),
floating_layer_behaviour: value.floating_layer_behaviour(),
wallpaper: None,
}
}
}
@@ -268,6 +333,12 @@ pub struct MonitorConfig {
/// Workspace padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
/// Specify a wallpaper for this monitor
#[serde(skip_serializing_if = "Option::is_none")]
pub wallpaper: Option<Wallpaper>,
/// 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>,
}
impl From<&Monitor> for MonitorConfig {
@@ -303,20 +374,24 @@ impl From<&Monitor> for MonitorConfig {
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
container_padding,
workspace_padding,
wallpaper: value.wallpaper().clone(),
floating_layer_behaviour: value.floating_layer_behaviour(),
}
}
}
#[serde_with::serde_as]
#[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),
Single(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
/// Multiple applications.json files
Multiple(Vec<PathBuf>),
Multiple(#[serde_as(as = "Vec<ResolvedPathBuf>")] Vec<PathBuf>),
}
#[serde_with::serde_as]
#[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`
@@ -366,7 +441,7 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_offset")]
pub border_offset: Option<i32>,
/// Display an active window border (default: false)
/// Display an active window border (default: true)
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border")]
pub border: Option<bool>,
@@ -457,6 +532,7 @@ pub struct StaticConfig {
/// Komorebi status bar configuration files for multiple instances on different monitors
// this option is a little special because it is only consumed by komorebic
#[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<Vec<ResolvedPathBuf>>")]
pub bar_configurations: Option<Vec<PathBuf>>,
/// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars
#[serde(skip_serializing_if = "Option::is_none")]
@@ -482,7 +558,7 @@ pub struct AnimationsConfig {
pub fps: Option<u64>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")]
pub enum KomorebiTheme {
@@ -556,6 +632,41 @@ pub enum KomorebiTheme {
#[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>,
/// 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>,
},
}
impl StaticConfig {
@@ -700,6 +811,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),
)),
})
};
@@ -856,10 +970,7 @@ impl StaticConfig {
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst);
border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
if let Some(enabled) = &self.border {
border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst);
}
border_manager::BORDER_ENABLED.store(self.border.unwrap_or(true), Ordering::SeqCst);
if let Some(colours) = &self.border_colours {
if let Some(single) = colours.single {
@@ -881,6 +992,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());
@@ -1021,7 +1137,7 @@ 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 {
@@ -1066,44 +1182,7 @@ impl StaticConfig {
pub fn read(path: &PathBuf) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?;
if let Some(path) = &mut value.app_specific_configuration_path {
match path {
AppSpecificConfigurationPath::Single(path) => {
*path = resolve_home_path(&*path)?;
}
AppSpecificConfigurationPath::Multiple(paths) => {
for path in paths {
*path = resolve_home_path(&*path)?;
}
}
}
}
if let Some(monitors) = &mut value.monitors {
for m in monitors {
for w in &mut m.workspaces {
if let Some(path) = &mut w.custom_layout {
*path = resolve_home_path(&*path)?;
}
if let Some(map) = &mut w.custom_layout_rules {
for path in map.values_mut() {
*path = resolve_home_path(&*path)?;
}
}
}
}
}
if let Some(bar_configurations) = &mut value.bar_configurations {
for path in bar_configurations {
*path = resolve_home_path(&*path)?;
}
}
Ok(value)
serde_json::from_str(&content).map_err(Into::into)
}
#[allow(clippy::too_many_lines)]
@@ -1256,6 +1335,8 @@ impl StaticConfig {
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.set_wallpaper(monitor_config.wallpaper.clone());
monitor.set_floating_layer_behaviour(monitor_config.floating_layer_behaviour);
monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
@@ -1341,6 +1422,7 @@ impl StaticConfig {
);
m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding);
m.set_floating_layer_behaviour(monitor_config.floating_layer_behaviour);
m.update_workspaces_globals(offset);
@@ -1427,6 +1509,8 @@ impl StaticConfig {
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.set_wallpaper(monitor_config.wallpaper.clone());
monitor.set_floating_layer_behaviour(monitor_config.floating_layer_behaviour);
monitor.update_workspaces_globals(offset);
@@ -1513,6 +1597,7 @@ impl StaticConfig {
);
m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding);
m.set_floating_layer_behaviour(monitor_config.floating_layer_behaviour);
m.update_workspaces_globals(offset);
@@ -1577,6 +1662,8 @@ impl StaticConfig {
for i in 0..monitor_count {
wm.update_focused_workspace_by_monitor_idx(i)?;
let ws_idx = wm.focused_workspace_idx_for_monitor_idx(i)?;
wm.apply_wallpaper_for_monitor_workspace(i, ws_idx)?;
}
Ok(())
@@ -1665,7 +1752,6 @@ fn handle_asc_file(
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)?;
@@ -1714,8 +1800,7 @@ fn handle_asc_file(
}
"json" => {
tracing::info!("loading applications.json from: {}", path.display());
let path = resolve_home_path(path)?;
let mut asc = ApplicationSpecificConfiguration::load(&path)?;
let mut asc = ApplicationSpecificConfiguration::load(path)?;
for entry in asc.values_mut() {
match entry {
@@ -1777,7 +1862,10 @@ fn handle_asc_file(
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::StaticConfig;
use crate::WorkspaceConfig;
#[test]
fn backwards_compat() {
@@ -1806,4 +1894,40 @@ mod tests {
StaticConfig::read_raw(&config).unwrap();
}
}
#[test]
fn deserialize_custom_layout_rules() {
// set an environment variable for testing
std::env::set_var("VAR", "VALUE");
let config = r#"
{
"name": "Test",
"custom_layout_rules": {
"1": "path/to/dir",
"2": "path/to/%VAR%"
}
}
"#;
let config = serde_json::from_str::<WorkspaceConfig>(config).unwrap();
let custom_layout_rules = config.custom_layout_rules.unwrap();
assert_eq!(
custom_layout_rules.get(&1).unwrap(),
&PathBuf::from("path/to/dir")
);
assert_eq!(
custom_layout_rules.get(&2).unwrap(),
&PathBuf::from("path/to/VALUE")
);
let config = r#"
{
"name": "Test",
}
"#;
let config = serde_json::from_str::<WorkspaceConfig>(config).unwrap();
assert_eq!(config.custom_layout_rules, None);
}
}

View File

@@ -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;
@@ -157,39 +158,100 @@ 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(*name);
.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,
stack_border,
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,
@@ -233,7 +295,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
CURRENT_THEME.store(Some(notification.0));
border_manager::send_notification(None);
border_manager::send_force_update();
stackbar_manager::send_notification();
}

View File

@@ -248,6 +248,9 @@ impl Default for GlobalState {
unfocused: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst),
))),
unfocused_locked: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
))),
},
border_style: STYLE.load(),
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
@@ -345,6 +348,7 @@ impl From<&WindowManager> for State {
floating_layer_behaviour: workspace.floating_layer_behaviour,
globals: workspace.globals,
locked_containers: workspace.locked_containers.clone(),
wallpaper: workspace.wallpaper.clone(),
workspace_config: None,
})
.collect::<VecDeque<_>>();
@@ -355,6 +359,8 @@ impl From<&WindowManager> for State {
workspace_names: monitor.workspace_names.clone(),
container_padding: monitor.container_padding,
workspace_padding: monitor.workspace_padding,
wallpaper: monitor.wallpaper.clone(),
floating_layer_behaviour: monitor.floating_layer_behaviour,
})
.collect::<VecDeque<_>>();
stripped_monitors.focus(wm.monitors.focused_idx());
@@ -646,14 +652,18 @@ impl WindowManager {
self.window_management_behaviour.float_override
};
let floating_layer_behaviour =
if let Some(behaviour) = workspace.floating_layer_behaviour() {
behaviour
} else {
monitor.floating_layer_behaviour().unwrap_or_default()
};
// If the workspace layer is `Floating` and the floating layer behaviour is `Float`,
// then consider it as if it had float override so that new windows spawn as floating
float_override = float_override
|| (matches!(workspace.layer, WorkspaceLayer::Floating)
&& matches!(
workspace.floating_layer_behaviour,
FloatingLayerBehaviour::Float
));
&& matches!(floating_layer_behaviour, FloatingLayerBehaviour::Float));
return WindowManagementBehaviour {
current_behaviour,
@@ -1010,6 +1020,8 @@ impl WindowManager {
let focused_workspace_idx = monitor.focused_workspace_idx();
monitor.update_workspace_globals(focused_workspace_idx, offset);
let hmonitor = monitor.id();
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?;
@@ -1021,6 +1033,12 @@ impl WindowManager {
}
}
if workspace.wallpaper().is_some() || monitor_wp.is_some() {
if let Err(error) = workspace.apply_wallpaper(hmonitor, &monitor_wp) {
tracing::error!("failed to apply wallpaper: {}", error);
}
}
workspace.update()?;
}
@@ -1704,6 +1722,30 @@ impl WindowManager {
Ok(())
}
/// Check for an existing wallpaper definition on the workspace/monitor index pair and apply it
/// if it exists
#[tracing::instrument(skip(self))]
pub fn apply_wallpaper_for_monitor_workspace(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
) -> Result<()> {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor"))?;
let hmonitor = monitor.id();
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor
.workspaces()
.get(workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace"))?;
workspace.apply_wallpaper(hmonitor, &monitor_wp)
}
pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {
let offset = self.work_area_offset;
@@ -3057,7 +3099,7 @@ impl WindowManager {
}
#[tracing::instrument(skip(self))]
pub fn toggle_float(&mut self) -> Result<()> {
pub fn toggle_float(&mut self, force_float: bool) -> Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let workspace = self.focused_workspace_mut()?;
@@ -3069,7 +3111,7 @@ impl WindowManager {
}
}
if is_floating_window {
if is_floating_window && !force_float {
workspace.set_layer(WorkspaceLayer::Tiling);
self.unfloat_window()?;
} else {
@@ -4980,4 +5022,134 @@ mod tests {
assert!(*workspace.tile());
}
}
#[test]
fn test_toggle_lock() {
let (mut wm, _context) = setup_window_manager();
{
// Add monitor with default workspace to
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let workspace = m.focused_workspace_mut().unwrap();
// Create containers to add to the workspace
for _ in 0..3 {
let container = Container::default();
workspace.add_container_to_back(container);
}
wm.monitors_mut().push_back(m);
}
{
// Ensure container 2 is not locked
let workspace = wm.focused_workspace_mut().unwrap();
assert!(!workspace.locked_containers().contains(&2));
}
// Toggle lock on focused container
wm.toggle_lock().unwrap();
{
// Ensure container 2 is locked
let workspace = wm.focused_workspace_mut().unwrap();
assert!(workspace.locked_containers().contains(&2));
}
// Toggle lock on focused container
wm.toggle_lock().unwrap();
{
// Ensure container 2 is not locked
let workspace = wm.focused_workspace_mut().unwrap();
assert!(!workspace.locked_containers().contains(&2));
}
}
#[test]
fn test_float_window() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Add focused window to floating window list
wm.float_window().ok();
{
let workspace = wm.focused_workspace().unwrap();
let floating_windows = workspace.floating_windows();
let container = workspace.focused_container().unwrap();
// Hwnd 0 should be added to floating_windows
assert_eq!(floating_windows[0].hwnd, 0);
// Should have a length of 1
assert_eq!(floating_windows.len(), 1);
// Should have 2 windows in the container
assert_eq!(container.windows().len(), 2);
// Should be focused on window 1
assert_eq!(container.focused_window(), Some(&Window { hwnd: 1 }));
}
// Add focused window to floating window list
wm.float_window().ok();
{
let workspace = wm.focused_workspace().unwrap();
let floating_windows = workspace.floating_windows();
let container = workspace.focused_container().unwrap();
// Hwnd 1 should be added to floating_windows
assert_eq!(floating_windows[1].hwnd, 1);
// Should have a length of 2
assert_eq!(floating_windows.len(), 2);
// Should have 1 window in the container
assert_eq!(container.windows().len(), 1);
// Should be focused on window 2
assert_eq!(container.focused_window(), Some(&Window { hwnd: 2 }));
}
}
}

View File

@@ -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;
@@ -933,7 +939,7 @@ impl WindowsApi {
pub fn exe(handle: HANDLE) -> Result<String> {
Ok(Self::exe_path(handle)?
.split('\\')
.last()
.next_back()
.ok_or_else(|| anyhow!("there is no last element"))?
.to_string())
}
@@ -1002,6 +1008,16 @@ impl WindowsApi {
Ok(ex_info)
}
pub fn monitor_device_path(hmonitor: isize) -> Option<String> {
for display in win32_display_data::connected_displays_all().flatten() {
if display.hmonitor == hmonitor {
return Some(display.device_path.clone());
}
}
None
}
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
for mut display in win32_display_data::connected_displays_all().flatten() {
if display.hmonitor == hmonitor {
@@ -1345,4 +1361,47 @@ 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, hmonitor: isize) -> 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)?;
}
let monitor_id = if let Some(path) = Self::monitor_device_path(hmonitor) {
PCWSTR::from_raw(HSTRING::from(path).as_ptr())
} else {
PCWSTR::null()
};
// Set the wallpaper
unsafe {
wallpaper.SetWallpaper(monitor_id, PCWSTR::from_raw(wallpaper_path.as_ptr()))?;
}
Ok(())
}
pub fn get_wallpaper(hmonitor: isize) -> Result<String> {
let wallpaper: IDesktopWallpaper =
unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL)? };
let monitor_id = if let Some(path) = Self::monitor_device_path(hmonitor) {
PCWSTR::from_raw(HSTRING::from(path).as_ptr())
} else {
PCWSTR::null()
};
// Set the wallpaper
unsafe {
wallpaper
.GetWallpaper(monitor_id)
.and_then(|pwstr| pwstr.to_string().map_err(|e| e.into()))
}
.process()
}
}

View File

@@ -1,20 +1,14 @@
use std::collections::BTreeSet;
use std::collections::VecDeque;
use std::ffi::OsStr;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io::Write;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use serde::Deserialize;
use serde::Serialize;
use crate::border_manager;
use crate::container::Container;
use crate::core::Axis;
use crate::core::CustomLayout;
use crate::core::CycleDirection;
@@ -22,11 +16,6 @@ use crate::core::DefaultLayout;
use crate::core::Layout;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::FloatingLayerBehaviour;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::container::Container;
use crate::locked_deque::LockedDeque;
use crate::ring::Ring;
use crate::should_act;
@@ -36,13 +25,28 @@ use crate::static_config::WorkspaceConfig;
use crate::window::Window;
use crate::window::WindowDetails;
use crate::windows_api::WindowsApi;
use crate::FloatingLayerBehaviour;
use crate::KomorebiTheme;
use crate::SocketMessage;
use crate::Wallpaper;
use crate::WindowContainerBehaviour;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use komorebi_themes::Base16ColourPalette;
use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixStream;
#[allow(clippy::struct_field_names)]
#[derive(
@@ -88,14 +92,17 @@ pub struct Workspace {
pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub float_override: Option<bool>,
#[serde(skip)]
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub globals: WorkspaceGlobals,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layer: WorkspaceLayer,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub floating_layer_behaviour: FloatingLayerBehaviour,
#[getset(get_copy = "pub", get_mut = "pub", set = "pub")]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub locked_containers: BTreeSet<usize>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub wallpaper: Option<Wallpaper>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
pub workspace_config: Option<WorkspaceConfig>,
@@ -148,6 +155,7 @@ impl Default for Workspace {
globals: Default::default(),
workspace_config: None,
locked_containers: Default::default(),
wallpaper: None,
}
}
}
@@ -178,10 +186,13 @@ pub enum WorkspaceWindowLocation {
pub struct WorkspaceGlobals {
pub container_padding: Option<i32>,
pub workspace_padding: Option<i32>,
pub border_width: i32,
pub border_offset: i32,
pub work_area: Rect,
pub work_area_offset: Option<Rect>,
pub window_based_work_area_offset: Option<Rect>,
pub window_based_work_area_offset_limit: isize,
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
}
impl Workspace {
@@ -254,7 +265,8 @@ impl Workspace {
self.set_float_override(config.float_override);
self.set_layout_flip(config.layout_flip);
self.set_floating_layer_behaviour(config.floating_layer_behaviour.unwrap_or_default());
self.set_floating_layer_behaviour(config.floating_layer_behaviour);
self.set_wallpaper(config.wallpaper.clone());
self.set_workspace_config(Some(config.clone()));
@@ -291,12 +303,133 @@ impl Workspace {
}
}
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
pub fn apply_wallpaper(&self, hmonitor: isize, monitor_wp: &Option<Wallpaper>) -> Result<()> {
if let Some(wallpaper) = self.wallpaper.as_ref().or(monitor_wp.as_ref()) {
if let Err(error) = WindowsApi::set_wallpaper(&wallpaper.path, hmonitor) {
tracing::error!("failed to set wallpaper: {error}");
}
if wallpaper.generate_theme.unwrap_or(true) {
let variant = wallpaper
.theme_options
.as_ref()
.and_then(|t| t.theme_variant)
.unwrap_or_default();
let cached_palette = DATA_DIR.join(format!(
"{}.base16.{variant}.json",
wallpaper
.path
.file_name()
.unwrap_or(OsStr::new("tmp"))
.to_string_lossy()
));
let mut base16_palette = None;
if cached_palette.is_file() {
tracing::info!(
"colour palette for wallpaper {} found in cache",
cached_palette.display()
);
// this code is VERY slow on debug builds - should only be a one-time issue when loading
// an uncached wallpaper
if let Ok(palette) = serde_json::from_str::<Base16ColourPalette>(
&std::fs::read_to_string(&cached_palette)?,
) {
base16_palette = Some(palette);
}
};
if base16_palette.is_none() {
base16_palette =
komorebi_themes::generate_base16_palette(&wallpaper.path, variant).ok();
std::fs::write(
&cached_palette,
serde_json::to_string_pretty(&base16_palette)?,
)?;
tracing::info!(
"colour palette for wallpaper {} cached",
cached_palette.display()
);
}
if let Some(palette) = base16_palette {
let komorebi_theme = KomorebiTheme::Custom {
colours: Box::new(palette),
single_border: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.single_border),
stack_border: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.stack_border),
monocle_border: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.monocle_border),
floating_border: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.floating_border),
unfocused_border: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.unfocused_border),
unfocused_locked_border: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.unfocused_locked_border),
stackbar_focused_text: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.stackbar_focused_text),
stackbar_unfocused_text: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.stackbar_unfocused_text),
stackbar_background: wallpaper
.theme_options
.as_ref()
.and_then(|o| o.stackbar_background),
bar_accent: wallpaper.theme_options.as_ref().and_then(|o| o.bar_accent),
};
let bytes = SocketMessage::Theme(Box::new(komorebi_theme)).as_bytes()?;
let socket = DATA_DIR.join("komorebi.sock");
match UnixStream::connect(socket) {
Ok(mut stream) => {
if let Err(error) = stream.write_all(&bytes) {
tracing::error!("failed to send theme update message: {error}")
}
}
Err(error) => {
tracing::error!("{error}")
}
}
}
}
}
Ok(())
}
pub fn restore(
&mut self,
mouse_follows_focus: bool,
hmonitor: isize,
monitor_wp: &Option<Wallpaper>,
) -> Result<()> {
if let Some(container) = self.monocle_container() {
if let Some(window) = container.focused_window() {
container.restore();
window.focus(mouse_follows_focus)?;
return Ok(());
return self.apply_wallpaper(hmonitor, monitor_wp);
}
}
@@ -340,7 +473,7 @@ impl Workspace {
floating_window.focus(mouse_follows_focus)?;
}
Ok(())
self.apply_wallpaper(hmonitor, monitor_wp)
}
pub fn update(&mut self) -> Result<()> {
@@ -348,6 +481,9 @@ impl Workspace {
return Ok(());
}
// make sure we are never holding on to empty containers
self.containers_mut().retain(|c| !c.windows().is_empty());
let container_padding = self
.container_padding()
.or(self.globals().container_padding)
@@ -356,6 +492,8 @@ impl Workspace {
.workspace_padding()
.or(self.globals().workspace_padding)
.unwrap_or_default();
let border_width = self.globals().border_width;
let border_offset = self.globals().border_offset;
let work_area = self.globals().work_area;
let work_area_offset = self.globals().work_area_offset;
let window_based_work_area_offset = self.globals().window_based_work_area_offset;
@@ -428,12 +566,8 @@ impl Workspace {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding);
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
adjusted_work_area.add_padding(border_offset);
let width = BORDER_WIDTH.load(Ordering::SeqCst);
adjusted_work_area.add_padding(width);
}
adjusted_work_area.add_padding(border_offset);
adjusted_work_area.add_padding(border_width);
window.set_position(&adjusted_work_area, true)?;
};
} else if let Some(window) = self.maximized_window_mut() {
@@ -461,13 +595,8 @@ impl Workspace {
let window_count = container.windows().len();
if let Some(layout) = layouts.get_mut(i) {
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
layout.add_padding(border_offset);
let width = BORDER_WIDTH.load(Ordering::SeqCst);
layout.add_padding(width);
}
layout.add_padding(border_offset);
layout.add_padding(border_width);
if stackbar_manager::should_have_stackbar(window_count) {
let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
@@ -559,13 +688,13 @@ impl Workspace {
for window in self.visible_windows().into_iter().flatten() {
if !window.is_window()
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
// docs is closed
//
// I hate every single person who worked on Microsoft Office 365, especially Word
|| !window.is_visible()
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
// docs is closed
//
// I hate every single person who worked on Microsoft Office 365, especially Word
|| !window.is_visible()
{
hwnds.push(window.hwnd);
}
@@ -1647,7 +1776,6 @@ impl Workspace {
#[cfg(test)]
mod tests {
use super::*;
use crate::container::Container;
use crate::Window;
use std::collections::BTreeSet;
@@ -2360,4 +2488,53 @@ mod tests {
let floating_windows = workspace.floating_windows_mut();
assert!(floating_windows.contains(&Window { hwnd: 0 }));
}
#[test]
fn test_visible_windows() {
let mut workspace = Workspace::default();
{
// Create and add a default Container with 2 windows
let mut container = Container::default();
container.windows_mut().push_back(Window::from(100));
container.windows_mut().push_back(Window::from(200));
workspace.add_container_to_back(container);
}
{
// visible_windows should return None and 100
let visible_windows = workspace.visible_windows();
assert_eq!(visible_windows.len(), 2);
assert!(visible_windows[0].is_none());
assert_eq!(visible_windows[1].unwrap().hwnd, 100);
}
{
// Create and add a default Container with 1 window
let mut container = Container::default();
container.windows_mut().push_back(Window::from(300));
workspace.add_container_to_back(container);
}
{
// visible_windows should return None, 100, and 300
let visible_windows = workspace.visible_windows();
assert_eq!(visible_windows.len(), 3);
assert!(visible_windows[0].is_none());
assert_eq!(visible_windows[1].unwrap().hwnd, 100);
assert_eq!(visible_windows[2].unwrap().hwnd, 300);
}
// Maximize window 200
workspace.set_maximized_window(Some(Window { hwnd: 200 }));
{
// visible_windows should return 200, 100, and 300
let visible_windows = workspace.visible_windows();
assert_eq!(visible_windows.len(), 3);
assert_eq!(visible_windows[0].unwrap().hwnd, 200);
assert_eq!(visible_windows[1].unwrap().hwnd, 100);
assert_eq!(visible_windows[2].unwrap().hwnd, 300);
}
}
}

View File

@@ -2,12 +2,13 @@
#![allow(clippy::missing_errors_doc, clippy::doc_markdown)]
use chrono::Utc;
use komorebi_client::replace_env_in_path;
use komorebi_client::PathExt;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
@@ -22,7 +23,6 @@ use color_eyre::eyre::bail;
use color_eyre::Result;
use dirs::data_local_dir;
use fs_tail::TailedFile;
use komorebi_client::resolve_home_path;
use komorebi_client::send_message;
use komorebi_client::send_query;
use komorebi_client::AppSpecificConfigurationPath;
@@ -64,8 +64,7 @@ lazy_static! {
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|_| dirs::home_dir().expect("there is no home directory"),
|home_path| {
let home = PathBuf::from(&home_path);
let home = home_path.replace_env();
if home.as_path().is_dir() {
HAS_CUSTOM_CONFIG_HOME.store(true, Ordering::SeqCst);
home
@@ -88,12 +87,12 @@ lazy_static! {
.join(".config")
},
|home_path| {
let whkd_config_home = PathBuf::from(&home_path);
let whkd_config_home = home_path.replace_env();
assert!(
whkd_config_home.as_path().is_dir(),
whkd_config_home.is_dir(),
"$Env:WHKD_CONFIG_HOME is set to '{}', which is not a valid directory",
whkd_config_home.to_string_lossy()
home_path
);
whkd_config_home
@@ -299,6 +298,7 @@ pub struct WorkspaceCustomLayout {
workspace: usize,
/// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -308,6 +308,7 @@ pub struct NamedWorkspaceCustomLayout {
workspace: String,
/// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -350,6 +351,7 @@ pub struct WorkspaceCustomLayoutRule {
at_container_count: usize,
/// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -362,6 +364,7 @@ pub struct NamedWorkspaceCustomLayoutRule {
at_container_count: usize,
/// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -770,6 +773,7 @@ struct Start {
ffm: bool,
/// Path to a static configuration JSON file
#[clap(short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>,
/// Wait for 'komorebic complete-configuration' to be sent before processing events
#[clap(short, long)]
@@ -832,18 +836,21 @@ struct Kill {
#[derive(Parser)]
struct SaveResize {
/// File to which the resize layout dimensions should be saved
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
#[derive(Parser)]
struct LoadResize {
/// File from which the resize layout dimensions should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
#[derive(Parser)]
struct LoadCustomLayout {
/// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -874,28 +881,34 @@ struct UnsubscribePipe {
#[derive(Parser)]
struct AhkAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
/// Optional YAML file of overrides to apply over the first file
#[clap(value_parser = replace_env_in_path)]
override_path: Option<PathBuf>,
}
#[derive(Parser)]
struct PwshAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
/// Optional YAML file of overrides to apply over the first file
#[clap(value_parser = replace_env_in_path)]
override_path: Option<PathBuf>,
}
#[derive(Parser)]
struct FormatAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
#[derive(Parser)]
struct ConvertAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -909,6 +922,7 @@ struct AltFocusHack {
struct EnableAutostart {
/// Path to a static configuration JSON file
#[clap(action, short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>,
/// Enable komorebi's custom focus-follows-mouse implementation
#[clap(hide = true)]
@@ -932,12 +946,14 @@ struct EnableAutostart {
struct Check {
/// Path to a static configuration JSON file
#[clap(action, short, long)]
#[clap(value_parser = replace_env_in_path)]
komorebi_config: Option<PathBuf>,
}
#[derive(Parser)]
struct ReplaceConfiguration {
/// Static configuration JSON file from which the configuration should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf,
}
@@ -978,6 +994,9 @@ enum SubCommand {
/// Show the path to whkdrc
#[clap(alias = "whkd")]
Whkdrc,
/// Show the path to komorebi's data directory in %LOCALAPPDATA%
#[clap(alias = "datadir")]
DataDirectory,
/// Show a JSON representation of the current window manager state
State,
/// Show a JSON representation of the current global state
@@ -1320,6 +1339,12 @@ enum SubCommand {
/// Set the operation behaviour when the focused window is not managed
#[clap(arg_required_else_help = true)]
UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour),
/// Add a rule to float the foreground window for the rest of this session
SessionFloatRule,
/// Show all session float rules
SessionFloatRules,
/// Clear all session float rules
ClearSessionFloatRules,
/// Add a rule to ignore the specified application
#[clap(arg_required_else_help = true)]
#[clap(alias = "float-rule")]
@@ -1668,7 +1693,7 @@ fn main() -> Result<()> {
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
}
Some(AppSpecificConfigurationPath::Single(path)) => {
if !Path::exists(Path::new(&path)) {
if !path.exists() {
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
}
}
@@ -1681,8 +1706,7 @@ fn main() -> Result<()> {
// errors
let _ = serde_json::from_str::<StaticConfig>(&config_source)?;
let path = resolve_home_path(static_config)?;
let raw = std::fs::read_to_string(path)?;
let raw = std::fs::read_to_string(static_config)?;
StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw);
StaticConfig::end_of_life(&raw);
@@ -1754,6 +1778,12 @@ fn main() -> Result<()> {
println!("{}", whkdrc.display());
}
}
SubCommand::DataDirectory => {
let dir = &*DATA_DIR;
if dir.exists() {
println!("{}", dir.display());
}
}
SubCommand::Log => {
let timestamp = Utc::now().format("%Y-%m-%d").to_string();
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
@@ -1979,13 +2009,13 @@ fn main() -> Result<()> {
send_message(&SocketMessage::WorkspaceLayoutCustom(
arg.monitor,
arg.workspace,
resolve_home_path(arg.path)?,
arg.path,
))?;
}
SubCommand::NamedWorkspaceCustomLayout(arg) => {
send_message(&SocketMessage::NamedWorkspaceLayoutCustom(
arg.workspace,
resolve_home_path(arg.path)?,
arg.path,
))?;
}
SubCommand::WorkspaceLayoutRule(arg) => {
@@ -2008,14 +2038,14 @@ fn main() -> Result<()> {
arg.monitor,
arg.workspace,
arg.at_container_count,
resolve_home_path(arg.path)?,
arg.path,
))?;
}
SubCommand::NamedWorkspaceCustomLayoutRule(arg) => {
send_message(&SocketMessage::NamedWorkspaceLayoutCustomRule(
arg.workspace,
arg.at_container_count,
resolve_home_path(arg.path)?,
arg.path,
))?;
}
SubCommand::ClearWorkspaceLayoutRules(arg) => {
@@ -2088,13 +2118,12 @@ fn main() -> Result<()> {
let mut flags = vec![];
if let Some(config) = &arg.config {
let path = resolve_home_path(config)?;
if !path.is_file() {
bail!("could not find file: {}", path.display());
if !config.is_file() {
bail!("could not find file: {}", config.display());
}
// we don't need to replace UNC prefix here as `resolve_home_path` already did
flags.push(format!("'--config=\"{}\"'", path.display()));
let config = dunce::simplified(config);
flags.push(format!("'--config=\"{}\"'", config.display()));
}
if arg.ffm {
@@ -2113,17 +2142,12 @@ fn main() -> Result<()> {
flags.push("'--clean-state'".to_string());
}
let exec = exec.unwrap_or("komorebi.exe");
let script = if flags.is_empty() {
format!(
"Start-Process '{}' -WindowStyle hidden",
exec.unwrap_or("komorebi.exe")
)
format!("Start-Process '{exec}' -WindowStyle hidden",)
} else {
let argument_list = flags.join(",");
format!(
"Start-Process '{}' -ArgumentList {argument_list} -WindowStyle hidden",
exec.unwrap_or("komorebi.exe")
)
format!("Start-Process '{exec}' -ArgumentList {argument_list} -WindowStyle hidden",)
};
let mut system = sysinfo::System::new_all();
@@ -2166,9 +2190,8 @@ fn main() -> Result<()> {
if !running {
println!("\nRunning komorebi.exe directly for detailed error output\n");
if let Some(config) = arg.config {
let path = resolve_home_path(config)?;
if let Ok(output) = Command::new("komorebi.exe")
.arg(format!("'--config=\"{}\"'", path.display()))
.arg(format!("'--config=\"{}\"'", config.display()))
.output()
{
println!("{}", String::from_utf8(output.stderr)?);
@@ -2218,25 +2241,20 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
}
}
let static_config = arg.config.clone().map_or_else(
|| {
let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() {
Option::from(komorebi_json)
} else {
None
}
},
Option::from,
);
let static_config = arg.config.clone().or_else(|| {
let komorebi_json = HOME_DIR.join("komorebi.json");
komorebi_json.is_file().then_some(komorebi_json)
});
if arg.bar {
if let Some(config) = &static_config {
let mut config = StaticConfig::read(config)?;
if let Some(display_bar_configurations) = &mut config.bar_configurations {
for config_file_path in &mut *display_bar_configurations {
let script = r#"Start-Process "komorebi-bar" '"--config" "CONFIGFILE"' -WindowStyle hidden"#
.replace("CONFIGFILE", &config_file_path.to_string_lossy());
let script = format!(
r#"Start-Process "komorebi-bar" '"--config" "{}"' -WindowStyle hidden"#,
config_file_path.to_string_lossy()
);
match powershell_script::run(&script) {
Ok(_) => {
@@ -2298,21 +2316,13 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
println!("\n# Documentation");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
let bar_config = arg.config.map_or_else(
|| {
let bar_json = HOME_DIR.join("komorebi.bar.json");
if bar_json.is_file() {
Option::from(bar_json)
} else {
None
}
},
Option::from,
);
let bar_config = arg.config.or_else(|| {
let bar_json = HOME_DIR.join("komorebi.bar.json");
bar_json.is_file().then_some(bar_json)
});
if let Some(config) = &static_config {
let path = resolve_home_path(config)?;
let raw = std::fs::read_to_string(path)?;
let raw = std::fs::read_to_string(config)?;
StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw);
StaticConfig::end_of_life(&raw);
@@ -2526,6 +2536,15 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
}
}
}
SubCommand::SessionFloatRule => {
send_message(&SocketMessage::SessionFloatRule)?;
}
SubCommand::SessionFloatRules => {
print_query(&SocketMessage::SessionFloatRules);
}
SubCommand::ClearSessionFloatRules => {
send_message(&SocketMessage::ClearSessionFloatRules)?;
}
SubCommand::IgnoreRule(arg) => {
send_message(&SocketMessage::IgnoreRule(arg.identifier, arg.id))?;
}
@@ -2605,9 +2624,7 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
send_message(&SocketMessage::CycleLayout(arg.cycle_direction))?;
}
SubCommand::LoadCustomLayout(arg) => {
send_message(&SocketMessage::ChangeLayoutCustom(resolve_home_path(
arg.path,
)?))?;
send_message(&SocketMessage::ChangeLayoutCustom(arg.path))?;
}
SubCommand::FlipLayout(arg) => {
send_message(&SocketMessage::FlipLayout(arg.axis))?;
@@ -2784,10 +2801,10 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
send_message(&SocketMessage::QuickLoad)?;
}
SubCommand::SaveResize(arg) => {
send_message(&SocketMessage::Save(resolve_home_path(arg.path)?))?;
send_message(&SocketMessage::Save(arg.path))?;
}
SubCommand::LoadResize(arg) => {
send_message(&SocketMessage::Load(resolve_home_path(arg.path)?))?;
send_message(&SocketMessage::Load(arg.path))?;
}
SubCommand::SubscribeSocket(arg) => {
send_message(&SocketMessage::AddSubscriberSocket(arg.socket))?;
@@ -2899,9 +2916,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
))?;
}
SubCommand::AhkAppSpecificConfiguration(arg) => {
let content = std::fs::read_to_string(resolve_home_path(arg.path)?)?;
let content = std::fs::read_to_string(arg.path)?;
let lines = if let Some(override_path) = arg.override_path {
let override_content = std::fs::read_to_string(resolve_home_path(override_path)?)?;
let override_content = std::fs::read_to_string(override_path)?;
ApplicationConfigurationGenerator::generate_ahk(
&content,
@@ -2926,9 +2943,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
);
}
SubCommand::PwshAppSpecificConfiguration(arg) => {
let content = std::fs::read_to_string(resolve_home_path(arg.path)?)?;
let content = std::fs::read_to_string(arg.path)?;
let lines = if let Some(override_path) = arg.override_path {
let override_content = std::fs::read_to_string(resolve_home_path(override_path)?)?;
let override_content = std::fs::read_to_string(override_path)?;
ApplicationConfigurationGenerator::generate_pwsh(
&content,
@@ -2953,23 +2970,21 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
);
}
SubCommand::ConvertAppSpecificConfiguration(arg) => {
let file_path = resolve_home_path(arg.path)?;
let content = std::fs::read_to_string(&file_path)?;
let content = std::fs::read_to_string(arg.path)?;
let mut asc = ApplicationConfigurationGenerator::load(&content)?;
asc.sort_by(|a, b| a.name.cmp(&b.name));
let v2 = ApplicationSpecificConfiguration::from(asc);
println!("{}", serde_json::to_string_pretty(&v2)?);
}
SubCommand::FormatAppSpecificConfiguration(arg) => {
let file_path = resolve_home_path(arg.path)?;
let content = std::fs::read_to_string(&file_path)?;
let content = std::fs::read_to_string(&arg.path)?;
let formatted_content = ApplicationConfigurationGenerator::format(&content)?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(file_path)?;
.open(arg.path)?;
file.write_all(formatted_content.as_bytes())?;

View File

@@ -49,37 +49,40 @@ plugins:
nav:
- Komorebi:
- index.md
- Design: design.md
- Getting started:
- 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
- common-workflows/animations.md
- common-workflows/autohotkey.md
- common-workflows/borders.md
- common-workflows/stackbar.md
- common-workflows/remove-gaps.md
- common-workflows/ignore-windows.md
- common-workflows/force-manage-windows.md
- common-workflows/floating-applications.md
- common-workflows/tray-and-multi-window-applications.md
- common-workflows/mouse-follows-focus.md
- 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
- About:
- index.md
- design.md
- Getting started:
- 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
- Komorebi Configuration:
- Schema: https://komorebi.lgug2z.com/schema
- Komorebi Bar Configuration:
- Schema: https://komorebi-bar.lgug2z.com/schema
- Common workflows:
- common-workflows/komorebi-config-home.md
- common-workflows/autostart.md
- common-workflows/animations.md
- common-workflows/autohotkey.md
- common-workflows/borders.md
- common-workflows/stackbar.md
- common-workflows/remove-gaps.md
- common-workflows/ignore-windows.md
- common-workflows/force-manage-windows.md
- common-workflows/floating-applications.md
- common-workflows/tray-and-multi-window-applications.md
- common-workflows/mouse-follows-focus.md
- common-workflows/dynamic-layout-switching.md
- common-workflows/set-display-index.md
- common-workflows/multiple-bar-instances.md
- common-workflows/multi-monitor-setup.md
- CLI reference:
- cli/quickstart.md
- cli/start.md
@@ -89,6 +92,7 @@ nav:
- cli/configuration.md
- cli/bar-configuration.md
- cli/whkdrc.md
- cli/data-directory.md
- cli/state.md
- cli/global-state.md
- cli/gui.md
@@ -205,6 +209,9 @@ nav:
- cli/cross-monitor-move-behaviour.md
- cli/toggle-cross-monitor-move-behaviour.md
- cli/unmanaged-window-operation-behaviour.md
- cli/session-float-rule.md
- cli/session-float-rules.md
- cli/clear-session-float-rules.md
- cli/ignore-rule.md
- cli/manage-rule.md
- cli/initial-workspace-rule.md

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff