Compare commits

...

67 Commits

Author SHA1 Message Date
LGUG2Z 3281c61113 wip 2025-05-07 09:33:20 -07:00
LGUG2Z 70f561e6ac feat(shortcuts): add helper written in egui
This commit adds a simple egui helper application which shows a list of
shortcuts defined in a user's whkdrc file. Parsing AHK files is not
supported.

In addition to listing out shortcuts defined in the whkdrc file, the top
line allows users to add filter a filter to narrow down the list of
commands and key bindings to the ones they are interested in.

A new komorebic command "toggle-shortcuts" has been introduced which
will first attempt to kill "komorebi-shortcuts.exe", and then exit if
the kill signal was successful (ie. a process was closed), or proceed to
open "komorebi-shortcuts.exe" if the kill signal was not successful (ie.
no process was closed, so we should open one).

"komorebi-shortcuts.exe" has been added as a floating application in
lib.rs to allow for users to use the "komorebic move" command to
manipulate its position via their existing keyboard bindings.
2025-05-07 08:52:08 -07:00
LGUG2Z 4ea835fa59 feat(wm): toggle monocle off on monitor container moves
This commit ensures that if a user sends one of the various messages
which ultimately call move_container_to_monitor, if the target workspace
on the target monitor currently has a monocle container, it will be
toggled off to allow space for the container being moved to be rendered.
2025-05-07 08:19:22 -07:00
LGUG2Z 09137af305 feat(wm): toggle monocle off on ws container moves
This commit ensures that if a user sends one of the various messages
which ultimately call move_container_to_workspace, if the target
workspace currently has a monocle container, it will be toggled off to
allow space for the container being moved to be rendered.
2025-05-06 17:03:57 -07:00
LGUG2Z ee89b344df chore(deps): update dependencies.json 2025-05-04 11:38:03 -07:00
LGUG2Z 46d5ea4a1d refactor(wm): log errors when allow_set_foreground_window fails
This startup Win32 API call can sporadically fail, so this commit adds
some retry logic and logging of errors every time it fails. If it
crosses the retry threshold, the application will exit (because you
can't really have a tiling window manager running that doesn't let you
set the foreground window).
2025-05-02 17:49:29 -07:00
LGUG2Z 82c2241601 fix(wm): don't reap ws windows when using hide instead of cloak
This commit ensures that workspace windows that are hidden when the user
is using HidingBehaviour::Hide will not be unintentionally reaped by a
hard-coded workaround for Microsoft Office's continuing
enshittification.

HidingBehaviour::Hide has also been marked in the docs as an EOL
feature, and some dead code that was ultimately migrated to reaper.rs
has been cleaned up.

