Compare commits

..

43 Commits

Author SHA1 Message Date
LGUG2Z
58295a15bf test(wm): don't rely on datadir for socket location
This commit avoids relying on DATA_DIR for the test socket location as
this path is not available to us in GitHub Actions runners.
2025-03-07 16:01:47 -08:00
LGUG2Z
c90769f5fa chore(deny): allow unmaintained crate paste 2025-03-07 16:01:41 -08:00
LGUG2Z
76002385ab test(wm): colocate tests with src files, add socket msg test 2025-03-06 19:39:59 -08:00
Jerry Kingsbury
f40e80cd61 test(wm): add window manager unit tests
Created a test that creates the WM instance and ensures the instance is
running. The test creates a custom socket and then cleans up the socket
file after completion.

Created a test that creates a WM instance, monitor instance, and
workpace. The tests checks to ensure that the expected workspace is
focused properly.

Included recommended fixes to ensure that the focus_workspace function
is used correctly and that the test accurately checks the workspaces
length, current workspace index, and switching to an existing workspace.
2025-03-06 15:34:30 -08:00
LGUG2Z
e4f9d8af86 chore(deps): add deny.toml and dependencies.json 2025-03-06 15:34:27 -08:00
LGUG2Z
0c64432c25 feat(cli): add cycle-empty-workspace cmd
This commit adds a variation of the cycle-workspace command which will
attempt to focus the next empty workspace in the given direction (with
the usual wraparound). If there are no empty workspaces available, this
command will do nothing.
2025-03-05 15:52:21 -08:00
LGUG2Z
fe20caa56a fix(client): expose schemars feature and allow opt-out 2025-03-05 15:49:46 -08:00
Csaba
02a2796e7d fix(bar): correct time widget binary format alignment
This commit adds a bit of offset to the time widget's binary clocks so
they are more in the middle.

It also fixes some visual changes that were caused by upgrading to
eframe 0.31
2025-03-04 14:57:21 -08:00
LGUG2Z
a0eb025cec fix(bar): add opt schemars derive on test case 2025-03-04 08:39:47 -08:00
alex-ds13
70a61376a8 fix(borders): ignore borders on all stack window updates 2025-03-04 08:20:35 -08:00
LGUG2Z
1761919707 perf(client): disable schemars feature on komorebi lib dep 2025-03-03 21:49:43 -08:00
LGUG2Z
1325da4e81 chore(deps): cargo update 2025-03-03 21:46:03 -08:00
alex-ds13
0776ca1565 feat(border): cache borders on all workspaces
This commit refactors the `border_manager` with the following changes:

- Rework the way the borders are created and their pointer is sent to
  the window message handler. Now we also store the same pointer as a
  `Box<Border>` on the `border_manager`'s `BORDER_STATE`. This means
  that the borders we have are exactly the same ones that the border
  window itself is accessing so we can now store the border's info
  inside the `Border` struct and all of it will be accessible by the
  border window as well. This makes it so the "ACTUAL" border struct is
  the one created on the thread of the `Border::create()` function and
  when that thread finishes (after the border window closes) it will
  handle the drop of the border itself. However this means we need to be
  careful with our own stored `Box<Border>` since it will point to the
  same memory we can't let the compiler dropped them as usual or
  otherwise it would create heap corruption errors. So this commit
  creates a special function called `destroy_border()` to actually close
  the window border without dropping the border, since it will be later
  dropped by the thread that created it.

- Remove `BORDERS_MONITORS`, `FOCUS_STATE` and `RENDER_TARGETS` arc
  mutexes, since now this info is stored on the border itself.

- Change the `BORDER_STATE` to now map an id (container or window) to a
  `Box<Border>`.

- Change the `WINDOWS_BORDERS` to now map a window hwnd to a border id.

- Create new struct `BorderInfo` which as the border window hwnd and the
  border kind. This struct is what is now returned by the function
  `window_border()` which checks if some window as a border attached to
  it and if it does it returns this info. There is no need to clone the
  entire border. If in the future we need more info we can just add it
  to this struct.

- Change the way we clear the `BORDER_STATE`. Like mentioned before we
  need to be sure we don't drop the `Box<Border>` when removing it, so
  now we use the `.drain` function to remove all the borders as an
  iterator and we call the `destroy_border()` on each border we are
  removing.

- We now check if a border's `tracking_hwnd` has changed and if it does
  we simply update it instead of destroying the border and create a new
  one.

- Create function `delete_border` so that we can remove a border
  properly from outside the `border_manager`.

- Create function `hide_border` which spawns a new thread that searches
  if a window hwnd has a border attached to it and if it does it hides
  said border window. This function is called on every `window.hide()`.

- Create function `show_border` which spawns a new thread that searches
  if a window hwnd has a border attached to it and if it does it restores
  said border window. This function is called on every `window.restore()`.

- This commit also changes the previous `window.hide()` and
  `window.restore()` functions to be named:
  - `window.hide_with_border(hide_border: bool)`: this is the same
    function as before but adds a check at the end in case `hide_border`
    is true it calls `border_manager::hide_border()`. A new function was
    created with the same name as before `window.hide()` which by
    default calls this new function with `hide_border = true`.
  - `window.restore_with_border(restore_border: bool)`: this is the same
    function as before but adds a check at the end in case `hide_border`
    is true it calls `border_manager::hide_border()`. A new function was
    created with the same name as before `window.hide()` which by
    default calls this new function with `hide_border = true`.

- This commit creates a new function on `Container` called
  `load_focused_window_ignore_borders()` which performs the same as
  `load_focused_window()` but it ignores the borders when hiding and
  restoring the windows. This function, along with the
  `hide_with_border(false)` and `restore_with_border(false)` are used on
  all functions related to changing focus on a stack since if we let the
  borders be hidden and restored when cycling or changing focus on a
  stack the border would flicker slightly, this prevents that. Ignore
  borders when clicking on the stackbar as well.
  (P.S. there might still be other places that I forgot to use these new
  functions, but if that is the case then what will happen is a simple
  flicker of the stack border...)

- The `remove_window` from `Workspace` needs to call the
  `border_manager::delete_border()` so that wew make sure we remove that
  windows's border window as well if it exists. This is essential when
  enforcing workspace rules, otherwise the border would be left behind.

- Lastly, but not least, now that we hide the borders windows along with
  their tracking window, we no longer remove the borders when swapping
  workspaces or when toggling monocle, etc. Instead we keep all borders
  of all workspaces cached and simply hide them. They are only removed
  when their tracking window is closed or cloaked on a stack (since on a
  stack we only keep one border for all the entire stack container).
  This means that when changing between workspaces we no longer see the
  borders showing up delayed after the windows show up. Now both the
  window and it's border show up as if they are one and the same.
2025-03-03 21:15:45 -08:00
alex-ds13
724b0b7692 fix(border): update border when moving from admin windows 2025-03-03 21:13:37 -08:00
LGUG2Z
b53de81754 perf(cargo): make schemars derives optional
This commit makes all schemars::JsonSchema derives optional. After
analyzing the output of cargo build timings and llvm-lines, it was clear
that the majority of the 2m+ incremental dev build times was taken up by
codegen, and the majority of it by schemars.

Developers can now run cargo commands with --no-default-features to
disable schemars::JsonSchema codegen, and all justfile commands have
been updated to take this flag by default, with the exception of the
jsonschema target, which will compile with all derives required to
export the various jsonschema files.

Incremental dev build times for komorebi.exe on my machine are now at
around ~18s, while clean dev build times for the entire workspace are at
around ~1m.
2025-03-03 21:12:46 -08:00
Csaba
a837fea40c feat(bar): add icons to workspace-layer widget
This commits adds the ability to set icons for the `workspace-layer` with
the `DisplayFormat` and a setting to specify if it should `show_when_tiling`
or not.

collab with @alex-ds13
2025-03-03 08:13:29 -08:00
LGUG2Z
dd577c0eb3 fix(wm): preserve resize dimensions on offset toggle
This commit ensures that the resize dimensions will be reserved for
other monitors and workspaces when the
toggle-window-based-work-area-offset command is used.
2025-03-03 08:13:29 -08:00
Csaba
7d497c3e14 fix(bar): always add stroke on selected_frame
This commit fixes a breaking change on the selected_frame that was
introduced by eframe version 0.31.

In this version, the stroke is drawn on the inside of a Frame instead it
being drawn on the outside like before.

This now means that a stroke needs to be added on all the states of the
Frame in order to avoid all the elements to be moving around on hover.
2025-03-03 08:13:29 -08:00
LGUG2Z
e6398c29f8 docs(readme): add active individual commercial use licenses count 2025-02-27 18:15:43 -08:00
LGUG2Z
ca893140f5 chore(deps): bump eframe to 0.31 2025-02-27 16:50:59 -08:00
alex-ds13
b26910aa58 fix(wm): allow stacking in all dirs, improve stack border rendering
Previously the stacking logic would sometimes change the focused
container without actually changing focus to said container.

This resulted in the stack showing up with an unfocused border even
though we had focus on one windows belonging to the stack (just not the
right one).

Also before it wasn't possible to stack windows on some directions
when we were already on a stack.

This commit fixes both issues.
2025-02-26 19:23:40 -08:00
alex-ds13
487c217497 fix(borders): address memory leaks
This commit fixes multiple issues with the borders which were resulting
in multiple borders being created and not completely destroyed which
meant that the amount of borders in memory kept increasing indefinitely
the more we used komorebi. To do so this commit does the following:

- Clear all the maps on `destroy_all_borders`.

- Create function `remove_border` which should always be used when we
  want to remove a border since this function destroys the border window
  and removes all its related data and clones from all the maps.

- Create function `remove_borders` which should always be used when we
  want to remove multiple borders or filter the current existing
  borders. It takes in a `condition` function that takes a ref to the
  border's container id and a ref to the border and should return a
  bool. This function is then applied to each existing border and if it
  evaluates to true it will call `remove_border` on it.

- Apply these new functions on all the code that was previously manually
  removing borders.

- When a container is a stack, we now check it's unfocused windows, in
  case they had borders attached to them we remove them, otherwise these
  borders would persist and be drawn below other borders.

- We now check if a container's border was previously tracking a different
  window, if it was we destroy that border and remove it's hwnd from the
  `FOCUS_STATE` and then we check if its `tracking_hwnd` still points to
  the same border and remove it if it does (if it doesn't that means
  that a new border was already attached to that window so we don't
  remove it).

  We don't call `remove_border` here since we don't want to actually
  remove the border but instead we replace it with a new one tracking
  the correct window. (I've tried updating the `tracking_hwnd` instead
  of destroying the border and creating a new one but that didn't work
  since it still kept tracking the previous window...).
2025-02-26 19:22:22 -08:00
alex-ds13
4031fbf033 feat(wm): move all windows on ws layer toggle
This commit makes it so when you toggle between workspace layers it
moves all windows of that layer to the top of the z-order.

So if you move to `Floating` layer, then all floating windows are moved
to the top, and if you go back to the `Tiling` layer, all tiled
containers are moved to the top.
2025-02-26 19:21:33 -08:00
LGUG2Z
dadc40777f chore(deps): bump shadow-rs from 0.38 to 1 2025-02-24 21:12:31 -08:00
Csaba
59544edb74 fix(bar): use accent color for active widget components
This commit makes sure that the accent color is used on certain bar
components, such as active workspace, selected layout and focused
window.

This will now make these components stand out even more for a better
visual indication.

fix #1288
2025-02-24 17:36:26 -08:00
LGUG2Z
5e2c18cad3 chore(deps): bump win32-display-data 2025-02-24 17:28:50 -08:00
Csaba
d69dfeb715 feat(bar): add opts to show all icons on workspace widget
This commit adds 3 new display options on the komorebi workspace widget
to show all icons.
2025-02-24 08:12:07 -08:00
LGUG2Z
3a208b577c chore(just): add wpm target 2025-02-23 20:24:30 -08:00
alex-ds13
20817b094d fix(wm): prevent floating focus change event infinite loops
This commit stops the `FocusChange` event from focusing a floating
window when it is the one emitting said `FocusChange` event, since it's
not needed and is the cause of some flicker bugs reported!

If that floating window was the one emitting the `FocusChange` event
then it means it is already the foreground window, and there is no
reason for us to focus the window again (since that will create an
infinite loop of events).

When the window emitting this event is not floating we don't try to
focus the window, we simply set the focus index for the container of
that window and the focused index for the window of that container.

Except in one case, which is if the workspace has a monocle container,
then it does focus a window, it focuses the monocle window to make sure
the monocle keeps showing in front of everything and doesn't let
anything come in front of it.
2025-02-23 20:16:32 -08:00
alex-ds13
394709e356 fix(reaper): avoid deadlocks at startup
Previously the reaper at startup would lock it's own `HWNDS_CACHE` and
then try to lock the WM to get its `known_hwnds`.

However if there was an event in the meantime, process_event would lock
the WM first and then it would try to lock the reaper's `HWNDS_CACHE` to
update it.  This would deadlock since that would be locked by the reaper
waiting for the WM lock to be released.

This commit now makes it so we pass the `known_hwnds` to the reaper as
an argument at startup and it also rearranges the order of loading the
listeners.

Now komorebi first loads all the manager-type listeners, and only
afterwards does it load the commands and events listeners.

After some testing this seems to be the best order that doesn't cause
any issues at all!

There were some other issues that I've noticed before when starting
komorebi while having other 3rd parties trying to subscribe to it (like
komorebi-bar and YASB), which would make those subscribers lock the
`process_command` thread. This doesn't seem to be happening on my tests
anymore with this new order.
2025-02-23 20:10:29 -08:00
alex-ds13
990a339d4e fix(bar): apply work area offset on monitor reconnect
This commit makes sure the bar applies the the `work_area_offset`
correctly after a monitor reconnects; if it is the first time a monitor
is connecting, the cached offset won't exist yet.
2025-02-23 15:25:19 -08:00
alex-ds13
f0222dd4ab fix(wm): properly load monitor on first connect
This commit fixes an issue where if you started komorebi without a
monitor connected, and then connected it later, it wasn't properly
loading the data returned from `win32-display-data`.
2025-02-23 15:24:15 -08:00
LGUG2Z
974e5a2b20 refactor(bar): add extend_enum! macro
This commit adds an extend_enum! macro and some unit tests to provide
an ergonomic way to specialize configs for specific (sub)widgets.

The macro takes the name of the existing enum, the desired name of the
new, extended enum, and a list of variants which should be added to the
extended enum.

The macro will add new variants to a new enum definition first, before
wrapping the existing enum in an Existing variant and tagging it with
the `#[serde(untagged)]` annotation.

From is also implemented for ergonomic from/into calls between shared
variants of the existing and extended enums.
2025-02-23 12:16:00 -08:00
alex-ds13
2bbc269b9f feat(wm): add padding per monitor
This commit adds the ability to set container and workspace padding per
monitor.

To do so (and to simplify any future need of changing some value per
monitor), and have it pass through to each workspace a new field was added
to `Workspace` called `globals` which has a new struct called
`WorkspaceGlobals`.

`WorkspaceGlobals` includes any global values that might be needed by
the workspace.

This field is updated by the monitor for all its workspaces whenever the
config is loaded or reloaded. It is also updated on `RetileAll` and on
the function `update_focused_workspace`.

This should make sure that every time a workspace needs to use it's
`update` function, it has all the `globals` up to date!

This also means that now the `update` function from workspaces doesn't
take any argument at all, reducing all the need to get all the
`work_area`, `work_area_offset`, `window_based_work_area_offset` or
`window_based_work_area_offset_limit` simplifying the callers of this
function quite a bit.

Lastly this commit has also (sort of accidentaly) fixed an existing bug
with the `move_workspace_to_monitor` function.

This was previous removing the workspace from a monitor, but wasn't
changing it's `focused_workspace_idx`, meaning that komorebi would get
all messed up after that command. For example, the `border_manager`
would get stuck and the komorebi-bar would crash.

Now, the `remove_focused_workspace` function also focuses the previous
workspace (which in turn will create a new workspace in case the removed
workspace was the last workspace).
2025-02-23 09:52:33 -08:00
alex-ds13
13ee42276d fix(wm): hide/restore floating windows on monocle toggle 2025-02-23 06:51:42 -08:00
alex-ds13
3641ce6b42 fix(wm): take layer into account on ws restore
Previously if a workspace had any floating windows it would always focus
the first one when restoring. Now it only focus the floating window if
the workspace layer is `Floating`.
2025-02-23 06:51:14 -08:00
LGUG2Z
9d41a293f6 feat(wm): add tiling and floating ws layers
This commit introduces an implementation of workspace layers to
komorebi.

Workspace layers change the kinds of windows that certain commands
operate on. This implementation features two variants,
WorkspaceLayer::Tiling and WorkspaceLayer::Floating.

The default behaviour until now has been WorkspaceLayer::Tiling.

When the user sets WorkspaceLayer::Floating, either through the
'toggle-workspace-layer' command or the new bar widget, the 'move',
'focus', 'cycle-focus' and 'resize-axis' commands will operate on
floating windows, if the currently focused window is a floating window.

As I don't have 'cycle-focus' bound to anything, 'focus up' and 'focus
down' double as incrementing and decrementing cycle focus commands,
iterating focus through the floating windows assigned to a workspace.

Floating windows in komorebi belong to specific workspaces, therefore
commands such as 'move' and 'resize-axis' will restrict movement and
resizing to the bounds of their workspace's work area (or more
accurately, the work area of the monitor that the workspace belongs to,
as floating windows are never constrained by workspace-specific work
area restrictions).
2025-02-22 15:57:22 -08:00
LGUG2Z
1756983978 build(cargo): add custom build profiles 2025-02-22 15:57:17 -08:00
alex-ds13
3d327c407c perf(reaper): switch to channel notifications
This commit changes the way the reaper works.

First this commit changed the `known_hwnds` held by the `WindowManager`
to be a HashMap of window handles (isize) to a pair of monitor_idx,
workspace_idx (usize, usize).

This commit then changes the reaper to have a cache of hwnds which is
updated by the `WindowManager` when they change. The reaper has a thread
that is continuously checking this cache to see if there is any window
handle that no longer exists. When it finds them, the thread sends a
notification to a channel which is then received by the reaper on
another thread that actually does the work on the `WindowManager` by
removing said windows.

This means that the reaper no longer tries to access and lock the
`WindowManager` every second like it used to, but instead it only does
it when it actually needs, when a window actually needs to be reaped.
This means that we can make the thread that checks for orphan windows
run much more frequently since it won't influence the rest of komorebi.

Since now the `known_hwnds` have the monitor/workspace index pair of the
window, we can simply get that info from the map and immediately access
that monitor/workspace or use that index info.
2025-02-22 12:29:27 -08:00
alex-ds13
e5fb5390a8 feat(wm): strip unncessary info from state 2025-02-22 12:28:30 -08:00
alex-ds13
6a8e362c21 refactor(wm): make workspace fields public 2025-02-22 12:28:23 -08:00
alex-ds13
1edeb44203 fix(wm): include workspace rules on cached monitor
The `WorkspaceConfig` stored on `Workspace` was changed to not be
serialized, however it needs to be serialized and deserialized when
caching a monitor (after a disconnect), so that when it reconnects it is
able to read all the workspace rules, which include:

- initial_workspace_rules
- workspace_rules
- window_container_behaviour_rules
- layout_rules
- custom_layout_rules

This commit changes the serde skip to only skip if is is `None`.

This means that the `komorebic state` command will have all this
information as well and it will send it when notifying subscribers too,
which isn't good at all, so we need to find another way of excluding it
from the state.
2025-02-22 12:27:55 -08:00
LGUG2Z
8bc04f0610 chore(deps): bump windows-rs from 0.58 to 0.60 2025-02-21 20:16:50 -08:00
76 changed files with 4125 additions and 1549 deletions

View File

@@ -18,6 +18,14 @@ on:
workflow_dispatch:
jobs:
cargo-deny:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: EmbarkStudios/cargo-deny-action@v2
build:
strategy:
fail-fast: true
@@ -47,7 +55,7 @@ jobs:
key: ${{ matrix.platform.target }}
- run: cargo +nightly fmt --check
- run: cargo clippy
- run: cargo test --package komorebi --test compat
- run: cargo test
- uses: houseabsolute/actions-rust-cross@v1
with:
command: "build"

