Compare commits

..

34 Commits

Author SHA1 Message Date
LGUG2Z
9a3dbccc89 chore(release): v0.1.30 2024-11-03 15:43:22 -08:00
LGUG2Z
24b43a154c fix(wm): remove panic on missing matching strategy
This commit ensures that if an IdWithIdentifer without an explicitly set
matching strategy makes it through to should_act_individual, it will be
treated the same as MatchingStrategy::Legacy instead of causing a
runtime panic.
2024-11-03 08:22:13 -08:00
LGUG2Z
6a09ec4b87 feat(cli): update start cmd output blurbs 2024-11-01 20:53:06 -07:00
LGUG2Z
374cd2c6d5 feat(wm): switch to asc v2 json format
This commit switches all relevant commands to treat the v2
applications.json asc format as the default format in all commands.

The v1 applications.yaml file will still be processed correctly if
passed.
2024-11-01 19:43:04 -07:00
alex-ds13
0f385d6245 fix(wm): restore hidden windows correctly
Fixes the issue talked on Discord[1] here where when using `Hide` as
hiding behaviour some windows were hidden and never restored.

The same would happen if using stacks with apps that matched a
tray_and_multi_window_identifiers rule.

[1]: https://discord.com/channels/898554690126630914/898554690608967786/1301581412008202298
2024-11-01 19:00:26 -07:00
alex-ds13
5503323695 fix(wm): handle moving windows to/from floating workspaces
This commit fixes the issue related to moving windows to/from a floating
workspace to a tiled workspace.

Previously the start of the move would be ignored however when moving
back from a tiled workspace since it didn't know about the existance of
that window it would also "move" that workspace focused tiled window
without physically moving it, leaving it in a weird state that seemed
like it was unmanaged.

This commit changes the way this mouse moves are handled and now also
handles moving `floating_windows` and even monocle or maximized windows.
2024-11-01 15:50:22 -07:00
LGUG2Z
aa9f50fd5c docs(readme): add link to awesome komorebi list 2024-11-01 14:17:19 -07:00
LGUG2Z
3a6ae01b12 fix(borders): permit failure on global destruction
This commit allows calls to Border::destroy to fail when called in the
context of border_manager::destroy_all_borders. This is important in the
context of the retile command, which calls this function, to not leave
the retile in an inconsistent state.
2024-11-01 11:53:21 -07:00
LGUG2Z
720089587b docs(readme): add to license explanation re: nonpersonal use 2024-11-01 09:38:43 -07:00
LGUG2Z
852d1f9eb0 docs(bar): update egui docstring link
Thanks to @dinesh-58 for pointing this out!

resolve #1078
2024-10-31 17:12:27 -07:00
LGUG2Z
ed5b0f9120 fix(wm): avoid slurping on stack -> stack ops
This commit ensures that when both the origin and target containers are
stacks during a stack operation, the "slurping" stack extension
behaviour introduced in cfb0c7f2ce will
not be applied.

fix #1085
2024-10-31 16:58:46 -07:00
alex-ds13
7f7b8c7c05 fix(borders): update border loc when moving
Currently, komorebi checks if a move is happening by checking if the
left mouse is pressed and updates the borders when there is a move while
the left mouse button is pressed (BTW this is why when moving with the
keyboard using the system move it only updates after pressing enter).

However, for some reason AltSnap somehow steals this left button
information and komorebi thinks the button is not pressed.

This PR makes it so it checks for the state of the pending_move_op and
keeps updating the borders while this is_some().

This fixes both that issue with AltSnap and the issue with system move,
as well as any other situations that might allow moving a window with
anything else that doesn't use a left mouse button press.
2024-10-31 12:39:32 -07:00
alex-ds13
4198f6fabc fix(wm): set last focused workspace on alt-tab 2024-10-30 09:59:04 -07:00
alex-ds13
4acd408b88 fix(wm): focus single window on show event
This commit makes sure we refocus the window on `Show` event when it is
the only window on the workspace.

This is needed because some windows send the `FocusChange` event before
the `Show` event and on the first event we will be focusing the desktop
window to unfocus any previous window from other workspace because the
workspace will still be empty. So after adding the window, we need to
focus it again.
2024-10-28 16:17:25 -07:00
alex-ds13
73d928771d fix(wm): handle monocled stack cycle commands 2024-10-28 16:16:59 -07:00
alex-ds13
d194afe6e6 docs(mkdocs): note that grid layout doesn't support resizing 2024-10-28 16:14:24 -07:00
Carlos Regis
72f4ed0575 docs(mkdocs): update border property name 2024-10-28 16:13:11 -07:00
dependabot[bot]
82ff69e337 chore(deps): bump shadow-rs from 0.35.1 to 0.35.2
Bumps [shadow-rs](https://github.com/baoyachi/shadow-rs) from 0.35.1 to 0.35.2.
- [Release notes](https://github.com/baoyachi/shadow-rs/releases)
- [Commits](https://github.com/baoyachi/shadow-rs/compare/v0.35.1...v0.35.2)

---
updated-dependencies:
- dependency-name: shadow-rs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 13:26:53 -07:00
dependabot[bot]
583d8b1e4c chore(deps): bump regex from 1.11.0 to 1.11.1
Bumps [regex](https://github.com/rust-lang/regex) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.0...1.11.1)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 13:26:43 -07:00
dependabot[bot]
2f1f1b160c chore(deps): bump serde from 1.0.210 to 1.0.213
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.210 to 1.0.213.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.210...v1.0.213)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 13:26:36 -07:00
dependabot[bot]
bcefea8a1a chore(deps): bump serde_json from 1.0.128 to 1.0.132
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.128 to 1.0.132.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.128...1.0.132)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 13:26:27 -07:00
dependabot[bot]
223404a1aa chore(deps): bump image from 0.25.3 to 0.25.4
Bumps [image](https://github.com/image-rs/image) from 0.25.3 to 0.25.4.
- [Changelog](https://github.com/image-rs/image/blob/main/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: image
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 13:26:17 -07:00
LGUG2Z
7d032db507 ci(github): add missing multiline backslash 2024-10-28 13:06:50 -07:00
alex-ds13
09afad624f fix(wm): correct monitor index preference handling 2024-10-18 10:29:54 -07:00
alex-ds13
903b6af507 feat(wm): apply workspace-rules to floating windows
This commit allows `workspace-rules` and `initial_workspace_rules` to be
applied to floating windows. As a by product of this commit, now the
command to show `visible-windows` will now also show the maximized
windows, monocled windows and floating windows.
2024-10-18 09:59:44 -07:00
LGUG2Z
1644fcf6f2 ci(github): add note about ghost tiles on bug template 2024-10-17 15:46:28 -07:00
LGUG2Z
b31e8911ce docs(mkdocs): add phantom tiles troubleshooting 2024-10-17 15:36:18 -07:00
alex-ds13
298fbafe00 feat(wm): add floating app info to ruledebug
This commit adds a `matches_floating_applications` to the `RuleDebug`
which allows users to know if a window was matched as a floating window
when using the debug part of the GUI.
2024-10-16 16:17:58 -07:00
LGUG2Z
e46a1a6117 ci(github): add sponsor check 2024-10-16 16:15:27 -07:00
LGUG2Z
34929f32a7 feat(wm): add theme socket message
This commit adds a new SocketMessage::Theme which allows for themes to
be set programmatically. This change has also been plumbed through to
komorebi-bar so that the bar theme will also update after komorebi
processes the message and passes it on to subscribers.

A new theme_manager module has been introduced to add notification-based
handling of theme changes, both from the static config file being
updated and from SocketMessage::Theme being received.
2024-10-16 15:10:34 -07:00
LGUG2Z
1ef7a09163 docs(mkdocs): generate latest cli docs 2024-10-16 09:14:00 -07:00
Arnaud Künzi
c0c3c81d69 docs(mkdocs): add more info on whkdrc config 2024-10-15 17:03:53 -07:00
Adinelson Brühmüller
7276dc2309 feat(cli): add --ahk flag to stop cmd
resolve #951
2024-10-15 17:03:53 -07:00
LGUG2Z
58d3086615 ci(github): update build + release workflow
This commit updates the build and release workflow to enable multi-arch
builds and releases.

A number of Rust-specific actions have been added, namely rust-cache to
handle cargo caching and actions-rust-cross to handle cross-compilation.

A release-dry-run target has been added to run on master which should
help catch any issues in release workflow changes early.

Releases drop goreleaser entirely in favour of action-gh-release which
was already in use to add msi installers to the releases previously
created by goreleaser.
2024-10-15 17:03:50 -07:00
63 changed files with 1369 additions and 947 deletions

View File

@@ -1,8 +1,16 @@
name: Bug report
description: File a bug report
labels: [bug]
labels: [ bug ]
title: "[BUG]: "
body:
- type: markdown
attributes:
value: |
Please **do not** open an issue for applications with invisible windows leaving ghost tiles.
You can run `komorebic visible-windows` when the ghost tile is present on your workspace to retrieve the invisible window's exe, class name and title, and then use that to [ignore the window](https://lgug2z.github.io/komorebi/common-workflows/ignore-windows.html) responsible for the ghost tile.
If it is not possible to uniquely identify the invisible window resulting in a ghost tile through a mixture of exe, title and class identifiers , then this is not a bug with komorebi but a bug with the application you are using, and should open an issue with the developer(s) of that application.
- type: textarea
validations:
required: true

View File

@@ -1,5 +1,5 @@
name: Feature request
description: Suggest a new feature
description: Suggest a new feature (Sponsors only)
labels: [enhancement]
title: "[FEAT]: "
body:

125
.github/workflows/sponsor-check.yaml vendored Normal file
View File

@@ -0,0 +1,125 @@
name: Feature Request Sponsor Check
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
test_username:
description: "Test username to check sponsorship for"
required: true
default: "octocat"
test_title:
description: "Test issue title"
required: true
default: "[FEAT] Test Feature Request"
test_sponsor_platform:
description: "Selected sponsor platform"
required: true
type: choice
options:
- "GitHub Sponsors"
- "Ko-fi"
- "Discord"
- "YouTube"
jobs:
check-sponsor:
runs-on: ubuntu-latest
if: |
(github.event_name == 'workflow_dispatch') || (github.event_name == 'issues' &&
startsWith(github.event.issue.title, '[FEAT]') &&
github.event.issue.user.login != 'LGUG2Z' &&
fromJSON(github.event.issue.body).Sponsors == 'GitHub Sponsors')
steps:
- name: Get Issue Details
id: issue-details
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "username=${{ github.event.inputs.test_username }}" >> $GITHUB_OUTPUT
echo "title=${{ github.event.inputs.test_title }}" >> $GITHUB_OUTPUT
echo "sponsor_platform=${{ github.event.inputs.test_sponsor_platform }}" >> $GITHUB_OUTPUT
else
echo "username=${{ github.event.issue.user.login }}" >> $GITHUB_OUTPUT
echo "title=${{ github.event.issue.title }}" >> $GITHUB_OUTPUT
echo "sponsor_platform=$(jq -r '.Sponsors' <<< '${{ github.event.issue.body }}')" >> $GITHUB_OUTPUT
fi
- name: Get Sponsorship Status
id: sponsorship
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PAT }}
script: |
const username = '${{ steps.issue-details.outputs.username }}';
const sponsorPlatform = '${{ steps.issue-details.outputs.sponsor_platform }}';
if (sponsorPlatform !== 'GitHub Sponsors') {
console.log('Sponsor platform is not GitHub Sponsors, skipping check');
return true;
}
const sponsorshipQuery = `query($user: String!) {
user(login: $user) {
... on Sponsorable {
sponsorshipForViewerAsSponsorable {
tier {
name
monthlyPriceInDollars
}
}
}
}
}`;
try {
const result = await github.graphql(sponsorshipQuery, {
user: username
});
console.log(result);
const sponsorship = result.user.sponsorshipForViewerAsSponsorable;
console.log(sponsorship);
const amount = sponsorship?.tier?.monthlyPriceInDollars || 0;
console.log(`Sponsorship amount for ${username}: $${amount}/month`);
return amount >= 5;
} catch (error) {
console.log(`Error checking sponsorship: ${error.message}`);
return false;
}
- name: Print Test Results
if: github.event_name == 'workflow_dispatch'
run: |
echo "Test Results for ${{ steps.issue-details.outputs.username }}:"
echo "Title: ${{ steps.issue-details.outputs.title }}"
echo "Platform: ${{ steps.issue-details.outputs.sponsor_platform }}"
echo "Would close issue: ${{ steps.sponsorship.outputs.result == 'false' }}"
- name: Close Issue If Not Sponsor
if: |
github.event_name == 'issues' &&
steps.sponsorship.outputs.result == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueNumber = context.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: 'Thank you for your feature request! This repository requires a GitHub sponsorship of at least $5/month to submit feature requests. Please consider becoming a sponsor at https://github.com/sponsors/LGUG2Z'
});
await github.rest.issues.update({
owner,
repo,
issue_number: issueNumber,
state: 'closed'
});