fix #1426
2025-05-02 17:29:41 -07:00
dependabot[bot] c28773b96a chore(deps): bump miette from 7.5.0 to 7.6.0
Bumps [miette](https://github.com/zkat/miette) from 7.5.0 to 7.6.0.
- [Release notes](https://github.com/zkat/miette/releases)
- [Changelog](https://github.com/zkat/miette/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zkat/miette/commits/miette-derive-v7.6.0)

---
updated-dependencies:
- dependency-name: miette
  dependency-version: 7.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 08:27:05 -07:00
LGUG2Z 577364a556 feat(bar): hide when leaving komorebi's virtual desktop
This commit adds a new VirtualDesktopNotification which is used to
notify subscribers when the user leaves and enters the virtual desktop
associated with komorebi.

komorebi-bar consumes these notifications to minmize and restore the bar
appropriately depending on the currently focused virtual desktop.

re #1420
2025-04-28 14:33:39 -07:00
LGUG2Z 17cd0308cb feat(bar): improve path handling on apps widget
This commit improves path handling for commands and icons in the new
Application widget by making use of PathExt::replace_env when loading
the user-specified ApplicationsConfig.

Crucially for scoop users, this means that user-agnostic references to
scoop apps can now be made like this:

```
$Env:USERPROFILE/scoop/apps/zed-nightly/current/zed.exe
```

When attempting to look up an icon for a command, we now split the
command on ".exe", and if this is a complete path to a file, we try to
use it to extract an icon, otherwise we try to resolve a complete path
using "which" before doing the same.
2025-04-27 11:47:19 -07:00
Alisher Galiev 10424b696f feat(bar): add applications widget
This pull request introduces a new Applications widget that displays a
user-defined list of application launchers in the UI. Each app entry
supports an icon, a label, and executes its configured command on click.

The design of this widget is inspired by the Applications Widget of YASB
Reborn. I personally missed this functionality and aimed to bring a
similar experience to komorebi-bar.

Further information is in the text of PR #1415
2025-04-27 11:45:06 -07:00
LGUG2Z 6e7d8fb922 fix(animation): avoid redundant async window pos calls
As pointed out by @alex-ds13, animations run on a separate thread and so
unresponsive apps will not block komorebi.
2025-04-25 17:53:57 -07:00
LGUG2Z 917cd9b7db fix(borders): destroy all if a different vd is detected
This commit ensures that all borders will destroyed if komorebi detects
that the user has switched to a Windows Virtual Desktop different from
the one it was launched on and associated with.

There is still some work to be done to forcibly redraw the borders when
a navigation back to the VD associated with komorebi is detected, but
that requires tracking the state of the current VD ID outside of the
process_event handler somewhere.

re #1420
2025-04-24 21:41:21 -07:00
LGUG2Z bdbd665b21 refactor(wm): add window handling sync/async enum
This commit adds a dedicated WindowHandlingBehaviour enum with Sync and
Async variants, and reverts a change to the render fn in window.rs to
use move_window instead of position_window if the
WindowHandlingBehaviour variant is Sync.
2025-04-24 18:50:54 -07:00
Kuukunen f3f2098451 feat(wm): add configuration option for async window handling
Add configuration option for enabling/disabling the asynchronous window
handling. The feature might cause unforeseen issues, so by default it is
off. It can be enabled with setting the option async_window_handling to
true.
2025-04-24 18:50:21 -07:00
Kuukunen 4ca2e8388b fix(wm): fix unresponsiveness by using asynchronous window handling
Unresponsive windows might cause Komorebi to hang because functions like
SetWindowPos wait for the target window's WindowProc.

I changed most SetWindowPos calls to use the SWP_ASYNCWINDOWPOS flag,
which should avoid hanging, and I changed the one
WindowsApi::move_window call to use position_window instead. I also
changed the ShowWindow to ShowWindowAsync.

The only issue I noticed was that it caused the stackbar to disappear
(and/or flicker), probably because the window is modified right after
moving it, so I disabled the async position_window for that.
2025-04-24 18:50:21 -07:00
thearturca 31752e422a feat(animation): cubic-bezier for styles
This commit adds ability to use cubic-bezier as an animation style,
which allows users to customize the smoothness of animations.
2025-04-24 17:42:04 -07:00
Jerry Kingsbury 5e308b9131 test(wm): add window handle to move based on workspace rules test
Created a simple test for the
add_window_handle_to_move_based_on_workspace_rules function.

The test creates mock data representing window and the movement details.

The test will call the function with an empty vector to hold the
workspace rules and then check that the workspace rules are in the
vector.
2025-04-22 18:03:42 -07:00
Jerry Kingsbury 1bf53b89af test(wm): ensure named workspace for monitor test
Created a test for the ensure_named_workspace_for_monitor function.

The test creates two monitors and holds a list of workspace names.

When calling the ensure_named_workspace_for_monitor_function the test
checks to ensure that the the the monitor contains the length of the
list workspaces and that the workspaces uses the name in the list.

The test adds more names to the list and repeats the check on the other
monitor.
2025-04-22 18:03:13 -07:00
Jerry Kingsbury 11690c6004 test(wm): test toggle_monocle and toggle_maximize
Created tests for the toggle_monocle and toggle_maximize functions. The
test are simialar to the maximize and unmaximize test and the monocle on
and off test.
2025-04-22 18:02:59 -07:00
Jerry Kingsbury 3457dfc04c test(wm): monocle on and off test
Created a test for the monocle_on and monocle_off functions.

The test checks to ensure that the focused workspace container becomes a
monocle container when calling the monocle_on function.

The test will also ensure that the container is moved to the workspace's
container ring when the monocle_off function is called.
2025-04-22 18:02:23 -07:00
Jerry Kingsbury af1c9b5aa9 test(wm): test maximize and unmaximize window
Created a test for maximizing and unmaximizing a window.

The test ensures that the when calling maximize_window, the focused
window is added to the maximized_window list.

When calling unmaximized window, the checks to ensure that None is
returned to ensure that the maximized window is removed from the list.

The test switches to a different window and performs the same checks.
2025-04-22 18:01:55 -07:00
dependabot[bot] 22fac5a9fb chore(deps): bump netdev from 0.33.0 to 0.34.0
Bumps [netdev](https://github.com/shellrow/netdev) from 0.33.0 to 0.34.0.
- [Release notes](https://github.com/shellrow/netdev/releases)
- [Commits](https://github.com/shellrow/netdev/compare/v0.33.0...v0.34.0)

---
updated-dependencies:
- dependency-name: netdev
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 17:16:48 -07:00
LGUG2Z 7a3990f106 chore(deps): cargo update 2025-04-20 10:39:28 -07:00
alex-ds13 74e93e5524 fix(wm): ensure default CrossBoundaryBehaviour consistency
This commit fixes the default for `CrossBoundaryBehaviour` which was set
to `Monitor` when loading a user configuration but was set to
`Workspace` if starting from fresh without a user config.

The docs also stated that this value was `Monitor` by default.
2025-04-20 10:26:01 -07:00
alex-ds13 86e78570d6 fix(wm): reset global configs to default on removal 2025-04-20 10:26:01 -07:00
alex-ds13 3ee3aac806 feat(wm): float placement configs
This commit adds a few more options to combine with the
`FloatingLayerBehaviour` which determine placement in different
situations:

- `"toggle_float_placement"`: the placement to be used by a floating
  window when it is forced to float with the `toggle-float` command.
- `"floating_layer_placement"`: the placement to be used by a floating
  window when it is spawned on the floating layer and the user has the
  floating layer behaviour set to float.
- `"floating_override_placement"`: the placement to be used by a window
  that is spawned when float override is active.
- `"float_rule_placement"`: the placement to be used by a window that
  matches a 'floating_applications' rule.

Each `Placement` can be one of the following types:

- "None": windows are spawned wherever Windows positions them with
  whatever size they had. Komorebi does not change its size or position.
- "Center": windows are centered without changing their size.
- "CenterAndResize": windows are centered and resized according to the
defined aspect ratio.

By default the placements are as follows:
- `"toggle_float_placement"`: `"CenterAndResize"`
- `"floating_layer_placement"`: `"Center"`
- `"floating_override_placement"`: `"None"`
- `"float_rule_placement"`: `"None"`

This commit also adds the `floating_layer_behaviour` as a global config.
2025-04-20 10:25:57 -07:00
alex-ds13 c408c1149c fix(wm): fix eager focus on hidden stacked window
When using the eager focus command to focus a window that was hidden on
a stack on an unfocused workspace it would load the workspace focus the
correct window but keep it cloaked with the previously focused window
showing. This would happen because we were focusing the workspace before
focusing the window which would create two window focus events, the
first one for the previously focused window and the second one for the
target window we wanted. The problem was that when the first event was
handled it would again refocus that window and cloak the target window
again so when the target window focus event came it would ignore it
since the `should_manage` function would return `false` because the
window was cloaked and had all its window styles as `None`, unless we
had transparency enabled, in that case it would override it and create a
focus loop.

This commit fixes this by not loading the newly focused workspace
immediately. Instead we first focus the workspace if it is needed
without loading it and mark it as needing to be loaded. Then we focus
the correct target window and only then do we load the workspace. This
way the workspace loads with the correct window already focused.

This is the same approach that is currently being done on the
`perform_reconciliation` function when there is an alt-tab.
2025-04-17 07:57:45 -07:00
LGUG2Z a8b02f40fd chore(dev): begin 0.1.37-dev 2025-04-13 09:38:12 -07:00
LGUG2Z 6608e5a5bb docs(schema): update schema.json 2025-04-13 09:37:13 -07:00
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: https://github.com/LGUG2Z/komorebi/commit/36e3eaad36336101321ecafbffdf089795028c1e

blame: https://github.com/LGUG2Z/komorebi/commit/bb31e7155d9eed68b03b54c83e6c4e8fb1e6d84c

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
72 changed files with 8899 additions and 1595 deletions
Generated
+991 -255
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -8,7 +8,8 @@ members = [
"komorebic", "komorebic",
"komorebic-no-console", "komorebic-no-console",
"komorebi-bar", "komorebi-bar",
"komorebi-themes" "komorebi-themes",
"komorebi-shortcuts"
] ]
[workspace.dependencies] [workspace.dependencies]
@@ -33,7 +34,7 @@ tracing = "0.1"
tracing-appender = "0.2" tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1" paste = "1"
sysinfo = "0.33" sysinfo = "0.34"
uds_windows = "1" uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "a28c6559a9de2f92c142a714947a9b081776caca" } win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "a28c6559a9de2f92c142a714947a9b081776caca" }
windows-numerics = { version = "0.2" } windows-numerics = { version = "0.2" }
+1 -1
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. Below is a simple example of how to use `komorebi-client` in a basic Rust application.
```rust ```rust
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.35"} // komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.36"}
use anyhow::Result; use anyhow::Result;
use komorebi_client::Notification; use komorebi_client::Notification;
+28 -9
View File
@@ -13,7 +13,8 @@ feature-depth = 1
[advisories] [advisories]
ignore = [ ignore = [
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" }, { id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" } { id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" },
{ id = "RUSTSEC-2024-0320", reason = "not using any yaml features from this library" }
] ]
[licenses] [licenses]
@@ -33,43 +34,58 @@ allow = [
"Ubuntu-font-1.0", "Ubuntu-font-1.0",
"Unicode-3.0", "Unicode-3.0",
"Zlib", "Zlib",
"LicenseRef-Komorebi-1.0" "LicenseRef-Komorebi-2.0"
] ]
confidence-threshold = 0.8 confidence-threshold = 0.8
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebi" crate = "komorebi"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebi-client" crate = "komorebi-client"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebic" crate = "komorebic"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebic-no-console" crate = "komorebic-no-console"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebi-themes" crate = "komorebi-themes"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebi-gui" crate = "komorebi-gui"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
crate = "komorebi-bar" crate = "komorebi-bar"
expression = "LicenseRef-Komorebi-1.0" expression = "LicenseRef-Komorebi-2.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-shortcuts"
expression = "LicenseRef-Komorebi-2.0"
license-files = []
[[licenses.clarify]]
crate = "whkd-core"
expression = "LicenseRef-Komorebi-2.0"
license-files = []
[[licenses.clarify]]
crate = "whkd-parser"
expression = "LicenseRef-Komorebi-2.0"
license-files = [] license-files = []
[[licenses.clarify]] [[licenses.clarify]]
@@ -93,4 +109,7 @@ allow-git = [
"https://github.com/LGUG2Z/catppuccin-egui", "https://github.com/LGUG2Z/catppuccin-egui",
"https://github.com/LGUG2Z/windows-icons", "https://github.com/LGUG2Z/windows-icons",
"https://github.com/LGUG2Z/win32-display-data", "https://github.com/LGUG2Z/win32-display-data",
"https://github.com/LGUG2Z/flavours",
"https://github.com/LGUG2Z/base16_color_scheme",
"https://github.com/LGUG2Z/whkd",
] ]
+281 -141
View File
@@ -3,8 +3,9 @@
[ [
"0BSD", "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", "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,74 +17,85 @@
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index", "accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index", "accesskit_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", "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", "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", "ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index", "anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index", "anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index", "anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index", "anstyle-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", "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", "anyhow 1.0.98 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", "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", "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", "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 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", "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", "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", "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 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", "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", "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 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",
"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 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-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", "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", "chrono-tz 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.31 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_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index", "clap 4.5.37 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.37 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", "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-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-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", "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", "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-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index", "crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index", "crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"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", "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 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", "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", "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", "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", "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", "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", "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", "ecolor 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.0 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.0 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-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-winit 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.0 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.0 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.14.0 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.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", "encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index", "enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index", "enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index", "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 0.31.1 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_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", "equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index", "eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.3.0 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", "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", "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", "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", "form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index", "futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
@@ -95,53 +107,67 @@
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index", "futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index", "futures-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", "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.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", "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", "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", "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 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_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", "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.14.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.2 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", "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", "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", "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", "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", "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 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", "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", "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", "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", "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", "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", "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_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", "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.12.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index", "itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 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", "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", "khronos_api 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index", "lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index", "libc 0.2.172 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", "libgit2-sys 0.18.1+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", "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", "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", "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",
"miette 7.5.0 registry+https://github.com/rust-lang/crates.io-index", "logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"miette-derive 7.5.0 registry+https://github.com/rust-lang/crates.io-index", "logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"miette 7.6.0 registry+https://github.com/rust-lang/crates.io-index",
"miette-derive 7.6.0 registry+https://github.com/rust-lang/crates.io-index",
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index", "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", "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", "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", "native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index", "net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
@@ -154,10 +180,13 @@
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index", "num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index", "num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index", "num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"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-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index", "num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"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", "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 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", "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", "paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
@@ -165,20 +194,26 @@
"pin-project-lite 0.2.16 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", "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", "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", "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", "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-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-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", "proc-macro2 1.0.95 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index", "profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index", "profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.26 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index", "qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index", "quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.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 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_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_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", "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 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", "rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
@@ -186,46 +221,58 @@
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index", "regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index", "regex-syntax 0.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", "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", "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-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", "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", "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", "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", "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 1.0.219 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index", "serde_derive 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_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 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_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index", "serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index", "serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.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", "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", "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", "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", "stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"stacker 0.1.21 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index", "supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"supports-hyperlinks 3.1.0 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", "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", "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", "tempfile 3.19.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.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 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 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 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", "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", "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 0.3.41 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.2 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", "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", "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", "tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"tzdb 0.7.2 registry+https://github.com/rust-lang/crates.io-index", "tzdb 0.7.2 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index", "unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index", "unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"unicode-linebreak 0.1.5 registry+https://github.com/rust-lang/crates.io-index", "unicode-linebreak 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
@@ -241,48 +288,59 @@
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index", "vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index", "version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.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", "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", "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.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.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.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-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.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.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.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.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.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.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.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.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.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-interface 0.59.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.0 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-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.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.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.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.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.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.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.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.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.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.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.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.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.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index", "windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"winit 0.30.9 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", "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 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-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-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" "zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
@@ -302,7 +360,7 @@
"rav1e 0.7.1 registry+https://github.com/rust-lang/crates.io-index", "rav1e 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"v_frame 0.3.8 registry+https://github.com/rust-lang/crates.io-index", "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 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"
] ]
], ],
[ [
@@ -316,7 +374,7 @@
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index", "encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"exr 1.73.0 registry+https://github.com/rust-lang/crates.io-index", "exr 1.73.0 registry+https://github.com/rust-lang/crates.io-index",
"lebe 0.5.2 registry+https://github.com/rust-lang/crates.io-index", "lebe 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"ravif 0.11.11 registry+https://github.com/rust-lang/crates.io-index" "ravif 0.11.12 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
@@ -343,7 +401,7 @@
"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index", "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", "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", "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,24 +410,28 @@
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index", "accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index", "accesskit_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_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", "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", "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", "aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"aligned-vec 0.5.0 registry+https://github.com/rust-lang/crates.io-index", "aligned-vec 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index", "anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index", "anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index", "anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index", "anstyle-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", "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", "anyhow 1.0.98 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", "arg_enum_proc_macro 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index", "arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"atomic-waker 1.1.2 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", "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 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", "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", "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", "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 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", "bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -378,58 +440,72 @@
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index", "brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
"built 0.7.7 registry+https://github.com/rust-lang/crates.io-index", "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 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 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index", "byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"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", "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 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-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", "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", "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", "chrono-tz 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.31 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_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index", "chumsky 0.9.3 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.37 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.37 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", "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-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-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", "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", "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", "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-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index", "crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index", "crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"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", "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 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", "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", "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", "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", "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", "ecolor 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.0 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.0 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-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-winit 0.31.1 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.0 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.0 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.14.0 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.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", "encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index", "enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index", "enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index", "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 0.31.1 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_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", "equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index", "eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.3.0 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", "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", "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", "fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index", "font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.1 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 +519,80 @@
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index", "futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index", "futures-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", "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.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", "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", "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", "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", "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", "h2 0.4.9 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.14.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.2 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", "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", "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", "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 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", "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 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-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", "hyper-util 0.1.11 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 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", "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", "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", "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", "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", "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_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", "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.12.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index", "itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 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", "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", "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", "libc 0.2.172 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", "libgit2-sys 0.18.1+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", "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", "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", "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", "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", "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", "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", "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", "memoffset 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index", "mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
"mime_guess2 2.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", "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", "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", "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", "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", "native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index", "net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"netdev 0.32.0 registry+https://github.com/rust-lang/crates.io-index", "netdev 0.34.0 registry+https://github.com/rust-lang/crates.io-index",
"new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index", "new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index", "nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index", "nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index",
@@ -514,36 +606,54 @@
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index", "num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index", "num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index", "num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"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-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index", "num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"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", "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", "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 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", "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 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", "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", "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", "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-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", "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", "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", "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", "powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.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-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-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", "proc-macro2 1.0.95 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index", "profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index", "profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.26 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index", "qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index", "quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.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 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_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_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", "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 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", "rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
@@ -552,8 +662,9 @@
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index", "regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index", "regex-syntax 0.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", "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", "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", "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-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", "rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -562,46 +673,59 @@
"schannel 0.1.27 registry+https://github.com/rust-lang/crates.io-index", "schannel 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"schemars 0.8.22 registry+https://github.com/rust-lang/crates.io-index", "schemars 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", "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", "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 1.0.219 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index", "serde_derive 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_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 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_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index", "serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index", "serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.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", "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", "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", "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-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", "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", "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", "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", "stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"stacker 0.1.21 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index", "strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index",
"strum 0.27.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", "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", "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", "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", "sysinfo 0.34.2 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", "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 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 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 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", "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", "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", "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 0.3.41 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.2 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.43.0 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-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 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-layer 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index", "tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
@@ -616,6 +740,7 @@
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index", "ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.18.0 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", "tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"uds_windows 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "uds_windows 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index", "unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index", "unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
@@ -634,54 +759,66 @@
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index", "walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
"want 0.3.1 registry+https://github.com/rust-lang/crates.io-index", "want 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index", "web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.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", "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 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", "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.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.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.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-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.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.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.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.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-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.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.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.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.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.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-interface 0.59.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.0 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-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.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.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.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.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.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index", "windows-sys 0.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.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.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.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.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.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.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.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index", "windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.53.0 registry+https://github.com/rust-lang/crates.io-index",
"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index", "winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index", "winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index",
"winsafe 0.0.19 registry+https://github.com/rust-lang/crates.io-index", "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", "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 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-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-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" "zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
@@ -691,25 +828,26 @@
"MIT-0", "MIT-0",
[ [
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index", "dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.1 registry+https://github.com/rust-lang/crates.io-index" "tzdb_data 0.2.2 registry+https://github.com/rust-lang/crates.io-index"
] ]
], ],
[ [
"MPL-2.0", "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", "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", "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 +856,11 @@
"icu_collections 1.5.0 registry+https://github.com/rust-lang/crates.io-index", "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 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 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 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 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 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", "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", "litemap 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
@@ -753,14 +891,16 @@
[ [
"Zlib", "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 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 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", "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", "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", "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", "raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index", "zune-core 0.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-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
+2 -3
View File
@@ -10,9 +10,8 @@ Options:
Desired ease function for animation Desired ease function for animation
[default: linear] [default: linear]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, [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-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-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
-a, --animation-type <ANIMATION_TYPE> -a, --animation-type <ANIMATION_TYPE>
Animation type to apply the style to. If not specified, sets global style Animation type to apply the style to. If not specified, sets global style
+12
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
```
+12
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
```
+1 -1
View File
@@ -7,7 +7,7 @@ Usage: komorebic.exe query <STATE_QUERY>
Arguments: Arguments:
<STATE_QUERY> <STATE_QUERY>
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name] [possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name, focused-workspace-layout, version]
Options: Options:
-h, --help -h, --help
+12
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
```
+12
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
```
+1 -2
View File
@@ -1,8 +1,7 @@
# toggle-workspace-float-override # 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 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
the opposite of the global value
Usage: komorebic.exe toggle-workspace-float-override Usage: komorebic.exe toggle-workspace-float-override
+1 -5
View File
@@ -8,12 +8,8 @@ configuration file.
```json ```json
{ {
"default_workspace_padding": 0, "default_workspace_padding": 0,
"default_container_padding": 0, "default_container_padding": -1,
"border_width": 0,
"border_offset": -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) [![Watch the tutorial video](https://img.youtube.com/vi/6QYLao953XE/hqdefault.jpg)](https://www.youtube.com/watch?v=6QYLao953XE)
+1 -1
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, "monitor": 0,
"font_family": "JetBrains Mono", "font_family": "JetBrains Mono",
"theme": { "theme": {
+1 -8
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", "app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak", "window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert", "cross_monitor_move_behaviour": "Insert",
@@ -14,13 +14,6 @@
"unfocused_border": "Base03", "unfocused_border": "Base03",
"bar_accent": "Base0D" "bar_accent": "Base0D"
}, },
"stackbar": {
"height": 40,
"mode": "OnStack",
"tabs": {
"width": 300
}
},
"monitors": [ "monitors": [
{ {
"workspaces": [ "workspaces": [
+16
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 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 device: `null`, suggesting an issue when the display resumes from a suspended
state. 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.
+2
View File
@@ -5,6 +5,8 @@
alt + o : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell alt + o : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
alt + shift + o : komorebic reload-configuration alt + shift + o : komorebic reload-configuration
alt + i : komorebic toggle-shortcuts
# App shortcuts - these require shell to be pwsh / powershell # App shortcuts - these require shell to be pwsh / powershell
# The apps will be focused if open, or launched if not open # The apps will be focused if open, or launched if not open
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox } # alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
+5 -2
View File
@@ -31,7 +31,7 @@ install:
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
install-with-jsonschema: install-with-jsonschema:
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
build-targets *targets: build-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ } "{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
@@ -40,7 +40,7 @@ build-target target:
cargo +stable build --package {{ target }} --locked --release --no-default-features cargo +stable build --package {{ target }} --locked --release --no-default-features
build: build:
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts
copy-target target: copy-target target:
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
@@ -88,3 +88,6 @@ schemagen:
generate-schema-doc ./schema.json --config template_name=js_offline --config minify=false ./static-config-docs/ generate-schema-doc ./schema.json --config template_name=js_offline --config minify=false ./static-config-docs/
generate-schema-doc ./schema.bar.json --config template_name=js_offline --config minify=false ./bar-config-docs/ generate-schema-doc ./schema.bar.json --config template_name=js_offline --config minify=false ./bar-config-docs/
mv ./bar-config-docs/schema.bar.html ./bar-config-docs/schema.html mv ./bar-config-docs/schema.bar.html ./bar-config-docs/schema.html
depgen:
cargo deny list --format json | jq 'del(.unlicensed)' > dependencies.json
+4 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-bar" name = "komorebi-bar"
version = "0.1.36" version = "0.1.37"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -22,7 +22,7 @@ font-loader = "0.11"
hotwatch = { workspace = true } hotwatch = { workspace = true }
image = "0.25" image = "0.25"
lazy_static = { workspace = true } lazy_static = { workspace = true }
netdev = "0.33" netdev = "0.34"
num = "0.4" num = "0.4"
num-derive = "0.4" num-derive = "0.4"
num-traits = "0.2" num-traits = "0.2"
@@ -35,6 +35,7 @@ starship-battery = "0.10"
sysinfo = { workspace = true } sysinfo = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
which = { workspace = true }
windows = { workspace = true } windows = { workspace = true }
windows-core = { workspace = true } windows-core = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" } windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
@@ -42,4 +43,4 @@ windows-icons-fallback = { package = "windows-icons", git = "https://github.com/
[features] [features]
default = ["schemars"] default = ["schemars"]
schemars = ["dep:schemars"] schemars = ["dep:schemars", "komorebi-client/schemars", "komorebi-themes/schemars"]
+114 -23
View File
@@ -14,6 +14,8 @@ use crate::widgets::komorebi::KomorebiNotificationState;
use crate::widgets::widget::BarWidget; use crate::widgets::widget::BarWidget;
use crate::widgets::widget::WidgetConfig; use crate::widgets::widget::WidgetConfig;
use crate::KomorebiEvent; use crate::KomorebiEvent;
use crate::AUTO_SELECT_FILL_COLOUR;
use crate::AUTO_SELECT_TEXT_COLOUR;
use crate::BAR_HEIGHT; use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING; use crate::DEFAULT_PADDING;
use crate::MAX_LABEL_WIDTH; use crate::MAX_LABEL_WIDTH;
@@ -43,12 +45,16 @@ use eframe::egui::Vec2;
use eframe::egui::Visuals; use eframe::egui::Visuals;
use font_loader::system_fonts; use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder; use font_loader::system_fonts::FontPropertyBuilder;
use komorebi_client::Colour;
use komorebi_client::KomorebiTheme; use komorebi_client::KomorebiTheme;
use komorebi_client::MonitorNotification; use komorebi_client::MonitorNotification;
use komorebi_client::NotificationEvent; use komorebi_client::NotificationEvent;
use komorebi_client::PathExt;
use komorebi_client::SocketMessage; use komorebi_client::SocketMessage;
use komorebi_client::VirtualDesktopNotification;
use komorebi_themes::catppuccin_egui; use komorebi_themes::catppuccin_egui;
use komorebi_themes::Base16Value; use komorebi_themes::Base16Value;
use komorebi_themes::Base16Wrapper;
use komorebi_themes::Catppuccin; use komorebi_themes::Catppuccin;
use komorebi_themes::CatppuccinValue; use komorebi_themes::CatppuccinValue;
use std::cell::RefCell; use std::cell::RefCell;
@@ -87,11 +93,14 @@ pub fn apply_theme(
grouping: Option<Grouping>, grouping: Option<Grouping>,
render_config: Rc<RefCell<RenderConfig>>, render_config: Rc<RefCell<RenderConfig>>,
) { ) {
match theme { let (auto_select_fill, auto_select_text) = match theme {
KomobarTheme::Catppuccin { KomobarTheme::Catppuccin {
name: catppuccin, name: catppuccin,
accent: catppuccin_value, accent: catppuccin_value,
} => match catppuccin { auto_select_fill: catppuccin_auto_select_fill,
auto_select_text: catppuccin_auto_select_text,
} => {
match catppuccin {
Catppuccin::Frappe => { Catppuccin::Frappe => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE); catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
let catppuccin_value = catppuccin_value.unwrap_or_default(); let catppuccin_value = catppuccin_value.unwrap_or_default();
@@ -148,14 +157,22 @@ pub fn apply_theme(
bg_color.replace(catppuccin_egui::MOCHA.base); 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 { KomobarTheme::Base16 {
name: base16, name: base16,
accent: base16_value, accent: base16_value,
auto_select_fill: base16_auto_select_fill,
auto_select_text: base16_auto_select_text,
} => { } => {
ctx.set_style(base16.style()); ctx.set_style(base16.style());
let base16_value = base16_value.unwrap_or_default(); let base16_value = base16_value.unwrap_or_default();
let accent = base16_value.color32(base16); let accent = base16_value.color32(Base16Wrapper::Base16(base16));
ctx.style_mut(|style| { ctx.style_mut(|style| {
style.visuals.selection.stroke.color = accent; style.visuals.selection.stroke.color = accent;
@@ -164,8 +181,46 @@ pub fn apply_theme(
}); });
bg_color.replace(base16.background()); 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 // Apply transparency_alpha
let theme_color = *bg_color.borrow(); let theme_color = *bg_color.borrow();
@@ -431,11 +486,11 @@ impl Komobar {
} }
fn try_apply_theme(&mut self, ctx: &Context) { fn try_apply_theme(&mut self, ctx: &Context) {
match self.config.theme { match &self.config.theme {
Some(theme) => { Some(theme) => {
apply_theme( apply_theme(
ctx, ctx,
theme, theme.clone(),
self.bg_color.clone(), self.bg_color.clone(),
self.bg_color_with_alpha.clone(), self.bg_color_with_alpha.clone(),
self.config.transparency_alpha, self.config.transparency_alpha,
@@ -447,13 +502,16 @@ impl Komobar {
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else( let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|_| dirs::home_dir().expect("there is no home directory"), |_| dirs::home_dir().expect("there is no home directory"),
|home_path| { |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
);
if home.as_path().is_dir() {
home home
} else {
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
}
}, },
); );
@@ -463,6 +521,26 @@ impl Komobar {
match komorebi_client::StaticConfig::read(&config) { match komorebi_client::StaticConfig::read(&config) {
Ok(config) => { Ok(config) => {
if let Some(theme) = config.theme { if let Some(theme) = config.theme {
let stack_accent = match theme {
KomorebiTheme::Catppuccin {
name, stack_border, ..
} => stack_border
.unwrap_or(CatppuccinValue::Green)
.color32(name.as_theme()),
KomorebiTheme::Base16 {
name, stack_border, ..
} => stack_border
.unwrap_or(Base16Value::Base0B)
.color32(Base16Wrapper::Base16(name)),
KomorebiTheme::Custom {
ref colours,
stack_border,
..
} => stack_border
.unwrap_or(Base16Value::Base0B)
.color32(Base16Wrapper::Custom(colours.clone())),
};
apply_theme( apply_theme(
ctx, ctx,
KomobarTheme::from(theme), KomobarTheme::from(theme),
@@ -473,17 +551,6 @@ impl Komobar {
self.render_config.clone(), 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 { if let Some(state) = &self.komorebi_notification_state {
state.borrow_mut().stack_accent = Some(stack_accent); state.borrow_mut().stack_accent = Some(stack_accent);
} }
@@ -692,6 +759,30 @@ impl eframe::App for Komobar {
self.monitor_index = monitor_index; self.monitor_index = monitor_index;
let mut should_apply_config = false; let mut should_apply_config = false;
match notification.event {
NotificationEvent::VirtualDesktop(
VirtualDesktopNotification::EnteredAssociatedVirtualDesktop,
) => {
tracing::debug!(
"back on komorebi's associated virtual desktop - restoring bar"
);
if let Some(hwnd) = self.hwnd {
komorebi_client::WindowsApi::restore_window(hwnd);
}
}
NotificationEvent::VirtualDesktop(
VirtualDesktopNotification::LeftAssociatedVirtualDesktop,
) => {
tracing::debug!(
"no longer on komorebi's associated virtual desktop - minimizing bar"
);
if let Some(hwnd) = self.hwnd {
komorebi_client::WindowsApi::minimize_window(hwnd);
}
}
_ => {}
}
if self.monitor_index.is_none() if self.monitor_index.is_none()
|| self || self
.monitor_index .monitor_index
@@ -782,7 +873,7 @@ impl eframe::App for Komobar {
self.bg_color_with_alpha.clone(), self.bg_color_with_alpha.clone(),
self.config.transparency_alpha, self.config.transparency_alpha,
self.config.grouping, self.config.grouping,
self.config.theme, self.config.theme.clone(),
self.render_config.clone(), self.render_config.clone(),
); );
} }
+28 -2
View File
@@ -13,7 +13,7 @@ use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.bar.json` configuration file reference for `v0.1.36` /// The `komorebi.bar.json` configuration file reference for `v0.1.37`
pub struct KomobarConfig { pub struct KomobarConfig {
/// Bar height (default: 50) /// Bar height (default: 50)
pub height: Option<f32>, pub height: Option<f32>,
@@ -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))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")] #[serde(tag = "palette")]
pub enum KomobarTheme { pub enum KomobarTheme {
@@ -382,12 +382,24 @@ pub enum KomobarTheme {
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin) /// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
name: komorebi_themes::Catppuccin, name: komorebi_themes::Catppuccin,
accent: Option<komorebi_themes::CatppuccinValue>, 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 /// A theme from base16-egui-themes
Base16 { Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/) /// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
name: komorebi_themes::Base16, name: komorebi_themes::Base16,
accent: Option<komorebi_themes::Base16Value>, 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 { } => Self::Catppuccin {
name, name,
accent: bar_accent, accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
}, },
KomorebiTheme::Base16 { KomorebiTheme::Base16 {
name, bar_accent, .. name, bar_accent, ..
} => Self::Base16 { } => Self::Base16 {
name, name,
accent: bar_accent, 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,
}, },
} }
} }
+23 -25
View File
@@ -16,6 +16,8 @@ use font_loader::system_fonts;
use hotwatch::EventKind; use hotwatch::EventKind;
use hotwatch::Hotwatch; use hotwatch::Hotwatch;
use image::RgbaImage; use image::RgbaImage;
use komorebi_client::replace_env_in_path;
use komorebi_client::PathExt;
use komorebi_client::SocketMessage; use komorebi_client::SocketMessage;
use komorebi_client::SubscribeOptions; use komorebi_client::SubscribeOptions;
use std::collections::HashMap; use std::collections::HashMap;
@@ -23,6 +25,7 @@ use std::io::BufReader;
use std::io::Read; use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::AtomicI32; use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -47,6 +50,9 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
pub static BAR_HEIGHT: f32 = 50.0; pub static BAR_HEIGHT: f32 = 50.0;
pub static DEFAULT_PADDING: f32 = 10.0; pub static DEFAULT_PADDING: f32 = 10.0;
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>>> = pub static ICON_CACHE: LazyLock<Mutex<HashMap<isize, RgbaImage>>> =
LazyLock::new(|| Mutex::new(HashMap::new())); LazyLock::new(|| Mutex::new(HashMap::new()));
@@ -61,6 +67,7 @@ struct Opts {
fonts: bool, fonts: bool,
/// Path to a JSON or YAML configuration file /// Path to a JSON or YAML configuration file
#[clap(short, long)] #[clap(short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>, config: Option<PathBuf>,
/// Write an example komorebi.bar.json to disk /// Write an example komorebi.bar.json to disk
#[clap(long)] #[clap(long)]
@@ -155,13 +162,15 @@ fn main() -> color_eyre::Result<()> {
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else( let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|_| dirs::home_dir().expect("there is no home directory"), |_| dirs::home_dir().expect("there is no home directory"),
|home_path| { |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
);
if home.as_path().is_dir() {
home home
} else {
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
}
}, },
); );
@@ -170,7 +179,7 @@ fn main() -> color_eyre::Result<()> {
std::fs::write(home_dir.join("komorebi.bar.json"), komorebi_bar_json)?; std::fs::write(home_dir.join("komorebi.bar.json"), komorebi_bar_json)?;
println!( println!(
"Example komorebi.bar.json file written to {}", "Example komorebi.bar.json file written to {}",
home_dir.as_path().display() home_dir.display()
); );
std::process::exit(0); std::process::exit(0);
@@ -178,16 +187,11 @@ fn main() -> color_eyre::Result<()> {
let default_config_path = home_dir.join("komorebi.bar.json"); let default_config_path = home_dir.join("komorebi.bar.json");
let config_path = opts.config.map_or_else( let config_path = opts.config.or_else(|| {
|| { default_config_path
if !default_config_path.is_file() { .is_file()
None .then_some(default_config_path.clone())
} else { });
Some(default_config_path.clone())
}
},
Option::from,
);
let mut config = match config_path { let mut config = match config_path {
None => { None => {
@@ -197,17 +201,14 @@ fn main() -> color_eyre::Result<()> {
std::fs::write(&default_config_path, komorebi_bar_json)?; std::fs::write(&default_config_path, komorebi_bar_json)?;
tracing::info!( tracing::info!(
"created example configuration file: {}", "created example configuration file: {}",
default_config_path.as_path().display() default_config_path.display()
); );
KomobarConfig::read(&default_config_path)? KomobarConfig::read(&default_config_path)?
} }
Some(ref config) => { Some(ref config) => {
if !opts.aliases { if !opts.aliases {
tracing::info!( tracing::info!("found configuration file: {}", config.display());
"found configuration file: {}",
config.as_path().to_string_lossy()
);
} }
KomobarConfig::read(config)? KomobarConfig::read(config)?
@@ -307,10 +308,7 @@ fn main() -> color_eyre::Result<()> {
hotwatch.watch(config_path, move |event| match event.kind { hotwatch.watch(config_path, move |event| match event.kind {
EventKind::Modify(_) | EventKind::Remove(_) => match KomobarConfig::read(&config_path_cl) { EventKind::Modify(_) | EventKind::Remove(_) => match KomobarConfig::read(&config_path_cl) {
Ok(updated) => { Ok(updated) => {
tracing::info!( tracing::info!("configuration file updated: {}", config_path_cl.display());
"configuration file updated: {}",
config_path_cl.as_path().to_string_lossy()
);
if let Err(error) = tx_config.send(updated) { if let Err(error) = tx_config.send(updated) {
tracing::error!("could not send configuration update to gui: {error}") tracing::error!("could not send configuration update to gui: {error}")
+15
View File
@@ -1,6 +1,8 @@
use crate::bar::Alignment; use crate::bar::Alignment;
use crate::config::KomobarConfig; use crate::config::KomobarConfig;
use crate::config::MonitorConfigOrIndex; use crate::config::MonitorConfigOrIndex;
use crate::AUTO_SELECT_FILL_COLOUR;
use crate::AUTO_SELECT_TEXT_COLOUR;
use eframe::egui::Color32; use eframe::egui::Color32;
use eframe::egui::Context; use eframe::egui::Context;
use eframe::egui::CornerRadius; use eframe::egui::CornerRadius;
@@ -11,8 +13,11 @@ use eframe::egui::Margin;
use eframe::egui::Shadow; use eframe::egui::Shadow;
use eframe::egui::TextStyle; use eframe::egui::TextStyle;
use eframe::egui::Ui; use eframe::egui::Ui;
use komorebi_client::Colour;
use komorebi_client::Rgb;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::num::NonZeroU32;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@@ -55,6 +60,10 @@ pub struct RenderConfig {
pub icon_font_id: FontId, pub icon_font_id: FontId,
/// Show all icons on the workspace section of the Komorebi widget /// Show all icons on the workspace section of the Komorebi widget
pub show_all_icons: bool, 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 { pub trait RenderExt {
@@ -108,6 +117,10 @@ impl RenderExt for &KomobarConfig {
text_font_id, text_font_id,
icon_font_id, icon_font_id,
show_all_icons, 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(), text_font_id: FontId::default(),
icon_font_id: FontId::default(), icon_font_id: FontId::default(),
show_all_icons: false, show_all_icons: false,
auto_select_fill: None,
auto_select_text: None,
} }
} }
+27 -4
View File
@@ -10,15 +10,29 @@ use eframe::egui::Ui;
/// Same as SelectableLabel, but supports all content /// Same as SelectableLabel, but supports all content
pub struct SelectableFrame { pub struct SelectableFrame {
selected: bool, selected: bool,
selected_fill: Option<Color32>,
} }
impl SelectableFrame { impl SelectableFrame {
pub fn new(selected: bool) -> Self { 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 { 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 Frame::NONE
.show(ui, |ui| { .show(ui, |ui| {
@@ -32,7 +46,16 @@ impl SelectableFrame {
); );
// since the stroke is drawn inside the frame, we always reserve space for it // 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); let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE Frame::NONE
@@ -47,7 +70,7 @@ impl SelectableFrame {
Frame::NONE Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_fill)) .stroke(Stroke::new(1.0, visuals.bg_fill))
.corner_radius(visuals.corner_radius) .corner_radius(visuals.corner_radius)
.fill(visuals.bg_fill) .fill(selected_fill.unwrap_or(visuals.bg_fill))
.inner_margin(inner_margin) .inner_margin(inner_margin)
.show(ui, add_contents); .show(ui, add_contents);
} else { } else {
+356
View File
@@ -0,0 +1,356 @@
use super::komorebi::img_to_texture;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget;
use eframe::egui::vec2;
use eframe::egui::Color32;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Image;
use eframe::egui::Label;
use eframe::egui::Margin;
use eframe::egui::RichText;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::StrokeKind;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use image::DynamicImage;
use image::RgbaImage;
use komorebi_client::PathExt;
use serde::Deserialize;
use serde::Serialize;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
use tracing;
use which::which;
/// Minimum interval between consecutive application launches to prevent accidental spamming.
const MIN_LAUNCH_INTERVAL: Duration = Duration::from_millis(800);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ApplicationsConfig {
/// Enables or disables the applications widget.
pub enable: bool,
/// Whether to show the launch command on hover (optional).
/// Could be overridden per application. Defaults to `false` if not set.
pub show_command_on_hover: Option<bool>,
/// Horizontal spacing between application buttons.
pub spacing: Option<f32>,
/// Default display format for all applications (optional).
/// Could be overridden per application. Defaults to `Icon`.
pub display: Option<DisplayFormat>,
/// List of configured applications to display.
pub items: Vec<AppConfig>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AppConfig {
/// Whether to enable this application button (optional).
/// Inherits from the global `Applications` setting if omitted.
pub enable: Option<bool>,
/// Whether to show the launch command on hover (optional).
/// Inherits from the global `Applications` setting if omitted.
pub show_command_on_hover: Option<bool>,
/// Display name of the application.
pub name: String,
/// Optional icon: a path to an image or a text-based glyph (e.g., from Nerd Fonts).
/// If not set, and if the `command` is a path to an executable, an icon might be extracted from it.
/// Note: glyphs require a compatible `font_family`.
pub icon: Option<String>,
/// Command to execute (e.g. path to the application or shell command).
pub command: String,
/// Display format for this application button (optional). Overrides global format if set.
pub display: Option<DisplayFormat>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DisplayFormat {
/// Show only the application icon.
#[default]
Icon,
/// Show only the application name as text.
Text,
/// Show both the application icon and name.
IconAndText,
}
#[derive(Clone, Debug)]
pub struct Applications {
/// Whether the applications widget is enabled.
pub enable: bool,
/// Horizontal spacing between application buttons.
pub spacing: Option<f32>,
/// Applications to be rendered in the UI.
pub items: Vec<App>,
}
impl BarWidget for Applications {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if !self.enable {
return;
}
let icon_config = IconConfig {
font_id: config.icon_font_id.clone(),
size: config.icon_font_id.size,
color: ctx.style().visuals.selection.stroke.color,
};
if let Some(spacing) = self.spacing {
ui.spacing_mut().item_spacing.x = spacing;
}
config.apply_on_widget(false, ui, |ui| {
for app in &mut self.items {
app.render(ctx, ui, &icon_config);
}
});
}
}
impl From<&ApplicationsConfig> for Applications {
fn from(applications_config: &ApplicationsConfig) -> Self {
// Allow immediate launch by initializing last_launch in the past.
let last_launch = Instant::now() - 2 * MIN_LAUNCH_INTERVAL;
let mut applications_config = applications_config.clone();
let items = applications_config
.items
.iter_mut()
.enumerate()
.map(|(index, app_config)| {
app_config.command = app_config
.command
.replace_env()
.to_string_lossy()
.to_string();
if let Some(icon) = &mut app_config.icon {
*icon = icon.replace_env().to_string_lossy().to_string();
}
App {
enable: app_config.enable.unwrap_or(applications_config.enable),
name: app_config
.name
.is_empty()
.then(|| format!("App {}", index + 1))
.unwrap_or_else(|| app_config.name.clone()),
icon: Icon::try_from(app_config),
command: app_config.command.clone(),
display: app_config
.display
.or(applications_config.display)
.unwrap_or_default(),
show_command_on_hover: app_config
.show_command_on_hover
.or(applications_config.show_command_on_hover)
.unwrap_or(false),
last_launch,
}
})
.collect();
Self {
enable: applications_config.enable,
items,
spacing: applications_config.spacing,
}
}
}
/// A single resolved application entry used at runtime.
#[derive(Clone, Debug)]
pub struct App {
/// Whether this application is enabled.
pub enable: bool,
/// Display name of the application. Defaults to "App N" if not set.
pub name: String,
/// Icon to display for this application, if available.
pub icon: Option<Icon>,
/// Command to execute when the application is launched.
pub command: String,
/// Display format (icon, text, or both).
pub display: DisplayFormat,
/// Whether to show the launch command on hover.
pub show_command_on_hover: bool,
/// Last time this application was launched (used for cooldown control).
pub last_launch: Instant,
}
impl App {
/// Renders the application button in the provided `Ui` context with a given icon size.
#[inline]
pub fn render(&mut self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {
if self.enable
&& SelectableFrame::new(false)
.show(ui, |ui| {
ui.spacing_mut().item_spacing = Vec2::splat(4.0);
match self.display {
DisplayFormat::Icon => self.draw_icon(ctx, ui, icon_config),
DisplayFormat::Text => self.draw_name(ui),
DisplayFormat::IconAndText => {
self.draw_icon(ctx, ui, icon_config);
self.draw_name(ui);
}
}
// Add hover text with command information
if self.show_command_on_hover {
ui.response()
.on_hover_text(format!("Launch: {}", self.command));
} else {
ui.response();
}
})
.clicked()
{
// Launch the application when clicked
self.launch_if_ready();
}
}
/// Draws the application's icon within the UI if available,
/// or falls back to a default placeholder icon.
#[inline]
fn draw_icon(&self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {
if let Some(icon) = &self.icon {
icon.draw(ctx, ui, icon_config);
} else {
Icon::draw_fallback(ui, Vec2::splat(icon_config.size));
}
}
/// Displays the application's name as a non-selectable label within the UI.
#[inline]
fn draw_name(&self, ui: &mut Ui) {
ui.add(Label::new(&self.name).selectable(false));
}
/// Attempts to launch the specified command in a separate thread if enough time has passed
/// since the last launch. This prevents repeated launches from rapid consecutive clicks.
///
/// Errors during launch are logged using the `tracing` crate.
pub fn launch_if_ready(&mut self) {
let now = Instant::now();
if now.duration_since(self.last_launch) < MIN_LAUNCH_INTERVAL {
return;
}
self.last_launch = now;
let command_string = self.command.clone();
// Launch the application in a separate thread to avoid blocking the UI
std::thread::spawn(move || {
if let Err(e) = Command::new("cmd").args(["/C", &command_string]).spawn() {
tracing::error!("Failed to launch command '{}': {}", command_string, e);
}
});
}
}
/// Holds decoded image data to be used as an icon in the UI.
#[derive(Clone, Debug)]
pub enum Icon {
/// RGBA image used for rendering the icon.
Image(RgbaImage),
/// Text-based icon, e.g. from a font like Nerd Fonts.
Text(String),
}
impl Icon {
/// Attempts to create an `Icon` from the given `AppConfig`.
/// Loads the image from a specified icon path or extracts it from the application's
/// executable if the command points to a valid executable file.
#[inline]
pub fn try_from(config: &AppConfig) -> Option<Self> {
if let Some(icon) = config.icon.as_deref().map(str::trim) {
if !icon.is_empty() {
let path = Path::new(&icon);
if path.is_file() {
match image::open(path).as_ref().map(DynamicImage::to_rgba8) {
Ok(image) => return Some(Icon::Image(image)),
Err(err) => {
tracing::error!("Failed to load icon from {}, error: {}", icon, err)
}
}
} else {
return Some(Icon::Text(icon.to_owned()));
}
}
}
let binary = PathBuf::from(config.command.split(".exe").next()?);
let path = if binary.is_file() {
Some(binary)
} else {
which(binary).ok()
};
match path {
Some(path) => windows_icons::get_icon_by_path(&path.to_string_lossy())
.or_else(|| windows_icons_fallback::get_icon_by_path(&path.to_string_lossy()))
.map(Icon::Image),
None => None,
}
}
/// Renders the icon in the given `Ui` context with the specified size.
#[inline]
pub fn draw(&self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {
match self {
Icon::Image(image) => {
Frame::NONE
.inner_margin(Margin::same(ui.style().spacing.button_padding.y as i8))
.show(ui, |ui| {
ui.add(
Image::from(&img_to_texture(ctx, image))
.maintain_aspect_ratio(true)
.fit_to_exact_size(Vec2::splat(icon_config.size)),
);
});
}
Icon::Text(icon) => {
let rich_text = RichText::new(icon)
.font(icon_config.font_id.clone())
.size(icon_config.size)
.color(icon_config.color);
ui.add(Label::new(rich_text).selectable(false));
}
}
}
/// Draws a fallback icon when the specified icon cannot be loaded.
/// Displays a simple crossed-out rectangle as a placeholder.
#[inline]
pub fn draw_fallback(ui: &mut Ui, icon_size: Vec2) {
let (response, painter) = ui.allocate_painter(icon_size, Sense::hover());
let stroke = Stroke::new(1.0, ui.style().visuals.text_color());
let mut rect = response.rect;
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
}
}
/// Configuration structure for icon rendering
#[derive(Clone, Debug)]
pub struct IconConfig {
/// Font used for text-based icons
pub font_id: FontId,
/// Size of the icon
pub size: f32,
/// Color of the icon used for text-based icons
pub color: Color32,
}
+53 -16
View File
@@ -28,6 +28,8 @@ pub struct BatteryConfig {
pub data_refresh_interval: Option<u64>, pub data_refresh_interval: Option<u64>,
/// Display label prefix /// Display label prefix
pub label_prefix: Option<LabelPrefix>, pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is under this value [[1-100]]
pub auto_select_under: Option<u8>,
} }
impl From<BatteryConfig> for Battery { impl From<BatteryConfig> for Battery {
@@ -38,9 +40,10 @@ impl From<BatteryConfig> for Battery {
enable: value.enable, enable: value.enable,
hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false), hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false),
manager: Manager::new().unwrap(), manager: Manager::new().unwrap(),
last_state: String::new(), last_state: None,
data_refresh_interval, data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon), label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
auto_select_under: value.auto_select_under.map(|u| u.clamp(1, 100)),
state: BatteryState::Discharging, state: BatteryState::Discharging,
last_updated: Instant::now() last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval)) .checked_sub(Duration::from_secs(data_refresh_interval))
@@ -52,6 +55,16 @@ impl From<BatteryConfig> for Battery {
pub enum BatteryState { pub enum BatteryState {
Charging, Charging,
Discharging, Discharging,
High,
Medium,
Low,
Warning,
}
#[derive(Clone, Debug)]
struct BatteryOutput {
label: String,
selected: bool,
} }
pub struct Battery { pub struct Battery {
@@ -61,37 +74,53 @@ pub struct Battery {
pub state: BatteryState, pub state: BatteryState,
data_refresh_interval: u64, data_refresh_interval: u64,
label_prefix: LabelPrefix, label_prefix: LabelPrefix,
last_state: String, auto_select_under: Option<u8>,
last_state: Option<BatteryOutput>,
last_updated: Instant, last_updated: Instant,
} }
impl Battery { impl Battery {
fn output(&mut self) -> String { fn output(&mut self) -> Option<BatteryOutput> {
let mut output = self.last_state.clone(); let mut output = self.last_state.clone();
let now = Instant::now(); let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
output.clear(); output = None;
if let Ok(mut batteries) = self.manager.batteries() { if let Ok(mut batteries) = self.manager.batteries() {
if let Some(Ok(first)) = batteries.nth(0) { 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 { if percentage == 100 && self.hide_on_full_charge {
output = String::new() output = None
} else { } else {
match first.state() { match first.state() {
State::Charging => self.state = BatteryState::Charging, 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 { let selected = self.auto_select_under.is_some_and(|u| percentage <= u);
output = Some(BatteryOutput {
label: match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => { LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage:.0}%") format!("BAT: {percentage}%")
} }
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"), 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) { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable { if self.enable {
let output = self.output(); let output = self.output();
if !output.is_empty() { if let Some(output) = output {
let emoji = match self.state { let emoji = match self.state {
BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING, BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING,
BatteryState::Discharging => egui_phosphor::regular::BATTERY_FULL, 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( let mut layout_job = LayoutJob::simple(
match self.label_prefix { match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(), LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(),
LabelPrefix::None | LabelPrefix::Text => String::new(), LabelPrefix::None | LabelPrefix::Text => String::new(),
}, },
config.icon_font_id.clone(), config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color, auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0, 100.0,
); );
layout_job.append( layout_job.append(
&output, &output.label,
10.0, 10.0,
TextFormat { TextFormat {
font_id: config.text_font_id.clone(), 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, valign: Align::Center,
..Default::default() ..Default::default()
}, },
); );
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| { 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))) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked() .clicked()
{ {
+29 -10
View File
@@ -25,6 +25,8 @@ pub struct CpuConfig {
pub data_refresh_interval: Option<u64>, pub data_refresh_interval: Option<u64>,
/// Display label prefix /// Display label prefix
pub label_prefix: Option<LabelPrefix>, pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>,
} }
impl From<CpuConfig> for Cpu { impl From<CpuConfig> for Cpu {
@@ -38,6 +40,7 @@ impl From<CpuConfig> for Cpu {
), ),
data_refresh_interval, data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), 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() last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval)) .checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(), .unwrap(),
@@ -45,26 +48,38 @@ impl From<CpuConfig> for Cpu {
} }
} }
#[derive(Clone, Debug)]
struct CpuOutput {
label: String,
selected: bool,
}
pub struct Cpu { pub struct Cpu {
pub enable: bool, pub enable: bool,
system: System, system: System,
data_refresh_interval: u64, data_refresh_interval: u64,
label_prefix: LabelPrefix, label_prefix: LabelPrefix,
auto_select_over: Option<u8>,
last_updated: Instant, last_updated: Instant,
} }
impl Cpu { impl Cpu {
fn output(&mut self) -> String { fn output(&mut self) -> CpuOutput {
let now = Instant::now(); let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.system.refresh_cpu_usage(); self.system.refresh_cpu_usage();
self.last_updated = now; self.last_updated = now;
} }
let used = self.system.global_cpu_usage(); let used = self.system.global_cpu_usage() as u8;
match self.label_prefix { let selected = self.auto_select_over.is_some_and(|o| used >= o);
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {:.0}%", used),
LabelPrefix::None | LabelPrefix::Icon => format!("{:.0}%", used), 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) { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable { if self.enable {
let output = self.output(); 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( let mut layout_job = LayoutJob::simple(
match self.label_prefix { match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => { LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -82,23 +99,25 @@ impl BarWidget for Cpu {
LabelPrefix::None | LabelPrefix::Text => String::new(), LabelPrefix::None | LabelPrefix::Text => String::new(),
}, },
config.icon_font_id.clone(), config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color, auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0, 100.0,
); );
layout_job.append( layout_job.append(
&output, &output.label,
10.0, 10.0,
TextFormat { TextFormat {
font_id: config.text_font_id.clone(), 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, valign: Align::Center,
..Default::default() ..Default::default()
}, },
); );
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| { 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))) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked() .clicked()
{ {
+138 -75
View File
@@ -11,7 +11,9 @@ use crate::widgets::widget::BarWidget;
use crate::ICON_CACHE; use crate::ICON_CACHE;
use crate::MAX_LABEL_WIDTH; use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX; use crate::MONITOR_INDEX;
use eframe::egui::text::LayoutJob;
use eframe::egui::vec2; use eframe::egui::vec2;
use eframe::egui::Align;
use eframe::egui::Color32; use eframe::egui::Color32;
use eframe::egui::ColorImage; use eframe::egui::ColorImage;
use eframe::egui::Context; use eframe::egui::Context;
@@ -24,6 +26,7 @@ use eframe::egui::RichText;
use eframe::egui::Sense; use eframe::egui::Sense;
use eframe::egui::Stroke; use eframe::egui::Stroke;
use eframe::egui::StrokeKind; use eframe::egui::StrokeKind;
use eframe::egui::TextFormat;
use eframe::egui::TextureHandle; use eframe::egui::TextureHandle;
use eframe::egui::TextureOptions; use eframe::egui::TextureOptions;
use eframe::egui::Ui; use eframe::egui::Ui;
@@ -55,8 +58,11 @@ pub struct KomorebiConfig {
pub layout: Option<KomorebiLayoutConfig>, pub layout: Option<KomorebiLayoutConfig>,
/// Configure the Workspace Layer widget /// Configure the Workspace Layer widget
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>, pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
/// Configure the Focused Window widget /// Configure the Focused Container widget
pub focused_window: Option<KomorebiFocusedWindowConfig>, #[serde(alias = "focused_window")]
pub focused_container: Option<KomorebiFocusedContainerConfig>,
/// Configure the Locked Container widget
pub locked_container: Option<KomorebiLockedContainerConfig>,
/// Configure the Configuration Switcher widget /// Configure the Configuration Switcher widget
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>, pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
} }
@@ -96,15 +102,26 @@ pub struct KomorebiWorkspaceLayerConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiFocusedWindowConfig { pub struct KomorebiFocusedContainerConfig {
/// Enable the Komorebi Focused Window widget /// Enable the Komorebi Focused Container widget
pub enable: bool, 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>, pub show_icon: Option<bool>,
/// Display format of the currently focused window /// Display format of the currently focused container
pub display: Option<DisplayFormat>, 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)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiConfigurationSwitcherConfig { pub struct KomorebiConfigurationSwitcherConfig {
@@ -140,15 +157,19 @@ impl From<&KomorebiConfig> for Komorebi {
.unwrap_or_default(), .unwrap_or_default(),
mouse_follows_focus: true, mouse_follows_focus: true,
work_area_offset: None, work_area_offset: None,
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY, focused_container_information: (
false,
KomorebiNotificationStateContainerInformation::EMPTY,
),
stack_accent: None, stack_accent: None,
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst), monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
monitor_usr_idx_map: HashMap::new(), monitor_usr_idx_map: HashMap::new(),
})), })),
workspaces: value.workspaces, workspaces: value.workspaces,
layout: value.layout.clone(), layout: value.layout.clone(),
focused_window: value.focused_window, focused_container: value.focused_container,
workspace_layer: value.workspace_layer, workspace_layer: value.workspace_layer,
locked_container: value.locked_container,
configuration_switcher, configuration_switcher,
} }
} }
@@ -159,8 +180,9 @@ pub struct Komorebi {
pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>, pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>,
pub workspaces: Option<KomorebiWorkspacesConfig>, pub workspaces: Option<KomorebiWorkspacesConfig>,
pub layout: Option<KomorebiLayoutConfig>, pub layout: Option<KomorebiLayoutConfig>,
pub focused_window: Option<KomorebiFocusedWindowConfig>, pub focused_container: Option<KomorebiFocusedContainerConfig>,
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>, pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
pub locked_container: Option<KomorebiLockedContainerConfig>,
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>, pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
} }
@@ -178,9 +200,10 @@ impl BarWidget for Komorebi {
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into()); let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
config.apply_on_widget(false, ui, |ui| { 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() komorebi_notification_state.workspaces.iter().enumerate()
{ {
if *should_show {
let is_selected = komorebi_notification_state.selected_workspace.eq(ws); let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
if SelectableFrame::new( if SelectableFrame::new(
@@ -269,7 +292,6 @@ impl BarWidget for Komorebi {
komorebi_notification_state.monitor_index, komorebi_notification_state.monitor_index,
i, i,
), ),
SocketMessage::RetileWithResizeDimensions,
SocketMessage::MouseFollowsFocus(true), SocketMessage::MouseFollowsFocus(true),
]) ])
.is_err() .is_err()
@@ -278,7 +300,6 @@ impl BarWidget for Komorebi {
"could not send the following batch of messages to komorebi:\n "could not send the following batch of messages to komorebi:\n
MouseFollowsFocus(false)\n MouseFollowsFocus(false)\n
FocusMonitorWorkspaceNumber({}, {})\n FocusMonitorWorkspaceNumber({}, {})\n
RetileWithResizeDimensions
MouseFollowsFocus(true)\n", MouseFollowsFocus(true)\n",
komorebi_notification_state.monitor_index, komorebi_notification_state.monitor_index,
i, i,
@@ -289,20 +310,19 @@ impl BarWidget for Komorebi {
komorebi_notification_state.monitor_index, komorebi_notification_state.monitor_index,
i, i,
), ),
SocketMessage::RetileWithResizeDimensions,
]) ])
.is_err() .is_err()
{ {
tracing::error!( tracing::error!(
"could not send the following batch of messages to komorebi:\n "could not send the following batch of messages to komorebi:\n
FocusMonitorWorkspaceNumber({}, {})\n FocusMonitorWorkspaceNumber({}, {})\n",
RetileWithResizeDimensions",
komorebi_notification_state.monitor_index, komorebi_notification_state.monitor_index,
i, i,
); );
} }
} }
} }
}
}); });
} }
@@ -318,7 +338,7 @@ impl BarWidget for Komorebi {
.workspaces .workspaces
.iter() .iter()
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0)) .find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
.map(|(_, _, layer)| layer); .map(|(_, _, layer, _)| layer);
if let Some(layer) = layer { if let Some(layer) = layer {
if (layer_config.show_when_tiling.unwrap_or_default() if (layer_config.show_when_tiling.unwrap_or_default()
@@ -335,7 +355,7 @@ impl BarWidget for Komorebi {
if matches!(layer, WorkspaceLayer::Tiling) { if matches!(layer, WorkspaceLayer::Tiling) {
let (response, painter) = let (response, painter) =
ui.allocate_painter(size, Sense::hover()); 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 stroke = Stroke::new(1.0, color);
let mut rect = response.rect; let mut rect = response.rect;
let corner = let corner =
@@ -365,7 +385,7 @@ impl BarWidget for Komorebi {
} else { } else {
let (response, painter) = let (response, painter) =
ui.allocate_painter(size, Sense::hover()); 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 stroke = Stroke::new(1.0, color);
let mut rect = response.rect; let mut rect = response.rect;
let corner = let corner =
@@ -448,72 +468,109 @@ impl BarWidget for Komorebi {
for (name, location) in configuration_switcher.configurations.iter() { for (name, location) in configuration_switcher.configurations.iter() {
let path = PathBuf::from(location); let path = PathBuf::from(location);
if path.is_file() { if path.is_file() {
config.apply_on_widget(false, ui,|ui|{ config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false).show(ui, |ui|{ if SelectableFrame::new(false)
ui.add(Label::new(name).selectable(false)) .show(ui, |ui| ui.add(Label::new(name).selectable(false)))
})
.clicked() .clicked()
{ {
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path); let canonicalized =
let mut proceed = true; dunce::canonicalize(path.clone()).unwrap_or(path);
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized, if komorebi_client::send_message(
)) &SocketMessage::ReplaceConfiguration(canonicalized),
)
.is_err() .is_err()
{ {
tracing::error!( tracing::error!(
"could not send message to komorebi: ReplaceConfiguration" "could not send message to komorebi: ReplaceConfiguration"
); );
proceed = false; }
}
});
}
}
}
} }
if let Some(rect) = komorebi_notification_state.work_area_offset { if let Some(locked_container_config) = self.locked_container {
if proceed { if locked_container_config.enable {
match komorebi_client::send_query(&SocketMessage::Query( let is_locked = komorebi_notification_state.focused_container_information.0;
komorebi_client::StateQuery::FocusedMonitorIndex,
)) { if locked_container_config
Ok(idx) => { .show_when_unlocked
if let Ok(monitor_idx) = idx.parse::<usize>() { .unwrap_or_default()
if komorebi_client::send_message( || is_locked
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
rect,
),
)
.is_err()
{ {
tracing::error!(
"could not send message to komorebi: MonitorWorkAreaOffset"
);
}
}
}
Err(_) => {
tracing::error!(
"could not send message to komorebi: Query"
);
}
}
}
}
}});
}
}
}
}
if let Some(focused_window) = self.focused_window {
if focused_window.enable {
let titles = &komorebi_notification_state let titles = &komorebi_notification_state
.focused_container_information .focused_container_information
.1
.titles; .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() { if !titles.is_empty() {
config.apply_on_widget(false, ui, |ui| { config.apply_on_widget(false, ui, |ui| {
let icons = &komorebi_notification_state let icons = &komorebi_notification_state
.focused_container_information .focused_container_information.1
.icons; .icons;
let focused_window_idx = komorebi_notification_state let focused_window_idx = komorebi_notification_state
.focused_container_information .focused_container_information.1
.focused_window_idx; .focused_window_idx;
let iter = titles.iter().zip(icons.iter()); let iter = titles.iter().zip(icons.iter());
@@ -521,13 +578,13 @@ impl BarWidget for Komorebi {
for (i, (title, icon)) in iter.enumerate() { for (i, (title, icon)) in iter.enumerate() {
let selected = i == focused_window_idx && len != 1; 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) if SelectableFrame::new(selected)
.show(ui, |ui| { .show(ui, |ui| {
// handle legacy setting // handle legacy setting
let format = focused_window.display.unwrap_or( let format = focused_container_config.display.unwrap_or(
if focused_window.show_icon.unwrap_or(false) { if focused_container_config.show_icon.unwrap_or(false) {
DisplayFormat::IconAndText DisplayFormat::IconAndText
} else { } else {
DisplayFormat::Text DisplayFormat::Text
@@ -613,7 +670,7 @@ impl BarWidget for Komorebi {
} }
} }
fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle { pub(super) fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
let size = [rgba_image.width() as usize, rgba_image.height() as usize]; let size = [rgba_image.width() as usize, rgba_image.height() as usize];
let pixels = rgba_image.as_flat_samples(); let pixels = rgba_image.as_flat_samples();
let color_image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()); let color_image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
@@ -627,9 +684,10 @@ pub struct KomorebiNotificationState {
String, String,
Vec<(bool, KomorebiNotificationStateContainerInformation)>, Vec<(bool, KomorebiNotificationStateContainerInformation)>,
WorkspaceLayer, WorkspaceLayer,
bool,
)>, )>,
pub selected_workspace: String, pub selected_workspace: String,
pub focused_container_information: KomorebiNotificationStateContainerInformation, pub focused_container_information: (bool, KomorebiNotificationStateContainerInformation),
pub layout: KomorebiLayout, pub layout: KomorebiLayout,
pub hide_empty_workspaces: bool, pub hide_empty_workspaces: bool,
pub mouse_follows_focus: bool, pub mouse_follows_focus: bool,
@@ -660,6 +718,7 @@ impl KomorebiNotificationState {
let show_all_icons = render_config.borrow().show_all_icons; let show_all_icons = render_config.borrow().show_all_icons;
match notification.event { match notification.event {
NotificationEvent::VirtualDesktop(_) => {}
NotificationEvent::WindowManager(_) => {} NotificationEvent::WindowManager(_) => {}
NotificationEvent::Monitor(_) => {} NotificationEvent::Monitor(_) => {}
NotificationEvent::Socket(message) => match message { NotificationEvent::Socket(message) => match message {
@@ -695,7 +754,7 @@ impl KomorebiNotificationState {
SocketMessage::Theme(theme) => { SocketMessage::Theme(theme) => {
apply_theme( apply_theme(
ctx, ctx,
KomobarTheme::from(theme), KomobarTheme::from(*theme),
bg_color, bg_color,
bg_color_with_alpha.clone(), bg_color_with_alpha.clone(),
transparency_alpha, transparency_alpha,
@@ -742,7 +801,6 @@ impl KomorebiNotificationState {
true true
}; };
if should_show {
workspaces.push(( workspaces.push((
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)), ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
if show_all_icons { if show_all_icons {
@@ -776,9 +834,9 @@ impl KomorebiNotificationState {
vec![(true, ws.into())] vec![(true, ws.into())]
}, },
ws.layer().to_owned(), ws.layer().to_owned(),
should_show,
)); ));
} }
}
self.workspaces = workspaces; self.workspaces = workspaces;
@@ -798,7 +856,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());
} }
} }
+1 -1
View File
@@ -251,7 +251,7 @@ impl KomorebiLayout {
let layout_frame = SelectableFrame::new(false) let layout_frame = SelectableFrame::new(false)
.show(ui, |ui| { .show(ui, |ui| {
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format { 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 { if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
+29 -9
View File
@@ -25,6 +25,8 @@ pub struct MemoryConfig {
pub data_refresh_interval: Option<u64>, pub data_refresh_interval: Option<u64>,
/// Display label prefix /// Display label prefix
pub label_prefix: Option<LabelPrefix>, pub label_prefix: Option<LabelPrefix>,
/// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>,
} }
impl From<MemoryConfig> for Memory { impl From<MemoryConfig> for Memory {
@@ -38,6 +40,7 @@ impl From<MemoryConfig> for Memory {
), ),
data_refresh_interval, data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), 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() last_updated: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval)) .checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(), .unwrap(),
@@ -45,16 +48,23 @@ impl From<MemoryConfig> for Memory {
} }
} }
#[derive(Clone, Debug)]
struct MemoryOutput {
label: String,
selected: bool,
}
pub struct Memory { pub struct Memory {
pub enable: bool, pub enable: bool,
system: System, system: System,
data_refresh_interval: u64, data_refresh_interval: u64,
label_prefix: LabelPrefix, label_prefix: LabelPrefix,
auto_select_over: Option<u8>,
last_updated: Instant, last_updated: Instant,
} }
impl Memory { impl Memory {
fn output(&mut self) -> String { fn output(&mut self) -> MemoryOutput {
let now = Instant::now(); let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.system.refresh_memory(); self.system.refresh_memory();
@@ -63,11 +73,17 @@ impl Memory {
let used = self.system.used_memory(); let used = self.system.used_memory();
let total = self.system.total_memory(); let total = self.system.total_memory();
match self.label_prefix { 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 => { LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("RAM: {}%", (used * 100) / total) format!("RAM: {}%", usage)
} }
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total), 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) { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable { if self.enable {
let output = self.output(); 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( let mut layout_job = LayoutJob::simple(
match self.label_prefix { match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => { LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -85,23 +103,25 @@ impl BarWidget for Memory {
LabelPrefix::None | LabelPrefix::Text => String::new(), LabelPrefix::None | LabelPrefix::Text => String::new(),
}, },
config.icon_font_id.clone(), config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color, auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0, 100.0,
); );
layout_job.append( layout_job.append(
&output, &output.label,
10.0, 10.0,
TextFormat { TextFormat {
font_id: config.text_font_id.clone(), 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, valign: Align::Center,
..Default::default() ..Default::default()
}, },
); );
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| { 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))) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked() .clicked()
{ {
+1
View File
@@ -1,3 +1,4 @@
pub mod applications;
pub mod battery; pub mod battery;
pub mod cpu; pub mod cpu;
pub mod date; pub mod date;
+244 -80
View File
@@ -1,9 +1,11 @@
use crate::bar::Alignment;
use crate::config::LabelPrefix; use crate::config::LabelPrefix;
use crate::render::RenderConfig; use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame; use crate::selected_frame::SelectableFrame;
use crate::widgets::widget::BarWidget; use crate::widgets::widget::BarWidget;
use eframe::egui::text::LayoutJob; use eframe::egui::text::LayoutJob;
use eframe::egui::Align; use eframe::egui::Align;
use eframe::egui::Color32;
use eframe::egui::Context; use eframe::egui::Context;
use eframe::egui::Label; use eframe::egui::Label;
use eframe::egui::TextFormat; use eframe::egui::TextFormat;
@@ -22,18 +24,36 @@ use sysinfo::Networks;
pub struct NetworkConfig { pub struct NetworkConfig {
/// Enable the Network widget /// Enable the Network widget
pub enable: bool, pub enable: bool,
/// Show total data transmitted /// Show total received and transmitted activity
pub show_total_data_transmitted: bool, #[serde(alias = "show_total_data_transmitted")]
/// Show network activity pub show_total_activity: bool,
pub show_network_activity: bool, /// Show received and transmitted activity
#[serde(alias = "show_network_activity")]
pub show_activity: bool,
/// Show default interface /// Show default interface
pub show_default_interface: Option<bool>, pub show_default_interface: Option<bool>,
/// Characters to reserve for network activity data /// Characters to reserve for received and transmitted activity
pub network_activity_fill_characters: Option<usize>, #[serde(alias = "network_activity_fill_characters")]
pub activity_left_padding: Option<usize>,
/// Data refresh interval (default: 10 seconds) /// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>, pub data_refresh_interval: Option<u64>,
/// Display label prefix /// Display label prefix
pub label_prefix: Option<LabelPrefix>, pub label_prefix: Option<LabelPrefix>,
/// 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 { impl From<NetworkConfig> for Network {
@@ -42,16 +62,15 @@ impl From<NetworkConfig> for Network {
Self { Self {
enable: value.enable, enable: value.enable,
show_total_activity: value.show_total_data_transmitted, show_total_activity: value.show_total_activity,
show_activity: value.show_network_activity, show_activity: value.show_activity,
show_default_interface: value.show_default_interface.unwrap_or(true), show_default_interface: value.show_default_interface.unwrap_or(true),
networks_network_activity: Networks::new_with_refreshed_list(), networks_network_activity: Networks::new_with_refreshed_list(),
default_interface: String::new(), default_interface: String::new(),
data_refresh_interval, data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon), label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
network_activity_fill_characters: value auto_select: value.auto_select,
.network_activity_fill_characters activity_left_padding: value.activity_left_padding.unwrap_or_default(),
.unwrap_or_default(),
last_state_total_activity: vec![], last_state_total_activity: vec![],
last_state_activity: vec![], last_state_activity: vec![],
last_updated_network_activity: Instant::now() last_updated_network_activity: Instant::now()
@@ -69,11 +88,12 @@ pub struct Network {
networks_network_activity: Networks, networks_network_activity: Networks,
data_refresh_interval: u64, data_refresh_interval: u64,
label_prefix: LabelPrefix, label_prefix: LabelPrefix,
auto_select: Option<NetworkSelectConfig>,
default_interface: String, default_interface: String,
last_state_total_activity: Vec<NetworkReading>, last_state_total_activity: Vec<NetworkReading>,
last_state_activity: Vec<NetworkReading>, last_state_activity: Vec<NetworkReading>,
last_updated_network_activity: Instant, last_updated_network_activity: Instant,
network_activity_fill_characters: usize, activity_left_padding: usize,
} }
impl Network { impl Network {
@@ -105,24 +125,32 @@ impl Network {
for (interface_name, data) in &self.networks_network_activity { for (interface_name, data) in &self.networks_network_activity {
if friendly_name.eq(interface_name) { if friendly_name.eq(interface_name) {
if self.show_activity { if self.show_activity {
activity.push(NetworkReading::new( let received = Self::to_pretty_bytes(
NetworkReadingFormat::Speed,
Self::to_pretty_bytes(
data.received(), data.received(),
self.data_refresh_interval, self.data_refresh_interval,
), );
Self::to_pretty_bytes( let transmitted = Self::to_pretty_bytes(
data.transmitted(), data.transmitted(),
self.data_refresh_interval, self.data_refresh_interval,
), );
activity.push(NetworkReading::new(
NetworkReadingFormat::Speed,
ReadingValue::from(received),
ReadingValue::from(transmitted),
)); ));
} }
if self.show_total_activity { 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( total_activity.push(NetworkReading::new(
NetworkReadingFormat::Total, NetworkReadingFormat::Total,
Self::to_pretty_bytes(data.total_received(), 1), ReadingValue::from(total_received),
Self::to_pretty_bytes(data.total_transmitted(), 1), ReadingValue::from(total_transmitted),
)) ))
} }
} }
@@ -138,105 +166,121 @@ impl Network {
(activity, total_activity) (activity, total_activity)
} }
fn reading_to_label( fn reading_to_labels(
&self, &self,
select_received: bool,
select_transmitted: bool,
ctx: &Context, ctx: &Context,
reading: NetworkReading, reading: &NetworkReading,
config: RenderConfig, config: RenderConfig,
) -> Label { ) -> (Label, Label) {
let (text_down, text_up) = match self.label_prefix { let (text_down, text_up) = match self.label_prefix {
LabelPrefix::None | LabelPrefix::Icon => match reading.format { LabelPrefix::None | LabelPrefix::Icon => match reading.format {
NetworkReadingFormat::Speed => ( NetworkReadingFormat::Speed => (
format!( format!(
"{: >width$}/s ", "{: >width$}/s ",
reading.received_text, reading.received.pretty,
width = self.network_activity_fill_characters width = self.activity_left_padding
), ),
format!( format!(
"{: >width$}/s", "{: >width$}/s",
reading.transmitted_text, reading.transmitted.pretty,
width = self.network_activity_fill_characters width = self.activity_left_padding
), ),
), ),
NetworkReadingFormat::Total => ( NetworkReadingFormat::Total => (
format!("{} ", reading.received_text), format!("{} ", reading.received.pretty),
reading.transmitted_text, reading.transmitted.pretty.clone(),
), ),
}, },
LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format { LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {
NetworkReadingFormat::Speed => ( NetworkReadingFormat::Speed => (
format!( format!(
"DOWN: {: >width$}/s ", "DOWN: {: >width$}/s ",
reading.received_text, reading.received.pretty,
width = self.network_activity_fill_characters width = self.activity_left_padding
), ),
format!( format!(
"UP: {: >width$}/s", "UP: {: >width$}/s",
reading.transmitted_text, reading.transmitted.pretty,
width = self.network_activity_fill_characters width = self.activity_left_padding
), ),
), ),
NetworkReadingFormat::Total => ( NetworkReadingFormat::Total => (
format!("\u{2211}DOWN: {}/s ", reading.received_text), format!("\u{2211}DOWN: {}/s ", reading.received.pretty),
format!("\u{2211}UP: {}/s", reading.transmitted_text), format!("\u{2211}UP: {}/s", reading.transmitted.pretty),
), ),
}, },
}; };
let icon_format = TextFormat::simple( let auto_text_color_received = config.auto_select_text.filter(|_| select_received);
config.icon_font_id.clone(), let auto_text_color_transmitted = config.auto_select_text.filter(|_| select_transmitted);
ctx.style().visuals.selection.stroke.color,
);
let text_format = TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
valign: Align::Center,
..Default::default()
};
// icon // icon
let mut layout_job = LayoutJob::simple( let mut layout_job_down = LayoutJob::simple(
match self.label_prefix { match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => { LabelPrefix::Icon | LabelPrefix::IconAndText => {
if select_received {
egui_phosphor::regular::ARROW_FAT_LINES_DOWN.to_string()
} else {
egui_phosphor::regular::ARROW_FAT_DOWN.to_string() egui_phosphor::regular::ARROW_FAT_DOWN.to_string()
} }
}
LabelPrefix::None | LabelPrefix::Text => String::new(), LabelPrefix::None | LabelPrefix::Text => String::new(),
}, },
icon_format.font_id.clone(), config.icon_font_id.clone(),
icon_format.color, auto_text_color_received.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0, 100.0,
); );
// text // text
layout_job.append( layout_job_down.append(
&text_down, &text_down,
ctx.style().spacing.item_spacing.x, 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 // icon
layout_job.append( let mut layout_job_up = LayoutJob::simple(
&match self.label_prefix { match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => { LabelPrefix::Icon | LabelPrefix::IconAndText => {
if select_transmitted {
egui_phosphor::regular::ARROW_FAT_LINES_UP.to_string()
} else {
egui_phosphor::regular::ARROW_FAT_UP.to_string() egui_phosphor::regular::ARROW_FAT_UP.to_string()
} }
}
LabelPrefix::None | LabelPrefix::Text => String::new(), LabelPrefix::None | LabelPrefix::Text => String::new(),
}, },
0.0, config.icon_font_id.clone(),
icon_format.clone(), auto_text_color_transmitted.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
); );
// text // text
layout_job.append( layout_job_up.append(
&text_up, &text_up,
ctx.style().spacing.item_spacing.x, 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 input = input_in_bytes as f32 / timespan_in_s as f32;
let mut magnitude = input.log(1024f32) as u32; let mut magnitude = input.log(1024f32) as u32;
@@ -248,10 +292,30 @@ impl Network {
let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude); let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);
let result = input / ((1u64) << (magnitude * 10)) as f32; let result = input / ((1u64) << (magnitude * 10)) as f32;
(
input as u64,
match base { match base {
Some(DataUnit::B) => format!("{result:.1} B"), Some(DataUnit::B) => format!("{result:.1} B"),
Some(unit) => format!("{result:.1} {unit}iB"), Some(unit) => format!("{result:.1} {unit}iB"),
None => String::from("Unknown data unit"), 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 { impl BarWidget for Network {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable { if self.enable {
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 // widget spacing: make sure to use the same config to call the apply_on_widget function
let mut render_config = config.clone(); let mut render_config = config.clone();
@@ -266,17 +332,102 @@ impl BarWidget for Network {
let (activity, total_activity) = self.network_activity(); let (activity, total_activity) = self.network_activity();
if self.show_total_activity { if self.show_total_activity {
for reading in total_activity { for reading in &total_activity {
render_config.apply_on_widget(true, ui, |ui| { render_config.apply_on_widget(false, ui, |ui| {
ui.add(self.reading_to_label(ctx, reading, config.clone())); 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 { if self.show_activity {
for reading in activity { for reading in &activity {
render_config.apply_on_widget(true, ui, |ui| { render_config.apply_on_widget(false, ui, |ui| {
ui.add(self.reading_to_label(ctx, reading, config.clone())); 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| { render_config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false) self.show_frame(false, None, ui, |ui| {
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) ui.add(Label::new(layout_job).selectable(false))
.clicked() });
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn()
{
eprintln!("{}", error)
}
}
}); });
} }
} }
@@ -339,19 +484,38 @@ enum NetworkReadingFormat {
Total = 1, 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)] #[derive(Clone)]
struct NetworkReading { struct NetworkReading {
pub format: NetworkReadingFormat, format: NetworkReadingFormat,
pub received_text: String, received: ReadingValue,
pub transmitted_text: String, transmitted: ReadingValue,
} }
impl NetworkReading { impl NetworkReading {
pub fn new(format: NetworkReadingFormat, received: String, transmitted: String) -> Self { fn new(
NetworkReading { format: NetworkReadingFormat,
received: ReadingValue,
transmitted: ReadingValue,
) -> Self {
Self {
format, format,
received_text: received, received,
transmitted_text: transmitted, transmitted,
} }
} }
} }
+46 -12
View File
@@ -1,3 +1,4 @@
use crate::bar::Alignment;
use crate::config::LabelPrefix; use crate::config::LabelPrefix;
use crate::render::RenderConfig; use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame; use crate::selected_frame::SelectableFrame;
@@ -24,6 +25,10 @@ pub struct StorageConfig {
pub data_refresh_interval: Option<u64>, pub data_refresh_interval: Option<u64>,
/// Display label prefix /// Display label prefix
pub label_prefix: Option<LabelPrefix>, pub label_prefix: Option<LabelPrefix>,
/// 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 { impl From<StorageConfig> for Storage {
@@ -33,21 +38,30 @@ impl From<StorageConfig> for Storage {
disks: Disks::new_with_refreshed_list(), disks: Disks::new_with_refreshed_list(),
data_refresh_interval: value.data_refresh_interval.unwrap_or(10), data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
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(), last_updated: Instant::now(),
} }
} }
} }
struct StorageDisk {
label: String,
selected: bool,
}
pub struct Storage { pub struct Storage {
pub enable: bool, pub enable: bool,
disks: Disks, disks: Disks,
data_refresh_interval: u64, data_refresh_interval: u64,
label_prefix: LabelPrefix, label_prefix: LabelPrefix,
auto_select_over: Option<u8>,
auto_hide_under: Option<u8>,
last_updated: Instant, last_updated: Instant,
} }
impl Storage { impl Storage {
fn output(&mut self) -> Vec<String> { fn output(&mut self) -> Vec<StorageDisk> {
let now = Instant::now(); let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.disks.refresh(true); self.disks.refresh(true);
@@ -61,17 +75,26 @@ impl Storage {
let total = disk.total_space(); let total = disk.total_space();
let available = disk.available_space(); let available = disk.available_space();
let used = total - available; let used = total - available;
let percentage = ((used * 100) / total) as u8;
disks.push(match self.label_prefix { 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 => { LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("{} {}%", mount.to_string_lossy(), (used * 100) / total) format!("{} {}%", mount.to_string_lossy(), percentage)
} }
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total), LabelPrefix::None | LabelPrefix::Icon => format!("{}%", percentage),
},
selected,
}) })
} }
}
disks.sort(); disks.sort_by(|a, b| a.label.cmp(&b.label));
disks.reverse();
disks disks
} }
@@ -80,7 +103,16 @@ impl Storage {
impl BarWidget for Storage { impl BarWidget for Storage {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable { if self.enable {
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( let mut layout_job = LayoutJob::simple(
match self.label_prefix { match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => { LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -89,23 +121,25 @@ impl BarWidget for Storage {
LabelPrefix::None | LabelPrefix::Text => String::new(), LabelPrefix::None | LabelPrefix::Text => String::new(),
}, },
config.icon_font_id.clone(), config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color, auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0, 100.0,
); );
layout_job.append( layout_job.append(
&output, &output.label,
10.0, 10.0,
TextFormat { TextFormat {
font_id: config.text_font_id.clone(), 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, valign: Align::Center,
..Default::default() ..Default::default()
}, },
); );
let auto_focus_fill = config.auto_select_fill;
config.apply_on_widget(false, ui, |ui| { 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))) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
.clicked() .clicked()
{ {
@@ -113,7 +147,7 @@ impl BarWidget for Storage {
.args([ .args([
"/C", "/C",
"explorer.exe", "explorer.exe",
output.split(' ').collect::<Vec<&str>>()[0], output.label.split(' ').collect::<Vec<&str>>()[0],
]) ])
.spawn() .spawn()
{ {
+6 -1
View File
@@ -1,4 +1,6 @@
use crate::render::RenderConfig; use crate::render::RenderConfig;
use crate::widgets::applications::Applications;
use crate::widgets::applications::ApplicationsConfig;
use crate::widgets::battery::Battery; use crate::widgets::battery::Battery;
use crate::widgets::battery::BatteryConfig; use crate::widgets::battery::BatteryConfig;
use crate::widgets::cpu::Cpu; use crate::widgets::cpu::Cpu;
@@ -33,6 +35,7 @@ pub trait BarWidget {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WidgetConfig { pub enum WidgetConfig {
Applications(ApplicationsConfig),
Battery(BatteryConfig), Battery(BatteryConfig),
Cpu(CpuConfig), Cpu(CpuConfig),
Date(DateConfig), Date(DateConfig),
@@ -49,6 +52,7 @@ pub enum WidgetConfig {
impl WidgetConfig { impl WidgetConfig {
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> { pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
match self { match self {
WidgetConfig::Applications(config) => Box::new(Applications::from(config)),
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)), WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)), WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())), WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
@@ -65,6 +69,7 @@ impl WidgetConfig {
pub fn enabled(&self) -> bool { pub fn enabled(&self) -> bool {
match self { match self {
WidgetConfig::Applications(config) => config.enable,
WidgetConfig::Battery(config) => config.enable, WidgetConfig::Battery(config) => config.enable,
WidgetConfig::Cpu(config) => config.enable, WidgetConfig::Cpu(config) => config.enable,
WidgetConfig::Date(config) => config.enable, WidgetConfig::Date(config) => config.enable,
@@ -72,7 +77,7 @@ impl WidgetConfig {
WidgetConfig::Komorebi(config) => { WidgetConfig::Komorebi(config) => {
config.workspaces.as_ref().is_some_and(|w| w.enable) config.workspaces.as_ref().is_some_and(|w| w.enable)
|| config.layout.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 || config
.configuration_switcher .configuration_switcher
.as_ref() .as_ref()
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-client" name = "komorebi-client"
version = "0.1.36" version = "0.1.37"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -3
View File
@@ -5,8 +5,6 @@ pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::animation::PerAnimationPrefixConfig; pub use komorebi::animation::PerAnimationPrefixConfig;
pub use komorebi::asc::ApplicationSpecificConfiguration; pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::border_manager::BorderInfo; pub use komorebi::border_manager::BorderInfo;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
pub use komorebi::config_generation::ApplicationConfiguration; pub use komorebi::config_generation::ApplicationConfiguration;
pub use komorebi::config_generation::IdWithIdentifier; pub use komorebi::config_generation::IdWithIdentifier;
pub use komorebi::config_generation::IdWithIdentifierAndComment; 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::config_generation::MatchingStrategy;
pub use komorebi::container::Container; pub use komorebi::container::Container;
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator; 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::AnimationStyle;
pub use komorebi::core::ApplicationIdentifier; pub use komorebi::core::ApplicationIdentifier;
pub use komorebi::core::Arrangement; pub use komorebi::core::Arrangement;
@@ -57,6 +55,7 @@ pub use komorebi::AnimationsConfig;
pub use komorebi::AppSpecificConfigurationPath; pub use komorebi::AppSpecificConfigurationPath;
pub use komorebi::AspectRatio; pub use komorebi::AspectRatio;
pub use komorebi::BorderColours; pub use komorebi::BorderColours;
pub use komorebi::Colour;
pub use komorebi::CrossBoundaryBehaviour; pub use komorebi::CrossBoundaryBehaviour;
pub use komorebi::GlobalState; pub use komorebi::GlobalState;
pub use komorebi::KomorebiTheme; pub use komorebi::KomorebiTheme;
@@ -64,12 +63,14 @@ pub use komorebi::MonitorConfig;
pub use komorebi::Notification; pub use komorebi::Notification;
pub use komorebi::NotificationEvent; pub use komorebi::NotificationEvent;
pub use komorebi::PredefinedAspectRatio; pub use komorebi::PredefinedAspectRatio;
pub use komorebi::Rgb;
pub use komorebi::RuleDebug; pub use komorebi::RuleDebug;
pub use komorebi::StackbarConfig; pub use komorebi::StackbarConfig;
pub use komorebi::State; pub use komorebi::State;
pub use komorebi::StaticConfig; pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions; pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig; pub use komorebi::TabsConfig;
pub use komorebi::VirtualDesktopNotification;
pub use komorebi::WindowContainerBehaviour; pub use komorebi::WindowContainerBehaviour;
pub use komorebi::WindowsApi; pub use komorebi::WindowsApi;
pub use komorebi::WorkspaceConfig; pub use komorebi::WorkspaceConfig;
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-gui" name = "komorebi-gui"
version = "0.1.36" version = "0.1.37"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+36
View File
@@ -41,7 +41,9 @@ struct BorderColours {
single: Color32, single: Color32,
stack: Color32, stack: Color32,
monocle: Color32, monocle: Color32,
floating: Color32,
unfocused: Color32, unfocused: Color32,
unfocused_locked: Color32,
} }
struct BorderConfig { struct BorderConfig {
@@ -154,7 +156,9 @@ impl KomorebiGui {
single: colour32(global_state.border_colours.single), single: colour32(global_state.border_colours.single),
stack: colour32(global_state.border_colours.stack), stack: colour32(global_state.border_colours.stack),
monocle: colour32(global_state.border_colours.monocle), monocle: colour32(global_state.border_colours.monocle),
floating: colour32(global_state.border_colours.floating),
unfocused: colour32(global_state.border_colours.unfocused), unfocused: colour32(global_state.border_colours.unfocused),
unfocused_locked: colour32(global_state.border_colours.unfocused_locked),
}; };
let border_config = BorderConfig { 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| { ui.collapsing("Unfocused", |ui| {
if egui::color_picker::color_picker_color32( if egui::color_picker::color_picker_color32(
ui, ui,
@@ -391,6 +411,22 @@ impl eframe::App for KomorebiGui {
)) ))
.unwrap(); .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();
}
}) })
}); });
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "komorebi-shortcuts"
version = "0.1.0"
edition = "2024"
[dependencies]
whkd-parser = { git = "https://github.com/LGUG2Z/whkd", rev = "29df24ff2dd715655b0366bd2a598837c699a8e9" }
whkd-core = { git = "https://github.com/LGUG2Z/whkd", rev = "29df24ff2dd715655b0366bd2a598837c699a8e9" }
eframe = { workspace = true }
dirs = { workspace = true }
+106
View File
@@ -0,0 +1,106 @@
use eframe::egui::ViewportBuilder;
use std::path::PathBuf;
use whkd_core::HotkeyBinding;
use whkd_core::Whkdrc;
#[derive(Default)]
struct Quicklook {
whkdrc: Option<Whkdrc>,
filter: String,
}
impl Quicklook {
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
// Restore app state using cc.storage (requires the "persistence" feature).
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
// for e.g. egui::PaintCallback.
let mut home = std::env::var("WHKD_CONFIG_HOME").map_or_else(
|_| {
dirs::home_dir()
.expect("no home directory found")
.join(".config")
},
|home_path| {
let home = PathBuf::from(&home_path);
if home.as_path().is_dir() {
home
} else {
panic!(
"$Env:WHKD_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
);
}
},
);
home.push("whkdrc");
Self {
whkdrc: whkd_parser::load(&home).ok(),
filter: String::new(),
}
}
}
impl eframe::App for Quicklook {
fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {
eframe::egui::CentralPanel::default().show(ctx, |ui| {
ui.set_max_width(ui.available_width());
ui.set_max_height(ui.available_height());
eframe::egui::ScrollArea::vertical().show(ui, |ui| {
eframe::egui::Grid::new("grid")
.num_columns(2)
.striped(true)
.spacing([40.0, 4.0])
.min_col_width(ui.available_width() / 2.0 - 20.0)
.show(ui, |ui| {
if let Some(whkdrc) = &self.whkdrc {
ui.label("Filter");
ui.add(
eframe::egui::text_edit::TextEdit::singleline(&mut self.filter)
.background_color(ctx.style().visuals.faint_bg_color),
);
ui.end_row();
ui.end_row();
for binding in &whkdrc.bindings {
if is_komorebic_binding(binding) {
let keys = binding.keys.join(" + ");
if self.filter.is_empty()
|| binding.command.contains(&self.filter)
{
ui.label(keys);
ui.label(&binding.command);
ui.end_row();
}
}
}
}
});
});
});
}
}
fn main() {
let viewport_builder = ViewportBuilder::default()
.with_resizable(true)
.with_decorations(false);
let native_options = eframe::NativeOptions {
viewport: viewport_builder,
centered: true,
..Default::default()
};
eframe::run_native(
"komorebi-shortcuts",
native_options,
Box::new(|cc| Ok(Box::new(Quicklook::new(cc)))),
)
.unwrap();
}
fn is_komorebic_binding(binding: &HotkeyBinding) -> bool {
binding.command.starts_with("komorebic")
}
+8 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi-themes" name = "komorebi-themes"
version = "0.1.36" version = "0.1.37"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
@@ -8,7 +8,13 @@ base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] } catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] } #catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
eframe = { workspace = true } eframe = { workspace = true }
schemars = { workspace = true } schemars = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
serde_variant = "0.1" serde_variant = "0.1"
strum = { workspace = true } strum = { workspace = true }
hex_color = { version = "3", features = ["serde"] }
flavours = { git = "https://github.com/LGUG2Z/flavours", version = "0.7.2" }
[features]
default = ["schemars"]
schemars = ["dep:schemars"]
@@ -1,5 +1,4 @@
use hex_color::HexColor; use hex_color::HexColor;
use komorebi_themes::Color32;
#[cfg(feature = "schemars")] #[cfg(feature = "schemars")]
use schemars::gen::SchemaGenerator; use schemars::gen::SchemaGenerator;
#[cfg(feature = "schemars")] #[cfg(feature = "schemars")]
@@ -9,6 +8,7 @@ use schemars::schema::Schema;
#[cfg(feature = "schemars")] #[cfg(feature = "schemars")]
use schemars::schema::SchemaObject; use schemars::schema::SchemaObject;
use crate::Color32;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -57,7 +57,7 @@ impl From<Colour> for Color32 {
} }
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub struct Hex(HexColor); pub struct Hex(pub HexColor);
#[cfg(feature = "schemars")] #[cfg(feature = "schemars")]
impl schemars::JsonSchema for Hex { impl schemars::JsonSchema for Hex {
+77
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])?)),
})
}
}
+177 -3
View File
@@ -1,18 +1,32 @@
#![warn(clippy::all)] #![warn(clippy::all)]
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
pub mod colour;
mod generator;
pub use generator::generate_base16_palette;
pub use generator::ThemeVariant;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use strum::Display; use strum::Display;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::colour::Colour;
pub use base16_egui_themes::Base16; pub use base16_egui_themes::Base16;
pub use catppuccin_egui; pub use catppuccin_egui;
use eframe::egui::style::Selection;
use eframe::egui::style::WidgetVisuals;
use eframe::egui::style::Widgets;
pub use eframe::egui::Color32; pub use eframe::egui::Color32;
use eframe::egui::Shadow;
use eframe::egui::Stroke;
use eframe::egui::Style;
use eframe::egui::Visuals;
use serde_variant::to_variant_name; use serde_variant::to_variant_name;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Theme { pub enum Theme {
/// A theme from catppuccin-egui /// A theme from catppuccin-egui
@@ -25,6 +39,140 @@ pub enum Theme {
name: Base16, name: Base16,
accent: Option<Base16Value>, 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 { impl Theme {
@@ -45,6 +193,7 @@ impl Theme {
.to_string() .to_string()
}) })
.collect(), .collect(),
Theme::Custom { .. } => vec!["Custom".to_string()],
} }
} }
} }
@@ -70,9 +219,15 @@ pub enum Base16Value {
Base0F, Base0F,
} }
pub enum Base16Wrapper {
Base16(Base16),
Custom(Box<Base16ColourPalette>),
}
impl Base16Value { impl Base16Value {
pub fn color32(&self, theme: Base16) -> Color32 { pub fn color32(&self, theme: Base16Wrapper) -> Color32 {
match self { match theme {
Base16Wrapper::Base16(theme) => match self {
Base16Value::Base00 => theme.base00(), Base16Value::Base00 => theme.base00(),
Base16Value::Base01 => theme.base01(), Base16Value::Base01 => theme.base01(),
Base16Value::Base02 => theme.base02(), Base16Value::Base02 => theme.base02(),
@@ -89,6 +244,25 @@ impl Base16Value {
Base16Value::Base0D => theme.base0d(), Base16Value::Base0D => theme.base0d(),
Base16Value::Base0E => theme.base0e(), Base16Value::Base0E => theme.base0e(),
Base16Value::Base0F => theme.base0f(), 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(),
},
} }
} }
} }
+3 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebi" name = "komorebi"
version = "0.1.36" version = "0.1.37"
description = "A tiling window manager for Windows" description = "A tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi" repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021" edition = "2021"
@@ -19,7 +19,6 @@ ctrlc = { version = "3", features = ["termination"] }
dirs = { workspace = true } dirs = { workspace = true }
dunce = { workspace = true } dunce = { workspace = true }
getset = "0.1" getset = "0.1"
hex_color = { version = "3", features = ["serde"] }
hotwatch = { workspace = true } hotwatch = { workspace = true }
lazy_static = { workspace = true } lazy_static = { workspace = true }
miow = "0.6" miow = "0.6"
@@ -28,6 +27,7 @@ net2 = "0.2"
os_info = "3.10" os_info = "3.10"
parking_lot = "0.12" parking_lot = "0.12"
paste = { workspace = true } paste = { workspace = true }
powershell_script = "1.0"
regex = "1" regex = "1"
schemars = { workspace = true, optional = true } schemars = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
@@ -49,6 +49,7 @@ windows-implement = { workspace = true }
windows-interface = { workspace = true } windows-interface = { workspace = true }
winput = "0.2" winput = "0.2"
winreg = "0.55" winreg = "0.55"
serde_with = { version = "3.12", features = ["schemars_0_8"] }
[build-dependencies] [build-dependencies]
shadow-rs = { workspace = true } shadow-rs = { workspace = true }
+56
View File
@@ -355,6 +355,61 @@ impl Ease for EaseInOutBounce {
} }
} }
pub struct CubicBezier {
pub x1: f64,
pub y1: f64,
pub x2: f64,
pub y2: f64,
}
impl CubicBezier {
fn x(&self, s: f64) -> f64 {
3.0 * self.x1 * s * (1.0 - s).powi(2) + 3.0 * self.x2 * s.powi(2) * (1.0 - s) + s.powi(3)
}
fn y(&self, s: f64) -> f64 {
3.0 * self.y1 * s * (1.0 - s).powi(2) + 3.0 * self.y2 * s.powi(2) * (1.0 - s) + s.powi(3)
}
fn dx_ds(&self, s: f64) -> f64 {
3.0 * self.x1 * (1.0 - s) * (1.0 - 3.0 * s)
+ 3.0 * self.x2 * (2.0 * s - 3.0 * s.powi(2))
+ 3.0 * s.powi(2)
}
fn find_s(&self, t: f64) -> f64 {
if t <= 0.0 {
return 0.0;
}
if t >= 1.0 {
return 1.0;
}
let mut s = t;
for _ in 0..8 {
let x_val = self.x(s);
let dx_val = self.dx_ds(s);
if dx_val.abs() < 1e-6 {
break;
}
let delta = (x_val - t) / dx_val;
s = (s - delta).clamp(0.0, 1.0);
if delta.abs() < 1e-6 {
break;
}
}
s
}
fn evaluate(&self, t: f64) -> f64 {
let s = self.find_s(t.clamp(0.0, 1.0));
self.y(s)
}
}
pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 { pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
match style { match style {
AnimationStyle::Linear => Linear::evaluate(t), AnimationStyle::Linear => Linear::evaluate(t),
@@ -387,5 +442,6 @@ pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
AnimationStyle::EaseInBounce => EaseInBounce::evaluate(t), AnimationStyle::EaseInBounce => EaseInBounce::evaluate(t),
AnimationStyle::EaseOutBounce => EaseOutBounce::evaluate(t), AnimationStyle::EaseOutBounce => EaseOutBounce::evaluate(t),
AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t), AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t),
AnimationStyle::CubicBezier(x1, y1, x2, y2) => CubicBezier { x1, y1, x2, y2 }.evaluate(t),
} }
} }
+19 -21
View File
@@ -9,13 +9,11 @@ use crate::core::Rect;
use crate::windows_api; use crate::windows_api;
use crate::WindowsApi; use crate::WindowsApi;
use crate::WINDOWS_11; use crate::WINDOWS_11;
use color_eyre::eyre::anyhow;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::LazyLock; use std::sync::LazyLock;
use std::sync::OnceLock;
use windows::Win32::Foundation::FALSE; use windows::Win32::Foundation::FALSE;
use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM; use windows::Win32::Foundation::LPARAM;
@@ -118,7 +116,7 @@ pub struct Border {
pub hwnd: isize, pub hwnd: isize,
pub id: String, pub id: String,
pub monitor_idx: Option<usize>, pub monitor_idx: Option<usize>,
pub render_target: OnceLock<RenderTarget>, pub render_target: Option<RenderTarget>,
pub tracking_hwnd: isize, pub tracking_hwnd: isize,
pub window_rect: Rect, pub window_rect: Rect,
pub window_kind: WindowKind, pub window_kind: WindowKind,
@@ -136,7 +134,7 @@ impl From<isize> for Border {
hwnd: value, hwnd: value,
id: String::new(), id: String::new(),
monitor_idx: None, monitor_idx: None,
render_target: OnceLock::new(), render_target: None,
tracking_hwnd: 0, tracking_hwnd: 0,
window_rect: Rect::default(), window_rect: Rect::default(),
window_kind: WindowKind::Unfocused, window_kind: WindowKind::Unfocused,
@@ -184,7 +182,7 @@ impl Border {
hwnd: 0, hwnd: 0,
id: container_id, id: container_id,
monitor_idx: Some(monitor_idx), monitor_idx: Some(monitor_idx),
render_target: OnceLock::new(), render_target: None,
tracking_hwnd, tracking_hwnd,
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(), window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
window_kind: WindowKind::Unfocused, window_kind: WindowKind::Unfocused,
@@ -243,8 +241,14 @@ impl Border {
let _ = DwmEnableBlurBehindWindow(border.hwnd(), &bh); 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 { 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(), pixelSize: Default::default(),
presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY, presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,
}; };
@@ -265,7 +269,7 @@ impl Border {
.CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties) .CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties)
} { } {
Ok(render_target) => unsafe { Ok(render_target) => unsafe {
border.brush_properties = *BRUSH_PROPERTIES.deref(); self.brush_properties = *BRUSH_PROPERTIES.deref();
for window_kind in [ for window_kind in [
WindowKind::Single, WindowKind::Single,
WindowKind::Stack, WindowKind::Stack,
@@ -283,24 +287,18 @@ impl Border {
}; };
if let Ok(brush) = 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); render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
if border self.render_target = Some(RenderTarget(render_target));
.render_target
.set(RenderTarget(render_target.clone()))
.is_err()
{
return Err(anyhow!("could not store border render target"));
}
border.rounded_rect = { self.rounded_rect = {
let radius = 8.0 + border.width as f32 / 2.0; let radius = 8.0 + self.width as f32 / 2.0;
D2D1_ROUNDED_RECT { D2D1_ROUNDED_RECT {
rect: Default::default(), rect: Default::default(),
radiusX: radius, radiusX: radius,
@@ -308,7 +306,7 @@ impl Border {
} }
}; };
Ok(border) Ok(())
}, },
Err(error) => Err(error.into()), Err(error) => Err(error.into()),
} }
@@ -395,7 +393,7 @@ impl Border {
} }
if !rect.is_same_size_as(&old_rect) { 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_width = (*border_pointer).width;
let border_offset = (*border_pointer).offset; let border_offset = (*border_pointer).offset;
@@ -477,7 +475,7 @@ impl Border {
tracing::error!("failed to update border position {error}"); 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).width = BORDER_WIDTH.load(Ordering::Relaxed);
(*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed); (*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed);
+58 -15
View File
@@ -7,8 +7,6 @@ use crate::core::WindowKind;
use crate::ring::Ring; use crate::ring::Ring;
use crate::windows_api; use crate::windows_api;
use crate::workspace::WorkspaceLayer; use crate::workspace::WorkspaceLayer;
use crate::Colour;
use crate::Rgb;
use crate::WindowManager; use crate::WindowManager;
use crate::WindowsApi; use crate::WindowsApi;
use border::border_hwnds; use border::border_hwnds;
@@ -17,6 +15,8 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell; use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume; use crossbeam_utils::atomic::AtomicConsume;
use komorebi_themes::colour::Colour;
use komorebi_themes::colour::Rgb;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize; 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)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct BorderInfo { pub struct BorderInfo {
@@ -102,16 +105,21 @@ fn event_rx() -> Receiver<Notification> {
} }
pub fn window_border(hwnd: isize) -> Option<BorderInfo> { pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
WINDOWS_BORDERS.lock().get(&hwnd).and_then(|id| { let id = WINDOWS_BORDERS.lock().get(&hwnd)?.clone();
BORDER_STATE.lock().get(id).map(|b| BorderInfo { BORDER_STATE.lock().get(&id).map(|b| BorderInfo {
border_hwnd: b.hwnd, border_hwnd: b.hwnd,
window_kind: b.window_kind, window_kind: b.window_kind,
}) })
})
} }
pub fn send_notification(hwnd: Option<isize>) { 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") tracing::warn!("channel is full; dropping notification")
} }
} }
@@ -127,6 +135,8 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
let _ = destroy_border(border); let _ = destroy_border(border);
} }
drop(borders);
WINDOWS_BORDERS.lock().clear(); WINDOWS_BORDERS.lock().clear();
let mut remaining_hwnds = vec![]; let mut remaining_hwnds = vec![];
@@ -175,7 +185,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
tracing::info!("listening"); tracing::info!("listening");
let receiver = event_rx(); let receiver = event_rx();
event_tx().send(Notification(None))?; event_tx().send(Notification::Update(None))?;
let mut previous_snapshot = Ring::default(); let mut previous_snapshot = Ring::default();
let mut previous_pending_move_op = None; let mut previous_pending_move_op = None;
@@ -261,6 +271,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} }
} }
BorderImplementation::Komorebi => { BorderImplementation::Komorebi => {
let should_process_notification = match notification {
Notification::Update(notification_hwnd) => {
let mut should_process_notification = true; let mut should_process_notification = true;
if monitors == previous_snapshot if monitors == previous_snapshot
@@ -286,9 +298,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} }
// when we switch focus to/from a floating window // when we switch focus to/from a floating window
let switch_focus_to_from_floating_window = floating_window_hwnds.iter().any(|fw| { let switch_focus_to_from_floating_window =
floating_window_hwnds.iter().any(|fw| {
// if we switch focus to a floating window // if we switch focus to a floating window
fw == &notification.0.unwrap_or_default() || fw == &notification_hwnd.unwrap_or_default() ||
// if there is any floating window with a `WindowKind::Floating` border // if there is any floating window with a `WindowKind::Floating` border
// that no longer is the foreground window then we need to update that // that no longer is the foreground window then we need to update that
// border. // border.
@@ -302,7 +315,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// komorebi it will have the same state has before, however the previously focused // 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. // window changed its border to unfocused so now we need to update it again.
if !should_process_notification if !should_process_notification
&& window_border(notification.0.unwrap_or_default()) && window_border(notification_hwnd.unwrap_or_default())
.is_some_and(|b| b.window_kind == WindowKind::Unfocused) .is_some_and(|b| b.window_kind == WindowKind::Unfocused)
{ {
should_process_notification = true; should_process_notification = true;
@@ -313,13 +326,21 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} }
if !should_process_notification { if !should_process_notification {
if let Some(ref previous) = previous_notification { if let Some(Notification::Update(ref previous)) = previous_notification
if previous.0.unwrap_or_default() != notification.0.unwrap_or_default() { {
if previous.unwrap_or_default()
!= notification_hwnd.unwrap_or_default()
{
should_process_notification = true; should_process_notification = true;
} }
} }
} }
should_process_notification
}
Notification::ForceUpdate => true,
};
if !should_process_notification { if !should_process_notification {
tracing::trace!("monitor state matches latest snapshot, skipping notification"); tracing::trace!("monitor state matches latest snapshot, skipping notification");
continue 'receiver; continue 'receiver;
@@ -415,6 +436,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if new_border { if new_border {
border.set_position(&rect, focused_window_hwnd)?; 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(); border.invalidate();
@@ -544,12 +570,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_rect = rect; border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer; let layer_changed = previous_layer != workspace_layer;
let forced_update = matches!(notification, Notification::ForceUpdate);
let should_invalidate = new_border let should_invalidate = new_border
|| (last_focus_state != new_focus_state) || (last_focus_state != new_focus_state)
|| layer_changed; || layer_changed
|| forced_update;
if should_invalidate { 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.set_position(&rect, focused_window_hwnd)?;
border.invalidate(); border.invalidate();
} }
@@ -594,12 +628,21 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_rect = rect; border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer; let layer_changed = previous_layer != workspace_layer;
let forced_update =
matches!(notification, Notification::ForceUpdate);
let should_invalidate = new_border let should_invalidate = new_border
|| (last_focus_state != new_focus_state) || (last_focus_state != new_focus_state)
|| layer_changed; || layer_changed
|| forced_update;
if should_invalidate { 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.set_position(&rect, window.hwnd)?;
border.invalidate(); border.invalidate();
} }
+79 -1
View File
@@ -1,11 +1,12 @@
use clap::ValueEnum; use clap::ValueEnum;
use serde::ser::SerializeSeq;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use strum::Display; use strum::Display;
use strum::EnumString; use strum::EnumString;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)] #[derive(Copy, Clone, Debug, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum AnimationStyle { pub enum AnimationStyle {
Linear, Linear,
@@ -38,4 +39,81 @@ pub enum AnimationStyle {
EaseInBounce, EaseInBounce,
EaseOutBounce, EaseOutBounce,
EaseInOutBounce, EaseInOutBounce,
#[value(skip)]
CubicBezier(f64, f64, f64, f64),
}
// Custom serde implementation
impl<'de> Deserialize<'de> for AnimationStyle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct AnimationStyleVisitor;
impl<'de> serde::de::Visitor<'de> for AnimationStyleVisitor {
type Value = AnimationStyle;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string or an array of four f64 values")
}
// Handle string variants (e.g., "EaseInOutExpo")
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
value.parse().map_err(|_| E::unknown_variant(value, &[]))
}
// Handle CubicBezier array (e.g., [0.32, 0.72, 0.0, 1.0])
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let x1 = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let y1 = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
let x2 = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
let y2 = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(3, &self))?;
// Ensure no extra elements
if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {
return Err(serde::de::Error::invalid_length(5, &self));
}
Ok(AnimationStyle::CubicBezier(x1, y1, x2, y2))
}
}
deserializer.deserialize_any(AnimationStyleVisitor)
}
}
impl Serialize for AnimationStyle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
// Serialize CubicBezier as an array
AnimationStyle::CubicBezier(x1, y1, x2, y2) => {
let mut seq = serializer.serialize_seq(Some(4))?;
seq.serialize_element(x1)?;
seq.serialize_element(y1)?;
seq.serialize_element(x2)?;
seq.serialize_element(y2)?;
seq.end()
}
// Serialize all other variants as strings
_ => serializer.serialize_str(&self.to_string()),
}
}
} }
+127 -58
View File
@@ -1,12 +1,10 @@
#![warn(clippy::all)] #![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)] #![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use clap::ValueEnum; use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result; use color_eyre::Result;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -28,7 +26,10 @@ pub use default_layout::DefaultLayout;
pub use direction::Direction; pub use direction::Direction;
pub use layout::Layout; pub use layout::Layout;
pub use operation_direction::OperationDirection; pub use operation_direction::OperationDirection;
pub use pathext::replace_env_in_path;
pub use pathext::resolve_option_hashmap_usize_path;
pub use pathext::PathExt; pub use pathext::PathExt;
pub use pathext::ResolvedPathBuf;
pub use rect::Rect; pub use rect::Rect;
pub mod animation; pub mod animation;
@@ -44,6 +45,8 @@ pub mod operation_direction;
pub mod pathext; pub mod pathext;
pub mod rect; pub mod rect;
// serde_as must be before derive
#[serde_with::serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, Display)] #[derive(Clone, Debug, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "type", content = "content")] #[serde(tag = "type", content = "content")]
@@ -105,7 +108,7 @@ pub enum SocketMessage {
AdjustWorkspacePadding(Sizing, i32), AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout), ChangeLayout(DefaultLayout),
CycleLayout(CycleDirection), CycleLayout(CycleDirection),
ChangeLayoutCustom(PathBuf), ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
FlipLayout(Axis), FlipLayout(Axis),
ToggleWorkspaceWindowContainerBehaviour, ToggleWorkspaceWindowContainerBehaviour,
ToggleWorkspaceFloatOverride, ToggleWorkspaceFloatOverride,
@@ -123,8 +126,8 @@ pub enum SocketMessage {
RetileWithResizeDimensions, RetileWithResizeDimensions,
QuickSave, QuickSave,
QuickLoad, QuickLoad,
Save(PathBuf), Save(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
Load(PathBuf), Load(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
CycleFocusMonitor(CycleDirection), CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection), CycleFocusWorkspace(CycleDirection),
CycleFocusEmptyWorkspace(CycleDirection), CycleFocusEmptyWorkspace(CycleDirection),
@@ -147,23 +150,28 @@ pub enum SocketMessage {
WorkspaceName(usize, usize, String), WorkspaceName(usize, usize, String),
WorkspaceLayout(usize, usize, DefaultLayout), WorkspaceLayout(usize, usize, DefaultLayout),
NamedWorkspaceLayout(String, DefaultLayout), NamedWorkspaceLayout(String, DefaultLayout),
WorkspaceLayoutCustom(usize, usize, PathBuf), WorkspaceLayoutCustom(usize, usize, #[serde_as(as = "ResolvedPathBuf")] PathBuf),
NamedWorkspaceLayoutCustom(String, PathBuf), NamedWorkspaceLayoutCustom(String, #[serde_as(as = "ResolvedPathBuf")] PathBuf),
WorkspaceLayoutRule(usize, usize, usize, DefaultLayout), WorkspaceLayoutRule(usize, usize, usize, DefaultLayout),
NamedWorkspaceLayoutRule(String, usize, DefaultLayout), NamedWorkspaceLayoutRule(String, usize, DefaultLayout),
WorkspaceLayoutCustomRule(usize, usize, usize, PathBuf), WorkspaceLayoutCustomRule(
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf), usize,
usize,
usize,
#[serde_as(as = "ResolvedPathBuf")] PathBuf,
),
NamedWorkspaceLayoutCustomRule(String, usize, #[serde_as(as = "ResolvedPathBuf")] PathBuf),
ClearWorkspaceLayoutRules(usize, usize), ClearWorkspaceLayoutRules(usize, usize),
ClearNamedWorkspaceLayoutRules(String), ClearNamedWorkspaceLayoutRules(String),
ToggleWorkspaceLayer, ToggleWorkspaceLayer,
// Configuration // Configuration
ReloadConfiguration, ReloadConfiguration,
ReplaceConfiguration(PathBuf), ReplaceConfiguration(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
ReloadStaticConfiguration(PathBuf), ReloadStaticConfiguration(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
WatchConfiguration(bool), WatchConfiguration(bool),
CompleteConfiguration, CompleteConfiguration,
AltFocusHack(bool), AltFocusHack(bool),
Theme(KomorebiTheme), Theme(Box<KomorebiTheme>),
Animation(bool, Option<AnimationPrefix>), Animation(bool, Option<AnimationPrefix>),
AnimationDuration(u64, Option<AnimationPrefix>), AnimationDuration(u64, Option<AnimationPrefix>),
AnimationFps(u64), AnimationFps(u64),
@@ -202,6 +210,9 @@ pub enum SocketMessage {
ClearNamedWorkspaceRules(String), ClearNamedWorkspaceRules(String),
ClearAllWorkspaceRules, ClearAllWorkspaceRules,
EnforceWorkspaceRules, EnforceWorkspaceRules,
SessionFloatRule,
SessionFloatRules,
ClearSessionFloatRules,
#[serde(alias = "FloatRule")] #[serde(alias = "FloatRule")]
IgnoreRule(ApplicationIdentifier, String), IgnoreRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String), ManageRule(ApplicationIdentifier, String),
@@ -329,6 +340,8 @@ pub enum StateQuery {
FocusedContainerIndex, FocusedContainerIndex,
FocusedWindowIndex, FocusedWindowIndex,
FocusedWorkspaceName, FocusedWorkspaceName,
FocusedWorkspaceLayout,
Version,
} }
#[derive( #[derive(
@@ -364,6 +377,21 @@ pub struct WindowManagementBehaviour {
/// that can be later toggled to tiled, when false it will default to /// that can be later toggled to tiled, when false it will default to
/// `current_behaviour` again. /// `current_behaviour` again.
pub float_override: bool, pub float_override: bool,
/// Determines if a new window should be spawned floating when on the floating layer and the
/// floating layer behaviour is set to float. This value is always calculated when checking for
/// the management behaviour on a specific workspace.
pub floating_layer_override: bool,
/// The floating layer behaviour to be used if the float override is being used
pub floating_layer_behaviour: FloatingLayerBehaviour,
/// The `Placement` to be used when toggling a window to float
pub toggle_float_placement: Placement,
/// The `Placement` to be used when spawning a window on the floating layer with the
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`
pub floating_layer_placement: Placement,
/// The `Placement` to be used when spawning a window with float override active
pub float_override_placement: Placement,
/// The `Placement` to be used when spawning a window that matches a 'floating_applications' rule
pub float_rule_placement: Placement,
} }
#[derive( #[derive(
@@ -383,17 +411,59 @@ pub enum WindowContainerBehaviour {
)] )]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum FloatingLayerBehaviour { pub enum FloatingLayerBehaviour {
/// Tile new windows (unless they match a float rule) /// Tile new windows (unless they match a float rule or float override is active)
#[default] #[default]
Tile, Tile,
/// Float new windows /// Float new windows
Float, Float,
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)] #[derive(
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Placement {
/// Does not change the size or position of the window
#[default]
None,
/// Center the window without changing the size
Center,
/// Center the window and resize it according to the `AspectRatio`
CenterAndResize,
}
impl FloatingLayerBehaviour {
pub fn should_float(&self) -> bool {
match self {
FloatingLayerBehaviour::Tile => false,
FloatingLayerBehaviour::Float => true,
}
}
}
impl Placement {
pub fn should_center(&self) -> bool {
match self {
Placement::None => false,
Placement::Center | Placement::CenterAndResize => true,
}
}
pub fn should_resize(&self) -> bool {
match self {
Placement::None | Placement::Center => false,
Placement::CenterAndResize => true,
}
}
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum MoveBehaviour { pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor /// Swap the window container with the window container at the edge of the adjacent monitor
#[default]
Swap, Swap,
/// Insert the window container into the focused workspace on the adjacent monitor /// Insert the window container into the focused workspace on the adjacent monitor
Insert, Insert,
@@ -401,19 +471,22 @@ pub enum MoveBehaviour {
NoOp, NoOp,
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)] #[derive(
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum CrossBoundaryBehaviour { pub enum CrossBoundaryBehaviour {
/// Attempt to perform actions across a workspace boundary /// Attempt to perform actions across a workspace boundary
Workspace, Workspace,
/// Attempt to perform actions across a monitor boundary /// Attempt to perform actions across a monitor boundary
#[default]
Monitor, Monitor,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum HidingBehaviour { pub enum HidingBehaviour {
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps) /// END OF LIFE FEATURE: Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
Hide, Hide,
/// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching) /// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
Minimize, Minimize,
@@ -421,10 +494,13 @@ pub enum HidingBehaviour {
Cloak, Cloak,
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)] #[derive(
Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum OperationBehaviour { pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows /// Process komorebic commands on temporarily unmanaged/floated windows
#[default]
Op, Op,
/// Ignore komorebic commands on temporarily unmanaged/floated windows /// Ignore komorebic commands on temporarily unmanaged/floated windows
NoOp, NoOp,
@@ -453,45 +529,38 @@ impl Sizing {
} }
} }
pub fn resolve_home_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> { #[derive(
let mut resolved_path = PathBuf::new(); Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
let mut resolved = false; )]
for c in path.as_ref().components() { #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
match c { pub enum WindowHandlingBehaviour {
std::path::Component::Normal(c) #[default]
if (c == "~" || c == "$Env:USERPROFILE" || c == "$HOME") && !resolved => Sync,
{ Async,
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?; }
resolved_path.extend(home.components()); #[cfg(test)]
resolved = true; mod tests {
} use super::*;
std::path::Component::Normal(c) if (c == "$Env:KOMOREBI_CONFIG_HOME") && !resolved => { #[test]
let komorebi_config_home = fn deserializes() {
PathBuf::from(std::env::var("KOMOREBI_CONFIG_HOME").ok().ok_or_else(|| { // Set a variable for testing
anyhow!("there is no KOMOREBI_CONFIG_HOME environment variable set") std::env::set_var("VAR", "VALUE");
})?);
let json = r#"{"type":"WorkspaceLayoutCustomRule","content":[0,0,0,"/path/%VAR%/d"]}"#;
resolved_path.extend(komorebi_config_home.components()); let message: SocketMessage = serde_json::from_str(json).unwrap();
resolved = true;
} let SocketMessage::WorkspaceLayoutCustomRule(
_workspace_index,
_ => resolved_path.push(c), _workspace_number,
} _monitor_index,
} path,
) = message
let parent = resolved_path else {
.parent() panic!("Expected WorkspaceLayoutCustomRule");
.ok_or_else(|| anyhow!("cannot parse parent directory"))?; };
Ok(if parent.is_dir() { assert_eq!(path, PathBuf::from("/path/VALUE/d"));
let file = resolved_path }
.components()
.last()
.ok_or_else(|| anyhow!("cannot parse filename"))?;
dunce::canonicalize(parent)?.join(file)
} else {
resolved_path
})
} }
+174 -30
View File
@@ -1,48 +1,192 @@
use std::collections::HashMap;
use std::env; use std::env;
use std::ffi::OsStr;
use std::path::Component; use std::path::Component;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
/// Path extension trait
pub trait PathExt { 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; 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 { fn replace_env(&self) -> PathBuf {
let mut result = PathBuf::new(); let mut out = PathBuf::new();
for component in self.components() { for c in self.as_ref().components() {
match component { match c {
Component::Normal(segment) => { Component::Normal(mut c) => {
// Check if it starts with `$` or `$Env:` // Special case for ~ and $HOME, replace with $Env:USERPROFILE
if let Some(stripped_segment) = segment.to_string_lossy().strip_prefix('$') { if c == OsStr::new("~") || c.eq_ignore_ascii_case("$HOME") {
let var_name = if let Some(env_name) = stripped_segment.strip_prefix("Env:") c = OsStr::new("$Env:USERPROFILE");
{ }
// Extract the variable name after `$Env:`
env_name let bytes = c.as_encoded_bytes();
} else if stripped_segment == "HOME" {
// Special case for `$HOME` // %LOCALAPPDATA%
"USERPROFILE" let var = if bytes[0] == b'%' && bytes[bytes.len() - 1] == b'%' {
Some(&bytes[1..bytes.len() - 1])
} else { } else {
// Extract the variable name after `$` // prefix length is 5 for $Env: and 1 for $
stripped_segment // 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 let Ok(value) = env::var(var_name) { // if component is a variable, get the value from the environment
result.push(&value); // Replace with the value if let Some(var) = var {
} else { let var = unsafe { OsStr::from_encoded_bytes_unchecked(var) };
result.push(segment); // Keep as-is if variable is not found if let Some(value) = env::var_os(var) {
} out.push(value);
} else { continue;
result.push(segment); // Keep as-is if not an environment variable
}
}
_ => {
// Add other components (e.g., root, parent) as-is
result.push(component.as_os_str());
}
} }
} }
result // if not a variable, or a value couldn't be obtained from environemnt
// then push the component as is
out.push(c);
}
// other components are pushed as is
_ => out.push(c),
}
}
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"));
} }
} }
+34 -10
View File
@@ -5,7 +5,6 @@ pub mod border_manager;
pub mod com; pub mod com;
#[macro_use] #[macro_use]
pub mod ring; pub mod ring;
pub mod colour;
pub mod container; pub mod container;
pub mod core; pub mod core;
pub mod focus_manager; pub mod focus_manager;
@@ -47,8 +46,8 @@ use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
pub use colour::*;
pub use core::*; pub use core::*;
pub use komorebi_themes::colour::*;
pub use process_command::*; pub use process_command::*;
pub use process_event::*; pub use process_event::*;
pub use static_config::*; pub use static_config::*;
@@ -64,6 +63,7 @@ use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy; use crate::core::config_generation::MatchingStrategy;
use crate::core::config_generation::WorkspaceMatchingRule; use crate::core::config_generation::WorkspaceMatchingRule;
use color_eyre::Result; use color_eyre::Result;
use crossbeam_utils::atomic::AtomicCell;
use os_info::Version; use os_info::Version;
use parking_lot::Mutex; use parking_lot::Mutex;
use parking_lot::RwLock; use parking_lot::RwLock;
@@ -159,7 +159,15 @@ lazy_static! {
matching_strategy: Option::from(MatchingStrategy::Equals), matching_strategy: Option::from(MatchingStrategy::Equals),
}) })
])); ]));
static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new())); static ref SESSION_FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
MatchingRule::Simple(IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("komorebi-shortcuts.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
})
]));
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![ static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"Chrome_RenderWidgetHostHWND".to_string(), "Chrome_RenderWidgetHostHWND".to_string(),
])); ]));
@@ -188,15 +196,16 @@ lazy_static! {
Arc::new(Mutex::new(HidingBehaviour::Cloak)); Arc::new(Mutex::new(HidingBehaviour::Cloak));
pub static ref HOME_DIR: PathBuf = { 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| { 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() { assert!(
home home.is_dir(),
} else { "$Env:KOMOREBI_CONFIG_HOME is set to '{}', which is not a valid directory",
panic!( home_path
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
); );
}
home
}) })
}; };
pub static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi"); pub static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
@@ -226,6 +235,8 @@ lazy_static! {
Arc::new(Mutex::new(HashMap::new())); Arc::new(Mutex::new(HashMap::new()));
static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen))); static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen)));
static ref CURRENT_VIRTUAL_DESKTOP: Arc<Mutex<Option<Vec<u8>>>> = Arc::new(Mutex::new(None));
} }
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10); pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
@@ -239,6 +250,11 @@ pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20); pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
pub static WINDOW_HANDLING_BEHAVIOUR: AtomicCell<WindowHandlingBehaviour> =
AtomicCell::new(WindowHandlingBehaviour::Sync);
shadow_rs::shadow!(build);
#[must_use] #[must_use]
pub fn current_virtual_desktop() -> Option<Vec<u8>> { pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER); let hkcu = RegKey::predef(HKEY_CURRENT_USER);
@@ -290,6 +306,14 @@ pub enum NotificationEvent {
WindowManager(WindowManagerEvent), WindowManager(WindowManagerEvent),
Socket(SocketMessage), Socket(SocketMessage),
Monitor(MonitorNotification), Monitor(MonitorNotification),
VirtualDesktop(VirtualDesktopNotification),
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum VirtualDesktopNotification {
EnteredAssociatedVirtualDesktop,
LeftAssociatedVirtualDesktop,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
+25 -4
View File
@@ -17,11 +17,13 @@ use std::time::Duration;
use clap::Parser; use clap::Parser;
use clap::ValueEnum; use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result; use color_eyre::Result;
use crossbeam_utils::Backoff; use crossbeam_utils::Backoff;
use komorebi::animation::AnimationEngine; use komorebi::animation::AnimationEngine;
use komorebi::animation::ANIMATION_ENABLED_GLOBAL; use komorebi::animation::ANIMATION_ENABLED_GLOBAL;
use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION; use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
use komorebi::replace_env_in_path;
#[cfg(feature = "deadlock_detection")] #[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock; use parking_lot::deadlock;
use parking_lot::Mutex; use parking_lot::Mutex;
@@ -56,8 +58,6 @@ use komorebi::HOME_DIR;
use komorebi::INITIAL_CONFIGURATION_LOADED; use komorebi::INITIAL_CONFIGURATION_LOADED;
use komorebi::SESSION_ID; use komorebi::SESSION_ID;
shadow_rs::shadow!(build);
fn setup(log_level: LogLevel) -> Result<(WorkerGuard, WorkerGuard)> { fn setup(log_level: LogLevel) -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() { if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1"); std::env::set_var("RUST_LIB_BACKTRACE", "1");
@@ -165,7 +165,7 @@ enum LogLevel {
} }
#[derive(Parser)] #[derive(Parser)]
#[clap(author, about, version = build::CLAP_LONG_VERSION)] #[clap(author, about, version = komorebi::build::CLAP_LONG_VERSION)]
struct Opts { struct Opts {
/// Allow the use of komorebi's custom focus-follows-mouse implementation /// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(short, long = "ffm")] #[clap(short, long = "ffm")]
@@ -178,6 +178,7 @@ struct Opts {
tcp_port: Option<usize>, tcp_port: Option<usize>,
/// Path to a static configuration JSON file /// Path to a static configuration JSON file
#[clap(short, long)] #[clap(short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>, config: Option<PathBuf>,
/// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi /// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi
#[clap(long)] #[clap(long)]
@@ -193,8 +194,28 @@ fn main() -> Result<()> {
let opts: Opts = Opts::parse(); let opts: Opts = Opts::parse();
CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst); CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
let mut set_foreground_window_retries = 5;
let mut set_foreground_window_succeeded = false;
let process_id = WindowsApi::current_process_id(); let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?; while set_foreground_window_retries > 0 && !set_foreground_window_succeeded {
match WindowsApi::allow_set_foreground_window(process_id) {
Ok(_) => {
set_foreground_window_succeeded = true;
}
Err(error) => {
tracing::error!("{error}");
set_foreground_window_retries -= 1;
}
}
if set_foreground_window_retries == 0 {
return Err(anyhow!(
"failed call to AllowSetForegroundWindow after 5 retries"
));
}
}
WindowsApi::set_process_dpi_awareness_context()?; WindowsApi::set_process_dpi_awareness_context()?;
let session_id = WindowsApi::process_id_to_session_id()?; let session_id = WindowsApi::process_id_to_session_id()?;
+200 -17
View File
@@ -12,15 +12,21 @@ use getset::Setters;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; 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::core::Rect;
use crate::container::Container; use crate::container::Container;
use crate::ring::Ring; use crate::ring::Ring;
use crate::workspace::Workspace; use crate::workspace::Workspace;
use crate::workspace::WorkspaceGlobals;
use crate::workspace::WorkspaceLayer; use crate::workspace::WorkspaceLayer;
use crate::DefaultLayout; use crate::DefaultLayout;
use crate::FloatingLayerBehaviour;
use crate::Layout; use crate::Layout;
use crate::OperationDirection; use crate::OperationDirection;
use crate::Wallpaper;
use crate::WindowsApi; use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING; use crate::DEFAULT_WORKSPACE_PADDING;
@@ -60,6 +66,10 @@ pub struct Monitor {
pub container_padding: Option<i32>, pub container_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")] #[getset(get_copy = "pub", set = "pub")]
pub workspace_padding: Option<i32>, 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); impl_ring_elements!(Monitor, Workspace);
@@ -115,6 +125,8 @@ pub fn new(
workspace_names: HashMap::default(), workspace_names: HashMap::default(),
container_padding: None, container_padding: None,
workspace_padding: None, workspace_padding: None,
wallpaper: None,
floating_layer_behaviour: None,
} }
} }
@@ -156,6 +168,8 @@ impl Monitor {
workspace_names: Default::default(), workspace_names: Default::default(),
container_padding: None, container_padding: None,
workspace_padding: None, workspace_padding: None,
wallpaper: None,
floating_layer_behaviour: None,
} }
} }
@@ -165,11 +179,23 @@ impl Monitor {
.unwrap_or(None) .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<()> { pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
let focused_idx = self.focused_workspace_idx(); 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() { for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
if i == focused_idx { if i == focused_idx {
workspace.restore(mouse_follows_focus)?; workspace.restore(mouse_follows_focus, hmonitor, &monitor_wp)?;
} else { } else {
workspace.hide(None); workspace.hide(None);
} }
@@ -186,18 +212,34 @@ impl Monitor {
let workspace_padding = self let workspace_padding = self
.workspace_padding() .workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst))); .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 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 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() { for workspace in self.workspaces_mut() {
workspace.globals_mut().container_padding = container_padding; workspace.globals = WorkspaceGlobals {
workspace.globals_mut().workspace_padding = workspace_padding; container_padding,
workspace.globals_mut().work_area = work_area; workspace_padding,
workspace.globals_mut().work_area_offset = offset; border_width,
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset; border_offset,
workspace.globals_mut().window_based_work_area_offset_limit = limit; 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 let workspace_padding = self
.workspace_padding() .workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst))); .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 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 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) { if let Some(workspace) = self.workspaces_mut().get_mut(workspace_idx) {
workspace.globals_mut().container_padding = container_padding; workspace.globals = WorkspaceGlobals {
workspace.globals_mut().workspace_padding = workspace_padding; container_padding,
workspace.globals_mut().work_area = work_area; workspace_padding,
workspace.globals_mut().work_area_offset = offset; border_width,
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset; border_offset,
workspace.globals_mut().window_based_work_area_offset_limit = limit; work_area,
work_area_offset,
window_based_work_area_offset,
window_based_work_area_offset_limit,
floating_layer_behaviour,
}
} }
} }
@@ -400,6 +458,18 @@ impl Monitor {
Some(workspace) => workspace, Some(workspace) => workspace,
}; };
if target_workspace.monocle_container().is_some() {
for container in target_workspace.containers_mut() {
container.restore();
}
for window in target_workspace.floating_windows_mut() {
window.restore();
}
target_workspace.reintegrate_monocle_container()?;
}
target_workspace.set_layer(WorkspaceLayer::Tiling); target_workspace.set_layer(WorkspaceLayer::Tiling);
if let Some(direction) = direction { if let Some(direction) = direction {
@@ -603,4 +673,117 @@ mod tests {
// Should be the last workspace index: 1 // Should be the last workspace index: 1
assert_eq!(new_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");
}
} }
@@ -553,6 +553,8 @@ where
workspace_names: cached.workspace_names.clone(), workspace_names: cached.workspace_names.clone(),
container_padding: cached.container_padding, container_padding: cached.container_padding,
workspace_padding: cached.workspace_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(); let focused_workspace_idx = m.focused_workspace_idx();
+190 -30
View File
@@ -1,6 +1,7 @@
use color_eyre::eyre::anyhow; use color_eyre::eyre::anyhow;
use color_eyre::eyre::OptionExt; use color_eyre::eyre::OptionExt;
use color_eyre::Result; use color_eyre::Result;
use komorebi_themes::colour::Rgb;
use miow::pipe::connect; use miow::pipe::connect;
use net2::TcpStreamExt; use net2::TcpStreamExt;
use parking_lot::Mutex; use parking_lot::Mutex;
@@ -19,9 +20,18 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use uds_windows::UnixStream; use uds_windows::UnixStream;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION; use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION; 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::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::IdWithIdentifier;
use crate::core::config_generation::MatchingRule; use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy; use crate::core::config_generation::MatchingStrategy;
@@ -38,16 +48,6 @@ use crate::core::SocketMessage;
use crate::core::StateQuery; use crate::core::StateQuery;
use crate::core::WindowContainerBehaviour; use crate::core::WindowContainerBehaviour;
use crate::core::WindowKind; 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::current_virtual_desktop;
use crate::monitor::MonitorInformation; use crate::monitor::MonitorInformation;
use crate::notify_subscribers; use crate::notify_subscribers;
@@ -72,6 +72,7 @@ use crate::State;
use crate::CUSTOM_FFM; use crate::CUSTOM_FFM;
use crate::DATA_DIR; use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES; use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::HIDING_BEHAVIOUR; use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS; use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED; use crate::INITIAL_CONFIGURATION_LOADED;
@@ -81,6 +82,7 @@ use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR; use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH; use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS; use crate::REMOVE_TITLEBARS;
use crate::SESSION_FLOATING_APPLICATIONS;
use crate::SUBSCRIPTION_PIPES; use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKETS; use crate::SUBSCRIPTION_SOCKETS;
use crate::SUBSCRIPTION_SOCKET_OPTIONS; use crate::SUBSCRIPTION_SOCKET_OPTIONS;
@@ -224,6 +226,7 @@ impl WindowManager {
_ => {} _ => {}
}; };
let mut force_update_borders = false;
match message { match message {
SocketMessage::Promote => self.promote_container_to_front()?, SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?, SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
@@ -233,31 +236,37 @@ impl WindowManager {
} }
SocketMessage::EagerFocus(ref exe) => { SocketMessage::EagerFocus(ref exe) => {
let focused_monitor_idx = self.focused_monitor_idx(); let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx = self.focused_workspace_idx()?;
let mut window_location = None; let mut window_location = None;
let mut monitor_workspace_indices = None; let mut monitor_to_focus = None;
let mut needs_workspace_loading = false;
'search: for (monitor_idx, monitor) in self.monitors().iter().enumerate() { 'search: for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() {
for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() { for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() {
if let Some(location) = workspace.location_from_exe(exe) { if let Some(location) = workspace.location_from_exe(exe) {
window_location = Some(location); window_location = Some(location);
monitor_workspace_indices = Some((monitor_idx, workspace_idx));
if monitor_idx != focused_monitor_idx {
monitor_to_focus = Some(monitor_idx);
}
// Focus workspace if it is not already the focused one, without
// loading it so that we don't give focus to the wrong window, we will
// load it later after focusing the wanted window
let focused_ws_idx = monitor.focused_workspace_idx();
if focused_ws_idx != workspace_idx {
monitor.set_last_focused_workspace(Option::from(focused_ws_idx));
monitor.focus_workspace(workspace_idx)?;
needs_workspace_loading = true;
}
break 'search; break 'search;
} }
} }
} }
if let Some((monitor_idx, workspace_idx)) = monitor_workspace_indices { if let Some(monitor_idx) = monitor_to_focus {
if monitor_idx != focused_monitor_idx {
self.focus_monitor(monitor_idx)?; self.focus_monitor(monitor_idx)?;
let focused_ws_idx = self.focused_workspace_idx()?;
if focused_ws_idx != workspace_idx {
self.focus_workspace(workspace_idx)?;
}
} else if workspace_idx != focused_workspace_idx {
self.focus_workspace(workspace_idx)?;
}
} }
if let Some(location) = window_location { if let Some(location) = window_location {
@@ -290,6 +299,13 @@ impl WindowManager {
} }
} }
} }
if needs_workspace_loading {
let mouse_follows_focus = self.mouse_follows_focus;
if let Some(monitor) = self.focused_monitor_mut() {
monitor.load_focused_workspace(mouse_follows_focus)?;
}
}
} }
} }
SocketMessage::FocusWindow(direction) => { SocketMessage::FocusWindow(direction) => {
@@ -395,7 +411,7 @@ impl WindowManager {
workspace.locked_containers.remove(&container_idx); workspace.locked_containers.remove(&container_idx);
} }
SocketMessage::ToggleLock => self.toggle_lock()?, SocketMessage::ToggleLock => self.toggle_lock()?,
SocketMessage::ToggleFloat => self.toggle_float()?, SocketMessage::ToggleFloat => self.toggle_float(false)?,
SocketMessage::ToggleMonocle => self.toggle_monocle()?, SocketMessage::ToggleMonocle => self.toggle_monocle()?,
SocketMessage::ToggleMaximize => self.toggle_maximize()?, SocketMessage::ToggleMaximize => self.toggle_maximize()?,
SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => { SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {
@@ -542,6 +558,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) => { SocketMessage::IgnoreRule(identifier, ref id) => {
let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock(); let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();
@@ -861,10 +924,12 @@ impl WindowManager {
} }
SocketMessage::Retile => { SocketMessage::Retile => {
border_manager::destroy_all_borders()?; border_manager::destroy_all_borders()?;
force_update_borders = true;
self.retile_all(false)? self.retile_all(false)?
} }
SocketMessage::RetileWithResizeDimensions => { SocketMessage::RetileWithResizeDimensions => {
border_manager::destroy_all_borders()?; border_manager::destroy_all_borders()?;
force_update_borders = true;
self.retile_all(true)? self.retile_all(true)?
} }
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
@@ -1231,6 +1296,12 @@ impl WindowManager {
window.lower()?; window.lower()?;
} }
} }
if let Some(container) = workspace.monocle_container() {
if let Some(window) = container.focused_window() {
window.lower()?;
}
}
} }
WorkspaceLayer::Floating => { WorkspaceLayer::Floating => {
workspace.set_layer(WorkspaceLayer::Tiling); workspace.set_layer(WorkspaceLayer::Tiling);
@@ -1377,6 +1448,20 @@ impl WindowManager {
.focused_workspace_name() .focused_workspace_name()
.unwrap_or_else(|| focused_monitor.focused_workspace_idx().to_string()) .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())?; reply.write_all(response.as_bytes())?;
@@ -1586,6 +1671,7 @@ impl WindowManager {
} }
SocketMessage::ReloadConfiguration => { SocketMessage::ReloadConfiguration => {
Self::reload_configuration(); Self::reload_configuration();
force_update_borders = true;
} }
SocketMessage::ReplaceConfiguration(ref config) => { SocketMessage::ReplaceConfiguration(ref config) => {
// Check that this is a valid static config file first // Check that this is a valid static config file first
@@ -1614,15 +1700,78 @@ impl WindowManager {
// Set self to the new wm instance // Set self to the new wm instance
*self = wm; *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) => { SocketMessage::ReloadStaticConfiguration(ref pathbuf) => {
self.reload_static_configuration(pathbuf)?; self.reload_static_configuration(pathbuf)?;
force_update_borders = true;
} }
SocketMessage::CompleteConfiguration => { SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) { if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst); INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
self.update_focused_workspace(false, false)?; self.update_focused_workspace(false, false)?;
force_update_borders = true;
} }
} }
SocketMessage::WatchConfiguration(enable) => { SocketMessage::WatchConfiguration(enable) => {
@@ -1880,6 +2029,8 @@ impl WindowManager {
self.remove_all_accents()?; self.remove_all_accents()?;
} }
} }
} else if matches!(IMPLEMENTATION.load(), BorderImplementation::Komorebi) {
force_update_borders = true;
} }
} }
SocketMessage::BorderImplementation(implementation) => { SocketMessage::BorderImplementation(implementation) => {
@@ -1892,16 +2043,16 @@ impl WindowManager {
match IMPLEMENTATION.load() { match IMPLEMENTATION.load() {
BorderImplementation::Komorebi => { BorderImplementation::Komorebi => {
self.remove_all_accents()?; self.remove_all_accents()?;
force_update_borders = true;
} }
BorderImplementation::Windows => { BorderImplementation::Windows => {
border_manager::destroy_all_borders()?; border_manager::destroy_all_borders()?;
} }
} }
border_manager::send_notification(None);
} }
} }
SocketMessage::BorderColour(kind, r, g, b) => match kind { SocketMessage::BorderColour(kind, r, g, b) => {
match kind {
WindowKind::Single => { WindowKind::Single => {
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
} }
@@ -1921,15 +2072,20 @@ impl WindowManager {
WindowKind::Floating => { WindowKind::Floating => {
border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
} }
}, }
force_update_borders = true;
}
SocketMessage::BorderStyle(style) => { SocketMessage::BorderStyle(style) => {
STYLE.store(style); STYLE.store(style);
force_update_borders = true;
} }
SocketMessage::BorderWidth(width) => { SocketMessage::BorderWidth(width) => {
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst); border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
force_update_borders = true;
} }
SocketMessage::BorderOffset(offset) => { SocketMessage::BorderOffset(offset) => {
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst); border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
force_update_borders = true;
} }
SocketMessage::Animation(enable, prefix) => match prefix { SocketMessage::Animation(enable, prefix) => match prefix {
Some(prefix) => { Some(prefix) => {
@@ -2091,8 +2247,8 @@ impl WindowManager {
reply.write_all(schema.as_bytes())?; reply.write_all(schema.as_bytes())?;
} }
SocketMessage::Theme(theme) => { SocketMessage::Theme(ref theme) => {
theme_manager::send_notification(theme); theme_manager::send_notification(*theme.clone());
} }
// Deprecated commands // Deprecated commands
SocketMessage::AltFocusHack(_) SocketMessage::AltFocusHack(_)
@@ -2110,7 +2266,11 @@ impl WindowManager {
initial_state.has_been_modified(self.as_ref()), initial_state.has_been_modified(self.as_ref()),
)?; )?;
if force_update_borders {
border_manager::send_force_update();
} else {
border_manager::send_notification(None); border_manager::send_notification(None);
}
transparency_manager::send_notification(); transparency_manager::send_notification();
stackbar_manager::send_notification(); stackbar_manager::send_notification();
+83 -19
View File
@@ -28,7 +28,9 @@ use crate::workspace::WorkspaceLayer;
use crate::Notification; use crate::Notification;
use crate::NotificationEvent; use crate::NotificationEvent;
use crate::State; use crate::State;
use crate::VirtualDesktopNotification;
use crate::Window; use crate::Window;
use crate::CURRENT_VIRTUAL_DESKTOP;
use crate::FLOATING_APPLICATIONS; use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS; use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS; use crate::REGEX_IDENTIFIERS;
@@ -116,15 +118,62 @@ impl WindowManager {
} }
} }
let mut last_known_virtual_desktop_id = CURRENT_VIRTUAL_DESKTOP.lock();
if let Some(virtual_desktop_id) = &self.virtual_desktop_id { if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
if let Some(id) = current_virtual_desktop() { let latest_virtual_desktop_id = current_virtual_desktop();
if let Some(id) = latest_virtual_desktop_id {
// if we are on the vd associated with komorebi
let should_retile = id == *virtual_desktop_id
// and we came from a vd not associated with komorebi
&& (*last_known_virtual_desktop_id).clone().unwrap_or_default() != id;
*last_known_virtual_desktop_id = Some(id.clone());
if id != *virtual_desktop_id { if id != *virtual_desktop_id {
tracing::info!( tracing::info!(
"ignoring events and commands while not on virtual desktop {:?}", "ignoring events and commands while not on virtual desktop {:?}",
virtual_desktop_id virtual_desktop_id
); );
// TODO: when returning from another VD to the VD associated with komorebi
// if borders are enabled, they will not be drawn again until the user interacts
// with the workspace or forces a retile
border_manager::destroy_all_borders()?;
// to be consumed by integrating gui applications like bars to know
// when to hide visual components which don't make sense when not on
// komorebi's associated virtual desktop
tracing::debug!("notifying subscribers that we have left komorebi's associated virtual desktop");
notify_subscribers(
Notification {
event: NotificationEvent::VirtualDesktop(
VirtualDesktopNotification::LeftAssociatedVirtualDesktop,
),
state: self.as_ref().into(),
},
true,
)?;
return Ok(()); return Ok(());
} }
if should_retile {
self.retile_all(true)?;
// to be consumed by integrating gui applications like bars to know
// when to show visual components associated with komorebi's virtual
// desktop
tracing::debug!("notifying subscribers that we are back on komorebi's associated virtual desktop");
notify_subscribers(
Notification {
event: NotificationEvent::VirtualDesktop(
VirtualDesktopNotification::EnteredAssociatedVirtualDesktop,
),
state: self.as_ref().into(),
},
true,
)?;
}
} }
} }
@@ -290,11 +339,10 @@ impl WindowManager {
WindowManagerEvent::Show(_, window) WindowManagerEvent::Show(_, window)
| WindowManagerEvent::Manage(window) | WindowManagerEvent::Manage(window)
| WindowManagerEvent::Uncloak(_, window) => { | WindowManagerEvent::Uncloak(_, window) => {
if matches!(event, WindowManagerEvent::Uncloak(_, _)) if matches!(event, WindowManagerEvent::Uncloak(_, _)) && self.uncloak_to_ignore >= 1
&& self.uncloack_to_ignore >= 1
{ {
tracing::info!("ignoring uncloak after monocle move by mouse across monitors"); tracing::info!("ignoring uncloak after monocle move by mouse across monitors");
self.uncloack_to_ignore = self.uncloack_to_ignore.saturating_sub(1); self.uncloak_to_ignore = self.uncloak_to_ignore.saturating_sub(1);
} else { } else {
let focused_monitor_idx = self.focused_monitor_idx(); let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx = let focused_workspace_idx =
@@ -346,13 +394,14 @@ impl WindowManager {
} }
if proceed { if proceed {
let mut behaviour = self.window_management_behaviour( let behaviour = self.window_management_behaviour(
focused_monitor_idx, focused_monitor_idx,
focused_workspace_idx, focused_workspace_idx,
); );
let workspace = self.focused_workspace_mut()?; let workspace = self.focused_workspace_mut()?;
let workspace_contains_window = workspace.contains_window(window.hwnd); let workspace_contains_window = workspace.contains_window(window.hwnd);
let monocle_container = workspace.monocle_container().clone(); let monocle_container = workspace.monocle_container().clone();
let mut workspace_layer = *workspace.layer();
if !workspace_contains_window && needs_reconciliation.is_none() { if !workspace_contains_window && needs_reconciliation.is_none() {
let floating_applications = FLOATING_APPLICATIONS.lock(); let floating_applications = FLOATING_APPLICATIONS.lock();
@@ -376,23 +425,33 @@ impl WindowManager {
} }
} }
behaviour.float_override = behaviour.float_override if behaviour.float_override
|| (should_float || behaviour.floating_layer_override
&& !matches!(event, WindowManagerEvent::Manage(_))); || (should_float && !matches!(event, WindowManagerEvent::Manage(_)))
{
if behaviour.float_override { let placement = if behaviour.floating_layer_override {
// Center floating windows if we are already on the `Floating` // Floating layer override placement
// layer and the window doesn't match a `floating_windows` rule and behaviour.floating_layer_placement
// the workspace is not a floating workspace } else if behaviour.float_override {
// Float override placement
behaviour.float_override_placement
} else {
// Float rule placement
behaviour.float_rule_placement
};
// Center floating windows according to the proper placement if not
// on a floating workspace
let center_spawned_floats = let center_spawned_floats =
matches!(workspace.layer, WorkspaceLayer::Floating) placement.should_center() && workspace.tile;
&& !should_float
&& workspace.tile;
workspace.floating_windows_mut().push_back(window); workspace.floating_windows_mut().push_back(window);
workspace.set_layer(WorkspaceLayer::Floating); workspace.set_layer(WorkspaceLayer::Floating);
workspace_layer = *workspace.layer();
if center_spawned_floats { if center_spawned_floats {
let mut floating_window = window; let mut floating_window = window;
floating_window.center(&workspace.globals().work_area)?; floating_window.center(
&workspace.globals().work_area,
placement.should_resize(),
)?;
} }
self.update_focused_workspace(false, false)?; self.update_focused_workspace(false, false)?;
} else { } else {
@@ -400,6 +459,7 @@ impl WindowManager {
WindowContainerBehaviour::Create => { WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window); workspace.new_container_for_window(window);
workspace.set_layer(WorkspaceLayer::Tiling); workspace.set_layer(WorkspaceLayer::Tiling);
workspace_layer = *workspace.layer();
self.update_focused_workspace(false, false)?; self.update_focused_workspace(false, false)?;
} }
WindowContainerBehaviour::Append => { WindowContainerBehaviour::Append => {
@@ -410,6 +470,7 @@ impl WindowManager {
})? })?
.add_window(window); .add_window(window);
workspace.set_layer(WorkspaceLayer::Tiling); workspace.set_layer(WorkspaceLayer::Tiling);
workspace_layer = *workspace.layer();
self.update_focused_workspace(true, false)?; self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification(); stackbar_manager::send_notification();
} }
@@ -442,7 +503,10 @@ impl WindowManager {
} }
} }
if !monocle_window_event && monocle_container.is_some() { if !monocle_window_event
&& monocle_container.is_some()
&& matches!(workspace_layer, WorkspaceLayer::Tiling)
{
window.hide(); window.hide();
} }
} }
@@ -709,7 +773,7 @@ impl WindowManager {
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows // If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
if let WindowManagerEvent::Unmanage(mut window) = event { if let WindowManagerEvent::Unmanage(mut window) = event {
window.center(&self.focused_monitor_work_area()?)?; window.center(&self.focused_monitor_work_area()?, true)?;
} }
// Update list of known_hwnds and their monitor/workspace index pair // Update list of known_hwnds and their monitor/workspace index pair
+9 -1
View File
@@ -3,11 +3,13 @@
use crate::border_manager; use crate::border_manager;
use crate::notify_subscribers; use crate::notify_subscribers;
use crate::winevent::WinEvent; use crate::winevent::WinEvent;
use crate::HidingBehaviour;
use crate::NotificationEvent; use crate::NotificationEvent;
use crate::Window; use crate::Window;
use crate::WindowManager; use crate::WindowManager;
use crate::WindowManagerEvent; use crate::WindowManagerEvent;
use crate::DATA_DIR; use crate::DATA_DIR;
use crate::HIDING_BEHAVIOUR;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
@@ -169,6 +171,7 @@ fn find_orphans() -> color_eyre::Result<()> {
loop { loop {
std::thread::sleep(Duration::from_millis(20)); std::thread::sleep(Duration::from_millis(20));
let hiding_behaviour = *HIDING_BEHAVIOUR.lock();
let mut cache = HWNDS_CACHE.lock(); let mut cache = HWNDS_CACHE.lock();
let mut orphan_hwnds = HashMap::new(); let mut orphan_hwnds = HashMap::new();
@@ -177,13 +180,18 @@ fn find_orphans() -> color_eyre::Result<()> {
let window = Window::from(*hwnd); let window = Window::from(*hwnd);
if !window.is_window() if !window.is_window()
|| (
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app // 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 // 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 // (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
// docs is closed // docs is closed
// //
// I hate every single person who worked on Microsoft Office 365, especially Word // I hate every single person who worked on Microsoft Office 365, especially Word
|| !window.is_visible() !window.is_visible()
// We cannot execute this lovely hack if the user is using HidingBehaviour::Hide because
// it will result in legitimate hidden, non-visible windows being yeeted from the state
&& !matches!(hiding_behaviour, HidingBehaviour::Hide)
)
{ {
orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx)); orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx));
} }
+3 -1
View File
@@ -180,7 +180,9 @@ impl Stackbar {
layout.top -= workspace_specific_offset + STACKBAR_TAB_HEIGHT.load_consume(); layout.top -= workspace_specific_offset + STACKBAR_TAB_HEIGHT.load_consume();
layout.left -= workspace_specific_offset; layout.left -= workspace_specific_offset;
WindowsApi::position_window(self.hwnd, &layout, false)?; // Async causes the stackbar to disappear or flicker because we modify it right after,
// so we have to do a synchronous call
WindowsApi::position_window(self.hwnd, &layout, false, false)?;
unsafe { unsafe {
let hdc = GetDC(Option::from(self.hwnd())); let hdc = GetDC(Option::from(self.hwnd()));
+258 -90
View File
@@ -13,14 +13,12 @@ use crate::border_manager;
use crate::border_manager::ZOrder; use crate::border_manager::ZOrder;
use crate::border_manager::IMPLEMENTATION; use crate::border_manager::IMPLEMENTATION;
use crate::border_manager::STYLE; use crate::border_manager::STYLE;
use crate::colour::Colour;
use crate::config_generation::WorkspaceMatchingRule; use crate::config_generation::WorkspaceMatchingRule;
use crate::core::config_generation::ApplicationConfiguration; use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator; use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions; use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule; use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy; use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use crate::core::AnimationStyle; use crate::core::AnimationStyle;
use crate::core::BorderImplementation; use crate::core::BorderImplementation;
use crate::core::BorderStyle; use crate::core::BorderStyle;
@@ -40,6 +38,7 @@ use crate::current_virtual_desktop;
use crate::monitor; use crate::monitor;
use crate::monitor::Monitor; use crate::monitor::Monitor;
use crate::monitor_reconciliator; use crate::monitor_reconciliator;
use crate::resolve_option_hashmap_usize_path;
use crate::ring::Ring; use crate::ring::Ring;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR; use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY; use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
@@ -61,7 +60,10 @@ use crate::AspectRatio;
use crate::Axis; use crate::Axis;
use crate::CrossBoundaryBehaviour; use crate::CrossBoundaryBehaviour;
use crate::FloatingLayerBehaviour; use crate::FloatingLayerBehaviour;
use crate::Placement;
use crate::PredefinedAspectRatio; use crate::PredefinedAspectRatio;
use crate::ResolvedPathBuf;
use crate::WindowHandlingBehaviour;
use crate::DATA_DIR; use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING; use crate::DEFAULT_WORKSPACE_PADDING;
@@ -82,11 +84,13 @@ use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST; use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11; use crate::WINDOWS_11;
use crate::WINDOW_HANDLING_BEHAVIOUR;
use crate::WORKSPACE_MATCHING_RULES; use crate::WORKSPACE_MATCHING_RULES;
use color_eyre::Result; use color_eyre::Result;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use hotwatch::EventKind; use hotwatch::EventKind;
use hotwatch::Hotwatch; use hotwatch::Hotwatch;
use komorebi_themes::colour::Colour;
use parking_lot::Mutex; use parking_lot::Mutex;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
@@ -119,8 +123,66 @@ pub struct BorderColours {
/// Border colour when the container is unfocused /// Border colour when the container is unfocused
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub unfocused: Option<Colour>, 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)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WorkspaceConfig { pub struct WorkspaceConfig {
@@ -131,12 +193,14 @@ pub struct WorkspaceConfig {
pub layout: Option<DefaultLayout>, pub layout: Option<DefaultLayout>,
/// END OF LIFE FEATURE: Custom Layout (default: None) /// END OF LIFE FEATURE: Custom Layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<ResolvedPathBuf>")]
pub custom_layout: Option<PathBuf>, pub custom_layout: Option<PathBuf>,
/// Layout rules in the format of threshold => layout (default: None) /// Layout rules in the format of threshold => layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub layout_rules: Option<HashMap<usize, DefaultLayout>>, pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
/// END OF LIFE FEATURE: Custom layout rules (default: None) /// END OF LIFE FEATURE: Custom layout rules (default: None)
#[serde(skip_serializing_if = "Option::is_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>>, pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,
/// Container padding (default: global) /// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -168,6 +232,9 @@ pub struct WorkspaceConfig {
/// Determine what happens to a new window when the Floating workspace layer is active (default: Tile) /// Determine what happens to a new window when the Floating workspace layer is active (default: Tile)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>, 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 { impl From<&Workspace> for WorkspaceConfig {
@@ -243,7 +310,8 @@ impl From<&Workspace> for WorkspaceConfig {
window_container_behaviour_rules: Option::from(window_container_behaviour_rules), window_container_behaviour_rules: Option::from(window_container_behaviour_rules),
float_override: *value.float_override(), float_override: *value.float_override(),
layout_flip: value.layout_flip(), 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 +336,12 @@ pub struct MonitorConfig {
/// Workspace padding (default: global) /// Workspace padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>, 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 { impl From<&Monitor> for MonitorConfig {
@@ -303,23 +377,27 @@ impl From<&Monitor> for MonitorConfig {
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()), window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
container_padding, container_padding,
workspace_padding, workspace_padding,
wallpaper: value.wallpaper().clone(),
floating_layer_behaviour: value.floating_layer_behaviour(),
} }
} }
} }
#[serde_with::serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)] #[serde(untagged)]
pub enum AppSpecificConfigurationPath { pub enum AppSpecificConfigurationPath {
/// A single applications.json file /// A single applications.json file
Single(PathBuf), Single(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
/// Multiple applications.json files /// 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)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.36` /// The `komorebi.json` static configuration file reference for `v0.1.37`
pub struct StaticConfig { pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required /// DEPRECATED from v0.1.22: no longer required
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -340,6 +418,25 @@ pub struct StaticConfig {
/// (default: false) /// (default: false)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub float_override: Option<bool>, pub float_override: Option<bool>,
/// Determines what happens on a new window when on the `FloatingLayer`
/// (default: Tile)
#[serde(skip_serializing_if = "Option::is_none")]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
/// Determines the placement of a new window when toggling to float (default: CenterAndResize)
#[serde(skip_serializing_if = "Option::is_none")]
pub toggle_float_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window on the floating layer with the
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float` (default: Center)
#[serde(skip_serializing_if = "Option::is_none")]
pub floating_layer_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window with float override active
/// (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window that matches a
/// 'floating_applications' rule (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_rule_placement: Option<Placement>,
/// Determine what happens when a window is moved across a monitor boundary (default: Swap) /// Determine what happens when a window is moved across a monitor boundary (default: Swap)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub cross_monitor_move_behaviour: Option<MoveBehaviour>, pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
@@ -366,7 +463,7 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_offset")] #[serde(alias = "active_window_border_offset")]
pub border_offset: Option<i32>, 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(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border")] #[serde(alias = "active_window_border")]
pub border: Option<bool>, pub border: Option<bool>,
@@ -457,6 +554,7 @@ pub struct StaticConfig {
/// Komorebi status bar configuration files for multiple instances on different monitors /// Komorebi status bar configuration files for multiple instances on different monitors
// this option is a little special because it is only consumed by komorebic // this option is a little special because it is only consumed by komorebic
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<Vec<ResolvedPathBuf>>")]
pub bar_configurations: Option<Vec<PathBuf>>, pub bar_configurations: Option<Vec<PathBuf>>,
/// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars /// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -464,6 +562,9 @@ pub struct StaticConfig {
/// Aspect ratio to resize with when toggling floating mode for a window /// Aspect ratio to resize with when toggling floating mode for a window
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub floating_window_aspect_ratio: Option<AspectRatio>, pub floating_window_aspect_ratio: Option<AspectRatio>,
/// Which Windows API behaviour to use when manipulating windows (default: Sync)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_handling_behaviour: Option<WindowHandlingBehaviour>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -482,7 +583,7 @@ pub struct AnimationsConfig {
pub fps: Option<u64>, pub fps: Option<u64>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")] #[serde(tag = "palette")]
pub enum KomorebiTheme { pub enum KomorebiTheme {
@@ -556,6 +657,41 @@ pub enum KomorebiTheme {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
bar_accent: Option<komorebi_themes::Base16Value>, 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 { impl StaticConfig {
@@ -700,6 +836,9 @@ impl From<&WindowManager> for StaticConfig {
unfocused: Option::from(Colour::from( unfocused: Option::from(Colour::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst), border_manager::UNFOCUSED.load(Ordering::SeqCst),
)), )),
unfocused_locked: Option::from(Colour::from(
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
)),
}) })
}; };
@@ -710,6 +849,21 @@ impl From<&WindowManager> for StaticConfig {
value.window_management_behaviour.current_behaviour, value.window_management_behaviour.current_behaviour,
), ),
float_override: Option::from(value.window_management_behaviour.float_override), float_override: Option::from(value.window_management_behaviour.float_override),
floating_layer_behaviour: Option::from(
value.window_management_behaviour.floating_layer_behaviour,
),
toggle_float_placement: Option::from(
value.window_management_behaviour.toggle_float_placement,
),
floating_layer_placement: Option::from(
value.window_management_behaviour.floating_layer_placement,
),
float_override_placement: Option::from(
value.window_management_behaviour.float_override_placement,
),
float_rule_placement: Option::from(
value.window_management_behaviour.float_rule_placement,
),
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour), cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour), cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour),
unmanaged_window_operation_behaviour: Option::from( unmanaged_window_operation_behaviour: Option::from(
@@ -772,6 +926,7 @@ impl From<&WindowManager> for StaticConfig {
bar_configurations: None, bar_configurations: None,
remove_titlebar_applications: Option::from(NO_TITLEBAR.lock().clone()), remove_titlebar_applications: Option::from(NO_TITLEBAR.lock().clone()),
floating_window_aspect_ratio: Option::from(*FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock()), floating_window_aspect_ratio: Option::from(*FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock()),
window_handling_behaviour: Option::from(WINDOW_HANDLING_BEHAVIOUR.load()),
} }
} }
} }
@@ -856,10 +1011,7 @@ impl StaticConfig {
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst); 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); border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
border_manager::BORDER_ENABLED.store(self.border.unwrap_or(true), Ordering::SeqCst);
if let Some(enabled) = &self.border {
border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst);
}
if let Some(colours) = &self.border_colours { if let Some(colours) = &self.border_colours {
if let Some(single) = colours.single { if let Some(single) = colours.single {
@@ -881,6 +1033,11 @@ impl StaticConfig {
if let Some(unfocused) = colours.unfocused { if let Some(unfocused) = colours.unfocused {
border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst); 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()); STYLE.store(self.border_style.unwrap_or_default());
@@ -1021,7 +1178,7 @@ impl StaticConfig {
} }
if let Some(theme) = &self.theme { 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 { if let Some(path) = &self.app_specific_configuration_path {
@@ -1057,6 +1214,10 @@ impl StaticConfig {
} }
} }
if let Some(behaviour) = self.window_handling_behaviour {
WINDOW_HANDLING_BEHAVIOUR.store(behaviour);
}
Ok(()) Ok(())
} }
@@ -1066,44 +1227,7 @@ impl StaticConfig {
pub fn read(path: &PathBuf) -> Result<Self> { pub fn read(path: &PathBuf) -> Result<Self> {
let content = std::fs::read_to_string(path)?; let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?; serde_json::from_str(&content).map_err(Into::into)
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)
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
@@ -1148,6 +1272,16 @@ impl StaticConfig {
.window_container_behaviour .window_container_behaviour
.unwrap_or(WindowContainerBehaviour::Create), .unwrap_or(WindowContainerBehaviour::Create),
float_override: value.float_override.unwrap_or_default(), float_override: value.float_override.unwrap_or_default(),
floating_layer_override: false, // this value is always automatically calculated
floating_layer_behaviour: FloatingLayerBehaviour::default(),
toggle_float_placement: value
.toggle_float_placement
.unwrap_or(Placement::CenterAndResize),
floating_layer_placement: value
.floating_layer_placement
.unwrap_or(Placement::Center),
float_override_placement: value.float_override_placement.unwrap_or(Placement::None),
float_rule_placement: value.float_rule_placement.unwrap_or(Placement::None),
}, },
cross_monitor_move_behaviour: value cross_monitor_move_behaviour: value
.cross_monitor_move_behaviour .cross_monitor_move_behaviour
@@ -1165,7 +1299,7 @@ impl StaticConfig {
has_pending_raise_op: false, has_pending_raise_op: false,
pending_move_op: Arc::new(None), pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())), already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0, uncloak_to_ignore: 0,
known_hwnds: HashMap::new(), known_hwnds: HashMap::new(),
}; };
@@ -1256,6 +1390,8 @@ impl StaticConfig {
); );
monitor.set_container_padding(monitor_config.container_padding); monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_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); monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() { for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
@@ -1341,6 +1477,7 @@ impl StaticConfig {
); );
m.set_container_padding(monitor_config.container_padding); m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding); m.set_workspace_padding(monitor_config.workspace_padding);
m.set_floating_layer_behaviour(monitor_config.floating_layer_behaviour);
m.update_workspaces_globals(offset); m.update_workspaces_globals(offset);
@@ -1427,6 +1564,8 @@ impl StaticConfig {
); );
monitor.set_container_padding(monitor_config.container_padding); monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_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); monitor.update_workspaces_globals(offset);
@@ -1513,6 +1652,7 @@ impl StaticConfig {
); );
m.set_container_padding(monitor_config.container_padding); m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding); m.set_workspace_padding(monitor_config.workspace_padding);
m.set_floating_layer_behaviour(monitor_config.floating_layer_behaviour);
m.update_workspaces_globals(offset); m.update_workspaces_globals(offset);
@@ -1529,41 +1669,32 @@ impl StaticConfig {
wm.enforce_workspace_rules()?; wm.enforce_workspace_rules()?;
if let Some(enabled) = value.border { border_manager::BORDER_ENABLED.store(value.border.unwrap_or(true), Ordering::SeqCst);
border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst); wm.window_management_behaviour.current_behaviour =
} value.window_container_behaviour.unwrap_or_default();
wm.window_management_behaviour.float_override = value.float_override.unwrap_or_default();
if let Some(val) = value.window_container_behaviour { wm.window_management_behaviour.floating_layer_behaviour =
wm.window_management_behaviour.current_behaviour = val; value.floating_layer_behaviour.unwrap_or_default();
} wm.window_management_behaviour.toggle_float_placement = value
.toggle_float_placement
if let Some(val) = value.float_override { .unwrap_or(Placement::CenterAndResize);
wm.window_management_behaviour.float_override = val; wm.window_management_behaviour.floating_layer_placement =
} value.floating_layer_placement.unwrap_or(Placement::Center);
wm.window_management_behaviour.float_override_placement =
if let Some(val) = value.cross_monitor_move_behaviour { value.float_override_placement.unwrap_or(Placement::None);
wm.cross_monitor_move_behaviour = val; wm.window_management_behaviour.float_rule_placement =
} value.float_rule_placement.unwrap_or(Placement::None);
wm.cross_monitor_move_behaviour = value.cross_monitor_move_behaviour.unwrap_or_default();
if let Some(val) = value.cross_boundary_behaviour { wm.cross_boundary_behaviour = value.cross_boundary_behaviour.unwrap_or_default();
wm.cross_boundary_behaviour = val; wm.unmanaged_window_operation_behaviour = value
} .unmanaged_window_operation_behaviour
.unwrap_or_default();
if let Some(val) = value.unmanaged_window_operation_behaviour { wm.resize_delta = value.resize_delta.unwrap_or(50);
wm.unmanaged_window_operation_behaviour = val; wm.mouse_follows_focus = value.mouse_follows_focus.unwrap_or(true);
}
if let Some(val) = value.resize_delta {
wm.resize_delta = val;
}
if let Some(val) = value.mouse_follows_focus {
wm.mouse_follows_focus = val;
}
wm.work_area_offset = value.global_work_area_offset; wm.work_area_offset = value.global_work_area_offset;
wm.focus_follows_mouse = value.focus_follows_mouse;
match value.focus_follows_mouse { match wm.focus_follows_mouse {
None => WindowsApi::disable_focus_follows_mouse()?, None => WindowsApi::disable_focus_follows_mouse()?,
Some(FocusFollowsMouseImplementation::Windows) => { Some(FocusFollowsMouseImplementation::Windows) => {
WindowsApi::enable_focus_follows_mouse()?; WindowsApi::enable_focus_follows_mouse()?;
@@ -1571,12 +1702,12 @@ impl StaticConfig {
Some(FocusFollowsMouseImplementation::Komorebi) => {} Some(FocusFollowsMouseImplementation::Komorebi) => {}
}; };
wm.focus_follows_mouse = value.focus_follows_mouse;
let monitor_count = wm.monitors().len(); let monitor_count = wm.monitors().len();
for i in 0..monitor_count { for i in 0..monitor_count {
wm.update_focused_workspace_by_monitor_idx(i)?; 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(()) Ok(())
@@ -1665,7 +1796,6 @@ fn handle_asc_file(
Some(ext) => match ext.to_string_lossy().to_string().as_str() { Some(ext) => match ext.to_string_lossy().to_string().as_str() {
"yaml" => { "yaml" => {
tracing::info!("loading applications.yaml from: {}", path.display()); tracing::info!("loading applications.yaml from: {}", path.display());
let path = resolve_home_path(path)?;
let content = std::fs::read_to_string(path)?; let content = std::fs::read_to_string(path)?;
let asc = ApplicationConfigurationGenerator::load(&content)?; let asc = ApplicationConfigurationGenerator::load(&content)?;
@@ -1714,8 +1844,7 @@ fn handle_asc_file(
} }
"json" => { "json" => {
tracing::info!("loading applications.json from: {}", path.display()); 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() { for entry in asc.values_mut() {
match entry { match entry {
@@ -1777,7 +1906,10 @@ fn handle_asc_file(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::PathBuf;
use crate::StaticConfig; use crate::StaticConfig;
use crate::WorkspaceConfig;
#[test] #[test]
fn backwards_compat() { fn backwards_compat() {
@@ -1806,4 +1938,40 @@ mod tests {
StaticConfig::read_raw(&config).unwrap(); 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);
}
} }
+73 -11
View File
@@ -5,11 +5,12 @@ use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR; use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR; use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR; use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::Colour;
use crate::KomorebiTheme; use crate::KomorebiTheme;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell; use crossbeam_utils::atomic::AtomicCell;
use komorebi_themes::colour::Colour;
use komorebi_themes::Base16Wrapper;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::OnceLock; use std::sync::OnceLock;
@@ -157,39 +158,100 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
} => { } => {
let single_border = single_border let single_border = single_border
.unwrap_or(komorebi_themes::Base16Value::Base0D) .unwrap_or(komorebi_themes::Base16Value::Base0D)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let stack_border = stack_border let stack_border = stack_border
.unwrap_or(komorebi_themes::Base16Value::Base0B) .unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let monocle_border = monocle_border let monocle_border = monocle_border
.unwrap_or(komorebi_themes::Base16Value::Base0F) .unwrap_or(komorebi_themes::Base16Value::Base0F)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let unfocused_border = unfocused_border let unfocused_border = unfocused_border
.unwrap_or(komorebi_themes::Base16Value::Base01) .unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let unfocused_locked_border = unfocused_locked_border let unfocused_locked_border = unfocused_locked_border
.unwrap_or(komorebi_themes::Base16Value::Base08) .unwrap_or(komorebi_themes::Base16Value::Base08)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let floating_border = floating_border let floating_border = floating_border
.unwrap_or(komorebi_themes::Base16Value::Base09) .unwrap_or(komorebi_themes::Base16Value::Base09)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let stackbar_focused_text = stackbar_focused_text let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::Base16Value::Base0B) .unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let stackbar_unfocused_text = stackbar_unfocused_text let stackbar_unfocused_text = stackbar_unfocused_text
.unwrap_or(komorebi_themes::Base16Value::Base05) .unwrap_or(komorebi_themes::Base16Value::Base05)
.color32(*name); .color32(Base16Wrapper::Base16(*name));
let stackbar_background = stackbar_background let stackbar_background = stackbar_background
.unwrap_or(komorebi_themes::Base16Value::Base01) .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, single_border,
@@ -233,7 +295,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
CURRENT_THEME.store(Some(notification.0)); CURRENT_THEME.store(Some(notification.0));
border_manager::send_notification(None); border_manager::send_force_update();
stackbar_manager::send_notification(); stackbar_manager::send_notification();
} }
+13 -5
View File
@@ -203,8 +203,8 @@ impl RenderDispatcher for MovementRenderDispatcher {
fn render(&self, progress: f64) -> Result<()> { fn render(&self, progress: f64) -> Result<()> {
let new_rect = self.start_rect.lerp(self.target_rect, progress, self.style); let new_rect = self.start_rect.lerp(self.target_rect, progress, self.style);
// using MoveWindow because it runs faster than SetWindowPos // we don't check WINDOW_HANDLING_BEHAVIOUR here because animations
// so animation have more fps and feel smoother // are always run on a separate thread
WindowsApi::move_window(self.hwnd, &new_rect, false)?; WindowsApi::move_window(self.hwnd, &new_rect, false)?;
WindowsApi::invalidate_rect(self.hwnd, None, false); WindowsApi::invalidate_rect(self.hwnd, None, false);
@@ -212,7 +212,9 @@ impl RenderDispatcher for MovementRenderDispatcher {
} }
fn post_render(&self) -> Result<()> { fn post_render(&self) -> Result<()> {
WindowsApi::position_window(self.hwnd, &self.target_rect, self.top)?; // we don't add the async_window_pos flag here because animations
// are always run on a separate thread
WindowsApi::position_window(self.hwnd, &self.target_rect, self.top, false)?;
if ANIMATION_MANAGER if ANIMATION_MANAGER
.lock() .lock()
.count_in_progress(MovementRenderDispatcher::PREFIX) .count_in_progress(MovementRenderDispatcher::PREFIX)
@@ -411,12 +413,18 @@ impl Window {
Ok(()) Ok(())
} }
pub fn center(&mut self, work_area: &Rect) -> Result<()> { pub fn center(&mut self, work_area: &Rect, resize: bool) -> Result<()> {
let (target_width, target_height) = if resize {
let (aspect_ratio_width, aspect_ratio_height) = FLOATING_WINDOW_TOGGLE_ASPECT_RATIO let (aspect_ratio_width, aspect_ratio_height) = FLOATING_WINDOW_TOGGLE_ASPECT_RATIO
.lock() .lock()
.width_and_height(); .width_and_height();
let target_height = work_area.bottom / 2; let target_height = work_area.bottom / 2;
let target_width = (target_height * aspect_ratio_width) / aspect_ratio_height; let target_width = (target_height * aspect_ratio_width) / aspect_ratio_height;
(target_width, target_height)
} else {
let current_rect = WindowsApi::window_rect(self.hwnd)?;
(current_rect.right, current_rect.bottom)
};
let x = work_area.left + ((work_area.right - target_width) / 2); let x = work_area.left + ((work_area.right - target_width) / 2);
let y = work_area.top + ((work_area.bottom - target_height) / 2); let y = work_area.top + ((work_area.bottom - target_height) / 2);
@@ -461,7 +469,7 @@ impl Window {
AnimationEngine::animate(render_dispatcher, duration) AnimationEngine::animate(render_dispatcher, duration)
} else { } else {
WindowsApi::position_window(self.hwnd, layout, top) WindowsApi::position_window(self.hwnd, layout, top, true)
} }
} }
+624 -19
View File
@@ -80,7 +80,6 @@ use crate::workspace::WorkspaceLayer;
use crate::BorderColours; use crate::BorderColours;
use crate::Colour; use crate::Colour;
use crate::CrossBoundaryBehaviour; use crate::CrossBoundaryBehaviour;
use crate::FloatingLayerBehaviour;
use crate::Rgb; use crate::Rgb;
use crate::CUSTOM_FFM; use crate::CUSTOM_FFM;
use crate::DATA_DIR; use crate::DATA_DIR;
@@ -121,7 +120,7 @@ pub struct WindowManager {
pub has_pending_raise_op: bool, pub has_pending_raise_op: bool,
pub pending_move_op: Arc<Option<(usize, usize, isize)>>, pub pending_move_op: Arc<Option<(usize, usize, isize)>>,
pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>, pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,
pub uncloack_to_ignore: usize, pub uncloak_to_ignore: usize,
/// Maps each known window hwnd to the (monitor, workspace) index pair managing it /// Maps each known window hwnd to the (monitor, workspace) index pair managing it
pub known_hwnds: HashMap<isize, (usize, usize)>, pub known_hwnds: HashMap<isize, (usize, usize)>,
} }
@@ -248,6 +247,9 @@ impl Default for GlobalState {
unfocused: Option::from(Colour::Rgb(Rgb::from( unfocused: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst), 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_style: STYLE.load(),
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst), border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
@@ -345,6 +347,7 @@ impl From<&WindowManager> for State {
floating_layer_behaviour: workspace.floating_layer_behaviour, floating_layer_behaviour: workspace.floating_layer_behaviour,
globals: workspace.globals, globals: workspace.globals,
locked_containers: workspace.locked_containers.clone(), locked_containers: workspace.locked_containers.clone(),
wallpaper: workspace.wallpaper.clone(),
workspace_config: None, workspace_config: None,
}) })
.collect::<VecDeque<_>>(); .collect::<VecDeque<_>>();
@@ -355,6 +358,8 @@ impl From<&WindowManager> for State {
workspace_names: monitor.workspace_names.clone(), workspace_names: monitor.workspace_names.clone(),
container_padding: monitor.container_padding, container_padding: monitor.container_padding,
workspace_padding: monitor.workspace_padding, workspace_padding: monitor.workspace_padding,
wallpaper: monitor.wallpaper.clone(),
floating_layer_behaviour: monitor.floating_layer_behaviour,
}) })
.collect::<VecDeque<_>>(); .collect::<VecDeque<_>>();
stripped_monitors.focus(wm.monitors.focused_idx()); stripped_monitors.focus(wm.monitors.focused_idx());
@@ -433,7 +438,7 @@ impl WindowManager {
work_area_offset: None, work_area_offset: None,
window_management_behaviour: WindowManagementBehaviour::default(), window_management_behaviour: WindowManagementBehaviour::default(),
cross_monitor_move_behaviour: MoveBehaviour::Swap, cross_monitor_move_behaviour: MoveBehaviour::Swap,
cross_boundary_behaviour: CrossBoundaryBehaviour::Workspace, cross_boundary_behaviour: CrossBoundaryBehaviour::Monitor,
unmanaged_window_operation_behaviour: OperationBehaviour::Op, unmanaged_window_operation_behaviour: OperationBehaviour::Op,
resize_delta: 50, resize_delta: 50,
focus_follows_mouse: None, focus_follows_mouse: None,
@@ -442,7 +447,7 @@ impl WindowManager {
has_pending_raise_op: false, has_pending_raise_op: false,
pending_move_op: Arc::new(None), pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())), already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0, uncloak_to_ignore: 0,
known_hwnds: HashMap::new(), known_hwnds: HashMap::new(),
}) })
} }
@@ -640,24 +645,40 @@ impl WindowManager {
self.window_management_behaviour.current_behaviour self.window_management_behaviour.current_behaviour
}; };
let mut float_override = if let Some(float_override) = workspace.float_override() { let float_override = if let Some(float_override) = workspace.float_override() {
*float_override *float_override
} else { } else {
self.window_management_behaviour.float_override self.window_management_behaviour.float_override
}; };
// If the workspace layer is `Floating` and the floating layer behaviour is `Float`, let floating_layer_behaviour =
// then consider it as if it had float override so that new windows spawn as floating if let Some(behaviour) = workspace.floating_layer_behaviour() {
float_override = float_override behaviour
|| (matches!(workspace.layer, WorkspaceLayer::Floating) } else {
&& matches!( monitor
workspace.floating_layer_behaviour, .floating_layer_behaviour()
FloatingLayerBehaviour::Float .unwrap_or(self.window_management_behaviour.floating_layer_behaviour)
)); };
// If the workspace layer is `Floating` and the floating layer behaviour should
// float then change floating_layer_override to true so that new windows spawn
// as floating
let floating_layer_override = matches!(workspace.layer, WorkspaceLayer::Floating)
&& floating_layer_behaviour.should_float();
return WindowManagementBehaviour { return WindowManagementBehaviour {
current_behaviour, current_behaviour,
float_override, float_override,
floating_layer_override,
floating_layer_behaviour,
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
floating_layer_placement: self
.window_management_behaviour
.floating_layer_placement,
float_override_placement: self
.window_management_behaviour
.float_override_placement,
float_rule_placement: self.window_management_behaviour.float_rule_placement,
}; };
} }
} }
@@ -665,6 +686,12 @@ impl WindowManager {
WindowManagementBehaviour { WindowManagementBehaviour {
current_behaviour: WindowContainerBehaviour::Create, current_behaviour: WindowContainerBehaviour::Create,
float_override: self.window_management_behaviour.float_override, float_override: self.window_management_behaviour.float_override,
floating_layer_override: self.window_management_behaviour.floating_layer_override,
floating_layer_behaviour: self.window_management_behaviour.floating_layer_behaviour,
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
floating_layer_placement: self.window_management_behaviour.floating_layer_placement,
float_override_placement: self.window_management_behaviour.float_override_placement,
float_rule_placement: self.window_management_behaviour.float_rule_placement,
} }
} }
@@ -1010,6 +1037,8 @@ impl WindowManager {
let focused_workspace_idx = monitor.focused_workspace_idx(); let focused_workspace_idx = monitor.focused_workspace_idx();
monitor.update_workspace_globals(focused_workspace_idx, offset); monitor.update_workspace_globals(focused_workspace_idx, offset);
let hmonitor = monitor.id();
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor let workspace = monitor
.focused_workspace_mut() .focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?; .ok_or_else(|| anyhow!("there is no workspace"))?;
@@ -1021,6 +1050,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()?; workspace.update()?;
} }
@@ -1199,7 +1234,7 @@ impl WindowManager {
// That workspace reconciliation would focus the window on the origin monitor. // That workspace reconciliation would focus the window on the origin monitor.
// So we need to ignore the uncloak events produced by the origin workspace // So we need to ignore the uncloak events produced by the origin workspace
// restore to avoid that issue. // restore to avoid that issue.
self.uncloack_to_ignore = uncloack_amount; self.uncloak_to_ignore = uncloack_amount;
} }
} else if origin_workspace } else if origin_workspace
.maximized_window() .maximized_window()
@@ -1482,7 +1517,7 @@ impl WindowManager {
} }
} }
WindowsApi::position_window(window.hwnd, &rect, false)?; WindowsApi::position_window(window.hwnd, &rect, false, true)?;
if mouse_follows_focus { if mouse_follows_focus {
WindowsApi::center_cursor_in_rect(&rect)?; WindowsApi::center_cursor_in_rect(&rect)?;
} }
@@ -1704,6 +1739,30 @@ impl WindowManager {
Ok(()) 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<()> { pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {
let offset = self.work_area_offset; let offset = self.work_area_offset;
@@ -1864,6 +1923,18 @@ impl WindowManager {
.focused_workspace_mut() .focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no focused workspace on target monitor"))?; .ok_or_else(|| anyhow!("there is no focused workspace on target monitor"))?;
if target_workspace.monocle_container().is_some() {
for container in target_workspace.containers_mut() {
container.restore();
}
for window in target_workspace.floating_windows_mut() {
window.restore();
}
target_workspace.reintegrate_monocle_container()?;
}
if let Some(window) = floating_window { if let Some(window) = floating_window {
target_workspace.floating_windows_mut().push_back(window); target_workspace.floating_windows_mut().push_back(window);
target_workspace.set_layer(WorkspaceLayer::Floating); target_workspace.set_layer(WorkspaceLayer::Floating);
@@ -2462,7 +2533,7 @@ impl WindowManager {
} }
} }
WindowsApi::position_window(window.hwnd, &rect, false)?; WindowsApi::position_window(window.hwnd, &rect, false, true)?;
if mouse_follows_focus { if mouse_follows_focus {
WindowsApi::center_cursor_in_rect(&rect)?; WindowsApi::center_cursor_in_rect(&rect)?;
} }
@@ -3057,7 +3128,7 @@ impl WindowManager {
} }
#[tracing::instrument(skip(self))] #[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 hwnd = WindowsApi::foreground_window()?;
let workspace = self.focused_workspace_mut()?; let workspace = self.focused_workspace_mut()?;
@@ -3069,7 +3140,7 @@ impl WindowManager {
} }
} }
if is_floating_window { if is_floating_window && !force_float {
workspace.set_layer(WorkspaceLayer::Tiling); workspace.set_layer(WorkspaceLayer::Tiling);
self.unfloat_window()?; self.unfloat_window()?;
} else { } else {
@@ -3100,6 +3171,8 @@ impl WindowManager {
let work_area = self.focused_monitor_work_area()?; let work_area = self.focused_monitor_work_area()?;
let toggle_float_placement = self.window_management_behaviour.toggle_float_placement;
let workspace = self.focused_workspace_mut()?; let workspace = self.focused_workspace_mut()?;
workspace.new_floating_window()?; workspace.new_floating_window()?;
@@ -3108,7 +3181,9 @@ impl WindowManager {
.back_mut() .back_mut()
.ok_or_else(|| anyhow!("there is no floating window"))?; .ok_or_else(|| anyhow!("there is no floating window"))?;
window.center(&work_area)?; if toggle_float_placement.should_center() {
window.center(&work_area, toggle_float_placement.should_resize())?;
}
window.focus(self.mouse_follows_focus)?; window.focus(self.mouse_follows_focus)?;
Ok(()) Ok(())
@@ -4980,4 +5055,534 @@ mod tests {
assert!(*workspace.tile()); 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 }));
}
}
#[test]
fn test_maximize_and_unmaximize_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);
}
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, None);
}
// Maximize the focused window
wm.maximize_window().ok();
{
// Window 0 should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, Some(Window::from(0)));
}
wm.unmaximize_window().ok();
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, None);
}
// Focus container at index 1
wm.focused_workspace_mut().unwrap().focus_container(1);
{
// Focus the window at index 1
let container = wm.focused_container_mut().unwrap();
container.focus_window(1);
}
// Maximize the focused window
wm.maximize_window().ok();
{
// Window 2 should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, Some(Window::from(2)));
}
wm.unmaximize_window().ok();
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, None);
}
}
#[test]
fn test_toggle_maximize() {
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);
}
// Toggle maximize on
wm.toggle_maximize().ok();
{
// Window 0 should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, Some(Window::from(0)));
}
// Toggle maximize off
wm.toggle_maximize().ok();
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window();
assert_eq!(*maximized_window, None);
}
}
#[test]
fn test_monocle_on_and_monocle_off() {
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 a window to the container
container.windows_mut().push_back(Window::from(1));
// Should have 1 window in the container
assert_eq!(container.windows().len(), 1);
// 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);
}
// Move container to monocle container
wm.monocle_on().ok();
{
// Container should be a monocle container
let monocle_container = wm
.focused_workspace()
.unwrap()
.monocle_container()
.as_ref()
.unwrap();
assert_eq!(monocle_container.windows().len(), 1);
assert_eq!(monocle_container.windows()[0].hwnd, 1);
}
{
// Should not have any containers
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 0);
}
// Move monocle container to regular container
wm.monocle_off().ok();
{
// Should have 1 container in the workspace
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 1);
assert_eq!(container.containers()[0].windows()[0].hwnd, 1);
}
{
// No windows should be in the monocle container
let monocle_container = wm.focused_workspace().unwrap().monocle_container();
assert_eq!(*monocle_container, None);
}
}
#[test]
fn test_toggle_monocle() {
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 a window to the container
container.windows_mut().push_back(Window::from(1));
// Should have 1 window in the container
assert_eq!(container.windows().len(), 1);
// 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);
}
// Toggle monocle on
wm.toggle_monocle().ok();
{
// Container should be a monocle container
let monocle_container = wm
.focused_workspace()
.unwrap()
.monocle_container()
.as_ref()
.unwrap();
assert_eq!(monocle_container.windows().len(), 1);
assert_eq!(monocle_container.windows()[0].hwnd, 1);
}
{
// Should not have any containers
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 0);
}
// Toggle monocle off
wm.toggle_monocle().ok();
{
// Should have 1 container in the workspace
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 1);
assert_eq!(container.containers()[0].windows()[0].hwnd, 1);
}
{
// No windows should be in the monocle container
let monocle_container = wm.focused_workspace().unwrap().monocle_container();
assert_eq!(*monocle_container, None);
}
}
#[test]
fn test_ensure_named_workspace_for_monitor() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
{
// Create a monitor
let m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor1".to_string(),
"TestDevice1".to_string(),
"TestDeviceID1".to_string(),
Some("TestMonitorID1".to_string()),
);
// Add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Workspace names list
let mut workspace_names = vec!["Workspace".to_string(), "Workspace1".to_string()];
// Ensure workspaces for monitor 1
wm.ensure_named_workspaces_for_monitor(1, &workspace_names)
.ok();
{
// Monitor 1 should have 2 workspaces with names "Workspace" and "Workspace1"
let monitor = wm.monitors().get(1).unwrap();
let workspaces = monitor.workspaces();
assert_eq!(workspaces.len(), workspace_names.len());
for (i, workspace) in workspaces.iter().enumerate() {
assert_eq!(workspace.name(), &Some(workspace_names[i].clone()));
}
}
// Add more workspaces to list
workspace_names.push("Workspace2".to_string());
workspace_names.push("Workspace3".to_string());
// Ensure workspaces for monitor 0
wm.ensure_named_workspaces_for_monitor(0, &workspace_names)
.ok();
{
// Monitor 0 should have 4 workspaces with names "Workspace", "Workspace1",
// "Workspace2" and "Workspace3"
let monitor = wm.monitors().front().unwrap();
let workspaces = monitor.workspaces();
assert_eq!(workspaces.len(), workspace_names.len());
for (i, workspace) in workspaces.iter().enumerate() {
assert_eq!(workspace.name(), &Some(workspace_names[i].clone()));
}
}
}
#[test]
fn test_add_window_handle_to_move_based_on_workspace_rule() {
let (wm, _context) = setup_window_manager();
// Mock Data representing a window and its workspace/movement details
let window_title = String::from("TestWindow");
let hwnd = 12345;
let origin_monitor_idx = 0;
let origin_workspace_idx = 0;
let target_monitor_idx = 2;
let target_workspace_idx = 3;
let floating = false;
// Empty vector to hold workspace rule enforcement operations
let mut to_move: Vec<EnforceWorkspaceRuleOp> = Vec::new();
// Call the function to add a window movement operation based on workspace rules
wm.add_window_handle_to_move_based_on_workspace_rule(
&window_title,
hwnd,
origin_monitor_idx,
origin_workspace_idx,
target_monitor_idx,
target_workspace_idx,
floating,
&mut to_move,
);
// Verify that the vector contains the expected operation with the correct values
assert_eq!(to_move.len(), 1);
let op = &to_move[0];
assert_eq!(op.hwnd, hwnd); // 12345
assert_eq!(op.origin_monitor_idx, origin_monitor_idx); // 0
assert_eq!(op.origin_workspace_idx, origin_workspace_idx); // 0
assert_eq!(op.target_monitor_idx, target_monitor_idx); // 2
assert_eq!(op.target_workspace_idx, target_workspace_idx); // 3
assert_eq!(op.floating, floating); // false
}
} }
+124 -14
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 core::ffi::c_void;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::mem::size_of; use std::mem::size_of;
use std::path::Path;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::eyre::Error;
use color_eyre::Result;
use windows::core::Result as WindowsCrateResult; use windows::core::Result as WindowsCrateResult;
use windows::core::PCWSTR; use windows::core::PCWSTR;
use windows::core::PWSTR; 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::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFOEXW; use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST; 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::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::Power::RegisterPowerSettingNotification; use windows::Win32::System::Power::RegisterPowerSettingNotification;
use windows::Win32::System::Power::HPOWERNOTIFY; 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::MOUSEINPUT;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON; use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU; 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::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop; use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW; use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
@@ -101,6 +106,7 @@ use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW; use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos; use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow; use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::ShowWindowAsync;
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW; use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint; use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT; use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
@@ -120,6 +126,7 @@ use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT; use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING; use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT; use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE; use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE; use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW; use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
@@ -141,6 +148,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP; use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU; use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use windows_core::BOOL; use windows_core::BOOL;
use windows_core::HSTRING;
use crate::core::Rect; use crate::core::Rect;
@@ -151,10 +159,12 @@ use crate::ring::Ring;
use crate::set_window_position::SetWindowPosition; use crate::set_window_position::SetWindowPosition;
use crate::windows_callbacks; use crate::windows_callbacks;
use crate::Window; use crate::Window;
use crate::WindowHandlingBehaviour;
use crate::WindowManager; use crate::WindowManager;
use crate::DISPLAY_INDEX_PREFERENCES; use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS; use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::MONITOR_INDEX_PREFERENCES; use crate::MONITOR_INDEX_PREFERENCES;
use crate::WINDOW_HANDLING_BEHAVIOUR;
macro_rules! as_ptr { macro_rules! as_ptr {
($value:expr) => { ($value:expr) => {
@@ -467,7 +477,12 @@ impl WindowsApi {
/// position window resizes the target window to the given layout, adjusting /// position window resizes the target window to the given layout, adjusting
/// the layout to account for any window shadow borders (the window painted /// the layout to account for any window shadow borders (the window painted
/// region will match layout on completion). /// region will match layout on completion).
pub fn position_window(hwnd: isize, layout: &Rect, top: bool) -> Result<()> { pub fn position_window(
hwnd: isize,
layout: &Rect,
top: bool,
with_async_window_pos: bool,
) -> Result<()> {
let hwnd = HWND(as_ptr!(hwnd)); let hwnd = HWND(as_ptr!(hwnd));
let mut flags = SetWindowPosition::NO_ACTIVATE let mut flags = SetWindowPosition::NO_ACTIVATE
@@ -478,6 +493,19 @@ impl WindowsApi {
// If the request is to place the window on top, then HWND_TOP will take // If the request is to place the window on top, then HWND_TOP will take
// effect, otherwise pass NO_Z_ORDER that will cause set_window_pos to // effect, otherwise pass NO_Z_ORDER that will cause set_window_pos to
// ignore the z-order paramter. // ignore the z-order paramter.
// By default SetWindowPos waits for target window's WindowProc thread
// to process the message, so we have to use ASYNC_WINDOW_POS to avoid
// blocking our thread in case the target window is not responding.
if with_async_window_pos
&& matches!(
WINDOW_HANDLING_BEHAVIOUR.load(),
WindowHandlingBehaviour::Async
)
{
flags |= SetWindowPosition::ASYNC_WINDOW_POS;
}
if !top { if !top {
flags |= SetWindowPosition::NO_Z_ORDER; flags |= SetWindowPosition::NO_Z_ORDER;
} }
@@ -512,11 +540,18 @@ impl WindowsApi {
/// Raise the window to the top of the Z order, but do not activate or focus /// Raise the window to the top of the Z order, but do not activate or focus
/// it. Use raise_and_focus_window to activate and focus a window. /// it. Use raise_and_focus_window to activate and focus a window.
pub fn raise_window(hwnd: isize) -> Result<()> { pub fn raise_window(hwnd: isize) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE let mut flags = SetWindowPosition::NO_MOVE
| SetWindowPosition::NO_SIZE | SetWindowPosition::NO_SIZE
| SetWindowPosition::NO_ACTIVATE | SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::SHOW_WINDOW; | SetWindowPosition::SHOW_WINDOW;
if matches!(
WINDOW_HANDLING_BEHAVIOUR.load(),
WindowHandlingBehaviour::Async
) {
flags |= SetWindowPosition::ASYNC_WINDOW_POS;
}
let position = HWND_TOP; let position = HWND_TOP;
Self::set_window_pos( Self::set_window_pos(
HWND(as_ptr!(hwnd)), HWND(as_ptr!(hwnd)),
@@ -529,11 +564,18 @@ impl WindowsApi {
/// Lower the window to the bottom of the Z order, but do not activate or focus /// Lower the window to the bottom of the Z order, but do not activate or focus
/// it. /// it.
pub fn lower_window(hwnd: isize) -> Result<()> { pub fn lower_window(hwnd: isize) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE let mut flags = SetWindowPosition::NO_MOVE
| SetWindowPosition::NO_SIZE | SetWindowPosition::NO_SIZE
| SetWindowPosition::NO_ACTIVATE | SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::SHOW_WINDOW; | SetWindowPosition::SHOW_WINDOW;
if matches!(
WINDOW_HANDLING_BEHAVIOUR.load(),
WindowHandlingBehaviour::Async
) {
flags |= SetWindowPosition::ASYNC_WINDOW_POS;
}
let position = HWND_BOTTOM; let position = HWND_BOTTOM;
Self::set_window_pos( Self::set_window_pos(
HWND(as_ptr!(hwnd)), HWND(as_ptr!(hwnd)),
@@ -544,12 +586,17 @@ impl WindowsApi {
} }
pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> { pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> {
let flags = { let mut flags = SetWindowPosition::NO_SEND_CHANGING
SetWindowPosition::NO_SEND_CHANGING
| SetWindowPosition::NO_ACTIVATE | SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::NO_REDRAW | SetWindowPosition::NO_REDRAW
| SetWindowPosition::SHOW_WINDOW | SetWindowPosition::SHOW_WINDOW;
};
if matches!(
WINDOW_HANDLING_BEHAVIOUR.load(),
WindowHandlingBehaviour::Async
) {
flags |= SetWindowPosition::ASYNC_WINDOW_POS;
}
Self::set_window_pos( Self::set_window_pos(
HWND(as_ptr!(hwnd)), HWND(as_ptr!(hwnd)),
@@ -575,6 +622,7 @@ impl WindowsApi {
.process() .process()
} }
/// move_windows calls MoveWindow, but cannot be called with async window pos, so it might hang
pub fn move_window(hwnd: isize, layout: &Rect, repaint: bool) -> Result<()> { pub fn move_window(hwnd: isize, layout: &Rect, repaint: bool) -> Result<()> {
let hwnd = HWND(as_ptr!(hwnd)); let hwnd = HWND(as_ptr!(hwnd));
@@ -592,10 +640,19 @@ impl WindowsApi {
// BOOL is returned but does not signify whether or not the operation was succesful // BOOL is returned but does not signify whether or not the operation was succesful
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
// TODO: error handling // TODO: error handling
if matches!(
WINDOW_HANDLING_BEHAVIOUR.load(),
WindowHandlingBehaviour::Async
) {
unsafe {
let _ = ShowWindowAsync(HWND(as_ptr!(hwnd)), command);
};
} else {
unsafe { unsafe {
let _ = ShowWindow(HWND(as_ptr!(hwnd)), command); let _ = ShowWindow(HWND(as_ptr!(hwnd)), command);
}; };
} }
}
pub fn minimize_window(hwnd: isize) { pub fn minimize_window(hwnd: isize) {
Self::show_window(hwnd, SW_MINIMIZE); Self::show_window(hwnd, SW_MINIMIZE);
@@ -650,7 +707,7 @@ impl WindowsApi {
0, 0,
0, 0,
0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS,
) )
.process(); .process();
SetForegroundWindow(HWND(as_ptr!(hwnd))) SetForegroundWindow(HWND(as_ptr!(hwnd)))
@@ -933,7 +990,7 @@ impl WindowsApi {
pub fn exe(handle: HANDLE) -> Result<String> { pub fn exe(handle: HANDLE) -> Result<String> {
Ok(Self::exe_path(handle)? Ok(Self::exe_path(handle)?
.split('\\') .split('\\')
.last() .next_back()
.ok_or_else(|| anyhow!("there is no last element"))? .ok_or_else(|| anyhow!("there is no last element"))?
.to_string()) .to_string())
} }
@@ -1002,6 +1059,16 @@ impl WindowsApi {
Ok(ex_info) 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> { pub fn monitor(hmonitor: isize) -> Result<Monitor> {
for mut display in win32_display_data::connected_displays_all().flatten() { for mut display in win32_display_data::connected_displays_all().flatten() {
if display.hmonitor == hmonitor { if display.hmonitor == hmonitor {
@@ -1345,4 +1412,47 @@ impl WindowsApi {
pub fn wts_register_session_notification(hwnd: isize) -> Result<()> { pub fn wts_register_session_notification(hwnd: isize) -> Result<()> {
unsafe { WTSRegisterSessionNotification(HWND(as_ptr!(hwnd)), 1) }.process() 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()
}
} }
+209 -110
View File
@@ -1,20 +1,14 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::ffi::OsStr;
use std::fmt::Display; use std::fmt::Display;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::io::Write;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::sync::atomic::Ordering; 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::border_manager;
use crate::container::Container;
use crate::core::Axis; use crate::core::Axis;
use crate::core::CustomLayout; use crate::core::CustomLayout;
use crate::core::CycleDirection; use crate::core::CycleDirection;
@@ -22,11 +16,6 @@ use crate::core::DefaultLayout;
use crate::core::Layout; use crate::core::Layout;
use crate::core::OperationDirection; use crate::core::OperationDirection;
use crate::core::Rect; 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::locked_deque::LockedDeque;
use crate::ring::Ring; use crate::ring::Ring;
use crate::should_act; use crate::should_act;
@@ -36,13 +25,28 @@ use crate::static_config::WorkspaceConfig;
use crate::window::Window; use crate::window::Window;
use crate::window::WindowDetails; use crate::window::WindowDetails;
use crate::windows_api::WindowsApi; use crate::windows_api::WindowsApi;
use crate::FloatingLayerBehaviour;
use crate::KomorebiTheme;
use crate::SocketMessage;
use crate::Wallpaper;
use crate::WindowContainerBehaviour; use crate::WindowContainerBehaviour;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING; use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED; use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR; use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS; use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS; 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)] #[allow(clippy::struct_field_names)]
#[derive( #[derive(
@@ -88,14 +92,17 @@ pub struct Workspace {
pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>, pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get = "pub", get_mut = "pub", set = "pub")]
pub float_override: Option<bool>, pub float_override: Option<bool>,
#[serde(skip)]
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get = "pub", get_mut = "pub", set = "pub")]
pub globals: WorkspaceGlobals, pub globals: WorkspaceGlobals,
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layer: WorkspaceLayer, pub layer: WorkspaceLayer,
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get_copy = "pub", get_mut = "pub", set = "pub")]
pub floating_layer_behaviour: FloatingLayerBehaviour, pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get = "pub", get_mut = "pub", set = "pub")]
pub locked_containers: BTreeSet<usize>, pub locked_containers: BTreeSet<usize>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub wallpaper: Option<Wallpaper>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")] #[getset(get = "pub", set = "pub")]
pub workspace_config: Option<WorkspaceConfig>, pub workspace_config: Option<WorkspaceConfig>,
@@ -148,6 +155,7 @@ impl Default for Workspace {
globals: Default::default(), globals: Default::default(),
workspace_config: None, workspace_config: None,
locked_containers: Default::default(), locked_containers: Default::default(),
wallpaper: None,
} }
} }
} }
@@ -178,10 +186,13 @@ pub enum WorkspaceWindowLocation {
pub struct WorkspaceGlobals { pub struct WorkspaceGlobals {
pub container_padding: Option<i32>, pub container_padding: Option<i32>,
pub workspace_padding: Option<i32>, pub workspace_padding: Option<i32>,
pub border_width: i32,
pub border_offset: i32,
pub work_area: Rect, pub work_area: Rect,
pub work_area_offset: Option<Rect>, pub work_area_offset: Option<Rect>,
pub window_based_work_area_offset: Option<Rect>, pub window_based_work_area_offset: Option<Rect>,
pub window_based_work_area_offset_limit: isize, pub window_based_work_area_offset_limit: isize,
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
} }
impl Workspace { impl Workspace {
@@ -254,7 +265,8 @@ impl Workspace {
self.set_float_override(config.float_override); self.set_float_override(config.float_override);
self.set_layout_flip(config.layout_flip); 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())); 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(container) = self.monocle_container() {
if let Some(window) = container.focused_window() { if let Some(window) = container.focused_window() {
container.restore(); container.restore();
window.focus(mouse_follows_focus)?; 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)?; floating_window.focus(mouse_follows_focus)?;
} }
Ok(()) self.apply_wallpaper(hmonitor, monitor_wp)
} }
pub fn update(&mut self) -> Result<()> { pub fn update(&mut self) -> Result<()> {
@@ -348,6 +481,9 @@ impl Workspace {
return Ok(()); 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 let container_padding = self
.container_padding() .container_padding()
.or(self.globals().container_padding) .or(self.globals().container_padding)
@@ -356,6 +492,8 @@ impl Workspace {
.workspace_padding() .workspace_padding()
.or(self.globals().workspace_padding) .or(self.globals().workspace_padding)
.unwrap_or_default(); .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 = self.globals().work_area;
let work_area_offset = self.globals().work_area_offset; let work_area_offset = self.globals().work_area_offset;
let window_based_work_area_offset = self.globals().window_based_work_area_offset; let window_based_work_area_offset = self.globals().window_based_work_area_offset;
@@ -428,12 +566,8 @@ impl Workspace {
if let Some(container) = self.monocle_container_mut() { if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() { if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding); adjusted_work_area.add_padding(container_padding);
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
adjusted_work_area.add_padding(border_offset); adjusted_work_area.add_padding(border_offset);
let width = BORDER_WIDTH.load(Ordering::SeqCst); adjusted_work_area.add_padding(border_width);
adjusted_work_area.add_padding(width);
}
window.set_position(&adjusted_work_area, true)?; window.set_position(&adjusted_work_area, true)?;
}; };
} else if let Some(window) = self.maximized_window_mut() { } else if let Some(window) = self.maximized_window_mut() {
@@ -461,13 +595,8 @@ impl Workspace {
let window_count = container.windows().len(); let window_count = container.windows().len();
if let Some(layout) = layouts.get_mut(i) { if let Some(layout) = layouts.get_mut(i) {
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
layout.add_padding(border_offset); layout.add_padding(border_offset);
layout.add_padding(border_width);
let width = BORDER_WIDTH.load(Ordering::SeqCst);
layout.add_padding(width);
}
if stackbar_manager::should_have_stackbar(window_count) { if stackbar_manager::should_have_stackbar(window_count) {
let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst); let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
@@ -531,84 +660,6 @@ impl Workspace {
Ok(()) Ok(())
} }
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
let mut hwnds = vec![];
let mut floating_hwnds = vec![];
let mut remove_monocle = false;
let mut remove_maximized = false;
if let Some(monocle) = &self.monocle_container {
let window_count = monocle.windows().len();
let mut orphan_count = 0;
for window in monocle.windows() {
if !window.is_window() {
hwnds.push(window.hwnd);
orphan_count += 1;
}
}
remove_monocle = orphan_count == window_count;
}
if let Some(window) = &self.maximized_window {
if !window.is_window() {
hwnds.push(window.hwnd);
remove_maximized = true;
}
}
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()
{
hwnds.push(window.hwnd);
}
}
for window in self.floating_windows() {
if !window.is_window() {
floating_hwnds.push(window.hwnd);
}
}
for hwnd in &hwnds {
tracing::debug!("reaping hwnd: {}", hwnd);
self.remove_window(*hwnd)?;
}
for hwnd in &floating_hwnds {
tracing::debug!("reaping floating hwnd: {}", hwnd);
self.floating_windows_mut()
.retain(|w| !floating_hwnds.contains(&w.hwnd));
}
let mut container_ids = vec![];
for container in self.containers() {
if container.windows().is_empty() {
container_ids.push(container.id().clone());
}
}
self.containers_mut()
.retain(|c| !container_ids.contains(c.id()));
if remove_monocle {
self.set_monocle_container(None);
}
if remove_maximized {
self.set_maximized_window(None);
}
Ok((hwnds.len() + floating_hwnds.len(), container_ids.len()))
}
pub fn container_for_window(&self, hwnd: isize) -> Option<&Container> { pub fn container_for_window(&self, hwnd: isize) -> Option<&Container> {
self.containers().get(self.container_idx_for_window(hwnd)?) self.containers().get(self.container_idx_for_window(hwnd)?)
} }
@@ -1647,7 +1698,6 @@ impl Workspace {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::container::Container; use crate::container::Container;
use crate::Window; use crate::Window;
use std::collections::BTreeSet; use std::collections::BTreeSet;
@@ -2360,4 +2410,53 @@ mod tests {
let floating_windows = workspace.floating_windows_mut(); let floating_windows = workspace.floating_windows_mut();
assert!(floating_windows.contains(&Window { hwnd: 0 })); 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);
}
}
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebic-no-console" name = "komorebic-no-console"
version = "0.1.36" version = "0.1.37"
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows" description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi" repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021" edition = "2021"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "komorebic" name = "komorebic"
version = "0.1.36" version = "0.1.37"
description = "The command-line interface for Komorebi, a tiling window manager for Windows" description = "The command-line interface for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi" repository = "https://github.com/LGUG2Z/komorebi"
edition = "2021" edition = "2021"
@@ -36,7 +36,7 @@ shadow-rs = { workspace = true }
[features] [features]
default = ["schemars"] default = ["schemars"]
schemars = ["dep:schemars"] schemars = ["dep:schemars", "komorebi-client/schemars"]
[lints.rust] [lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] } unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] }
+93 -67
View File
@@ -2,12 +2,13 @@
#![allow(clippy::missing_errors_doc, clippy::doc_markdown)] #![allow(clippy::missing_errors_doc, clippy::doc_markdown)]
use chrono::Utc; use chrono::Utc;
use komorebi_client::replace_env_in_path;
use komorebi_client::PathExt;
use std::fs::File; use std::fs::File;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::BufRead; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::io::Write; use std::io::Write;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@@ -22,7 +23,6 @@ use color_eyre::eyre::bail;
use color_eyre::Result; use color_eyre::Result;
use dirs::data_local_dir; use dirs::data_local_dir;
use fs_tail::TailedFile; use fs_tail::TailedFile;
use komorebi_client::resolve_home_path;
use komorebi_client::send_message; use komorebi_client::send_message;
use komorebi_client::send_query; use komorebi_client::send_query;
use komorebi_client::AppSpecificConfigurationPath; use komorebi_client::AppSpecificConfigurationPath;
@@ -64,8 +64,7 @@ lazy_static! {
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else( std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|_| dirs::home_dir().expect("there is no home directory"), |_| dirs::home_dir().expect("there is no home directory"),
|home_path| { |home_path| {
let home = PathBuf::from(&home_path); let home = home_path.replace_env();
if home.as_path().is_dir() { if home.as_path().is_dir() {
HAS_CUSTOM_CONFIG_HOME.store(true, Ordering::SeqCst); HAS_CUSTOM_CONFIG_HOME.store(true, Ordering::SeqCst);
home home
@@ -88,12 +87,12 @@ lazy_static! {
.join(".config") .join(".config")
}, },
|home_path| { |home_path| {
let whkd_config_home = PathBuf::from(&home_path); let whkd_config_home = home_path.replace_env();
assert!( 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", "$Env:WHKD_CONFIG_HOME is set to '{}', which is not a valid directory",
whkd_config_home.to_string_lossy() home_path
); );
whkd_config_home whkd_config_home
@@ -299,6 +298,7 @@ pub struct WorkspaceCustomLayout {
workspace: usize, workspace: usize,
/// JSON or YAML file from which the custom layout definition should be loaded /// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -308,6 +308,7 @@ pub struct NamedWorkspaceCustomLayout {
workspace: String, workspace: String,
/// JSON or YAML file from which the custom layout definition should be loaded /// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -350,6 +351,7 @@ pub struct WorkspaceCustomLayoutRule {
at_container_count: usize, at_container_count: usize,
/// JSON or YAML file from which the custom layout definition should be loaded /// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -362,6 +364,7 @@ pub struct NamedWorkspaceCustomLayoutRule {
at_container_count: usize, at_container_count: usize,
/// JSON or YAML file from which the custom layout definition should be loaded /// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -770,6 +773,7 @@ struct Start {
ffm: bool, ffm: bool,
/// Path to a static configuration JSON file /// Path to a static configuration JSON file
#[clap(short, long)] #[clap(short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>, config: Option<PathBuf>,
/// Wait for 'komorebic complete-configuration' to be sent before processing events /// Wait for 'komorebic complete-configuration' to be sent before processing events
#[clap(short, long)] #[clap(short, long)]
@@ -832,18 +836,21 @@ struct Kill {
#[derive(Parser)] #[derive(Parser)]
struct SaveResize { struct SaveResize {
/// File to which the resize layout dimensions should be saved /// File to which the resize layout dimensions should be saved
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
#[derive(Parser)] #[derive(Parser)]
struct LoadResize { struct LoadResize {
/// File from which the resize layout dimensions should be loaded /// File from which the resize layout dimensions should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
#[derive(Parser)] #[derive(Parser)]
struct LoadCustomLayout { struct LoadCustomLayout {
/// JSON or YAML file from which the custom layout definition should be loaded /// JSON or YAML file from which the custom layout definition should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -874,28 +881,34 @@ struct UnsubscribePipe {
#[derive(Parser)] #[derive(Parser)]
struct AhkAppSpecificConfiguration { struct AhkAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded /// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
/// Optional YAML file of overrides to apply over the first file /// Optional YAML file of overrides to apply over the first file
#[clap(value_parser = replace_env_in_path)]
override_path: Option<PathBuf>, override_path: Option<PathBuf>,
} }
#[derive(Parser)] #[derive(Parser)]
struct PwshAppSpecificConfiguration { struct PwshAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded /// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
/// Optional YAML file of overrides to apply over the first file /// Optional YAML file of overrides to apply over the first file
#[clap(value_parser = replace_env_in_path)]
override_path: Option<PathBuf>, override_path: Option<PathBuf>,
} }
#[derive(Parser)] #[derive(Parser)]
struct FormatAppSpecificConfiguration { struct FormatAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded /// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
#[derive(Parser)] #[derive(Parser)]
struct ConvertAppSpecificConfiguration { struct ConvertAppSpecificConfiguration {
/// YAML file from which the application-specific configurations should be loaded /// YAML file from which the application-specific configurations should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -909,6 +922,7 @@ struct AltFocusHack {
struct EnableAutostart { struct EnableAutostart {
/// Path to a static configuration JSON file /// Path to a static configuration JSON file
#[clap(action, short, long)] #[clap(action, short, long)]
#[clap(value_parser = replace_env_in_path)]
config: Option<PathBuf>, config: Option<PathBuf>,
/// Enable komorebi's custom focus-follows-mouse implementation /// Enable komorebi's custom focus-follows-mouse implementation
#[clap(hide = true)] #[clap(hide = true)]
@@ -932,12 +946,14 @@ struct EnableAutostart {
struct Check { struct Check {
/// Path to a static configuration JSON file /// Path to a static configuration JSON file
#[clap(action, short, long)] #[clap(action, short, long)]
#[clap(value_parser = replace_env_in_path)]
komorebi_config: Option<PathBuf>, komorebi_config: Option<PathBuf>,
} }
#[derive(Parser)] #[derive(Parser)]
struct ReplaceConfiguration { struct ReplaceConfiguration {
/// Static configuration JSON file from which the configuration should be loaded /// Static configuration JSON file from which the configuration should be loaded
#[clap(value_parser = replace_env_in_path)]
path: PathBuf, path: PathBuf,
} }
@@ -978,12 +994,17 @@ enum SubCommand {
/// Show the path to whkdrc /// Show the path to whkdrc
#[clap(alias = "whkd")] #[clap(alias = "whkd")]
Whkdrc, 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 /// Show a JSON representation of the current window manager state
State, State,
/// Show a JSON representation of the current global state /// Show a JSON representation of the current global state
GlobalState, GlobalState,
/// Launch the komorebi-gui debugging tool /// Launch the komorebi-gui debugging tool
Gui, Gui,
/// Toggle the komorebi-shortcuts helper
ToggleShortcuts,
/// Show a JSON representation of visible windows /// Show a JSON representation of visible windows
VisibleWindows, VisibleWindows,
/// Show information about connected monitors /// Show information about connected monitors
@@ -1320,6 +1341,12 @@ enum SubCommand {
/// Set the operation behaviour when the focused window is not managed /// Set the operation behaviour when the focused window is not managed
#[clap(arg_required_else_help = true)] #[clap(arg_required_else_help = true)]
UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour), 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 /// Add a rule to ignore the specified application
#[clap(arg_required_else_help = true)] #[clap(arg_required_else_help = true)]
#[clap(alias = "float-rule")] #[clap(alias = "float-rule")]
@@ -1668,7 +1695,7 @@ fn main() -> Result<()> {
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"); println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
} }
Some(AppSpecificConfigurationPath::Single(path)) => { 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()); println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
} }
} }
@@ -1681,8 +1708,7 @@ fn main() -> Result<()> {
// errors // errors
let _ = serde_json::from_str::<StaticConfig>(&config_source)?; let _ = serde_json::from_str::<StaticConfig>(&config_source)?;
let path = resolve_home_path(static_config)?; let raw = std::fs::read_to_string(static_config)?;
let raw = std::fs::read_to_string(path)?;
StaticConfig::aliases(&raw); StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw); StaticConfig::deprecated(&raw);
StaticConfig::end_of_life(&raw); StaticConfig::end_of_life(&raw);
@@ -1754,6 +1780,12 @@ fn main() -> Result<()> {
println!("{}", whkdrc.display()); println!("{}", whkdrc.display());
} }
} }
SubCommand::DataDirectory => {
let dir = &*DATA_DIR;
if dir.exists() {
println!("{}", dir.display());
}
}
SubCommand::Log => { SubCommand::Log => {
let timestamp = Utc::now().format("%Y-%m-%d").to_string(); let timestamp = Utc::now().format("%Y-%m-%d").to_string();
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}")); let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
@@ -1979,13 +2011,13 @@ fn main() -> Result<()> {
send_message(&SocketMessage::WorkspaceLayoutCustom( send_message(&SocketMessage::WorkspaceLayoutCustom(
arg.monitor, arg.monitor,
arg.workspace, arg.workspace,
resolve_home_path(arg.path)?, arg.path,
))?; ))?;
} }
SubCommand::NamedWorkspaceCustomLayout(arg) => { SubCommand::NamedWorkspaceCustomLayout(arg) => {
send_message(&SocketMessage::NamedWorkspaceLayoutCustom( send_message(&SocketMessage::NamedWorkspaceLayoutCustom(
arg.workspace, arg.workspace,
resolve_home_path(arg.path)?, arg.path,
))?; ))?;
} }
SubCommand::WorkspaceLayoutRule(arg) => { SubCommand::WorkspaceLayoutRule(arg) => {
@@ -2008,14 +2040,14 @@ fn main() -> Result<()> {
arg.monitor, arg.monitor,
arg.workspace, arg.workspace,
arg.at_container_count, arg.at_container_count,
resolve_home_path(arg.path)?, arg.path,
))?; ))?;
} }
SubCommand::NamedWorkspaceCustomLayoutRule(arg) => { SubCommand::NamedWorkspaceCustomLayoutRule(arg) => {
send_message(&SocketMessage::NamedWorkspaceLayoutCustomRule( send_message(&SocketMessage::NamedWorkspaceLayoutCustomRule(
arg.workspace, arg.workspace,
arg.at_container_count, arg.at_container_count,
resolve_home_path(arg.path)?, arg.path,
))?; ))?;
} }
SubCommand::ClearWorkspaceLayoutRules(arg) => { SubCommand::ClearWorkspaceLayoutRules(arg) => {
@@ -2088,13 +2120,12 @@ fn main() -> Result<()> {
let mut flags = vec![]; let mut flags = vec![];
if let Some(config) = &arg.config { if let Some(config) = &arg.config {
let path = resolve_home_path(config)?; if !config.is_file() {
if !path.is_file() { bail!("could not find file: {}", config.display());
bail!("could not find file: {}", path.display());
} }
// we don't need to replace UNC prefix here as `resolve_home_path` already did let config = dunce::simplified(config);
flags.push(format!("'--config=\"{}\"'", path.display())); flags.push(format!("'--config=\"{}\"'", config.display()));
} }
if arg.ffm { if arg.ffm {
@@ -2113,17 +2144,12 @@ fn main() -> Result<()> {
flags.push("'--clean-state'".to_string()); flags.push("'--clean-state'".to_string());
} }
let exec = exec.unwrap_or("komorebi.exe");
let script = if flags.is_empty() { let script = if flags.is_empty() {
format!( format!("Start-Process '{exec}' -WindowStyle hidden",)
"Start-Process '{}' -WindowStyle hidden",
exec.unwrap_or("komorebi.exe")
)
} else { } else {
let argument_list = flags.join(","); let argument_list = flags.join(",");
format!( format!("Start-Process '{exec}' -ArgumentList {argument_list} -WindowStyle hidden",)
"Start-Process '{}' -ArgumentList {argument_list} -WindowStyle hidden",
exec.unwrap_or("komorebi.exe")
)
}; };
let mut system = sysinfo::System::new_all(); let mut system = sysinfo::System::new_all();
@@ -2166,9 +2192,8 @@ fn main() -> Result<()> {
if !running { if !running {
println!("\nRunning komorebi.exe directly for detailed error output\n"); println!("\nRunning komorebi.exe directly for detailed error output\n");
if let Some(config) = arg.config { if let Some(config) = arg.config {
let path = resolve_home_path(config)?;
if let Ok(output) = Command::new("komorebi.exe") if let Ok(output) = Command::new("komorebi.exe")
.arg(format!("'--config=\"{}\"'", path.display())) .arg(format!("'--config=\"{}\"'", config.display()))
.output() .output()
{ {
println!("{}", String::from_utf8(output.stderr)?); println!("{}", String::from_utf8(output.stderr)?);
@@ -2218,25 +2243,20 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
} }
} }
let static_config = arg.config.clone().map_or_else( let static_config = arg.config.clone().or_else(|| {
|| {
let komorebi_json = HOME_DIR.join("komorebi.json"); let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() { komorebi_json.is_file().then_some(komorebi_json)
Option::from(komorebi_json) });
} else {
None
}
},
Option::from,
);
if arg.bar { if arg.bar {
if let Some(config) = &static_config { if let Some(config) = &static_config {
let mut config = StaticConfig::read(config)?; let mut config = StaticConfig::read(config)?;
if let Some(display_bar_configurations) = &mut config.bar_configurations { if let Some(display_bar_configurations) = &mut config.bar_configurations {
for config_file_path in &mut *display_bar_configurations { for config_file_path in &mut *display_bar_configurations {
let script = r#"Start-Process "komorebi-bar" '"--config" "CONFIGFILE"' -WindowStyle hidden"# let script = format!(
.replace("CONFIGFILE", &config_file_path.to_string_lossy()); r#"Start-Process "komorebi-bar" '"--config" "{}"' -WindowStyle hidden"#,
config_file_path.to_string_lossy()
);
match powershell_script::run(&script) { match powershell_script::run(&script) {
Ok(_) => { Ok(_) => {
@@ -2298,21 +2318,13 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
println!("\n# Documentation"); println!("\n# Documentation");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands"); 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_config = arg.config.or_else(|| {
|| {
let bar_json = HOME_DIR.join("komorebi.bar.json"); let bar_json = HOME_DIR.join("komorebi.bar.json");
if bar_json.is_file() { bar_json.is_file().then_some(bar_json)
Option::from(bar_json) });
} else {
None
}
},
Option::from,
);
if let Some(config) = &static_config { if let Some(config) = &static_config {
let path = resolve_home_path(config)?; let raw = std::fs::read_to_string(config)?;
let raw = std::fs::read_to_string(path)?;
StaticConfig::aliases(&raw); StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw); StaticConfig::deprecated(&raw);
StaticConfig::end_of_life(&raw); StaticConfig::end_of_life(&raw);
@@ -2526,6 +2538,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) => { SubCommand::IgnoreRule(arg) => {
send_message(&SocketMessage::IgnoreRule(arg.identifier, arg.id))?; send_message(&SocketMessage::IgnoreRule(arg.identifier, arg.id))?;
} }
@@ -2605,9 +2626,7 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
send_message(&SocketMessage::CycleLayout(arg.cycle_direction))?; send_message(&SocketMessage::CycleLayout(arg.cycle_direction))?;
} }
SubCommand::LoadCustomLayout(arg) => { SubCommand::LoadCustomLayout(arg) => {
send_message(&SocketMessage::ChangeLayoutCustom(resolve_home_path( send_message(&SocketMessage::ChangeLayoutCustom(arg.path))?;
arg.path,
)?))?;
} }
SubCommand::FlipLayout(arg) => { SubCommand::FlipLayout(arg) => {
send_message(&SocketMessage::FlipLayout(arg.axis))?; send_message(&SocketMessage::FlipLayout(arg.axis))?;
@@ -2696,6 +2715,15 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::Gui => { SubCommand::Gui => {
Command::new("komorebi-gui").spawn()?; Command::new("komorebi-gui").spawn()?;
} }
SubCommand::ToggleShortcuts => {
let output = Command::new("taskkill")
.args(["/F", "/IM", "komorebi-shortcuts.exe"])
.output()?;
if !output.status.success() {
Command::new("komorebi-shortcuts.exe").spawn()?;
}
}
SubCommand::VisibleWindows => { SubCommand::VisibleWindows => {
print_query(&SocketMessage::VisibleWindows); print_query(&SocketMessage::VisibleWindows);
} }
@@ -2784,10 +2812,10 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
send_message(&SocketMessage::QuickLoad)?; send_message(&SocketMessage::QuickLoad)?;
} }
SubCommand::SaveResize(arg) => { SubCommand::SaveResize(arg) => {
send_message(&SocketMessage::Save(resolve_home_path(arg.path)?))?; send_message(&SocketMessage::Save(arg.path))?;
} }
SubCommand::LoadResize(arg) => { SubCommand::LoadResize(arg) => {
send_message(&SocketMessage::Load(resolve_home_path(arg.path)?))?; send_message(&SocketMessage::Load(arg.path))?;
} }
SubCommand::SubscribeSocket(arg) => { SubCommand::SubscribeSocket(arg) => {
send_message(&SocketMessage::AddSubscriberSocket(arg.socket))?; send_message(&SocketMessage::AddSubscriberSocket(arg.socket))?;
@@ -2899,9 +2927,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
))?; ))?;
} }
SubCommand::AhkAppSpecificConfiguration(arg) => { 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 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( ApplicationConfigurationGenerator::generate_ahk(
&content, &content,
@@ -2926,9 +2954,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
); );
} }
SubCommand::PwshAppSpecificConfiguration(arg) => { 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 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( ApplicationConfigurationGenerator::generate_pwsh(
&content, &content,
@@ -2953,23 +2981,21 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
); );
} }
SubCommand::ConvertAppSpecificConfiguration(arg) => { SubCommand::ConvertAppSpecificConfiguration(arg) => {
let file_path = resolve_home_path(arg.path)?; let content = std::fs::read_to_string(arg.path)?;
let content = std::fs::read_to_string(&file_path)?;
let mut asc = ApplicationConfigurationGenerator::load(&content)?; let mut asc = ApplicationConfigurationGenerator::load(&content)?;
asc.sort_by(|a, b| a.name.cmp(&b.name)); asc.sort_by(|a, b| a.name.cmp(&b.name));
let v2 = ApplicationSpecificConfiguration::from(asc); let v2 = ApplicationSpecificConfiguration::from(asc);
println!("{}", serde_json::to_string_pretty(&v2)?); println!("{}", serde_json::to_string_pretty(&v2)?);
} }
SubCommand::FormatAppSpecificConfiguration(arg) => { SubCommand::FormatAppSpecificConfiguration(arg) => {
let file_path = resolve_home_path(arg.path)?; let content = std::fs::read_to_string(&arg.path)?;
let content = std::fs::read_to_string(&file_path)?;
let formatted_content = ApplicationConfigurationGenerator::format(&content)?; let formatted_content = ApplicationConfigurationGenerator::format(&content)?;
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.truncate(true) .truncate(true)
.open(file_path)?; .open(arg.path)?;
file.write_all(formatted_content.as_bytes())?; file.write_all(formatted_content.as_bytes())?;
+10 -3
View File
@@ -49,8 +49,9 @@ plugins:
nav: nav:
- Komorebi: - Komorebi:
- About:
- index.md - index.md
- Design: design.md - design.md
- Getting started: - Getting started:
- Installation: installation.md - Installation: installation.md
- Example configurations: example-configurations.md - Example configurations: example-configurations.md
@@ -61,6 +62,10 @@ nav:
- usage/stacking-windows.md - usage/stacking-windows.md
- usage/focusing-workspaces.md - usage/focusing-workspaces.md
- usage/moving-windows-across-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:
- common-workflows/komorebi-config-home.md - common-workflows/komorebi-config-home.md
- common-workflows/autostart.md - common-workflows/autostart.md
@@ -78,8 +83,6 @@ nav:
- common-workflows/set-display-index.md - common-workflows/set-display-index.md
- common-workflows/multiple-bar-instances.md - common-workflows/multiple-bar-instances.md
- common-workflows/multi-monitor-setup.md - common-workflows/multi-monitor-setup.md
- Configuration reference: https://komorebi.lgug2z.com/schema
- Bar reference: https://komorebi-bar.lgug2z.com/schema
- CLI reference: - CLI reference:
- cli/quickstart.md - cli/quickstart.md
- cli/start.md - cli/start.md
@@ -89,6 +92,7 @@ nav:
- cli/configuration.md - cli/configuration.md
- cli/bar-configuration.md - cli/bar-configuration.md
- cli/whkdrc.md - cli/whkdrc.md
- cli/data-directory.md
- cli/state.md - cli/state.md
- cli/global-state.md - cli/global-state.md
- cli/gui.md - cli/gui.md
@@ -205,6 +209,9 @@ nav:
- cli/cross-monitor-move-behaviour.md - cli/cross-monitor-move-behaviour.md
- cli/toggle-cross-monitor-move-behaviour.md - cli/toggle-cross-monitor-move-behaviour.md
- cli/unmanaged-window-operation-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/ignore-rule.md
- cli/manage-rule.md - cli/manage-rule.md
- cli/initial-workspace-rule.md - cli/initial-workspace-rule.md
+1569 -55
View File
File diff suppressed because it is too large Load Diff
+1669 -38
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -101,6 +101,9 @@
<Component Id='binary4' Guid='*'> <Component Id='binary4' Guid='*'>
<File Id='exe4' Name='komorebi-bar.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-bar.exe' KeyPath='yes' /> <File Id='exe4' Name='komorebi-bar.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-bar.exe' KeyPath='yes' />
</Component> </Component>
<Component Id='binary5' Guid='*'>
<File Id='exe5' Name='komorebi-shortcuts.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-shortcuts.exe' KeyPath='yes' />
</Component>
</Directory> </Directory>
</Directory> </Directory>
</Directory> </Directory>
@@ -123,6 +126,8 @@
<ComponentRef Id='binary4' /> <ComponentRef Id='binary4' />
<ComponentRef Id='binary5' />
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'> <Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
<ComponentRef Id='Path' /> <ComponentRef Id='Path' />
</Feature> </Feature>