678
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,8 @@ chrono = "0.4"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
color-eyre = "0.6"
eframe = "0.30"
egui_extras = "0.30"
eframe = "0.31"
egui_extras = "0.31"
dirs = "6"
dunce = "1"
hotwatch = "0.5"
@@ -27,21 +27,23 @@ lazy_static = "1"
serde = { version = "1", features = ["derive"] }
serde_json = { package = "serde_json_lenient", version = "0.2" }
serde_yaml = "0.9"
strum = { version = "0.27", features = ["derive"] }
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1"
sysinfo = "0.33"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "14406d46a94d4e29ffe8a789539e453fa15cf3d3" }
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
windows-numerics = { version = "0.1" }
windows-implement = { version = "0.59" }
windows-interface = { version = "0.59" }
windows-core = { version = "0.59" }
shadow-rs = "0.38"
windows-core = { version = "0.60" }
shadow-rs = "1"
which = "7"
[workspace.dependencies.windows]
version = "0.59"
version = "0.60"
features = [
"Foundation_Numerics",
"Win32_Devices",
@@ -69,4 +71,4 @@ features = [
"Win32_System_WindowsProgramming",
"Media",
"Media_Control"
]
]

View File

@@ -7,9 +7,9 @@ Tiling Window Management for Windows.
<img alt="Tech for Palestine" src="https://badge.techforpalestine.org/default">
</a>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LGUG2Z/komorebi/.github/workflows/windows.yaml">
<img alt="GitHub" src="https://img.shields.io/github/license/LGUG2Z/komorebi">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/LGUG2Z/komorebi/total">
<img alt="GitHub commits since latest release (by date) for a branch" src="https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest">
<img alt="Active Individual Commercial Use Licenses" src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Flgug2z-ecstaticmagentacheetah.web.val.run&query=%24.&label=active%20individual%20commercial%20use%20licenses&cacheSeconds=3600&link=https%3A%2F%2Flgug2z.com%2Fsoftware%2Fkomorebi">
<a href="https://discord.gg/mGkn66PHkx">
<img alt="Discord" src="https://img.shields.io/discord/898554690126630914">
</a>

96
deny.toml Normal file
View File

@@ -0,0 +1,96 @@
[graph]
targets = [
"x86_64-pc-windows-msvc",
"i686-pc-windows-msvc",
"aarch64-pc-windows-msvc",
]
all-features = false
no-default-features = false
[output]
feature-depth = 1
[advisories]
ignore = [
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" }
]
[licenses]
allow = [
"0BSD",
"Apache-2.0",
"Artistic-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"ISC",
"MIT",
"MIT-0",
"MPL-2.0",
"OFL-1.1",
"Ubuntu-font-1.0",
"Unicode-3.0",
"Zlib",
"LicenseRef-Komorebi-1.0"
]
confidence-threshold = 0.8
[[licenses.clarify]]
crate = "komorebi"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-client"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebic"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebic-no-console"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-themes"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-gui"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "komorebi-bar"
expression = "LicenseRef-Komorebi-1.0"
license-files = []
[[licenses.clarify]]
crate = "base16-egui-themes"
expression = "MIT"
license-files = []
[bans]
multiple-versions = "allow"
wildcards = "allow"
highlight = "all"
workspace-default-features = "allow"
external-default-features = "allow"
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [
"https://github.com/LGUG2Z/base16-egui-themes",
"https://github.com/LGUG2Z/catppuccin-egui",
"https://github.com/LGUG2Z/windows-icons",
"https://github.com/LGUG2Z/win32-display-data",
]

771
dependencies.json Normal file
View File

