Compare commits

...

60 Commits

Author SHA1 Message Date
LGUG2Z
2dbf7da249 feat(config): add default_workspace_layout opt
This commit adds a default_workspace_layout opt, which defaults to BSP
to maintain backwards compatibility. This can also be set to "None".

When set to "None" or omitted, the default behaviour for new or
undefined workspaces (i.e. on monitors without config blocks) will be
non-tiling.  Otherwise, the given value will be the default layout
applied.
2026-01-13 08:26:41 -08:00
LGUG2Z
95838fc896 build(just): add target for running procdump 2026-01-13 08:26:23 -08:00
LGUG2Z
bdef1448e1 fix(borders): prevent use-after-free in border destruction
This commit fixes a use-after-free bug in the border_manager that was
causing crashes with exception code 0xc000041d when borders were being
destroyed during workspace/monitor changes.

The root cause was a race condition between the main thread destroying a
border window and the border's window thread still processing queued
window messages (WM_PAINT, EVENT_OBJECT_LOCATIONCHANGE). The crash
occurred when these callbacks invoked render_target.EndDraw(), which
internally calls HwndPresenter::Present() to present the rendered frame
to the HWND. By this point, the HWND and its associated DirectX surfaces
had already been freed, resulting in Direct2D attempting to dereference
freed memory (0xFEEEFEEEFEEEFEEE - Windows heap poison value).

The issue stemmed from two problems in destroy_border():

1. Direct2D resources (render_target and brushes) were not being
   released before closing the window. These COM objects hold internal
   pointers to the HWND and its DirectX swap chain/surfaces. When
   close_window() was called, Windows began tearing down these resources
   while the Direct2D objects still held dangling pointers to them.

2. GWLP_USERDATA was not being cleared before closing the window. This
   meant that any messages already queued in the window's message queue
   could still retrieve the border_pointer and attempt to render using
   the now-invalid Direct2D resources.

This commit addresses both issues:

- In destroy_border() (mod.rs): Explicitly set render_target to None and
  clear the brushes HashMap before calling destroy(). This ensures that
  Direct2D COM objects are properly released while the HWND is still
  valid, preventing EndDraw() from accessing freed HWND resources.

- In destroy() (border.rs): Clear GWLP_USERDATA before calling
  close_window(). This ensures that any pending window messages will see
  a null pointer and exit early from the callbacks (which already have
  null pointer checks in place).

These changes create two layers of defense against the race condition:
the callbacks won't access the border_pointer (it's null), and even if
they somehow did, the render_target would be None so no rendering
operations would occur.

I think this is the root cause of a lot of crash tickets that people are
mistakenly attributing to specific applications which lack
reproducibility across different users/machines, i.e. #1626, #1624.
2026-01-13 08:26:23 -08:00
Rejdukien
0b39551470 fix(wm): clear last error before calling GetWindowLongPtrW
Apparently there is a quirk of GetWindowLongPtr when querying styles. If
the style bits are genuinely 0, the API returns 0 but does not clear the
last error.

If a previous API call set an error, GetWindowLongPtr might define
"failure" as "return 0 AND GetLastError != 0".

Hence, it is important to clear the last/previous error before calling
GetWindowLongPtrW to ensure that we don't misinterpret a valid 0 return
value as an error.
2026-01-13 08:26:23 -08:00
LGUG2Z
ab528f799d chore(deps): cargo update 2026-01-13 08:26:22 -08:00
LGUG2Z
8805fafa99 feat(bar): make explicit monitor config optional
This commit makes the monitor configuration option for the komorebi-bar
optional, defaulting to index 0.
2026-01-04 19:15:24 -08:00
LGUG2Z
1219c3d118 refactor(bar): inline defaults in schemars attrs 2026-01-04 19:08:41 -08:00
LGUG2Z
b792328676 fix(splash): show mdm workspace tenant name instead of mdm url
The WorkspaceTenantName is populated far more consistently than MdmUrl.

This commit switches to extracting that instead and passing it on to the
MDM splash screen so that users on non-corporate devices who may have
unintentionally enrolled themselves into BYOD MDM by logging into an
account a clicking "Yes" on some dark pattern pop-up have a clear
indication of why they are seeing the splash, and can take the
appropriate steps to remove the MDM profile from their system if
desired.
2026-01-04 07:33:32 -08:00
LGUG2Z
a7380db47c docs(readme): add debugging instructions for people who don't know they've been mdm'd 2026-01-04 07:15:59 -08:00
LGUG2Z
086993a7c0 fix(cli): respect --bar flag in start even when no config is resolved
I think this got broken as part of the automatic Rust version syntax
upgrades which joined two if clauses together with && which should have
been kept separate.

Now, if the user gives the --bar flag to the start command, and a static
config file is not resolved, or if the static config file does not have
a bar_configurations stanza, it will fallthrough to the default
PowerShell snippet to try and start komorebi-bar without an explicit
--config flag.
2026-01-03 16:55:59 -08:00
LGUG2Z
51e1337f40 fix(wm): consider window count when calculating scrolling column width
This commit fixes the stupidest of stupid bugs. Column width
calculations on the Scrolling layout should take the number of windows
into account, especially when lower than the configured column count.
2026-01-01 13:07:18 -08:00
LGUG2Z
6cef8d9ef6 refactor(schema): simplify defaults in static_config 2025-12-31 13:04:34 -08:00
LGUG2Z
6e36b81669 build(nix): add flake.nix for cross-compilation from darwin and linux
I was getting really tired of having to switch between display inputs to
different platform-specific machines to be able to make and test changes
on komorebi for Windows and komorebi for Mac.

With this commit, the `flake.nix` provides a Nix devShell and crane
build for users to make and validate changes with `cargo check`, `cargo
clippy` and `cargo build` with the Windows MSVC toolchain on Linux and
macOS.
2025-12-28 17:24:41 -08:00
LGUG2Z
0758c7d900 refactor(theme): centralize theme-related code in komorebi-themes
Getting tired of making little changes in both this and the komorebi for
Mac repo - I think eventually either komorebi-themes will live in its
own repo or komorebi for Mac will be integrated here.

But for now, at least everything is defined in komorebi-themes and I
don't have to redefine any theme-related stuff in komorebi for Mac.
2025-12-27 21:58:39 -08:00
LGUG2Z
f77a303b30 docs(schema): ensure every enum variant has a renderable title 2025-12-27 20:34:31 -08:00
LGUG2Z
3a81f7babb docs(schema): ensure all public-facing bar config opts have docstrings 2025-12-27 13:40:45 -08:00
LGUG2Z
3d8778a7d6 docs(schema): ensure all public-facing static config opts have docstrings 2025-12-27 12:58:46 -08:00
LGUG2Z
a42e809ade docs(config): encode defaults of unwrapped options into schema
This commit ensures that the various default values that the different
Option<T> config properties can be unwrapped to are encoded by schemars
so that they can be picked up by docgen.
2025-12-26 18:16:55 -08:00
LGUG2Z
66c5766848 feat(cli): add custom output fir for docgen cmd 2025-12-26 10:26:00 -08:00
LGUG2Z
ac56590791 chore(clippy): address warnings 2025-12-23 17:19:00 -08:00
LGUG2Z
4d67f5fed3 build(just): update schemagen and add schemapub 2025-12-22 20:45:44 -08:00
LGUG2Z
c91224295f docs(schema): avoid duplicating display format enums in bar 2025-12-22 19:30:29 -08:00
LGUG2Z
4b1e3bd448 docs(schema): add deprecations and improve formatting 2025-12-21 21:56:17 -08:00
LGUG2Z
1cb8ed7f10 chore(deps): bump schemars to 1.1 2025-12-21 16:26:25 -08:00
LGUG2Z
90271f1f99 docs(schema): clarify that 'theme' take priority over 'border_colours' 2025-12-20 21:12:56 -08:00
LGUG2Z
9fde9ed6f8 chore(deps): cargo update 2025-12-20 11:40:31 -08:00
LGUG2Z
ce4479eb9f chore(deps): cargo update 2025-12-16 18:05:51 -08:00
LGUG2Z
f3b7f5ac42 chore(clippy): odd unused_assignment failures in rust 1.92.0 2025-12-14 20:00:18 -08:00
LGUG2Z
382896dfd2 docs(readme): add detailed instructions for students w/ mdm devices 2025-12-14 14:16:48 -08:00
LGUG2Z
ff4187aecf chore(dev): begin v0.1.40-dev 2025-12-14 14:07:44 -08:00
LGUG2Z
8f982e45d1 chore(release): v0.1.39 2025-12-09 17:06:14 -08:00
LGUG2Z
309bfdfee1 chore(deps): update dependencies.json 2025-12-09 16:55:47 -08:00
LGUG2Z
a0239faea0 refactor(cli): use "args" var name uniformly 2025-12-07 19:09:56 -08:00
LGUG2Z
999dd5d20d chore(deps): cargo update 2025-12-06 14:02:52 -08:00
bilogic
69086299e1 ci(github): render backticks in bug report template 2025-11-30 11:30:39 -08:00
LGUG2Z
4114b10b05 chore(deps): cargo update 2025-11-25 09:00:14 -08:00
LGUG2Z
71402fe01b docs(wm): update splash for students 2025-11-25 08:59:35 -08:00
dependabot[bot]
5c2767d589 chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 14:42:05 -08:00
LGUG2Z
ac67c41377 docs(schema): update jsonschema 2025-11-23 10:56:01 -08:00
Csaba
faed6ec535 fix(bar): update the network widget in a separate thread
This commit changes the way how the default interface and network
activity is loaded by spawning a new thread on each request. This way
the main thread is not blocked by this process.

There has been instances where getting the default interface blocked the
egui ui thread, resulting in a frozen bar.

fix #1423 #1499
2025-11-23 10:55:55 -08:00
LGUG2Z
4ebba08b7d chore(deps): bump whkd to v0.2.10 2025-11-22 12:47:11 -08:00
alex-ds13
a733fecb49 fix(client): expose more useful structs 2025-11-22 12:44:48 -08:00
alex-ds13
31c969fc55 fix(wm): promoting a container shouldn't change resize dimensions
- When promoting a container it is using the
  `remove_focused_container()` and `insert_container_at_idx` functions
  which will remove the focused container size from `resize_dimensions`
  and then add the new container with size `None`. Then it was adding
  the previously stored size of the main container back to the first
  one. However this would result in at least 3 issues:

    1. We were removing the size from index 0, however when inserting it
       back on the primary_idx we might not add it to index 0, specially
       now that there could be locked containers so if container 0 is
       locked we would be adding it to the next container, which would
       result in the main container having no size information which
       would result in a recalculation on the next update and it would
       revert back to default, so if a user had resized their containers
       that information would be lost.

    2. We were removing the size from index 0. Then on
       `remove_focused_container` it would remove the size from the
       focused container index, but since we had already removed one
       size from the `resize_dimensions` that index would no longer
       match the actual focused container, so once again we would be
       losing information.

    3. Since we were removing the size of the focused container idx from
       `resize_dimensions` (albeit the wrong one as stated above), this
       would mean that the container that moved to that position would
       have no resize information so it would show up as if the main
       container was taking up half width of the area, ignoring any user
       resize, until the next workspace update was triggered.

- This commit completely ignores the `resize_dimensions` since promoting
  a container simply means moving one container from one position to the
  main one (usually index 0) and then shift all the rest to the "right".
  The existing `resize_dimensions` should be kept untouched. To do this
  we use the functions `remove_respecting_locks` and
  `insert_respecting_locks` on the containers themselves, which simply
  move the container and shifts the others which is precisely what we
  want.
2025-11-22 12:43:02 -08:00
LGUG2Z
df2adde13d feat(wm): remove scrolling layout column fallbacks
This commit removes the fallback to the columns layout when the number
of columns on the scrolling layout is less than 3.
2025-11-18 16:00:28 -08:00
LGUG2Z
a77b3e774a fix(docs): replace deprecated aliases in komorebi.bar.example.json
This commit ensures that we are using show_total_activity and
show_activity instead of show_total_data_transmitted and
show_network_activity in the komorebi.bar.example.json file which is
populated for users in the quickstart command.

JSONSchema is not smart enough to resolve aliases for backwards compat,
which results in confusing behaviour for new users trying to edit this
file.

fix #1596
2025-11-14 08:02:04 -08:00
LGUG2Z
c8c4c3507c chore(deps): handle egui-related upgrades 2025-11-12 16:20:19 -08:00
LGUG2Z
df38facf9e feat(wm): make promote-swap reversible
This commit makes the komorebic promote-swap command reversible by
storing the previous container index in the Workspace state.

If the current container index is the same as the layout's primary
index, and there is a previous promotion swap container index available,
the two containers at those indices will be swapped, effectively making
a second call to promote-swap an undo.
2025-11-04 13:18:35 -08:00
LGUG2Z
416dd94670 feat(wm): promote containers by swapping indices
This commit adds a new SocketMessage variant PromoteSwap and
corresponding komorebic promote-swap command.

PromoteSwap will also promote the focused window container to the
largest tile in the layout, but instead of removing the focused
container x from the Ring and inserting it into position 0, possibly
changing the positions of other windows between 0 and x, the indices x
and 0 in the Ring will be swapped directly.
2025-11-04 08:02:07 -08:00
LGUG2Z
d0ae92ca3a feat(wm): add visual feedback for preselection
This commit adds visual feedback in the form of a ghost tile for
preselections made by the preselect-direction command.

A container with the id "PRESELECT" will be added to the workspace, and
replaced when the next manage-able window is spawned.

A new command, cancel-preselect, has been added to remove both the
preselection index and the ghost tile if the user changes their mind.
2025-11-02 11:06:39 -08:00
LGUG2Z
3491dc7590 docs(readme): add note for people looking for komorebi for mac 2025-10-31 19:26:25 -07:00
LGUG2Z
adbb6c1cb0 feat(wm): add direction preselection
This commit adds a new feature to preselect the direction of the next
spawned window with a corresponding komorebic preselect-direction
command which takes an OperationDirection.

If the OperationDirection is valid from the current position, it will be
stored in the Workspace state, and then read, applied, and deleted when
the next manage-able window is spawned.

Direction preselection does not (yet?) support the Grid layout.
2025-10-31 15:08:57 -07:00
alex-ds13
18ee667896 fix(wm): simplify stack-all command
This commit makes the `stack-all` command simpler, by simply making a
`VecDeque` with all the tiled windows of the workspace, then it creates
a new container with that `VecDeque` and changes the workspace
containers to be just this new container which has all the windows in
it.

Then and only then do we focus and show the previously focused window
and hide all the rest.

This way we don't have a bunch of `FocusChange`/`Cloak`/`Uncloak` events
coming up which might conflict with each other when the user has
transparency and animations enabled.