View File

@@ -15,6 +15,7 @@ on:
- v*
schedule:
- cron: "30 0 * * 0" # Every day at 00:30 UTC
workflow_dispatch:
jobs:
build:
@@ -43,6 +44,7 @@ jobs:
with:
cache-on-failure: "true"
cache-all-crates: "true"
key: ${{ matrix.platform.target }}
- run: cargo +nightly fmt --check
- run: cargo clippy
- uses: houseabsolute/actions-rust-cross@v0
@@ -66,7 +68,7 @@ jobs:
needs: build
runs-on: windows-latest
permissions: write-all
if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'schedule' }}
if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ) }}
env:
GH_TOKEN: ${{ github.token }}
steps:
@@ -84,6 +86,10 @@ jobs:
Compress-Archive -Force ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/aarch64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip
Copy-Item ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/wix/*aarch64.msi -Destination ./komorebi-$Env:VERSION-aarch64.msi
echo "$((Get-FileHash komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip).Hash.ToLower()) komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip" >>checksums.txt
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: "true"
cache-all-crates: "true"
- shell: bash
run: |
if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi
@@ -98,7 +104,7 @@ jobs:
--target $GITHUB_SHA \
--prerelease \
--title "komorebi nightly (${GITHUB_SHA})" \
--notes-file CHANGELOG.md
--notes-file CHANGELOG.md \
komorebi-nightly-x86_64-pc-windows-msvc.zip \
komorebi-nightly-x86_64.msi \
komorebi-nightly-aarch64-pc-windows-msvc.zip \
@@ -160,7 +166,7 @@ jobs:
fetch-depth: 0
- shell: bash
run: |
TAG=${{ github.event.release.tag_name }}
TAG=${{ github.ref_name }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- uses: actions/download-artifact@v4
- run: |

1
.gitignore vendored
View File

@@ -4,4 +4,5 @@
CHANGELOG.md
dummy.go
komorebic/applications.yaml
komorebic/applications.json
/.vs

388
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,7 @@ windows-implement = { version = "0.58" }
windows-interface = { version = "0.58" }
windows-core = { version = "0.58" }
shadow-rs = "0.35"
which = "6"
which = "7"
[workspace.dependencies.windows]
version = "0.58"

View File

@@ -60,6 +60,9 @@ channel](https://www.youtube.com/channel/UCeai3-do-9O4MNy9_xjO6mg) where I post
_komorebi_ development videos. If you would like to be notified of upcoming
videos please subscribe and turn on notifications.
There is an [Awesome List](https://github.com/LGUG2Z/awesome-komorebi) which
showcases the many awesome projects that exist in the _komorebi_ ecosystem.
_komorebi_ is a free and source-available project, and one that encourages you to
make charitable donations if you find the software to be useful and have the
financial means.
@@ -96,7 +99,6 @@ video will answer the majority of your questions.
[![Watch the comparison video](https://img.youtube.com/vi/0LCbS_gm0RA/hqdefault.jpg)](https://www.youtube.com/watch?v=0LCbS_gm0RA)
# Demonstrations
[@amnweb](https://github.com/amnweb) showing _komorebi_ `v0.1.28` running on Windows 11 with window borders,
@@ -105,7 +107,6 @@ _komorebi_'s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komo
https://github.com/LGUG2Z/komorebi/assets/13164844/21be8dc4-fa76-4f70-9b37-1d316f4b40c2
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
11 with a terminal emulator, a web browser and a code editor. The original
video can be viewed
@@ -194,13 +195,19 @@ required.
`komorebi` is licensed under the [Komorebi 1.0.0 license](./LICENSE.md), which
is a fork of the [PolyForm Strict 1.0.0
license](https://polyformproject.org/licenses/strict/1.0.0). On a high level
this means that you are free to do whatever you want with `komorebi` other than
redistribution, or distribution of new works (ie. hard-forks) based on the
software.
this means that you are free to do whatever you want with `komorebi` for
personal use other than redistribution, or distribution of new works (ie.
hard-forks) based on the software.
Anyone is free to make their own fork of `komorebi` with changes intended
either for personal use or for integration back upstream via pull requests.
The [Komorebi 1.0.0 License](./LICENSE.md) does not permit any kind of
commercial use.
A dedicated license and EULA will be introduced in 2025 for both commercial and
noncommercial organizations.
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for more information about how
code contributions to `komorebi` are licensed.
@@ -359,7 +366,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.29"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.30"}
use anyhow::Result;
use komorebi_client::Notification;

View File

@@ -10,9 +10,8 @@ Options:
Desired ease function for animation
[default: linear]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart,
ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ,
ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo,
ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
-h, --help
Print help

View File

@@ -1,7 +1,7 @@
# application-specific-configuration-schema
```
Generate a JSON Schema for applications.yaml
Generate a JSON Schema for applications.json
Usage: komorebic.exe application-specific-configuration-schema

View File

@@ -18,7 +18,7 @@ Arguments:
Options:
-w, --window-kind <WINDOW_KIND>
[default: single]
[possible values: single, stack, monocle, unfocused]
[possible values: single, stack, monocle, unfocused, floating]
-h, --help
Print help

View File

@@ -0,0 +1,16 @@
# convert-app-specific-configuration
```
Convert a v1 ASC YAML file to a v2 ASC JSON file
Usage: komorebic.exe convert-app-specific-configuration <PATH>
Arguments:
<PATH>
YAML file from which the application-specific configurations should be loaded
Options:
-h, --help
Print help
```

View File

@@ -9,9 +9,6 @@ Options:
-c, --config <CONFIG>
Path to a static configuration JSON file
-f, --ffm
Enable komorebi's custom focus-follows-mouse implementation
--whkd
Enable autostart of whkd

View File

@@ -1,7 +1,7 @@
# fetch-app-specific-configuration
```
Fetch the latest version of applications.yaml from komorebi-application-specific-configuration
Fetch the latest version of applications.json from komorebi-application-specific-configuration
Usage: komorebic.exe fetch-app-specific-configuration

View File

@@ -1,23 +0,0 @@
# focus-follows-mouse
```
Enable or disable focus follows mouse for the operating system
Usage: komorebic.exe focus-follows-mouse [OPTIONS] <BOOLEAN_STATE>
Arguments:
<BOOLEAN_STATE>
[possible values: enable, disable]
Options:
-i, --implementation <IMPLEMENTATION>
[default: windows]
Possible values:
- komorebi: A custom FFM implementation (slightly more CPU-intensive)
- windows: The native (legacy) Windows FFM implementation
-h, --help
Print help (see a summary with '-h')
```

View File

@@ -1,16 +0,0 @@
# format-app-specific-configuration
```
Format a YAML file for use with the 'ahk-app-specific-configuration' command
Usage: komorebic.exe format-app-specific-configuration <PATH>
Arguments:
<PATH>
YAML file from which the application-specific configurations should be loaded
Options:
-h, --help
Print help
```

View File

@@ -1,9 +1,9 @@
# float-rule
# ignore-rule
```
Add a rule to always float the specified application
Add a rule to ignore the specified application
Usage: komorebic.exe float-rule <IDENTIFIER> <ID>
Usage: komorebic.exe ignore-rule <IDENTIFIER> <ID>
Arguments:
<IDENTIFIER>

View File

@@ -1,16 +0,0 @@
# load-custom-layout
```
Load a custom layout from file for the focused workspace
Usage: komorebic.exe load-custom-layout <PATH>
Arguments:
<PATH>
JSON or YAML file from which the custom layout definition should be loaded
Options:
-h, --help
Print help
```

View File

@@ -1,22 +0,0 @@
# named-workspace-custom-layout-rule
```
Add a dynamic custom layout for the specified workspace
Usage: komorebic.exe named-workspace-custom-layout-rule <WORKSPACE> <AT_CONTAINER_COUNT> <PATH>
Arguments:
<WORKSPACE>
Target workspace name
<AT_CONTAINER_COUNT>
The number of window containers on-screen required to trigger this layout rule
<PATH>
JSON or YAML file from which the custom layout definition should be loaded
Options:
-h, --help
Print help
```

View File

@@ -1,19 +0,0 @@
# named-workspace-custom-layout
```
Set a custom layout for the specified workspace
Usage: komorebic.exe named-workspace-custom-layout <WORKSPACE> <PATH>
Arguments:
<WORKSPACE>
Target workspace name
<PATH>
JSON or YAML file from which the custom layout definition should be loaded
Options:
-h, --help
Print help
```

View File

@@ -6,9 +6,6 @@ Start komorebi.exe as a background process
Usage: komorebic.exe start [OPTIONS]
Options:
-f, --ffm
Allow the use of komorebi's custom focus-follows-mouse implementation
-c, --config <CONFIG>
Path to a static configuration JSON file

View File

@@ -9,6 +9,9 @@ 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

@@ -0,0 +1,12 @@
# toggle-float-override
```
Enable or disable float override, which makes it so every new window opens in floating mode
Usage: komorebic.exe toggle-float-override
Options:
-h, --help
Print help
```

View File

@@ -1,19 +0,0 @@
# toggle-focus-follows-mouse
```
Toggle focus follows mouse for the operating system
Usage: komorebic.exe toggle-focus-follows-mouse [OPTIONS]
Options:
-i, --implementation <IMPLEMENTATION>
[default: windows]
Possible values:
- komorebi: A custom FFM implementation (slightly more CPU-intensive)
- windows: The native (legacy) Windows FFM implementation
-h, --help
Print help (see a summary with '-h')
```

View File

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

View File

@@ -0,0 +1,12 @@
# toggle-workspace-window-container-behaviour
```
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the global value
Usage: komorebic.exe toggle-workspace-window-container-behaviour
Options:
-h, --help
Print help
```

View File

@@ -1,25 +0,0 @@
# workspace-custom-layout-rule
```
Add a dynamic custom layout for the specified workspace
Usage: komorebic.exe workspace-custom-layout-rule <MONITOR> <WORKSPACE> <AT_CONTAINER_COUNT> <PATH>
Arguments:
<MONITOR>
Monitor index (zero-indexed)
<WORKSPACE>
Workspace index on the specified monitor (zero-indexed)
<AT_CONTAINER_COUNT>
The number of window containers on-screen required to trigger this layout rule
<PATH>
JSON or YAML file from which the custom layout definition should be loaded
Options:
-h, --help
Print help
```

View File

@@ -1,22 +0,0 @@
# workspace-custom-layout
```
Set a custom layout for the specified workspace
Usage: komorebic.exe workspace-custom-layout <MONITOR> <WORKSPACE> <PATH>
Arguments:
<MONITOR>
Monitor index (zero-indexed)
<WORKSPACE>
Workspace index on the specified monitor (zero-indexed)
<PATH>
JSON or YAML file from which the custom layout definition should be loaded
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,29 @@
# Autostart
If you would like to autostart `komorebi`, you can use the `komorebic enable-autostart` command to generate a shortcut
in the `shell:startup` folder.
```
Generates the komorebi.lnk shortcut in shell:startup to autostart komorebi
Usage: komorebic.exe enable-autostart [OPTIONS]
Options:
-c, --config <CONFIG>
Path to a static configuration JSON file
-f, --ffm
Enable komorebi's custom focus-follows-mouse implementation
--whkd
Enable autostart of whkd
--ahk
Enable autostart of ahk
--bar
Enable autostart of komorebi-bar
-h, --help
Print help
```

View File

@@ -1,70 +0,0 @@
# Custom Layouts
Particularly for users of ultrawide monitors, traditional tiling layouts may
not seem like the most efficient use of screen space. If you feel this is the
case with any of the default layouts, you are also welcome to create your own
custom layouts and save them as JSON or YAML.
If you're not comfortable writing the layouts directly in JSON or YAML, you can
use the [komorebi Custom Layout
Generator](https://lgug2z.github.io/komorebi-custom-layout-generator/) to
interactively define a custom layout, and then copy the generated JSON content.
Custom layouts can be loaded on the current workspace or configured for a
specific workspace in the `komorebi.json` configuration file.
```json
{
"monitors": [
{
"workspaces": [
{
"name": "personal",
"custom_layout": "C:/Users/LGUG2Z/my-custom-layout.json"
},
]
}
]
}
```
The fundamental building block of a custom _komorebi_ layout is the Column.
Columns come in three variants:
- **Primary**: This is where your primary focus will be on the screen most of
the time. There must be exactly one Primary Column in any custom layout.
Optionally, you can specify the percentage of the screen width that you want
the Primary Column to occupy.
- **Secondary**: This is an optional column that can either be full height of
split horizontally into a fixed number of maximum rows. There can be any
number of Secondary Columns in a custom layout.
- **Tertiary**: This is the final column where any remaining windows will be
split horizontally into rows as they get added.
If there is only one window on the screen when a custom layout is selected,
that window will take up the full work area of the screen.
If the number of windows is equal to or less than the total number of columns
defined in a custom layout, the windows will be arranged in an equal-width
columns.
When the number of windows is greater than the number of columns defined in the
custom layout, the windows will begin to be arranged according to the
constraints set on the Primary and Secondary columns of the layout.
Here is an example custom layout that can be used as a starting point for your
own:
```yaml
- column: Secondary
configuration: !Horizontal 2 # max number of rows
- column: Primary
configuration: !WidthPercentage 50 # percentage of screen
- column: Tertiary
configuration: Horizontal
```
<!-- TODO: Record a new video -->
[![Watch the tutorial video](https://img.youtube.com/vi/SgmBHKEOcQ4/hqdefault.jpg)](https://www.youtube.com/watch?v=SgmBHKEOcQ4)

View File

@@ -0,0 +1,16 @@
# Floating Windows
Sometimes you will want a specific application to be managed as a floating window.
You can add rules to enforce this behaviour in the `komorebi.json` configuration file.
```json
{
"floating_applications": [
{
"kind": "Title",
"id": "Media Player",
"matching_strategy": "Equals"
}
]
}
```

View File

@@ -1,34 +0,0 @@
# Focus Follows Mouse
`komorebi` supports two focus-follows-mouse implementations; the native Windows
Xmouse implementation, which treats the desktop, the task bar, and the system
tray as windows and switches focus to them eagerly, and a custom `komorebi`
implementation, which only considers windows managed by `komorebi` as valid
targets to switch focus to when moving the mouse.
To enable the `komorebi` implementation you must start the process with the
`--ffm` flag to explicitly enable the feature. This is because the mouse
tracking required for this feature significantly increases the CPU usage of the
process (on my machine, it jumps from <1% to ~4~), and this CPU increase
persists regardless of whether focus-follows-mouse is enabled or disabled at
any given time via `komorebic`'s configuration commands.
If the `komorebi` process has been started with the `--ffm` flag, you can
enable focus follows mouse behaviour in the `komorebi.json` configuration file.
```json
{
"focus_follows_mouse": "Komorebi"
}
```
When calling any of the `komorebic` commands related to focus-follows-mouse
functionality, the `windows` implementation will be chosen as the default
implementation. You can optionally specify the `komorebi` implementation by
passing it as an argument to the `--implementation` flag:
```powershell
komorebic.exe toggle-focus-follows-mouse --implementation komorebi
```

View File

@@ -5,12 +5,12 @@ applications are [already generated for
you](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
Sometimes you will want a specific application to never be tiled, and instead
float all the time. You can add rules to enforce this behaviour in the
be completely ignored. You can add rules to enforce this behaviour in the
`komorebi.json` configuration file.
```json
{
"float_rules": [
"ignore_rules": [
{
"kind": "Title",
"id": "Media Player",

View File

@@ -0,0 +1,19 @@
# Multiple Bar Instances
If you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so
by maintaining multiple `komorebi.bar.json` configuration files and specifying their paths in the `bar_configurations`
array in your `komorebi.json` configuration file.
```json
{
"bar_configurations": [
"C:/Users/LGUG2Z/komorebi.bar.monitor1.json",
"C:/Users/LGUG2Z/komorebi.bar.monitor2.json"
]
}
```
You may also use `$Env:USERPROFILE` or `$Env:KOMOREBI_CONFIG_HOME` when specifying the paths.
The main difference between different `komorebi.bar.json` files will be the value of `monitor.index` which is used to
target the monitor for each instance of `komorebi-bar`.

View File

@@ -62,7 +62,7 @@ using `default_workspace_padding` and `default_container_padding`.
You may have seen videos and screenshots of people using `komorebi` with a
thick, colourful active window border. You can also enable this by setting
`active_window_border` to `true`. However, please be warned that this feature
`border` to `true`. However, please be warned that this feature
is a crude hack trying to compensate for the insistence of Microsoft Windows
design teams to make custom borders with widths that are actually visible to
the user a thing of the past and removing this capability from the Win32 API.
@@ -162,6 +162,8 @@ If you have an ultrawide monitor, I recommend using this layout.
If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layouts) this is almost exactly the same!
The `grid` layout does not support resizing windows tiles.
```
+-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
| | | | | | | | | | | | | | |
@@ -177,7 +179,7 @@ If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layou
`whkd` is a fairly basic piece of software with a simple configuration format:
key bindings go to the left of the colon, and shell commands go to the right of the
colon. By default, the `whkdrc` file should be located in the `$Env:USERPROFILE/.config/` directory.
colon.
Please remember that `whkd` does not support overriding Microsoft's limitations
on hotkey bindings that include the `Windows` key. If this is important to you,
@@ -188,6 +190,25 @@ bindings for `komorebic` commands instead.
{% include "./whkdrc.sample" %}
```
### Configuration
`whkd` searches for a `whkdrc` configuration file in the following locations:
* `$Env:WHKD_CONFIG_HOME`
* `$Env:USERPROFILE/.config`
It is also possible to change a hotkey behavior depending on which application has focus:
```
alt + n [
# ProcessName as shown by `Get-Process`
Firefox : echo "hello firefox"
# Spaces are fine, no quotes required
Google Chrome : echo "hello chrome"
]
```
### Setting .shell
There is one special directive at the top of the file, `.shell` which can be

View File

@@ -134,6 +134,26 @@ an offline machine to install.
Once installed, proceed to get the [example configurations](example-configurations.md) (none of the commands for
first-time set up and running komorebi require an internet connection).
## Upgrades
Before upgrading, make sure to run `komorebic stop --whkd --bar`. This is to ensure that all the current
komorebi-related exe files can be replaced without issue.
Then, depending on whether you installed via `scoop` or `winget`, you can run the appropriate command:
```powershell
# for winget
winget upgrade LGUG2Z.komorebi
```
```powershell
# for scoop
scoop update komorebi
```
Once the upgrade is completed you can confirm that you have the latest version by running `komorebic --version`, and
then start it with `komorebic start --whkd --bar`.
## Uninstallation
Before uninstalling, first run `komorebic stop --whkd --bar` to make sure that
@@ -147,7 +167,7 @@ files created by the `quickstart` command and any other runtime files:
```powershell
rm $Env:USERPROFILE\komorebi.json
rm $Env:USERPROFILE\applications.yaml
rm $Env:USERPROFILE\applications.json
rm $Env:USERPROFILE\.config\whkdrc
rm -r -Force $Env:LOCALAPPDATA\komorebi
```

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.29/schema.bar.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.30/schema.bar.json",
"monitor": {
"index": 0,
"work_area_offset": {

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.29/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.30/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",
"default_workspace_padding": 20,

View File

@@ -1,58 +0,0 @@
# v0.1.22
In addition to the [changelog](https://github.com/LGUG2Z/komorebi/releases/tag/v0.1.22) of new features and fixes,
please note the following changes from `v0.1.21` to adjust your configuration files accordingly.
## tl;dr
The way windows are sized and drawn has been improved to remove the need to manually specify and remove invisible
borders for applications that overflow them. If you use the active window border, the first time you launch `v0.1.22`
you may end up with a _huge_ border due to these changes.
`active_window_border_width` and `active_window_border_offset` have been renamed to `border_width` and `border_offset`
as they now also apply outside the context of the active window border.
```json
{
"active_window_border": true,
"border_width": 8,
"border_offset": -1
}
```
Users of the active window border should start from these settings and read the notes below before making further
adjustments.
## Changes to `active_window_border`, and window sizing:
- The border no longer creates a second drop-shadow around the active window
- Windows are now sized to fill the layout region entirely, ignoring window decorations such as drop shadows
- Border offset now starts exactly at the paint edge of the window on all sides
- Windows are sized such that the border offset and border width are taken into account
## Recommended patterns
### Gapless
- Disable "transparency effects" Personalization > Colors
- Set the following settings in `komorebi.json`:
```json
{
"default_workspace_padding": 0,
"default_container_padding": 0,
"border_offset": -1,
"border_width": 0
}
```
### 1px border
A 1px border is drawn around the window edge. Users may see a gap for a single pixel, if the system theme has a
transparent edge - this is the windows themed edge, and is not present for all applications.
```json
{
"border_offset": 0,
"border_width": 1
}
```

View File

@@ -1,5 +1,15 @@
# Troubleshooting
## Phantom Tiles
Sometimes you may experience an application which leaves "ghost tiles" on a workspace, where there is space reserved for
a window but no window visible.
You can ignore these windows by following these steps:
* Run `komorebic visible-windows` to find details about the invisible window
* Using that information, [create a rule to ignore that window](common-workflows/ignore-windows.md)
## AutoHotKey executable not found
If you try to start komorebi with AHK using `komorebic start --ahk`, and you have
@@ -85,10 +95,10 @@ running `komorebic stop` and `komorebic start`.
To avoid waiting an eternity:
- _Control Panel_ -> _Hardware and Sound_ -> _Power Options_ -> _Edit Plan
Settings_
- _Control Panel_ -> _Hardware and Sound_ -> _Power Options_ -> _Edit Plan
Settings_
_Turn off the display: 1 minute_
_Turn off the display: 1 minute_
Allow a minute for the display to reset, then once it actually shuts off
allow for any additional time as prompted by your monitor for the cycle to

View File

@@ -26,17 +26,17 @@ install:
run target:
cargo +stable run --bin {{ target }} --locked
warn $RUST_LOG="warn":
just run
warn target $RUST_LOG="warn":
just run {{ target }}
info $RUST_LOG="info":
just run
info target $RUST_LOG="info":
just run {{ target }}
debug $RUST_LOG="debug":
just run
debug target $RUST_LOG="debug":
just run {{ target }}
trace $RUST_LOG="trace":
just run
trace target $RUST_LOG="trace":
just run {{ target }}
deadlock $RUST_LOG="trace":
cargo +stable run --bin komorebi --locked --features deadlock_detection

View File

@@ -16,7 +16,7 @@ pub struct KomobarConfig {
/// Bar positioning options
#[serde(alias = "viewport")]
pub position: Option<PositionConfig>,
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/struct.Frame.html)
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)
pub frame: Option<FrameConfig>,
/// Monitor options
pub monitor: MonitorConfig,
@@ -136,11 +136,13 @@ impl From<Position> for Pos2 {
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>,
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)
name: komorebi_themes::Base16,
accent: Option<komorebi_themes::Base16Value>,
},

View File

@@ -451,15 +451,23 @@ impl KomorebiNotificationState {
}
},
Ok(notification) => {
if let NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(path)) =
notification.event
{
if let Ok(config) = komorebi_client::StaticConfig::read(&path) {
if let Some(theme) = config.theme {
apply_theme(ctx, KomobarTheme::from(theme), bg_color);
tracing::info!("applied theme from updated komorebi.json");
match notification.event {
NotificationEvent::WindowManager(_) => {}
NotificationEvent::Socket(message) => match message {
SocketMessage::ReloadStaticConfiguration(path) => {
if let Ok(config) = komorebi_client::StaticConfig::read(&path) {
if let Some(theme) = config.theme {
apply_theme(ctx, KomobarTheme::from(theme), bg_color.clone());
tracing::info!("applied theme from updated komorebi.json");
}
}
}
}
SocketMessage::Theme(theme) => {
apply_theme(ctx, KomobarTheme::from(theme), bg_color);
tracing::info!("applied theme from komorebi socket message");
}
_ => {}
},
}
self.mouse_follows_focus = notification.state.mouse_follows_focus;

View File

@@ -4,9 +4,11 @@ version = "0.1.30"
edition = "2021"
[dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "57c38257cb0c6434321320d3746049bd58c34674" }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "f579847bf2f552b144361d5a78ed8cf360b55cbb" }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui28"] }
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "c11fbe2a3a4681485c5065b899a4c4d85fad3b04" }
#catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "f579847bf2f552b144361d5a78ed8cf360b55cbb" }
catppuccin-egui = { version = "5", default-features = false, features = ["egui29"] }
eframe = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_variant = "0.1"
strum = "0.26"

View File

@@ -4,10 +4,12 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::IntoEnumIterator;
pub use base16_egui_themes::Base16;
pub use catppuccin_egui;
pub use eframe::egui::Color32;
use serde_variant::to_variant_name;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type")]
@@ -24,6 +26,28 @@ pub enum Theme {
},
}
impl Theme {
pub fn variant_names(&self) -> Vec<String> {
match self {
Theme::Catppuccin { .. } => {
vec![
"Frappe".to_string(),
"Latte".to_string(),
"Macchiato".to_string(),
"Mocha".to_string(),
]
}
Theme::Base16 { .. } => Base16::iter()
.map(|variant| {
to_variant_name(&variant)
.expect("could not convert to variant name")
.to_string()
})
.collect(),
}
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub enum Base16Value {
Base00,

View File

@@ -89,7 +89,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
);
for (_, border) in borders.iter() {
border.destroy()?;
let _ = border.destroy();
}
borders.clear();
@@ -107,7 +107,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
for hwnd in remaining_hwnds {
Border::from(hwnd).destroy()?;
let _ = Border::from(hwnd).destroy();
}
}
@@ -157,7 +157,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let focused_workspace_idx =
state.monitors.elements()[focused_monitor_idx].focused_workspace_idx();
let monitors = state.monitors.clone();
let pending_move_op = state.pending_move_op;
let weak_pending_move_op = Arc::downgrade(&state.pending_move_op);
let pending_move_op = *state.pending_move_op;
let floating_window_hwnds = state.monitors.elements()[focused_monitor_idx].workspaces()
[focused_workspace_idx]
.floating_windows()
@@ -413,7 +414,19 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
c.focused_window().copied().unwrap_or_default().hwnd,
)?;
while WindowsApi::lbutton_is_pressed() {
// We create a new variable to track the actual pending move op so
// that the other variable `pending_move_op` still holds the
// pending move info so that when the move ends we know on the next
// notification that the previous pending move and pending move are
// different (because a move just finished) and still handle the
// notification. If otherwise we updated the pending_move_op here
// directly then the next pending move after finish would be the
// same because we had already updated it here.
let mut sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
while sync_pending_move_op.is_some() {
sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
@@ -496,7 +509,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if pending_move_op.is_some() && hwnd == notification_hwnd {
let mut rect = WindowsApi::window_rect(hwnd)?;
while WindowsApi::lbutton_is_pressed() {
// Check comment above for containers move
let mut sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
while sync_pending_move_op.is_some() {
sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
let border = match borders.entry(hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {

View File

@@ -14,6 +14,7 @@ use serde::Serialize;
use strum::Display;
use strum::EnumString;
use crate::KomorebiTheme;
pub use animation::AnimationStyle;
pub use arrangement::Arrangement;
pub use arrangement::Axis;
@@ -143,6 +144,7 @@ pub enum SocketMessage {
WatchConfiguration(bool),
CompleteConfiguration,
AltFocusHack(bool),
Theme(KomorebiTheme),
Animation(bool),
AnimationDuration(u64),
AnimationFps(u64),

View File

@@ -20,6 +20,7 @@ pub mod set_window_position;
pub mod stackbar_manager;
pub mod static_config;
pub mod styles;
pub mod theme_manager;
pub mod transparency_manager;
pub mod window;
pub mod window_manager;
@@ -300,10 +301,12 @@ pub struct Notification {
}
pub fn notify_subscribers(notification: Notification, state_has_been_modified: bool) -> Result<()> {
let is_subscription_event = matches!(
let is_override_event = matches!(
notification.event,
NotificationEvent::Socket(SocketMessage::AddSubscriberSocket(_))
| NotificationEvent::Socket(SocketMessage::AddSubscriberSocketWithOptions(_, _))
| NotificationEvent::Socket(SocketMessage::Theme(_))
| NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(_))
);
let notification = &serde_json::to_string(&notification)?;
@@ -318,7 +321,7 @@ pub fn notify_subscribers(notification: Notification, state_has_been_modified: b
.unwrap_or_default()
.filter_state_changes;
if !apply_state_filter || state_has_been_modified || is_subscription_event {
if !apply_state_filter || state_has_been_modified || is_override_event {
match UnixStream::connect(path) {
Ok(mut stream) => {
tracing::debug!("pushed notification to subscriber: {socket}");

View File

@@ -38,6 +38,7 @@ use komorebi::process_movement::listen_for_movements;
use komorebi::reaper;
use komorebi::stackbar_manager;
use komorebi::static_config::StaticConfig;
use komorebi::theme_manager;
use komorebi::transparency_manager;
use komorebi::window_manager::WindowManager;
use komorebi::windows_api::WindowsApi;
@@ -271,6 +272,7 @@ fn main() -> Result<()> {
monitor_reconciliator::listen_for_notifications(wm.clone())?;
reaper::watch_for_orphans(wm.clone());
focus_manager::listen_for_notifications(wm.clone());
theme_manager::listen_for_notifications();
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {

View File

@@ -51,6 +51,7 @@ use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
use crate::stackbar_manager::STACKBAR_FONT_SIZE;
use crate::static_config::StaticConfig;
use crate::theme_manager;
use crate::transparency_manager;
use crate::window::RuleDebug;
use crate::window::Window;
@@ -1588,6 +1589,9 @@ impl WindowManager {
reply.write_all(schema.as_bytes())?;
}
SocketMessage::Theme(theme) => {
theme_manager::send_notification(theme);
}
// Deprecated commands
SocketMessage::AltFocusHack(_)
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}

View File

@@ -228,8 +228,7 @@ impl WindowManager {
.is_some();
if !window.is_window()
|| should_act
|| !programmatically_hidden_hwnds.contains(&window.hwnd)
|| (should_act && !programmatically_hidden_hwnds.contains(&window.hwnd))
{
hide = true;
}
@@ -279,158 +278,190 @@ impl WindowManager {
WindowManagerEvent::Show(_, window)
| WindowManagerEvent::Manage(window)
| WindowManagerEvent::Uncloak(_, window) => {
let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx =
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
if matches!(event, WindowManagerEvent::Uncloak(_, _))
&& self.uncloack_to_ignore >= 1
{
tracing::info!("ignoring uncloak after monocle move by mouse across monitors");
self.uncloack_to_ignore = self.uncloack_to_ignore.saturating_sub(1);
} else {
let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx =
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
let mut needs_reconciliation = false;
let mut needs_reconciliation = false;
for (i, monitors) in self.monitors().iter().enumerate() {
for (j, workspace) in monitors.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
// At this point we know we are going to send a notification to the workspace reconciliator
// So we get the topmost window returned by EnumWindows, which is almost always the window
// that has been selected by alt-tab
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
if let Some(first) =
alt_tab_windows.iter().find(|w| w.title().is_ok())
for (i, monitors) in self.monitors().iter().enumerate() {
for (j, workspace) in monitors.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
// At this point we know we are going to send a notification to the workspace reconciliator
// So we get the topmost window returned by EnumWindows, which is almost always the window
// that has been selected by alt-tab
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
if let Some(first) =
alt_tab_windows.iter().find(|w| w.title().is_ok())
{
// If our record of this HWND hasn't been updated in over a minute
let mut instant = ALT_TAB_HWND_INSTANT.lock();
if instant.elapsed().gt(&Duration::from_secs(1)) {
// Update our record with the HWND we just found
ALT_TAB_HWND.store(Some(first.hwnd));
// Update the timestamp of our record
*instant = Instant::now();
}
}
}
workspace_reconciliator::send_notification(i, j);
needs_reconciliation = true;
}
}
}
// There are some applications such as Firefox where, if they are focused when a
// workspace switch takes place, it will fire an additional Show event, which will
// result in them being associated with both the original workspace and the workspace
// being switched to. This loop is to try to ensure that we don't end up with
// duplicates across multiple workspaces, as it results in ghost layout tiles.
let mut proceed = true;
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd)
&& i != self.focused_monitor_idx()
&& j != monitor.focused_workspace_idx()
{
tracing::debug!(
"ignoring show event for window already associated with another workspace"
);
window.hide();
proceed = false;
}
}
}
if proceed {
let mut behaviour = self.window_management_behaviour(
focused_monitor_idx,
focused_workspace_idx,
);
let workspace = self.focused_workspace_mut()?;
let workspace_contains_window = workspace.contains_window(window.hwnd);
let monocle_container = workspace.monocle_container().clone();
if !workspace_contains_window && !needs_reconciliation {
let floating_applications = FLOATING_APPLICATIONS.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let mut should_float = false;
if !floating_applications.is_empty() {
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) =
(window.title(), window.exe(), window.class(), window.path())
{
// If our record of this HWND hasn't been updated in over a minute
let mut instant = ALT_TAB_HWND_INSTANT.lock();
if instant.elapsed().gt(&Duration::from_secs(1)) {
// Update our record with the HWND we just found
ALT_TAB_HWND.store(Some(first.hwnd));
// Update the timestamp of our record
*instant = Instant::now();
should_float = should_act(
&title,
&exe_name,
&class,
&path,
&floating_applications,
&regex_identifiers,
)
.is_some();
}
}
behaviour.float_override = behaviour.float_override
|| (should_float
&& !matches!(event, WindowManagerEvent::Manage(_)));
if behaviour.float_override {
workspace.floating_windows_mut().push(window);
self.update_focused_workspace(false, false)?;
} else {
match behaviour.current_behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
self.update_focused_workspace(false, false)?;
}
WindowContainerBehaviour::Append => {
workspace
.focused_container_mut()
.ok_or_else(|| {
anyhow!("there is no focused container")
})?
.add_window(window);
self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification();
}
}
}
workspace_reconciliator::send_notification(i, j);
needs_reconciliation = true;
}
}
}
// There are some applications such as Firefox where, if they are focused when a
// workspace switch takes place, it will fire an additional Show event, which will
// result in them being associated with both the original workspace and the workspace
// being switched to. This loop is to try to ensure that we don't end up with
// duplicates across multiple workspaces, as it results in ghost layout tiles.
let mut proceed = true;
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
if workspace.container_for_window(window.hwnd).is_some()
&& i != self.focused_monitor_idx()
&& j != monitor.focused_workspace_idx()
{
tracing::debug!(
"ignoring show event for window already associated with another workspace"
);
window.hide();
proceed = false;
}
}
}
if proceed {
let mut behaviour = self
.window_management_behaviour(focused_monitor_idx, focused_workspace_idx);
let workspace = self.focused_workspace_mut()?;
let workspace_contains_window = workspace.contains_window(window.hwnd);
let monocle_container = workspace.monocle_container().clone();
if !workspace_contains_window && !needs_reconciliation {
let floating_applications = FLOATING_APPLICATIONS.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let mut should_float = false;
if !floating_applications.is_empty() {
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) =
(window.title(), window.exe(), window.class(), window.path())
if (self.focused_workspace()?.containers().len() == 1
&& self.focused_workspace()?.floating_windows().is_empty())
|| (self.focused_workspace()?.containers().is_empty()
&& self.focused_workspace()?.floating_windows().len() == 1)
{
should_float = should_act(
&title,
&exe_name,
&class,
&path,
&floating_applications,
&regex_identifiers,
)
.is_some();
// If after adding this window the workspace only contains 1 window, it
// means it was previously empty and we focused the desktop to unfocus
// any previous window from other workspace, so now we need to focus
// this window again. This is needed because sometimes some windows
// first send the `FocusChange` event and only the `Show` event after
// and we will be focusing the desktop on the `FocusChange` event since
// it is still empty.
window.focus(self.mouse_follows_focus)?;
}
}
behaviour.float_override = behaviour.float_override
|| (should_float && !matches!(event, WindowManagerEvent::Manage(_)));
if behaviour.float_override {
workspace.floating_windows_mut().push(window);
self.update_focused_workspace(false, false)?;
} else {
match behaviour.current_behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
self.update_focused_workspace(false, false)?;
}
WindowContainerBehaviour::Append => {
workspace
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no focused container"))?
.add_window(window);
self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification();
if workspace_contains_window {
let mut monocle_window_event = false;
if let Some(ref monocle) = monocle_container {
if let Some(monocle_window) = monocle.focused_window() {
if monocle_window.hwnd == window.hwnd {
monocle_window_event = true;
}
}
}
}
}
if workspace_contains_window {
let mut monocle_window_event = false;
if let Some(ref monocle) = monocle_container {
if let Some(monocle_window) = monocle.focused_window() {
if monocle_window.hwnd == window.hwnd {
monocle_window_event = true;
}
if !monocle_window_event && monocle_container.is_some() {
window.hide();
}
}
if !monocle_window_event && monocle_container.is_some() {
window.hide();
}
}
}
}
WindowManagerEvent::MoveResizeStart(_, window) => {
if *self.focused_workspace()?.tile() {
let monitor_idx = self.focused_monitor_idx();
let workspace_idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
.focused_workspace_idx();
let container_idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace with this idx"))?
.focused_container_idx();
let monitor_idx = self.focused_monitor_idx();
let workspace_idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
.focused_workspace_idx();
WindowsApi::bring_window_to_top(window.hwnd)?;
WindowsApi::bring_window_to_top(window.hwnd)?;
self.pending_move_op =
Option::from((monitor_idx, workspace_idx, container_idx));
}
let pending_move_op = Arc::make_mut(&mut self.pending_move_op);
*pending_move_op = Option::from((monitor_idx, workspace_idx, window.hwnd));
}
WindowManagerEvent::MoveResizeEnd(_, window) => {
// We need this because if the event ends on a different monitor,
// that monitor will already have been focused and updated in the state
let pending = self.pending_move_op;
let pending = *self.pending_move_op;
// Always consume the pending move op whenever this event is handled
self.pending_move_op = None;
let pending_move_op = Arc::make_mut(&mut self.pending_move_op);
*pending_move_op = None;
// If the window handles don't match then something went wrong and the pending move
// is not related to this current move, if so abort this operation.
if let Some((_, _, w_hwnd)) = pending {
if w_hwnd != window.hwnd {
color_eyre::eyre::bail!(
"window handles for move operation don't match: {} != {}",
w_hwnd,
window.hwnd
);
}
}
let target_monitor_idx = self
.monitor_idx_from_current_pos()
@@ -476,8 +507,7 @@ impl WindowManager {
.get(origin_workspace_idx)
.ok_or_else(|| anyhow!("cannot get workspace idx"))?;
let managed_window =
origin_workspace.contains_managed_window(window.hwnd);
let managed_window = origin_workspace.contains_window(window.hwnd);
if !managed_window {
moved_across_monitors = false;
@@ -507,11 +537,8 @@ impl WindowManager {
tracing::info!("moving with mouse");
if moved_across_monitors {
if let Some((
origin_monitor_idx,
origin_workspace_idx,
origin_container_idx,
)) = pending
if let Some((origin_monitor_idx, origin_workspace_idx, w_hwnd)) =
pending
{
let target_workspace_idx = self
.monitors()
@@ -531,18 +558,13 @@ impl WindowManager {
// Default to 0 in the case of an empty workspace
.unwrap_or(0);
self.transfer_container(
(
origin_monitor_idx,
origin_workspace_idx,
origin_container_idx,
),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
let origin = (origin_monitor_idx, origin_workspace_idx, w_hwnd);
let target = (
target_monitor_idx,
target_workspace_idx,
target_container_idx,
);
self.transfer_window(origin, target)?;
// We want to make sure both the origin and target monitors are updated,
// so that we don't have ghost tiles until we force an interaction on
@@ -554,9 +576,10 @@ impl WindowManager {
self.focus_monitor(target_monitor_idx)?;
self.focus_workspace(target_workspace_idx)?;
self.update_focused_workspace(false, false)?;
// Make sure to give focus to the moved window again
window.focus(self.mouse_follows_focus)?;
}
// Here we handle a simple move on the same monitor which is treated as
// a container swap
} else if window_management_behaviour.float_override {
workspace.floating_windows_mut().push(window);
self.update_focused_workspace(false, false)?;

View File

@@ -20,6 +20,7 @@ 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::theme_manager;
use crate::transparency_manager;
use crate::window;
use crate::window_manager::WindowManager;
@@ -268,7 +269,7 @@ pub struct StaticConfig {
/// Enable or disable mouse follows focus (default: true)
#[serde(skip_serializing_if = "Option::is_none")]
pub mouse_follows_focus: Option<bool>,
/// Path to applications.yaml from komorebi-application-specific-configurations (default: None)
/// Path to applications.json from komorebi-application-specific-configurations (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub app_specific_configuration_path: Option<PathBuf>,
/// Width of the window border (default: 8)
@@ -386,7 +387,7 @@ pub struct AnimationsConfig {
pub enum KomorebiTheme {
/// A theme from catppuccin-egui
Catppuccin {
/// Name of the Catppuccin theme
/// 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)
single_border: Option<komorebi_themes::CatppuccinValue>,
@@ -409,7 +410,7 @@ pub enum KomorebiTheme {
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)
name: komorebi_themes::Base16,
/// Border colour when the container contains a single window (default: Base0D)
single_border: Option<komorebi_themes::Base16Value>,
@@ -457,6 +458,7 @@ impl StaticConfig {
println!("\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n")
}
}
pub fn aliases(raw: &str) {
let mut map = HashMap::new();
map.insert("border", ["active_window_border"]);
@@ -464,6 +466,8 @@ impl StaticConfig {
map.insert("border_offset", ["active_window_border_offset"]);
map.insert("border_colours", ["active_window_border_colours"]);
map.insert("border_style", ["active_window_border_style"]);
map.insert("applications.json", ["applications.yaml"]);
map.insert("ignore_rules", ["float_rules"]);
let mut display = false;
@@ -815,151 +819,7 @@ impl StaticConfig {
}
if let Some(theme) = &self.theme {
let (
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
) = match theme {
KomorebiTheme::Catppuccin {
name,
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
..
} => {
let single_border = single_border
.unwrap_or(komorebi_themes::CatppuccinValue::Blue)
.color32(name.as_theme());
let stack_border = stack_border
.unwrap_or(komorebi_themes::CatppuccinValue::Green)
.color32(name.as_theme());
let monocle_border = monocle_border
.unwrap_or(komorebi_themes::CatppuccinValue::Pink)
.color32(name.as_theme());
let floating_border = floating_border
.unwrap_or(komorebi_themes::CatppuccinValue::Yellow)
.color32(name.as_theme());
let unfocused_border = unfocused_border
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
.color32(name.as_theme());
let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::CatppuccinValue::Green)
.color32(name.as_theme());
let stackbar_unfocused_text = stackbar_unfocused_text
.unwrap_or(komorebi_themes::CatppuccinValue::Text)
.color32(name.as_theme());
let stackbar_background = stackbar_background
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
.color32(name.as_theme());
(
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
)
}
KomorebiTheme::Base16 {
name,
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
..
} => {
let single_border = single_border
.unwrap_or(komorebi_themes::Base16Value::Base0D)
.color32(*name);
let stack_border = stack_border
.unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name);
let monocle_border = monocle_border
.unwrap_or(komorebi_themes::Base16Value::Base0F)
.color32(*name);
let unfocused_border = unfocused_border
.unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name);
let floating_border = floating_border
.unwrap_or(komorebi_themes::Base16Value::Base09)
.color32(*name);
let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name);
let stackbar_unfocused_text = stackbar_unfocused_text
.unwrap_or(komorebi_themes::Base16Value::Base05)
.color32(*name);
let stackbar_background = stackbar_background
.unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name);
(
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
)
}
};
border_manager::FOCUSED.store(u32::from(Colour::from(single_border)), Ordering::SeqCst);
border_manager::MONOCLE
.store(u32::from(Colour::from(monocle_border)), Ordering::SeqCst);
border_manager::STACK.store(u32::from(Colour::from(stack_border)), Ordering::SeqCst);
border_manager::FLOATING
.store(u32::from(Colour::from(floating_border)), Ordering::SeqCst);
border_manager::UNFOCUSED
.store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst);
STACKBAR_TAB_BACKGROUND_COLOUR.store(
u32::from(Colour::from(stackbar_background)),
Ordering::SeqCst,
);
STACKBAR_FOCUSED_TEXT_COLOUR.store(
u32::from(Colour::from(stackbar_focused_text)),
Ordering::SeqCst,
);
STACKBAR_UNFOCUSED_TEXT_COLOUR.store(
u32::from(Colour::from(stackbar_unfocused_text)),
Ordering::SeqCst,
);
theme_manager::send_notification(*theme);
}
if let Some(path) = &self.app_specific_configuration_path {
@@ -1192,8 +1052,9 @@ impl StaticConfig {
mouse_follows_focus: value.mouse_follows_focus.unwrap_or(true),
hotwatch: Hotwatch::new()?,
has_pending_raise_op: false,
pending_move_op: None,
pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0,
};
match value.focus_follows_mouse {

View File

@@ -0,0 +1,224 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::border_manager;
use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::Colour;
use crate::KomorebiTheme;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::OnceLock;
pub struct Notification(KomorebiTheme);
pub static CURRENT_THEME: AtomicCell<Option<KomorebiTheme>> = AtomicCell::new(None);
impl Deref for Notification {
type Target = KomorebiTheme;
fn deref(&self) -> &Self::Target {
&self.0
}
}
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
}
fn event_tx() -> Sender<Notification> {
channel().0.clone()
}
fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
// Currently this should only be used for async focus updates, such as
// when an animation finishes and we need to focus to set the cursor
// position if the user has mouse follows focus enabled
pub fn send_notification(theme: KomorebiTheme) {
if event_tx().try_send(Notification(theme)).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn listen_for_notifications() {
std::thread::spawn(move || loop {
match handle_notifications() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
}
pub fn handle_notifications() -> color_eyre::Result<()> {
tracing::info!("listening");
let receiver = event_rx();
for notification in receiver {
let theme = &notification.0;
let (
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
) = match theme {
KomorebiTheme::Catppuccin {
name,
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
..
} => {
let single_border = single_border
.unwrap_or(komorebi_themes::CatppuccinValue::Blue)
.color32(name.as_theme());
let stack_border = stack_border
.unwrap_or(komorebi_themes::CatppuccinValue::Green)
.color32(name.as_theme());
let monocle_border = monocle_border
.unwrap_or(komorebi_themes::CatppuccinValue::Pink)
.color32(name.as_theme());
let floating_border = floating_border
.unwrap_or(komorebi_themes::CatppuccinValue::Yellow)
.color32(name.as_theme());
let unfocused_border = unfocused_border
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
.color32(name.as_theme());
let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::CatppuccinValue::Green)
.color32(name.as_theme());
let stackbar_unfocused_text = stackbar_unfocused_text
.unwrap_or(komorebi_themes::CatppuccinValue::Text)
.color32(name.as_theme());
let stackbar_background = stackbar_background
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
.color32(name.as_theme());
(
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
)
}
KomorebiTheme::Base16 {
name,
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
..
} => {
let single_border = single_border
.unwrap_or(komorebi_themes::Base16Value::Base0D)
.color32(*name);
let stack_border = stack_border
.unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name);
let monocle_border = monocle_border
.unwrap_or(komorebi_themes::Base16Value::Base0F)
.color32(*name);
let unfocused_border = unfocused_border
.unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name);
let floating_border = floating_border
.unwrap_or(komorebi_themes::Base16Value::Base09)
.color32(*name);
let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name);
let stackbar_unfocused_text = stackbar_unfocused_text
.unwrap_or(komorebi_themes::Base16Value::Base05)
.color32(*name);
let stackbar_background = stackbar_background
.unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name);
(
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
stackbar_background,
)
}
};
border_manager::FOCUSED.store(u32::from(Colour::from(single_border)), Ordering::SeqCst);
border_manager::MONOCLE.store(u32::from(Colour::from(monocle_border)), Ordering::SeqCst);
border_manager::STACK.store(u32::from(Colour::from(stack_border)), Ordering::SeqCst);
border_manager::FLOATING.store(u32::from(Colour::from(floating_border)), Ordering::SeqCst);
border_manager::UNFOCUSED
.store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst);
STACKBAR_TAB_BACKGROUND_COLOUR.store(
u32::from(Colour::from(stackbar_background)),
Ordering::SeqCst,
);
STACKBAR_FOCUSED_TEXT_COLOUR.store(
u32::from(Colour::from(stackbar_focused_text)),
Ordering::SeqCst,
);
STACKBAR_UNFOCUSED_TEXT_COLOUR.store(
u32::from(Colour::from(stackbar_unfocused_text)),
Ordering::SeqCst,
);
CURRENT_THEME.store(Some(notification.0));
border_manager::send_notification(None);
stackbar_manager::send_notification();
}
Ok(())
}

View File

@@ -41,6 +41,7 @@ use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
@@ -565,6 +566,7 @@ pub struct RuleDebug {
pub matches_ignore_identifier: Option<MatchingRule>,
pub matches_managed_override: Option<MatchingRule>,
pub matches_layered_whitelist: Option<MatchingRule>,
pub matches_floating_applications: Option<MatchingRule>,
pub matches_wsl2_gui: Option<String>,
pub matches_no_titlebar: Option<String>,
}
@@ -592,7 +594,7 @@ fn window_is_eligible(
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let should_float = if let Some(rule) = should_act(
let should_ignore = if let Some(rule) = should_act(
title,
exe_name,
class,
@@ -621,7 +623,19 @@ fn window_is_eligible(
false
};
if should_float && !managed_override {
let floating_identifiers = FLOATING_APPLICATIONS.lock();
if let Some(rule) = should_act(
title,
exe_name,
class,
path,
&floating_identifiers,
&regex_identifiers,
) {
debug.matches_floating_applications = Some(rule);
}
if should_ignore && !managed_override {
return false;
}
@@ -763,10 +777,7 @@ pub fn should_act_individual(
let mut should_act = false;
match identifier.matching_strategy {
None => {
panic!("there is no matching strategy identified for this rule");
}
Some(MatchingStrategy::Legacy) => match identifier.kind {
None | Some(MatchingStrategy::Legacy) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
should_act = true;

View File

@@ -102,8 +102,9 @@ pub struct WindowManager {
pub hotwatch: Hotwatch,
pub virtual_desktop_id: Option<Vec<u8>>,
pub has_pending_raise_op: bool,
pub pending_move_op: Option<(usize, usize, usize)>,
pub pending_move_op: Arc<Option<(usize, usize, isize)>>,
pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,
pub uncloack_to_ignore: usize,
}
#[allow(clippy::struct_excessive_bools)]
@@ -288,6 +289,7 @@ struct EnforceWorkspaceRuleOp {
origin_workspace_idx: usize,
target_monitor_idx: usize,
target_workspace_idx: usize,
floating: bool,
}
impl EnforceWorkspaceRuleOp {
const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
@@ -338,8 +340,9 @@ impl WindowManager {
mouse_follows_focus: true,
hotwatch: Hotwatch::new()?,
has_pending_raise_op: false,
pending_move_op: None,
pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0,
})
}
@@ -505,6 +508,7 @@ impl WindowManager {
origin_workspace_idx: usize,
target_monitor_idx: usize,
target_workspace_idx: usize,
floating: bool,
to_move: &mut Vec<EnforceWorkspaceRuleOp>,
) -> () {
tracing::trace!(
@@ -521,6 +525,7 @@ impl WindowManager {
origin_workspace_idx,
target_monitor_idx,
target_workspace_idx,
floating,
});
}
@@ -576,6 +581,8 @@ impl WindowManager {
};
if matched {
let floating = workspace.floating_windows().contains(window);
if rule.initial_only {
if !already_moved_window_handles.contains(&window.hwnd) {
already_moved_window_handles.insert(window.hwnd);
@@ -587,6 +594,7 @@ impl WindowManager {
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
@@ -598,6 +606,7 @@ impl WindowManager {
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
@@ -616,17 +625,34 @@ impl WindowManager {
// Parse the operation and remove any windows that are not placed according to their rules
for op in &to_move {
let origin_workspace = self
let target_area = *self
.monitors_mut()
.get_mut(op.target_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
.work_area_size();
let origin_monitor = self
.monitors_mut()
.get_mut(op.origin_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
.ok_or_else(|| anyhow!("there is no monitor with that index"))?;
let origin_area = *origin_monitor.work_area_size();
let origin_workspace = origin_monitor
.workspaces_mut()
.get_mut(op.origin_workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
let mut window = Window::from(op.hwnd);
// If it is a floating window move it to the target area
if op.floating {
window.move_to_area(&origin_area, &target_area)?;
}
// Hide the window we are about to remove if it is on the currently focused workspace
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
Window::from(op.hwnd).hide();
window.hide();
should_update_focused_workspace = true;
}
@@ -656,7 +682,21 @@ impl WindowManager {
.get_mut(op.target_workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
target_workspace.new_container_for_window(Window::from(op.hwnd));
if op.floating {
target_workspace
.floating_windows_mut()
.push(Window::from(op.hwnd));
} else {
//TODO(alex-ds13): should this take into account the target workspace
//`window_container_behaviour`?
//In the case above a floating window should always be moved as floating,
//because it was set as so either manually by the user or by a
//`floating_applications` rule so it should stay that way. But a tiled window
//when moving to another workspace by a `workspace_rule` should honor that
//workspace `window_container_behaviour` in my opinion! Maybe this should be done
//on the `new_container_for_window` function instead.
target_workspace.new_container_for_window(Window::from(op.hwnd));
}
}
// Only re-tile the focused workspace if we need to
@@ -781,6 +821,133 @@ impl WindowManager {
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn transfer_window(
&mut self,
origin: (usize, usize, isize),
target: (usize, usize, usize),
) -> Result<()> {
let (origin_monitor_idx, origin_workspace_idx, w_hwnd) = origin;
let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;
let origin_workspace = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_else(|| anyhow!("cannot get monitor idx"))?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_else(|| anyhow!("cannot get workspace idx"))?;
let origin_container_idx = origin_workspace
.container_for_window(w_hwnd)
.and_then(|c| origin_workspace.containers().iter().position(|cc| cc == c));
if let Some(origin_container_idx) = origin_container_idx {
// Moving normal container window
self.transfer_container(
(
origin_monitor_idx,
origin_workspace_idx,
origin_container_idx,
),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
} else if let Some(idx) = origin_workspace
.floating_windows()
.iter()
.position(|w| w.hwnd == w_hwnd)
{
// Moving floating window
// There is no need to physically move the floating window between areas with
// `move_to_area` because the user already did that, so we only need to transfer the
// window to the target `floating_windows`
let floating_window = origin_workspace.floating_windows_mut().remove(idx);
let target_workspace = self
.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no focused workspace for this monitor"))?;
target_workspace
.floating_windows_mut()
.push(floating_window);
} else if origin_workspace
.monocle_container()
.as_ref()
.and_then(|monocle| monocle.focused_window().map(|w| w.hwnd == w_hwnd))
.unwrap_or_default()
{
// Moving monocle container
if let Some(monocle_idx) = origin_workspace.monocle_container_restore_idx() {
let origin_workspace = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace for this monitor"))?;
let mut uncloack_amount = 0;
for container in origin_workspace.containers_mut() {
container.restore();
uncloack_amount += 1;
}
origin_workspace.reintegrate_monocle_container()?;
self.transfer_container(
(origin_monitor_idx, origin_workspace_idx, monocle_idx),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
// After we restore the origin workspace, some windows that were cloacked
// by the monocle might now be uncloacked which would trigger a workspace
// reconciliation since the focused monitor would be different from origin.
// That workspace reconciliation would focus the window on the origin monitor.
// So we need to ignore the uncloak events produced by the origin workspace
// restore to avoid that issue.
self.uncloack_to_ignore = uncloack_amount;
}
} else if origin_workspace
.maximized_window()
.as_ref()
.map(|max| max.hwnd == w_hwnd)
.unwrap_or_default()
{
// Moving maximized_window
if let Some(maximized_idx) = origin_workspace.maximized_window_restore_idx() {
self.focus_monitor(origin_monitor_idx)?;
let origin_monitor = self
.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no origin monitor"))?;
origin_monitor.focus_workspace(origin_workspace_idx)?;
self.unmaximize_window()?;
self.focus_monitor(target_monitor_idx)?;
let target_monitor = self
.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no target monitor"))?;
target_monitor.focus_workspace(target_workspace_idx)?;
self.transfer_container(
(origin_monitor_idx, origin_workspace_idx, maximized_idx),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn transfer_container(
&mut self,
@@ -1832,7 +1999,12 @@ impl WindowManager {
tracing::info!("cycling container windows");
let container = self.focused_container_mut()?;
let container =
if let Some(container) = self.focused_workspace_mut()?.monocle_container_mut() {
container
} else {
self.focused_container_mut()?
};
let len = NonZeroUsize::new(container.windows().len())
.ok_or_else(|| anyhow!("there must be at least one window in a container"))?;
@@ -1856,7 +2028,12 @@ impl WindowManager {
tracing::info!("focusing container window at index {idx}");
let container = self.focused_container_mut()?;
let container =
if let Some(container) = self.focused_workspace_mut()?.monocle_container_mut() {
container
} else {
self.focused_container_mut()?
};
let len = NonZeroUsize::new(container.windows().len())
.ok_or_else(|| anyhow!("there must be at least one window in a container"))?;
@@ -1972,8 +2149,16 @@ impl WindowManager {
new_idx
};
let mut target_container_is_stack = false;
if let Some(container) = workspace.containers().get(adjusted_new_index) {
if container.windows().len() > 1 {
target_container_is_stack = true;
}
}
if let Some(current) = workspace.focused_container() {
if current.windows().len() > 1 {
if current.windows().len() > 1 && !target_container_is_stack {
workspace.focus_container(adjusted_new_index);
workspace.move_window_to_container(current_container_idx)?;
} else {

View File

@@ -300,14 +300,21 @@ impl WindowsApi {
}
}
if monitors.elements().is_empty() {
monitors.elements_mut().push_back(m);
} else if let Some(preference) = index_preference {
while *preference > monitors.elements().len() {
if let Some(preference) = index_preference {
while *preference >= monitors.elements().len() {
monitors.elements_mut().push_back(Monitor::placeholder());
}
monitors.elements_mut().insert(*preference, m);
let current_name = monitors
.elements_mut()
.get(*preference)
.map_or("", |m| m.name());
if current_name == "PLACEHOLDER" {
let _ = monitors.elements_mut().remove(*preference);
monitors.elements_mut().insert(*preference, m);
} else {
monitors.elements_mut().insert(*preference, m);
}
} else {
monitors.elements_mut().push_back(m);
}

View File

@@ -431,7 +431,7 @@ impl Workspace {
}
}
for window in self.visible_windows_mut().into_iter().flatten() {
for window in self.visible_windows().into_iter().flatten() {
if !window.is_window() {
hwnds.push(window.hwnd);
}
@@ -1409,16 +1409,41 @@ impl Workspace {
pub fn visible_windows(&self) -> Vec<Option<&Window>> {
let mut vec = vec![];
vec.push(self.maximized_window().as_ref());
if let Some(monocle) = self.monocle_container() {
vec.push(monocle.focused_window());
}
for container in self.containers() {
vec.push(container.focused_window());
}
for window in self.floating_windows() {
vec.push(Some(window));
}
vec
}
pub fn visible_window_details(&self) -> Vec<WindowDetails> {
let mut vec: Vec<WindowDetails> = vec![];
if let Some(maximized) = self.maximized_window() {
if let Ok(details) = (*maximized).try_into() {
vec.push(details);
}
}
if let Some(monocle) = self.monocle_container() {
if let Some(focused) = monocle.focused_window() {
if let Ok(details) = (*focused).try_into() {
vec.push(details);
}
}
}
for container in self.containers() {
if let Some(focused) = container.focused_window() {
if let Ok(details) = (*focused).try_into() {
@@ -1427,13 +1452,10 @@ impl Workspace {
}
}
vec
}
pub fn visible_windows_mut(&mut self) -> Vec<Option<&mut Window>> {
let mut vec = vec![];
for container in self.containers_mut() {
vec.push(container.focused_window_mut());
for window in self.floating_windows() {
if let Ok(details) = (*window).try_into() {
vec.push(details);
}
}
vec

View File

@@ -88,6 +88,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let mouse_follows_focus = wm.mouse_follows_focus;
if let Some(monitor) = wm.focused_monitor_mut() {
let previous_idx = monitor.focused_workspace_idx();
monitor.set_last_focused_workspace(Option::from(previous_idx));
monitor.focus_workspace(notification.workspace_idx)?;
monitor.load_focused_workspace(mouse_follows_focus)?;
}

View File

@@ -1,9 +1,9 @@
fn main() {
if std::fs::metadata("applications.yaml").is_err() {
let applications_yaml = reqwest::blocking::get(
"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml"
if std::fs::metadata("applications.json").is_err() {
let applications_json = reqwest::blocking::get(
"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json"
).unwrap().text().unwrap();
std::fs::write("applications.yaml", applications_yaml).unwrap();
std::fs::write("applications.json", applications_json).unwrap();
}
shadow_rs::new().unwrap();

View File

@@ -748,6 +748,7 @@ struct AnimationStyle {
#[allow(clippy::struct_excessive_bools)]
struct Start {
/// Allow the use of komorebi's custom focus-follows-mouse implementation
#[clap(hide = true)]
#[clap(short, long = "ffm")]
ffm: bool,
/// Path to a static configuration JSON file
@@ -775,6 +776,9 @@ struct Stop {
/// Stop whkd if it is running as a background process
#[clap(long)]
whkd: bool,
/// Stop ahk if it is running as a background process
#[clap(long)]
ahk: bool,
/// Stop komorebi-bar if it is running as a background process
#[clap(long)]
bar: bool,
@@ -862,6 +866,7 @@ struct EnableAutostart {
#[clap(action, short, long)]
config: Option<PathBuf>,
/// Enable komorebi's custom focus-follows-mouse implementation
#[clap(hide = true)]
#[clap(short, long = "ffm")]
ffm: bool,
/// Enable autostart of whkd
@@ -1095,6 +1100,7 @@ enum SubCommand {
#[clap(arg_required_else_help = true)]
CycleLayout(CycleLayout),
/// Load a custom layout from file for the focused workspace
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
LoadCustomLayout(LoadCustomLayout),
/// Flip the layout on the focused workspace (BSP only)
@@ -1139,9 +1145,11 @@ enum SubCommand {
#[clap(arg_required_else_help = true)]
NamedWorkspaceLayout(NamedWorkspaceLayout),
/// Set a custom layout for the specified workspace
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
WorkspaceCustomLayout(WorkspaceCustomLayout),
/// Set a custom layout for the specified workspace
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
NamedWorkspaceCustomLayout(NamedWorkspaceCustomLayout),
/// Add a dynamic layout rule for the specified workspace
@@ -1151,9 +1159,11 @@ enum SubCommand {
#[clap(arg_required_else_help = true)]
NamedWorkspaceLayoutRule(NamedWorkspaceLayoutRule),
/// Add a dynamic custom layout for the specified workspace
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
WorkspaceCustomLayoutRule(WorkspaceCustomLayoutRule),
/// Add a dynamic custom layout for the specified workspace
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
NamedWorkspaceCustomLayoutRule(NamedWorkspaceCustomLayoutRule),
/// Clear all dynamic layout rules for the specified workspace
@@ -1312,9 +1322,11 @@ enum SubCommand {
#[clap(arg_required_else_help = true)]
AnimationStyle(AnimationStyle),
/// Enable or disable focus follows mouse for the operating system
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
FocusFollowsMouse(FocusFollowsMouse),
/// Toggle focus follows mouse for the operating system
#[clap(hide = true)]
#[clap(arg_required_else_help = true)]
ToggleFocusFollowsMouse(ToggleFocusFollowsMouse),
/// Enable or disable mouse follows focus on all workspaces
@@ -1337,11 +1349,12 @@ enum SubCommand {
/// Format a YAML file for use with the 'app-specific-configuration' command
#[clap(arg_required_else_help = true)]
#[clap(alias = "fmt-asc")]
#[clap(hide = true)]
FormatAppSpecificConfiguration(FormatAppSpecificConfiguration),
/// Fetch the latest version of applications.yaml from komorebi-application-specific-configuration
/// Fetch the latest version of applications.json from komorebi-application-specific-configuration
#[clap(alias = "fetch-asc")]
FetchAppSpecificConfiguration,
/// Generate a JSON Schema for applications.yaml
/// Generate a JSON Schema for applications.json
#[clap(alias = "asc-schema")]
ApplicationSpecificConfigurationSchema,
/// Generate a JSON Schema of subscription notifications
@@ -1399,6 +1412,14 @@ fn main() -> Result<()> {
"docgen",
"alt-focus-hack",
"identify-border-overflow-application",
"load-custom-layout",
"workspace-custom-layout",
"named-workspace-custom-layout",
"workspace-custom-layout-rule",
"named-workspace-custom-layout-rule",
"focus-follows-mouse",
"toggle-focus-follows-mouse",
"format-app-specific-configuration",
];
for cmd in subcommands {
@@ -1431,13 +1452,13 @@ fn main() -> Result<()> {
std::fs::write(HOME_DIR.join("komorebi.json"), komorebi_json)?;
std::fs::write(HOME_DIR.join("komorebi.bar.json"), komorebi_bar_json)?;
let applications_yaml = include_str!("../applications.yaml");
std::fs::write(HOME_DIR.join("applications.yaml"), applications_yaml)?;
let applications_json = include_str!("../applications.json");
std::fs::write(HOME_DIR.join("applications.json"), applications_json)?;
let whkdrc = include_str!("../../docs/whkdrc.sample");
std::fs::write(WHKD_CONFIG_DIR.join("whkdrc"), whkdrc)?;
println!("Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.yaml files created");
println!("Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created");
println!("You can now run komorebic start --whkd --bar");
}
SubCommand::EnableAutostart(args) => {
@@ -2090,11 +2111,16 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
}
println!("\nThank you for using komorebi!\n");
println!("* Become a sponsor https://github.com/sponsors/LGUG2Z - Even $1/month makes a big difference");
println!("# Sponsorship");
println!("* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference");
println!("* Leave a tip https://ko-fi.com/lgug2z - An alternative to GitHub Sponsors");
println!(
"* Subscribe to https://youtube.com/@LGUG2Z - Live dev videos and feature previews"
"* Subscribe to https://youtube.com/@LGUG2Z - Development videos, feature previews and release overviews"
);
println!("\n# Community");
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
println!("* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem");
println!("\n# Documentation");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
let bar_config = arg.config.map_or_else(
@@ -2152,6 +2178,35 @@ Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
}
}
if arg.ahk {
let script = r#"
if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
(Get-CimInstance Win32_Process | Where-Object {
($_.CommandLine -like '*komorebi.ahk"') -and
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe'))
} | Select-Object -First 1) | ForEach-Object {
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
}
} else {
(Get-WmiObject Win32_Process | Where-Object {
($_.CommandLine -like '*komorebi.ahk"') -and
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe'))
} | Select-Object -First 1) | ForEach-Object {
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
}
}
"#;
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
send_message(&SocketMessage::Stop)?;
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All);
@@ -2613,7 +2668,7 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
file.write_all(content.as_bytes())?;
println!("Latest version of applications.yaml from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n");
println!("Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n");
println!(
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
output_file.display().to_string().replace("\\", "/")

View File

@@ -57,6 +57,7 @@ nav:
- Troubleshooting: troubleshooting.md
- Common workflows:
- common-workflows/komorebi-config-home.md
- common-workflows/autostart.md
- common-workflows/animations.md
- common-workflows/autohotkey.md
- common-workflows/borders.md
@@ -64,14 +65,12 @@ nav:
- common-workflows/remove-gaps.md
- common-workflows/ignore-windows.md
- common-workflows/force-manage-windows.md
- common-workflows/floating-applications.md
- common-workflows/tray-and-multi-window-applications.md
- common-workflows/focus-follows-mouse.md
- common-workflows/mouse-follows-focus.md
- common-workflows/custom-layouts.md
- common-workflows/dynamic-layout-switching.md
- common-workflows/set-display-index.md
- Release notes:
- release/v0-1-22.md
- common-workflows/multiple-bar-instances.md
- Configuration reference: https://komorebi.lgug2z.com/schema
- Bar reference: https://komorebi-bar.lgug2z.com/schema
- CLI reference:
@@ -146,7 +145,6 @@ nav:
- cli/adjust-workspace-padding.md
- cli/change-layout.md
- cli/cycle-layout.md
- cli/load-custom-layout.md
- cli/flip-layout.md
- cli/promote.md
- cli/promote-focus.md
@@ -162,18 +160,17 @@ nav:
- cli/named-workspace-padding.md
- cli/workspace-layout.md
- cli/named-workspace-layout.md
- cli/workspace-custom-layout.md
- cli/named-workspace-custom-layout.md
- cli/workspace-layout-rule.md
- cli/named-workspace-layout-rule.md
- cli/workspace-custom-layout-rule.md
- cli/named-workspace-custom-layout-rule.md
- cli/clear-workspace-layout-rules.md
- cli/clear-named-workspace-layout-rules.md
- cli/workspace-tiling.md
- cli/named-workspace-tiling.md
- cli/workspace-name.md
- cli/toggle-window-container-behaviour.md
- cli/toggle-float-override.md
- cli/toggle-workspace-window-container-behaviour.md
- cli/toggle-workspace-float-override.md
- cli/toggle-pause.md
- cli/toggle-tiling.md
- cli/toggle-float.md
@@ -190,7 +187,7 @@ nav:
- cli/cross-monitor-move-behaviour.md
- cli/toggle-cross-monitor-move-behaviour.md
- cli/unmanaged-window-operation-behaviour.md
- cli/float-rule.md
- cli/ignore-rule.md
- cli/manage-rule.md
- cli/initial-workspace-rule.md
- cli/initial-named-workspace-rule.md
@@ -217,13 +214,11 @@ nav:
- cli/animation-duration.md
- cli/animation-fps.md
- cli/animation-style.md
- cli/focus-follows-mouse.md
- cli/toggle-focus-follows-mouse.md
- cli/mouse-follows-focus.md
- cli/toggle-mouse-follows-focus.md
- cli/ahk-app-specific-configuration.md
- cli/pwsh-app-specific-configuration.md
- cli/format-app-specific-configuration.md
- cli/convert-app-specific-configuration.md
- cli/fetch-app-specific-configuration.md
- cli/application-specific-configuration-schema.md
- cli/notification-schema.md

View File

@@ -19,7 +19,7 @@
"format": "float"
},
"frame": {
"description": "Frame options (see: https://docs.rs/egui/latest/egui/containers/struct.Frame.html)",
"description": "Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)",
"type": "object",
"required": [
"inner_margin"
@@ -1428,6 +1428,7 @@
]
},
"name": {
"description": "Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)",
"type": "string",
"enum": [
"Frappe",
@@ -1474,6 +1475,7 @@
]
},
"name": {
"description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)",
"type": "string",
"enum": [
"3024",

View File

@@ -66,7 +66,7 @@
}
},
"app_specific_configuration_path": {
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
"description": "Path to applications.json from komorebi-application-specific-configurations (default: None)",
"type": "string"
},
"bar_configurations": {
@@ -579,7 +579,7 @@
}
},
"focus_follows_mouse": {
"description": "Determine focus follows mouse implementation (default: None)",
"description": "END OF LIFE FEATURE: Determine focus follows mouse implementation (default: None)",
"oneOf": [
{
"description": "A custom FFM implementation (slightly more CPU-intensive)",
@@ -1052,11 +1052,11 @@
"format": "int32"
},
"custom_layout": {
"description": "Custom Layout (default: None)",
"description": "END OF LIFE FEATURE: Custom Layout (default: None)",
"type": "string"
},
"custom_layout_rules": {
"description": "Layout rules (default: None)",
"description": "END OF LIFE FEATURE: Custom layout rules (default: None)",
"type": "object",
"additionalProperties": {
"type": "string"
@@ -1751,7 +1751,7 @@
]
},
"name": {
"description": "Name of the Catppuccin theme",
"description": "Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)",
"type": "string",
"enum": [
"Frappe",
@@ -2035,7 +2035,7 @@
]
},
"name": {
"description": "Name of the Base16 theme",
"description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)",
"type": "string",
"enum": [
"3024",