@@ -0,0 +1,771 @@
{
"licenses": [
[
"0BSD",
[
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"win32-display-data 0.1.0 git+https://github.com/LGUG2Z/win32-display-data?rev=55cebdebfbd68dbd14945a1ba90f6b05b7be2893"
]
],
[
"Apache-2.0",
[
"ab_glyph 0.2.29 registry+https://github.com/rust-lang/crates.io-index",
"ab_glyph_rasterizer 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_winit 0.23.1 registry+https://github.com/rust-lang/crates.io-index",
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.7 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.97 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.4.1 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"autocfg 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
"backtrace 0.3.71 registry+https://github.com/rust-lang/crates.io-index",
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.16 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.40 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.4 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.3 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.4.2 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.14 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"dpi 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"dyn-clone 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-winit 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"either 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"filetime 0.2.25 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"glutin 0.32.2 registry+https://github.com/rust-lang/crates.io-index",
"glutin_egl_sys 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"glutin_wgl_sys 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"half 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.61 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.5 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"imgref 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"immutable-chunkmap 2.0.6 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"is_terminal_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.32 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"khronos_api 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.0+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.21 registry+https://github.com/rust-lang/crates.io-index",
"litrs 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.26 registry+https://github.com/rust-lang/crates.io-index",
"miette 7.5.0 registry+https://github.com/rust-lang/crates.io-index",
"miette-derive 7.5.0 registry+https://github.com/rust-lang/crates.io-index",
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"ntapi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
"owned_ttf_parser 0.25.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.10 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
"png 0.17.16 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error-attr2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.94 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.39 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.11.1 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.12 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rustversion 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_json 1.0.140 registry+https://github.com/rust-lang/crates.io-index",
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"socket2 0.5.8 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"supports-hyperlinks 3.1.0 registry+https://github.com/rust-lang/crates.io-index",
"supports-unicode 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.99 registry+https://github.com/rust-lang/crates.io-index",
"sync_wrapper 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.17.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.8 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.37 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.18.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"tzdb 0.7.2 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"unicode-linebreak 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"uom 0.36.0 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.4 registry+https://github.com/rust-lang/crates.io-index",
"utf16_iter 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"winit 0.30.9 registry+https://github.com/rust-lang/crates.io-index",
"wmi 0.15.1 registry+https://github.com/rust-lang/crates.io-index",
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Artistic-2.0",
[
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"BSD-2-Clause",
[
"av1-grain 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"rav1e 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"v_frame 0.3.8 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"BSD-3-Clause",
[
"alloc-no-stdlib 2.0.4 registry+https://github.com/rust-lang/crates.io-index",
"alloc-stdlib 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"avif-serialize 0.8.3 registry+https://github.com/rust-lang/crates.io-index",
"brotli 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"exr 1.73.0 registry+https://github.com/rust-lang/crates.io-index",
"lebe 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"ravif 0.11.11 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"BSL-1.0",
[
"clipboard-win 5.4.0 registry+https://github.com/rust-lang/crates.io-index",
"error-code 3.3.1 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"CC0-1.0",
[
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"imgref 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"notify 6.1.1 registry+https://github.com/rust-lang/crates.io-index",
"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"ISC",
[
"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"libloading 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"starship-battery 0.10.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MIT",
[
"accesskit 0.17.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.26.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.24.1 registry+https://github.com/rust-lang/crates.io-index",
"adler2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"ahash 0.8.11 registry+https://github.com/rust-lang/crates.io-index",
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"aligned-vec 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.18 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.7 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.97 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.4.1 registry+https://github.com/rust-lang/crates.io-index",
"arg_enum_proc_macro 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index",
"autocfg 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
"backtrace 0.3.71 registry+https://github.com/rust-lang/crates.io-index",
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"brotli 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 2.5.1 registry+https://github.com/rust-lang/crates.io-index",
"built 0.7.7 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"bytes 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"catppuccin-egui 5.3.1 git+https://github.com/LGUG2Z/catppuccin-egui?rev=bdaff30959512c4f7ee7304117076a48633d777f",
"cc 1.2.16 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"cfg_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.40 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.31 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.28 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.4 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.3 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"color_quant 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.4.2 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.14 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.4.5 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"dyn-clone 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.9.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-winit 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"either 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index",
"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index",
"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index",
"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index",
"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"filetime 0.2.25 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
"form_urlencoded 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"fs-tail 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"getset 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"glutin-winit 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"h2 0.4.8 registry+https://github.com/rust-lang/crates.io-index",
"half 2.4.1 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"http-body 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"http-body-util 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper 1.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hyper-util 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.61 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.5 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"immutable-chunkmap 2.0.6 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"is_terminal_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.32 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.170 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.0+1.9.0 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.21 registry+https://github.com/rust-lang/crates.io-index",
"litrs 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.26 registry+https://github.com/rust-lang/crates.io-index",
"loop9 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"matchers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"maybe-rayon 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"memchr 2.7.4 registry+https://github.com/rust-lang/crates.io-index",
"memoffset 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index",
"mime_guess2 2.0.5 registry+https://github.com/rust-lang/crates.io-index",
"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"mio 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"nanoid 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"native-tls 0.2.14 registry+https://github.com/rust-lang/crates.io-index",
"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index",
"netdev 0.32.0 registry+https://github.com/rust-lang/crates.io-index",
"new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index",
"noop_proc_macro 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"ntapi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"nu-ansi-term 0.46.0 registry+https://github.com/rust-lang/crates.io-index",
"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index",
"num-conv 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index",
"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.20.3 registry+https://github.com/rust-lang/crates.io-index",
"os_info 3.10.0 registry+https://github.com/rust-lang/crates.io-index",
"overload 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"owo-colors 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"owo-colors 4.2.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.10 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.1 registry+https://github.com/rust-lang/crates.io-index",
"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index",
"png 0.17.16 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.20 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error-attr2 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro-error2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.94 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.16 registry+https://github.com/rust-lang/crates.io-index",
"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.39 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"random_word 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.10.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.12.1 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.11.1 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.6.29 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.12 registry+https://github.com/rust-lang/crates.io-index",
"rgb 0.8.50 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pemfile 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rustversion 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"schannel 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"schemars 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
"schemars_derive 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.218 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_json 1.0.140 registry+https://github.com/rust-lang/crates.io-index",
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"simd-adler32 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"slab 0.4.9 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.14.0 registry+https://github.com/rust-lang/crates.io-index",
"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"socket2 0.5.8 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index",
"strum 0.27.1 registry+https://github.com/rust-lang/crates.io-index",
"strum_macros 0.27.1 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.99 registry+https://github.com/rust-lang/crates.io-index",
"synstructure 0.13.1 registry+https://github.com/rust-lang/crates.io-index",
"sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.17.1 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"textwrap 0.16.2 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 1.0.69 registry+https://github.com/rust-lang/crates.io-index",
"thiserror-impl 2.0.12 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.8 registry+https://github.com/rust-lang/crates.io-index",
"tiff 0.9.1 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.37 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"tokio 1.43.0 registry+https://github.com/rust-lang/crates.io-index",
"tokio-native-tls 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"tokio-util 0.7.13 registry+https://github.com/rust-lang/crates.io-index",
"tower 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"tower-layer 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"tracing 0.1.41 registry+https://github.com/rust-lang/crates.io-index",
"tracing-appender 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"tracing-attributes 0.1.28 registry+https://github.com/rust-lang/crates.io-index",
"tracing-core 0.1.33 registry+https://github.com/rust-lang/crates.io-index",
"tracing-error 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"tracing-log 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"tracing-subscriber 0.3.19 registry+https://github.com/rust-lang/crates.io-index",
"try-lock 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.18.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"uds_windows 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index",
"unicode-width 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index",
"unsafe-libyaml 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"uom 0.36.0 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.4 registry+https://github.com/rust-lang/crates.io-index",
"utf16_iter 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index",
"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index",
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
"want 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.0.3 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.8 registry+https://github.com/rust-lang/crates.io-index",
"which 7.0.2 registry+https://github.com/rust-lang/crates.io-index",
"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
"winapi-util 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5",
"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-interface 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-link 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-result 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index",
"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index",
"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index",
"winsafe 0.0.19 registry+https://github.com/rust-lang/crates.io-index",
"wmi 0.15.1 registry+https://github.com/rust-lang/crates.io-index",
"write16 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"xml-rs 0.8.25 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.7.35 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MIT-0",
[
"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.1 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MPL-2.0",
[
"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"OFL-1.1",
[
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Ubuntu-font-1.0",
[
"epaint_default_fonts 0.31.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Unicode-3.0",
[
"icu_collections 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locid 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locid_transform 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locid_transform_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties 1.5.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties_data 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider_macros 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"litemap 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"tinystr 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.18 registry+https://github.com/rust-lang/crates.io-index",
"writeable 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
"yoke 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"yoke-derive 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"zerofrom 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"zerofrom-derive 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"zerovec 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"zerovec-derive 0.10.3 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Unlicense",
[
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"memchr 2.7.4 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index",
"winapi-util 0.1.9 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Zlib",
[
"bytemuck 1.22.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"const_format 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
"const_format_proc_macros 0.2.34 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"foldhash 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.4.14 registry+https://github.com/rust-lang/crates.io-index"
]
]
]
}

View File

@@ -0,0 +1,16 @@
# cycle-empty-workspace
```
Focus the next empty workspace in the given cycle direction (if one exists)
Usage: komorebic.exe cycle-empty-workspace <CYCLE_DIRECTION>
Arguments:
<CYCLE_DIRECTION>
[possible values: previous, next]
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,12 @@
# toggle-workspace-layer
```
Toggle between the Tiling and Floating layers on the focused workspace
Usage: komorebic.exe toggle-workspace-layer
Options:
-h, --help
Print help
```

View File

@@ -19,22 +19,43 @@ install-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
install-target target:
cargo +stable install --path {{ target }} --locked --no-default-features
install-targets-with-jsonschema *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just install-target-with-jsonschema $_ }
install-target-with-jsonschema target:
cargo +stable install --path {{ target }} --locked
install:
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
install-with-jsonschema:
just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
build-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
build-target target:
cargo +stable build --release --package {{ target }} --locked
cargo +stable build --package {{ target }} --locked --release --no-default-features
build:
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
copy-target target:
cp .\target\release\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
copy-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just copy-target $_ }
wpm target:
just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}
copy:
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
run target:
cargo +stable run --bin {{ target }} --locked
cargo +stable run --bin {{ target }} --locked --no-default-features
warn target $RUST_LOG="warn":
just run {{ target }}
@@ -49,7 +70,7 @@ trace target $RUST_LOG="trace":
just run {{ target }}
deadlock $RUST_LOG="trace":
cargo +stable run --bin komorebi --locked --features deadlock_detection
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
docgen:
cargo run --package komorebic -- docgen

View File

@@ -16,7 +16,7 @@ crossbeam-channel = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
eframe = { workspace = true }
egui-phosphor = "0.8"
egui-phosphor = "0.9"
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
@@ -26,7 +26,7 @@ num-derive = "0.4"
num-traits = "0.2"
random_word = { version = "0.4", features = ["en"] }
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
starship-battery = "0.10"
@@ -34,4 +34,9 @@ sysinfo = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
windows = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
windows-core = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
[features]
default = ["schemars"]
schemars = ["dep:schemars"]

View File

@@ -178,11 +178,11 @@ pub fn apply_theme(
{
if let Some(rounding) = config.rounding {
ctx.style_mut(|style| {
style.visuals.widgets.noninteractive.rounding = rounding.into();
style.visuals.widgets.inactive.rounding = rounding.into();
style.visuals.widgets.hovered.rounding = rounding.into();
style.visuals.widgets.active.rounding = rounding.into();
style.visuals.widgets.open.rounding = rounding.into();
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
style.visuals.widgets.inactive.corner_radius = rounding.into();
style.visuals.widgets.hovered.corner_radius = rounding.into();
style.visuals.widgets.active.corner_radius = rounding.into();
style.visuals.widgets.open.corner_radius = rounding.into();
});
}
}
@@ -365,16 +365,10 @@ impl Komobar {
&SocketMessage::MonitorWorkAreaOffset(monitor_index, new_rect),
) {
tracing::error!(
"error applying work area offset to monitor '{}': {}",
monitor_index,
error,
"error applying work area offset to monitor '{monitor_index}': {error}"
);
} else {
tracing::info!(
"work area offset applied to monitor: {}\n, {:#?}",
monitor_index,
new_rect
);
tracing::info!("work area offset applied to monitor: {monitor_index}",);
}
}
}
@@ -508,11 +502,12 @@ impl Komobar {
{
if let Some(rounding) = config.rounding {
ctx.style_mut(|style| {
style.visuals.widgets.noninteractive.rounding = rounding.into();
style.visuals.widgets.inactive.rounding = rounding.into();
style.visuals.widgets.hovered.rounding = rounding.into();
style.visuals.widgets.active.rounding = rounding.into();
style.visuals.widgets.open.rounding = rounding.into();
style.visuals.widgets.noninteractive.corner_radius =
rounding.into();
style.visuals.widgets.inactive.corner_radius = rounding.into();
style.visuals.widgets.hovered.corner_radius = rounding.into();
style.visuals.widgets.active.corner_radius = rounding.into();
style.visuals.widgets.open.corner_radius = rounding.into();
});
}
}
@@ -631,10 +626,10 @@ impl Komobar {
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&self.size_rect, false) {
Ok(_) => {
tracing::info!("updated bar position: {:#?}", &self.size_rect);
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{}", error.to_string())
tracing::error!("{error}")
}
}
}
@@ -724,6 +719,12 @@ impl eframe::App for Komobar {
}
}
// Reset the current `work_area_offset` so that it gets recalculated and
// properly applied again, since if the monitor has connected for the first
// time it won't have the work_area_offset applied but the bar thinks it
// does.
self.work_area_offset = komorebi_client::Rect::default();
should_apply_config = true;
}
self.disabled = false;
@@ -839,26 +840,26 @@ impl eframe::App for Komobar {
let frame = match &self.config.padding {
None => {
if let Some(frame) = &self.config.frame {
Frame::none()
Frame::NONE
.inner_margin(Margin::symmetric(
frame.inner_margin.x,
frame.inner_margin.y,
frame.inner_margin.x as i8,
frame.inner_margin.y as i8,
))
.fill(*self.bg_color_with_alpha.borrow())
} else {
Frame::none()
.inner_margin(Margin::same(0.0))
Frame::NONE
.inner_margin(Margin::same(0))
.fill(*self.bg_color_with_alpha.borrow())
}
}
Some(padding) => {
let padding = padding.to_individual(DEFAULT_PADDING);
Frame::none()
Frame::NONE
.inner_margin(Margin {
top: padding.top,
bottom: padding.bottom,
left: padding.left,
right: padding.right,
top: padding.top as i8,
bottom: padding.bottom as i8,
left: padding.left as i8,
right: padding.right as i8,
})
.fill(*self.bg_color_with_alpha.borrow())
}
@@ -871,13 +872,13 @@ impl eframe::App for Komobar {
CentralPanel::default().frame(frame).show(ctx, |ui| {
// Apply grouping logic for the bar as a whole
let area_frame = if let Some(frame) = &self.config.frame {
Frame::none()
.inner_margin(Margin::symmetric(0.0, frame.inner_margin.y))
.outer_margin(Margin::same(0.0))
Frame::NONE
.inner_margin(Margin::symmetric(0, frame.inner_margin.y as i8))
.outer_margin(Margin::same(0))
} else {
Frame::none()
.inner_margin(Margin::same(0.0))
.outer_margin(Margin::same(0.0))
Frame::NONE
.inner_margin(Margin::same(0))
.outer_margin(Margin::same(0))
};
let available_height = ui.max_rect().max.y;
@@ -897,13 +898,13 @@ impl eframe::App for Komobar {
.as_ref()
.map(|s| s.to_individual(DEFAULT_PADDING))
{
left_area_frame.inner_margin.left = padding.left;
left_area_frame.inner_margin.top = padding.top;
left_area_frame.inner_margin.bottom = padding.bottom;
left_area_frame.inner_margin.left = padding.left as i8;
left_area_frame.inner_margin.top = padding.top as i8;
left_area_frame.inner_margin.bottom = padding.bottom as i8;
} else if let Some(frame) = &self.config.frame {
left_area_frame.inner_margin.left = frame.inner_margin.x;
left_area_frame.inner_margin.top = frame.inner_margin.y;
left_area_frame.inner_margin.bottom = frame.inner_margin.y;
left_area_frame.inner_margin.left = frame.inner_margin.x as i8;
left_area_frame.inner_margin.top = frame.inner_margin.y as i8;
left_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
}
left_area_frame.show(ui, |ui| {
@@ -933,13 +934,13 @@ impl eframe::App for Komobar {
.as_ref()
.map(|s| s.to_individual(DEFAULT_PADDING))
{
right_area_frame.inner_margin.right = padding.right;
right_area_frame.inner_margin.top = padding.top;
right_area_frame.inner_margin.bottom = padding.bottom;
right_area_frame.inner_margin.right = padding.right as i8;
right_area_frame.inner_margin.top = padding.top as i8;
right_area_frame.inner_margin.bottom = padding.bottom as i8;
} else if let Some(frame) = &self.config.frame {
right_area_frame.inner_margin.right = frame.inner_margin.x;
right_area_frame.inner_margin.top = frame.inner_margin.y;
right_area_frame.inner_margin.bottom = frame.inner_margin.y;
right_area_frame.inner_margin.right = frame.inner_margin.x as i8;
right_area_frame.inner_margin.top = frame.inner_margin.y as i8;
right_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
}
right_area_frame.show(ui, |ui| {
@@ -977,11 +978,11 @@ impl eframe::App for Komobar {
.as_ref()
.map(|s| s.to_individual(DEFAULT_PADDING))
{
center_area_frame.inner_margin.top = padding.top;
center_area_frame.inner_margin.bottom = padding.bottom;
center_area_frame.inner_margin.top = padding.top as i8;
center_area_frame.inner_margin.bottom = padding.bottom as i8;
} else if let Some(frame) = &self.config.frame {
center_area_frame.inner_margin.top = frame.inner_margin.y;
center_area_frame.inner_margin.bottom = frame.inner_margin.y;
center_area_frame.inner_margin.top = frame.inner_margin.y as i8;
center_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
}
center_area_frame.show(ui, |ui| {

View File

@@ -8,7 +8,6 @@ use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use starship_battery::units::ratio::percent;
@@ -18,7 +17,8 @@ use std::process::Command;
use std::time::Duration;
use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct BatteryConfig {
/// Enable the Battery widget
pub enable: bool,

View File

@@ -6,13 +6,13 @@ use eframe::egui::TextBuffer;
use eframe::egui::Vec2;
use komorebi_client::KomorebiTheme;
use komorebi_client::Rect;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.bar.json` configuration file reference for `v0.1.35`
pub struct KomobarConfig {
/// Bar height (default: 50)
@@ -125,9 +125,19 @@ impl KomobarConfig {
}
}
}
pub fn show_all_icons_on_komorebi_workspace(widgets: &[WidgetConfig]) -> bool {
widgets
.iter()
.any(|w| matches!(w, WidgetConfig::Komorebi(config) if config.workspaces.is_some_and(|w| w.enable && w.display.is_some_and(|s| matches!(s,
WorkspacesDisplayFormat::AllIcons
| WorkspacesDisplayFormat::AllIconsAndText
| WorkspacesDisplayFormat::AllIconsAndTextOnSelected)))))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct PositionConfig {
/// The desired starting position of the bar (0,0 = top left of the screen)
#[serde(alias = "position")]
@@ -137,13 +147,15 @@ pub struct PositionConfig {
pub end: Option<Position>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct FrameConfig {
/// Margin inside the painted frame
pub inner_margin: Position,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum MonitorConfigOrIndex {
/// The monitor index where you want the bar to show
@@ -152,7 +164,8 @@ pub enum MonitorConfigOrIndex {
MonitorConfig(MonitorConfig),
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct MonitorConfig {
/// Komorebi monitor index of the monitor on which to render the bar
pub index: usize,
@@ -163,7 +176,8 @@ pub struct MonitorConfig {
pub type Padding = SpacingKind;
pub type Margin = SpacingKind;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
// WARNING: To any developer messing with this code in the future: The order here matters!
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
@@ -214,20 +228,23 @@ impl SpacingKind {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GroupedSpacingConfig {
pub vertical: Option<GroupedSpacingOptions>,
pub horizontal: Option<GroupedSpacingOptions>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum GroupedSpacingOptions {
Symmetrical(f32),
Split(f32, f32),
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndividualSpacingConfig {
pub top: f32,
pub bottom: f32,
@@ -329,7 +346,8 @@ impl KomobarConfig {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Position {
/// X coordinate
pub x: f32,
@@ -355,7 +373,8 @@ impl From<Position> for Pos2 {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")]
pub enum KomobarTheme {
/// A theme from catppuccin-egui
@@ -391,7 +410,8 @@ impl From<KomorebiTheme> for KomobarTheme {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum LabelPrefix {
/// Show no prefix
None,
@@ -403,7 +423,8 @@ pub enum LabelPrefix {
IconAndText,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DisplayFormat {
/// Show only icon
Icon,
@@ -416,3 +437,91 @@ pub enum DisplayFormat {
/// Show an icon and text for the selected element, and icons on the rest
IconAndTextOnSelected,
}
macro_rules! extend_enum {
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum $new_enum {
// Add new variants
$(
$(#[$meta])*
$variant,
)*
// Include a variant that wraps the existing enum and flatten it when deserializing
#[serde(untagged)]
Existing($existing_enum),
}
// Implement From for the existing enum
impl From<$existing_enum> for $new_enum {
fn from(value: $existing_enum) -> Self {
$new_enum::Existing(value)
}
}
};
}
extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
/// Show all icons only
AllIcons,
/// Show both all icons and text
AllIconsAndText,
/// Show all icons and text for the selected element, and all icons on the rest
AllIconsAndTextOnSelected,
});
#[cfg(test)]
mod tests {
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum OriginalDisplayFormat {
/// Show None Of The Things
NoneOfTheThings,
}
extend_enum!(OriginalDisplayFormat, ExtendedDisplayFormat, {
/// Show Some Of The Things
SomeOfTheThings,
});
#[derive(serde::Deserialize)]
struct ExampleConfig {
#[allow(unused)]
format: ExtendedDisplayFormat,
}
#[test]
pub fn extend_new_variant() {
let raw = json!({
"format": "SomeOfTheThings",
})
.to_string();
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
}
#[test]
pub fn extend_existing_variant() {
let raw = json!({
"format": "NoneOfTheThings",
})
.to_string();
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
}
#[test]
pub fn extend_invalid_variant() {
let raw = json!({
"format": "ALLOFTHETHINGS",
})
.to_string();
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_err())
}
}

View File

@@ -8,7 +8,6 @@ use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
@@ -17,7 +16,8 @@ use std::time::Instant;
use sysinfo::RefreshKind;
use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct CpuConfig {
/// Enable the Cpu widget
pub enable: bool,

View File

@@ -9,12 +9,12 @@ use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::WidgetText;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
/// Custom format with additive modifiers for integer format specifiers
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct CustomModifiers {
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
format: String,
@@ -55,7 +55,8 @@ impl CustomModifiers {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct DateConfig {
/// Enable the Date widget
pub enable: bool,
@@ -75,7 +76,8 @@ impl From<DateConfig> for Date {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DateFormat {
/// Month/Date/Year format (09/08/24)
MonthDateYear,

View File

@@ -8,7 +8,6 @@ use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::WidgetText;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
@@ -23,7 +22,8 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
const ERROR_TEXT: &str = "Error";
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KeyboardConfig {
/// Enable the Input widget
pub enable: bool,

View File

@@ -1,6 +1,7 @@
use crate::bar::apply_theme;
use crate::config::DisplayFormat;
use crate::config::KomobarTheme;
use crate::config::WorkspacesDisplayFormat;
use crate::komorebi_layout::KomorebiLayout;
use crate::render::Grouping;
use crate::render::RenderConfig;
@@ -14,13 +15,15 @@ use eframe::egui::vec2;
use eframe::egui::Color32;
use eframe::egui::ColorImage;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::Frame;
use eframe::egui::Image;
use eframe::egui::Label;
use eframe::egui::Margin;
use eframe::egui::Rounding;
use eframe::egui::RichText;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::StrokeKind;
use eframe::egui::TextureHandle;
use eframe::egui::TextureOptions;
use eframe::egui::Ui;
@@ -33,7 +36,7 @@ use komorebi_client::Rect;
use komorebi_client::SocketMessage;
use komorebi_client::Window;
use komorebi_client::Workspace;
use schemars::JsonSchema;
use komorebi_client::WorkspaceLayer;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
@@ -43,29 +46,34 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::Ordering;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiConfig {
/// Configure the Workspaces widget
pub workspaces: Option<KomorebiWorkspacesConfig>,
/// Configure the Layout widget
pub layout: Option<KomorebiLayoutConfig>,
/// Configure the Workspace Layer widget
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
/// Configure the Focused Window widget
pub focused_window: Option<KomorebiFocusedWindowConfig>,
/// Configure the Configuration Switcher widget
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiWorkspacesConfig {
/// Enable the Komorebi Workspaces widget
pub enable: bool,
/// Hide workspaces without any windows
pub hide_empty_workspaces: bool,
/// Display format of the workspace
pub display: Option<DisplayFormat>,
pub display: Option<WorkspacesDisplayFormat>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiLayoutConfig {
/// Enable the Komorebi Layout widget
pub enable: bool,
@@ -75,7 +83,19 @@ pub struct KomorebiLayoutConfig {
pub display: Option<DisplayFormat>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiWorkspaceLayerConfig {
/// Enable the Komorebi Workspace Layer widget
pub enable: bool,
/// Display format of the current layer
pub display: Option<DisplayFormat>,
/// Show the widget event if the layer is Tiling
pub show_when_tiling: Option<bool>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiFocusedWindowConfig {
/// Enable the Komorebi Focused Window widget
pub enable: bool,
@@ -85,7 +105,8 @@ pub struct KomorebiFocusedWindowConfig {
pub display: Option<DisplayFormat>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KomorebiConfigurationSwitcherConfig {
/// Enable the Komorebi Configurations widget
pub enable: bool,
@@ -127,6 +148,7 @@ impl From<&KomorebiConfig> for Komorebi {
workspaces: value.workspaces,
layout: value.layout.clone(),
focused_window: value.focused_window,
workspace_layer: value.workspace_layer,
configuration_switcher,
}
}
@@ -138,6 +160,7 @@ pub struct Komorebi {
pub workspaces: Option<KomorebiWorkspacesConfig>,
pub layout: Option<KomorebiLayoutConfig>,
pub focused_window: Option<KomorebiFocusedWindowConfig>,
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
@@ -145,84 +168,92 @@ impl BarWidget for Komorebi {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
let icon_size = Vec2::splat(config.icon_font_id.size);
let text_size = Vec2::splat(config.text_font_id.size);
if let Some(workspaces) = self.workspaces {
if workspaces.enable {
let mut update = None;
if !komorebi_notification_state.workspaces.is_empty() {
let format = workspaces.display.unwrap_or(DisplayFormat::Text);
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
config.apply_on_widget(false, ui, |ui| {
for (i, (ws, container_information)) in
for (i, (ws, containers, _)) in
komorebi_notification_state.workspaces.iter().enumerate()
{
let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
if SelectableFrame::new(
komorebi_notification_state.selected_workspace.eq(ws),
is_selected,
)
.show(ui, |ui| {
let mut has_icon = false;
if format == DisplayFormat::Icon
|| format == DisplayFormat::IconAndText
|| format == DisplayFormat::IconAndTextOnSelected
|| (format == DisplayFormat::TextAndIconOnSelected
&& komorebi_notification_state.selected_workspace.eq(ws))
if format == WorkspacesDisplayFormat::AllIcons
|| format == WorkspacesDisplayFormat::AllIconsAndText
|| format == WorkspacesDisplayFormat::AllIconsAndTextOnSelected
|| format == DisplayFormat::Icon.into()
|| format == DisplayFormat::IconAndText.into()
|| format == DisplayFormat::IconAndTextOnSelected.into()
|| (format == DisplayFormat::TextAndIconOnSelected.into() && is_selected)
{
let icons: Vec<_> =
container_information.icons.iter().flatten().collect();
has_icon = containers.iter().any(|(_, container_info)| {
container_info.icons.iter().any(|icon| icon.is_some())
});
if !icons.is_empty() {
Frame::none()
if has_icon {
Frame::NONE
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y,
ui.style().spacing.button_padding.y as i8,
))
.show(ui, |ui| {
for icon in icons {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.fit_to_exact_size(icon_size),
);
if !has_icon {
has_icon = true;
for (is_focused, container) in containers {
for icon in container.icons.iter().flatten().collect::<Vec<_>>() {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.fit_to_exact_size(if *is_focused { icon_size } else { text_size }),
);
}
}
});
}
}
// draw a custom icon when there is no app icon
if match format {
DisplayFormat::Icon => !has_icon,
_ => false,
} {
// draw a custom icon when there is no app icon or text
if !has_icon && (matches!(format, WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon))
|| (!is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))) {
let (response, painter) =
ui.allocate_painter(icon_size, Sense::hover());
let stroke = Stroke::new(
1.0,
ctx.style().visuals.selection.stroke.color,
if is_selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() },
);
let mut rect = response.rect;
let rounding = Rounding::same(rect.width() * 0.1);
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);
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
response.on_hover_text(ws.to_string())
// add hover text when there are only icons
} else if match format {
DisplayFormat::Icon => has_icon,
WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon) => has_icon,
_ => false,
} {
ui.response().on_hover_text(ws.to_string())
} else if format != DisplayFormat::IconAndTextOnSelected
|| (format == DisplayFormat::IconAndTextOnSelected
&& komorebi_notification_state.selected_workspace.eq(ws))
// add label only
} else if (format != WorkspacesDisplayFormat::AllIconsAndTextOnSelected && format != DisplayFormat::IconAndTextOnSelected.into())
|| (is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))
{
ui.add(Label::new(ws.to_string()).selectable(false))
if is_selected {
ui.add(Label::new(RichText::new(ws.to_string()).color(ctx.style().visuals.selection.stroke.color)).selectable(false))
}
else {
ui.add(Label::new(ws.to_string()).selectable(false))
}
} else {
ui.response()
}
@@ -281,6 +312,119 @@ impl BarWidget for Komorebi {
}
}
if let Some(layer_config) = &self.workspace_layer {
if layer_config.enable {
let layer = komorebi_notification_state
.workspaces
.iter()
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
.map(|(_, _, layer)| layer);
if let Some(layer) = layer {
if (layer_config.show_when_tiling.unwrap_or_default()
&& matches!(layer, WorkspaceLayer::Tiling))
|| matches!(layer, WorkspaceLayer::Floating)
{
let display_format = layer_config.display.unwrap_or(DisplayFormat::Text);
let size = Vec2::splat(config.icon_font_id.size);
config.apply_on_widget(false, ui, |ui| {
let layer_frame = SelectableFrame::new(false)
.show(ui, |ui| {
if display_format != DisplayFormat::Text {
if matches!(layer, WorkspaceLayer::Tiling) {
let (response, painter) =
ui.allocate_painter(size, Sense::hover());
let color = ui.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let corner =
CornerRadius::same((rect.width() * 0.1) as u8);
rect = rect.shrink(stroke.width);
// tiling
let mut rect_left = response.rect;
rect_left.set_width(rect.width() * 0.48);
rect_left.set_height(rect.height() * 0.98);
let mut rect_right = rect_left;
rect_left = rect_left.translate(Vec2::new(
rect.width() * 0.01 + stroke.width,
rect.width() * 0.01 + stroke.width,
));
rect_right = rect_right.translate(Vec2::new(
rect.width() * 0.51 + stroke.width,
rect.width() * 0.01 + stroke.width,
));
painter.rect_filled(rect_left, corner, color);
painter.rect_stroke(
rect_right,
corner,
stroke,
StrokeKind::Outside,
);
} else {
let (response, painter) =
ui.allocate_painter(size, Sense::hover());
let color = ui.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let corner =
CornerRadius::same((rect.width() * 0.1) as u8);
rect = rect.shrink(stroke.width);
// floating
let mut rect_left = response.rect;
rect_left.set_width(rect.width() * 0.65);
rect_left.set_height(rect.height() * 0.65);
let mut rect_right = rect_left;
rect_left = rect_left.translate(Vec2::new(
rect.width() * 0.01 + stroke.width,
rect.width() * 0.01 + stroke.width,
));
rect_right = rect_right.translate(Vec2::new(
rect.width() * 0.34 + stroke.width,
rect.width() * 0.34 + stroke.width,
));
painter.rect_filled(rect_left, corner, color);
painter.rect_stroke(
rect_right,
corner,
stroke,
StrokeKind::Outside,
);
}
}
if display_format != DisplayFormat::Icon {
ui.add(Label::new(layer.to_string()).selectable(false));
}
})
.on_hover_text(layer.to_string());
if layer_frame.clicked()
&& komorebi_client::send_batch([
SocketMessage::MouseFollowsFocus(false),
SocketMessage::ToggleWorkspaceLayer,
SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
),
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n\
MouseFollowsFocus(false),
ToggleWorkspaceLayer,
MouseFollowsFocus({})",
komorebi_notification_state.mouse_follows_focus,
);
}
});
}
}
}
}
if let Some(layout_config) = &self.layout {
if layout_config.enable {
let workspace_idx: Option<usize> = komorebi_notification_state
@@ -376,6 +520,7 @@ impl BarWidget for Komorebi {
for (i, (title, icon)) in iter.enumerate() {
let selected = i == focused_window_idx && len != 1;
let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() };
if SelectableFrame::new(selected)
.show(ui, |ui| {
@@ -395,9 +540,9 @@ impl BarWidget for Komorebi {
&& i == focused_window_idx)
{
if let Some(img) = icon {
Frame::none()
Frame::NONE
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y,
ui.style().spacing.button_padding.y as i8,
))
.show(ui, |ui| {
let response = ui.add(
@@ -427,7 +572,7 @@ impl BarWidget for Komorebi {
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(title).selectable(false).truncate(),
Label::new(RichText::new( title).color(text_color)).selectable(false).truncate(),
);
}
})
@@ -474,9 +619,14 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
ctx.load_texture("icon", color_image, TextureOptions::default())
}
#[allow(clippy::type_complexity)]
#[derive(Clone, Debug)]
pub struct KomorebiNotificationState {
pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>,
pub workspaces: Vec<(
String,
Vec<(bool, KomorebiNotificationStateContainerInformation)>,
WorkspaceLayer,
)>,
pub selected_workspace: String,
pub focused_container_information: KomorebiNotificationStateContainerInformation,
pub layout: KomorebiLayout,
@@ -506,6 +656,8 @@ impl KomorebiNotificationState {
default_theme: Option<KomobarTheme>,
render_config: Rc<RefCell<RenderConfig>>,
) {
let show_all_icons = render_config.borrow().show_all_icons;
match notification.event {
NotificationEvent::WindowManager(_) => {}
NotificationEvent::Monitor(_) => {}
@@ -576,6 +728,7 @@ impl KomorebiNotificationState {
let focused_workspace_idx = monitor.focused_workspace_idx();
let mut workspaces = vec![];
self.selected_workspace = monitor.workspaces()[focused_workspace_idx]
.name()
.to_owned()
@@ -591,7 +744,37 @@ impl KomorebiNotificationState {
if should_show {
workspaces.push((
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
ws.into(),
if show_all_icons {
let mut containers = vec![];
let mut has_monocle = false;
// add monocle container
if let Some(container) = ws.monocle_container() {
containers.push((true, container.into()));
has_monocle = true;
}
// add all tiled windows
for (i, container) in ws.containers().iter().enumerate() {
containers.push((
!has_monocle && i == ws.focused_container_idx(),
container.into(),
));
}
// add all floating windows
for floating_window in ws.floating_windows() {
containers.push((
!has_monocle && floating_window.is_focused(),
floating_window.into(),
));
}
containers
} else {
vec![(true, ws.into())]
},
ws.layer().to_owned(),
));
}
}

View File

@@ -4,16 +4,16 @@ use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use eframe::egui::vec2;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Label;
use eframe::egui::Rounding;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::StrokeKind;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use komorebi_client::SocketMessage;
use schemars::JsonSchema;
use serde::de::Error;
use serde::Deserialize;
use serde::Deserializer;
@@ -22,7 +22,8 @@ use serde_json::from_str;
use std::fmt::Display;
use std::fmt::Formatter;
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum KomorebiLayout {
Default(komorebi_client::DefaultLayout),
@@ -122,18 +123,22 @@ impl KomorebiLayout {
}
}
fn show_icon(&mut self, font_id: FontId, ctx: &Context, ui: &mut Ui) {
fn show_icon(&mut self, is_selected: bool, font_id: FontId, ctx: &Context, ui: &mut Ui) {
// paint custom icons for the layout
let size = Vec2::splat(font_id.size);
let (response, painter) = ui.allocate_painter(size, Sense::hover());
let color = ctx.style().visuals.selection.stroke.color;
let color = if is_selected {
ctx.style().visuals.selection.stroke.color
} else {
ui.style().visuals.text_color()
};
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let rounding = Rounding::same(rect.width() * 0.1);
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);
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
match self {
KomorebiLayout::Default(layout) => match layout {
@@ -189,7 +194,7 @@ impl KomorebiLayout {
rect.width() * 0.35 + stroke.width,
));
painter.rect_filled(rect_left, rounding, color);
painter.rect_stroke(rect_right, rounding, stroke);
painter.rect_stroke(rect_right, rounding, stroke, StrokeKind::Outside);
}
KomorebiLayout::Paused => {
let mut rect_left = response.rect;
@@ -236,7 +241,7 @@ impl KomorebiLayout {
let layout_frame = SelectableFrame::new(false)
.show(ui, |ui| {
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
self.show_icon(font_id.clone(), ctx, ui);
self.show_icon(false, font_id.clone(), ctx, ui);
}
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
@@ -251,7 +256,7 @@ impl KomorebiLayout {
if show_options {
if let Some(workspace_idx) = workspace_idx {
Frame::none().show(ui, |ui| {
Frame::NONE.show(ui, |ui| {
ui.add(
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
.selectable(false),
@@ -279,8 +284,12 @@ impl KomorebiLayout {
]);
for layout_option in &mut layout_options {
if SelectableFrame::new(self == layout_option)
.show(ui, |ui| layout_option.show_icon(font_id.clone(), ctx, ui))
let is_selected = self == layout_option;
if SelectableFrame::new(is_selected)
.show(ui, |ui| {
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
})
.on_hover_text(match layout_option {
KomorebiLayout::Default(layout) => layout.to_string(),
KomorebiLayout::Monocle => "Toggle monocle".to_string(),

View File

@@ -30,7 +30,6 @@ use hotwatch::Hotwatch;
use image::RgbaImage;
use komorebi_client::SocketMessage;
use komorebi_client::SubscribeOptions;
use schemars::gen::SchemaSettings;
use std::collections::HashMap;
use std::io::BufReader;
use std::io::Read;
@@ -42,7 +41,6 @@ use std::sync::LazyLock;
use std::sync::Mutex;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::System::Threading::GetCurrentProcessId;
@@ -51,6 +49,7 @@ use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows_core::BOOL;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
@@ -125,8 +124,9 @@ fn main() -> color_eyre::Result<()> {
let opts: Opts = Opts::parse();
#[cfg(feature = "schemars")]
if opts.schema {
let settings = SchemaSettings::default().with(|s| {
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;

View File

@@ -10,13 +10,13 @@ use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::Ordering;
use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct MediaConfig {
/// Enable the Media widget
pub enable: bool,

View File

@@ -8,7 +8,6 @@ use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
@@ -17,7 +16,8 @@ use std::time::Instant;
use sysinfo::RefreshKind;
use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct MemoryConfig {
/// Enable the Memory widget
pub enable: bool,

View File

@@ -9,7 +9,6 @@ use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use num_derive::FromPrimitive;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
@@ -18,7 +17,8 @@ use std::time::Duration;
use std::time::Instant;
use sysinfo::Networks;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct NetworkConfig {
/// Enable the Network widget
pub enable: bool,

View File

@@ -3,16 +3,14 @@ use crate::config::KomobarConfig;
use crate::config::MonitorConfigOrIndex;
use eframe::egui::Color32;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::InnerResponse;
use eframe::egui::Margin;
use eframe::egui::Rounding;
use eframe::egui::Shadow;
use eframe::egui::TextStyle;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::AtomicUsize;
@@ -21,7 +19,8 @@ use std::sync::Arc;
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "kind")]
pub enum Grouping {
/// No grouping is applied
@@ -54,6 +53,8 @@ pub struct RenderConfig {
pub text_font_id: FontId,
/// FontId for icon (based on scaling the text font id)
pub icon_font_id: FontId,
/// Show all icons on the workspace section of the Komorebi widget
pub show_all_icons: bool,
}
pub trait RenderExt {
@@ -87,6 +88,15 @@ impl RenderExt for &KomobarConfig {
MonitorConfigOrIndex::Index(idx) => *idx,
};
// check if any of the alignments have a komorebi widget with the workspace set to show all icons
let show_all_icons =
KomobarConfig::show_all_icons_on_komorebi_workspace(&self.left_widgets)
|| self
.center_widgets
.as_ref()
.is_some_and(|list| KomobarConfig::show_all_icons_on_komorebi_workspace(list))
|| KomobarConfig::show_all_icons_on_komorebi_workspace(&self.right_widgets);
RenderConfig {
monitor_idx,
spacing: self.widget_spacing.unwrap_or(10.0),
@@ -97,6 +107,7 @@ impl RenderExt for &KomobarConfig {
applied_on_widget: false,
text_font_id,
icon_font_id,
show_all_icons,
}
}
}
@@ -121,6 +132,7 @@ impl RenderConfig {
applied_on_widget: false,
text_font_id: FontId::default(),
icon_font_id: FontId::default(),
show_all_icons: false,
}
}
@@ -135,10 +147,10 @@ impl RenderConfig {
return self.define_group_frame(
//TODO: this outer margin can be a config
Some(Margin {
left: 10.0,
right: 10.0,
top: 6.0,
bottom: 6.0,
left: 10,
right: 10,
top: 6,
bottom: 6,
}),
config,
ui_style,
@@ -191,11 +203,11 @@ impl RenderConfig {
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Frame::none()
Frame::NONE
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(5.0, 0.0),
false => Margin::same(0.0),
true => Margin::symmetric(5, 0),
false => Margin::same(0),
})
.show(ui, add_contents)
}
@@ -220,13 +232,13 @@ impl RenderConfig {
Frame::group(ui_style)
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(6.0, 1.0),
false => Margin::symmetric(1.0, 1.0),
true => Margin::symmetric(6, 1),
false => Margin::symmetric(1, 1),
})
.stroke(ui_style.visuals.widgets.noninteractive.bg_stroke)
.rounding(match config.rounding {
.corner_radius(match config.rounding {
Some(rounding) => rounding.into(),
None => ui_style.visuals.widgets.noninteractive.rounding,
None => ui_style.visuals.widgets.noninteractive.corner_radius,
})
.fill(
self.background_color
@@ -237,27 +249,27 @@ impl RenderConfig {
// new styles can be added if needed here
GroupingStyle::Default => Shadow::NONE,
GroupingStyle::DefaultWithShadowB4O1S3 => Shadow {
blur: 4.0,
offset: Vec2::new(1.0, 1.0),
spread: 3.0,
blur: 4,
offset: [1, 1],
spread: 3,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithShadowB4O0S3 => Shadow {
blur: 4.0,
offset: Vec2::new(0.0, 0.0),
spread: 3.0,
blur: 4,
offset: [0, 0],
spread: 3,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithShadowB0O1S3 => Shadow {
blur: 0.0,
offset: Vec2::new(1.0, 1.0),
spread: 3.0,
blur: 0,
offset: [1, 1],
spread: 3,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithGlowB3O1S2 => Shadow {
blur: 3.0,
offset: Vec2::new(1.0, 1.0),
spread: 2.0,
blur: 3,
offset: [1, 1],
spread: 2,
color: ui_style
.visuals
.selection
@@ -266,9 +278,9 @@ impl RenderConfig {
.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithGlowB3O0S2 => Shadow {
blur: 3.0,
offset: Vec2::new(0.0, 0.0),
spread: 2.0,
blur: 3,
offset: [0, 0],
spread: 2,
color: ui_style
.visuals
.selection
@@ -277,9 +289,9 @@ impl RenderConfig {
.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithGlowB0O1S2 => Shadow {
blur: 0.0,
offset: Vec2::new(1.0, 1.0),
spread: 2.0,
blur: 0,
offset: [1, 1],
spread: 2,
color: ui_style
.visuals
.selection
@@ -295,9 +307,9 @@ impl RenderConfig {
fn widget_outer_margin(&mut self, ui: &mut Ui) -> Margin {
let spacing = if self.applied_on_widget {
// Remove the default item spacing from the margin
self.spacing - ui.spacing().item_spacing.x
(self.spacing - ui.spacing().item_spacing.x) as i8
} else {
0.0
0
};
if !self.applied_on_widget {
@@ -309,25 +321,26 @@ impl RenderConfig {
Some(align) => match align {
Alignment::Left => spacing,
Alignment::Center => spacing,
Alignment::Right => 0.0,
Alignment::Right => 0,
},
None => 0.0,
None => 0,
},
right: match self.alignment {
Some(align) => match align {
Alignment::Left => 0.0,
Alignment::Center => 0.0,
Alignment::Left => 0,
Alignment::Center => 0,
Alignment::Right => spacing,
},
None => 0.0,
None => 0,
},
top: 0.0,
bottom: 0.0,
top: 0,
bottom: 0,
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GroupingConfig {
/// Styles for the grouping
pub style: Option<GroupingStyle>,
@@ -337,7 +350,8 @@ pub struct GroupingConfig {
pub rounding: Option<RoundingConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum GroupingStyle {
#[serde(alias = "CtByte")]
Default,
@@ -357,7 +371,8 @@ pub enum GroupingStyle {
DefaultWithGlowB0O1S2,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum RoundingConfig {
/// All 4 corners are the same
@@ -366,16 +381,19 @@ pub enum RoundingConfig {
Individual([f32; 4]),
}
impl From<RoundingConfig> for Rounding {
impl From<RoundingConfig> for CornerRadius {
fn from(value: RoundingConfig) -> Self {
match value {
RoundingConfig::Same(value) => Rounding::same(value),
RoundingConfig::Individual(values) => Self {
nw: values[0],
ne: values[1],
sw: values[2],
se: values[3],
},
RoundingConfig::Same(value) => Self::same(value as u8),
RoundingConfig::Individual(values) => {
let values = values.map(|f| f as u8);
Self {
nw: values[0],
ne: values[1],
sw: values[2],
se: values[3],
}
}
}
}
}

View File

@@ -1,8 +1,10 @@
use eframe::egui::Color32;
use eframe::egui::CursorIcon;
use eframe::egui::Frame;
use eframe::egui::Margin;
use eframe::egui::Response;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::Ui;
/// Same as SelectableLabel, but supports all content
@@ -18,31 +20,39 @@ impl SelectableFrame {
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
let Self { selected } = self;
Frame::none()
Frame::NONE
.show(ui, |ui| {
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
if ui.is_rect_visible(response.rect) {
// take into account the stroke width
let inner_margin = Margin::symmetric(
ui.style().spacing.button_padding.x,
ui.style().spacing.button_padding.y,
ui.style().spacing.button_padding.x as i8 - 1,
ui.style().spacing.button_padding.y as i8 - 1,
);
if selected
|| response.hovered()
|| response.highlighted()
|| response.has_focus()
{
// since the stroke is drawn inside the frame, we always reserve space for it
if response.hovered() || response.highlighted() || response.has_focus() {
let visuals = ui.style().interact_selectable(&response, selected);
Frame::none()
.stroke(visuals.bg_stroke)
.rounding(visuals.rounding)
Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_stroke.color))
.corner_radius(visuals.corner_radius)
.fill(visuals.bg_fill)
.inner_margin(inner_margin)
.show(ui, add_contents);
} else if selected {
let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE
.stroke(Stroke::new(1.0, visuals.bg_fill))
.corner_radius(visuals.corner_radius)
.fill(visuals.bg_fill)
.inner_margin(inner_margin)
.show(ui, add_contents);
} else {
Frame::none()
Frame::NONE
.stroke(Stroke::new(1.0, Color32::TRANSPARENT))
.inner_margin(inner_margin)
.show(ui, add_contents);
}

View File

@@ -8,7 +8,6 @@ use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
@@ -16,7 +15,8 @@ use std::time::Duration;
use std::time::Instant;
use sysinfo::Disks;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct StorageConfig {
/// Enable the Storage widget
pub enable: bool,

View File

@@ -6,18 +6,19 @@ use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::Label;
use eframe::egui::Rounding;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use schemars::JsonSchema;
use eframe::epaint::StrokeKind;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TimeConfig {
/// Enable the Time widget
pub enable: bool,
@@ -37,7 +38,8 @@ impl From<TimeConfig> for Time {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum TimeFormat {
/// Twelve-hour format (with seconds)
TwelveHour,
@@ -108,36 +110,37 @@ impl Time {
let full_height = size;
let height = full_height / 4.0;
let width = height;
let offset = height / 2.0 - height / 8.0;
let (response, painter) =
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
let color = ctx.style().visuals.text_color();
let c = response.rect.center();
let r = height / 2.0 - 0.5;
if number == 1 || number == 3 || number == 5 || number == 7 || number == 9 {
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r, color);
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r, color);
} else {
painter.circle_filled(c + Vec2::new(0.0, height * 1.50), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r / 2.5, color);
}
if number == 2 || number == 3 || number == 6 || number == 7 {
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r, color);
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r, color);
} else {
painter.circle_filled(c + Vec2::new(0.0, height * 0.50), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r / 2.5, color);
}
if number == 4 || number == 5 || number == 6 || number == 7 {
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r, color);
} else if max_power > 2 {
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r / 2.5, color);
}
if number == 8 || number == 9 {
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r, color);
} else if max_power > 3 {
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50), r / 2.5, color);
painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r / 2.5, color);
}
}
@@ -152,37 +155,46 @@ impl Time {
let full_height = size;
let height = full_height / 4.0;
let width = height * 1.5;
let offset = height / 2.0 - height / 8.0;
let (response, painter) =
ui.allocate_painter(Vec2::new(width, full_height), Sense::hover());
ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());
let color = ctx.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
let round_all = Rounding::same(response.rect.width() * 0.1);
let round_top = Rounding {
let round_all = CornerRadius::same((response.rect.width() * 0.1) as u8);
let round_top = CornerRadius {
nw: round_all.nw,
ne: round_all.ne,
..Default::default()
};
let round_none = Rounding::ZERO;
let round_bottom = Rounding {
let round_none = CornerRadius::ZERO;
let round_bottom = CornerRadius {
sw: round_all.nw,
se: round_all.ne,
..Default::default()
};
if max_power == 2 {
let mut rect = response.rect.shrink(stroke.width);
let mut rect = response
.rect
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
rect.set_height(rect.height() - height * 2.0);
rect = rect.translate(Vec2::new(0.0, height * 2.0));
painter.rect_stroke(rect, round_all, stroke);
rect = rect.translate(Vec2::new(0.0, height * 2.0 + offset));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
} else if max_power == 3 {
let mut rect = response.rect.shrink(stroke.width);
let mut rect = response
.rect
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
rect.set_height(rect.height() - height);
rect = rect.translate(Vec2::new(0.0, height));
painter.rect_stroke(rect, round_all, stroke);
rect = rect.translate(Vec2::new(0.0, height + offset));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
} else {
painter.rect_stroke(response.rect.shrink(stroke.width), round_all, stroke);
let mut rect = response
.rect
.shrink2(Vec2::new(stroke.width, stroke.width + offset));
rect = rect.translate(Vec2::new(0.0, 0.0 + offset));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
}
let mut rect_bin = response.rect;
@@ -191,7 +203,7 @@ impl Time {
if number == 1 || number == 5 || number == 9 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(0.0, height * 3.0)),
rect_bin.translate(Vec2::new(stroke.width, height * 3.0 + offset * 2.0)),
round_bottom,
color,
);
@@ -199,7 +211,7 @@ impl Time {
if number == 2 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
if max_power == 2 {
round_top
} else {
@@ -211,7 +223,7 @@ impl Time {
if number == 3 {
rect_bin.set_height(height * 2.0);
painter.rect_filled(
rect_bin.translate(Vec2::new(0.0, height * 2.0)),
rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),
round_bottom,
color,
);
@@ -219,7 +231,7 @@ impl Time {
if number == 4 || number == 5 {
rect_bin.set_height(height);
painter.rect_filled(
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
if max_power == 3 {
round_top
} else {
@@ -231,7 +243,7 @@ impl Time {
if number == 6 {
rect_bin.set_height(height * 2.0);
painter.rect_filled(
rect_bin.translate(Vec2::new(0.0, height * 1.0)),
rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),
if max_power == 3 {
round_top
} else {
@@ -243,7 +255,7 @@ impl Time {
if number == 7 {
rect_bin.set_height(height * 3.0);
painter.rect_filled(
rect_bin.translate(Vec2::new(0.0, height)),
rect_bin.translate(Vec2::new(stroke.width, height + offset * 2.0)),
if max_power == 3 {
round_all
} else {
@@ -254,7 +266,11 @@ impl Time {
}
if number == 8 || number == 9 {
rect_bin.set_height(height);
painter.rect_filled(rect_bin.translate(Vec2::new(0.0, 0.0)), round_top, color);
painter.rect_filled(
rect_bin.translate(Vec2::new(stroke.width, 0.0 + offset * 2.0)),
round_top,
color,
);
}
}
}

View File

@@ -8,14 +8,14 @@ use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct UpdateConfig {
/// Enable the Update widget
pub enable: bool,

View File

@@ -23,7 +23,6 @@ use crate::update::Update;
use crate::update::UpdateConfig;
use eframe::egui::Context;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -31,7 +30,8 @@ pub trait BarWidget {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WidgetConfig {
Battery(BatteryConfig),
Cpu(CpuConfig),

View File

@@ -10,3 +10,7 @@ komorebi = { path = "../komorebi" }
uds_windows = { workspace = true }
serde_json = { workspace = true }
[features]
default = ["schemars"]
schemars = ["komorebi/schemars"]

View File

@@ -4,6 +4,7 @@
pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::animation::PerAnimationPrefixConfig;
pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::border_manager::BorderInfo;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
pub use komorebi::config_generation::ApplicationConfiguration;
@@ -48,6 +49,8 @@ pub use komorebi::ring::Ring;
pub use komorebi::window::Window;
pub use komorebi::window_manager_event::WindowManagerEvent;
pub use komorebi::workspace::Workspace;
pub use komorebi::workspace::WorkspaceGlobals;
pub use komorebi::workspace::WorkspaceLayer;
pub use komorebi::AnimationsConfig;
pub use komorebi::AspectRatio;
pub use komorebi::BorderColours;

View File

@@ -12,4 +12,5 @@ eframe = { workspace = true }
egui_extras = { workspace = true }
random_word = { version = "0.4", features = ["en"] }
serde_json = { workspace = true }
windows-core = { workspace = true }
windows = { workspace = true }

View File

@@ -215,7 +215,7 @@ impl KomorebiGui {
extern "system" fn enum_window(
hwnd: windows::Win32::Foundation::HWND,
lparam: windows::Win32::Foundation::LPARAM,
) -> windows::Win32::Foundation::BOOL {
) -> windows_core::BOOL {
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
let window = Window::from(hwnd.0 as isize);

View File

@@ -4,11 +4,11 @@ version = "0.1.35"
edition = "2021"
[dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "911079d" }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "f85cc3c", default-features = false, features = ["egui30"] }
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "96f26c88d83781f234d42222293ec73d23a39ad8" }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
eframe = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_variant = "0.1"
strum = "0.26"
strum = { workspace = true }

View File

@@ -29,12 +29,12 @@ os_info = "3.10"
parking_lot = "0.12"
paste = { workspace = true }
regex = "1"
schemars = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
shadow-rs = { workspace = true }
strum = { version = "0.26", features = ["derive"] }
strum = { workspace = true }
sysinfo = { workspace = true }
tracing = { workspace = true }
tracing-appender = { workspace = true }
@@ -44,6 +44,7 @@ which = { workspace = true }
win32-display-data = { workspace = true }
windows = { workspace = true }
windows-core = { workspace = true }
windows-numerics = { workspace = true }
windows-implement = { workspace = true }
windows-interface = { workspace = true }
winput = "0.2"
@@ -54,6 +55,9 @@ shadow-rs = { workspace = true }
[dev-dependencies]
reqwest = { version = "0.12", features = ["blocking"] }
uuid = { version = "1", features = ["v4"] }
[features]
default = ["schemars"]
deadlock_detection = ["parking_lot/deadlock_detection"]
schemars = ["dep:schemars"]

View File

@@ -1,7 +1,5 @@
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::Ordering;
@@ -13,7 +11,8 @@ use super::ANIMATION_DURATION_GLOBAL;
use super::ANIMATION_FPS;
use super::ANIMATION_MANAGER;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AnimationEngine;
impl AnimationEngine {

View File

@@ -18,11 +18,12 @@ pub mod prefix;
pub mod render_dispatcher;
pub use render_dispatcher::RenderDispatcher;
pub mod style;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum PerAnimationPrefixConfig<T> {
Prefix(HashMap<AnimationPrefix, T>),

View File

@@ -1,24 +1,14 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum AnimationPrefix {

View File

@@ -3,8 +3,6 @@ use crate::border_manager::RenderTarget;
use crate::border_manager::WindowKind;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::FOCUS_STATE;
use crate::border_manager::RENDER_TARGETS;
use crate::border_manager::STYLE;
use crate::core::BorderStyle;
use crate::core::Rect;
@@ -18,8 +16,6 @@ use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::LazyLock;
use std::sync::OnceLock;
use windows::Foundation::Numerics::Matrix3x2;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::FALSE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
@@ -68,7 +64,9 @@ use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::BOOL;
use windows_core::PCWSTR;
use windows_numerics::Matrix3x2;
pub struct RenderFactory(ID2D1Factory);
unsafe impl Sync for RenderFactory {}
@@ -114,6 +112,8 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
#[derive(Debug, Clone)]
pub struct Border {
pub hwnd: isize,
pub id: String,
pub monitor_idx: Option<usize>,
pub render_target: OnceLock<RenderTarget>,
pub tracking_hwnd: isize,
pub window_rect: Rect,
@@ -130,6 +130,8 @@ impl From<isize> for Border {
fn from(value: isize) -> Self {
Self {
hwnd: value,
id: String::new(),
monitor_idx: None,
render_target: OnceLock::new(),
tracking_hwnd: 0,
window_rect: Rect::default(),
@@ -149,7 +151,11 @@ impl Border {
HWND(windows_api::as_ptr!(self.hwnd))
}
pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result<Self> {
pub fn create(
id: &str,
tracking_hwnd: isize,
monitor_idx: usize,
) -> color_eyre::Result<Box<Self>> {
let name: Vec<u16> = format!("komoborder-{id}\0").encode_utf16().collect();
let class_name = PCWSTR(name.as_ptr());
@@ -168,9 +174,12 @@ impl Border {
let (border_sender, border_receiver) = mpsc::channel();
let instance = h_module.0 as isize;
let container_id = id.to_owned();
std::thread::spawn(move || -> color_eyre::Result<()> {
let mut border = Self {
hwnd: 0,
id: container_id,
monitor_idx: Some(monitor_idx),
render_target: OnceLock::new(),
tracking_hwnd,
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
@@ -183,12 +192,15 @@ impl Border {
brushes: HashMap::new(),
};
let border_pointer = std::ptr::addr_of!(border);
let border_pointer = &raw mut border;
let hwnd =
WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance, border_pointer)?;
border.hwnd = hwnd;
border_sender.send(border_pointer as isize)?;
let boxed = unsafe {
(*border_pointer).hwnd = hwnd;
Box::from_raw(border_pointer)
};
border_sender.send(boxed)?;
let mut msg: MSG = MSG::default();
@@ -207,8 +219,7 @@ impl Border {
Ok(())
});
let border_ref = border_receiver.recv()?;
let border = unsafe { &mut *(border_ref as *mut Border) };
let mut border = border_receiver.recv()?;
// I have literally no idea, apparently this is to get rid of the black pixels
// around the edges of rounded corners? @lukeyou05 borrowed this from PowerToys
@@ -292,17 +303,13 @@ impl Border {
}
};
let mut render_targets = RENDER_TARGETS.lock();
render_targets.insert(border.hwnd, RenderTarget(render_target));
Ok(border.clone())
Ok(border)
},
Err(error) => Err(error.into()),
}
}
pub fn destroy(&self) -> color_eyre::Result<()> {
let mut render_targets = RENDER_TARGETS.lock();
render_targets.remove(&self.hwnd);
WindowsApi::close_window(self.hwnd)
}
@@ -361,7 +368,7 @@ impl Border {
return LRESULT(0);
}
let reference_hwnd = lparam.0;
let reference_hwnd = (*border_pointer).tracking_hwnd;
let old_rect = (*border_pointer).window_rect;
let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default();
@@ -475,13 +482,6 @@ impl Border {
});
// Get window kind and color
(*border_pointer).window_kind = FOCUS_STATE
.lock()
.get(&(window.0 as isize))
.copied()
.unwrap_or(WindowKind::Unfocused);
let window_kind = (*border_pointer).window_kind;
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
render_target.BeginDraw();

View File

@@ -5,6 +5,8 @@ use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::WindowKind;
use crate::ring::Ring;
use crate::windows_api;
use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::Colour;
use crate::Rgb;
@@ -18,7 +20,6 @@ use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::hash_map::Entry;
@@ -31,6 +32,7 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
use strum::Display;
use windows::Win32::Foundation::HWND;
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
@@ -54,11 +56,8 @@ lazy_static! {
}
lazy_static! {
static ref BORDERS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, Border>> = Mutex::new(HashMap::new());
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
static ref RENDER_TARGETS: Mutex<HashMap<isize, RenderTarget>> = Mutex::new(HashMap::new());
static ref BORDER_STATE: Mutex<HashMap<String, Box<Border>>> = Mutex::new(HashMap::new());
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, String>> = Mutex::new(HashMap::new());
}
#[derive(Debug, Clone)]
@@ -75,6 +74,18 @@ impl Deref for RenderTarget {
pub struct Notification(pub Option<isize>);
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct BorderInfo {
pub border_hwnd: isize,
pub window_kind: WindowKind,
}
impl BorderInfo {
pub fn hwnd(&self) -> HWND {
HWND(windows_api::as_ptr!(self.border_hwnd))
}
}
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
@@ -89,8 +100,13 @@ fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn window_border(hwnd: isize) -> Option<Border> {
WINDOWS_BORDERS.lock().get(&hwnd).cloned()
pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
WINDOWS_BORDERS.lock().get(&hwnd).and_then(|id| {
BORDER_STATE.lock().get(id).map(|b| BorderInfo {
border_hwnd: b.hwnd,
window_kind: b.window_kind,
})
})
}
pub fn send_notification(hwnd: Option<isize>) {
@@ -106,14 +122,11 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()
);
for (_, border) in borders.iter() {
let _ = border.destroy();
for (_, border) in borders.drain() {
let _ = destroy_border(border);
}
borders.clear();
BORDERS_MONITORS.lock().clear();
FOCUS_STATE.lock().clear();
RENDER_TARGETS.lock().clear();
WINDOWS_BORDERS.lock().clear();
let mut remaining_hwnds = vec![];
@@ -126,7 +139,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
for hwnd in remaining_hwnds {
let _ = Border::from(hwnd).destroy();
let _ = destroy_border(Box::new(Border::from(hwnd)));
}
}
@@ -166,6 +179,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let mut previous_pending_move_op = None;
let mut previous_is_paused = false;
let mut previous_notification: Option<Notification> = None;
let mut previous_layer = WorkspaceLayer::default();
'receiver: for notification in receiver {
// Check the wm state every time we receive a notification
@@ -182,6 +196,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
let workspace_layer = *state.monitors.elements()[focused_monitor_idx].workspaces()
[focused_workspace_idx]
.layer();
let foreground_window = WindowsApi::foreground_window().unwrap_or_default();
drop(state);
@@ -271,10 +288,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// border.
(fw != &foreground_window
&& window_border(*fw)
.map(|b| b.window_kind == WindowKind::Floating)
.unwrap_or_default())
.is_some_and(|b| b.window_kind == WindowKind::Floating))
});
// when the focused window has an `Unfocused` border kind, usually this happens if
// we focus an admin window and then refocus the previously focused window. For
// komorebi it will have the same state has before, however the previously focused
// window changed its border to unfocused so now we need to update it again.
if !should_process_notification
&& window_border(notification.0.unwrap_or_default())
.is_some_and(|b| b.window_kind == WindowKind::Unfocused)
{
should_process_notification = true;
}
if !should_process_notification && switch_focus_to_from_floating_window {
should_process_notification = true;
}
@@ -293,7 +320,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
let mut borders = BORDER_STATE.lock();
let mut borders_monitors = BORDERS_MONITORS.lock();
let mut windows_borders = WINDOWS_BORDERS.lock();
// If borders are disabled
@@ -304,11 +330,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|| ALT_TAB_HWND.load().is_some()
{
// Destroy the borders we know about
for (_, border) in borders.iter() {
border.destroy()?;
for (_, border) in borders.drain() {
destroy_border(border)?;
}
borders.clear();
windows_borders.clear();
previous_is_paused = is_paused;
@@ -320,19 +345,13 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Some(ws) = m.focused_workspace() {
// Workspaces with tiling disabled don't have borders
if !ws.tile() {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default()
== monitor_idx
{
border.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
// Remove all borders on this monitor
remove_borders(
&mut borders,
&mut windows_borders,
monitor_idx,
|_, _| true,
)?;
continue 'monitors;
}
@@ -340,12 +359,16 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Handle the monocle container separately
if let Some(monocle) = ws.monocle_container() {
let mut new_border = false;
let border = match borders.entry(monocle.id().clone()) {
let focused_window_hwnd =
monocle.focused_window().map(|w| w.hwnd).unwrap_or_default();
let id = monocle.id().clone();
let border = match borders.entry(id.clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(
monocle.id(),
monocle.focused_window().copied().unwrap_or_default().hwnd,
focused_window_hwnd,
monitor_idx,
) {
new_border = true;
entry.insert(border)
@@ -361,43 +384,44 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
WindowKind::Monocle
};
border.window_kind = new_focus_state;
{
let mut focus_state = FOCUS_STATE.lock();
focus_state.insert(border.hwnd, new_focus_state);
// Update the borders tracking_hwnd in case it changed and remove the
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
if border.tracking_hwnd != focused_window_hwnd {
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {
// Only remove the border from `windows_borders` if it
// still corresponds to the same border, if doesn't then
// it means it was already updated by another border for
// that window and in that case we don't want to remove it.
if previous == &id {
windows_borders.remove(&border.tracking_hwnd);
}
}
border.tracking_hwnd = focused_window_hwnd;
if !WindowsApi::is_window_visible(border.hwnd) {
WindowsApi::restore_window(border.hwnd);
}
}
let reference_hwnd =
monocle.focused_window().copied().unwrap_or_default().hwnd;
let rect = WindowsApi::window_rect(reference_hwnd)?;
let rect = WindowsApi::window_rect(focused_window_hwnd)?;
border.window_rect = rect;
if new_border {
border.set_position(&rect, reference_hwnd)?;
border.set_position(&rect, focused_window_hwnd)?;
}
border.invalidate();
borders_monitors.insert(monocle.id().clone(), monitor_idx);
windows_borders.insert(
monocle.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
windows_borders.insert(focused_window_hwnd, id);
let border_hwnd = border.hwnd;
let mut to_remove = vec![];
for (id, b) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default()
== monitor_idx
&& border_hwnd != b.hwnd
{
b.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
// Remove all borders on this monitor except monocle
remove_borders(
&mut borders,
&mut windows_borders,
monitor_idx,
|_, b| border_hwnd != b.hwnd,
)?;
continue 'monitors;
}
@@ -409,24 +433,18 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
&& WindowsApi::is_zoomed(foreground_hwnd);
if is_maximized {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default()
== monitor_idx
{
border.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
// Remove all borders on this monitor
remove_borders(
&mut borders,
&mut windows_borders,
monitor_idx,
|_, _| true,
)?;
continue 'monitors;
}
// Destroy any borders not associated with the focused workspace
// Collect focused workspace container and floating windows ID's
let mut container_and_floating_window_ids = ws
.containers()
.iter()
@@ -437,30 +455,27 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
container_and_floating_window_ids.push(w.hwnd.to_string());
}
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& !container_and_floating_window_ids.contains(id)
{
border.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
// Remove any borders not associated with the focused workspace
remove_borders(
&mut borders,
&mut windows_borders,
monitor_idx,
|id, _| !container_and_floating_window_ids.contains(id),
)?;
'containers: for (idx, c) in ws.containers().iter().enumerate() {
let focused_window_hwnd =
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
let id = c.id().clone();
// Get the border entry for this container from the map or create one
let mut new_border = false;
let border = match borders.entry(c.id().clone()) {
let border = match borders.entry(id.clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(
c.id(),
c.focused_window().copied().unwrap_or_default().hwnd,
) {
if let Ok(border) =
Border::create(c.id(), focused_window_hwnd, monitor_idx)
{
new_border = true;
entry.insert(border)
} else {
@@ -469,14 +484,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
};
#[allow(unused_assignments)]
let mut last_focus_state = None;
let last_focus_state = border.window_kind;
let new_focus_state = if idx != ws.focused_container_idx()
|| monitor_idx != focused_monitor_idx
|| c.focused_window()
.map(|w| w.hwnd != foreground_window)
.unwrap_or_default()
|| focused_window_hwnd != foreground_window
{
WindowKind::Unfocused
} else if c.windows().len() > 1 {
@@ -484,58 +496,65 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} else {
WindowKind::Single
};
border.window_kind = new_focus_state;
// Update the focused state for all containers on this workspace
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
// Update the borders `tracking_hwnd` in case it changed and remove the
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
if border.tracking_hwnd != focused_window_hwnd {
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {
// Only remove the border from `windows_borders` if it
// still corresponds to the same border, if doesn't then
// it means it was already updated by another border for
// that window and in that case we don't want to remove it.
if previous == &id {
windows_borders.remove(&border.tracking_hwnd);
}
}
border.tracking_hwnd = focused_window_hwnd;
if !WindowsApi::is_window_visible(border.hwnd) {
WindowsApi::restore_window(border.hwnd);
}
}
let reference_hwnd =
c.focused_window().copied().unwrap_or_default().hwnd;
// avoid getting into a thread restart loop if we try to look up
// rect info for a window that has been destroyed by the time
// we get here
let rect = match WindowsApi::window_rect(reference_hwnd) {
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
Ok(rect) => rect,
Err(_) => {
let _ = border.destroy();
borders.remove(c.id());
remove_border(c.id(), &mut borders, &mut windows_borders)?;
continue 'containers;
}
};
border.window_rect = rect;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => last_focus_state != new_focus_state,
};
let layer_changed = previous_layer != workspace_layer;
if new_border || should_invalidate {
border.set_position(&rect, reference_hwnd)?;
}
let should_invalidate = new_border
|| (last_focus_state != new_focus_state)
|| layer_changed;
if should_invalidate {
border.set_position(&rect, focused_window_hwnd)?;
border.invalidate();
}
borders_monitors.insert(c.id().clone(), monitor_idx);
windows_borders.insert(
c.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
windows_borders.insert(focused_window_hwnd, id);
}
{
for window in ws.floating_windows() {
let mut new_border = false;
let border = match borders.entry(window.hwnd.to_string()) {
let id = window.hwnd.to_string();
let border = match borders.entry(id.clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) =
Border::create(&window.hwnd.to_string(), window.hwnd)
{
if let Ok(border) = Border::create(
&window.hwnd.to_string(),
window.hwnd,
monitor_idx,
) {
new_border = true;
entry.insert(border)
} else {
@@ -544,38 +563,31 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
};
#[allow(unused_assignments)]
let mut last_focus_state = None;
let mut new_focus_state = WindowKind::Unfocused;
let last_focus_state = border.window_kind;
if foreground_window == window.hwnd {
new_focus_state = WindowKind::Floating;
}
border.window_kind = new_focus_state;
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state =
focus_state.insert(border.hwnd, new_focus_state);
}
let rect = WindowsApi::window_rect(window.hwnd)?;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => last_focus_state != new_focus_state,
let new_focus_state = if foreground_window == window.hwnd {
WindowKind::Floating
} else {
WindowKind::Unfocused
};
if new_border {
border.set_position(&rect, window.hwnd)?;
}
border.window_kind = new_focus_state;
let rect = WindowsApi::window_rect(window.hwnd)?;
border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer;
let should_invalidate = new_border
|| (last_focus_state != new_focus_state)
|| layer_changed;
if should_invalidate {
border.set_position(&rect, window.hwnd)?;
border.invalidate();
}
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
windows_borders.insert(window.hwnd, border.clone());
windows_borders.insert(window.hwnd, id);
}
}
}
@@ -587,12 +599,113 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
previous_pending_move_op = pending_move_op;
previous_is_paused = is_paused;
previous_notification = Some(notification);
previous_layer = workspace_layer;
}
Ok(())
}
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, JsonSchema, PartialEq)]
/// Removes all borders from monitor with index `monitor_idx` filtered by
/// `condition`. This condition is a function that will take a reference to
/// the container id and the border and returns a bool, if true that border
/// will be removed.
fn remove_borders(
borders: &mut HashMap<String, Box<Border>>,
windows_borders: &mut HashMap<isize, String>,
monitor_idx: usize,
condition: impl Fn(&String, &Border) -> bool,
) -> color_eyre::Result<()> {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
// if border is on this monitor
if border.monitor_idx.is_some_and(|idx| idx == monitor_idx)
// and the condition applies
&& condition(id, border)
// and the border is visible (we don't remove hidden borders)
&& WindowsApi::is_window_visible(border.hwnd)
{
// we mark it to be removed
to_remove.push(id.clone());
}
}
for id in &to_remove {
remove_border(id, borders, windows_borders)?;
}
Ok(())
}
/// Removes the border with `id` and all its related info from all maps
fn remove_border(
id: &str,
borders: &mut HashMap<String, Box<Border>>,
windows_borders: &mut HashMap<isize, String>,
) -> color_eyre::Result<()> {
if let Some(removed_border) = borders.remove(id) {
windows_borders.remove(&removed_border.tracking_hwnd);
destroy_border(removed_border)?;
}
Ok(())
}
/// IMPORTANT: BEWARE when changing this function. We need to make sure that we don't let the
/// `Box<Border>` be dropped normally. We need to turn the `Box` into the raw pointer and use that
/// pointer to call the `.destroy()` funtion of the border so it closes the window. This way the
/// `Box` is consumed and the pointer is dropped like a normal `Copy` number instead of trying to
/// drop the struct it points to. The actual border is owned by the thread that created the window
/// and once the window closes that thread gets out of its loop, finishes and properly disposes of
/// the border.
fn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {
let raw_pointer = Box::into_raw(border);
unsafe {
(*raw_pointer).destroy()?;
}
Ok(())
}
/// Removes the border around window with `tracking_hwnd` if it exists
pub fn delete_border(tracking_hwnd: isize) {
std::thread::spawn(move || {
let id = {
WINDOWS_BORDERS
.lock()
.get(&tracking_hwnd)
.cloned()
.unwrap_or_default()
};
let mut borders = BORDER_STATE.lock();
let mut windows_borders = WINDOWS_BORDERS.lock();
if let Err(error) = remove_border(&id, &mut borders, &mut windows_borders) {
tracing::error!("Failed to delete border: {}", error);
}
});
}
/// Shows the border around window with `tracking_hwnd` if it exists
pub fn show_border(tracking_hwnd: isize) {
std::thread::spawn(move || {
if let Some(border_info) = window_border(tracking_hwnd) {
WindowsApi::restore_window(border_info.border_hwnd);
}
});
}
/// Hides the border around window with `tracking_hwnd` if it exists, unless the border kind is a
/// `Stack` border.
pub fn hide_border(tracking_hwnd: isize) {
std::thread::spawn(move || {
if let Some(border_info) = window_border(tracking_hwnd) {
WindowsApi::hide_window(border_info.border_hwnd);
}
});
}
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ZOrder {
Top,
NoTopMost,

View File

@@ -1,14 +1,19 @@
use hex_color::HexColor;
use komorebi_themes::Color32;
#[cfg(feature = "schemars")]
use schemars::gen::SchemaGenerator;
#[cfg(feature = "schemars")]
use schemars::schema::InstanceType;
#[cfg(feature = "schemars")]
use schemars::schema::Schema;
#[cfg(feature = "schemars")]
use schemars::schema::SchemaObject;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum Colour {
/// Colour represented as RGB
@@ -54,7 +59,8 @@ impl From<Colour> for Color32 {
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub struct Hex(HexColor);
impl JsonSchema for Hex {
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Hex {
fn schema_name() -> String {
String::from("Hex")
}
@@ -78,7 +84,8 @@ impl From<Colour> for u32 {
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Rgb {
/// Red
pub r: u32,

View File

@@ -13,11 +13,11 @@ use windows::core::HRESULT;
use windows::core::HSTRING;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::SIZE;
use windows::Win32::UI::Shell::Common::IObjectArray;
use windows_core::BOOL;
type DesktopID = GUID;

View File

@@ -2,14 +2,14 @@ use std::collections::VecDeque;
use getset::Getters;
use nanoid::nanoid;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::ring::Ring;
use crate::window::Window;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, JsonSchema)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Container {
#[getset(get = "pub")]
id: String,
@@ -52,13 +52,17 @@ impl Container {
}
}
/// Hides the unfocused windows of the container and restores the focused one. This function
/// is used to make sure we update the window that should be shown on a stack. If the container
/// isn't a stack this function won't change anything.
pub fn load_focused_window(&mut self) {
let focused_idx = self.focused_window_idx();
for (i, window) in self.windows_mut().iter_mut().enumerate() {
if i == focused_idx {
window.restore();
window.restore_with_border(false);
} else {
window.hide();
window.hide_with_border(false);
}
}
}
@@ -98,14 +102,13 @@ impl Container {
}
pub fn idx_for_window(&self, hwnd: isize) -> Option<usize> {
let mut idx = None;
for (i, window) in self.windows().iter().enumerate() {
if window.hwnd == hwnd {
idx = Option::from(i);
return Option::from(i);
}
}
idx
None
}
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {

View File

@@ -1,22 +1,12 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum AnimationStyle {
Linear,
EaseInSine,

View File

@@ -1,7 +1,6 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -603,18 +602,8 @@ impl Arrangement for CustomLayout {
}
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Axis {
Horizontal,
Vertical,

View File

@@ -2,7 +2,6 @@ use crate::config_generation::ApplicationConfiguration;
use crate::config_generation::ApplicationOptions;
use crate::config_generation::MatchingRule;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
@@ -10,10 +9,12 @@ use std::ops::Deref;
use std::ops::DerefMut;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ApplicationSpecificConfiguration(pub BTreeMap<String, AscApplicationRulesOrSchema>);
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum AscApplicationRulesOrSchema {
AscApplicationRules(AscApplicationRules),
@@ -46,7 +47,8 @@ impl ApplicationSpecificConfiguration {
}
/// Rules that determine how an application is handled
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AscApplicationRules {
/// Rules to ignore specific windows
#[serde(skip_serializing_if = "Option::is_none")]

View File

@@ -1,6 +1,5 @@
use clap::ValueEnum;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -8,9 +7,8 @@ use strum::EnumString;
use super::ApplicationIdentifier;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationOptions {
@@ -52,14 +50,16 @@ impl ApplicationOptions {
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum MatchingRule {
Simple(IdWithIdentifier),
Composite(Vec<IdWithIdentifier>),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WorkspaceMatchingRule {
pub monitor_index: usize,
pub workspace_index: usize,
@@ -67,7 +67,8 @@ pub struct WorkspaceMatchingRule {
pub initial_only: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IdWithIdentifier {
pub kind: ApplicationIdentifier,
pub id: String,
@@ -75,7 +76,8 @@ pub struct IdWithIdentifier {
pub matching_strategy: Option<MatchingStrategy>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, JsonSchema)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum MatchingStrategy {
Legacy,
Equals,
@@ -89,7 +91,8 @@ pub enum MatchingStrategy {
DoesNotContain,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IdWithIdentifierAndComment {
pub kind: ApplicationIdentifier,
pub id: String,
@@ -109,7 +112,8 @@ impl From<IdWithIdentifierAndComment> for IdWithIdentifier {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ApplicationConfiguration {
pub name: String,
pub identifier: IdWithIdentifier,
@@ -133,7 +137,8 @@ impl ApplicationConfiguration {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ApplicationConfigurationGenerator;
impl ApplicationConfigurationGenerator {

View File

@@ -8,13 +8,13 @@ use std::path::Path;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use super::Rect;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct CustomLayout(Vec<Column>);
impl Deref for CustomLayout {
@@ -250,7 +250,8 @@ impl CustomLayout {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "column", content = "configuration")]
pub enum Column {
Primary(Option<ColumnWidth>),
@@ -258,18 +259,21 @@ pub enum Column {
Tertiary(ColumnSplit),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ColumnWidth {
WidthPercentage(f32),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ColumnSplit {
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ColumnSplitWithCapacity {
Horizontal(usize),
Vertical(usize),

View File

@@ -1,15 +1,13 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum CycleDirection {
Previous,
Next,

View File

@@ -1,5 +1,4 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -10,18 +9,9 @@ use super::Rect;
use super::Sizing;
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Eq,
PartialEq,
Display,
EnumString,
ValueEnum,
JsonSchema,
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DefaultLayout {
BSP,
Columns,

View File

@@ -1,4 +1,3 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -7,7 +6,8 @@ use super::CustomLayout;
use super::DefaultLayout;
use super::Direction;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Layout {
Default(DefaultLayout),
Custom(CustomLayout),

View File

@@ -8,7 +8,6 @@ use std::str::FromStr;
use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -45,7 +44,8 @@ pub mod operation_direction;
pub mod pathext;
pub mod rect;
#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "type", content = "content")]
pub enum SocketMessage {
// Window / Container Commands
@@ -122,6 +122,7 @@ pub enum SocketMessage {
Load(PathBuf),
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
CycleFocusEmptyWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusMonitorAtCursor,
FocusLastWorkspace,
@@ -149,6 +150,7 @@ pub enum SocketMessage {
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
ClearWorkspaceLayoutRules(usize, usize),
ClearNamedWorkspaceLayoutRules(String),
ToggleWorkspaceLayer,
// Configuration
ReloadConfiguration,
ReplaceConfiguration(PathBuf),
@@ -240,24 +242,23 @@ impl FromStr for SocketMessage {
}
}
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SubscribeOptions {
/// Only emit notifications when the window manager state has changed
pub filter_state_changes: bool,
}
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema, ValueEnum,
)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum StackbarMode {
Always,
Never,
OnStack,
}
#[derive(
Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
)]
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum StackbarLabel {
#[default]
Process,
@@ -265,18 +266,9 @@ pub enum StackbarLabel {
}
#[derive(
Default,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Display,
Serialize,
Deserialize,
JsonSchema,
ValueEnum,
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum BorderStyle {
#[default]
/// Use the system border style
@@ -288,18 +280,9 @@ pub enum BorderStyle {
}
#[derive(
Default,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Display,
Serialize,
Deserialize,
JsonSchema,
ValueEnum,
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum BorderImplementation {
#[default]
/// Use the adjustable komorebi border implementation
@@ -312,27 +295,28 @@ pub enum BorderImplementation {
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Eq,
Hash,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WindowKind {
Single,
Stack,
Monocle,
#[default]
Unfocused,
Floating,
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum StateQuery {
FocusedMonitorIndex,
FocusedWorkspaceIndex,
@@ -342,18 +326,9 @@ pub enum StateQuery {
}
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ApplicationIdentifier {
#[serde(alias = "exe")]
Exe,
@@ -365,18 +340,8 @@ pub enum ApplicationIdentifier {
Path,
}
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum FocusFollowsMouseImplementation {
/// A custom FFM implementation (slightly more CPU-intensive)
Komorebi,
@@ -384,7 +349,8 @@ pub enum FocusFollowsMouseImplementation {
Windows,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WindowManagementBehaviour {
/// The current WindowContainerBehaviour to be used
pub current_behaviour: WindowContainerBehaviour,
@@ -395,18 +361,9 @@ pub struct WindowManagementBehaviour {
}
#[derive(
Clone,
Copy,
Debug,
Default,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WindowContainerBehaviour {
/// Create a new container for each new window
#[default]
@@ -415,18 +372,8 @@ pub enum WindowContainerBehaviour {
Append,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor
Swap,
@@ -436,18 +383,8 @@ pub enum MoveBehaviour {
NoOp,
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum CrossBoundaryBehaviour {
/// Attempt to perform actions across a workspace boundary
Workspace,
@@ -455,18 +392,8 @@ pub enum CrossBoundaryBehaviour {
Monitor,
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum HidingBehaviour {
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
Hide,
@@ -476,18 +403,8 @@ pub enum HidingBehaviour {
Cloak,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows
Op,
@@ -495,9 +412,8 @@ pub enum OperationBehaviour {
NoOp,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Sizing {
Increase,
Decrease,

View File

@@ -1,7 +1,6 @@
use std::num::NonZeroUsize;
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -10,9 +9,8 @@ use strum::EnumString;
use super::direction::Direction;
use super::Axis;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum OperationDirection {
Left,
Right,

View File

@@ -1,9 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use windows::Win32::Foundation::RECT;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Rect {
/// The left point in a Win32 Rect
pub left: i32,

View File

@@ -66,7 +66,6 @@ use color_eyre::Result;
use os_info::Version;
use parking_lot::Mutex;
use regex::Regex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixStream;
@@ -280,7 +279,8 @@ pub fn current_virtual_desktop() -> Option<Vec<u8>> {
current
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum NotificationEvent {
WindowManager(WindowManagerEvent),
@@ -288,7 +288,8 @@ pub enum NotificationEvent {
Monitor(MonitorNotification),
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Notification {
pub event: NotificationEvent,
pub state: State,

View File

@@ -235,6 +235,7 @@ fn main() -> Result<()> {
} else {
Arc::new(Mutex::new(WindowManager::new(
winevent_listener::event_rx(),
None,
)?))
};
@@ -244,16 +245,10 @@ fn main() -> Result<()> {
StaticConfig::postload(config, &wm)?;
}
listen_for_commands(wm.clone());
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
};
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
if static_config.is_none() {
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
@@ -280,21 +275,27 @@ fn main() -> Result<()> {
wm.lock().retile_all(false)?;
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
border_manager::listen_for_notifications(wm.clone());
stackbar_manager::listen_for_notifications(wm.clone());
transparency_manager::listen_for_notifications(wm.clone());
workspace_reconciliator::listen_for_notifications(wm.clone());
monitor_reconciliator::listen_for_notifications(wm.clone())?;
reaper::watch_for_orphans(wm.clone());
reaper::listen_for_notifications(wm.clone(), wm.lock().known_hwnds.clone());
focus_manager::listen_for_notifications(wm.clone());
theme_manager::listen_for_notifications();
listen_for_commands(wm.clone());
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {
ctrlc_sender

View File

@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
@@ -8,7 +9,6 @@ use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -21,19 +21,13 @@ use crate::DefaultLayout;
use crate::Layout;
use crate::OperationDirection;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
pub id: isize,
@@ -61,6 +55,10 @@ pub struct Monitor {
pub last_focused_workspace: Option<usize>,
#[getset(get_mut = "pub")]
pub workspace_names: HashMap<usize, String>,
#[getset(get_copy = "pub", set = "pub")]
pub container_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
pub workspace_padding: Option<i32>,
}
impl_ring_elements!(Monitor, Workspace);
@@ -114,6 +112,8 @@ pub fn new(
workspaces,
last_focused_workspace: None,
workspace_names: HashMap::default(),
container_padding: None,
workspace_padding: None,
}
}
@@ -153,6 +153,8 @@ impl Monitor {
workspaces: Default::default(),
last_focused_workspace: None,
workspace_names: Default::default(),
container_padding: None,
workspace_padding: None,
}
}
@@ -175,6 +177,52 @@ impl Monitor {
Ok(())
}
/// Updates the `globals` field of all workspaces
pub fn update_workspaces_globals(&mut self, offset: Option<Rect>) {
let container_padding = self
.container_padding()
.or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
let workspace_padding = self
.workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
let work_area = *self.work_area_size();
let offset = self.work_area_offset.or(offset);
let window_based_work_area_offset = self.window_based_work_area_offset();
let limit = self.window_based_work_area_offset_limit();
for workspace in self.workspaces_mut() {
workspace.globals_mut().container_padding = container_padding;
workspace.globals_mut().workspace_padding = workspace_padding;
workspace.globals_mut().work_area = work_area;
workspace.globals_mut().work_area_offset = offset;
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
workspace.globals_mut().window_based_work_area_offset_limit = limit;
}
}
/// Updates the `globals` field of workspace with index `workspace_idx`
pub fn update_workspace_globals(&mut self, workspace_idx: usize, offset: Option<Rect>) {
let container_padding = self
.container_padding()
.or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
let workspace_padding = self
.workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
let work_area = *self.work_area_size();
let offset = self.work_area_offset.or(offset);
let window_based_work_area_offset = self.window_based_work_area_offset();
let limit = self.window_based_work_area_offset_limit();
if let Some(workspace) = self.workspaces_mut().get_mut(workspace_idx) {
workspace.globals_mut().container_padding = container_padding;
workspace.globals_mut().workspace_padding = workspace_padding;
workspace.globals_mut().work_area = work_area;
workspace.globals_mut().work_area_offset = offset;
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
workspace.globals_mut().window_based_work_area_offset_limit = limit;
}
}
pub fn add_container(
&mut self,
container: Container,
@@ -401,21 +449,17 @@ impl Monitor {
}
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
let work_area = *self.work_area_size();
let window_based_work_area_offset = (
self.window_based_work_area_offset_limit(),
self.window_based_work_area_offset(),
);
let offset = if self.work_area_offset().is_some() {
self.work_area_offset()
} else {
offset
};
let focused_workspace_idx = self.focused_workspace_idx();
self.update_workspace_globals(focused_workspace_idx, offset);
self.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?
.update(&work_area, offset, window_based_work_area_offset)?;
.update()?;
Ok(())
}

View File

@@ -17,7 +17,6 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
@@ -28,7 +27,8 @@ use std::sync::OnceLock;
pub mod hidden;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "type", content = "content")]
pub enum MonitorNotification {
ResolutionScalingChanged,
@@ -264,6 +264,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|| attached.device_id().eq(monitor.device_id())
{
monitor.set_id(attached.id());
monitor.set_device(attached.device().clone());
monitor.set_device_id(attached.device_id().clone());
monitor.set_serial_number_id(attached.serial_number_id().clone());
monitor.set_name(attached.name().clone());
@@ -385,7 +386,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
// Update known_hwnds
wm.known_hwnds.retain(|i| !windows_to_remove.contains(i));
wm.known_hwnds.retain(|i, _| !windows_to_remove.contains(i));
if !newly_removed_displays.is_empty() {
// After we have cached them, remove them from our state
@@ -462,9 +463,31 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
tracing::info!("found monitor and workspace configuration for {id} in the monitor cache, applying");
// If it does, load the monitor removing any window that has since
// been closed or moved to another workspace
*m = cached.clone();
// If it does, update the cached monitor info with the new one and
// load the cached monitor removing any window that has since been
// closed or moved to another workspace
*m = Monitor {
// Data that should be the one just read from `win32-display-data`
id: m.id,
name: m.name.clone(),
device: m.device.clone(),
device_id: m.device_id.clone(),
serial_number_id: m.serial_number_id.clone(),
size: m.size,
work_area_size: m.work_area_size,
// The rest should come from the cached monitor
work_area_offset: cached.work_area_offset,
window_based_work_area_offset: cached
.window_based_work_area_offset,
window_based_work_area_offset_limit: cached
.window_based_work_area_offset_limit,
workspaces: cached.workspaces.clone(),
last_focused_workspace: cached.last_focused_workspace,
workspace_names: cached.workspace_names.clone(),
container_padding: cached.container_padding,
workspace_padding: cached.workspace_padding,
};
let focused_workspace_idx = m.focused_workspace_idx();
@@ -481,7 +504,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
{
container.windows_mut().retain(|window| {
window.exe().is_ok()
&& !known_hwnds.contains(&window.hwnd)
&& !known_hwnds.contains_key(&window.hwnd)
});
if container.windows().is_empty() {
@@ -519,7 +542,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Some(window) = workspace.maximized_window() {
if window.exe().is_err()
|| known_hwnds.contains(&window.hwnd)
|| known_hwnds.contains_key(&window.hwnd)
{
workspace.set_maximized_window(None);
} else if is_focused_workspace {
@@ -530,7 +553,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Some(container) = workspace.monocle_container_mut() {
container.windows_mut().retain(|window| {
window.exe().is_ok()
&& !known_hwnds.contains(&window.hwnd)
&& !known_hwnds.contains_key(&window.hwnd)
});
if container.windows().is_empty() {
@@ -552,7 +575,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
workspace.floating_windows_mut().retain(|window| {
window.exe().is_ok() && !known_hwnds.contains(&window.hwnd)
window.exe().is_ok()
&& !known_hwnds.contains_key(&window.hwnd)
});
if is_focused_workspace {

View File

@@ -1,3 +1,8 @@
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::fs::File;
use std::fs::OpenOptions;
@@ -11,20 +16,11 @@ use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use schemars::gen::SchemaSettings;
use schemars::schema_for;
use uds_windows::UnixStream;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
@@ -66,6 +62,7 @@ use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::winevent_listener;
use crate::workspace::WorkspaceLayer;
use crate::workspace::WorkspaceWindowLocation;
use crate::GlobalState;
use crate::Notification;
@@ -208,7 +205,9 @@ impl WindowManager {
let initial_state = State::from(self.as_ref());
match message {
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
SocketMessage::CycleFocusEmptyWorkspace(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusWorkspaceNumber(_) => {
if let Some(monitor) = self.focused_monitor_mut() {
let idx = monitor.focused_workspace_idx();
monitor.set_last_focused_workspace(Option::from(idx));
@@ -291,13 +290,37 @@ impl WindowManager {
}
}
SocketMessage::FocusWindow(direction) => {
self.focus_container_in_direction(direction)?;
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer() {
WorkspaceLayer::Tiling => {
self.focus_container_in_direction(direction)?;
}
WorkspaceLayer::Floating => {
self.focus_floating_window_in_direction(direction)?;
}
}
}
SocketMessage::MoveWindow(direction) => {
self.move_container_in_direction(direction)?;
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer() {
WorkspaceLayer::Tiling => {
self.move_container_in_direction(direction)?;
}
WorkspaceLayer::Floating => {
self.move_floating_window_in_direction(direction)?;
}
}
}
SocketMessage::CycleFocusWindow(direction) => {
self.focus_container_in_cycle_direction(direction)?;
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer() {
WorkspaceLayer::Tiling => {
self.focus_container_in_cycle_direction(direction)?;
}
WorkspaceLayer::Floating => {
self.focus_floating_window_in_cycle_direction(direction)?;
}
}
}
SocketMessage::CycleMoveWindow(direction) => {
self.move_container_in_cycle_direction(direction)?;
@@ -879,6 +902,55 @@ impl WindowManager {
self.focus_workspace(workspace_idx)?;
}
SocketMessage::CycleFocusEmptyWorkspace(direction) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
}
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let mut empty_workspaces = vec![];
for (idx, w) in focused_monitor.workspaces().iter().enumerate() {
if w.is_empty() {
empty_workspaces.push(idx);
}
}
if !empty_workspaces.is_empty() {
let mut workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
while !empty_workspaces.contains(&workspace_idx) {
workspace_idx = direction.next_idx(
workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
}
self.focus_workspace(workspace_idx)?;
}
}
SocketMessage::CloseWorkspace => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
@@ -1020,6 +1092,50 @@ impl WindowManager {
self.focus_workspace(workspace_idx)?;
}
}
SocketMessage::ToggleWorkspaceLayer => {
let mouse_follows_focus = self.mouse_follows_focus;
let workspace = self.focused_workspace_mut()?;
let mut to_focus = None;
match workspace.layer() {
WorkspaceLayer::Tiling => {
workspace.set_layer(WorkspaceLayer::Floating);
for (i, window) in workspace.floating_windows().iter().enumerate() {
if i == 0 {
to_focus = Some(*window);
}
window.raise()?;
}
for container in workspace.containers() {
if let Some(window) = container.focused_window() {
window.lower()?;
}
}
}
WorkspaceLayer::Floating => {
workspace.set_layer(WorkspaceLayer::Tiling);
let focused_container_idx = workspace.focused_container_idx();
for (i, container) in workspace.containers_mut().iter_mut().enumerate() {
if let Some(window) = container.focused_window() {
if i == focused_container_idx {
to_focus = Some(*window);
}
window.raise()?;
}
}
for window in workspace.floating_windows() {
window.lower()?;
}
}
};
if let Some(window) = to_focus {
window.focus(mouse_follows_focus)?;
}
}
SocketMessage::Stop => {
self.stop(false)?;
}
@@ -1464,7 +1580,7 @@ impl WindowManager {
!workspace.apply_window_based_work_area_offset(),
);
self.retile_all(false)?;
self.retile_all(true)?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
@@ -1759,35 +1875,49 @@ impl WindowManager {
*STACKBAR_FONT_FAMILY.lock() = font_family.clone();
}
SocketMessage::ApplicationSpecificConfigurationSchema => {
let asc = schema_for!(Vec<ApplicationConfiguration>);
let schema = serde_json::to_string_pretty(&asc)?;
#[cfg(feature = "schemars")]
{
let asc = schemars::schema_for!(
Vec<crate::core::config_generation::ApplicationConfiguration>
);
let schema = serde_json::to_string_pretty(&asc)?;
reply.write_all(schema.as_bytes())?;
reply.write_all(schema.as_bytes())?;
}
}
SocketMessage::NotificationSchema => {
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
#[cfg(feature = "schemars")]
{
let notification = schemars::schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
reply.write_all(schema.as_bytes())?;
reply.write_all(schema.as_bytes())?;
}
}
SocketMessage::SocketSchema => {
let socket_message = schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
#[cfg(feature = "schemars")]
{
let socket_message = schemars::schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
reply.write_all(schema.as_bytes())?;
}
}
SocketMessage::StaticConfigSchema => {
let settings = SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
#[cfg(feature = "schemars")]
{
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
reply.write_all(schema.as_bytes())?;
}
}
SocketMessage::GenerateStaticConfig => {
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
@@ -1835,6 +1965,9 @@ impl WindowManager {
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
};
// Update list of known_hwnds and their monitor/workspace index pair
self.update_known_hwnds();
notify_subscribers(
Notification {
event: NotificationEvent::Socket(message.clone()),
@@ -1936,3 +2069,73 @@ pub fn read_commands_tcp(
Ok(())
}
#[cfg(test)]
mod tests {
use crate::monitor;
use crate::window_manager::WindowManager;
use crate::Rect;
use crate::SocketMessage;
use crate::WindowManagerEvent;
use crossbeam_channel::bounded;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
use uds_windows::UnixStream;
use uuid::Uuid;
fn send_socket_message(socket: &PathBuf, message: SocketMessage) {
let mut stream = UnixStream::connect(socket).unwrap();
stream
.set_write_timeout(Some(Duration::from_secs(1)))
.unwrap();
stream
.write_all(serde_json::to_string(&message).unwrap().as_bytes())
.unwrap();
}
#[test]
fn test_receive_socket_message() {
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
bounded(1);
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
let socket_path = PathBuf::from(&socket_name);
let mut wm = WindowManager::new(receiver, Some(socket_path.clone())).unwrap();
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
wm.monitors_mut().push_back(m);
// send a message
send_socket_message(&socket_path, SocketMessage::FocusWorkspaceNumber(5));
let (stream, _) = wm.command_listener.accept().unwrap();
let reader = BufReader::new(stream.try_clone().unwrap());
let next = reader.lines().next();
// read and deserialize the message
let message_string = next.unwrap().unwrap();
let message = SocketMessage::from_str(&message_string).unwrap();
assert!(matches!(message, SocketMessage::FocusWorkspaceNumber(5)));
// process the message
wm.process_command(message, stream).unwrap();
// check the updated window manager state
assert_eq!(wm.focused_workspace_idx().unwrap(), 5);
std::fs::remove_file(socket_path).unwrap();
}
}

View File

@@ -1,4 +1,3 @@
use std::fs::OpenOptions;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
@@ -27,13 +26,13 @@ use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent;
use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::DATA_DIR;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS;
@@ -282,10 +281,12 @@ impl WindowManager {
} else {
workspace.focus_container_by_window(window.hwnd)?;
}
workspace.set_layer(WorkspaceLayer::Tiling);
}
Some(idx) => {
if let Some(window) = workspace.floating_windows().get(idx) {
window.focus(false)?;
if let Some(_window) = workspace.floating_windows().get(idx) {
workspace.set_layer(WorkspaceLayer::Floating);
}
}
}
@@ -307,30 +308,28 @@ impl WindowManager {
let mut needs_reconciliation = false;
for (i, monitors) in self.monitors().iter().enumerate() {
for (j, workspace) in monitors.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
// At this point we know we are going to send a notification to the workspace reconciliator
// So we get the topmost window returned by EnumWindows, which is almost always the window
// that has been selected by alt-tab
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
if let Some(first) =
alt_tab_windows.iter().find(|w| w.title().is_ok())
{
// If our record of this HWND hasn't been updated in over a minute
let mut instant = ALT_TAB_HWND_INSTANT.lock();
if instant.elapsed().gt(&Duration::from_secs(1)) {
// Update our record with the HWND we just found
ALT_TAB_HWND.store(Some(first.hwnd));
// Update the timestamp of our record
*instant = Instant::now();
}
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
if focused_pair != (*m_idx, *w_idx) {
// At this point we know we are going to send a notification to the workspace reconciliator
// So we get the topmost window returned by EnumWindows, which is almost always the window
// that has been selected by alt-tab
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
if let Some(first) =
alt_tab_windows.iter().find(|w| w.title().is_ok())
{
// If our record of this HWND hasn't been updated in over a minute
let mut instant = ALT_TAB_HWND_INSTANT.lock();
if instant.elapsed().gt(&Duration::from_secs(1)) {
// Update our record with the HWND we just found
ALT_TAB_HWND.store(Some(first.hwnd));
// Update the timestamp of our record
*instant = Instant::now();
}
}
workspace_reconciliator::send_notification(i, j);
needs_reconciliation = true;
}
workspace_reconciliator::send_notification(*m_idx, *w_idx);
needs_reconciliation = true;
}
}
@@ -341,11 +340,14 @@ impl WindowManager {
// duplicates across multiple workspaces, as it results in ghost layout tiles.
let mut proceed = true;
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd)
&& i != self.focused_monitor_idx()
&& j != monitor.focused_workspace_idx()
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
if let Some(focused_workspace_idx) = self
.monitors()
.get(*m_idx)
.map(|m| m.focused_workspace_idx())
{
if *m_idx != self.focused_monitor_idx()
&& *w_idx != focused_workspace_idx
{
tracing::debug!(
"ignoring show event for window already associated with another workspace"
@@ -394,11 +396,13 @@ impl WindowManager {
if behaviour.float_override {
workspace.floating_windows_mut().push(window);
workspace.set_layer(WorkspaceLayer::Floating);
self.update_focused_workspace(false, false)?;
} else {
match behaviour.current_behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
workspace.set_layer(WorkspaceLayer::Tiling);
self.update_focused_workspace(false, false)?;
}
WindowContainerBehaviour::Append => {
@@ -408,6 +412,7 @@ impl WindowManager {
anyhow!("there is no focused container")
})?
.add_window(window);
workspace.set_layer(WorkspaceLayer::Tiling);
self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification();
}
@@ -504,15 +509,9 @@ impl WindowManager {
// This will be true if we have moved to another monitor
let mut moved_across_monitors = false;
for (i, monitors) in self.monitors().iter().enumerate() {
for workspace in monitors.workspaces() {
if workspace.contains_window(window.hwnd) && i != target_monitor_idx {
moved_across_monitors = true;
break;
}
}
if moved_across_monitors {
break;
if let Some((m_idx, _)) = self.known_hwnds.get(&window.hwnd) {
if *m_idx != target_monitor_idx {
moved_across_monitors = true;
}
}
@@ -716,42 +715,8 @@ impl WindowManager {
window.center(&self.focused_monitor_work_area()?)?;
}
tracing::trace!("updating list of known hwnds");
let mut known_hwnds = vec![];
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
for container in workspace.containers() {
for window in container.windows() {
known_hwnds.push(window.hwnd);
}
}
for window in workspace.floating_windows() {
known_hwnds.push(window.hwnd);
}
if let Some(window) = workspace.maximized_window() {
known_hwnds.push(window.hwnd);
}
if let Some(container) = workspace.monocle_container() {
for window in container.windows() {
known_hwnds.push(window.hwnd);
}
}
}
}
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(hwnd_json)?;
serde_json::to_writer_pretty(&file, &known_hwnds)?;
self.known_hwnds = known_hwnds;
// Update list of known_hwnds and their monitor/workspace index pair
self.update_known_hwnds();
notify_subscribers(
Notification {

View File

@@ -1,14 +1,155 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::border_manager;
use crate::notify_subscribers;
use crate::winevent::WinEvent;
use crate::NotificationEvent;
use crate::Window;
use crate::WindowManager;
use crate::WindowManagerEvent;
use crate::DATA_DIR;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
lazy_static! {
pub static ref HWNDS_CACHE: Arc<Mutex<HashMap<isize, (usize, usize)>>> =
Arc::new(Mutex::new(HashMap::new()));
}
pub struct ReaperNotification(pub HashMap<isize, (usize, usize)>);
static CHANNEL: OnceLock<(Sender<ReaperNotification>, Receiver<ReaperNotification>)> =
OnceLock::new();
pub fn channel() -> &'static (Sender<ReaperNotification>, Receiver<ReaperNotification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))
}
fn event_tx() -> Sender<ReaperNotification> {
channel().0.clone()
}
fn event_rx() -> Receiver<ReaperNotification> {
channel().1.clone()
}
pub fn send_notification(hwnds: HashMap<isize, (usize, usize)>) {
if event_tx().try_send(ReaperNotification(hwnds)).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn listen_for_notifications(
wm: Arc<Mutex<WindowManager>>,
known_hwnds: HashMap<isize, (usize, usize)>,
) {
watch_for_orphans(known_hwnds);
std::thread::spawn(move || loop {
match find_orphans(wm.clone()) {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
}
fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("listening");
let receiver = event_rx();
for notification in receiver {
let orphan_hwnds = notification.0;
let mut wm = wm.lock();
let mut update_borders = false;
for (hwnd, (m_idx, w_idx)) in orphan_hwnds.iter() {
if let Some(monitor) = wm.monitors_mut().get_mut(*m_idx) {
let focused_workspace_idx = monitor.focused_workspace_idx();
if let Some(workspace) = monitor.workspaces_mut().get_mut(*w_idx) {
// Remove orphan window
if let Err(error) = workspace.remove_window(*hwnd) {
tracing::warn!(
"error reaping orphan window ({}) on monitor: {}, workspace: {}. Error: {}",
hwnd,
m_idx,
w_idx,
error,
);
}
if focused_workspace_idx == *w_idx {
// If this is not a focused workspace there is no need to update the
// workspace or the borders. That will already be done when the user
// changes to this workspace.
workspace.update()?;
update_borders = true;
}
tracing::info!(
"reaped orphan window ({}) on monitor: {}, workspace: {}",
hwnd,
m_idx,
w_idx,
);
}
}
wm.known_hwnds.remove(hwnd);
let window = Window::from(*hwnd);
notify_subscribers(
crate::Notification {
event: NotificationEvent::WindowManager(WindowManagerEvent::Destroy(
WinEvent::ObjectDestroy,
window,
)),
state: wm.as_ref().into(),
},
true,
)?;
}
if update_borders {
border_manager::send_notification(None);
}
// Save to file
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(hwnd_json)?;
serde_json::to_writer_pretty(&file, &wm.known_hwnds.keys().collect::<Vec<_>>())?;
}
Ok(())
}
fn watch_for_orphans(known_hwnds: HashMap<isize, (usize, usize)>) {
// Cache current hwnds
{
let mut cache = HWNDS_CACHE.lock();
*cache = known_hwnds;
}
std::thread::spawn(move || loop {
match find_orphans() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
@@ -23,50 +164,37 @@ pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
});
}
pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
fn find_orphans() -> color_eyre::Result<()> {
tracing::info!("watching");
let arc = wm.clone();
loop {
std::thread::sleep(Duration::from_secs(1));
std::thread::sleep(Duration::from_millis(20));
let mut wm = arc.lock();
let offset = wm.work_area_offset;
let mut cache = HWNDS_CACHE.lock();
let mut orphan_hwnds = HashMap::new();
let mut update_borders = false;
for (hwnd, (m_idx, w_idx)) in cache.iter() {
let window = Window::from(*hwnd);
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let work_area = *monitor.work_area_size();
let window_based_work_area_offset = (
monitor.window_based_work_area_offset_limit(),
monitor.window_based_work_area_offset(),
);
let offset = if monitor.work_area_offset().is_some() {
monitor.work_area_offset()
} else {
offset
};
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
let reaped_orphans = workspace.reap_orphans()?;
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
workspace.update(&work_area, offset, window_based_work_area_offset)?;
update_borders = true;
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,
reaped_orphans.1,
i,
j
);
}
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()
{
orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx));
}
}
if update_borders {
border_manager::send_notification(None);
if !orphan_hwnds.is_empty() {
// Update reaper cache
cache.retain(|h, _| !orphan_hwnds.contains_key(h));
// Send handles to remove
event_tx().send(ReaperNotification(orphan_hwnds))?;
}
}
}

View File

@@ -1,10 +1,10 @@
use std::collections::VecDeque;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Ring<T> {
elements: VecDeque<T>,
focused: usize,

View File

@@ -350,7 +350,7 @@ impl Stackbar {
}
// Restore the window corresponding to the tab we have clicked
window.restore();
window.restore_with_border(false);
if let Err(err) = window.focus(false) {
tracing::error!(
"stackbar WMLBUTTONDOWN focus error: hwnd {} ({})",
@@ -361,7 +361,7 @@ impl Stackbar {
} else {
// Hide any windows in the stack that don't correspond to the window
// we have clicked
window.hide();
window.hide_with_border(false);
}
}
}

View File

@@ -7,14 +7,35 @@ use crate::animation::ANIMATION_FPS;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::animation::DEFAULT_ANIMATION_FPS;
use crate::asc::ApplicationSpecificConfiguration;
use crate::asc::AscApplicationRulesOrSchema;
use crate::border_manager;
use crate::border_manager::ZOrder;
use crate::border_manager::IMPLEMENTATION;
use crate::border_manager::STYLE;
use crate::colour::Colour;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use crate::core::AnimationStyle;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::current_virtual_desktop;
use crate::monitor;
use crate::monitor::Monitor;
@@ -61,35 +82,12 @@ use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::asc::ApplicationSpecificConfiguration;
use crate::asc::AscApplicationRulesOrSchema;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use crate::core::AnimationStyle;
use crate::core::BorderStyle;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::HidingBehaviour;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
use hotwatch::Hotwatch;
use parking_lot::Mutex;
use regex::Regex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
@@ -102,7 +100,8 @@ use std::sync::Arc;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct BorderColours {
/// Border colour when the container contains a single window
#[serde(skip_serializing_if = "Option::is_none")]
@@ -121,7 +120,8 @@ pub struct BorderColours {
pub unfocused: Option<Colour>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WorkspaceConfig {
/// Name
pub name: String,
@@ -140,7 +140,7 @@ pub struct WorkspaceConfig {
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub container_padding: Option<i32>,
/// Container padding (default: global)
/// Workspace padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
/// Initial workspace application rules
@@ -243,7 +243,8 @@ impl From<&Workspace> for WorkspaceConfig {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct MonitorConfig {
/// Workspace configurations
pub workspaces: Vec<WorkspaceConfig>,
@@ -256,6 +257,12 @@ pub struct MonitorConfig {
/// Open window limit after which the window based work area offset will no longer be applied (default: 1)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_based_work_area_offset_limit: Option<isize>,
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub container_padding: Option<i32>,
/// Workspace padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
}
impl From<&Monitor> for MonitorConfig {
@@ -265,16 +272,38 @@ impl From<&Monitor> for MonitorConfig {
workspaces.push(WorkspaceConfig::from(w));
}
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
let container_padding = value.container_padding().and_then(|container_padding| {
if container_padding == default_container_padding {
None
} else {
Option::from(container_padding)
}
});
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
if workspace_padding == default_workspace_padding {
None
} else {
Option::from(workspace_padding)
}
});
Self {
workspaces,
work_area_offset: value.work_area_offset(),
window_based_work_area_offset: value.window_based_work_area_offset(),
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
container_padding,
workspace_padding,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.35`
pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required
@@ -422,7 +451,8 @@ pub struct StaticConfig {
pub floating_window_aspect_ratio: Option<AspectRatio>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AnimationsConfig {
/// Enable or disable animations (default: false)
pub enabled: PerAnimationPrefixConfig<bool>,
@@ -437,7 +467,8 @@ pub struct AnimationsConfig {
pub fps: Option<u64>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")]
pub enum KomorebiTheme {
/// A theme from catppuccin-egui
@@ -587,7 +618,8 @@ impl StaticConfig {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TabsConfig {
/// Width of a stackbar tab
#[serde(skip_serializing_if = "Option::is_none")]
@@ -609,7 +641,8 @@ pub struct TabsConfig {
pub font_size: Option<i32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct StackbarConfig {
/// Stackbar height
#[serde(skip_serializing_if = "Option::is_none")]
@@ -1208,7 +1241,7 @@ impl StaticConfig {
pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0,
known_hwnds: Vec::new(),
known_hwnds: HashMap::new(),
};
match value.focus_follows_mouse {
@@ -1250,6 +1283,7 @@ impl StaticConfig {
workspace_matching_rules.clear();
drop(workspace_matching_rules);
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
@@ -1295,7 +1329,10 @@ impl StaticConfig {
.window_based_work_area_offset_limit
.unwrap_or(1),
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
@@ -1377,6 +1414,10 @@ impl StaticConfig {
.window_based_work_area_offset_limit
.unwrap_or(1),
);
m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding);
m.update_workspaces_globals(offset);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
@@ -1411,6 +1452,7 @@ impl StaticConfig {
workspace_matching_rules.clear();
drop(workspace_matching_rules);
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
@@ -1458,6 +1500,10 @@ impl StaticConfig {
.window_based_work_area_offset_limit
.unwrap_or(1),
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
@@ -1540,6 +1586,10 @@ impl StaticConfig {
.window_based_work_area_offset_limit
.unwrap_or(1),
);
m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding);
m.update_workspaces_globals(offset);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
@@ -1671,3 +1721,36 @@ fn populate_rules(
Ok(())
}
#[cfg(test)]
mod tests {
use crate::StaticConfig;
#[test]
fn backwards_compat() {
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
let docs = vec![
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27",
"0.1.28", "0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33", "0.1.34",
];
let mut versions = vec![];
let client = reqwest::blocking::Client::new();
for version in root {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for version in docs {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for (version, config) in versions {
println!("{version}");
StaticConfig::read_raw(&config).unwrap();
}
}
}

View File

@@ -10,14 +10,44 @@ use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_MANAGER;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::border_manager;
use crate::com::SetCloak;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::focus_manager;
use crate::stackbar_manager;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api;
use crate::windows_api::WindowsApi;
use crate::AnimationStyle;
use crate::FLOATING_APPLICATIONS;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
use color_eyre::eyre;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use regex::Regex;
use serde::ser::SerializeStruct;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
@@ -27,47 +57,15 @@ use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use color_eyre::eyre;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use regex::Regex;
use schemars::JsonSchema;
use serde::ser::SerializeStruct;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use strum::Display;
use strum::EnumString;
use windows::Win32::Foundation::HWND;
use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
pub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);
pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Window {
pub hwnd: isize,
}
@@ -87,7 +85,8 @@ impl From<HWND> for Window {
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WindowDetails {
pub title: String,
pub exe: String,
@@ -299,9 +298,8 @@ impl RenderDispatcher for TransparencyRenderDispatcher {
}
}
#[derive(
Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
)]
#[derive(Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum AspectRatio {
/// A predefined aspect ratio
@@ -316,9 +314,8 @@ impl Default for AspectRatio {
}
}
#[derive(
Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
)]
#[derive(Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum PredefinedAspectRatio {
/// 21:9
Ultrawide,
@@ -480,7 +477,7 @@ impl Window {
WindowsApi::is_window_visible(self.hwnd)
}
pub fn hide(self) {
pub fn hide_with_border(self, hide_border: bool) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
programmatically_hidden_hwnds.push(self.hwnd);
@@ -492,9 +489,16 @@ impl Window {
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd),
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2),
}
if hide_border {
border_manager::hide_border(self.hwnd);
}
}
pub fn restore(self) {
pub fn hide(self) {
self.hide_with_border(true);
}
pub fn restore_with_border(self, restore_border: bool) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds
.iter()
@@ -510,6 +514,13 @@ impl Window {
}
HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0),
}
if restore_border {
border_manager::show_border(self.hwnd);
}
}
pub fn restore(self) {
self.restore_with_border(true);
}
pub fn minimize(self) {
@@ -736,6 +747,30 @@ impl Window {
self.update_style(&style)
}
/// 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 also checks if there is a border attached to this window and if it is
/// it raises it as well.
pub fn raise(self) -> Result<()> {
WindowsApi::raise_window(self.hwnd)?;
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::raise_window(border_info.border_hwnd)?;
}
Ok(())
}
/// Lower the window to the bottom of the Z order, but do not activate or focus
/// it.
/// It also checks if there is a border attached to this window and if it is
/// it lowers it as well.
pub fn lower(self) -> Result<()> {
WindowsApi::lower_window(self.hwnd)?;
if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::lower_window(border_info.border_hwnd)?;
}
Ok(())
}
#[tracing::instrument(fields(exe, title), skip(debug))]
pub fn should_manage(
self,

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
use std::fmt::Display;
use std::fmt::Formatter;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -12,7 +11,8 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::REGEX_IDENTIFIERS;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "type", content = "content")]
pub enum WindowManagerEvent {
Destroy(WinEvent, Window),

View File

@@ -12,7 +12,6 @@ use windows::core::Result as WindowsCrateResult;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
@@ -110,6 +109,7 @@ use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
@@ -140,6 +140,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use windows_core::BOOL;
use crate::core::Rect;
@@ -477,10 +478,13 @@ impl WindowsApi {
unsafe { BringWindowToTop(HWND(as_ptr!(hwnd))) }.process()
}
// 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.
/// 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.
pub fn raise_window(hwnd: isize) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE | SetWindowPosition::NO_ACTIVATE;
let flags = SetWindowPosition::NO_MOVE
| SetWindowPosition::NO_SIZE
| SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::SHOW_WINDOW;
let position = HWND_TOP;
Self::set_window_pos(
@@ -491,6 +495,23 @@ impl WindowsApi {
)
}
/// Lower the window to the bottom of the Z order, but do not activate or focus
/// it.
pub fn lower_window(hwnd: isize) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE
| SetWindowPosition::NO_SIZE
| SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::SHOW_WINDOW;
let position = HWND_BOTTOM;
Self::set_window_pos(
HWND(as_ptr!(hwnd)),
&Rect::default(),
position,
flags.bits(),
)
}
pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> {
let flags = {
SetWindowPosition::NO_SEND_CHANGING
@@ -1139,7 +1160,7 @@ impl WindowsApi {
pub fn create_border_window(
name: PCWSTR,
instance: isize,
border: *const Border,
border: *mut Border,
) -> Result<isize> {
unsafe {
CreateWindowExW(

View File

@@ -8,7 +8,6 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent;
use crate::winevent_listener;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::WPARAM;
@@ -21,6 +20,7 @@ use windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows_core::BOOL;
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
@@ -105,12 +105,16 @@ pub extern "system" fn win_event_hook(
WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy
) && !has_filtered_style(hwnd)
{
let border_window = border_manager::window_border(hwnd.0 as isize);
let border_info = border_manager::window_border(hwnd.0 as isize);
if let Some(border) = border_window {
if let Some(border_info) = border_info {
unsafe {
let _ =
SendNotifyMessageW(border.hwnd(), event, WPARAM(0), LPARAM(hwnd.0 as isize));
let _ = SendNotifyMessageW(
border_info.hwnd(),
event,
WPARAM(0),
LPARAM(hwnd.0 as isize),
);
}
}
}

View File

@@ -1,6 +1,5 @@
#![allow(clippy::use_self)]
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -89,7 +88,8 @@ use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display, JsonSchema)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[repr(u32)]
#[allow(dead_code)]
pub enum WinEvent {

View File

@@ -1,4 +1,6 @@
use std::collections::VecDeque;
use std::fmt::Display;
use std::fmt::Formatter;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
@@ -8,10 +10,10 @@ use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::border_manager;
use crate::core::Axis;
use crate::core::CustomLayout;
use crate::core::CycleDirection;
@@ -41,60 +43,73 @@ use crate::REMOVE_TITLEBARS;
#[allow(clippy::struct_field_names)]
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Workspace {
#[getset(get = "pub", set = "pub")]
name: Option<String>,
containers: Ring<Container>,
pub name: Option<String>,
pub containers: Ring<Container>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
monocle_container: Option<Container>,
pub monocle_container: Option<Container>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
monocle_container_restore_idx: Option<usize>,
pub monocle_container_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
maximized_window: Option<Window>,
pub maximized_window: Option<Window>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
maximized_window_restore_idx: Option<usize>,
pub maximized_window_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub")]
floating_windows: Vec<Window>,
pub floating_windows: Vec<Window>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
layout: Layout,
pub layout: Layout,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
layout_rules: Vec<(usize, Layout)>,
pub layout_rules: Vec<(usize, Layout)>,
#[getset(get_copy = "pub", set = "pub")]
layout_flip: Option<Axis>,
pub layout_flip: Option<Axis>,
#[getset(get_copy = "pub", set = "pub")]
workspace_padding: Option<i32>,
pub workspace_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
container_padding: Option<i32>,
pub container_padding: Option<i32>,
#[getset(get = "pub", set = "pub")]
latest_layout: Vec<Rect>,
pub latest_layout: Vec<Rect>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
resize_dimensions: Vec<Option<Rect>>,
pub resize_dimensions: Vec<Option<Rect>>,
#[getset(get = "pub", set = "pub")]
tile: bool,
pub tile: bool,
#[getset(get_copy = "pub", set = "pub")]
apply_window_based_work_area_offset: bool,
pub apply_window_based_work_area_offset: bool,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
window_container_behaviour: Option<WindowContainerBehaviour>,
pub window_container_behaviour: Option<WindowContainerBehaviour>,
#[getset(get = "pub", get_mut = "pub", set = "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")]
float_override: Option<bool>,
#[serde(skip)]
pub float_override: Option<bool>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub globals: WorkspaceGlobals,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layer: WorkspaceLayer,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
workspace_config: Option<WorkspaceConfig>,
pub workspace_config: Option<WorkspaceConfig>,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum WorkspaceLayer {
#[default]
Tiling,
Floating,
}
impl Display for WorkspaceLayer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WorkspaceLayer::Tiling => write!(f, "Tiling"),
WorkspaceLayer::Floating => write!(f, "Floating"),
}
}
}
impl_ring_elements!(Workspace, Container);
@@ -121,6 +136,8 @@ impl Default for Workspace {
window_container_behaviour: None,
window_container_behaviour_rules: None,
float_override: None,
layer: Default::default(),
globals: Default::default(),
workspace_config: None,
}
}
@@ -134,21 +151,37 @@ pub enum WorkspaceWindowLocation {
Floating(usize), // idx in floating_windows
}
#[derive(
Debug,
Default,
Copy,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Settings setup either by the parent monitor or by the `WindowManager`
pub struct WorkspaceGlobals {
pub container_padding: Option<i32>,
pub workspace_padding: Option<i32>,
pub work_area: Rect,
pub work_area_offset: Option<Rect>,
pub window_based_work_area_offset: Option<Rect>,
pub window_based_work_area_offset_limit: isize,
}
impl Workspace {
pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> {
self.name = Option::from(config.name.clone());
if config.container_padding.is_some() {
self.set_container_padding(config.container_padding);
} else {
self.set_container_padding(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
}
self.set_container_padding(config.container_padding);
if config.workspace_padding.is_some() {
self.set_workspace_padding(config.workspace_padding);
} else {
self.set_container_padding(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
}
self.set_workspace_padding(config.workspace_padding);
if let Some(layout) = &config.layout {
self.layout = Layout::Default(*layout);
@@ -282,7 +315,7 @@ impl Workspace {
// Maximised windows and floating windows should always be drawn at the top of the Z order
// when switching to a workspace
if let Some(window) = to_focus {
if self.maximized_window().is_none() && self.floating_windows().is_empty() {
if self.maximized_window().is_none() && matches!(self.layer, WorkspaceLayer::Tiling) {
window.focus(mouse_follows_focus)?;
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.focus(mouse_follows_focus)?;
@@ -294,24 +327,29 @@ impl Workspace {
Ok(())
}
pub fn update(
&mut self,
work_area: &Rect,
work_area_offset: Option<Rect>,
window_based_work_area_offset: (isize, Option<Rect>),
) -> Result<()> {
pub fn update(&mut self) -> Result<()> {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
return Ok(());
}
let (window_based_work_area_offset_limit, window_based_work_area_offset) =
window_based_work_area_offset;
let container_padding = self
.container_padding()
.or(self.globals().container_padding)
.unwrap_or_default();
let workspace_padding = self
.workspace_padding()
.or(self.globals().workspace_padding)
.unwrap_or_default();
let work_area = self.globals().work_area;
let work_area_offset = self.globals().work_area_offset;
let window_based_work_area_offset = self.globals().window_based_work_area_offset;
let window_based_work_area_offset_limit =
self.globals().window_based_work_area_offset_limit;
let container_padding = self.container_padding();
let mut adjusted_work_area = work_area_offset.map_or_else(
|| *work_area,
|| work_area,
|offset| {
let mut with_offset = *work_area;
let mut with_offset = work_area;
with_offset.left += offset.left;
with_offset.top += offset.top;
with_offset.right -= offset.right;
@@ -339,7 +377,7 @@ impl Workspace {
);
}
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
adjusted_work_area.add_padding(workspace_padding);
self.enforce_resize_constraints();
@@ -373,7 +411,7 @@ impl Workspace {
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding.unwrap_or_default());
adjusted_work_area.add_padding(container_padding);
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
adjusted_work_area.add_padding(border_offset);
@@ -392,7 +430,7 @@ impl Workspace {
"there must be at least one container to calculate a workspace layout"
)
})?,
self.container_padding(),
Some(container_padding),
self.layout_flip(),
self.resize_dimensions(),
);
@@ -401,7 +439,6 @@ impl Workspace {
let no_titlebar = NO_TITLEBAR.lock().clone();
let regex_identifiers = REGEX_IDENTIFIERS.lock().clone();
let container_padding = self.container_padding().unwrap_or(0);
let containers = self.containers_mut();
for (i, container) in containers.iter_mut().enumerate() {
@@ -810,6 +847,8 @@ impl Workspace {
}
pub fn remove_window(&mut self, hwnd: isize) -> Result<()> {
border_manager::delete_border(hwnd);
if self.floating_windows().iter().any(|w| w.hwnd == hwnd) {
self.floating_windows_mut().retain(|w| w.hwnd != hwnd);
return Ok(());

View File

@@ -1,29 +0,0 @@
use komorebi::StaticConfig;
#[test]
fn backwards_compat() {
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
let docs = vec![
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27", "0.1.28",
"0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33",
];
let mut versions = vec![];
let client = reqwest::blocking::Client::new();
for version in root {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for version in docs {
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
versions.push((version, client.execute(request).unwrap().text().unwrap()));
}
for (version, config) in versions {
println!("{version}");
StaticConfig::read_raw(&config).unwrap();
}
}

View File

@@ -21,7 +21,7 @@ miette = { version = "7", features = ["fancy"] }
paste = { workspace = true }
powershell_script = "1.0"
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
shadow-rs = { workspace = true }
@@ -34,5 +34,9 @@ windows = { workspace = true }
reqwest = { version = "0.12", features = ["blocking"] }
shadow-rs = { workspace = true }
[features]
default = ["schemars"]
schemars = ["dep:schemars"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] }

View File

@@ -26,15 +26,12 @@ use komorebi_client::resolve_home_path;
use komorebi_client::send_message;
use komorebi_client::send_query;
use komorebi_client::ApplicationSpecificConfiguration;
use komorebi_client::Notification;
use lazy_static::lazy_static;
use miette::NamedSource;
use miette::Report;
use miette::SourceOffset;
use miette::SourceSpan;
use paste::paste;
use schemars::gen::SchemaSettings;
use schemars::schema_for;
use serde::Deserialize;
use sysinfo::ProcessesToUpdate;
use which::which;
@@ -158,6 +155,7 @@ gen_enum_subcommand_args! {
CycleMoveToMonitor: CycleDirection,
CycleMonitor: CycleDirection,
CycleWorkspace: CycleDirection,
CycleEmptyWorkspace: CycleDirection,
CycleMoveWorkspaceToMonitor: CycleDirection,
Stack: OperationDirection,
CycleStack: CycleDirection,
@@ -1132,6 +1130,9 @@ enum SubCommand {
/// Focus the workspace in the given cycle direction
#[clap(arg_required_else_help = true)]
CycleWorkspace(CycleWorkspace),
/// Focus the next empty workspace in the given cycle direction (if one exists)
#[clap(arg_required_else_help = true)]
CycleEmptyWorkspace(CycleWorkspace),
/// Move the focused workspace to the specified monitor
#[clap(arg_required_else_help = true)]
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
@@ -1269,6 +1270,8 @@ enum SubCommand {
/// 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.
ToggleWorkspaceFloatOverride,
/// Toggle between the Tiling and Floating layers on the focused workspace
ToggleWorkspaceLayer,
/// Toggle window tiling on the focused workspace
TogglePause,
/// Toggle window tiling on the focused workspace
@@ -2625,6 +2628,11 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::CycleWorkspace(arg) => {
send_message(&SocketMessage::CycleFocusWorkspace(arg.cycle_direction))?;
}
SubCommand::CycleEmptyWorkspace(arg) => {
send_message(&SocketMessage::CycleFocusEmptyWorkspace(
arg.cycle_direction,
))?;
}
SubCommand::NewWorkspace => {
send_message(&SocketMessage::NewWorkspace)?;
}
@@ -2854,6 +2862,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::ToggleWorkspaceFloatOverride => {
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
}
SubCommand::ToggleWorkspaceLayer => {
send_message(&SocketMessage::ToggleWorkspaceLayer)?;
}
SubCommand::WindowHidingBehaviour(arg) => {
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?;
}
@@ -2968,31 +2979,43 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
);
}
SubCommand::ApplicationSpecificConfigurationSchema => {
let asc = schema_for!(ApplicationSpecificConfiguration);
let schema = serde_json::to_string_pretty(&asc)?;
println!("{schema}");
#[cfg(feature = "schemars")]
{
let asc = schemars::schema_for!(ApplicationSpecificConfiguration);
let schema = serde_json::to_string_pretty(&asc)?;
println!("{schema}");
}
}
SubCommand::NotificationSchema => {
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
println!("{schema}");
#[cfg(feature = "schemars")]
{
let notification = schemars::schema_for!(komorebi_client::Notification);
let schema = serde_json::to_string_pretty(&notification)?;
println!("{schema}");
}
}
SubCommand::SocketSchema => {
let socket_message = schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
#[cfg(feature = "schemars")]
{
let socket_message = schemars::schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}
}
SubCommand::StaticConfigSchema => {
let settings = SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
#[cfg(feature = "schemars")]
{
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}
}
SubCommand::GenerateStaticConfig => {
print_query(&SocketMessage::GenerateStaticConfig);

View File

@@ -136,6 +136,7 @@ nav:
- cli/close-workspace.md
- cli/cycle-monitor.md
- cli/cycle-workspace.md
- cli/cycle-empty-workspace.md
- cli/move-workspace-to-monitor.md
- cli/cycle-move-workspace-to-monitor.md
- cli/swap-workspaces-with-monitor.md
@@ -177,6 +178,7 @@ nav:
- cli/toggle-float-override.md
- cli/toggle-workspace-window-container-behaviour.md
- cli/toggle-workspace-float-override.md
- cli/toggle-workspace-layer.md
- cli/toggle-pause.md
- cli/toggle-tiling.md
- cli/toggle-float.md

View File

@@ -504,16 +504,15 @@
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"workspace_layer": {
"description": "Configure the Workspace Layer widget",
"type": "object",
"required": [
"enable",
"hide_empty_workspaces"
"enable"
],
"properties": {
"display": {
"description": "Display format of the workspace",
"description": "Display format of the current layer",
"oneOf": [
{
"description": "Show only icon",
@@ -552,6 +551,98 @@
}
]
},
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
},
"show_when_tiling": {
"description": "Show the widget event if the layer is Tiling",
"type": "boolean"
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"type": "object",
"required": [
"enable",
"hide_empty_workspaces"
],
"properties": {
"display": {
"description": "Display format of the workspace",
"oneOf": [
{
"description": "Show all icons only",
"type": "string",
"enum": [
"AllIcons"
]
},
{
"description": "Show both all icons and text",
"type": "string",
"enum": [
"AllIconsAndText"
]
},
{
"description": "Show all icons and text for the selected element, and all icons on the rest",
"type": "string",
"enum": [
"AllIconsAndTextOnSelected"
]
},
{
"type": "object",
"required": [
"Existing"
],
"properties": {
"Existing": {
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
}
},
"additionalProperties": false
}
]
},
"enable": {
"description": "Enable the Komorebi Workspaces widget",
"type": "boolean"
@@ -1811,16 +1902,15 @@
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"workspace_layer": {
"description": "Configure the Workspace Layer widget",
"type": "object",
"required": [
"enable",
"hide_empty_workspaces"
"enable"
],
"properties": {
"display": {
"description": "Display format of the workspace",
"description": "Display format of the current layer",
"oneOf": [
{
"description": "Show only icon",
@@ -1859,6 +1949,98 @@
}
]
},
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
},
"show_when_tiling": {
"description": "Show the widget event if the layer is Tiling",
"type": "boolean"
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"type": "object",
"required": [
"enable",
"hide_empty_workspaces"
],
"properties": {
"display": {
"description": "Display format of the workspace",
"oneOf": [
{
"description": "Show all icons only",
"type": "string",
"enum": [
"AllIcons"
]
},
{
"description": "Show both all icons and text",
"type": "string",
"enum": [
"AllIconsAndText"
]
},
{
"description": "Show all icons and text for the selected element, and all icons on the rest",
"type": "string",
"enum": [
"AllIconsAndTextOnSelected"
]
},
{
"type": "object",
"required": [
"Existing"
],
"properties": {
"Existing": {
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
}
},
"additionalProperties": false
}
]
},
"enable": {
"description": "Enable the Komorebi Workspaces widget",
"type": "boolean"
@@ -3051,16 +3233,15 @@
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"workspace_layer": {
"description": "Configure the Workspace Layer widget",
"type": "object",
"required": [
"enable",
"hide_empty_workspaces"
"enable"
],
"properties": {
"display": {
"description": "Display format of the workspace",
"description": "Display format of the current layer",
"oneOf": [
{
"description": "Show only icon",
@@ -3099,6 +3280,98 @@
}
]
},
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
},
"show_when_tiling": {
"description": "Show the widget event if the layer is Tiling",
"type": "boolean"
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"type": "object",
"required": [
"enable",
"hide_empty_workspaces"
],
"properties": {
"display": {
"description": "Display format of the workspace",
"oneOf": [
{
"description": "Show all icons only",
"type": "string",
"enum": [
"AllIcons"
]
},
{
"description": "Show both all icons and text",
"type": "string",
"enum": [
"AllIconsAndText"
]
},
{
"description": "Show all icons and text for the selected element, and all icons on the rest",
"type": "string",
"enum": [
"AllIconsAndTextOnSelected"
]
},
{
"type": "object",
"required": [
"Existing"
],
"properties": {
"Existing": {
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
}
},
"additionalProperties": false
}
]
},
"enable": {
"description": "Enable the Komorebi Workspaces widget",
"type": "boolean"

View File

@@ -1075,6 +1075,11 @@
"workspaces"
],
"properties": {
"container_padding": {
"description": "Container padding (default: global)",
"type": "integer",
"format": "int32"
},
"window_based_work_area_offset": {
"description": "Window based work area offset (default: None)",
"type": "object",
@@ -1144,6 +1149,11 @@
}
}
},
"workspace_padding": {
"description": "Workspace padding (default: global)",
"type": "integer",
"format": "int32"
},
"workspaces": {
"description": "Workspace configurations",
"type": "array",
@@ -1346,7 +1356,7 @@
}
},
"workspace_padding": {
"description": "Container padding (default: global)",
"description": "Workspace padding (default: global)",
"type": "integer",
"format": "int32"
},