With this commit, there will only be one focus event for the focused
window and one cloak event for each of the other windows.
2025-10-30 13:57:17 -07:00
LGUG2Z
1a42c64620 feat(cli): add feedback to the license cmd 2025-10-29 08:48:25 -07:00
LGUG2Z
b613986474 feat(wm): add a splash screen for mdm devices
A little nudge for people to do the right thing, 1990s vibes.
2025-10-28 18:03:17 -07:00
dependabot[bot]
e93751aa5f chore(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 09:07:45 -07:00
dependabot[bot]
9872dcd0d7 chore(deps): bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 09:07:32 -07:00
LGUG2Z
f99c82f5d4 ci(github): reduce workflow costs 2025-10-20 08:12:27 -07:00
LGUG2Z
b255058467 docs(cli): add eol annotations to ahk flags
Marking all --ahk flags in the start/stop/kill/enable-autostart commands
as EOL; there are any number of ways that users can manage their own
launching of AutoHotKey scripts and the complexity of the different ways
that AHK can be installed is not worth the maintenance burden for this
project.
2025-10-17 16:41:06 -07:00
LGUG2Z
2abe618354 feat(wm): add opt to constrain grid layout by rows
This commit adds a new LayoutOptions option, GridLayoutOptions,
currently with a single configurable "rows" opt which can be use to
constrain the grid by number of rows.
2025-10-17 16:41:03 -07:00
LGUG2Z
e953715fef feat(wm): track virtual desktop ids on state and global state
Also moved out State and GlobalState to state.rs to match the emerging
pattern in komorebi for Mac.
2025-10-14 13:11:12 -07:00
93 changed files with 17935 additions and 67910 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

View File

@@ -55,6 +55,7 @@ body:
label: Hotkey Configuration
description: >
Please provide your whkdrc or komorebi.ahk hotkey configuration file
render: shell
- type: textarea
validations:
required: true
@@ -62,3 +63,4 @@ body:
label: Output of komorebic check
description: >
Please provide the output of `komorebic check`
render: shell

View File

@@ -13,15 +13,15 @@ on:
- hotfix/*
tags:
- v*
schedule:
- cron: "30 0 * * 0" # Every day at 00:30 UTC
# schedule:
# - cron: "30 0 * * 0" # Every day at 00:30 UTC
workflow_dispatch:
jobs:
cargo-deny:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: EmbarkStudios/cargo-deny-action@v2
@@ -43,7 +43,7 @@ jobs:
RUSTFLAGS: -Ctarget-feature=+crt-static -Dwarnings
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: rustup toolchain install stable --profile minimal
@@ -65,7 +65,7 @@ jobs:
- run: |
cargo install cargo-wix
cargo wix --no-build -p komorebi --nocapture -I .\wix\main.wxs --target ${{ matrix.platform.target }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v5
with:
name: komorebi-${{ matrix.platform.target }}-${{ github.sha }}
path: |
@@ -82,12 +82,12 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- shell: bash
run: echo "VERSION=nightly" >> $GITHUB_ENV
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v6
- run: |
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
@@ -129,14 +129,14 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- shell: bash
run: |
TAG=${{ github.event.release.tag_name }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v6
- run: |
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi
@@ -171,14 +171,14 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- shell: bash
run: |
TAG=${{ github.ref_name }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v6
- run: |
Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip
Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi

6
.gitignore vendored
View File

@@ -6,3 +6,9 @@ dummy.go
komorebic/applications.yaml
komorebic/applications.json
/.vs
/bar-schema
/komorebi-schema
/.wrangler
/.xwin-cache
result
/.direnv

2152
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,14 @@
resolver = "2"
members = [
"komorebi",
"komorebi-client",
"komorebi-gui",
"komorebic",
"komorebic-no-console",
"komorebi-bar",
"komorebi-themes",
"komorebi-shortcuts",
"komorebi",
"komorebi-client",
"komorebi-gui",
"komorebic",
"komorebic-no-console",
"komorebi-bar",
"komorebi-themes",
"komorebi-shortcuts",
]
[workspace.dependencies]
@@ -19,12 +19,12 @@ chrono = "0.4"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
color-eyre = "0.6"
eframe = "0.32"
egui_extras = "0.32"
eframe = "0.33"
egui_extras = "0.33"
dirs = "6"
dunce = "1"
hotwatch = "0.5"
schemars = "0.8"
schemars = "1.1"
lazy_static = "1"
serde = { version = "1", features = ["derive"] }
serde_json = { package = "serde_json_lenient", version = "0.2" }
@@ -48,32 +48,32 @@ which = "8"
[workspace.dependencies.windows]
version = "0.62"
features = [
"Foundation_Numerics",
"Win32_Devices",
"Win32_Devices_Display",
"Win32_System_Com",
"Win32_UI_Shell_Common", # for IObjectArray
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Dxgi_Common",
"Win32_System_LibraryLoader",
"Win32_System_Power",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_UI_Accessibility",
"Win32_UI_HiDpi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging",
"Win32_System_SystemServices",
"Win32_System_WindowsProgramming",
"Media",
"Media_Control",
"Foundation_Numerics",
"Win32_Devices",
"Win32_Devices_Display",
"Win32_System_Com",
"Win32_UI_Shell_Common", # for IObjectArray
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Dxgi_Common",
"Win32_System_LibraryLoader",
"Win32_System_Power",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_UI_Accessibility",
"Win32_UI_HiDpi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging",
"Win32_System_SystemServices",
"Win32_System_WindowsProgramming",
"Media",
"Media_Control",
]
[profile.release-opt]
@@ -82,3 +82,6 @@ lto = true
panic = "abort"
codegen-units = 1
strip = true
[workspace.metadata.crane]
name = "komorebi-workspace"

View File

@@ -29,6 +29,46 @@ Tiling Window Management for Windows.
![screenshot](https://user-images.githubusercontent.com/13164844/184027064-f5a6cec2-2865-4d65-a549-a1f1da589abf.png)
## Note: Students using devices enrolled in mobile device management (MDM)
Your usage still falls under the [Komorebi License 2.0.0](./LICENSE.md).
You can email me at the address I sign my commits with (add `.patch` to the end
of any commit URL on GitHub to find it) from the address associated with your
institution with the subject "komorebi - student with an MDM device", and I will
be able to remove the splash intended for corporate users, whose usage falls
under the [Individual Commercial Use
License](https://lgug2z.com/software/komorebi).
This is currently a manual process - most days this shouldn't take more than
12h, and you will receive an email reply from me when the process is complete.
If you haven't had a reply to your email within 24h you can reach out to me on
Discord.
## Note: Unexpected mobile device management (MDM) detection prompts
You have most likely unintentionally enrolled your device in "Bring Your Own
Device" (BYOD) MDM. You can confirm if this is the case by running `dsregcmd
/status` and then take the appropriate steps to remove the MDM profile and take
back full control of your system.
If you need help doing this you can ask on Discord.
## Note: komorebi for Mac
If you made your way to this repo looking for [komorebi for
Mac](https://github.com/KomoCorp/komorebi-for-mac), the project is currently
being developed in private with [early access available to GitHub
Sponsors](https://github.com/sponsors/LGUG2Z).
If you want to see how far along development is before signing up for early
access (spoiler: it's very far along!) there is an overview video you can watch
[here](https://www.youtube.com/watch?v=u3eJcsa_MJk).
Sponsors with early access can install komorebi for Mac either by compiling
from source, by using Homebrew, or by using the project's Nix Flake.
## Overview
_komorebi_ is a tiling window manager that works as an extension to Microsoft's
@@ -394,7 +434,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
```rust
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.38"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.39"}
use anyhow::Result;
use komorebi_client::Notification;

432
check_schema_docs.py Normal file
View File

@@ -0,0 +1,432 @@
#!/usr/bin/env python3
"""
Check schema.json and schema.bar.json for missing docstrings and map them to Rust source files.
This script analyzes the generated JSON schemas and identifies:
1. Type definitions ($defs) missing top-level descriptions
2. Enum variants missing descriptions (in oneOf/anyOf)
3. Enum variants missing titles (object variants in oneOf/anyOf)
4. Struct properties missing descriptions
5. Top-level schema properties missing descriptions
For each missing docstring, it attempts to find the corresponding Rust source
file and line number where the docstring should be added.
"""
import json
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
@dataclass
class MissingDoc:
type_name: str
kind: str # "type", "variant", "property", "variant_title"
item_name: Optional[str] # variant or property name
rust_file: Optional[str] = None
rust_line: Optional[int] = None
def __str__(self):
location = ""
if self.rust_file and self.rust_line:
location = f" -> {self.rust_file}:{self.rust_line}"
elif self.rust_file:
location = f" -> {self.rust_file}"
if self.kind == "type":
return f"[TYPE] {self.type_name}{location}"
elif self.kind == "variant":
return f"[VARIANT] {self.type_name}::{self.item_name}{location}"
elif self.kind == "variant_title":
return f"[VARIANT_TITLE] {self.type_name}::{self.item_name}{location}"
else:
return f"[PROPERTY] {self.type_name}.{self.item_name}{location}"
@dataclass
class SchemaConfig:
"""Configuration for a schema to check."""
schema_file: str
search_paths: list[str]
display_name: str
def find_rust_definition(
type_name: str, item_name: Optional[str], kind: str, search_paths: list[Path]
) -> tuple[Optional[str], Optional[int]]:
"""Find the Rust file and line number for a type/variant/property definition."""
if kind == "type":
patterns = [
rf"pub\s+enum\s+{type_name}\b",
rf"pub\s+struct\s+{type_name}\b",
]
elif kind in ("variant", "variant_title"):
patterns = [
rf"^\s*{re.escape(item_name)}\s*[,\(\{{]",
rf"^\s*{re.escape(item_name)}\s*$",
rf"^\s*#\[.*\]\s*\n\s*{re.escape(item_name)}\b",
]
else: # property
patterns = [rf"pub\s+{re.escape(item_name)}\s*:"]
for search_path in search_paths:
for rust_file in search_path.rglob("*.rs"):
try:
content = rust_file.read_text()
lines = content.split("\n")
if kind == "type":
for pattern in patterns:
for i, line in enumerate(lines):
if re.search(pattern, line):
return str(rust_file), i + 1
elif kind in ("variant", "variant_title", "property"):
parent_pattern = rf"pub\s+(?:enum|struct)\s+{type_name}\b"
in_type = False
brace_count = 0
found_open_brace = False
for i, line in enumerate(lines):
if re.search(parent_pattern, line):
in_type = True
brace_count = 0
found_open_brace = False
if in_type:
if "{" in line:
found_open_brace = True
brace_count += line.count("{") - line.count("}")
for pattern in patterns:
if re.search(pattern, line):
return str(rust_file), i + 1
if found_open_brace and brace_count <= 0:
in_type = False
except Exception:
continue
return None, None
def _get_variant_identifier(variant: dict) -> str:
"""Extract a meaningful identifier for a variant.
Tries to find the best identifier by checking:
1. A top-level const value (e.g., {"const": "Linear"})
2. A property with a const value (e.g., {"kind": {"const": "Bar"}})
3. The first required property name
4. The type field
5. Falls back to "unknown"
"""
# Check for top-level const value (simple enum variant)
if "const" in variant:
return str(variant["const"])
properties = variant.get("properties", {})
# Check for a property with a const value (common pattern for tagged enums)
for prop_name, prop_def in properties.items():
if isinstance(prop_def, dict) and "const" in prop_def:
return str(prop_def["const"])
# Fall back to first required property name
required = variant.get("required", [])
if required:
return str(required[0])
# Fall back to type
if "type" in variant:
return str(variant["type"])
return "unknown"
def check_type_description(type_name: str, type_def: dict) -> list[MissingDoc]:
"""Check if a type definition has proper documentation."""
missing = []
has_top_description = "description" in type_def
# Always check for top-level type description first
# (except for types that are purely references or have special handling)
needs_type_description = True
# Check oneOf variants (tagged enums with variant descriptions)
if "oneOf" in type_def:
# oneOf types should have a top-level description
if not has_top_description:
missing.append(MissingDoc(type_name, "type", None, None, None))
for variant in type_def["oneOf"]:
# Case 1: Simple const variant (e.g., {"const": "Swap", "description": "..."})
variant_name = variant.get("const") or variant.get("title")
if variant_name and "description" not in variant:
missing.append(
MissingDoc(type_name, "variant", str(variant_name), None, None)
)
# Case 2: String enum inside oneOf (e.g., {"type": "string", "enum": [...]})
# These variants don't have individual descriptions in the schema
if "enum" in variant and variant.get("type") == "string":
for enum_variant in variant["enum"]:
missing.append(
MissingDoc(type_name, "variant", str(enum_variant), None, None)
)
# Case 3: Object variant with properties (e.g., CubicBezier)
if "properties" in variant and "description" not in variant:
for prop_name in variant.get("required", []):
missing.append(
MissingDoc(type_name, "variant", str(prop_name), None, None)
)
# Case 4: Object variant missing title (needed for schema UI display)
# Object variants should have a title or const for proper display in editors
if (
"properties" in variant
and "title" not in variant
and "const" not in variant
):
# Try to find a good identifier for the variant (for display only)
variant_id = _get_variant_identifier(variant)
missing.append(
MissingDoc(type_name, "variant_title", str(variant_id), None, None)
)
# Check anyOf variants - check each variant individually
elif "anyOf" in type_def:
# anyOf types should have a top-level description
if not has_top_description:
missing.append(MissingDoc(type_name, "type", None, None, None))
# Check each variant for description (skip pure $ref and null types)
for variant in type_def["anyOf"]:
# Skip null type variants (used for Option<T>)
if variant.get("type") == "null":
continue
# Skip pure $ref variants (the referenced type is checked separately)
if "$ref" in variant and len(variant) == 1:
continue
# Skip $ref variants that have a description
if "$ref" in variant and "description" in variant:
continue
# Variant with $ref but no description
if "$ref" in variant and "description" not in variant:
# Extract the type name from the $ref
ref_name = variant["$ref"].split("/")[-1]
missing.append(MissingDoc(type_name, "variant", ref_name, None, None))
# Non-ref variant without description
elif "description" not in variant and "$ref" not in variant:
# Try to identify the variant by its type or const
variant_id = variant.get("const") or variant.get("type") or "unknown"
missing.append(
MissingDoc(type_name, "variant", str(variant_id), None, None)
)
# Check for missing title on object variants in anyOf
if (
"properties" in variant
and "title" not in variant
and "const" not in variant
):
variant_id = _get_variant_identifier(variant)
missing.append(
MissingDoc(type_name, "variant_title", str(variant_id), None, None)
)
# Check simple string enums (no oneOf means no variant descriptions possible in schema)
elif "enum" in type_def:
if not has_top_description:
missing.append(MissingDoc(type_name, "type", None, None, None))
# Each enum variant needs a docstring - these can't have descriptions in simple enum format
for variant in type_def["enum"]:
missing.append(MissingDoc(type_name, "variant", str(variant), None, None))
# Check struct properties
elif "properties" in type_def:
# Structs should always have a top-level description
if not has_top_description:
missing.append(MissingDoc(type_name, "type", None, None, None))
for prop_name, prop_def in type_def["properties"].items():
if "description" not in prop_def:
missing.append(MissingDoc(type_name, "property", prop_name, None, None))
# Simple type without description (like PathBuf, Hex)
elif not has_top_description:
# Only flag if it has a concrete type (not just a $ref)
if type_def.get("type") is not None:
missing.append(MissingDoc(type_name, "type", None, None, None))
return missing
def check_top_level_properties(schema: dict, root_type_name: str) -> list[MissingDoc]:
"""Check top-level schema properties for missing descriptions."""
missing = []
properties = schema.get("properties", {})
for prop_name, prop_def in properties.items():
if "description" not in prop_def:
missing.append(
MissingDoc(root_type_name, "property", prop_name, None, None)
)
return missing
def check_schema(
schema_path: Path,
search_paths: list[Path],
project_root: Path,
display_name: str,
) -> tuple[list[MissingDoc], int]:
"""Check a single schema file and return missing docs and exit code."""
if not schema_path.exists():
print(f"Error: {schema_path.name} not found at {schema_path}")
return [], 1
with open(schema_path) as f:
schema = json.load(f)
all_missing: list[MissingDoc] = []
# Check top-level schema properties
root_type_name = schema.get("title", "Root")
all_missing.extend(check_top_level_properties(schema, root_type_name))
# Check all type definitions
for type_name, type_def in sorted(schema.get("$defs", {}).items()):
# Skip PerAnimationPrefixConfig2/3 as they're generated variants
if (
type_name.startswith("PerAnimationPrefixConfig")
and type_name != "PerAnimationPrefixConfig"
):
continue
all_missing.extend(check_type_description(type_name, type_def))
# Find Rust source locations
print(f"Scanning Rust source files for {display_name}...", file=sys.stderr)
for doc in all_missing:
doc.rust_file, doc.rust_line = find_rust_definition(
doc.type_name, doc.item_name, doc.kind, search_paths
)
if doc.rust_file:
try:
doc.rust_file = str(Path(doc.rust_file).relative_to(project_root))
except ValueError:
pass
return all_missing, 0
def print_results(all_missing: list[MissingDoc], display_name: str) -> None:
"""Print the results for a schema check."""
# Group by file
by_file: dict[str, list[MissingDoc]] = {}
external: list[MissingDoc] = []
for doc in all_missing:
if doc.rust_file:
by_file.setdefault(doc.rust_file, []).append(doc)
else:
external.append(doc)
# Print summary
print("\n" + "=" * 70)
print(f"MISSING DOCSTRINGS IN SCHEMA ({display_name})")
print("=" * 70)
type_count = sum(1 for d in all_missing if d.kind == "type")
variant_count = sum(1 for d in all_missing if d.kind == "variant")
variant_title_count = sum(1 for d in all_missing if d.kind == "variant_title")
prop_count = sum(1 for d in all_missing if d.kind == "property")
print(f"\nTotal: {len(all_missing)} missing docstrings/titles")
print(f" - {type_count} types")
print(f" - {variant_count} variants")
print(f" - {variant_title_count} variant titles")
print(f" - {prop_count} properties")
# Print by file
for rust_file in sorted(by_file.keys()):
docs = sorted(by_file[rust_file], key=lambda d: d.rust_line or 0)
print(f"\n{rust_file}:")
print("-" * len(rust_file))
for doc in docs:
print(f" {doc}")
# Print external items (types not found in source)
if external:
print(f"\nExternal/Unknown location:")
print("-" * 25)
for doc in external:
print(f" {doc}")
print("\n" + "=" * 70)
def main():
project_root = Path.cwd()
# Define schemas to check with their respective search paths
schemas = [
SchemaConfig(
schema_file="schema.json",
search_paths=["komorebi/src", "komorebi-themes/src"],
display_name="komorebi",
),
SchemaConfig(
schema_file="schema.bar.json",
search_paths=["komorebi-bar/src", "komorebi-themes/src"],
display_name="komorebi-bar",
),
]
total_missing = 0
has_errors = False
for schema_config in schemas:
schema_path = project_root / schema_config.schema_file
search_paths = [
project_root / p
for p in schema_config.search_paths
if (project_root / p).exists()
]
missing, error_code = check_schema(
schema_path,
search_paths,
project_root,
schema_config.display_name,
)
if error_code != 0:
has_errors = True
continue
print_results(missing, schema_config.display_name)
total_missing += len(missing)
# Print combined summary
if len(schemas) > 1:
print("\n" + "=" * 70)
print("COMBINED SUMMARY")
print("=" * 70)
print(f"Total missing docstrings across all schemas: {total_missing}")
print("=" * 70)
if has_errors:
return 1
return 1 if total_missing > 0 else 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,8 +1,8 @@
[graph]
targets = [
"x86_64-pc-windows-msvc",
"i686-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"x86_64-pc-windows-msvc",
"i686-pc-windows-msvc",
"aarch64-pc-windows-msvc",
]
all-features = false
no-default-features = false
@@ -12,30 +12,31 @@ feature-depth = 1
[advisories]
ignore = [
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" },
{ id = "RUSTSEC-2024-0320", reason = "not using any yaml features from this library" },
{ id = "RUSTSEC-2025-0056", reason = "only used for colour palette generation" }
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" },
{ id = "RUSTSEC-2024-0320", reason = "not using any yaml features from this library" },
{ id = "RUSTSEC-2025-0056", reason = "only used for colour palette generation" },
]
[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-2.0"
"0BSD",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"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-2.0",
]
confidence-threshold = 0.8
@@ -94,6 +95,11 @@ crate = "base16-egui-themes"
expression = "MIT"
license-files = []
[[licenses.clarify]]
crate = "win32-display-data"
expression = "0BSD"
license-files = []
[bans]
multiple-versions = "allow"
wildcards = "allow"
@@ -106,11 +112,12 @@ 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/windows-icons",
"https://github.com/LGUG2Z/win32-display-data",
"https://github.com/LGUG2Z/flavours",
"https://github.com/LGUG2Z/base16_color_scheme",
"https://github.com/LGUG2Z/whkd",
# "https://github.com/LGUG2Z/catppuccin-egui",
"https://github.com/LGUG2Z/base16-egui-themes",
"https://github.com/LGUG2Z/windows-icons",
"https://github.com/LGUG2Z/win32-display-data",
"https://github.com/LGUG2Z/flavours",
"https://github.com/LGUG2Z/base16_color_scheme",
"https://github.com/LGUG2Z/whkd",
"https://github.com/LGUG2Z/catppuccin-egui",
"https://github.com/amPerl/egui-phosphor",
]

View File

@@ -13,23 +13,25 @@
[
"ab_glyph 0.2.32 registry+https://github.com/rust-lang/crates.io-index",
"ab_glyph_rasterizer 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"accesskit 0.19.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.28.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.27.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_winit 0.27.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit 0.21.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.29.2 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_winit 0.29.2 registry+https://github.com/rust-lang/crates.io-index",
"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index",
"aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.21 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.13 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.7 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.4 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.100 registry+https://github.com/rust-lang/crates.io-index",
"approx 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.6.1 registry+https://github.com/rust-lang/crates.io-index",
"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"as-slice 0.2.1 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.5.0 registry+https://github.com/rust-lang/crates.io-index",
"backtrace 0.3.76 registry+https://github.com/rust-lang/crates.io-index",
@@ -39,63 +41,70 @@
"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.3 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.4 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 2.10.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 4.9.0 registry+https://github.com/rust-lang/crates.io-index",
"block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.24.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.40 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.51 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.3 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"chrono 0.4.42 registry+https://github.com/rust-lang/crates.io-index",
"chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.48 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.48 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.47 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.49 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"core2 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.5.1 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.5.4 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
"digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.12 registry+https://github.com/rust-lang/crates.io-index",
"dpi 0.1.2 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.20 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.10.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-winit 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"ed25519 2.2.3 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.10.0 git+https://github.com/amPerl/egui-phosphor?rev=d13688738478ecd12b426e3e74c59d6577a85b59",
"egui-winit 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.33.3 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.32.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.33.3 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.26 registry+https://github.com/rust-lang/crates.io-index",
"find-msvc-tools 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.4 registry+https://github.com/rust-lang/crates.io-index",
"find-msvc-tools 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.5 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.2 registry+https://github.com/rust-lang/crates.io-index",
"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
@@ -109,56 +118,55 @@
"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.3 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.2 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.14.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.3 registry+https://github.com/rust-lang/crates.io-index",
"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"glob 0.3.3 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.3 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.6.0 registry+https://github.com/rust-lang/crates.io-index",
"half 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.16.1 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.3.1 registry+https://github.com/rust-lang/crates.io-index",
"http 1.4.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.64 registry+https://github.com/rust-lang/crates.io-index",
"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.8 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.9 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"imgref 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.11.4 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.13.0 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"iri-string 0.7.8 registry+https://github.com/rust-lang/crates.io-index",
"iri-string 0.7.10 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",
"is_terminal_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.1.22 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.176 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.2+1.9.1 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.22 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.180 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.23 registry+https://github.com/rust-lang/crates.io-index",
"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index",
"litrs 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.28 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.29 registry+https://github.com/rust-lang/crates.io-index",
"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
@@ -169,11 +177,11 @@
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"moxcms 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"moxcms 0.7.11 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",
"ntapi 0.4.2 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",
@@ -184,14 +192,16 @@
"num-rational 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"object 0.32.2 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.21.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
"once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"owned_ttf_parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"pastey 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.2 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",
@@ -200,14 +210,14 @@
"png 0.18.0 registry+https://github.com/rust-lang/crates.io-index",
"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.101 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.105 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"pxfm 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.28 registry+https://github.com/rust-lang/crates.io-index",
"pxfm 0.1.27 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.41 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.43 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.9.2 registry+https://github.com/rust-lang/crates.io-index",
@@ -221,50 +231,54 @@
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.11.3 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.11 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.23 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.12.2 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.13 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.8 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index",
"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.26 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.13.2 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.22 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"semver 1.0.27 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.228 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.145 registry+https://github.com/rust-lang/crates.io-index",
"serde_json 1.0.149 registry+https://github.com/rust-lang/crates.io-index",
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.15.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.15.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.15.1 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.6.0 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"socket2 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"stacker 0.1.22 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-hyperlinks 3.2.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 1.0.109 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.106 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.114 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.23.0 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.24.0 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.3 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.17 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.17 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.44 registry+https://github.com/rust-lang/crates.io-index",
@@ -272,25 +286,26 @@
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index",
"typenum 1.19.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"tzdb 0.7.2 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"tzdb 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.22 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.2 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.37.0 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.7 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.8 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.5 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index",
"win-msgbox 0.2.1 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",
@@ -320,7 +335,7 @@
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.5.3 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.6.1 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.4 registry+https://github.com/rust-lang/crates.io-index",
@@ -351,11 +366,20 @@
"winit 0.30.12 registry+https://github.com/rust-lang/crates.io-index",
"wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index",
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.27 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
"zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.5.0 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.21 registry+https://github.com/rust-lang/crates.io-index"
"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.5.8 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Apache-2.0 WITH LLVM-exception",
[
"ar_archive_writer 0.2.0 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -368,10 +392,11 @@
[
"BSD-2-Clause",
[
"av1-grain 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"rav1e 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"av1-grain 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"rav1e 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"v_frame 0.3.9 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.27 registry+https://github.com/rust-lang/crates.io-index"
"zerocopy 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.8.33 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -382,12 +407,15 @@
"avif-serialize 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index",
"curve25519-dalek 4.1.3 registry+https://github.com/rust-lang/crates.io-index",
"ed25519-dalek 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index",
"exr 1.73.0 registry+https://github.com/rust-lang/crates.io-index",
"exr 1.74.0 registry+https://github.com/rust-lang/crates.io-index",
"lebe 0.5.3 registry+https://github.com/rust-lang/crates.io-index",
"moxcms 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"pxfm 0.1.24 registry+https://github.com/rust-lang/crates.io-index",
"ravif 0.11.20 registry+https://github.com/rust-lang/crates.io-index"
"moxcms 0.7.11 registry+https://github.com/rust-lang/crates.io-index",
"pxfm 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"ravif 0.12.0 registry+https://github.com/rust-lang/crates.io-index",
"subtle 2.6.1 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -395,7 +423,7 @@
[
"clipboard-win 5.4.1 registry+https://github.com/rust-lang/crates.io-index",
"error-code 3.3.2 registry+https://github.com/rust-lang/crates.io-index",
"ryu 1.0.20 registry+https://github.com/rust-lang/crates.io-index"
"ryu 1.0.22 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -413,32 +441,35 @@
[
"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"libloading 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"starship-battery 0.10.2 registry+https://github.com/rust-lang/crates.io-index"
"starship-battery 0.10.3 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"MIT",
[
"accesskit 0.19.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.28.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.27.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit 0.21.1 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_consumer 0.31.0 registry+https://github.com/rust-lang/crates.io-index",
"accesskit_windows 0.29.2 registry+https://github.com/rust-lang/crates.io-index",
"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index",
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"aho-corasick 1.1.4 registry+https://github.com/rust-lang/crates.io-index",
"aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"aligned-vec 0.6.4 registry+https://github.com/rust-lang/crates.io-index",
"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index",
"anstream 0.6.21 registry+https://github.com/rust-lang/crates.io-index",
"anstyle 1.0.13 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-parse 0.2.7 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.4 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.10 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index",
"anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index",
"anyhow 1.0.100 registry+https://github.com/rust-lang/crates.io-index",
"arboard 3.6.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",
"as-slice 0.2.1 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.5.0 registry+https://github.com/rust-lang/crates.io-index",
"av-scenechange 0.14.1 registry+https://github.com/rust-lang/crates.io-index",
"backtrace 0.3.76 registry+https://github.com/rust-lang/crates.io-index",
"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index",
"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme",
@@ -446,71 +477,78 @@
"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"bit_field 0.10.3 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.4 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"bitflags 2.10.0 registry+https://github.com/rust-lang/crates.io-index",
"bitstream-io 4.9.0 registry+https://github.com/rust-lang/crates.io-index",
"block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index",
"brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index",
"built 0.7.7 registry+https://github.com/rust-lang/crates.io-index",
"built 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck 1.24.0 registry+https://github.com/rust-lang/crates.io-index",
"bytemuck_derive 1.10.2 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.1 registry+https://github.com/rust-lang/crates.io-index",
"bytes 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"calm_io 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"calmio_filters 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"catppuccin-egui 5.6.0 registry+https://github.com/rust-lang/crates.io-index",
"cc 1.2.40 registry+https://github.com/rust-lang/crates.io-index",
"catppuccin-egui 5.6.0 git+https://github.com/LGUG2Z/catppuccin-egui?rev=b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a",
"cc 1.2.51 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.3 registry+https://github.com/rust-lang/crates.io-index",
"cfg-if 1.0.4 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.42 registry+https://github.com/rust-lang/crates.io-index",
"chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index",
"chumsky 0.9.3 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.48 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.48 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.47 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.5 registry+https://github.com/rust-lang/crates.io-index",
"clap 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
"clap_builder 4.5.54 registry+https://github.com/rust-lang/crates.io-index",
"clap_derive 4.5.49 registry+https://github.com/rust-lang/crates.io-index",
"clap_lex 0.7.6 registry+https://github.com/rust-lang/crates.io-index",
"color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index",
"color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"color-thief 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"color_quant 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"colorchoice 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"core2 0.4.0 registry+https://github.com/rust-lang/crates.io-index",
"cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index",
"crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index",
"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.5.0 registry+https://github.com/rust-lang/crates.io-index",
"crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"ctrlc 3.5.1 registry+https://github.com/rust-lang/crates.io-index",
"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"darling 0.21.3 registry+https://github.com/rust-lang/crates.io-index",
"darling_core 0.21.3 registry+https://github.com/rust-lang/crates.io-index",
"darling_macro 0.21.3 registry+https://github.com/rust-lang/crates.io-index",
"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.5.4 registry+https://github.com/rust-lang/crates.io-index",
"deranged 0.5.5 registry+https://github.com/rust-lang/crates.io-index",
"digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index",
"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.11 registry+https://github.com/rust-lang/crates.io-index",
"document-features 0.2.12 registry+https://github.com/rust-lang/crates.io-index",
"dpi 0.1.2 registry+https://github.com/rust-lang/crates.io-index",
"dyn-clone 1.0.20 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.10.0 registry+https://github.com/rust-lang/crates.io-index",
"egui-winit 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"ecolor 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"ed25519 2.2.3 registry+https://github.com/rust-lang/crates.io-index",
"eframe 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui-phosphor 0.10.0 git+https://github.com/amPerl/egui-phosphor?rev=d13688738478ecd12b426e3e74c59d6577a85b59",
"egui-winit 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_extras 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"egui_glow 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"emath 0.33.3 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.32.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.32.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index",
"equator 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"equator-macro 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index",
@@ -520,8 +558,8 @@
"fax_derive 0.2.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.26 registry+https://github.com/rust-lang/crates.io-index",
"find-msvc-tools 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.4 registry+https://github.com/rust-lang/crates.io-index",
"find-msvc-tools 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"flate2 1.1.5 registry+https://github.com/rust-lang/crates.io-index",
"flavours 0.7.2 git+https://github.com/LGUG2Z/flavours",
"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index",
"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -536,63 +574,64 @@
"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",
"generic-array 0.14.7 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.2.16 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.3 registry+https://github.com/rust-lang/crates.io-index",
"getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.13.3 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.2 registry+https://github.com/rust-lang/crates.io-index",
"gif 0.14.1 registry+https://github.com/rust-lang/crates.io-index",
"git2 0.20.3 registry+https://github.com/rust-lang/crates.io-index",
"glob 0.3.3 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.12 registry+https://github.com/rust-lang/crates.io-index",
"half 2.6.0 registry+https://github.com/rust-lang/crates.io-index",
"h2 0.4.13 registry+https://github.com/rust-lang/crates.io-index",
"half 2.7.1 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.16.0 registry+https://github.com/rust-lang/crates.io-index",
"hashbrown 0.16.1 registry+https://github.com/rust-lang/crates.io-index",
"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index",
"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index",
"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"http 1.3.1 registry+https://github.com/rust-lang/crates.io-index",
"http 1.4.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.3 registry+https://github.com/rust-lang/crates.io-index",
"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper 1.7.0 registry+https://github.com/rust-lang/crates.io-index",
"hyper 1.8.1 registry+https://github.com/rust-lang/crates.io-index",
"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index",
"hyper-util 0.1.17 registry+https://github.com/rust-lang/crates.io-index",
"hyper-util 0.1.19 registry+https://github.com/rust-lang/crates.io-index",
"iana-time-zone 0.1.64 registry+https://github.com/rust-lang/crates.io-index",
"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"idna_adapter 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"image 0.23.14 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.8 registry+https://github.com/rust-lang/crates.io-index",
"image 0.25.9 registry+https://github.com/rust-lang/crates.io-index",
"image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.11.4 registry+https://github.com/rust-lang/crates.io-index",
"indexmap 2.13.0 registry+https://github.com/rust-lang/crates.io-index",
"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index",
"iri-string 0.7.8 registry+https://github.com/rust-lang/crates.io-index",
"iri-string 0.7.10 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",
"is_terminal_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"itoa 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index",
"jpeg-decoder 0.1.22 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.176 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.2+1.9.1 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.22 registry+https://github.com/rust-lang/crates.io-index",
"libc 0.2.180 registry+https://github.com/rust-lang/crates.io-index",
"libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index",
"libz-sys 1.1.23 registry+https://github.com/rust-lang/crates.io-index",
"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index",
"litrs 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index",
"lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.28 registry+https://github.com/rust-lang/crates.io-index",
"log 0.4.29 registry+https://github.com/rust-lang/crates.io-index",
"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index",
"loop9 0.1.5 registry+https://github.com/rust-lang/crates.io-index",
"mac-addr 0.3.0 registry+https://github.com/rust-lang/crates.io-index",
"matchers 0.2.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.6 registry+https://github.com/rust-lang/crates.io-index",
@@ -603,18 +642,19 @@
"miniz_oxide 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index",
"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"mio 1.0.4 registry+https://github.com/rust-lang/crates.io-index",
"mio 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"miow 0.6.1 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.36.0 registry+https://github.com/rust-lang/crates.io-index",
"netdev 0.40.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",
"nom 8.0.0 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.50.1 registry+https://github.com/rust-lang/crates.io-index",
"ntapi 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"nu-ansi-term 0.50.3 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",
@@ -625,15 +665,18 @@
"num-rational 0.3.2 registry+https://github.com/rust-lang/crates.io-index",
"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index",
"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index",
"object 0.32.2 registry+https://github.com/rust-lang/crates.io-index",
"once_cell 1.21.3 registry+https://github.com/rust-lang/crates.io-index",
"once_cell_polyfill 1.70.1 registry+https://github.com/rust-lang/crates.io-index",
"os_info 3.12.0 registry+https://github.com/rust-lang/crates.io-index",
"once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index",
"open 5.3.3 registry+https://github.com/rust-lang/crates.io-index",
"os_info 3.14.0 registry+https://github.com/rust-lang/crates.io-index",
"owo-colors 4.2.3 registry+https://github.com/rust-lang/crates.io-index",
"palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index",
"parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index",
"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index",
"pastey 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index",
"phf 0.11.3 registry+https://github.com/rust-lang/crates.io-index",
"phf 0.12.1 registry+https://github.com/rust-lang/crates.io-index",
@@ -651,13 +694,13 @@
"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.21 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.101 registry+https://github.com/rust-lang/crates.io-index",
"proc-macro2 1.0.105 registry+https://github.com/rust-lang/crates.io-index",
"profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.27 registry+https://github.com/rust-lang/crates.io-index",
"psm 0.1.28 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.41 registry+https://github.com/rust-lang/crates.io-index",
"quote 1.0.43 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index",
"rand 0.9.2 registry+https://github.com/rust-lang/crates.io-index",
@@ -672,98 +715,102 @@
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"rayon 1.11.0 registry+https://github.com/rust-lang/crates.io-index",
"rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.11.3 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.11 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.6 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.23 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index",
"regex 1.12.2 registry+https://github.com/rust-lang/crates.io-index",
"regex-automata 0.4.13 registry+https://github.com/rust-lang/crates.io-index",
"regex-syntax 0.8.8 registry+https://github.com/rust-lang/crates.io-index",
"reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index",
"rgb 0.8.52 registry+https://github.com/rust-lang/crates.io-index",
"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc-demangle 0.1.26 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.12.0 registry+https://github.com/rust-lang/crates.io-index",
"rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index",
"rustls-pki-types 1.13.2 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.28 registry+https://github.com/rust-lang/crates.io-index",
"schemars 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
"schemars_derive 0.8.22 registry+https://github.com/rust-lang/crates.io-index",
"schemars 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"schemars_derive 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"scoped_threadpool 0.1.9 registry+https://github.com/rust-lang/crates.io-index",
"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"semver 1.0.27 registry+https://github.com/rust-lang/crates.io-index",
"serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index",
"serde_derive 1.0.228 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.145 registry+https://github.com/rust-lang/crates.io-index",
"serde_json 1.0.149 registry+https://github.com/rust-lang/crates.io-index",
"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.15.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.15.0 registry+https://github.com/rust-lang/crates.io-index",
"serde_with 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_with_macros 3.16.1 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index",
"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.4.0 registry+https://github.com/rust-lang/crates.io-index",
"sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index",
"shadow-rs 1.5.0 registry+https://github.com/rust-lang/crates.io-index",
"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.0 registry+https://github.com/rust-lang/crates.io-index",
"shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index",
"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index",
"simd-adler32 0.3.7 registry+https://github.com/rust-lang/crates.io-index",
"signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index",
"simd-adler32 0.3.8 registry+https://github.com/rust-lang/crates.io-index",
"simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index",
"siphasher 1.0.1 registry+https://github.com/rust-lang/crates.io-index",
"slab 0.4.11 registry+https://github.com/rust-lang/crates.io-index",
"smallvec 1.15.1 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.6.0 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.0 registry+https://github.com/rust-lang/crates.io-index",
"socket2 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index",
"stacker 0.1.22 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.2 registry+https://github.com/rust-lang/crates.io-index",
"strum_macros 0.27.2 registry+https://github.com/rust-lang/crates.io-index",
"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.106 registry+https://github.com/rust-lang/crates.io-index",
"syn 2.0.114 registry+https://github.com/rust-lang/crates.io-index",
"synstructure 0.13.2 registry+https://github.com/rust-lang/crates.io-index",
"sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index",
"sysinfo 0.37.2 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.23.0 registry+https://github.com/rust-lang/crates.io-index",
"tempfile 3.24.0 registry+https://github.com/rust-lang/crates.io-index",
"terminal_size 0.4.3 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.17 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.17 registry+https://github.com/rust-lang/crates.io-index",
"thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index",
"tiff 0.10.3 registry+https://github.com/rust-lang/crates.io-index",
"tiff 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"time 0.3.44 registry+https://github.com/rust-lang/crates.io-index",
"time-core 0.1.6 registry+https://github.com/rust-lang/crates.io-index",
"tokio 1.47.1 registry+https://github.com/rust-lang/crates.io-index",
"tokio 1.49.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.16 registry+https://github.com/rust-lang/crates.io-index",
"tokio-util 0.7.18 registry+https://github.com/rust-lang/crates.io-index",
"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index",
"tower 0.5.2 registry+https://github.com/rust-lang/crates.io-index",
"tower-http 0.6.6 registry+https://github.com/rust-lang/crates.io-index",
"tower-http 0.6.8 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.30 registry+https://github.com/rust-lang/crates.io-index",
"tracing-core 0.1.34 registry+https://github.com/rust-lang/crates.io-index",
"tracing 0.1.44 registry+https://github.com/rust-lang/crates.io-index",
"tracing-appender 0.2.4 registry+https://github.com/rust-lang/crates.io-index",
"tracing-attributes 0.1.31 registry+https://github.com/rust-lang/crates.io-index",
"tracing-core 0.1.36 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.20 registry+https://github.com/rust-lang/crates.io-index",
"tracing-subscriber 0.3.22 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.19.0 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.0 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"tz-rs 0.7.1 registry+https://github.com/rust-lang/crates.io-index",
"tzdb_data 0.2.3 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.19 registry+https://github.com/rust-lang/crates.io-index",
"unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.22 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.2 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.37.0 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.7 registry+https://github.com/rust-lang/crates.io-index",
"url 2.5.8 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",
@@ -771,9 +818,10 @@
"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.5 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.10 registry+https://github.com/rust-lang/crates.io-index",
"webbrowser 1.0.6 registry+https://github.com/rust-lang/crates.io-index",
"weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index",
"which 8.0.0 registry+https://github.com/rust-lang/crates.io-index",
"win-msgbox 0.2.1 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.11 registry+https://github.com/rust-lang/crates.io-index",
"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index",
@@ -806,7 +854,7 @@
"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index",
"windows-numerics 0.3.1 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.5.3 registry+https://github.com/rust-lang/crates.io-index",
"windows-registry 0.6.1 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.4 registry+https://github.com/rust-lang/crates.io-index",
@@ -838,20 +886,25 @@
"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.2 registry+https://github.com/rust-lang/crates.io-index",
"xml-rs 0.8.27 registry+https://github.com/rust-lang/crates.io-index",
"xml-rs 0.8.28 registry+https://github.com/rust-lang/crates.io-index",
"y4m 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.27 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
"zerocopy-derive 0.8.33 registry+https://github.com/rust-lang/crates.io-index",
"zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index",
"zmij 1.0.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.5.0 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.21 registry+https://github.com/rust-lang/crates.io-index"
"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.5.8 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.2 registry+https://github.com/rust-lang/crates.io-index"
"tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
@@ -864,43 +917,43 @@
[
"OFL-1.1",
[
"epaint_default_fonts 0.32.3 registry+https://github.com/rust-lang/crates.io-index"
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Ubuntu-font-1.0",
[
"epaint_default_fonts 0.32.3 registry+https://github.com/rust-lang/crates.io-index"
"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Unicode-3.0",
[
"icu_collections 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_locale_core 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer_data 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties_data 2.0.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider 2.0.0 registry+https://github.com/rust-lang/crates.io-index",
"litemap 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"potential_utf 0.1.3 registry+https://github.com/rust-lang/crates.io-index",
"tinystr 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.19 registry+https://github.com/rust-lang/crates.io-index",
"writeable 0.6.1 registry+https://github.com/rust-lang/crates.io-index",
"yoke 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"yoke-derive 0.8.0 registry+https://github.com/rust-lang/crates.io-index",
"icu_collections 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_locale_core 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_normalizer_data 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"icu_properties_data 2.1.2 registry+https://github.com/rust-lang/crates.io-index",
"icu_provider 2.1.1 registry+https://github.com/rust-lang/crates.io-index",
"litemap 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"potential_utf 0.1.4 registry+https://github.com/rust-lang/crates.io-index",
"tinystr 0.8.2 registry+https://github.com/rust-lang/crates.io-index",
"unicode-ident 1.0.22 registry+https://github.com/rust-lang/crates.io-index",
"writeable 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"yoke 0.8.1 registry+https://github.com/rust-lang/crates.io-index",
"yoke-derive 0.8.1 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",
"zerotrie 0.2.2 registry+https://github.com/rust-lang/crates.io-index",
"zerovec 0.11.4 registry+https://github.com/rust-lang/crates.io-index",
"zerovec-derive 0.11.1 registry+https://github.com/rust-lang/crates.io-index"
"zerotrie 0.2.3 registry+https://github.com/rust-lang/crates.io-index",
"zerovec 0.11.5 registry+https://github.com/rust-lang/crates.io-index",
"zerovec-derive 0.11.2 registry+https://github.com/rust-lang/crates.io-index"
]
],
[
"Unlicense",
[
"aho-corasick 1.1.3 registry+https://github.com/rust-lang/crates.io-index",
"aho-corasick 1.1.4 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.6 registry+https://github.com/rust-lang/crates.io-index",
@@ -924,8 +977,10 @@
"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index",
"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index",
"zune-core 0.5.0 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.21 registry+https://github.com/rust-lang/crates.io-index"
"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index",
"zune-jpeg 0.5.8 registry+https://github.com/rust-lang/crates.io-index"
]
]
]

View File

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

View File

@@ -0,0 +1,12 @@
# cancel-preselect
```
Cancel a workspace preselect set by the preselect-direction command, if one exists
Usage: komorebic.exe cancel-preselect
Options:
-h, --help
Print help
```

View File

@@ -12,9 +12,6 @@ Options:
--whkd
Enable autostart of whkd
--ahk
Enable autostart of ahk
--bar
Enable autostart of komorebi-bar

View File

@@ -9,9 +9,6 @@ Options:
--whkd
Kill whkd if it is running as a background process
--ahk
Kill ahk if it is running as a background process
--bar
Kill komorebi-bar if it is running as a background process

16
docs/cli/license.md Normal file
View File

@@ -0,0 +1,16 @@
# license
```
Specify an email associated with an Individual Commercial Use License
Usage: komorebic.exe license <EMAIL>
Arguments:
<EMAIL>
Email address associated with an Individual Commercial Use License
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,16 @@
# preselect-direction
```
Preselect the specified direction for the next window to be spawned on supported layouts
Usage: komorebic.exe preselect-direction <OPERATION_DIRECTION>
Arguments:
<OPERATION_DIRECTION>
[possible values: left, right, up, down]
Options:
-h, --help
Print help
```

12
docs/cli/promote-swap.md Normal file
View File

@@ -0,0 +1,12 @@
# promote-swap
```
Promote the focused window to the largest tile by swapping container indices with the largest tile
Usage: komorebic.exe promote-swap
Options:
-h, --help
Print help
```

View File

@@ -1,7 +1,7 @@
# promote
```
Promote the focused window to the top of the tree
Promote the focused window to the largest tile via container removal and re-insertion
Usage: komorebic.exe promote

View File

@@ -18,9 +18,6 @@ Options:
--whkd
Start whkd in a background process
--ahk
Start autohotkey configuration file
--bar
Start komorebi-bar in a background process

View File

@@ -9,9 +9,6 @@ Options:
--whkd
Stop whkd if it is running as a background process
--ahk
Stop ahk if it is running as a background process
--bar
Stop komorebi-bar if it is running as a background process

View File

@@ -16,6 +16,19 @@ the example files have been downloaded. For most new users this will be in the
komorebic quickstart
```
## Corporate Devices Enrolled in MDM
If you are using `komorebi` on a corporate device enrolled in mobile device
management, you will receive a pop-up when you run `komorebic start` reminding
you that the [Komorebi License](https://github.com/LGUG2Z/komorebi-license) does
not permit any kind of commercial use.
You can remove this pop-up by running `komorebic license <email>` with the email
associated with your Individual Commercial Use License. A single HTTP request
will be sent with the given email address to verify license validity.
## Starting komorebi
With the example configurations downloaded, you can now start `komorebi`,
`komorebi-bar` and `whkd`.
@@ -23,6 +36,9 @@ With the example configurations downloaded, you can now start `komorebi`,
komorebic start --whkd --bar
```
If you don't want to use the komorebi status bar, you can remove the `--bar` option
from the above command.
## komorebi.json
The example window manager configuration sets some sane defaults and provides

View File

@@ -1,6 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.39/schema.bar.json",
"monitor": 0,
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.bar.json",
"font_family": "JetBrains Mono",
"theme": {
"palette": "Base16",
@@ -48,8 +47,8 @@
{
"Network": {
"enable": true,
"show_total_data_transmitted": true,
"show_network_activity": true
"show_activity": true,
"show_total_activity": true
}
},
{

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.39/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.40/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",

179
flake.lock generated Normal file
View File

@@ -0,0 +1,179 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1766774972,
"narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1765835352,
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1765911976,
"narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1766870016,
"narHash": "sha256-fHmxAesa6XNqnIkcS6+nIHuEmgd/iZSP/VXxweiEuQw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5c2bc52fb9f8c264ed6c93bd20afa2ff5e763dce",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1765674936,
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-parts": "flake-parts",
"git-hooks-nix": "git-hooks-nix",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1766890375,
"narHash": "sha256-0Zi7ChAtjq/efwQYmp7kOJPcSt6ya9ynSUe6ppgZhsQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "91e1f7a0017065360f447622d11b7ce6ed04772f",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1766000401,
"narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

360
flake.nix Normal file
View File

@@ -0,0 +1,360 @@
{
description = "komorebi for Windows";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
crane.url = "github:ipetkov/crane";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
git-hooks-nix.url = "github:cachix/git-hooks.nix";
git-hooks-nix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
inputs@{
self,
nixpkgs,
flake-parts,
crane,
rust-overlay,
...
}:
let
windowsSdkVersion = "10.0.26100";
windowsCrtVersion = "14.44.17.14";
mkWindowsSdk =
pkgs:
pkgs.stdenvNoCC.mkDerivation {
name = "windows-sdk-${windowsSdkVersion}-crt-${windowsCrtVersion}";
nativeBuildInputs = [ pkgs.xwin ];
outputHashAlgo = "sha256";
outputHashMode = "recursive";
outputHash = "sha256-6cLS5q1BDRpLPScfmmKpTTEHUzsgKTKD1+mKvGX9Deo=";
buildCommand = ''
export HOME=$(mktemp -d)
xwin --accept-license \
--sdk-version ${windowsSdkVersion} \
--crt-version ${windowsCrtVersion} \
splat --output $out
'';
};
mkMsvcEnv =
{ pkgs, windowsSdk }:
let
clangVersion = pkgs.lib.versions.major pkgs.llvmPackages.clang.version;
in
{
# linker for the windows target
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER = "lld-link";
# c/c++ compiler
CC_x86_64_pc_windows_msvc = "clang-cl";
CXX_x86_64_pc_windows_msvc = "clang-cl";
AR_x86_64_pc_windows_msvc = "llvm-lib";
# IMPORTANT: libclang include path MUST come first to avoid header conflicts
CFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [
"--target=x86_64-pc-windows-msvc"
"-Wno-unused-command-line-argument"
"-fuse-ld=lld-link"
"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include"
"/imsvc${windowsSdk}/crt/include"
"/imsvc${windowsSdk}/sdk/include/ucrt"
"/imsvc${windowsSdk}/sdk/include/um"
"/imsvc${windowsSdk}/sdk/include/shared"
];
CXXFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [
"--target=x86_64-pc-windows-msvc"
"-Wno-unused-command-line-argument"
"-fuse-ld=lld-link"
"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include"
"/imsvc${windowsSdk}/crt/include"
"/imsvc${windowsSdk}/sdk/include/ucrt"
"/imsvc${windowsSdk}/sdk/include/um"
"/imsvc${windowsSdk}/sdk/include/shared"
];
# target-specific rust flags with linker flavor and library search paths
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS = builtins.concatStringsSep " " [
"-Clinker-flavor=lld-link"
"-Lnative=${windowsSdk}/crt/lib/x86_64"
"-Lnative=${windowsSdk}/sdk/lib/um/x86_64"
"-Lnative=${windowsSdk}/sdk/lib/ucrt/x86_64"
];
# cargo target
CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc";
};
mkKomorebiPackages =
{ pkgs, windowsSdk }:
let
# toolchain with windows msvc target
toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
targets = [ "x86_64-pc-windows-msvc" ];
};
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
version = "0.1.0";
msvcEnv = mkMsvcEnv { inherit pkgs windowsSdk; };
src = pkgs.lib.cleanSourceWith {
src = ./.;
filter =
path: type:
(craneLib.filterCargoSources path type)
|| (pkgs.lib.hasInfix "/docs/" path)
|| (builtins.match ".*/docs/.*" path != null);
};
commonArgs = {
inherit src version;
strictDeps = true;
COMMIT_HASH = self.rev or (pkgs.lib.removeSuffix "-dirty" self.dirtyRev);
# build inputs for cross-compilation
nativeBuildInputs = [
pkgs.llvmPackages.clang-unwrapped
pkgs.llvmPackages.lld
pkgs.llvmPackages.llvm
];
# cross-compilation environment
inherit (msvcEnv)
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER
CC_x86_64_pc_windows_msvc
CXX_x86_64_pc_windows_msvc
AR_x86_64_pc_windows_msvc
CFLAGS_x86_64_pc_windows_msvc
CXXFLAGS_x86_64_pc_windows_msvc
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS
CARGO_BUILD_TARGET
;
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
individualCrateArgs = commonArgs // {
inherit cargoArtifacts;
doCheck = false;
doDoc = false;
};
fullBuild = craneLib.buildPackage (
individualCrateArgs
// {
pname = "komorebi-workspace";
}
);
extractBinary =
binaryName:
pkgs.runCommand "komorebi-${binaryName}"
{
meta = fullBuild.meta // { };
}
''
mkdir -p $out/bin
cp ${fullBuild}/bin/${binaryName}.exe $out/bin/
'';
in
{
inherit
craneLib
src
individualCrateArgs
fullBuild
msvcEnv
;
komorebi = extractBinary "komorebi";
komorebic = extractBinary "komorebic";
komorebic-no-console = extractBinary "komorebic-no-console";
komorebi-bar = extractBinary "komorebi-bar";
komorebi-gui = extractBinary "komorebi-gui";
komorebi-shortcuts = extractBinary "komorebi-shortcuts";
};
mkPkgs =
system:
import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
in
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [
"aarch64-darwin"
"x86_64-linux"
"aarch64-linux"
];
imports = [
inputs.treefmt-nix.flakeModule
inputs.git-hooks-nix.flakeModule
];
perSystem =
{ config, system, ... }:
let
pkgs = mkPkgs system;
windowsSdk = mkWindowsSdk pkgs;
build = mkKomorebiPackages { inherit pkgs windowsSdk; };
# toolchain with windows target and nightly rustfmt
rustToolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
targets = [ "x86_64-pc-windows-msvc" ];
};
nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt;
rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin {
name = "rust-toolchain-with-nightly-rustfmt";
paths = [
nightlyRustfmt
rustToolchain
];
};
nightlyToolchain = pkgs.rust-bin.nightly.latest.default.override {
targets = [ "x86_64-pc-windows-msvc" ];
};
cargo-udeps = pkgs.writeShellScriptBin "cargo-udeps" ''
export PATH="${nightlyToolchain}/bin:$PATH"
exec ${pkgs.cargo-udeps}/bin/cargo-udeps "$@"
'';
in
{
treefmt = {
projectRootFile = "flake.nix";
programs = {
deadnix.enable = true;
just.enable = true;
nixfmt.enable = true;
taplo.enable = true;
rustfmt = {
enable = true;
package = pkgs.rust-bin.nightly.latest.rustfmt;
};
};
};
checks = {
komorebi-workspace-clippy = build.craneLib.cargoClippy (
build.individualCrateArgs
// {
cargoClippyExtraArgs = "--all-targets -- -D warnings";
}
);
komorebi-workspace-fmt = build.craneLib.cargoFmt {
inherit (build) src;
};
komorebi-workspace-toml-fmt = build.craneLib.taploFmt {
src = pkgs.lib.sources.sourceFilesBySuffices build.src [ ".toml" ];
};
komorebi-workspace-deny = build.craneLib.cargoDeny {
inherit (build) src;
};
komorebi-workspace-nextest = build.craneLib.cargoNextest build.individualCrateArgs;
};
packages = {
inherit (build)
komorebi
komorebic
komorebic-no-console
komorebi-bar
komorebi-gui
komorebi-shortcuts
;
inherit windowsSdk;
komorebi-full = build.fullBuild;
default = build.fullBuild;
};
apps = {
komorebi = {
type = "app";
program = "${build.komorebi}/bin/komorebi.exe";
};
komorebic = {
type = "app";
program = "${build.komorebic}/bin/komorebic.exe";
};
komorebic-no-console = {
type = "app";
program = "${build.komorebic-no-console}/bin/komorebic-no-console.exe";
};
komorebi-bar = {
type = "app";
program = "${build.komorebi-bar}/bin/komorebi-bar.exe";
};
komorebi-gui = {
type = "app";
program = "${build.komorebi-gui}/bin/komorebi-gui.exe";
};
komorebi-shortcuts = {
type = "app";
program = "${build.komorebi-shortcuts}/bin/komorebi-shortcuts.exe";
};
default = {
type = "app";
program = "${build.fullBuild}/bin/komorebi.exe";
};
};
devShells.default = pkgs.mkShell {
name = "komorebi";
RUST_BACKTRACE = "full";
# cross-compilation environment
inherit (build.msvcEnv)
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER
CC_x86_64_pc_windows_msvc
CXX_x86_64_pc_windows_msvc
AR_x86_64_pc_windows_msvc
CFLAGS_x86_64_pc_windows_msvc
CXXFLAGS_x86_64_pc_windows_msvc
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS
CARGO_BUILD_TARGET
;
packages = [
rustToolchainWithNightlyRustfmt
cargo-udeps
# cross-compilation tooling
pkgs.llvmPackages.clang-unwrapped # provides clang-cl
pkgs.llvmPackages.lld # provides lld-link
pkgs.llvmPackages.llvm # provides llvm-lib
pkgs.cargo-deny
pkgs.cargo-nextest
pkgs.cargo-outdated
pkgs.jq
pkgs.just
pkgs.prettier
];
};
pre-commit = {
check.enable = true;
settings.hooks.treefmt = {
enable = true;
package = config.treefmt.build.wrapper;
pass_filenames = false;
};
};
};
};
}

View File

@@ -15,6 +15,9 @@ fmt:
prettier --write .github/FUNDING.yml
prettier --write .github/workflows/windows.yaml
fix:
cargo clippy --fix --allow-dirty
install-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just install-target $_ }
@@ -72,22 +75,38 @@ trace target $RUST_LOG="trace":
deadlock $RUST_LOG="trace":
cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection
docgen:
cargo run --package komorebic -- docgen
Get-ChildItem -Path "docs/cli" -Recurse -File | ForEach-Object { (Get-Content $_.FullName) -replace 'Usage: ', 'Usage: komorebic.exe ' | Set-Content $_.FullName }
docgen starlight:
rm {{ starlight }}/src/data/cli/windows/*.md
cargo run --package komorebic -- docgen --output {{ starlight }}/src/data/cli/windows
schemars-docgen ./schema.json --output {{ starlight }}/src/content/docs/reference/komorebi-windows.mdx --title "komorebi.json (Windows)" --description "komorebi for Windows configuration schema reference"
schemars-docgen ./schema.bar.json --output {{ starlight }}/src/content/docs/reference/bar-windows.mdx --title "komorebi.bar.json (Windows)" --description "komorebi-bar for Windows configuration schema reference"
jsonschema:
cargo run --package komorebic -- static-config-schema > schema.json
cargo run --package komorebic -- application-specific-configuration-schema > schema.asc.json
cargo run --package komorebi-bar -- --schema > schema.bar.json
# this part is run in a nix shell because python is a nightmare
schemagen:
rm -rf static-config-docs bar-config-docs
mkdir -p static-config-docs bar-config-docs
generate-schema-doc ./schema.json --config template_name=js_offline --config minify=false ./static-config-docs/
generate-schema-doc ./schema.bar.json --config template_name=js_offline --config minify=false ./bar-config-docs/
mv ./bar-config-docs/schema.bar.html ./bar-config-docs/schema.html
version := `cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "komorebi") | .version'`
schemapub:
rm -Force komorebi-schema
mkdir -Force komorebi-schema
cp schema.json komorebi-schema/komorebi.{{ version }}.schema.json
cp schema.bar.json komorebi-schema/komorebi.bar.{{ version }}.schema.json
npx wrangler pages deploy --project-name komorebi --branch main .\komorebi-schema
depcheck:
cargo outdated --depth 2
cargo +nightly udeps --quiet
deps:
cargo update
just depgen
depgen:
cargo deny check
cargo deny list --format json | jq 'del(.unlicensed)' > dependencies.json
procdump:
cargo build --bin komorebi
.\procdump.exe -ma -e -x . .\target\debug\komorebi.exe

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-bar"
version = "0.1.39"
version = "0.1.40"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,12 +17,12 @@ crossbeam-channel = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
eframe = { workspace = true }
egui-phosphor = "0.10"
egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" }
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
lazy_static = { workspace = true }
netdev = "0.36"
netdev = "0.40"
num = "0.4"
num-derive = "0.4"
num-traits = "0.2"
@@ -44,4 +44,8 @@ windows-icons-fallback = { package = "windows-icons", git = "https://github.com/
[features]
default = ["schemars"]
schemars = ["dep:schemars", "komorebi-client/default", "komorebi-themes/default"]
schemars = [
"dep:schemars",
"komorebi-client/default",
"komorebi-themes/default",
]

View File

@@ -55,6 +55,9 @@ use komorebi_client::SocketMessage;
use komorebi_client::VirtualDesktopNotification;
use komorebi_themes::Base16Wrapper;
use komorebi_themes::Catppuccin;
use komorebi_themes::KomobarThemeBase16;
use komorebi_themes::KomobarThemeCatppuccin;
use komorebi_themes::KomobarThemeCustom;
use komorebi_themes::catppuccin_egui;
use lazy_static::lazy_static;
use parking_lot::Mutex;
@@ -183,12 +186,12 @@ pub fn apply_theme(
render_config: Rc<RefCell<RenderConfig>>,
) {
let (auto_select_fill, auto_select_text) = match theme {
KomobarTheme::Catppuccin {
KomobarTheme::Catppuccin(KomobarThemeCatppuccin {
name: catppuccin,
accent: catppuccin_value,
auto_select_fill: catppuccin_auto_select_fill,
auto_select_text: catppuccin_auto_select_text,
} => {
}) => {
match catppuccin {
Catppuccin::Frappe => {
catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);
@@ -253,12 +256,12 @@ pub fn apply_theme(
catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())),
)
}
KomobarTheme::Base16 {
KomobarTheme::Base16(KomobarThemeBase16 {
name: base16,
accent: base16_value,
auto_select_fill: base16_auto_select_fill,
auto_select_text: base16_auto_select_text,
} => {
}) => {
ctx.set_style(base16.style());
let base16_value = base16_value.unwrap_or_default();
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
@@ -276,12 +279,12 @@ pub fn apply_theme(
base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))),
)
}
KomobarTheme::Custom {
KomobarTheme::Custom(KomobarThemeCustom {
colours,
accent: base16_value,
auto_select_fill: base16_auto_select_fill,
auto_select_text: base16_auto_select_text,
} => {
}) => {
let background = colours.background();
ctx.set_style(colours.style());
let base16_value = base16_value.unwrap_or_default();
@@ -450,10 +453,11 @@ impl Komobar {
self.right_widgets = right_widgets;
let (usr_monitor_index, config_work_area_offset) = match &self.config.monitor {
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
(monitor_config.index, monitor_config.work_area_offset)
}
MonitorConfigOrIndex::Index(idx) => (*idx, None),
Some(MonitorConfigOrIndex::Index(idx)) => (*idx, None),
None => (0, None),
};
let mapped_info = self.monitor_info.as_ref().map(|info| {
@@ -863,9 +867,13 @@ impl eframe::App for Komobar {
Ok(KomorebiEvent::Notification(notification)) => {
let state = &notification.state;
let usr_monitor_index = match &self.config.monitor {
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
MonitorConfigOrIndex::Index(idx) => *idx,
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
monitor_config.index
}
Some(MonitorConfigOrIndex::Index(idx)) => *idx,
None => 0,
};
let monitor_index = state.monitor_usr_idx_map.get(&usr_monitor_index).copied();
self.monitor_index = monitor_index;
let mut should_apply_config = false;

View File

@@ -5,7 +5,6 @@ use crate::widgets::widget::WidgetConfig;
use eframe::egui::Pos2;
use eframe::egui::TextBuffer;
use eframe::egui::Vec2;
use komorebi_client::KomorebiTheme;
use komorebi_client::PathExt;
use komorebi_client::Rect;
use komorebi_client::SocketMessage;
@@ -16,9 +15,10 @@ use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.bar.json` configuration file reference for `v0.1.39`
/// The `komorebi.bar.json` configuration file reference for `v0.1.40`
pub struct KomobarConfig {
/// Bar height (default: 50)
/// Bar height
#[cfg_attr(feature = "schemars", schemars(extend("default" = 50.0)))]
pub height: Option<f32>,
/// Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or
/// vertical definition which can each take a single value for a symmetric padding or two
@@ -76,20 +76,26 @@ pub struct KomobarConfig {
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)
pub frame: Option<FrameConfig>,
/// The monitor index or the full monitor options
pub monitor: MonitorConfigOrIndex,
#[cfg_attr(feature = "schemars", schemars(extend("default" = MonitorConfigOrIndex::Index(0))))]
pub monitor: Option<MonitorConfigOrIndex>,
/// Font family
pub font_family: Option<String>,
/// Font size (default: 12.5)
/// Font size
#[cfg_attr(feature = "schemars", schemars(extend("default" = 12.5)))]
pub font_size: Option<f32>,
/// Scale of the icons relative to the font_size [[1.0-2.0]]. (default: 1.4)
/// Scale of the icons relative to the font_size [[1.0-2.0]]
#[cfg_attr(feature = "schemars", schemars(extend("default" = 1.4)))]
pub icon_scale: Option<f32>,
/// Max label width before text truncation (default: 400.0)
/// Max label width before text truncation
#[cfg_attr(feature = "schemars", schemars(extend("default" = 400.0)))]
pub max_label_width: Option<f32>,
/// Theme
pub theme: Option<KomobarTheme>,
/// Alpha value for the color transparency [[0-255]] (default: 200)
/// Alpha value for the color transparency [[0-255]]
#[cfg_attr(feature = "schemars", schemars(extend("default" = 200)))]
pub transparency_alpha: Option<u8>,
/// Spacing between widgets (default: 10.0)
/// Spacing between widgets
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10.0)))]
pub widget_spacing: Option<f32>,
/// Visual grouping for widgets
pub grouping: Option<Grouping>,
@@ -145,6 +151,7 @@ impl KomobarConfig {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Position configuration
pub struct PositionConfig {
/// The desired starting position of the bar (0,0 = top left of the screen)
#[serde(alias = "position")]
@@ -156,6 +163,7 @@ pub struct PositionConfig {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Frame configuration
pub struct FrameConfig {
/// Margin inside the painted frame
pub inner_margin: Position,
@@ -164,6 +172,7 @@ pub struct FrameConfig {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Monitor configuration or monitor index
pub enum MonitorConfigOrIndex {
/// The monitor index where you want the bar to show
Index(usize),
@@ -173,6 +182,7 @@ pub enum MonitorConfigOrIndex {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Monitor configuration
pub struct MonitorConfig {
/// Komorebi monitor index of the monitor on which to render the bar
pub index: usize,
@@ -190,9 +200,13 @@ pub type Margin = SpacingKind;
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
// `GroupedSpacingConfig` with both `vertical` and `horizontal` set to `None` ignoring the
// individual values.
/// Spacing kind
pub enum SpacingKind {
/// Spacing applied to all sides
All(f32),
/// Individual spacing applied to each side
Individual(IndividualSpacingConfig),
/// Grouped vertical and horizontal spacing
Grouped(GroupedSpacingConfig),
}
@@ -237,25 +251,36 @@ impl SpacingKind {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Grouped vertical and horizontal spacing
pub struct GroupedSpacingConfig {
/// Vertical grouped spacing
pub vertical: Option<GroupedSpacingOptions>,
/// Horizontal grouped spacing
pub horizontal: Option<GroupedSpacingOptions>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Grouped spacing options
pub enum GroupedSpacingOptions {
/// Symmetrical grouped spacing
Symmetrical(f32),
/// Split grouped spacing
Split(f32, f32),
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Individual spacing configuration
pub struct IndividualSpacingConfig {
/// Spacing for the top
pub top: f32,
/// Spacing for the bottom
pub bottom: f32,
/// Spacing for the left
pub left: f32,
/// Spacing for the right
pub right: f32,
}
@@ -335,6 +360,7 @@ pub fn get_individual_spacing(
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Mouse message
pub enum MouseMessage {
/// Send a message to the komorebi client.
/// By default, a batch of messages are sent in the following order:
@@ -379,10 +405,13 @@ pub enum MouseMessage {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi socket mouse message
pub struct KomorebiMouseMessage {
/// Send the FocusMonitorAtCursor message (default:true)
/// Send the FocusMonitorAtCursor message
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub focus_monitor_at_cursor: Option<bool>,
/// Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message (default:true)
/// Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub ignore_mouse_follows_focus: Option<bool>,
/// The message to send to the komorebi client
pub message: komorebi_client::SocketMessage,
@@ -390,6 +419,7 @@ pub struct KomorebiMouseMessage {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Mouse configuration
pub struct MouseConfig {
/// Command to send on primary/left double button click
pub on_primary_double_click: Option<MouseMessage>,
@@ -402,14 +432,16 @@ pub struct MouseConfig {
/// Command to send on extra2/forward button click
pub on_extra2_click: Option<MouseMessage>,
/// Defines how many points a user needs to scroll vertically to make a "tick" on a mouse/touchpad/touchscreen (default: 30)
/// Defines how many points a user needs to scroll vertically to make a "tick" on a mouse/touchpad/touchscreen
#[cfg_attr(feature = "schemars", schemars(extend("default" = 30.0)))]
pub vertical_scroll_threshold: Option<f32>,
/// Command to send on scrolling up (every tick)
pub on_scroll_up: Option<MouseMessage>,
/// Command to send on scrolling down (every tick)
pub on_scroll_down: Option<MouseMessage>,
/// Defines how many points a user needs to scroll horizontally to make a "tick" on a mouse/touchpad/touchscreen (default: 30)
/// Defines how many points a user needs to scroll horizontally to make a "tick" on a mouse/touchpad/touchscreen
#[cfg_attr(feature = "schemars", schemars(extend("default" = 30.0)))]
pub horizontal_scroll_threshold: Option<f32>,
/// Command to send on scrolling left (every tick)
pub on_scroll_left: Option<MouseMessage>,
@@ -495,6 +527,7 @@ impl KomobarConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Position
pub struct Position {
/// X coordinate
pub x: f32,
@@ -520,71 +553,11 @@ impl From<Position> for Pos2 {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")]
pub enum KomobarTheme {
/// A theme from catppuccin-egui
Catppuccin {
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
name: komorebi_themes::Catppuccin,
accent: Option<komorebi_themes::CatppuccinValue>,
auto_select_fill: Option<komorebi_themes::CatppuccinValue>,
auto_select_text: Option<komorebi_themes::CatppuccinValue>,
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
name: komorebi_themes::Base16,
accent: Option<komorebi_themes::Base16Value>,
auto_select_fill: Option<komorebi_themes::Base16Value>,
auto_select_text: Option<komorebi_themes::Base16Value>,
},
/// A custom Base16 theme
Custom {
/// Colours of the custom Base16 theme palette
colours: Box<komorebi_themes::Base16ColourPalette>,
accent: Option<komorebi_themes::Base16Value>,
auto_select_fill: Option<komorebi_themes::Base16Value>,
auto_select_text: Option<komorebi_themes::Base16Value>,
},
}
impl From<KomorebiTheme> for KomobarTheme {
fn from(value: KomorebiTheme) -> Self {
match value {
KomorebiTheme::Catppuccin {
name, bar_accent, ..
} => Self::Catppuccin {
name,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
},
KomorebiTheme::Base16 {
name, bar_accent, ..
} => Self::Base16 {
name,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
},
KomorebiTheme::Custom {
colours,
bar_accent,
..
} => Self::Custom {
colours,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
},
}
}
}
pub use komorebi_themes::KomobarTheme;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Label prefix
pub enum LabelPrefix {
/// Show no prefix
None,
@@ -598,6 +571,7 @@ pub enum LabelPrefix {
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Display format
pub enum DisplayFormat {
/// Show only icon
Icon,
@@ -612,9 +586,10 @@ pub enum DisplayFormat {
}
macro_rules! extend_enum {
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
($(#[$type_meta:meta])* $existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
$(#[$type_meta])*
pub enum $new_enum {
// Add new variants
$(
@@ -635,7 +610,9 @@ macro_rules! extend_enum {
};
}
extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
extend_enum!(
/// Workspaces display format
DisplayFormat, WorkspacesDisplayFormat, {
/// Show all icons only
AllIcons,
/// Show both all icons and text

View File

@@ -103,7 +103,7 @@ fn process_hwnd() -> Option<isize> {
}
pub enum KomorebiEvent {
Notification(komorebi_client::Notification),
Notification(Box<komorebi_client::Notification>),
Reconnect,
}
@@ -114,15 +114,8 @@ fn main() -> color_eyre::Result<()> {
#[cfg(feature = "schemars")]
if opts.schema {
let settings = schemars::r#gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let generator = settings.into_generator();
let socket_message = generator.into_root_schema_for::<KomobarConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
let bar_config = schemars::schema_for!(KomobarConfig);
let schema = serde_json::to_string_pretty(&bar_config)?;
println!("{schema}");
std::process::exit(0);
@@ -223,11 +216,13 @@ fn main() -> color_eyre::Result<()> {
)?)?;
let (usr_monitor_index, work_area_offset) = match &config.monitor {
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {
(monitor_config.index, monitor_config.work_area_offset)
}
MonitorConfigOrIndex::Index(idx) => (*idx, None),
Some(MonitorConfigOrIndex::Index(idx)) => (*idx, None),
None => (0, None),
};
let monitor_index = state
.monitor_usr_idx_map
.get(&usr_monitor_index)
@@ -381,7 +376,7 @@ fn main() -> color_eyre::Result<()> {
Ok(notification) => {
tracing::debug!("received notification from komorebi");
if let Err(error) = tx_gui.send(KomorebiEvent::Notification(notification)) {
if let Err(error) = tx_gui.send(KomorebiEvent::Notification(Box::new(notification))) {
tracing::error!("could not send komorebi notification update to gui thread: {error}")
}

View File

@@ -27,18 +27,24 @@ static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "kind")]
/// Grouping
pub enum Grouping {
/// No grouping is applied
#[cfg_attr(feature = "schemars", schemars(title = "None"))]
None,
/// Widgets are grouped as a whole
#[cfg_attr(feature = "schemars", schemars(title = "Bar"))]
Bar(GroupingConfig),
/// Widgets are grouped by alignment
#[cfg_attr(feature = "schemars", schemars(title = "Alignment"))]
Alignment(GroupingConfig),
/// Widgets are grouped individually
#[cfg_attr(feature = "schemars", schemars(title = "Widget"))]
Widget(GroupingConfig),
}
#[derive(Clone)]
/// Render configuration
pub struct RenderConfig {
/// Komorebi monitor index of the monitor on which to render the bar
pub monitor_idx: usize,
@@ -93,8 +99,9 @@ impl RenderExt for &KomobarConfig {
icon_font_id.size *= icon_scale.unwrap_or(1.4).clamp(1.0, 2.0);
let monitor_idx = match &self.monitor {
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
MonitorConfigOrIndex::Index(idx) => *idx,
Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => monitor_config.index,
Some(MonitorConfigOrIndex::Index(idx)) => *idx,
None => 0,
};
// check if any of the alignments have a komorebi widget with the workspace set to show all icons
@@ -356,6 +363,7 @@ impl RenderConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Grouping configuration
pub struct GroupingConfig {
/// Styles for the grouping
pub style: Option<GroupingStyle>,
@@ -367,7 +375,9 @@ pub struct GroupingConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Grouping Style
pub enum GroupingStyle {
/// Default
#[serde(alias = "CtByte")]
Default,
/// A shadow is added under the default group. (blur: 4, offset: x-1 y-1, spread: 3)
@@ -389,8 +399,9 @@ pub enum GroupingStyle {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Rounding configuration
pub enum RoundingConfig {
/// All 4 corners are the same
/// All 4 corners are the same
Same(f32),
/// All 4 corners are custom. Order: NW, NE, SW, SE
Individual([f32; 4]),

View File

@@ -34,6 +34,7 @@ const MIN_LAUNCH_INTERVAL: Duration = Duration::from_millis(800);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Applications widget configuration
pub struct ApplicationsConfig {
/// Enables or disables the applications widget.
pub enable: bool,
@@ -44,13 +45,14 @@ pub struct ApplicationsConfig {
pub spacing: Option<f32>,
/// Default display format for all applications (optional).
/// Could be overridden per application. Defaults to `Icon`.
pub display: Option<DisplayFormat>,
pub display: Option<ApplicationsDisplayFormat>,
/// List of configured applications to display.
pub items: Vec<AppConfig>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Application button configuration
pub struct AppConfig {
/// Whether to enable this application button (optional).
/// Inherits from the global `Applications` setting if omitted.
@@ -67,12 +69,13 @@ pub struct AppConfig {
/// Command to execute (e.g. path to the application or shell command).
pub command: String,
/// Display format for this application button (optional). Overrides global format if set.
pub display: Option<DisplayFormat>,
pub display: Option<ApplicationsDisplayFormat>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DisplayFormat {
/// Applications widget display format
pub enum ApplicationsDisplayFormat {
/// Show only the application icon.
#[default]
Icon,
@@ -168,7 +171,7 @@ pub struct App {
/// Command to execute when the application is launched.
pub command: UserCommand,
/// Display format (icon, text, or both).
pub display: DisplayFormat,
pub display: ApplicationsDisplayFormat,
/// Whether to show the launch command on hover.
pub show_command_on_hover: bool,
}
@@ -183,9 +186,9 @@ impl App {
ui.spacing_mut().item_spacing = Vec2::splat(4.0);
match self.display {
DisplayFormat::Icon => self.draw_icon(ctx, ui, icon_config),
DisplayFormat::Text => self.draw_name(ui),
DisplayFormat::IconAndText => {
ApplicationsDisplayFormat::Icon => self.draw_icon(ctx, ui, icon_config),
ApplicationsDisplayFormat::Text => self.draw_name(ui),
ApplicationsDisplayFormat::IconAndText => {
self.draw_icon(ctx, ui, icon_config);
self.draw_name(ui);
}

View File

@@ -19,12 +19,14 @@ use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Battery widget configuration
pub struct BatteryConfig {
/// Enable the Battery widget
pub enable: bool,
/// Hide the widget if the battery is at full charge
pub hide_on_full_charge: Option<bool>,
/// Data refresh interval (default: 10 seconds)
/// Data refresh interval in seconds
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,

View File

@@ -18,10 +18,12 @@ use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// CPU widget configuration
pub struct CpuConfig {
/// Enable the Cpu widget
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
/// Data refresh interval in seconds
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,

View File

@@ -62,6 +62,7 @@ impl CustomModifiers {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Date widget configuration
pub struct DateConfig {
/// Enable the Date widget
pub enable: bool,
@@ -104,6 +105,7 @@ impl From<DateConfig> for Date {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Date widget format
pub enum DateFormat {
/// Month/Date/Year format (09/08/24)
MonthDateYear,
@@ -114,8 +116,10 @@ pub enum DateFormat {
/// Day Date Month Year format (8 September 2024)
DayDateMonthYear,
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
Custom(String),
/// Custom format with modifiers
#[cfg_attr(feature = "schemars", schemars(title = "CustomModifiers"))]
CustomModifiers(CustomModifiers),
}

View File

@@ -21,15 +21,16 @@ use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyboardLayout;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
const ERROR_TEXT: &str = "Error";
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Keyboard widget configuration
pub struct KeyboardConfig {
/// Enable the Input widget
pub enable: bool,
/// Data refresh interval (default: 1 second)
/// Data refresh interval
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
@@ -37,9 +38,7 @@ pub struct KeyboardConfig {
impl From<KeyboardConfig> for Keyboard {
fn from(value: KeyboardConfig) -> Self {
let data_refresh_interval = value
.data_refresh_interval
.unwrap_or(DEFAULT_DATA_REFRESH_INTERVAL);
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
Self {
enable: value.enable,

View File

@@ -49,6 +49,7 @@ use std::sync::atomic::Ordering;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget configuration
pub struct KomorebiConfig {
/// Configure the Workspaces widget
pub workspaces: Option<KomorebiWorkspacesConfig>,
@@ -67,6 +68,7 @@ pub struct KomorebiConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget workspaces configuration
pub struct KomorebiWorkspacesConfig {
/// Enable the Komorebi Workspaces widget
pub enable: bool,
@@ -78,6 +80,7 @@ pub struct KomorebiWorkspacesConfig {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget layout configuration
pub struct KomorebiLayoutConfig {
/// Enable the Komorebi Layout widget
pub enable: bool,
@@ -89,6 +92,7 @@ pub struct KomorebiLayoutConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget workspace layer configuration
pub struct KomorebiWorkspaceLayerConfig {
/// Enable the Komorebi Workspace Layer widget
pub enable: bool,
@@ -100,10 +104,12 @@ pub struct KomorebiWorkspaceLayerConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget focused container configuration
pub struct KomorebiFocusedContainerConfig {
/// Enable the Komorebi Focused Container widget
pub enable: bool,
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused container)
/// DEPRECATED: use `display` instead (Show the icon of the currently focused container)
#[deprecated(note = "Use `display` instead")]
pub show_icon: Option<bool>,
/// Display format of the currently focused container
pub display: Option<DisplayFormat>,
@@ -111,6 +117,7 @@ pub struct KomorebiFocusedContainerConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget locked container configuration
pub struct KomorebiLockedContainerConfig {
/// Enable the Komorebi Locked Container widget
pub enable: bool,
@@ -122,6 +129,7 @@ pub struct KomorebiLockedContainerConfig {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Komorebi widget configuration switcher configuration
pub struct KomorebiConfigurationSwitcherConfig {
/// Enable the Komorebi Configurations widget
pub enable: bool,
@@ -506,7 +514,9 @@ impl FocusedContainerBar {
if !value.enable {
return None;
}
// Handle legacy setting - convert show_icon to display format
#[allow(deprecated)]
let format = value
.display
.unwrap_or(if value.show_icon.unwrap_or(false) {

View File

@@ -26,11 +26,18 @@ use std::fmt::Formatter;
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Komorebi layout kind
pub enum KomorebiLayout {
/// Predefined layout
#[cfg_attr(feature = "schemars", schemars(title = "Default"))]
Default(komorebi_client::DefaultLayout),
/// Monocle mode
Monocle,
/// Floating layer
Floating,
/// Paused
Paused,
/// Custom layout
Custom,
}

View File

@@ -17,6 +17,7 @@ use windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Media widget configuration
pub struct MediaConfig {
/// Enable the Media widget
pub enable: bool,

View File

@@ -18,10 +18,12 @@ use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Memory widget configuration
pub struct MemoryConfig {
/// Enable the Memory widget
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
/// Data refresh interval in seconds
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,

View File

@@ -15,12 +15,18 @@ use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use std::process::Command;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use sysinfo::Networks;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Network widget configuration
pub struct NetworkConfig {
/// Enable the Network widget
pub enable: bool,
@@ -35,7 +41,8 @@ pub struct NetworkConfig {
/// Characters to reserve for received and transmitted activity
#[serde(alias = "network_activity_fill_characters")]
pub activity_left_padding: Option<usize>,
/// Data refresh interval (default: 10 seconds)
/// Data refresh interval in seconds
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
@@ -45,6 +52,7 @@ pub struct NetworkConfig {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Network select configuration
pub struct NetworkSelectConfig {
/// Select the total received data when it's over this value
pub total_received_over: Option<u64>,
@@ -59,28 +67,34 @@ pub struct NetworkSelectConfig {
impl From<NetworkConfig> for Network {
fn from(value: NetworkConfig) -> Self {
let default_refresh_interval = 10;
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
let data_refresh_interval = value
.data_refresh_interval
.unwrap_or(default_refresh_interval);
Self {
enable: value.enable,
show_total_activity: value.show_total_activity,
show_activity: value.show_activity,
show_default_interface: value.show_default_interface.unwrap_or(true),
networks_network_activity: Networks::new_with_refreshed_list(),
default_interface: String::new(),
networks_network_activity: Arc::new(Mutex::new(Networks::new_with_refreshed_list())),
default_interface: Arc::new(Mutex::new(String::new())),
interface_generation: Arc::new(AtomicU64::new(0)),
default_refresh_interval,
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
auto_select: value.auto_select,
activity_left_padding: value.activity_left_padding.unwrap_or_default(),
last_updated_default_interface: Instant::now()
last_update_request_default_interface: Instant::now()
.checked_sub(Duration::from_secs(default_refresh_interval))
.unwrap(),
last_state_total_activity: vec![],
last_state_activity: vec![],
last_updated_network_activity: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
last_state_total_activity: Arc::new(Mutex::new(vec![])),
last_state_activity: Arc::new(Mutex::new(vec![])),
last_update_request_network_activity: Arc::new(Mutex::new(
Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
)),
activity_generation: Arc::new(AtomicU64::new(0)),
}
}
}
@@ -90,63 +104,82 @@ pub struct Network {
pub show_total_activity: bool,
pub show_activity: bool,
pub show_default_interface: bool,
networks_network_activity: Networks,
networks_network_activity: Arc<Mutex<Networks>>,
default_refresh_interval: u64,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
auto_select: Option<NetworkSelectConfig>,
default_interface: String,
last_updated_default_interface: Instant,
last_state_total_activity: Vec<NetworkReading>,
last_state_activity: Vec<NetworkReading>,
last_updated_network_activity: Instant,
default_interface: Arc<Mutex<String>>,
interface_generation: Arc<AtomicU64>,
last_update_request_default_interface: Instant,
activity_generation: Arc<AtomicU64>,
last_state_activity: Arc<Mutex<Vec<NetworkReading>>>,
last_state_total_activity: Arc<Mutex<Vec<NetworkReading>>>,
last_update_request_network_activity: Arc<Mutex<Instant>>,
activity_left_padding: usize,
}
impl Network {
fn default_interface(&mut self) {
let now = Instant::now();
fn update_default_interface_async(&mut self) {
let gen_ = self.interface_generation.fetch_add(1, Ordering::SeqCst) + 1;
let gen_arc = Arc::clone(&self.interface_generation);
let iface_arc = Arc::clone(&self.default_interface);
if now.duration_since(self.last_updated_default_interface)
> Duration::from_secs(self.default_refresh_interval)
{
thread::spawn(move || {
if let Ok(interface) = netdev::get_default_interface()
&& let Some(friendly_name) = &interface.friendly_name
{
self.default_interface.clone_from(friendly_name);
// Only update if this is the latest request
if gen_ == gen_arc.load(Ordering::SeqCst)
&& let Ok(mut iface) = iface_arc.lock()
{
*iface = friendly_name.clone();
}
}
self.last_updated_default_interface = now;
}
});
}
fn network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
let mut activity = self.last_state_activity.clone();
let mut total_activity = self.last_state_total_activity.clone();
fn default_interface(&mut self) -> String {
let current = self.default_interface.lock().unwrap().clone();
let now = Instant::now();
if now.duration_since(self.last_updated_network_activity)
> Duration::from_secs(self.data_refresh_interval)
if now.duration_since(self.last_update_request_default_interface)
> Duration::from_secs(self.default_refresh_interval)
{
activity.clear();
total_activity.clear();
self.last_update_request_default_interface = now;
self.update_default_interface_async();
}
current
}
fn update_network_activity_async(&mut self) {
let gen_ = self.activity_generation.fetch_add(1, Ordering::SeqCst) + 1;
let gen_arc = Arc::clone(&self.activity_generation);
let activity_arc = Arc::clone(&self.last_state_activity);
let total_activity_arc = Arc::clone(&self.last_state_total_activity);
let data_refresh_interval = self.data_refresh_interval;
let show_activity = self.show_activity;
let show_total_activity = self.show_total_activity;
let networks_network_activity_arc = Arc::clone(&self.networks_network_activity);
thread::spawn(move || {
let mut activity = Vec::new();
let mut total_activity = Vec::new();
if let Ok(interface) = netdev::get_default_interface()
&& let Some(friendly_name) = &interface.friendly_name
&& let Ok(mut networks) = networks_network_activity_arc.lock()
{
self.default_interface.clone_from(friendly_name);
networks.refresh(true);
self.networks_network_activity.refresh(true);
for (interface_name, data) in &self.networks_network_activity {
for (interface_name, data) in &*networks {
if friendly_name.eq(interface_name) {
if self.show_activity {
if show_activity {
let received =
Self::to_pretty_bytes(data.received(), self.data_refresh_interval);
let transmitted = Self::to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval,
);
Network::to_pretty_bytes(data.received(), data_refresh_interval);
let transmitted =
Network::to_pretty_bytes(data.transmitted(), data_refresh_interval);
activity.push(NetworkReading::new(
NetworkReadingFormat::Speed,
@@ -155,26 +188,55 @@ impl Network {
));
}
if self.show_total_activity {
let total_received = Self::to_pretty_bytes(data.total_received(), 1);
if show_total_activity {
let total_received = Network::to_pretty_bytes(data.total_received(), 1);
let total_transmitted =
Self::to_pretty_bytes(data.total_transmitted(), 1);
Network::to_pretty_bytes(data.total_transmitted(), 1);
total_activity.push(NetworkReading::new(
NetworkReadingFormat::Total,
ReadingValue::from(total_received),
ReadingValue::from(total_transmitted),
))
));
}
}
}
}
self.last_state_activity.clone_from(&activity);
self.last_state_total_activity.clone_from(&total_activity);
self.last_updated_network_activity = now;
// Only update if this is the latest request
if gen_ == gen_arc.load(Ordering::SeqCst) {
if let Ok(mut act) = activity_arc.lock() {
*act = activity;
}
if let Ok(mut tot) = total_activity_arc.lock() {
*tot = total_activity;
}
}
});
}
fn network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
let now = Instant::now();
let should_update = {
let last_update_request = self.last_update_request_network_activity.lock().unwrap();
now.duration_since(*last_update_request)
> Duration::from_secs(self.data_refresh_interval)
};
if should_update {
{
let mut last_updated = self.last_update_request_network_activity.lock().unwrap();
*last_updated = now;
}
self.update_network_activity_async();
}
self.get_network_activity()
}
fn get_network_activity(&self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
let activity = self.last_state_activity.lock().unwrap().clone();
let total_activity = self.last_state_total_activity.lock().unwrap().clone();
(activity, total_activity)
}
@@ -445,9 +507,9 @@ impl BarWidget for Network {
}
if self.show_default_interface {
self.default_interface();
let mut self_default_interface = self.default_interface();
if !self.default_interface.is_empty() {
if !self_default_interface.is_empty() {
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
@@ -461,11 +523,11 @@ impl BarWidget for Network {
);
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
self.default_interface.insert_str(0, "NET: ");
self_default_interface.insert_str(0, "NET: ");
}
layout_job.append(
&self.default_interface,
&self_default_interface,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),

View File

@@ -18,16 +18,20 @@ use sysinfo::Disks;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Storage widget configuration
pub struct StorageConfig {
/// Enable the Storage widget
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
/// Data refresh interval in seconds
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
/// Show disks that are read only. (default: false)
/// Show disks that are read only
#[cfg_attr(feature = "schemars", schemars(extend("default" = false)))]
pub show_read_only_disks: Option<bool>,
/// Show removable disks. (default: true)
/// Show removable disks
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub show_removable_disks: Option<bool>,
/// Select when the current percentage is over this value [[1-100]]
pub auto_select_over: Option<u8>,

View File

@@ -72,6 +72,7 @@ lazy_static! {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Time widget configuration
pub struct TimeConfig {
/// Enable the Time widget
pub enable: bool,
@@ -92,7 +93,7 @@ pub struct TimeConfig {
///}
/// ```
pub timezone: Option<String>,
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00. (default: false)
/// Change the icon depending on the time. The default icon is used between 8:30 and 12:00
pub changing_icon: Option<bool>,
}
@@ -119,6 +120,7 @@ impl From<TimeConfig> for Time {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Time format
pub enum TimeFormat {
/// Twelve-hour format (with seconds)
TwelveHour,
@@ -133,6 +135,7 @@ pub enum TimeFormat {
/// Twenty-four-hour format displayed as a binary clock with rectangles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)
BinaryRectangle,
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
Custom(String),
}

View File

@@ -16,10 +16,12 @@ use std::time::Instant;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Update widget configuration
pub struct UpdateConfig {
/// Enable the Update widget
pub enable: bool,
/// Data refresh interval (default: 12 hours)
/// Data refresh interval in hours
#[cfg_attr(feature = "schemars", schemars(extend("default" = 12)))]
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,

View File

@@ -34,18 +34,43 @@ pub trait BarWidget {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Widget configuration
pub enum WidgetConfig {
/// Applications widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Applications"))]
Applications(ApplicationsConfig),
/// Battery widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Battery"))]
Battery(BatteryConfig),
/// CPU widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Cpu"))]
Cpu(CpuConfig),
/// Date widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Date"))]
Date(DateConfig),
/// Keyboard widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Keyboard"))]
Keyboard(KeyboardConfig),
/// Komorebi widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Komorebi"))]
Komorebi(KomorebiConfig),
/// Media widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Media"))]
Media(MediaConfig),
/// Memory widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Memory"))]
Memory(MemoryConfig),
/// Network widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Network"))]
Network(NetworkConfig),
/// Storage widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Storage"))]
Storage(StorageConfig),
/// Time widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Time"))]
Time(TimeConfig),
/// Update widget configuration
#[cfg_attr(feature = "schemars", schemars(title = "Update"))]
Update(UpdateConfig),
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-client"
version = "0.1.39"
version = "0.1.40"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -7,21 +7,26 @@ pub use komorebi::AspectRatio;
pub use komorebi::BorderColours;
pub use komorebi::Colour;
pub use komorebi::CrossBoundaryBehaviour;
pub use komorebi::GlobalState;
pub use komorebi::GridLayoutOptions;
pub use komorebi::KomorebiTheme;
pub use komorebi::LayoutOptions;
pub use komorebi::MonitorConfig;
pub use komorebi::Notification;
pub use komorebi::NotificationEvent;
pub use komorebi::Placement;
pub use komorebi::PredefinedAspectRatio;
pub use komorebi::Rgb;
pub use komorebi::RuleDebug;
pub use komorebi::ScrollingLayoutOptions;
pub use komorebi::StackbarConfig;
pub use komorebi::State;
pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig;
pub use komorebi::ThemeOptions;
pub use komorebi::VirtualDesktopNotification;
pub use komorebi::Wallpaper;
pub use komorebi::WindowContainerBehaviour;
pub use komorebi::WindowHandlingBehaviour;
pub use komorebi::WindowsApi;
pub use komorebi::WorkspaceConfig;
pub use komorebi::animation::PerAnimationPrefixConfig;
@@ -68,6 +73,9 @@ pub use komorebi::core::replace_env_in_path;
pub use komorebi::monitor::Monitor;
pub use komorebi::monitor_reconciliator::MonitorNotification;
pub use komorebi::ring::Ring;
pub use komorebi::splash;
pub use komorebi::state::GlobalState;
pub use komorebi::state::State;
pub use komorebi::win32_display_data;
pub use komorebi::window::Window;
pub use komorebi::window_manager_event::WindowManagerEvent;

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-gui"
version = "0.1.39"
version = "0.1.40"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,4 +13,4 @@ egui_extras = { workspace = true }
random_word = { version = "0.5", features = ["en"] }
serde_json = { workspace = true }
windows-core = { workspace = true }
windows = { workspace = true }
windows = { workspace = true }

View File

@@ -247,7 +247,7 @@ impl eframe::App for KomorebiGui {
egui::CentralPanel::default().show(ctx, |ui| {
ctx.set_pixels_per_point(2.0);
egui::ScrollArea::vertical().show(ui, |ui| {
ui.set_width(ctx.screen_rect().width());
ui.set_width(ctx.content_rect().width());
ui.collapsing("Debugging", |ui| {
ui.collapsing("Window Rules", |ui| {
let window = Window::from(self.debug_hwnd);

View File

@@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2024"
[dependencies]
whkd-parser = { git = "https://github.com/LGUG2Z/whkd", rev = "v0.2.9" }
whkd-core = { git = "https://github.com/LGUG2Z/whkd", rev = "v0.2.9" }
whkd-parser = { git = "https://github.com/LGUG2Z/whkd", rev = "v0.2.10" }
whkd-core = { git = "https://github.com/LGUG2Z/whkd", rev = "v0.2.10" }
eframe = { workspace = true }
dirs = { workspace = true }
dirs = { workspace = true }

View File

@@ -1,11 +1,14 @@
[package]
name = "komorebi-themes"
version = "0.1.39"
version = "0.1.40"
edition = "2024"
[dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "c9008bd5cfa288c926e9ea3aa18c92073f9281bd" }
catppuccin-egui = { version = "5", default-features = false, features = ["egui32"] }
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "b9e26b31f7a0e7ed239b14e5317e95d1bdc544bd" }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui32"] }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a", default-features = false, features = [
"egui33",
] }
eframe = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }

View File

@@ -1,12 +1,8 @@
use hex_color::HexColor;
#[cfg(feature = "schemars")]
use schemars::Schema;
#[cfg(feature = "schemars")]
use schemars::SchemaGenerator;
#[cfg(feature = "schemars")]
use schemars::schema::InstanceType;
#[cfg(feature = "schemars")]
use schemars::schema::Schema;
#[cfg(feature = "schemars")]
use schemars::schema::SchemaObject;
use crate::Color32;
use serde::Deserialize;
@@ -15,6 +11,7 @@ use serde::Serialize;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Colour representation
pub enum Colour {
/// Colour represented as RGB
Rgb(Rgb),
@@ -56,22 +53,22 @@ impl From<Colour> for Color32 {
}
}
/// Colour represented as a Hex string
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub struct Hex(pub HexColor);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Hex {
fn schema_name() -> String {
String::from("Hex")
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("Hex")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject {
instance_type: Some(InstanceType::String.into()),
format: Some("color-hex".to_string()),
..Default::default()
}
.into()
schemars::json_schema!({
"type": "string",
"format": "color-hex",
"description": "Colour represented as a Hex string"
})
}
}
@@ -86,6 +83,7 @@ impl From<Colour> for u32 {
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Colour represented as RGB
pub struct Rgb {
/// Red
pub r: u32,

View File

@@ -12,9 +12,12 @@ use serde::Serialize;
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Theme variant
pub enum ThemeVariant {
#[default]
/// Dark variant
Dark,
/// Light variant
Light,
}

View File

@@ -28,18 +28,19 @@ use serde_variant::to_variant_name;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "type")]
/// Theme
pub enum Theme {
/// A theme from catppuccin-egui
/// Theme from catppuccin-egui
Catppuccin {
name: Catppuccin,
accent: Option<CatppuccinValue>,
},
/// A theme from base16-egui-themes
/// Theme from base16-egui-themes
Base16 {
name: Base16,
accent: Option<Base16Value>,
},
/// A custom base16 palette
/// Custom base16 palette
Custom {
palette: Box<Base16ColourPalette>,
accent: Option<Base16Value>,
@@ -47,22 +48,39 @@ pub enum Theme {
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
/// Base16 colour palette: https://github.com/chriskempson/base16
pub struct Base16ColourPalette {
/// Base00
pub base_00: Colour,
/// Base01
pub base_01: Colour,
/// Base02
pub base_02: Colour,
/// Base03
pub base_03: Colour,
/// Base04
pub base_04: Colour,
/// Base05
pub base_05: Colour,
/// Base06
pub base_06: Colour,
/// Base07
pub base_07: Colour,
/// Base08
pub base_08: Colour,
/// Base09
pub base_09: Colour,
/// Base0A
pub base_0a: Colour,
/// Base0B
pub base_0b: Colour,
/// Base0C
pub base_0c: Colour,
/// Base0D
pub base_0d: Colour,
/// Base0E
pub base_0e: Colour,
/// Base0F
pub base_0f: Colour,
}
@@ -199,28 +217,48 @@ impl Theme {
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
/// Base16 value
pub enum Base16Value {
/// Base00
Base00,
/// Base01
Base01,
/// Base02
Base02,
/// Base03
Base03,
/// Base04
Base04,
/// Base05
Base05,
/// Base06
#[default]
Base06,
/// Base07
Base07,
/// Base08
Base08,
/// Base09
Base09,
/// Base0A
Base0A,
/// Base0B
Base0B,
/// Base0C
Base0C,
/// Base0D
Base0D,
/// Base0E
Base0E,
/// Base0F
Base0F,
}
/// Wrapper around a Base16 colour palette
pub enum Base16Wrapper {
/// Predefined Base16 colour palette
Base16(Base16),
/// Custom Base16 colour palette
Custom(Box<Base16ColourPalette>),
}
@@ -268,10 +306,15 @@ impl Base16Value {
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
/// Catppuccin palette
pub enum Catppuccin {
/// Frappe (https://catppuccin.com/palette#flavor-frappe)
Frappe,
/// Latte (https://catppuccin.com/palette#flavor-latte)
Latte,
/// Macchiato (https://catppuccin.com/palette#flavor-macchiato)
Macchiato,
/// Mocha (https://catppuccin.com/palette#flavor-mocha)
Mocha,
}
@@ -293,33 +336,60 @@ impl From<Catppuccin> for catppuccin_egui::Theme {
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
/// Catppuccin Value
pub enum CatppuccinValue {
/// Rosewater
Rosewater,
/// Flamingo
Flamingo,
/// Pink
Pink,
/// Mauve
Mauve,
/// Red
Red,
/// Maroon
Maroon,
/// Peach
Peach,
/// Yellow
Yellow,
/// Green
Green,
/// Teal
Teal,
/// Sky
Sky,
/// Sapphire
Sapphire,
/// Blue
Blue,
/// Lavender
Lavender,
#[default]
/// Text
Text,
/// Subtext1
Subtext1,
/// Subtext0
Subtext0,
/// Overlay2
Overlay2,
/// Overlay1
Overlay1,
/// Overlay0
Overlay0,
/// Surface2
Surface2,
/// Surface1
Surface1,
/// Surface0
Surface0,
/// Base
Base,
/// Mantle
Mantle,
/// Crust
Crust,
}
@@ -359,3 +429,275 @@ impl CatppuccinValue {
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
/// Theme from catppuccin-egui
pub struct KomorebiThemeCatppuccin {
/// Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)
pub name: Catppuccin,
/// Single window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
pub single_border: Option<CatppuccinValue>,
/// Stack window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Green)))]
pub stack_border: Option<CatppuccinValue>,
/// Monocle window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Pink)))]
pub monocle_border: Option<CatppuccinValue>,
/// Floating window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Yellow)))]
pub floating_border: Option<CatppuccinValue>,
/// Unfocused window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Base)))]
pub unfocused_border: Option<CatppuccinValue>,
/// Unfocused locked window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Red)))]
pub unfocused_locked_border: Option<CatppuccinValue>,
#[cfg(target_os = "windows")]
/// Stackbar focused text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Green)))]
pub stackbar_focused_text: Option<CatppuccinValue>,
#[cfg(target_os = "windows")]
/// Stackbar unfocused text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Text)))]
pub stackbar_unfocused_text: Option<CatppuccinValue>,
#[cfg(target_os = "windows")]
/// Stackbar background colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Base)))]
pub stackbar_background: Option<CatppuccinValue>,
/// Bar accent colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
pub bar_accent: Option<CatppuccinValue>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
/// Theme from base16-egui-themes
pub struct KomorebiThemeBase16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
pub name: Base16,
/// Single window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
pub single_border: Option<Base16Value>,
/// Stack window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
pub stack_border: Option<Base16Value>,
/// Monocle window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0F)))]
pub monocle_border: Option<Base16Value>,
/// Floating window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base09)))]
pub floating_border: Option<Base16Value>,
/// Unfocused window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
pub unfocused_border: Option<Base16Value>,
/// Unfocused locked window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base08)))]
pub unfocused_locked_border: Option<Base16Value>,
#[cfg(target_os = "windows")]
/// Stackbar focused text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
pub stackbar_focused_text: Option<Base16Value>,
#[cfg(target_os = "windows")]
/// Stackbar unfocused text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base05)))]
pub stackbar_unfocused_text: Option<Base16Value>,
#[cfg(target_os = "windows")]
/// Stackbar background colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
pub stackbar_background: Option<Base16Value>,
/// Bar accent colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
pub bar_accent: Option<Base16Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
/// Custom Base16 theme
pub struct KomorebiThemeCustom {
/// Colours of the custom Base16 theme palette
pub colours: Box<Base16ColourPalette>,
/// Single window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
pub single_border: Option<Base16Value>,
/// Stack window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
pub stack_border: Option<Base16Value>,
/// Monocle window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0F)))]
pub monocle_border: Option<Base16Value>,
/// Floating window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base09)))]
pub floating_border: Option<Base16Value>,
/// Unfocused window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
pub unfocused_border: Option<Base16Value>,
/// Unfocused locked window border colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base08)))]
pub unfocused_locked_border: Option<Base16Value>,
#[cfg(target_os = "windows")]
/// Stackbar focused text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0B)))]
pub stackbar_focused_text: Option<Base16Value>,
#[cfg(target_os = "windows")]
/// Stackbar unfocused text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base05)))]
pub stackbar_unfocused_text: Option<Base16Value>,
#[cfg(target_os = "windows")]
/// Stackbar background colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base01)))]
pub stackbar_background: Option<Base16Value>,
/// Bar accent colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
pub bar_accent: Option<Base16Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(tag = "palette")]
/// Komorebi theme
pub enum KomorebiTheme {
#[cfg_attr(feature = "schemars", schemars(title = "Catppuccin"))]
/// Theme from catppuccin-egui
Catppuccin(KomorebiThemeCatppuccin),
#[cfg_attr(feature = "schemars", schemars(title = "Base16"))]
/// Theme from base16-egui-themes
Base16(KomorebiThemeBase16),
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
/// Custom Base16 theme
Custom(KomorebiThemeCustom),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
/// Theme from catppuccin-egui
pub struct KomobarThemeCatppuccin {
/// Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)
pub name: Catppuccin,
/// Accent colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
pub accent: Option<CatppuccinValue>,
/// Auto select fill colour
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_select_fill: Option<CatppuccinValue>,
/// Auto select text colour
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_select_text: Option<CatppuccinValue>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
/// Theme from base16-egui-themes
pub struct KomobarThemeBase16 {
/// Name of the Base16 theme (previews: https://tinted-theming.github.io/tinted-gallery/)
pub name: Base16,
/// Accent colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Base16Value::Base0D)))]
pub accent: Option<Base16Value>,
/// Auto select fill colour
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_select_fill: Option<Base16Value>,
/// Auto select text colour
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_select_text: Option<Base16Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
/// Theme from base16-egui-themes
pub struct KomobarThemeCustom {
/// Colours of the custom Base16 theme palette
pub colours: Box<Base16ColourPalette>,
/// Accent colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CatppuccinValue::Blue)))]
pub accent: Option<Base16Value>,
/// Auto select fill colour
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_select_fill: Option<Base16Value>,
/// Auto select text colour
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_select_text: Option<Base16Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(tag = "palette")]
/// Komorebi bar theme
pub enum KomobarTheme {
#[cfg_attr(feature = "schemars", schemars(title = "Catppuccin"))]
/// Theme from catppuccin-egui
Catppuccin(KomobarThemeCatppuccin),
#[cfg_attr(feature = "schemars", schemars(title = "Base16"))]
/// Theme from base16-egui-themes
Base16(KomobarThemeBase16),
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
/// Custom Base16 theme
Custom(KomobarThemeCustom),
}
impl From<KomorebiTheme> for KomobarTheme {
fn from(value: KomorebiTheme) -> Self {
match value {
KomorebiTheme::Catppuccin(KomorebiThemeCatppuccin {
name, bar_accent, ..
}) => Self::Catppuccin(KomobarThemeCatppuccin {
name,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
}),
KomorebiTheme::Base16(KomorebiThemeBase16 {
name, bar_accent, ..
}) => Self::Base16(KomobarThemeBase16 {
name,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
}),
KomorebiTheme::Custom(KomorebiThemeCustom {
colours,
bar_accent,
..
}) => Self::Custom(KomobarThemeCustom {
colours,
accent: bar_accent,
auto_select_fill: None,
auto_select_text: None,
}),
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.39"
version = "0.1.40"
description = "A tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024"
@@ -10,13 +10,16 @@ edition = "2024"
[dependencies]
komorebi-themes = { path = "../komorebi-themes" }
base64 = "0.22"
bitflags = { version = "2", features = ["serde"] }
clap = { workspace = true }
chrono = { workspace = true }
color-eyre = { workspace = true }
crossbeam-channel = { workspace = true }
crossbeam-utils = { workspace = true }
ctrlc = { version = "3", features = ["termination"] }
dirs = { workspace = true }
ed25519-dalek = "2"
hotwatch = { workspace = true }
lazy_static = { workspace = true }
miow = "0.6"
@@ -27,9 +30,10 @@ parking_lot = { workspace = true }
paste = { workspace = true }
powershell_script = "1.0"
regex = "1"
reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_json = { workspace = true, features = ["preserve_order"] }
serde_yaml = { workspace = true }
shadow-rs = { workspace = true }
strum = { workspace = true }
@@ -47,7 +51,7 @@ windows-implement = { workspace = true }
windows-interface = { workspace = true }
winput = "0.2"
winreg = "0.55"
serde_with = { version = "3.12", features = ["schemars_0_8"] }
serde_with = { version = "3.12", features = ["schemars_1"] }
[build-dependencies]
shadow-rs = { workspace = true }

View File

@@ -25,8 +25,33 @@ use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Animation configuration
///
/// This can be either global:
/// ```json
/// {
/// "enabled": true,
/// "style": "EaseInSine",
/// "fps": 60,
/// "duration": 250
/// }
/// ```
///
/// Or scoped by an animation kind prefix:
/// ```json
/// {
/// "movement": {
/// "enabled": true,
/// "style": "EaseInSine",
/// "fps": 60,
/// "duration": 250
/// }
/// }
/// ```
pub enum PerAnimationPrefixConfig<T> {
/// Animation configuration prefixed for a specific animation kind
Prefix(HashMap<AnimationPrefix, T>),
/// Animation configuration for all animation kinds
Global(T),
}

View File

@@ -420,6 +420,7 @@ pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
AnimationStyle::EaseOutQuad => EaseOutQuad::evaluate(t),
AnimationStyle::EaseInOutQuad => EaseInOutQuad::evaluate(t),
AnimationStyle::EaseInCubic => EaseInCubic::evaluate(t),
AnimationStyle::EaseOutCubic => EaseOutCubic::evaluate(t),
AnimationStyle::EaseInOutCubic => EaseInOutCubic::evaluate(t),
AnimationStyle::EaseInQuart => EaseInQuart::evaluate(t),
AnimationStyle::EaseOutQuart => EaseOutQuart::evaluate(t),

View File

@@ -313,6 +313,11 @@ impl Border {
}
pub fn destroy(&self) -> color_eyre::Result<()> {
// clear user data **BEFORE** closing window
// pending messages will see a null pointer and exit early
unsafe {
SetWindowLongPtrW(self.hwnd(), GWLP_USERDATA, 0);
}
WindowsApi::close_window(self.hwnd)
}

View File

@@ -767,6 +767,13 @@ fn remove_border(
fn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {
let raw_pointer = Box::into_raw(border);
unsafe {
// release d2d resources **BEFORE** destroying window
// this drops render_target and brushes while HWND is still valid
// prevents EndDraw() from accessing freed HWND resources
(*raw_pointer).render_target = None;
(*raw_pointer).brushes.clear();
// Now safe to destroy window
(*raw_pointer).destroy()?;
}
Ok(())
@@ -813,10 +820,26 @@ pub fn hide_border(tracking_hwnd: isize) {
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Z Order (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos)
pub enum ZOrder {
/// HWND_TOP
///
/// Places the window at the top of the Z order.
Top,
/// HWND_NOTOPMOST
///
/// Places the window above all non-topmost windows (that is, behind all topmost windows).
/// This flag has no effect if the window is already a non-topmost window.
NoTopMost,
/// HWND_BOTTOM
///
/// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window,
/// the window loses its topmost status and is placed at the bottom of all other windows.
Bottom,
/// HWND_TOPMOST
///
/// Places the window above all non-topmost windows.
/// The window maintains its topmost position even when it is deactivated.
TopMost,
}

View File

@@ -41,6 +41,18 @@ impl Lockable for Container {
}
impl Container {
pub fn preselect() -> Self {
Self {
id: "PRESELECT".to_string(),
locked: false,
windows: Default::default(),
}
}
pub fn is_preselect(&self) -> bool {
self.id == "PRESELECT"
}
pub fn hide(&self, omit: Option<isize>) {
for window in self.windows().iter().rev() {
let mut should_hide = omit.is_none();

View File

@@ -8,38 +8,73 @@ use strum::EnumString;
#[derive(Copy, Clone, Debug, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Mathematical function which describes the rate at which a value changes
pub enum AnimationStyle {
/// Linear
Linear,
/// Ease in sine
EaseInSine,
/// Ease out sine
EaseOutSine,
/// Ease in out sine
EaseInOutSine,
/// Ease in quad
EaseInQuad,
/// Ease out quad
EaseOutQuad,
/// Ease in out quad
EaseInOutQuad,
/// Ease in cubic
EaseInCubic,
/// Ease out cubic
EaseOutCubic,
/// Ease in out cubic
EaseInOutCubic,
/// Ease in quart
EaseInQuart,
/// Ease out quart
EaseOutQuart,
/// Ease in out quart
EaseInOutQuart,
/// Ease in quint
EaseInQuint,
/// Ease out quint
EaseOutQuint,
/// Ease in out quint
EaseInOutQuint,
/// Ease in expo
EaseInExpo,
/// Ease out expo
EaseOutExpo,
/// Ease in out expo
EaseInOutExpo,
/// Ease in circ
EaseInCirc,
/// Ease out circ
EaseOutCirc,
/// Ease in out circ
EaseInOutCirc,
/// Ease in back
EaseInBack,
/// Ease out back
EaseOutBack,
/// Ease in out back
EaseInOutBack,
/// Ease in elastic
EaseInElastic,
/// Ease out elastic
EaseOutElastic,
/// Ease in out elastic
EaseInOutElastic,
/// Ease in bounce
EaseInBounce,
/// Ease out bounce
EaseOutBounce,
/// Ease in out bounce
EaseInOutBounce,
#[cfg_attr(feature = "schemars", schemars(title = "CubicBezier"))]
#[value(skip)]
/// Custom Cubic Bézier function
CubicBezier(f64, f64, f64, f64),
}

View File

@@ -49,114 +49,86 @@ impl Arrangement for DefaultLayout {
.and_then(|o| o.scrolling.map(|s| s.columns))
.unwrap_or(3);
let column_width = area.right / column_count as i32;
let column_width = area.right / column_count.min(len) as i32;
let mut layouts = Vec::with_capacity(len);
match len {
// treat < 3 windows the same as the columns layout
len if len < 3 => {
layouts = columns(area, len);
let visible_columns = area.right / column_width;
let keep_centered = layout_options
.and_then(|o| {
o.scrolling
.map(|s| s.center_focused_column.unwrap_or_default())
})
.unwrap_or(false);
let adjustment = calculate_columns_adjustment(resize_dimensions);
layouts.iter_mut().zip(adjustment.iter()).for_each(
|(layout, adjustment)| {
layout.top += adjustment.top;
layout.bottom += adjustment.bottom;
layout.left += adjustment.left;
layout.right += adjustment.right;
},
);
let first_visible: isize = if focused_idx == 0 {
// if focused idx is 0, we are at the beginning of the scrolling strip
0
} else {
let previous_first_visible = if latest_layout.is_empty() {
0
} else {
// previous first_visible based on the left position of the first visible window
let left_edge = area.left;
latest_layout
.iter()
.position(|rect| rect.left >= left_edge)
.unwrap_or(0) as isize
};
if matches!(
layout_flip,
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
) && let 2.. = len
{
columns_reverse(&mut layouts);
}
}
// treat >= column_count as scrolling
len => {
let visible_columns = area.right / column_width;
let keep_centered = layout_options
.and_then(|o| {
o.scrolling
.map(|s| s.center_focused_column.unwrap_or_default())
})
.unwrap_or(false);
let focused_idx = focused_idx as isize;
let first_visible: isize = if focused_idx == 0 {
// if focused idx is 0, we are at the beginning of the scrolling strip
0
// if center_focused_column is enabled, and we have an odd number of visible columns,
// center the focused window column
if keep_centered && visible_columns % 2 == 1 {
let center_offset = visible_columns as isize / 2;
(focused_idx - center_offset).max(0).min(
(len as isize)
.saturating_sub(visible_columns as isize)
.max(0),
)
} else {
if focused_idx < previous_first_visible {
// focused window is off the left edge, we need to scroll left
focused_idx
} else if focused_idx >= previous_first_visible + visible_columns as isize {
// focused window is off the right edge, we need to scroll right
// and make sure it's the last visible window
(focused_idx + 1 - visible_columns as isize).max(0)
} else {
let previous_first_visible = if latest_layout.is_empty() {
0
} else {
// previous first_visible based on the left position of the first visible window
let left_edge = area.left;
latest_layout
.iter()
.position(|rect| rect.left >= left_edge)
.unwrap_or(0) as isize
};
let focused_idx = focused_idx as isize;
// if center_focused_column is enabled, and we have an odd number of visible columns,
// center the focused window column
if keep_centered && visible_columns % 2 == 1 {
let center_offset = visible_columns as isize / 2;
(focused_idx - center_offset).max(0).min(
(len as isize)
.saturating_sub(visible_columns as isize)
.max(0),
)
} else {
if focused_idx < previous_first_visible {
// focused window is off the left edge, we need to scroll left
focused_idx
} else if focused_idx
>= previous_first_visible + visible_columns as isize
{
// focused window is off the right edge, we need to scroll right
// and make sure it's the last visible window
(focused_idx + 1 - visible_columns as isize).max(0)
} else {
// focused window is already visible, we don't need to scroll
previous_first_visible
}
.min(
(len as isize)
.saturating_sub(visible_columns as isize)
.max(0),
)
}
};
for i in 0..len {
let position = (i as isize) - first_visible;
let left = area.left + (position as i32 * column_width);
layouts.push(Rect {
left,
top: area.top,
right: column_width,
bottom: area.bottom,
});
// focused window is already visible, we don't need to scroll
previous_first_visible
}
let adjustment = calculate_scrolling_adjustment(resize_dimensions);
layouts.iter_mut().zip(adjustment.iter()).for_each(
|(layout, adjustment)| {
layout.top += adjustment.top;
layout.bottom += adjustment.bottom;
layout.left += adjustment.left;
layout.right += adjustment.right;
},
);
.min(
(len as isize)
.saturating_sub(visible_columns as isize)
.max(0),
)
}
};
for i in 0..len {
let position = (i as isize) - first_visible;
let left = area.left + (position as i32 * column_width);
layouts.push(Rect {
left,
top: area.top,
right: column_width,
bottom: area.bottom,
});
}
let adjustment = calculate_scrolling_adjustment(resize_dimensions);
layouts
.iter_mut()
.zip(adjustment.iter())
.for_each(|(layout, adjustment)| {
layout.top += adjustment.top;
layout.bottom += adjustment.bottom;
layout.left += adjustment.left;
layout.right += adjustment.right;
});
layouts
}
Self::BSP => recursive_fibonacci(
@@ -542,14 +514,25 @@ impl Arrangement for DefaultLayout {
let len = len as i32;
let num_cols = (len as f32).sqrt().ceil() as i32;
let row_constraint = layout_options.and_then(|o| o.grid.map(|g| g.rows));
let num_cols = if let Some(rows) = row_constraint {
((len as f32) / (rows as f32)).ceil() as i32
} else {
(len as f32).sqrt().ceil() as i32
};
let mut iter = layouts.iter_mut().enumerate().peekable();
for col in 0..num_cols {
let iter_peek = iter.peek().map(|x| x.0).unwrap_or_default() as i32;
let remaining_windows = len - iter_peek;
let remaining_columns = num_cols - col;
let num_rows_in_this_col = remaining_windows / remaining_columns;
let num_rows_in_this_col = if let Some(rows) = row_constraint {
(remaining_windows / remaining_columns).min(rows as i32)
} else {
remaining_windows / remaining_columns
};
let win_height = area.bottom / num_rows_in_this_col;
let win_width = area.right / num_cols;
@@ -721,9 +704,13 @@ impl Arrangement for CustomLayout {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Axis on which to perform an operation
pub enum Axis {
/// Horizontal axis
Horizontal,
/// Vertical axis
Vertical,
/// Both horizontal and vertical axes
HorizontalAndVertical,
}

View File

@@ -53,41 +53,64 @@ impl ApplicationOptions {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Rule for matching applications
pub enum MatchingRule {
/// Simple matching rule which must evaluate to true
Simple(IdWithIdentifier),
/// Composite matching rule where all conditions must evaluate to true
Composite(Vec<IdWithIdentifier>),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Rule for assigning applications to a workspace
pub struct WorkspaceMatchingRule {
/// Target monitor index
pub monitor_index: usize,
/// Target workspace index
pub workspace_index: usize,
/// Matching rule for the application
pub matching_rule: MatchingRule,
/// Whether to apply the rule only when the application is initially launched
pub initial_only: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Rule for matching applications
pub struct IdWithIdentifier {
/// Kind of identifier to target
pub kind: ApplicationIdentifier,
/// Target identifier
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// Matching strategy to use
pub matching_strategy: Option<MatchingStrategy>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Strategy for matching identifiers
pub enum MatchingStrategy {
/// Should not be used, only kept for backward compatibility
Legacy,
/// Equals
Equals,
/// Starts With
StartsWith,
/// Ends With
EndsWith,
/// Contains
Contains,
/// Regex
Regex,
/// Does not end with
DoesNotEndWith,
/// Does not start with
DoesNotStartWith,
/// Does not equal
DoesNotEqual,
/// Does not contain
DoesNotContain,
}

View File

@@ -1,4 +1,5 @@
use clap::ValueEnum;
use core::str::FromStr;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
@@ -8,32 +9,139 @@ use super::OperationDirection;
use super::Rect;
use super::Sizing;
pub fn deserialize_option_none_default_layout<'de, D>(
deserializer: D,
) -> Result<Option<DefaultLayout>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == "None" {
Ok(None)
} else {
<DefaultLayout as FromStr>::from_str(&s)
.map(Some)
.map_err(serde::de::Error::custom)
}
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// A predefined komorebi layout
pub enum DefaultLayout {
/// BSP Layout
///
/// ```text
/// +-------+-----+
/// | | |
/// | +--+--+
/// | | |--|
/// +-------+--+--+
/// ```
BSP,
/// Columns Layout
///
/// ```text
/// +--+--+--+--+
/// | | | | |
/// | | | | |
/// | | | | |
/// +--+--+--+--+
/// ```
Columns,
/// Rows Layout
///
/// ```text
/// +-----------+
/// |-----------|
/// |-----------|
/// |-----------|
/// +-----------+
/// ```
Rows,
/// Vertical Stack Layout
///
/// ```text
/// +-------+-----+
/// | | |
/// | +-----+
/// | | |
/// +-------+-----+
/// ```
VerticalStack,
/// Horizontal Stack Layout
///
/// ```text
/// +------+------+
/// | |
/// |------+------+
/// | | |
/// +------+------+
/// ```
HorizontalStack,
/// Ultrawide Vertical Stack Layout
///
/// ```text
/// +-----+-----------+-----+
/// | | | |
/// | | +-----+
/// | | | |
/// | | +-----+
/// | | | |
/// +-----+-----------+-----+
/// ```
UltrawideVerticalStack,
/// Grid Layout
///
/// ```text
/// +-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
/// | | | | | | | | | | | | | | |
/// | | | | | | | | | | | | | +---+
/// +-----+-----+ | +---+---+ +---+---+---+ +---+---| |
/// | | | | | | | | | | | | | +---+
/// | | | | | | | | | | | | | | |
/// +-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
/// 4 windows 5 windows 6 windows 7 windows
/// ```
Grid,
/// Right Main Vertical Stack Layout
///
/// ```text
/// +-----+-------+
/// | | |
/// +-----+ |
/// | | |
/// +-----+-------+
/// ```
RightMainVerticalStack,
/// Scrolling Layout
///
/// ```text
/// +--+--+--+--+--+--+
/// | | | |
/// | | | |
/// | | | |
/// +--+--+--+--+--+--+
/// ```
Scrolling,
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Options for specific layouts
pub struct LayoutOptions {
/// Options related to the Scrolling layout
pub scrolling: Option<ScrollingLayoutOptions>,
/// Options related to the Grid layout
pub grid: Option<GridLayoutOptions>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Options for the Scrolling layout
pub struct ScrollingLayoutOptions {
/// Desired number of visible columns (default: 3)
pub columns: usize,
@@ -41,6 +149,14 @@ pub struct ScrollingLayoutOptions {
pub center_focused_column: Option<bool>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Options for the Grid layout
pub struct GridLayoutOptions {
/// Maximum number of rows per grid column
pub rows: usize,
}
impl DefaultLayout {
pub fn leftmost_index(&self, len: usize) -> usize {
match self {

View File

@@ -4,6 +4,7 @@ use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
use super::custom_layout::CustomLayout;
use crate::default_layout::LayoutOptions;
pub trait Direction {
fn index_in_direction(
@@ -11,6 +12,7 @@ pub trait Direction {
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> Option<usize>;
fn is_valid_direction(
@@ -18,30 +20,35 @@ pub trait Direction {
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> bool;
fn up_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize;
fn down_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize;
fn left_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize;
fn right_index(
&self,
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize;
}
@@ -51,32 +58,53 @@ impl Direction for DefaultLayout {
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> Option<usize> {
match op_direction {
OperationDirection::Left => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.left_index(Some(op_direction), idx, Some(count)))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.left_index(
Some(op_direction),
idx,
Some(count),
layout_options,
))
} else {
None
}
}
OperationDirection::Right => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.right_index(Some(op_direction), idx, Some(count)))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.right_index(
Some(op_direction),
idx,
Some(count),
layout_options,
))
} else {
None
}
}
OperationDirection::Up => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.up_index(Some(op_direction), idx, Some(count)))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.up_index(
Some(op_direction),
idx,
Some(count),
layout_options,
))
} else {
None
}
}
OperationDirection::Down => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.down_index(Some(op_direction), idx, Some(count)))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.down_index(
Some(op_direction),
idx,
Some(count),
layout_options,
))
} else {
None
}
@@ -89,6 +117,7 @@ impl Direction for DefaultLayout {
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> bool {
if count < 2 {
return false;
@@ -101,7 +130,7 @@ impl Direction for DefaultLayout {
Self::Rows | Self::HorizontalStack => idx != 0,
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx > 2,
Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
Self::Scrolling => false,
},
OperationDirection::Down => match self {
@@ -111,7 +140,7 @@ impl Direction for DefaultLayout {
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,
Self::HorizontalStack => idx == 0,
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
Self::Scrolling => false,
},
OperationDirection::Left => match self {
@@ -121,7 +150,7 @@ impl Direction for DefaultLayout {
Self::Rows => false,
Self::HorizontalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx != 1,
Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
Self::Scrolling => idx != 0,
},
OperationDirection::Right => match self {
@@ -135,7 +164,7 @@ impl Direction for DefaultLayout {
2 => idx != 0,
_ => idx < 2,
},
Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),
Self::Scrolling => idx != count - 1,
},
}
@@ -146,6 +175,7 @@ impl Direction for DefaultLayout {
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize {
match self {
Self::BSP => {
@@ -161,7 +191,7 @@ impl Direction for DefaultLayout {
| Self::UltrawideVerticalStack
| Self::RightMainVerticalStack => idx - 1,
Self::HorizontalStack => 0,
Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
Self::Scrolling => unreachable!(),
}
}
@@ -171,6 +201,7 @@ impl Direction for DefaultLayout {
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize {
match self {
Self::BSP
@@ -180,7 +211,7 @@ impl Direction for DefaultLayout {
| Self::RightMainVerticalStack => idx + 1,
Self::Columns => unreachable!(),
Self::HorizontalStack => 1,
Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
Self::Scrolling => unreachable!(),
}
}
@@ -190,6 +221,7 @@ impl Direction for DefaultLayout {
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize {
match self {
Self::BSP => {
@@ -208,7 +240,7 @@ impl Direction for DefaultLayout {
1 => unreachable!(),
_ => 0,
},
Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
Self::Scrolling => idx - 1,
}
}
@@ -218,6 +250,7 @@ impl Direction for DefaultLayout {
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize {
match self {
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
@@ -229,7 +262,7 @@ impl Direction for DefaultLayout {
0 => 2,
_ => unreachable!(),
},
Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),
Self::Scrolling => idx + 1,
}
}
@@ -260,21 +293,32 @@ struct GridTouchingEdges {
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
fn get_grid_item(idx: usize, count: usize) -> GridItem {
let num_cols = (count as f32).sqrt().ceil() as usize;
fn get_grid_item(idx: usize, count: usize, layout_options: Option<LayoutOptions>) -> GridItem {
let row_constraint = layout_options.and_then(|o| o.grid.map(|g| g.rows));
let num_cols = if let Some(rows) = row_constraint {
((count as f32) / (rows as f32)).ceil() as i32
} else {
(count as f32).sqrt().ceil() as i32
};
let mut iter = 0;
for col in 0..num_cols {
let remaining_windows = count - iter;
let remaining_windows = (count - iter) as i32;
let remaining_columns = num_cols - col;
let num_rows_in_this_col = remaining_windows / remaining_columns;
let num_rows_in_this_col = if let Some(rows) = row_constraint {
(remaining_windows / remaining_columns).min(rows as i32)
} else {
remaining_windows / remaining_columns
};
for row in 0..num_rows_in_this_col {
if iter == idx {
return GridItem {
state: GridItemState::Valid,
row: row + 1,
num_rows: num_rows_in_this_col,
row: (row + 1) as usize,
num_rows: num_rows_in_this_col as usize,
touching_edges: GridTouchingEdges {
left: col == 0,
right: col == num_cols - 1,
@@ -301,8 +345,13 @@ fn get_grid_item(idx: usize, count: usize) -> GridItem {
}
}
fn is_grid_edge(op_direction: OperationDirection, idx: usize, count: usize) -> bool {
let item = get_grid_item(idx, count);
fn is_grid_edge(
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> bool {
let item = get_grid_item(idx, count, layout_options);
match item.state {
GridItemState::Invalid => false,
@@ -319,6 +368,7 @@ fn grid_neighbor(
op_direction: Option<OperationDirection>,
idx: usize,
count: Option<usize>,
layout_options: Option<LayoutOptions>,
) -> usize {
let Some(op_direction) = op_direction else {
return 0;
@@ -328,11 +378,11 @@ fn grid_neighbor(
return 0;
};
let item = get_grid_item(idx, count);
let item = get_grid_item(idx, count, layout_options);
match op_direction {
OperationDirection::Left => {
let item_from_prev_col = get_grid_item(idx - item.row, count);
let item_from_prev_col = get_grid_item(idx - item.row, count, layout_options);
if item.touching_edges.up && item.num_rows != item_from_prev_col.num_rows {
return idx - (item.num_rows - 1);
@@ -356,36 +406,42 @@ impl Direction for CustomLayout {
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> Option<usize> {
if count <= self.len() {
return DefaultLayout::Columns.index_in_direction(op_direction, idx, count);
return DefaultLayout::Columns.index_in_direction(
op_direction,
idx,
count,
layout_options,
);
}
match op_direction {
OperationDirection::Left => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.left_index(None, idx, None))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.left_index(None, idx, None, layout_options))
} else {
None
}
}
OperationDirection::Right => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.right_index(None, idx, None))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.right_index(None, idx, None, layout_options))
} else {
None
}
}
OperationDirection::Up => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.up_index(None, idx, None))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.up_index(None, idx, None, layout_options))
} else {
None
}
}
OperationDirection::Down => {
if self.is_valid_direction(op_direction, idx, count) {
Option::from(self.down_index(None, idx, None))
if self.is_valid_direction(op_direction, idx, count, layout_options) {
Option::from(self.down_index(None, idx, None, layout_options))
} else {
None
}
@@ -398,9 +454,15 @@ impl Direction for CustomLayout {
op_direction: OperationDirection,
idx: usize,
count: usize,
layout_options: Option<LayoutOptions>,
) -> bool {
if count <= self.len() {
return DefaultLayout::Columns.is_valid_direction(op_direction, idx, count);
return DefaultLayout::Columns.is_valid_direction(
op_direction,
idx,
count,
layout_options,
);
}
match op_direction {
@@ -444,6 +506,7 @@ impl Direction for CustomLayout {
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
_layout_options: Option<LayoutOptions>,
) -> usize {
idx - 1
}
@@ -453,6 +516,7 @@ impl Direction for CustomLayout {
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
_layout_options: Option<LayoutOptions>,
) -> usize {
idx + 1
}
@@ -462,6 +526,7 @@ impl Direction for CustomLayout {
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
_layout_options: Option<LayoutOptions>,
) -> usize {
let column_idx = self.column_for_container_idx(idx);
if column_idx - 1 == 0 {
@@ -476,6 +541,7 @@ impl Direction for CustomLayout {
_op_direction: Option<OperationDirection>,
idx: usize,
_count: Option<usize>,
_layout_options: Option<LayoutOptions>,
) -> usize {
let column_idx = self.column_for_container_idx(idx);
self.first_container_idx(column_idx + 1)

View File

@@ -1,5 +1,6 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
#![allow(deprecated)] // allow deprecated variants like HidingBehaviour::Hide to be used in derive macros
use std::num::NonZeroUsize;
use std::path::PathBuf;
@@ -23,7 +24,7 @@ pub use custom_layout::ColumnSplitWithCapacity;
pub use custom_layout::ColumnWidth;
pub use custom_layout::CustomLayout;
pub use cycle_direction::CycleDirection;
pub use default_layout::DefaultLayout;
pub use default_layout::*;
pub use direction::Direction;
pub use layout::Layout;
pub use operation_direction::OperationDirection;
@@ -55,6 +56,8 @@ pub enum SocketMessage {
// Window / Container Commands
FocusWindow(OperationDirection),
MoveWindow(OperationDirection),
PreselectDirection(OperationDirection),
CancelPreselect,
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
@@ -87,6 +90,7 @@ pub enum SocketMessage {
Close,
Minimize,
Promote,
PromoteSwap,
PromoteFocus,
PromoteWindow(OperationDirection),
EagerFocus(String),
@@ -270,17 +274,24 @@ pub struct SubscribeOptions {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Stackbar mode
pub enum StackbarMode {
/// Always show
Always,
/// Never show
Never,
/// Show on stack
OnStack,
}
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Starbar label
pub enum StackbarLabel {
#[default]
/// Process name
Process,
/// Window title
Title,
}
@@ -288,6 +299,7 @@ pub enum StackbarLabel {
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Border style
pub enum BorderStyle {
#[default]
/// Use the system border style
@@ -302,6 +314,7 @@ pub enum BorderStyle {
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Border style
pub enum BorderImplementation {
#[default]
/// Use the adjustable komorebi border implementation
@@ -325,13 +338,20 @@ pub enum BorderImplementation {
Hash,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Window kind
pub enum WindowKind {
/// Single window
Single,
/// Stack container
Stack,
/// Monocle container
Monocle,
#[default]
/// Unfocused window
Unfocused,
/// Unfocused locked container
UnfocusedLocked,
/// Floating window
Floating,
}
@@ -352,30 +372,37 @@ pub enum StateQuery {
Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Application identifier
pub enum ApplicationIdentifier {
/// Executable name
#[serde(alias = "exe")]
Exe,
/// Class
#[serde(alias = "class")]
Class,
#[serde(alias = "title")]
/// Window title
Title,
/// Executable path
#[serde(alias = "path")]
Path,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Focus follows mouse implementation
pub enum FocusFollowsMouseImplementation {
/// A custom FFM implementation (slightly more CPU-intensive)
/// Custom FFM implementation (slightly more CPU-intensive)
Komorebi,
/// The native (legacy) Windows FFM implementation
/// Native (legacy) Windows FFM implementation
Windows,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Window management behaviour
pub struct WindowManagementBehaviour {
/// The current WindowContainerBehaviour to be used
/// The current [`WindowContainerBehaviour`] to be used
pub current_behaviour: WindowContainerBehaviour,
/// Override of `current_behaviour` to open new windows as floating windows
/// that can be later toggled to tiled, when false it will default to
@@ -394,7 +421,7 @@ pub struct WindowManagementBehaviour {
pub floating_layer_placement: Placement,
/// The `Placement` to be used when spawning a window with float override active
pub float_override_placement: Placement,
/// The `Placement` to be used when spawning a window that matches a 'floating_applications' rule
/// The `Placement` to be used when spawning a window that matches a `floating_applications` rule
pub float_rule_placement: Placement,
}
@@ -402,6 +429,7 @@ pub struct WindowManagementBehaviour {
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Window container behaviour when a new window is opened
pub enum WindowContainerBehaviour {
/// Create a new container for each new window
#[default]
@@ -414,6 +442,7 @@ pub enum WindowContainerBehaviour {
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Floating layer behaviour when a new window is opened
pub enum FloatingLayerBehaviour {
/// Tile new windows (unless they match a float rule or float override is active)
#[default]
@@ -426,6 +455,7 @@ pub enum FloatingLayerBehaviour {
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Placement behaviour for floating windows
pub enum Placement {
/// Does not change the size or position of the window
#[default]
@@ -465,6 +495,7 @@ impl Placement {
Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Move behaviour when the operation works across a monitor boundary
pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor
#[default]
@@ -479,6 +510,7 @@ pub enum MoveBehaviour {
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Behaviour when an action would cross a monitor boundary
pub enum CrossBoundaryBehaviour {
/// Attempt to perform actions across a workspace boundary
Workspace,
@@ -489,10 +521,12 @@ pub enum CrossBoundaryBehaviour {
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Window hiding behaviour
pub enum HidingBehaviour {
/// END OF LIFE FEATURE: Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
/// END OF LIFE FEATURE: Use the `SW_HIDE` flag to hide windows when switching workspaces (has issues with Electron apps)
#[deprecated(note = "End of life feature")]
Hide,
/// Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)
/// Use the `SW_MINIMIZE` flag to hide windows when switching workspaces (has issues with frequent workspace switching)
Minimize,
/// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces
Cloak,
@@ -502,18 +536,22 @@ pub enum HidingBehaviour {
Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Operation behaviour for temporarily unmanaged and floating windows
pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows
/// Process commands on temporarily unmanaged/floated windows
#[default]
Op,
/// Ignore komorebic commands on temporarily unmanaged/floated windows
/// Ignore commands on temporarily unmanaged/floated windows
NoOp,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Sizing
pub enum Sizing {
/// Increase
Increase,
/// Decrease
Decrease,
}
@@ -537,9 +575,12 @@ impl Sizing {
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Window handling behaviour
pub enum WindowHandlingBehaviour {
#[default]
/// Synchronous
Sync,
/// Asynchronous
Async,
}

View File

@@ -1,14 +1,14 @@
use std::num::NonZeroUsize;
use super::Axis;
use super::direction::Direction;
use crate::default_layout::LayoutOptions;
use clap::ValueEnum;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::Axis;
use super::direction::Direction;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum OperationDirection {
@@ -57,7 +57,8 @@ impl OperationDirection {
layout_flip: Option<Axis>,
idx: usize,
len: NonZeroUsize,
layout_options: Option<LayoutOptions>,
) -> Option<usize> {
layout.index_in_direction(self.flip(layout_flip), idx, len.get())
layout.index_in_direction(self.flip(layout_flip), idx, len.get(), layout_options)
}
}

View File

@@ -121,13 +121,16 @@ impl<'de> serde_with::DeserializeAs<'de, PathBuf> for ResolvedPathBuf {
}
#[cfg(feature = "schemars")]
impl serde_with::schemars_0_8::JsonSchemaAs<PathBuf> for ResolvedPathBuf {
fn schema_name() -> String {
"PathBuf".to_owned()
impl serde_with::schemars_1::JsonSchemaAs<PathBuf> for ResolvedPathBuf {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("PathBuf")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::schema::Schema {
<PathBuf as schemars::JsonSchema>::json_schema(generator)
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"description": "A file system path. Environment variables like %VAR%, $Env:VAR, or $VAR are automatically resolved."
})
}
}

View File

@@ -4,14 +4,15 @@ use windows::Win32::Foundation::RECT;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Rectangle dimensions
pub struct Rect {
/// The left point in a Win32 Rect
/// Left point of the rectangle
pub left: i32,
/// The top point in a Win32 Rect
/// Top point of the rectangle
pub top: i32,
/// The right point in a Win32 Rect
/// Width of the recentangle (from the left point)
pub right: i32,
/// The bottom point in a Win32 Rect
/// Height of the rectangle (from the top point)
pub bottom: i32,
}

View File

@@ -16,7 +16,9 @@ pub mod process_event;
pub mod process_movement;
pub mod reaper;
pub mod set_window_position;
pub mod splash;
pub mod stackbar_manager;
pub mod state;
pub mod static_config;
pub mod styles;
pub mod theme_manager;
@@ -70,6 +72,7 @@ use parking_lot::RwLock;
use regex::Regex;
use serde::Deserialize;
use serde::Serialize;
use state::State;
use uds_windows::UnixStream;
use which::which;
use winreg::RegKey;
@@ -237,9 +240,13 @@ lazy_static! {
static ref CURRENT_VIRTUAL_DESKTOP: Arc<Mutex<Option<Vec<u8>>>> = Arc::new(Mutex::new(None));
}
pub static DEFAULT_WORKSPACE_LAYOUT: AtomicCell<Option<DefaultLayout>> =
AtomicCell::new(Some(DefaultLayout::BSP));
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
pub static DEFAULT_RESIZE_DELTA: i32 = 50;
pub static DEFAULT_MOUSE_FOLLOWS_FOCUS: bool = true;
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
@@ -253,6 +260,21 @@ pub static WINDOW_HANDLING_BEHAVIOUR: AtomicCell<WindowHandlingBehaviour> =
shadow_rs::shadow!(build);
pub const PUBLIC_KEY: [u8; 32] = [
0x5a, 0x69, 0x4a, 0xe1, 0x3c, 0x4b, 0xc8, 0x4e, 0xc3, 0x79, 0x0f, 0xab, 0x27, 0x6b, 0x7e, 0xdd,
0x6b, 0x39, 0x6f, 0xa2, 0xc3, 0x9f, 0x3d, 0x48, 0xf2, 0x72, 0x56, 0x41, 0x1b, 0xc8, 0x08, 0xdb,
];
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
pub struct License {
#[serde(rename = "hasValidSubscription")]
pub has_valid_subscription: bool,
pub timestamp: i64,
#[serde(rename = "currentEndPeriod")]
pub current_end_period: Option<i64>,
pub signature: String,
}
/// A trait for types that can be marked as locked or unlocked.
pub trait Lockable {
/// Returns `true` if the item is locked.

View File

@@ -50,10 +50,10 @@ use komorebi::process_event::listen_for_events;
use komorebi::process_movement::listen_for_movements;
use komorebi::reaper;
use komorebi::stackbar_manager;
use komorebi::state::State;
use komorebi::static_config::StaticConfig;
use komorebi::theme_manager;
use komorebi::transparency_manager;
use komorebi::window_manager::State;
use komorebi::window_manager::WindowManager;
use komorebi::windows_api::WindowsApi;
use komorebi::winevent_listener;
@@ -176,7 +176,7 @@ struct Opts {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(short, long = "ffm")]
focus_follows_mouse: bool,
/// Wait for 'komorebic complete-configuration' to be sent before processing events
/// Wait for `komorebic complete-configuration` to be sent before processing events
#[clap(short, long)]
await_configuration: bool,
/// Start a TCP server on the given port to allow the direct sending of SocketMessages

View File

@@ -4,7 +4,6 @@ use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::WORKSPACE_MATCHING_RULES;
use crate::WindowManager;
use crate::WindowsApi;
@@ -15,6 +14,7 @@ use crate::monitor;
use crate::monitor::Monitor;
use crate::monitor_reconciliator::hidden::Hidden;
use crate::notify_subscribers;
use crate::state::State;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;

View File

@@ -24,7 +24,6 @@ use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::GlobalState;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
@@ -40,7 +39,6 @@ use crate::SESSION_FLOATING_APPLICATIONS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
use crate::SUBSCRIPTION_SOCKETS;
use crate::State;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
@@ -81,12 +79,14 @@ use crate::notify_subscribers;
use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
use crate::stackbar_manager::STACKBAR_FONT_SIZE;
use crate::state;
use crate::state::GlobalState;
use crate::state::State;
use crate::static_config::StaticConfig;
use crate::theme_manager;
use crate::transparency_manager;
use crate::window::RuleDebug;
use crate::window::Window;
use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::winevent_listener;
@@ -213,6 +213,7 @@ impl WindowManager {
let mut force_update_borders = false;
match message {
SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteSwap => self.promote_container_swap()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
SocketMessage::PromoteWindow(direction) => {
self.focus_container_in_direction(direction)?;
@@ -303,6 +304,28 @@ impl WindowManager {
}
}
}
SocketMessage::PreselectDirection(direction) => {
let focused_workspace = self.focused_workspace()?;
let mut update = false;
if focused_workspace.preselected_container_idx.is_some() {
tracing::warn!(
"ignoring command as this workspace already has a direction preselect set"
);
} else if matches!(focused_workspace.layer, WorkspaceLayer::Tiling) {
self.preselect_container_in_direction(direction)?;
update = true;
}
if update {
self.focused_workspace_mut()?.update()?;
}
}
SocketMessage::CancelPreselect => {
let focused_workspace = self.focused_workspace_mut()?;
focused_workspace.cancel_preselect();
focused_workspace.update()?;
}
SocketMessage::MoveWindow(direction) => {
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer {
@@ -923,6 +946,7 @@ impl WindowManager {
columns: count.into(),
center_focused_column: Default::default(),
}),
grid: None,
},
};
@@ -1358,8 +1382,7 @@ impl WindowManager {
self.set_workspace_name(monitor_idx, workspace_idx, name.to_string())?;
}
SocketMessage::State => {
let state = match serde_json::to_string_pretty(&window_manager::State::from(&*self))
{
let state = match serde_json::to_string_pretty(&state::State::from(&*self)) {
Ok(state) => state,
Err(error) => error.to_string(),
};
@@ -2201,14 +2224,7 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
SocketMessage::StaticConfigSchema => {
#[cfg(feature = "schemars")]
{
let settings = schemars::r#gen::SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let generator = settings.into_generator();
let socket_message = generator.into_root_schema_for::<StaticConfig>();
let socket_message = schemars::schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;

View File

@@ -1,3 +1,4 @@
use std::process::Command;
use std::sync::Arc;
use std::sync::atomic::Ordering;
@@ -19,7 +20,6 @@ use crate::Layout;
use crate::Notification;
use crate::NotificationEvent;
use crate::REGEX_IDENTIFIERS;
use crate::State;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::VirtualDesktopNotification;
use crate::Window;
@@ -28,7 +28,10 @@ use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::splash;
use crate::splash::mdm_enrollment;
use crate::stackbar_manager;
use crate::state::State;
use crate::transparency_manager;
use crate::window::RuleDebug;
use crate::window::should_act;
@@ -42,6 +45,26 @@ use crate::workspace::WorkspaceLayer;
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
let receiver = wm.lock().incoming_events.clone();
std::thread::spawn(|| {
loop {
if let Ok((mdm, server)) = mdm_enrollment() {
#[allow(clippy::collapsible_if)]
if mdm && splash::should().map(|f| f.into()).unwrap_or(true) {
let mut args = vec!["splash".to_string()];
if let Some(server) = server {
if !server.trim().is_empty() {
args.push(server);
}
}
let _ = Command::new("komorebic").args(&args).spawn();
}
}
std::thread::sleep(std::time::Duration::from_secs(14400));
}
});
std::thread::spawn(move || {
tracing::info!("listening");
loop {

View File

@@ -183,6 +183,7 @@ fn find_orphans() -> color_eyre::Result<()> {
for (hwnd, (m_idx, w_idx)) in cache.iter() {
let window = Window::from(*hwnd);
#[allow(deprecated)]
if !window.is_window()
|| (
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app

157
komorebi/src/splash.rs Normal file
View File

@@ -0,0 +1,157 @@
use crate::DATA_DIR;
use crate::License;
use crate::PUBLIC_KEY;
use base64::Engine;
use base64::engine::general_purpose;
use chrono::Duration;
use chrono::TimeZone;
use chrono::Utc;
use color_eyre::eyre;
use color_eyre::eyre::OptionExt;
use ed25519_dalek::Verifier;
use ed25519_dalek::VerifyingKey;
use std::path::PathBuf;
use std::process::Command;
pub fn mdm_enrollment() -> eyre::Result<(bool, Option<String>)> {
let mut command = Command::new("dsregcmd");
command.args(["/status"]);
let stdout = command.output()?.stdout;
let output = std::str::from_utf8(&stdout)?;
if !output.contains("WorkspaceTenantName") {
return Ok((false, None));
}
let mut tenant = None;
for line in output.lines() {
if line.contains("WorkspaceTenantName") {
let line = line.trim().to_string();
tenant = Some(
line.trim_start_matches("WorkspaceTenantName : ")
.to_string(),
)
}
}
Ok((true, tenant))
}
fn is_valid_payload(raw: &str, fresh: bool) -> eyre::Result<bool> {
let mut validation_successful = false;
let payload = serde_json::from_str::<License>(raw)?;
let signature = ed25519_dalek::Signature::from_slice(
general_purpose::STANDARD
.decode(&payload.signature)?
.as_slice(),
)?;
let mut value: serde_json::Value = serde_json::from_str(raw)?;
if let serde_json::Value::Object(ref mut map) = value {
map.remove("signature");
}
let message_to_verify = serde_json::to_string(&value)?;
let verifying_key = VerifyingKey::from_bytes(&PUBLIC_KEY)?;
if verifying_key
.verify(message_to_verify.as_bytes(), &signature)
.is_ok()
{
if fresh {
let timestamp = Utc
.timestamp_opt(payload.timestamp, 0)
.single()
.ok_or_eyre("invalid timestamp")?;
let valid_duration = Utc::now() - Duration::minutes(5);
if timestamp <= valid_duration {
tracing::debug!("individual commercial use license verification payload was stale");
return Ok(true);
}
}
if payload.has_valid_subscription
&& let Some(current_end_period) = payload.current_end_period
{
let subscription_valid_until = Utc
.timestamp_opt(current_end_period, 0)
.single()
.ok_or_eyre("invalid timestamp")?;
if Utc::now() <= subscription_valid_until {
tracing::debug!(
"individual commercial use license verification - subscription valid until: {subscription_valid_until}",
);
validation_successful = true;
}
}
}
Ok(validation_successful)
}
pub enum ValidationFeedback {
Successful(PathBuf),
Unsuccessful(String),
NoEmail,
NoConnectivity,
}
impl From<ValidationFeedback> for bool {
fn from(value: ValidationFeedback) -> Self {
match value {
ValidationFeedback::Successful(_) => false,
ValidationFeedback::Unsuccessful(_)
| ValidationFeedback::NoEmail
| ValidationFeedback::NoConnectivity => true,
}
}
}
pub fn should() -> eyre::Result<ValidationFeedback> {
let icul_validation = DATA_DIR.join("icul.validation");
if icul_validation.exists() {
tracing::debug!("found local individual commercial use license validation payload");
let raw_payload = std::fs::read_to_string(&icul_validation)?;
if is_valid_payload(&raw_payload, false)? {
return Ok(ValidationFeedback::Successful(icul_validation));
} else {
std::fs::remove_file(&icul_validation)?;
}
}
let icul = DATA_DIR.join("icul");
if !icul.exists() {
return Ok(ValidationFeedback::NoEmail);
}
let email = std::fs::read_to_string(icul)?;
tracing::debug!("found individual commercial use license email: {}", email);
let client = reqwest::blocking::Client::new();
let response = match client
.get("https://kw-icul.lgug2z.com")
.query(&[("email", email.trim())])
.send()
{
Ok(response) => response,
Err(error) => {
tracing::error!("{error}");
return Ok(ValidationFeedback::NoConnectivity);
}
};
let raw_payload = response.text()?;
if is_valid_payload(&raw_payload, true)? {
std::fs::write(&icul_validation, &raw_payload)?;
Ok(ValidationFeedback::Successful(icul_validation))
} else {
Ok(ValidationFeedback::Unsuccessful(raw_payload))
}
}

308
komorebi/src/state.rs Normal file
View File

@@ -0,0 +1,308 @@
use crate::BorderColours;
use crate::BorderStyle;
use crate::CURRENT_VIRTUAL_DESKTOP;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::FocusFollowsMouseImplementation;
use crate::HIDING_BEHAVIOUR;
use crate::HOME_DIR;
use crate::HidingBehaviour;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::MoveBehaviour;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OperationBehaviour;
use crate::REMOVE_TITLEBARS;
use crate::Rect;
use crate::StackbarLabel;
use crate::StackbarMode;
use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_MATCHING_RULES;
use crate::WindowContainerBehaviour;
use crate::WindowManager;
use crate::border_manager;
use crate::border_manager::STYLE;
use crate::config_generation::MatchingRule;
use crate::config_generation::WorkspaceMatchingRule;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_LABEL;
use crate::stackbar_manager::STACKBAR_MODE;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::transparency_manager::TRANSPARENCY_ALPHA;
use crate::transparency_manager::TRANSPARENCY_ENABLED;
use crate::workspace::Workspace;
use komorebi_themes::colour::Colour;
use komorebi_themes::colour::Rgb;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct State {
pub monitors: Ring<Monitor>,
pub monitor_usr_idx_map: HashMap<usize, usize>,
pub is_paused: bool,
pub resize_delta: i32,
pub new_window_behaviour: WindowContainerBehaviour,
pub float_override: bool,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub unmanaged_window_operation_behaviour: OperationBehaviour,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub mouse_follows_focus: bool,
pub has_pending_raise_op: bool,
pub virtual_desktop_id: Option<Vec<u8>>,
}
impl State {
pub fn has_been_modified(&self, wm: &WindowManager) -> bool {
let new = Self::from(wm);
if self.monitors != new.monitors {
return true;
}
if self.is_paused != new.is_paused {
return true;
}
if self.new_window_behaviour != new.new_window_behaviour {
return true;
}
if self.float_override != new.float_override {
return true;
}
if self.cross_monitor_move_behaviour != new.cross_monitor_move_behaviour {
return true;
}
if self.unmanaged_window_operation_behaviour != new.unmanaged_window_operation_behaviour {
return true;
}
if self.work_area_offset != new.work_area_offset {
return true;
}
if self.focus_follows_mouse != new.focus_follows_mouse {
return true;
}
if self.mouse_follows_focus != new.mouse_follows_focus {
return true;
}
if self.has_pending_raise_op != new.has_pending_raise_op {
return true;
}
false
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GlobalState {
pub border_enabled: bool,
pub border_colours: BorderColours,
pub border_style: BorderStyle,
pub border_offset: i32,
pub border_width: i32,
pub stackbar_mode: StackbarMode,
pub stackbar_label: StackbarLabel,
pub stackbar_focused_text_colour: Colour,
pub stackbar_unfocused_text_colour: Colour,
pub stackbar_tab_background_colour: Colour,
pub stackbar_tab_width: i32,
pub stackbar_height: i32,
pub transparency_enabled: bool,
pub transparency_alpha: u8,
pub transparency_blacklist: Vec<MatchingRule>,
pub remove_titlebars: bool,
#[serde(alias = "float_identifiers")]
pub ignore_identifiers: Vec<MatchingRule>,
pub manage_identifiers: Vec<MatchingRule>,
pub layered_whitelist: Vec<MatchingRule>,
pub tray_and_multi_window_identifiers: Vec<MatchingRule>,
pub name_change_on_launch_identifiers: Vec<MatchingRule>,
pub monitor_index_preferences: HashMap<usize, Rect>,
pub display_index_preferences: HashMap<usize, String>,
pub ignored_duplicate_monitor_serial_ids: Vec<String>,
pub workspace_rules: Vec<WorkspaceMatchingRule>,
pub window_hiding_behaviour: HidingBehaviour,
pub configuration_dir: PathBuf,
pub data_dir: PathBuf,
pub custom_ffm: bool,
pub current_virtual_desktop_id: Option<Vec<u8>>,
}
impl Default for GlobalState {
fn default() -> Self {
Self {
border_enabled: border_manager::BORDER_ENABLED.load(Ordering::SeqCst),
border_colours: BorderColours {
single: Option::from(Colour::Rgb(Rgb::from(
border_manager::FOCUSED.load(Ordering::SeqCst),
))),
stack: Option::from(Colour::Rgb(Rgb::from(
border_manager::STACK.load(Ordering::SeqCst),
))),
monocle: Option::from(Colour::Rgb(Rgb::from(
border_manager::MONOCLE.load(Ordering::SeqCst),
))),
floating: Option::from(Colour::Rgb(Rgb::from(
border_manager::FLOATING.load(Ordering::SeqCst),
))),
unfocused: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst),
))),
unfocused_locked: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
))),
},
border_style: STYLE.load(),
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst),
stackbar_mode: STACKBAR_MODE.load(),
stackbar_label: STACKBAR_LABEL.load(),
stackbar_focused_text_colour: Colour::Rgb(Rgb::from(
STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
)),
stackbar_unfocused_text_colour: Colour::Rgb(Rgb::from(
STACKBAR_UNFOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
)),
stackbar_tab_background_colour: Colour::Rgb(Rgb::from(
STACKBAR_TAB_BACKGROUND_COLOUR.load(Ordering::SeqCst),
)),
stackbar_tab_width: STACKBAR_TAB_WIDTH.load(Ordering::SeqCst),
stackbar_height: STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst),
transparency_enabled: TRANSPARENCY_ENABLED.load(Ordering::SeqCst),
transparency_alpha: TRANSPARENCY_ALPHA.load(Ordering::SeqCst),
transparency_blacklist: TRANSPARENCY_BLACKLIST.lock().clone(),
remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst),
ignore_identifiers: IGNORE_IDENTIFIERS.lock().clone(),
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
layered_whitelist: LAYERED_WHITELIST.lock().clone(),
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),
monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(),
display_index_preferences: DISPLAY_INDEX_PREFERENCES.read().clone(),
ignored_duplicate_monitor_serial_ids: DUPLICATE_MONITOR_SERIAL_IDS.read().clone(),
workspace_rules: WORKSPACE_MATCHING_RULES.lock().clone(),
window_hiding_behaviour: *HIDING_BEHAVIOUR.lock(),
configuration_dir: HOME_DIR.clone(),
data_dir: DATA_DIR.clone(),
custom_ffm: CUSTOM_FFM.load(Ordering::SeqCst),
current_virtual_desktop_id: CURRENT_VIRTUAL_DESKTOP.lock().clone(),
}
}
}
impl From<&WindowManager> for State {
fn from(wm: &WindowManager) -> Self {
// This is used to remove any information that doesn't need to be passed on to subscribers
// or to be shown with the `komorebic state` command. Currently it is only removing the
// `workspace_config` field from every workspace, but more stripping can be added later if
// needed.
let mut stripped_monitors = Ring::default();
*stripped_monitors.elements_mut() = wm
.monitors()
.iter()
.map(|monitor| Monitor {
id: monitor.id,
name: monitor.name.clone(),
device: monitor.device.clone(),
device_id: monitor.device_id.clone(),
serial_number_id: monitor.serial_number_id.clone(),
size: monitor.size,
work_area_size: monitor.work_area_size,
work_area_offset: monitor.work_area_offset,
window_based_work_area_offset: monitor.window_based_work_area_offset,
window_based_work_area_offset_limit: monitor.window_based_work_area_offset_limit,
workspaces: {
let mut ws = Ring::default();
*ws.elements_mut() = monitor
.workspaces()
.iter()
.map(|workspace| Workspace {
name: workspace.name.clone(),
containers: workspace.containers.clone(),
monocle_container: workspace.monocle_container.clone(),
monocle_container_restore_idx: workspace.monocle_container_restore_idx,
maximized_window: workspace.maximized_window,
maximized_window_restore_idx: workspace.maximized_window_restore_idx,
floating_windows: workspace.floating_windows.clone(),
layout: workspace.layout.clone(),
layout_options: workspace.layout_options,
layout_rules: workspace.layout_rules.clone(),
layout_flip: workspace.layout_flip,
workspace_padding: workspace.workspace_padding,
container_padding: workspace.container_padding,
latest_layout: workspace.latest_layout.clone(),
resize_dimensions: workspace.resize_dimensions.clone(),
tile: workspace.tile,
work_area_offset: workspace.work_area_offset,
apply_window_based_work_area_offset: workspace
.apply_window_based_work_area_offset,
window_container_behaviour: workspace.window_container_behaviour,
window_container_behaviour_rules: workspace
.window_container_behaviour_rules
.clone(),
float_override: workspace.float_override,
layer: workspace.layer,
floating_layer_behaviour: workspace.floating_layer_behaviour,
globals: workspace.globals,
wallpaper: workspace.wallpaper.clone(),
workspace_config: None,
preselected_container_idx: None,
promotion_swap_container_idx: None,
})
.collect::<VecDeque<_>>();
ws.focus(monitor.workspaces.focused_idx());
ws
},
last_focused_workspace: monitor.last_focused_workspace,
workspace_names: monitor.workspace_names.clone(),
container_padding: monitor.container_padding,
workspace_padding: monitor.workspace_padding,
wallpaper: monitor.wallpaper.clone(),
floating_layer_behaviour: monitor.floating_layer_behaviour,
})
.collect::<VecDeque<_>>();
stripped_monitors.focus(wm.monitors.focused_idx());
Self {
monitors: stripped_monitors,
monitor_usr_idx_map: wm.monitor_usr_idx_map.clone(),
is_paused: wm.is_paused,
work_area_offset: wm.work_area_offset,
resize_delta: wm.resize_delta,
new_window_behaviour: wm.window_management_behaviour.current_behaviour,
float_override: wm.window_management_behaviour.float_override,
cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour,
focus_follows_mouse: wm.focus_follows_mouse,
mouse_follows_focus: wm.mouse_follows_focus,
has_pending_raise_op: wm.has_pending_raise_op,
unmanaged_window_operation_behaviour: wm.unmanaged_window_operation_behaviour,
virtual_desktop_id: wm.virtual_desktop_id.clone(),
}
}
}

View File

@@ -3,6 +3,9 @@ use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_MOUSE_FOLLOWS_FOCUS;
use crate::DEFAULT_RESIZE_DELTA;
use crate::DEFAULT_WORKSPACE_LAYOUT;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
@@ -108,6 +111,7 @@ use uds_windows::UnixStream;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Border colours for different container states
pub struct BorderColours {
/// Border colour when the container contains a single window
#[serde(skip_serializing_if = "Option::is_none")]
@@ -131,54 +135,69 @@ pub struct BorderColours {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Theme options
pub struct ThemeOptions {
/// Specify Light or Dark variant for theme generation (default: Dark)
/// Specify Light or Dark variant for theme generation
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::ThemeVariant::Dark)))]
pub theme_variant: Option<komorebi_themes::ThemeVariant>,
/// Border colour when the container contains a single window (default: Base0D)
/// Border colour when the container contains a single window
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base0D)))]
pub single_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container contains multiple windows (default: Base0B)
/// Border colour when the container contains multiple windows
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base0B)))]
pub stack_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is in monocle mode (default: Base0F)
/// Border colour when the container is in monocle mode
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base0F)))]
pub monocle_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the window is floating (default: Base09)
/// Border colour when the window is floating
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base09)))]
pub floating_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused (default: Base01)
/// Border colour when the container is unfocused
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base01)))]
pub unfocused_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused and locked (default: Base08)
/// Border colour when the container is unfocused and locked
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base08)))]
pub unfocused_locked_border: Option<komorebi_themes::Base16Value>,
/// Stackbar focused tab text colour (default: Base0B)
/// Stackbar focused tab text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base0B)))]
pub stackbar_focused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar unfocused tab text colour (default: Base05)
/// Stackbar unfocused tab text colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base05)))]
pub stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar tab background colour (default: Base01)
/// Stackbar tab background colour
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base01)))]
pub stackbar_background: Option<komorebi_themes::Base16Value>,
/// Komorebi status bar accent (default: Base0D)
/// Komorebi status bar accent
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::Base16Value::Base0D)))]
pub bar_accent: Option<komorebi_themes::Base16Value>,
}
#[serde_with::serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Wallpaper configuration
pub struct Wallpaper {
/// Path to the wallpaper image file
#[serde_as(as = "ResolvedPathBuf")]
pub path: PathBuf,
/// Generate and apply Base16 theme for this wallpaper (default: true)
/// Generate and apply Base16 theme for this wallpaper
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub generate_theme: Option<bool>,
/// Specify Light or Dark variant for theme generation (default: Dark)
/// Specify Light or Dark variant for theme generation
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = komorebi_themes::ThemeVariant::Dark)))]
pub theme_options: Option<ThemeOptions>,
}
@@ -186,23 +205,27 @@ pub struct Wallpaper {
#[serde_with::serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Workspace configuration
pub struct WorkspaceConfig {
/// Name
pub name: String,
/// Layout (default: BSP)
/// Layout
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DefaultLayout::BSP)))]
pub layout: Option<DefaultLayout>,
/// Layout-specific options (default: None)
/// Layout-specific options
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_options: Option<LayoutOptions>,
/// END OF LIFE FEATURE: Custom Layout (default: None)
/// END OF LIFE FEATURE: Custom Layout
#[deprecated(note = "End of life feature")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<ResolvedPathBuf>")]
pub custom_layout: Option<PathBuf>,
/// Layout rules in the format of threshold => layout (default: None)
/// Layout rules in the format of threshold => layout
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
/// END OF LIFE FEATURE: Custom layout rules (default: None)
/// END OF LIFE FEATURE: Custom layout rules
#[deprecated(note = "End of life feature")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(deserialize_with = "resolve_option_hashmap_usize_path", default)]
pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,
@@ -218,29 +241,34 @@ pub struct WorkspaceConfig {
/// Permanent workspace application rules
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_rules: Option<Vec<MatchingRule>>,
/// Workspace specific work area offset (default: None)
/// Workspace specific work area offset
#[serde(skip_serializing_if = "Option::is_none")]
pub work_area_offset: Option<Rect>,
/// Apply this monitor's window-based work area offset (default: true)
/// Apply this monitor's window-based work area offset
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub apply_window_based_work_area_offset: Option<bool>,
/// Determine what happens when a new window is opened (default: Create)
/// Determine what happens when a new window is opened
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = WindowContainerBehaviour::Create)))]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
/// Window container behaviour rules in the format of threshold => behaviour (default: None)
/// Window container behaviour rules in the format of threshold => behaviour
#[serde(skip_serializing_if = "Option::is_none")]
pub window_container_behaviour_rules: Option<HashMap<usize, WindowContainerBehaviour>>,
/// Enable or disable float override, which makes it so every new window opens in floating mode (default: false)
/// Enable or disable float override, which makes it so every new window opens in floating mode
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = false)))]
pub float_override: Option<bool>,
/// Enable or disable tiling for the workspace (default: true)
/// Enable or disable tiling for the workspace
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
pub tile: Option<bool>,
/// Specify an axis on which to flip the selected layout (default: None)
/// Specify an axis on which to flip the selected layout
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_flip: Option<Axis>,
/// Determine what happens to a new window when the Floating workspace layer is active (default: Tile)
/// Determine what happens to a new window when the Floating workspace layer is active
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = FloatingLayerBehaviour::Tile)))]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
/// Specify a wallpaper for this workspace
#[serde(skip_serializing_if = "Option::is_none")]
@@ -299,11 +327,13 @@ impl From<&Workspace> for WorkspaceConfig {
})
.flatten(),
layout_options: value.layout_options,
#[allow(deprecated)]
custom_layout: value
.workspace_config
.as_ref()
.and_then(|c| c.custom_layout.clone()),
layout_rules,
#[allow(deprecated)]
custom_layout_rules: value
.workspace_config
.as_ref()
@@ -333,17 +363,19 @@ impl From<&Workspace> for WorkspaceConfig {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Monitor configuration
pub struct MonitorConfig {
/// Workspace configurations
pub workspaces: Vec<WorkspaceConfig>,
/// Monitor-specific work area offset (default: None)
/// Monitor-specific work area offset
#[serde(skip_serializing_if = "Option::is_none")]
pub work_area_offset: Option<Rect>,
/// Window based work area offset (default: None)
/// Window based work area offset
#[serde(skip_serializing_if = "Option::is_none")]
pub window_based_work_area_offset: Option<Rect>,
/// Open window limit after which the window based work area offset will no longer be applied (default: 1)
/// Open window limit after which the window based work area offset will no longer be applied
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = 1)))]
pub window_based_work_area_offset_limit: Option<isize>,
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
@@ -354,8 +386,9 @@ pub struct MonitorConfig {
/// Specify a wallpaper for this monitor
#[serde(skip_serializing_if = "Option::is_none")]
pub wallpaper: Option<Wallpaper>,
/// Determine what happens to a new window when the Floating workspace layer is active (default: Tile)
/// Determine what happens to a new window when the Floating workspace layer is active
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = FloatingLayerBehaviour::Tile)))]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
}
@@ -402,19 +435,21 @@ impl From<&Monitor> for MonitorConfig {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Path(s) to application-specific configuration file(s)
pub enum AppSpecificConfigurationPath {
/// A single applications.json file
/// A single `applications.json` file
Single(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
/// Multiple applications.json files
/// Multiple `applications.json` files
Multiple(#[serde_as(as = "Vec<ResolvedPathBuf>")] Vec<PathBuf>),
}
#[serde_with::serde_as]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.39`
/// The `komorebi.json` static configuration file reference for `v0.1.40`
pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required
#[deprecated(note = "No longer required")]
#[serde(skip_serializing_if = "Option::is_none")]
pub invisible_borders: Option<Rect>,
/// DISCOURAGED: Minimum width for a window to be eligible for tiling
@@ -423,101 +458,127 @@ pub struct StaticConfig {
/// DISCOURAGED: Minimum height for a window to be eligible for tiling
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum_window_height: Option<i32>,
/// Delta to resize windows by (default 50)
/// Delta to resize windows by
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DEFAULT_RESIZE_DELTA)))]
pub resize_delta: Option<i32>,
/// Determine what happens when a new window is opened (default: Create)
/// Determine what happens when a new window is opened
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = WindowContainerBehaviour::Create)))]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
/// Enable or disable float override, which makes it so every new window opens in floating mode
/// (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = false)))]
pub float_override: Option<bool>,
/// Determines what happens on a new window when on the `FloatingLayer`
/// (default: Tile)
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = FloatingLayerBehaviour::Tile)))]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
/// Determines the placement of a new window when toggling to float (default: CenterAndResize)
/// Determines the placement of a new window when toggling to float
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Placement::CenterAndResize)))]
pub toggle_float_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window on the floating layer with the
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float` (default: Center)
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = Placement::Center)))]
pub floating_layer_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window with float override active
/// (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window that matches a
/// 'floating_applications' rule (default: None)
/// `floating_applications` rule
#[serde(skip_serializing_if = "Option::is_none")]
pub float_rule_placement: Option<Placement>,
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
/// Determine what happens when a window is moved across a monitor boundary
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = MoveBehaviour::Swap)))]
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
/// Determine what happens when an action is called on a window at a monitor boundary (default: Monitor)
/// Determine what happens when an action is called on a window at a monitor boundary
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = CrossBoundaryBehaviour::Monitor)))]
pub cross_boundary_behaviour: Option<CrossBoundaryBehaviour>,
/// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)
/// Determine what happens when commands are sent while an unmanaged window is in the foreground
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = OperationBehaviour::Op)))]
pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,
/// END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead
#[deprecated(
note = "End of life feature, use [masir](https://github.com/LGUG2Z/masir) instead"
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
/// Enable or disable mouse follows focus (default: true)
/// Enable or disable mouse follows focus
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DEFAULT_MOUSE_FOLLOWS_FOCUS)))]
pub mouse_follows_focus: Option<bool>,
/// Path to applications.json from komorebi-application-specific-configurations (default: None)
/// Path to applications.json from komorebi-application-specific-configurations
#[serde(skip_serializing_if = "Option::is_none")]
pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,
/// Width of the window border (default: 8)
/// Width of window borders
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_width")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = border_manager::BORDER_WIDTH)))]
pub border_width: Option<i32>,
/// Offset of the window border (default: -1)
/// Offset of window borders
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_offset")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = border_manager::BORDER_OFFSET)))]
pub border_offset: Option<i32>,
/// Display an active window border (default: true)
/// Display window borders
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = border_manager::BORDER_ENABLED)))]
pub border: Option<bool>,
/// Active window border colours for different container types
/// Window border colours for different container types (has no effect if [`theme`] is defined)
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_colours")]
pub border_colours: Option<BorderColours>,
/// Active window border style (default: System)
/// Window border style
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_style")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = BorderStyle::System)))]
pub border_style: Option<BorderStyle>,
/// DEPRECATED from v0.1.31: no longer required
#[deprecated(note = "No longer required")]
#[serde(skip_serializing_if = "Option::is_none")]
pub border_z_order: Option<ZOrder>,
/// Active window border implementation (default: Komorebi)
/// Window border implementation
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = BorderImplementation::Komorebi)))]
pub border_implementation: Option<BorderImplementation>,
/// Add transparency to unfocused windows (default: false)
/// Add transparency to unfocused windows
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = transparency_manager::TRANSPARENCY_ENABLED)))]
pub transparency: Option<bool>,
/// Alpha value for unfocused window transparency [[0-255]] (default: 200)
/// Alpha value for unfocused window transparency [[0-255]]
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = transparency_manager::TRANSPARENCY_ALPHA)))]
pub transparency_alpha: Option<u8>,
/// Individual window transparency ignore rules
#[serde(skip_serializing_if = "Option::is_none")]
pub transparency_ignore_rules: Option<Vec<MatchingRule>>,
/// Global default workspace padding (default: 10)
/// Global default workspace layout for new workspaces
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DefaultLayout::BSP)))]
#[serde(deserialize_with = "crate::default_layout::deserialize_option_none_default_layout")]
pub default_workspace_layout: Option<DefaultLayout>,
/// Global default workspace padding
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DEFAULT_WORKSPACE_PADDING)))]
pub default_workspace_padding: Option<i32>,
/// Global default container padding (default: 10)
/// Global default container padding
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = DEFAULT_CONTAINER_PADDING)))]
pub default_container_padding: Option<i32>,
/// Monitor and workspace configurations
#[serde(skip_serializing_if = "Option::is_none")]
pub monitors: Option<Vec<MonitorConfig>>,
/// Which Windows signal to use when hiding windows (default: Cloak)
/// Which Windows signal to use when hiding windows
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = HidingBehaviour::Cloak)))]
pub window_hiding_behaviour: Option<HidingBehaviour>,
/// Global work area (space used for tiling) offset (default: None)
/// Global work area (space used for tiling) offset
#[serde(skip_serializing_if = "Option::is_none")]
pub global_work_area_offset: Option<Rect>,
/// Individual window floating rules
@@ -536,13 +597,13 @@ pub struct StaticConfig {
/// Identify tray and multi-window applications
#[serde(skip_serializing_if = "Option::is_none")]
pub tray_and_multi_window_applications: Option<Vec<MatchingRule>>,
/// Identify applications that have the WS_EX_LAYERED extended window style
/// Identify applications that have the `WS_EX_LAYERED` extended window style
#[serde(skip_serializing_if = "Option::is_none")]
pub layered_applications: Option<Vec<MatchingRule>>,
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
/// Identify applications that send `EVENT_OBJECT_NAMECHANGE` on launch (very rare)
#[serde(skip_serializing_if = "Option::is_none")]
pub object_name_change_applications: Option<Vec<MatchingRule>>,
/// Do not process EVENT_OBJECT_NAMECHANGE events as Show events for identified applications matching these title regexes
/// Do not process `EVENT_OBJECT_NAMECHANGE` events as Show events for identified applications matching these title regexes
#[serde(skip_serializing_if = "Option::is_none")]
pub object_name_change_title_ignore_list: Option<Vec<String>>,
/// Set monitor index preferences
@@ -558,13 +619,16 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub animation: Option<AnimationsConfig>,
/// Theme configuration options
///
/// If a theme is specified, `border_colours` will have no effect
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<KomorebiTheme>,
/// Identify applications which are slow to send initial event notifications
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application_identifiers: Option<Vec<MatchingRule>>,
/// How long to wait when compensating for slow applications, in milliseconds (default: 20)
/// How long to wait when compensating for slow applications, in milliseconds
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = SLOW_APPLICATION_COMPENSATION_TIME)))]
pub slow_application_compensation_time: Option<u64>,
/// Komorebi status bar configuration files for multiple instances on different monitors
// this option is a little special because it is only consumed by komorebic
@@ -577,137 +641,34 @@ pub struct StaticConfig {
/// Aspect ratio to resize with when toggling floating mode for a window
#[serde(skip_serializing_if = "Option::is_none")]
pub floating_window_aspect_ratio: Option<AspectRatio>,
/// Which Windows API behaviour to use when manipulating windows (default: Sync)
/// Which Windows API behaviour to use when manipulating windows
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = WindowHandlingBehaviour::Sync)))]
pub window_handling_behaviour: Option<WindowHandlingBehaviour>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Animations configuration options
pub struct AnimationsConfig {
/// Enable or disable animations (default: false)
/// Enable or disable animations
#[cfg_attr(feature = "schemars", schemars(extend("default" = PerAnimationPrefixConfig::Global(false))))]
pub enabled: PerAnimationPrefixConfig<bool>,
/// Set the animation duration in ms (default: 250)
/// Set the animation duration in ms
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = PerAnimationPrefixConfig::Global(250))))]
pub duration: Option<PerAnimationPrefixConfig<u64>>,
/// Set the animation style (default: Linear)
/// Set the animation style
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = PerAnimationPrefixConfig::Global(AnimationStyle::Linear))))]
pub style: Option<PerAnimationPrefixConfig<AnimationStyle>>,
/// Set the animation FPS (default: 60)
/// Set the animation FPS
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = ANIMATION_FPS)))]
pub fps: Option<u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(tag = "palette")]
pub enum KomorebiTheme {
/// A theme from catppuccin-egui
Catppuccin {
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
name: komorebi_themes::Catppuccin,
/// Border colour when the container contains a single window (default: Blue)
#[serde(skip_serializing_if = "Option::is_none")]
single_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container contains multiple windows (default: Green)
#[serde(skip_serializing_if = "Option::is_none")]
stack_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is in monocle mode (default: Pink)
#[serde(skip_serializing_if = "Option::is_none")]
monocle_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the window is floating (default: Yellow)
#[serde(skip_serializing_if = "Option::is_none")]
floating_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is unfocused (default: Base)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is unfocused and locked (default: Red)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_locked_border: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar focused tab text colour (default: Green)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar unfocused tab text colour (default: Text)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_unfocused_text: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar tab background colour (default: Base)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_background: Option<komorebi_themes::CatppuccinValue>,
/// Komorebi status bar accent (default: Blue)
#[serde(skip_serializing_if = "Option::is_none")]
bar_accent: Option<komorebi_themes::CatppuccinValue>,
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
name: komorebi_themes::Base16,
/// Border colour when the container contains a single window (default: Base0D)
#[serde(skip_serializing_if = "Option::is_none")]
single_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container contains multiple windows (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stack_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is in monocle mode (default: Base0F)
#[serde(skip_serializing_if = "Option::is_none")]
monocle_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the window is floating (default: Base09)
#[serde(skip_serializing_if = "Option::is_none")]
floating_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused and locked (default: Base08)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_locked_border: Option<komorebi_themes::Base16Value>,
/// Stackbar focused tab text colour (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar unfocused tab text colour (default: Base05)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar tab background colour (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_background: Option<komorebi_themes::Base16Value>,
/// Komorebi status bar accent (default: Base0D)
#[serde(skip_serializing_if = "Option::is_none")]
bar_accent: Option<komorebi_themes::Base16Value>,
},
/// A custom Base16 theme
Custom {
/// Colours of the custom Base16 theme palette
colours: Box<komorebi_themes::Base16ColourPalette>,
/// Border colour when the container contains a single window (default: Base0D)
#[serde(skip_serializing_if = "Option::is_none")]
single_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container contains multiple windows (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stack_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is in monocle mode (default: Base0F)
#[serde(skip_serializing_if = "Option::is_none")]
monocle_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the window is floating (default: Base09)
#[serde(skip_serializing_if = "Option::is_none")]
floating_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused and locked (default: Base08)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_locked_border: Option<komorebi_themes::Base16Value>,
/// Stackbar focused tab text colour (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar unfocused tab text colour (default: Base05)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar tab background colour (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_background: Option<komorebi_themes::Base16Value>,
/// Komorebi status bar accent (default: Base0D)
#[serde(skip_serializing_if = "Option::is_none")]
bar_accent: Option<komorebi_themes::Base16Value>,
},
}
pub use komorebi_themes::KomorebiTheme;
impl StaticConfig {
pub fn end_of_life(raw: &str) {
@@ -796,6 +757,7 @@ impl StaticConfig {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Stackbar tabs configuration
pub struct TabsConfig {
/// Width of a stackbar tab
#[serde(skip_serializing_if = "Option::is_none")]
@@ -819,6 +781,7 @@ pub struct TabsConfig {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Stackbar configuration
pub struct StackbarConfig {
/// Stackbar height
#[serde(skip_serializing_if = "Option::is_none")]
@@ -826,8 +789,9 @@ pub struct StackbarConfig {
/// Stackbar label
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<StackbarLabel>,
/// Stackbar mode (default: Never)
/// Stackbar mode
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schemars", schemars(extend("default" = StackbarMode::Never)))]
pub mode: Option<StackbarMode>,
/// Stackbar tab configuration options
#[serde(skip_serializing_if = "Option::is_none")]
@@ -862,6 +826,7 @@ impl From<&WindowManager> for StaticConfig {
};
Self {
#[allow(deprecated)]
invisible_borders: None,
resize_delta: Option::from(value.resize_delta),
window_container_behaviour: Option::from(
@@ -890,6 +855,7 @@ impl From<&WindowManager> for StaticConfig {
),
minimum_window_height: Some(window::MINIMUM_HEIGHT.load(Ordering::SeqCst)),
minimum_window_width: Some(window::MINIMUM_WIDTH.load(Ordering::SeqCst)),
#[allow(deprecated)]
focus_follows_mouse: value.focus_follows_mouse,
mouse_follows_focus: Option::from(value.mouse_follows_focus),
app_specific_configuration_path: None,
@@ -905,6 +871,7 @@ impl From<&WindowManager> for StaticConfig {
),
transparency_ignore_rules: None,
border_style: Option::from(STYLE.load()),
#[allow(deprecated)]
border_z_order: None,
border_implementation: Option::from(IMPLEMENTATION.load()),
default_workspace_padding: Option::from(
@@ -946,6 +913,7 @@ impl From<&WindowManager> for StaticConfig {
remove_titlebar_applications: Option::from(NO_TITLEBAR.lock().clone()),
floating_window_aspect_ratio: Option::from(*FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock()),
window_handling_behaviour: Option::from(WINDOW_HANDLING_BEHAVIOUR.load()),
default_workspace_layout: DEFAULT_WORKSPACE_LAYOUT.load(),
}
}
}
@@ -1024,13 +992,23 @@ impl StaticConfig {
DEFAULT_CONTAINER_PADDING.store(container, Ordering::SeqCst);
}
DEFAULT_WORKSPACE_LAYOUT.store(self.default_workspace_layout);
if let Some(workspace) = self.default_workspace_padding {
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
}
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst);
border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
border_manager::BORDER_ENABLED.store(self.border.unwrap_or(true), Ordering::SeqCst);
if let Some(border_width) = self.border_width {
border_manager::BORDER_WIDTH.store(border_width, Ordering::SeqCst);
}
if let Some(border_offset) = self.border_offset {
border_manager::BORDER_OFFSET.store(border_offset, Ordering::SeqCst);
}
if let Some(border_enabled) = self.border {
border_manager::BORDER_ENABLED.store(border_enabled, Ordering::SeqCst);
}
if let Some(colours) = &self.border_colours {
if let Some(single) = colours.single {
@@ -1084,10 +1062,14 @@ impl StaticConfig {
border_manager::send_notification(None);
}
transparency_manager::TRANSPARENCY_ENABLED
.store(self.transparency.unwrap_or(false), Ordering::SeqCst);
transparency_manager::TRANSPARENCY_ALPHA
.store(self.transparency_alpha.unwrap_or(200), Ordering::SeqCst);
if let Some(transparency_enabled) = self.transparency {
transparency_manager::TRANSPARENCY_ENABLED
.store(transparency_enabled, Ordering::SeqCst);
}
if let Some(transparency_alpha) = self.transparency_alpha {
transparency_manager::TRANSPARENCY_ALPHA.store(transparency_alpha, Ordering::SeqCst);
}
let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
@@ -1311,9 +1293,12 @@ impl StaticConfig {
unmanaged_window_operation_behaviour: value
.unmanaged_window_operation_behaviour
.unwrap_or(OperationBehaviour::Op),
resize_delta: value.resize_delta.unwrap_or(50),
resize_delta: value.resize_delta.unwrap_or(DEFAULT_RESIZE_DELTA),
#[allow(deprecated)]
focus_follows_mouse: value.focus_follows_mouse,
mouse_follows_focus: value.mouse_follows_focus.unwrap_or(true),
mouse_follows_focus: value
.mouse_follows_focus
.unwrap_or(DEFAULT_MOUSE_FOLLOWS_FOCUS),
hotwatch: Hotwatch::new()?,
has_pending_raise_op: false,
pending_move_op: Arc::new(None),
@@ -1322,6 +1307,7 @@ impl StaticConfig {
known_hwnds: HashMap::new(),
};
#[allow(deprecated)]
match value.focus_follows_mouse {
None => WindowsApi::disable_focus_follows_mouse()?,
Some(FocusFollowsMouseImplementation::Windows) => {
@@ -1679,7 +1665,10 @@ impl StaticConfig {
wm.enforce_workspace_rules()?;
border_manager::BORDER_ENABLED.store(value.border.unwrap_or(true), Ordering::SeqCst);
if let Some(border_enabled) = value.border {
border_manager::BORDER_ENABLED.store(border_enabled, Ordering::SeqCst);
}
wm.window_management_behaviour.current_behaviour =
value.window_container_behaviour.unwrap_or_default();
wm.window_management_behaviour.float_override = value.float_override.unwrap_or_default();
@@ -1699,10 +1688,15 @@ impl StaticConfig {
wm.unmanaged_window_operation_behaviour = value
.unmanaged_window_operation_behaviour
.unwrap_or_default();
wm.resize_delta = value.resize_delta.unwrap_or(50);
wm.mouse_follows_focus = value.mouse_follows_focus.unwrap_or(true);
wm.resize_delta = value.resize_delta.unwrap_or(DEFAULT_RESIZE_DELTA);
wm.mouse_follows_focus = value
.mouse_follows_focus
.unwrap_or(DEFAULT_MOUSE_FOLLOWS_FOCUS);
wm.work_area_offset = value.global_work_area_offset;
wm.focus_follows_mouse = value.focus_follows_mouse;
#[allow(deprecated)]
{
wm.focus_follows_mouse = value.focus_follows_mouse;
}
match wm.focus_follows_mouse {
None => WindowsApi::disable_focus_follows_mouse()?,
@@ -1928,7 +1922,7 @@ mod tests {
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", "0.1.35",
"0.1.36", "0.1.37",
"0.1.36", "0.1.37", "0.1.38",
];
let mut versions = vec![];
@@ -1969,6 +1963,7 @@ mod tests {
"#;
let config = serde_json::from_str::<WorkspaceConfig>(config).unwrap();
#[allow(deprecated)]
let custom_layout_rules = config.custom_layout_rules.unwrap();
assert_eq!(
@@ -1985,7 +1980,10 @@ mod tests {
"name": "Test",
}
"#;
let config = serde_json::from_str::<WorkspaceConfig>(config).unwrap();
assert_eq!(config.custom_layout_rules, None);
#[allow(deprecated)]
let custom_layout_rules = config.custom_layout_rules;
assert_eq!(custom_layout_rules, None);
}
}

View File

@@ -10,6 +10,9 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use komorebi_themes::Base16Wrapper;
use komorebi_themes::KomorebiThemeBase16 as Base16;
use komorebi_themes::KomorebiThemeCatppuccin as Catppuccin;
use komorebi_themes::KomorebiThemeCustom as Custom;
use komorebi_themes::colour::Colour;
use std::ops::Deref;
use std::sync::OnceLock;
@@ -84,7 +87,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
stackbar_unfocused_text,
stackbar_background,
) = match theme {
KomorebiTheme::Catppuccin {
KomorebiTheme::Catppuccin(Catppuccin {
name,
single_border,
stack_border,
@@ -96,7 +99,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
stackbar_unfocused_text,
stackbar_background,
..
} => {
}) => {
let single_border = single_border
.unwrap_or(komorebi_themes::CatppuccinValue::Blue)
.color32(name.as_theme());
@@ -145,7 +148,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
stackbar_background,
)
}
KomorebiTheme::Base16 {
KomorebiTheme::Base16(Base16 {
name,
single_border,
stack_border,
@@ -157,7 +160,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
stackbar_unfocused_text,
stackbar_background,
..
} => {
}) => {
let single_border = single_border
.unwrap_or(komorebi_themes::Base16Value::Base0D)
.color32(Base16Wrapper::Base16(*name));
@@ -206,7 +209,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
stackbar_background,
)
}
KomorebiTheme::Custom {
KomorebiTheme::Custom(Custom {
colours,
single_border,
stack_border,
@@ -218,7 +221,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
stackbar_unfocused_text,
stackbar_background,
..
} => {
}) => {
let single_border = single_border
.unwrap_or(komorebi_themes::Base16Value::Base0D)
.color32(Base16Wrapper::Custom(colours.clone()));

View File

@@ -302,10 +302,13 @@ impl RenderDispatcher for TransparencyRenderDispatcher {
#[derive(Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
/// Aspect ratio for temporarily floating windows
pub enum AspectRatio {
/// A predefined aspect ratio
/// Predefined aspect ratio
#[cfg_attr(feature = "schemars", schemars(title = "Predefined"))]
Predefined(PredefinedAspectRatio),
/// A custom W:H aspect ratio
/// Custom W:H aspect ratio
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
Custom(i32, i32),
}
@@ -317,6 +320,7 @@ impl Default for AspectRatio {
#[derive(Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// Predefined aspect ratio
pub enum PredefinedAspectRatio {
/// 21:9
Ultrawide,
@@ -491,6 +495,8 @@ impl Window {
}
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
#[allow(deprecated)]
match *hiding_behaviour {
HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd),
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd),
@@ -515,6 +521,8 @@ impl Window {
}
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
#[allow(deprecated)]
match *hiding_behaviour {
HidingBehaviour::Hide | HidingBehaviour::Minimize => {
WindowsApi::restore_window(self.hwnd);

View File

@@ -19,8 +19,6 @@ use hotwatch::EventKind;
use hotwatch::Hotwatch;
use hotwatch::notify::ErrorKind as NotifyErrorKind;
use parking_lot::Mutex;
use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
@@ -30,69 +28,40 @@ use crate::animation::AnimationEngine;
use crate::core::Arrangement;
use crate::core::Axis;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::CycleDirection;
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::OperationDirection;
use crate::core::Rect;
use crate::core::Sizing;
use crate::core::StackbarLabel;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::core::config_generation::MatchingRule;
use crate::core::custom_layout::CustomLayout;
use crate::BorderColours;
use crate::CUSTOM_FFM;
use crate::Colour;
use crate::CrossBoundaryBehaviour;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::DUPLICATE_MONITOR_SERIAL_IDS;
use crate::HIDING_BEHAVIOUR;
use crate::HOME_DIR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
use crate::Rgb;
use crate::SUBSCRIPTION_SOCKETS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_MATCHING_RULES;
use crate::border_manager;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::STYLE;
use crate::config_generation::WorkspaceMatchingRule;
use crate::container::Container;
use crate::core::StackbarMode;
use crate::current_virtual_desktop;
use crate::load_configuration;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::should_act;
use crate::should_act_individual;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_LABEL;
use crate::stackbar_manager::STACKBAR_MODE;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::state::State;
use crate::static_config::StaticConfig;
use crate::transparency_manager;
use crate::transparency_manager::TRANSPARENCY_ALPHA;
use crate::transparency_manager::TRANSPARENCY_ENABLED;
use crate::window::Window;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
@@ -125,263 +94,12 @@ pub struct WindowManager {
pub known_hwnds: HashMap<isize, (usize, usize)>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct State {
pub monitors: Ring<Monitor>,
pub monitor_usr_idx_map: HashMap<usize, usize>,
pub is_paused: bool,
pub resize_delta: i32,
pub new_window_behaviour: WindowContainerBehaviour,
pub float_override: bool,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub unmanaged_window_operation_behaviour: OperationBehaviour,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub mouse_follows_focus: bool,
pub has_pending_raise_op: bool,
}
impl State {
pub fn has_been_modified(&self, wm: &WindowManager) -> bool {
let new = Self::from(wm);
if self.monitors != new.monitors {
return true;
}
if self.is_paused != new.is_paused {
return true;
}
if self.new_window_behaviour != new.new_window_behaviour {
return true;
}
if self.float_override != new.float_override {
return true;
}
if self.cross_monitor_move_behaviour != new.cross_monitor_move_behaviour {
return true;
}
if self.unmanaged_window_operation_behaviour != new.unmanaged_window_operation_behaviour {
return true;
}
if self.work_area_offset != new.work_area_offset {
return true;
}
if self.focus_follows_mouse != new.focus_follows_mouse {
return true;
}
if self.mouse_follows_focus != new.mouse_follows_focus {
return true;
}
if self.has_pending_raise_op != new.has_pending_raise_op {
return true;
}
false
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GlobalState {
pub border_enabled: bool,
pub border_colours: BorderColours,
pub border_style: BorderStyle,
pub border_offset: i32,
pub border_width: i32,
pub stackbar_mode: StackbarMode,
pub stackbar_label: StackbarLabel,
pub stackbar_focused_text_colour: Colour,
pub stackbar_unfocused_text_colour: Colour,
pub stackbar_tab_background_colour: Colour,
pub stackbar_tab_width: i32,
pub stackbar_height: i32,
pub transparency_enabled: bool,
pub transparency_alpha: u8,
pub transparency_blacklist: Vec<MatchingRule>,
pub remove_titlebars: bool,
#[serde(alias = "float_identifiers")]
pub ignore_identifiers: Vec<MatchingRule>,
pub manage_identifiers: Vec<MatchingRule>,
pub layered_whitelist: Vec<MatchingRule>,
pub tray_and_multi_window_identifiers: Vec<MatchingRule>,
pub name_change_on_launch_identifiers: Vec<MatchingRule>,
pub monitor_index_preferences: HashMap<usize, Rect>,
pub display_index_preferences: HashMap<usize, String>,
pub ignored_duplicate_monitor_serial_ids: Vec<String>,
pub workspace_rules: Vec<WorkspaceMatchingRule>,
pub window_hiding_behaviour: HidingBehaviour,
pub configuration_dir: PathBuf,
pub data_dir: PathBuf,
pub custom_ffm: bool,
}
impl Default for GlobalState {
fn default() -> Self {
Self {
border_enabled: border_manager::BORDER_ENABLED.load(Ordering::SeqCst),
border_colours: BorderColours {
single: Option::from(Colour::Rgb(Rgb::from(
border_manager::FOCUSED.load(Ordering::SeqCst),
))),
stack: Option::from(Colour::Rgb(Rgb::from(
border_manager::STACK.load(Ordering::SeqCst),
))),
monocle: Option::from(Colour::Rgb(Rgb::from(
border_manager::MONOCLE.load(Ordering::SeqCst),
))),
floating: Option::from(Colour::Rgb(Rgb::from(
border_manager::FLOATING.load(Ordering::SeqCst),
))),
unfocused: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst),
))),
unfocused_locked: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
))),
},
border_style: STYLE.load(),
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst),
stackbar_mode: STACKBAR_MODE.load(),
stackbar_label: STACKBAR_LABEL.load(),
stackbar_focused_text_colour: Colour::Rgb(Rgb::from(
STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
)),
stackbar_unfocused_text_colour: Colour::Rgb(Rgb::from(
STACKBAR_UNFOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
)),
stackbar_tab_background_colour: Colour::Rgb(Rgb::from(
STACKBAR_TAB_BACKGROUND_COLOUR.load(Ordering::SeqCst),
)),
stackbar_tab_width: STACKBAR_TAB_WIDTH.load(Ordering::SeqCst),
stackbar_height: STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst),
transparency_enabled: TRANSPARENCY_ENABLED.load(Ordering::SeqCst),
transparency_alpha: TRANSPARENCY_ALPHA.load(Ordering::SeqCst),
transparency_blacklist: TRANSPARENCY_BLACKLIST.lock().clone(),
remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst),
ignore_identifiers: IGNORE_IDENTIFIERS.lock().clone(),
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
layered_whitelist: LAYERED_WHITELIST.lock().clone(),
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),
monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(),
display_index_preferences: DISPLAY_INDEX_PREFERENCES.read().clone(),
ignored_duplicate_monitor_serial_ids: DUPLICATE_MONITOR_SERIAL_IDS.read().clone(),
workspace_rules: WORKSPACE_MATCHING_RULES.lock().clone(),
window_hiding_behaviour: *HIDING_BEHAVIOUR.lock(),
configuration_dir: HOME_DIR.clone(),
data_dir: DATA_DIR.clone(),
custom_ffm: CUSTOM_FFM.load(Ordering::SeqCst),
}
}
}
impl AsRef<Self> for WindowManager {
fn as_ref(&self) -> &Self {
self
}
}
impl From<&WindowManager> for State {
fn from(wm: &WindowManager) -> Self {
// This is used to remove any information that doesn't need to be passed on to subscribers
// or to be shown with the `komorebic state` command. Currently it is only removing the
// `workspace_config` field from every workspace, but more stripping can be added later if
// needed.
let mut stripped_monitors = Ring::default();
*stripped_monitors.elements_mut() = wm
.monitors()
.iter()
.map(|monitor| Monitor {
id: monitor.id,
name: monitor.name.clone(),
device: monitor.device.clone(),
device_id: monitor.device_id.clone(),
serial_number_id: monitor.serial_number_id.clone(),
size: monitor.size,
work_area_size: monitor.work_area_size,
work_area_offset: monitor.work_area_offset,
window_based_work_area_offset: monitor.window_based_work_area_offset,
window_based_work_area_offset_limit: monitor.window_based_work_area_offset_limit,
workspaces: {
let mut ws = Ring::default();
*ws.elements_mut() = monitor
.workspaces()
.iter()
.map(|workspace| Workspace {
name: workspace.name.clone(),
containers: workspace.containers.clone(),
monocle_container: workspace.monocle_container.clone(),
monocle_container_restore_idx: workspace.monocle_container_restore_idx,
maximized_window: workspace.maximized_window,
maximized_window_restore_idx: workspace.maximized_window_restore_idx,
floating_windows: workspace.floating_windows.clone(),
layout: workspace.layout.clone(),
layout_options: workspace.layout_options,
layout_rules: workspace.layout_rules.clone(),
layout_flip: workspace.layout_flip,
workspace_padding: workspace.workspace_padding,
container_padding: workspace.container_padding,
latest_layout: workspace.latest_layout.clone(),
resize_dimensions: workspace.resize_dimensions.clone(),
tile: workspace.tile,
work_area_offset: workspace.work_area_offset,
apply_window_based_work_area_offset: workspace
.apply_window_based_work_area_offset,
window_container_behaviour: workspace.window_container_behaviour,
window_container_behaviour_rules: workspace
.window_container_behaviour_rules
.clone(),
float_override: workspace.float_override,
layer: workspace.layer,
floating_layer_behaviour: workspace.floating_layer_behaviour,
globals: workspace.globals,
wallpaper: workspace.wallpaper.clone(),
workspace_config: None,
})
.collect::<VecDeque<_>>();
ws.focus(monitor.workspaces.focused_idx());
ws
},
last_focused_workspace: monitor.last_focused_workspace,
workspace_names: monitor.workspace_names.clone(),
container_padding: monitor.container_padding,
workspace_padding: monitor.workspace_padding,
wallpaper: monitor.wallpaper.clone(),
floating_layer_behaviour: monitor.floating_layer_behaviour,
})
.collect::<VecDeque<_>>();
stripped_monitors.focus(wm.monitors.focused_idx());
Self {
monitors: stripped_monitors,
monitor_usr_idx_map: wm.monitor_usr_idx_map.clone(),
is_paused: wm.is_paused,
work_area_offset: wm.work_area_offset,
resize_delta: wm.resize_delta,
new_window_behaviour: wm.window_management_behaviour.current_behaviour,
float_override: wm.window_management_behaviour.float_override,
cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour,
focus_follows_mouse: wm.focus_follows_mouse,
mouse_follows_focus: wm.mouse_follows_focus,
has_pending_raise_op: wm.has_pending_raise_op,
unmanaged_window_operation_behaviour: wm.unmanaged_window_operation_behaviour,
}
}
}
impl_ring_elements!(WindowManager, Monitor);
#[derive(Debug, Clone, Copy)]
@@ -1557,6 +1275,7 @@ impl WindowManager {
workspace.layout_flip,
focused_idx,
len,
workspace.layout_options,
)
.is_some()
{
@@ -2323,6 +2042,53 @@ impl WindowManager {
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn preselect_container_in_direction(
&mut self,
direction: OperationDirection,
) -> eyre::Result<()> {
let workspace = self.focused_workspace_mut()?;
let focused_idx = workspace.focused_container_idx();
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
tracing::warn!("preselection is not supported on the grid layout");
return Ok(());
}
tracing::info!("preselecting container");
let new_idx =
if workspace.maximized_window.is_some() || workspace.monocle_container.is_some() {
None
} else {
workspace.new_idx_for_direction(direction)
};
match new_idx {
Some(new_idx) => {
let adjusted_idx = match direction {
OperationDirection::Left | OperationDirection::Up => {
if focused_idx.abs_diff(new_idx) == 1 {
new_idx + 1
} else {
new_idx
}
}
_ => new_idx,
};
workspace.preselect_container_idx(adjusted_idx);
}
None => {
tracing::debug!(
"this is not a valid preselection direction from the current position"
)
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn focus_container_in_direction(
&mut self,
@@ -2963,8 +2729,6 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn stack_all(&mut self) -> eyre::Result<()> {
self.unstack_all(false)?;
self.handle_unmanaged_window_behaviour()?;
tracing::info!("stacking all windows on workspace");
@@ -2977,16 +2741,27 @@ impl WindowManager {
focused_hwnd = Some(window.hwnd);
}
workspace.focus_container(workspace.containers().len().saturating_sub(1));
while workspace.focused_container_idx() > 0 {
workspace.move_window_to_container(0)?;
workspace.focus_container(workspace.containers().len().saturating_sub(1));
}
let workspace_hwnds =
workspace
.containers()
.iter()
.fold(VecDeque::new(), |mut hwnds, c| {
hwnds.extend(c.windows().clone());
hwnds
});
if let Some(hwnd) = focused_hwnd {
workspace.focus_container_by_window(hwnd)?;
}
let mut container = Container::default();
*container.windows_mut() = workspace_hwnds;
*workspace.containers_mut() = VecDeque::from([container]);
workspace.focus_container(0);
if let Some(hwnd) = focused_hwnd
&& let Some(c) = workspace.focused_container_mut()
&& let Some(w_idx_to_focus) = c.idx_for_window(hwnd)
{
c.focus_window(w_idx_to_focus);
c.load_focused_window();
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
@@ -3045,6 +2820,7 @@ impl WindowManager {
workspace.layout_flip,
workspace.focused_container_idx(),
len,
workspace.layout_options,
)
.is_some();
@@ -3116,6 +2892,39 @@ impl WindowManager {
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn promote_container_swap(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace_mut()?;
let focused_container_idx = workspace.focused_container_idx();
let primary_idx = match &workspace.layout {
Layout::Default(_) => 0,
Layout::Custom(layout) => layout.first_container_idx(
layout
.primary_idx()
.ok_or_eyre("this custom layout does not have a primary column")?,
),
};
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
tracing::debug!("ignoring promote-swap command for grid layout");
return Ok(());
}
let primary_tile_is_focused = focused_container_idx == primary_idx;
if primary_tile_is_focused && let Some(swap_idx) = workspace.promotion_swap_container_idx {
workspace.swap_containers(focused_container_idx, swap_idx);
} else {
workspace.promotion_swap_container_idx = Some(focused_container_idx);
workspace.swap_containers(focused_container_idx, primary_idx);
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn promote_focus_to_front(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
@@ -4067,6 +3876,7 @@ impl WindowManager {
#[cfg(test)]
mod tests {
use super::*;
use crate::DEFAULT_WORKSPACE_LAYOUT;
use crate::monitor;
use crossbeam_channel::Sender;
use crossbeam_channel::bounded;
@@ -5393,6 +5203,7 @@ mod tests {
#[test]
fn test_toggle_tiling() {
let (mut wm, _context) = setup_window_manager();
DEFAULT_WORKSPACE_LAYOUT.store(Some(DefaultLayout::BSP));
{
let mut m = monitor::new(

View File

@@ -10,6 +10,7 @@ use std::mem::size_of;
use std::path::Path;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::GetLastError;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HMODULE;
@@ -17,6 +18,8 @@ use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::POINT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::SetLastError;
use windows::Win32::Foundation::WIN32_ERROR;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
@@ -911,18 +914,42 @@ impl WindowsApi {
fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> eyre::Result<isize> {
// Can return 0, which does not always mean that an error has occurred
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
Result::from(WindowsResult::from(unsafe {
GetWindowLongPtrW(hwnd, index)
}))
unsafe {
SetLastError(WIN32_ERROR(0));
let result = GetWindowLongPtrW(hwnd, index);
if result != 0 {
Ok(result)
} else {
let last_error = GetLastError();
if last_error == WIN32_ERROR(0) {
Ok(0)
} else {
Err(std::io::Error::from_raw_os_error(last_error.0 as i32).into())
}
}
}
}
#[cfg(target_pointer_width = "32")]
fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> eyre::Result<i32> {
// Can return 0, which does not always mean that an error has occurred
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
Result::from(WindowsResult::from(unsafe {
GetWindowLongPtrW(hwnd, index)
}))
unsafe {
SetLastError(WIN32_ERROR(0));
let result = GetWindowLongPtrW(hwnd, index);
if result != 0 {
Ok(result)
} else {
let last_error = GetLastError();
if last_error == WIN32_ERROR(0) {
Ok(0)
} else {
Err(std::io::Error::from_raw_os_error(last_error.0 as i32).into())
}
}
}
}
#[cfg(target_pointer_width = "64")]

View File

@@ -8,6 +8,7 @@ use std::sync::atomic::Ordering;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_LAYOUT;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::FloatingLayerBehaviour;
use crate::INITIAL_CONFIGURATION_LOADED;
@@ -40,6 +41,7 @@ use crate::windows_api::WindowsApi;
use color_eyre::eyre;
use color_eyre::eyre::OptionExt;
use komorebi_themes::Base16ColourPalette;
use komorebi_themes::KomorebiThemeCustom as Custom;
use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixStream;
@@ -78,6 +80,10 @@ pub struct Workspace {
pub wallpaper: Option<Wallpaper>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_config: Option<WorkspaceConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preselected_container_idx: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub promotion_swap_container_idx: Option<usize>,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -102,6 +108,8 @@ impl_ring_elements!(Workspace, Window, "floating_window");
impl Default for Workspace {
fn default() -> Self {
let default_layout = DEFAULT_WORKSPACE_LAYOUT.load();
Self {
name: None,
containers: Ring::default(),
@@ -110,7 +118,7 @@ impl Default for Workspace {
maximized_window_restore_idx: None,
monocle_container_restore_idx: None,
floating_windows: Ring::default(),
layout: Layout::Default(DefaultLayout::BSP),
layout: Layout::Default(default_layout.unwrap_or(DefaultLayout::BSP)),
layout_options: None,
layout_rules: vec![],
layout_flip: None,
@@ -118,7 +126,7 @@ impl Default for Workspace {
container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)),
latest_layout: vec![],
resize_dimensions: vec![],
tile: true,
tile: default_layout.is_some(),
work_area_offset: None,
apply_window_based_work_area_offset: true,
window_container_behaviour: None,
@@ -129,6 +137,8 @@ impl Default for Workspace {
globals: Default::default(),
workspace_config: None,
wallpaper: None,
preselected_container_idx: None,
promotion_swap_container_idx: None,
}
}
}
@@ -168,14 +178,19 @@ impl Workspace {
self.layout = Layout::Default(*layout);
}
#[allow(deprecated)]
if let Some(pathbuf) = &config.custom_layout {
let layout = CustomLayout::from_path(pathbuf)?;
self.layout = Layout::Custom(layout);
}
self.tile =
!(config.custom_layout.is_none() && config.layout.is_none() && config.tile.is_none()
#[allow(deprecated)]
{
self.tile = !(config.custom_layout.is_none()
&& config.layout.is_none()
&& config.tile.is_none()
|| config.tile.is_some_and(|tile| !tile));
}
let mut all_layout_rules = vec![];
if let Some(layout_rules) = &config.layout_rules {
@@ -189,6 +204,7 @@ impl Workspace {
self.layout_rules = all_layout_rules.clone();
#[allow(deprecated)]
if let Some(layout_rules) = &config.custom_layout_rules {
for (count, pathbuf) in layout_rules {
let rule = CustomLayout::from_path(pathbuf)?;
@@ -322,7 +338,7 @@ impl Workspace {
}
if let Some(palette) = base16_palette {
let komorebi_theme = KomorebiTheme::Custom {
let komorebi_theme = KomorebiTheme::Custom(Custom {
colours: Box::new(palette),
single_border: wallpaper
.theme_options
@@ -361,7 +377,7 @@ impl Workspace {
.as_ref()
.and_then(|o| o.stackbar_background),
bar_accent: wallpaper.theme_options.as_ref().and_then(|o| o.bar_accent),
};
});
let bytes = SocketMessage::Theme(Box::new(komorebi_theme)).as_bytes()?;
@@ -446,7 +462,8 @@ impl Workspace {
}
// make sure we are never holding on to empty containers
self.containers_mut().retain(|c| !c.windows().is_empty());
self.containers_mut()
.retain(|c| c.is_preselect() || !c.windows().is_empty());
let container_padding = self
.container_padding
@@ -818,9 +835,10 @@ impl Workspace {
}
pub fn promote_container(&mut self) -> eyre::Result<()> {
let resize = self.resize_dimensions.remove(0);
let focused_idx = self.focused_container_idx();
let container = self
.remove_focused_container()
.containers_mut()
.remove_respecting_locks(focused_idx)
.ok_or_eyre("there is no container")?;
let primary_idx = match &self.layout {
@@ -832,9 +850,10 @@ impl Workspace {
),
};
let insertion_idx = self.insert_container_at_idx(primary_idx, container);
self.resize_dimensions[insertion_idx] = resize;
self.focus_container(primary_idx);
let insertion_idx = self
.containers_mut()
.insert_respecting_locks(primary_idx, container);
self.focus_container(insertion_idx);
Ok(())
}
@@ -976,6 +995,18 @@ impl Workspace {
container
}
pub fn preselect_container_idx(&mut self, insertion_idx: usize) {
self.preselected_container_idx = Some(insertion_idx);
self.insert_container_at_idx(insertion_idx, Container::preselect());
}
pub fn cancel_preselect(&mut self) {
if let Some(idx) = self.preselected_container_idx {
self.containers_mut().remove_respecting_locks(idx);
self.preselected_container_idx = None;
}
}
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
let len = NonZeroUsize::new(self.containers().len())?;
@@ -984,6 +1015,7 @@ impl Workspace {
self.layout_flip,
self.focused_container_idx(),
len,
self.layout_options,
)
}
@@ -1074,7 +1106,12 @@ impl Workspace {
}
pub fn new_container_for_window(&mut self, window: Window) {
let next_idx = if self.containers().is_empty() {
let next_idx = if let Some(idx) = self.preselected_container_idx {
let next = idx;
self.preselected_container_idx = None;
self.remove_container_by_idx(next);
next
} else if self.containers().is_empty() {
0
} else {
self.focused_container_idx() + 1
@@ -1237,7 +1274,7 @@ impl Workspace {
0 | 1 => self.enforce_no_resize(),
_ => {
// Zero is actually on the left
if let Some(mut left) = resize_dimensions[0] {
if let Some(left) = resize_dimensions[0].as_mut() {
left.top = 0;
left.bottom = 0;
left.left = 0;
@@ -1270,7 +1307,7 @@ impl Workspace {
0 | 1 => self.enforce_no_resize(),
_ => {
// Zero is actually on the right
if let Some(mut left) = resize_dimensions[1] {
if let Some(left) = resize_dimensions[1].as_mut() {
left.top = 0;
left.bottom = 0;
left.right = 0;
@@ -1301,7 +1338,7 @@ impl Workspace {
match resize_dimensions.len() {
0 | 1 => self.enforce_no_resize(),
_ => {
if let Some(mut left) = resize_dimensions[0] {
if let Some(left) = resize_dimensions[0].as_mut() {
left.top = 0;
left.left = 0;
left.right = 0;
@@ -1332,14 +1369,14 @@ impl Workspace {
// Two windows can only be resized in the middle
2 => {
// Zero is actually on the right
if let Some(mut right) = resize_dimensions[0] {
if let Some(right) = resize_dimensions[0].as_mut() {
right.top = 0;
right.bottom = 0;
right.right = 0;
}
// One is on the left
if let Some(mut left) = resize_dimensions[1] {
if let Some(left) = resize_dimensions[1].as_mut() {
left.top = 0;
left.bottom = 0;
left.left = 0;
@@ -1349,13 +1386,13 @@ impl Workspace {
// stack on the right
_ => {
// Central can be resized left or right
if let Some(mut right) = resize_dimensions[0] {
if let Some(right) = resize_dimensions[0].as_mut() {
right.top = 0;
right.bottom = 0;
}
// Left one can only be resized to the right
if let Some(mut left) = resize_dimensions[1] {
if let Some(left) = resize_dimensions[1].as_mut() {
left.top = 0;
left.bottom = 0;
left.left = 0;

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic-no-console"
version = "0.1.39"
version = "0.1.40"
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024"
@@ -8,5 +8,3 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.39"
version = "0.1.40"
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024"
@@ -18,6 +18,7 @@ dunce = { workspace = true }
fs-tail = "0.1"
lazy_static = { workspace = true }
miette = { version = "7", features = ["fancy"] }
open = "5"
paste = { workspace = true }
powershell_script = "1.0"
reqwest = { version = "0.12", features = ["blocking"] }
@@ -28,6 +29,7 @@ shadow-rs = { workspace = true }
sysinfo = { workspace = true }
thiserror = "2"
which = { workspace = true }
win-msgbox = "0.2"
windows = { workspace = true }
[build-dependencies]
@@ -39,4 +41,4 @@ default = ["schemars"]
schemars = ["dep:schemars", "komorebi-client/default"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)'] }

File diff suppressed because it is too large Load Diff

View File

@@ -84,6 +84,7 @@ nav:
- common-workflows/multi-monitor-setup.md
- CLI reference:
- cli/quickstart.md
- cli/license.md
- cli/start.md
- cli/stop.md
- cli/kill.md
@@ -110,6 +111,8 @@ nav:
- cli/load-resize.md
- cli/focus.md
- cli/move.md
- cli/preselect-direction.md
- cli/cancel-preselect.md
- cli/minimize.md
- cli/close.md
- cli/force-focus.md
@@ -169,6 +172,7 @@ nav:
- cli/scrolling-layout-columns.md
- cli/flip-layout.md
- cli/promote.md
- cli/promote-swap.md
- cli/promote-focus.md
- cli/promote-window.md
- cli/retile.md
@@ -255,4 +259,4 @@ nav:
- cli/static-config-schema.md
- cli/generate-static-config.md
- cli/enable-autostart.md
- cli/disable-autostart.md
- cli/disable-autostart.md

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "stable"
channel = "stable"

View File

@@ -1 +1 @@
imports_granularity = "Item"
imports_granularity = "Item"

View File

@@ -1,18 +1,34 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ApplicationSpecificConfiguration",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/AscApplicationRulesOrSchema"
"$ref": "#/$defs/AscApplicationRulesOrSchema"
},
"definitions": {
"$defs": {
"ApplicationIdentifier": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
"description": "Application identifier",
"oneOf": [
{
"description": "Executable name",
"type": "string",
"const": "Exe"
},
{
"description": "Class",
"type": "string",
"const": "Class"
},
{
"description": "Window title",
"type": "string",
"const": "Title"
},
{
"description": "Executable path",
"type": "string",
"const": "Path"
}
]
},
"AscApplicationRules": {
@@ -26,7 +42,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"ignore": {
@@ -36,7 +52,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"layered": {
@@ -46,7 +62,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"manage": {
@@ -56,7 +72,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"object_name_change": {
@@ -66,7 +82,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"slow_application": {
@@ -76,7 +92,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"transparency_ignore": {
@@ -86,7 +102,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
},
"tray_and_multi_window": {
@@ -96,7 +112,7 @@
"null"
],
"items": {
"$ref": "#/definitions/MatchingRule"
"$ref": "#/$defs/MatchingRule"
}
}
}
@@ -104,7 +120,7 @@
"AscApplicationRulesOrSchema": {
"anyOf": [
{
"$ref": "#/definitions/AscApplicationRules"
"$ref": "#/$defs/AscApplicationRules"
},
{
"type": "string"
@@ -112,56 +128,103 @@
]
},
"IdWithIdentifier": {
"description": "Rule for matching applications",
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"description": "Target identifier",
"type": "string"
},
"kind": {
"$ref": "#/definitions/ApplicationIdentifier"
"description": "Kind of identifier to target",
"$ref": "#/$defs/ApplicationIdentifier"
},
"matching_strategy": {
"description": "Matching strategy to use",
"anyOf": [
{
"$ref": "#/definitions/MatchingStrategy"
"$ref": "#/$defs/MatchingStrategy"
},
{
"type": "null"
}
]
}
}
},
"required": [
"kind",
"id"
]
},
"MatchingRule": {
"description": "Rule for matching applications",
"anyOf": [
{
"$ref": "#/definitions/IdWithIdentifier"
"description": "Simple matching rule which must evaluate to true",
"$ref": "#/$defs/IdWithIdentifier"
},
{
"description": "Composite matching rule where all conditions must evaluate to true",
"type": "array",
"items": {
"$ref": "#/definitions/IdWithIdentifier"
"$ref": "#/$defs/IdWithIdentifier"
}
}
]
},
"MatchingStrategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
"description": "Strategy for matching identifiers",
"oneOf": [
{
"description": "Should not be used, only kept for backward compatibility",
"type": "string",
"const": "Legacy"
},
{
"description": "Equals",
"type": "string",
"const": "Equals"
},
{
"description": "Starts With",
"type": "string",
"const": "StartsWith"
},
{
"description": "Ends With",
"type": "string",
"const": "EndsWith"
},
{
"description": "Contains",
"type": "string",
"const": "Contains"
},
{
"description": "Regex",
"type": "string",
"const": "Regex"
},
{
"description": "Does not end with",
"type": "string",
"const": "DoesNotEndWith"
},
{
"description": "Does not start with",
"type": "string",
"const": "DoesNotStartWith"
},
{
"description": "Does not equal",
"type": "string",
"const": "DoesNotEqual"
},
{
"description": "Does not contain",
"type": "string",
"const": "DoesNotContain"
}
]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +0,0 @@
{pkgs ? import <nixpkgs> {}}:
with pkgs;
mkShell {
name = "komorebi";
buildInputs = [
python311Packages.mkdocs-material
python311Packages.mkdocs-macros
python311Packages.setuptools
python311Packages.json-schema-for-humans
];
}