mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-27 15:37:38 +01:00
Compare commits
1 Commits
monitor-pr
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b575cba9f4 |
3
.github/workflows/windows.yaml
vendored
3
.github/workflows/windows.yaml
vendored
@@ -93,10 +93,9 @@ jobs:
|
|||||||
target/${{ matrix.target }}/release/komorebi.exe
|
target/${{ matrix.target }}/release/komorebi.exe
|
||||||
target/${{ matrix.target }}/release/komorebic.exe
|
target/${{ matrix.target }}/release/komorebic.exe
|
||||||
target/${{ matrix.target }}/release/komorebic-no-console.exe
|
target/${{ matrix.target }}/release/komorebic-no-console.exe
|
||||||
target/${{ matrix.target }}/release/komorebi-gui.exe
|
|
||||||
target/${{ matrix.target }}/release/komorebi.pdb
|
target/${{ matrix.target }}/release/komorebi.pdb
|
||||||
target/${{ matrix.target }}/release/komorebic.pdb
|
target/${{ matrix.target }}/release/komorebic.pdb
|
||||||
target/${{ matrix.target }}/release/komorebi-gui.pdb
|
target/${{ matrix.target }}/release/komorebic-no-console.pdb
|
||||||
target/wix/komorebi-*.msi
|
target/wix/komorebi-*.msi
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: Check GoReleaser
|
- name: Check GoReleaser
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ before:
|
|||||||
builds:
|
builds:
|
||||||
- id: komorebi
|
- id: komorebi
|
||||||
main: dummy.go
|
main: dummy.go
|
||||||
goos: [ "windows" ]
|
goos: ["windows"]
|
||||||
goarch: [ "amd64" ]
|
goarch: ["amd64"]
|
||||||
binary: komorebi
|
binary: komorebi
|
||||||
hooks:
|
hooks:
|
||||||
post:
|
post:
|
||||||
@@ -19,8 +19,8 @@ builds:
|
|||||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64_v1\komorebi.exe"
|
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi.exe" ".\dist\komorebi_windows_amd64_v1\komorebi.exe"
|
||||||
- id: komorebic
|
- id: komorebic
|
||||||
main: dummy.go
|
main: dummy.go
|
||||||
goos: [ "windows" ]
|
goos: ["windows"]
|
||||||
goarch: [ "amd64" ]
|
goarch: ["amd64"]
|
||||||
binary: komorebic
|
binary: komorebic
|
||||||
hooks:
|
hooks:
|
||||||
post:
|
post:
|
||||||
@@ -28,28 +28,19 @@ builds:
|
|||||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64_v1\komorebic.exe"
|
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic.exe" ".\dist\komorebic_windows_amd64_v1\komorebic.exe"
|
||||||
- id: komorebic-no-console
|
- id: komorebic-no-console
|
||||||
main: dummy.go
|
main: dummy.go
|
||||||
goos: [ "windows" ]
|
goos: ["windows"]
|
||||||
goarch: [ "amd64" ]
|
goarch: ["amd64"]
|
||||||
binary: komorebic-no-console
|
binary: komorebic-no-console
|
||||||
hooks:
|
hooks:
|
||||||
post:
|
post:
|
||||||
- mkdir -p dist/windows_amd64
|
- mkdir -p dist/windows_amd64
|
||||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic-no-console.exe" ".\dist\komorebic-no-console_windows_amd64_v1\komorebic-no-console.exe"
|
- cp ".\target\x86_64-pc-windows-msvc\release\komorebic-no-console.exe" ".\dist\komorebic-no-console_windows_amd64_v1\komorebic-no-console.exe"
|
||||||
- id: komorebi-gui
|
|
||||||
main: dummy.go
|
|
||||||
goos: [ "windows" ]
|
|
||||||
goarch: [ "amd64" ]
|
|
||||||
binary: komorebi-gui
|
|
||||||
hooks:
|
|
||||||
post:
|
|
||||||
- mkdir -p dist/windows_amd64
|
|
||||||
- cp ".\target\x86_64-pc-windows-msvc\release\komorebi-gui.exe" ".\dist\komorebi-gui_windows_amd64_v1\komorebi-gui.exe"
|
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"
|
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"
|
||||||
format: zip
|
format: zip
|
||||||
files:
|
files:
|
||||||
- LICENSE.md
|
- LICENSE
|
||||||
- CHANGELOG.md
|
- CHANGELOG.md
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
# Contributing to the Project
|
|
||||||
|
|
||||||
The project is a collection of contributions from both the project leaders and
|
|
||||||
community members. There are many ways to contribute, this can include content
|
|
||||||
in the project repositories, as well as contributing in public and private
|
|
||||||
conversation, assisting users, writing blog posts, and many other ways.
|
|
||||||
|
|
||||||
## How contributions are made
|
|
||||||
|
|
||||||
Contributions to the project primarily happen in the project source
|
|
||||||
repositories, but may also occur in other places, such as discussion forums and
|
|
||||||
public and private discourse.
|
|
||||||
|
|
||||||
## Contributing content to the Project
|
|
||||||
|
|
||||||
In order for the project leaders to manage sustained progress toward the
|
|
||||||
project goals and maintain project velocity, focus and quality, the project may
|
|
||||||
adjust the license terms over time.
|
|
||||||
|
|
||||||
Content contributed to the project must therefore be provided under
|
|
||||||
sufficiently liberal terms to allow these operations to proceed unimpeded. As
|
|
||||||
such contributions are accepted with the following understanding:
|
|
||||||
|
|
||||||
* Contributed content is licensed under the terms of the 0-BSD license
|
|
||||||
* Contributors accept the terms of the project license at the time of
|
|
||||||
contribution
|
|
||||||
|
|
||||||
By making a contribution, you accept both the current project license terms,
|
|
||||||
and that all contributions that you have made are provided under the terms of
|
|
||||||
the 0-BSD license.
|
|
||||||
|
|
||||||
## Zero-Clause BSD
|
|
||||||
|
|
||||||
```
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for
|
|
||||||
any purpose with or without fee is hereby granted.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
|
||||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
||||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
|
||||||
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
|
||||||
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
|
||||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
|
||||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
```
|
|
||||||
3542
Cargo.lock
generated
3542
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ members = [
|
|||||||
"komorebi",
|
"komorebi",
|
||||||
"komorebi-client",
|
"komorebi-client",
|
||||||
"komorebi-core",
|
"komorebi-core",
|
||||||
"komorebi-gui",
|
|
||||||
"komorebic",
|
"komorebic",
|
||||||
"komorebic-no-console",
|
"komorebic-no-console",
|
||||||
]
|
]
|
||||||
@@ -18,9 +17,6 @@ dunce = "1"
|
|||||||
dirs = "5"
|
dirs = "5"
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
serde_json = { package = "serde_json_lenient", version = "0.1" }
|
serde_json = { package = "serde_json_lenient", version = "0.1" }
|
||||||
sysinfo = "0.30"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
uds_windows = "1"
|
|
||||||
|
|
||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
version = "0.54"
|
version = "0.54"
|
||||||
|
|||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Jade Iqbal
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
105
LICENSE.md
105
LICENSE.md
@@ -1,105 +0,0 @@
|
|||||||
# PolyForm Strict License 1.0.0
|
|
||||||
|
|
||||||
<https://polyformproject.org/licenses/strict/1.0.0>
|
|
||||||
|
|
||||||
## Acceptance
|
|
||||||
|
|
||||||
In order to get any license under these terms, you must agree
|
|
||||||
to them as both strict obligations and conditions to all
|
|
||||||
your licenses.
|
|
||||||
|
|
||||||
## Copyright License
|
|
||||||
|
|
||||||
The licensor grants you a copyright license for the software
|
|
||||||
to do everything you might do with the software that would
|
|
||||||
otherwise infringe the licensor's copyright in it for any
|
|
||||||
permitted purpose, other than distributing the software or
|
|
||||||
making changes or new works based on the software.
|
|
||||||
|
|
||||||
## Patent License
|
|
||||||
|
|
||||||
The licensor grants you a patent license for the software that
|
|
||||||
covers patent claims the licensor can license, or becomes able
|
|
||||||
to license, that you would infringe by using the software.
|
|
||||||
|
|
||||||
## Noncommercial Purposes
|
|
||||||
|
|
||||||
Any noncommercial purpose is a permitted purpose.
|
|
||||||
|
|
||||||
## Personal Uses
|
|
||||||
|
|
||||||
Personal use for research, experiment, and testing for
|
|
||||||
the benefit of public knowledge, personal study, private
|
|
||||||
entertainment, hobby projects, amateur pursuits, or religious
|
|
||||||
observance, without any anticipated commercial application,
|
|
||||||
is use for a permitted purpose.
|
|
||||||
|
|
||||||
## Noncommercial Organizations
|
|
||||||
|
|
||||||
Use by any charitable organization, educational institution,
|
|
||||||
public research organization, public safety or health
|
|
||||||
organization, environmental protection organization,
|
|
||||||
or government institution is use for a permitted purpose
|
|
||||||
regardless of the source of funding or obligations resulting
|
|
||||||
from the funding.
|
|
||||||
|
|
||||||
## Fair Use
|
|
||||||
|
|
||||||
You may have "fair use" rights for the software under the
|
|
||||||
law. These terms do not limit them.
|
|
||||||
|
|
||||||
## No Other Rights
|
|
||||||
|
|
||||||
These terms do not allow you to sublicense or transfer any of
|
|
||||||
your licenses to anyone else, or prevent the licensor from
|
|
||||||
granting licenses to anyone else. These terms do not imply
|
|
||||||
any other licenses.
|
|
||||||
|
|
||||||
## Patent Defense
|
|
||||||
|
|
||||||
If you make any written claim that the software infringes or
|
|
||||||
contributes to infringement of any patent, your patent license
|
|
||||||
for the software granted under these terms ends immediately. If
|
|
||||||
your company makes such a claim, your patent license ends
|
|
||||||
immediately for work on behalf of your company.
|
|
||||||
|
|
||||||
## Violations
|
|
||||||
|
|
||||||
The first time you are notified in writing that you have
|
|
||||||
violated any of these terms, or done anything with the software
|
|
||||||
not covered by your licenses, your licenses can nonetheless
|
|
||||||
continue if you come into full compliance with these terms,
|
|
||||||
and take practical steps to correct past violations, within
|
|
||||||
32 days of receiving notice. Otherwise, all your licenses
|
|
||||||
end immediately.
|
|
||||||
|
|
||||||
## No Liability
|
|
||||||
|
|
||||||
***As far as the law allows, the software comes as is, without
|
|
||||||
any warranty or condition, and the licensor will not be liable
|
|
||||||
to you for any damages arising out of these terms or the use
|
|
||||||
or nature of the software, under any kind of legal claim.***
|
|
||||||
|
|
||||||
## Definitions
|
|
||||||
|
|
||||||
The **licensor** is the individual or entity offering these
|
|
||||||
terms, and the **software** is the software the licensor makes
|
|
||||||
available under these terms.
|
|
||||||
|
|
||||||
**You** refers to the individual or entity agreeing to these
|
|
||||||
terms.
|
|
||||||
|
|
||||||
**Your company** is any legal entity, sole proprietorship,
|
|
||||||
or other kind of organization that you work for, plus all
|
|
||||||
organizations that have control over, are under the control of,
|
|
||||||
or are under common control with that organization. **Control**
|
|
||||||
means ownership of substantially all the assets of an entity,
|
|
||||||
or the power to direct its management and policies by vote,
|
|
||||||
contract, or otherwise. Control can be direct or indirect.
|
|
||||||
|
|
||||||
**Your licenses** are all the licenses granted to you for the
|
|
||||||
software under these terms.
|
|
||||||
|
|
||||||
**Use** means anything you do with the software requiring one
|
|
||||||
of your licenses.
|
|
||||||
|
|
||||||
29
README.md
29
README.md
@@ -84,19 +84,6 @@ using `scoop`, `winget` or building from source.
|
|||||||
|
|
||||||
[](https://www.youtube.com/watch?v=H9-_c1egQ4g)
|
[](https://www.youtube.com/watch?v=H9-_c1egQ4g)
|
||||||
|
|
||||||
# Comparison With Fancy Zones
|
|
||||||
|
|
||||||
Community member [Olge](https://www.youtube.com/@polle5555) has created an
|
|
||||||
excellent video which compares the default window management features of
|
|
||||||
Windows 11, Fancy Zones and komorebi.
|
|
||||||
|
|
||||||
If you are not familiar with tiling window managers or if you are looking at
|
|
||||||
komorebi and wondering "how is this different from Fancy Zones? 🤔", this short
|
|
||||||
video will answer the majority of your questions.
|
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=0LCbS_gm0RA)
|
|
||||||
|
|
||||||
|
|
||||||
# Demonstrations
|
# Demonstrations
|
||||||
|
|
||||||
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
|
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
|
||||||
@@ -182,20 +169,6 @@ ability for users to specify colours in `komorebi.json` in Hex format alongside
|
|||||||
There is also a process in place for graceful, non-breaking, deprecation of configuration options that are no longer
|
There is also a process in place for graceful, non-breaking, deprecation of configuration options that are no longer
|
||||||
required.
|
required.
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
`komorebi` is licensed under 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for more information about how
|
|
||||||
code contributions to `komorebi` are licensed.
|
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
|
|
||||||
If you use IntelliJ, you should enable the following settings to ensure that code generated by macros is recognised by
|
If you use IntelliJ, you should enable the following settings to ensure that code generated by macros is recognised by
|
||||||
@@ -351,7 +324,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
|
|||||||
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.25"}
|
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.22"}
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use komorebi_client::Notification;
|
use komorebi_client::Notification;
|
||||||
|
|||||||
26
docs/cli/active-window-border-colour.md
Normal file
26
docs/cli/active-window-border-colour.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# active-window-border-colour
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the colour for the active window border
|
||||||
|
|
||||||
|
Usage: komorebic.exe active-window-border-colour [OPTIONS] <R> <G> <B>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<R>
|
||||||
|
Red
|
||||||
|
|
||||||
|
<G>
|
||||||
|
Green
|
||||||
|
|
||||||
|
<B>
|
||||||
|
Blue
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-w, --window-kind <WINDOW_KIND>
|
||||||
|
[default: single]
|
||||||
|
[possible values: single, stack, monocle]
|
||||||
|
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
16
docs/cli/active-window-border-offset.md
Normal file
16
docs/cli/active-window-border-offset.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# active-window-border-offset
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the offset for the active window border
|
||||||
|
|
||||||
|
Usage: komorebic.exe active-window-border-offset <OFFSET>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<OFFSET>
|
||||||
|
Desired offset of the active window border
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
16
docs/cli/active-window-border-width.md
Normal file
16
docs/cli/active-window-border-width.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# active-window-border-width
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the width for the active window border
|
||||||
|
|
||||||
|
Usage: komorebic.exe active-window-border-width <WIDTH>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<WIDTH>
|
||||||
|
Desired width of the active window border
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
16
docs/cli/active-window-border.md
Normal file
16
docs/cli/active-window-border.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# active-window-border
|
||||||
|
|
||||||
|
```
|
||||||
|
Enable or disable the active window border
|
||||||
|
|
||||||
|
Usage: komorebic.exe active-window-border <BOOLEAN_STATE>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<BOOLEAN_STATE>
|
||||||
|
[possible values: enable, disable]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# border
|
# alt-focus-hack
|
||||||
|
|
||||||
```
|
```
|
||||||
Enable or disable borders
|
DEPRECATED since v0.1.22
|
||||||
|
|
||||||
Usage: komorebic.exe border <BOOLEAN_STATE>
|
Usage: komorebic.exe alt-focus-hack <BOOLEAN_STATE>
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<BOOLEAN_STATE>
|
<BOOLEAN_STATE>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# border-colour
|
|
||||||
|
|
||||||
```
|
|
||||||
Set the colour for a window border kind
|
|
||||||
|
|
||||||
Usage: komorebic.exe border-colour [OPTIONS] <R> <G> <B>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<R>
|
|
||||||
Red
|
|
||||||
|
|
||||||
<G>
|
|
||||||
Green
|
|
||||||
|
|
||||||
<B>
|
|
||||||
Blue
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-w, --window-kind <WINDOW_KIND>
|
|
||||||
[default: single]
|
|
||||||
[possible values: single, stack, monocle, unfocused]
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# border-offset
|
|
||||||
|
|
||||||
```
|
|
||||||
Set the border offset
|
|
||||||
|
|
||||||
Usage: komorebic.exe border-offset <OFFSET>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<OFFSET>
|
|
||||||
Desired offset of the window border
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# border-width
|
|
||||||
|
|
||||||
```
|
|
||||||
Set the border width
|
|
||||||
|
|
||||||
Usage: komorebic.exe border-width <WIDTH>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<WIDTH>
|
|
||||||
Desired width of the window border
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe change-layout <DEFAULT_LAYOUT>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<DEFAULT_LAYOUT>
|
<DEFAULT_LAYOUT>
|
||||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ Arguments:
|
|||||||
Possible values:
|
Possible values:
|
||||||
- swap: Swap the window container with the window container at the edge of the adjacent monitor
|
- swap: Swap the window container with the window container at the edge of the adjacent monitor
|
||||||
- insert: Insert the window container into the focused workspace on the adjacent monitor
|
- insert: Insert the window container into the focused workspace on the adjacent monitor
|
||||||
- no-op: Do nothing if trying to move a window container in the direction of an adjacent monitor
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# cycle-move-workspace-to-monitor
|
|
||||||
|
|
||||||
```
|
|
||||||
Move the focused workspace monitor in the given cycle direction
|
|
||||||
|
|
||||||
Usage: komorebic.exe cycle-move-workspace-to-monitor <CYCLE_DIRECTION>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<CYCLE_DIRECTION>
|
|
||||||
[possible values: previous, next]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe float-rule <IDENTIFIER> <ID>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# global-state
|
|
||||||
|
|
||||||
```
|
|
||||||
Show a JSON representation of the current global state
|
|
||||||
|
|
||||||
Usage: komorebic.exe global-state
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# gui
|
|
||||||
|
|
||||||
```
|
|
||||||
Launch the komorebi-gui debugging tool
|
|
||||||
|
|
||||||
Usage: komorebic.exe gui
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
19
docs/cli/identify-border-overflow-application.md
Normal file
19
docs/cli/identify-border-overflow-application.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# identify-border-overflow-application
|
||||||
|
|
||||||
|
```
|
||||||
|
Identify an application that has overflowing borders
|
||||||
|
|
||||||
|
Usage: komorebic.exe identify-border-overflow-application <IDENTIFIER> <ID>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<IDENTIFIER>
|
||||||
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
|
<ID>
|
||||||
|
Identifier as a string
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe identify-layered-application <IDENTIFIER> <ID>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe identify-object-name-change-application <IDENTIFIER> <ID>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe identify-tray-application <IDENTIFIER> <ID>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe initial-named-workspace-rule <IDENTIFIER> <ID> <WORKSPACE>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe initial-workspace-rule <IDENTIFIER> <ID> <MONITOR> <WORKSPA
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe manage-rule <IDENTIFIER> <ID>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# move-to-monitor-workspace
|
|
||||||
|
|
||||||
```
|
|
||||||
Move the focused window to the specified monitor workspace
|
|
||||||
|
|
||||||
Usage: komorebic.exe move-to-monitor-workspace <TARGET_MONITOR> <TARGET_WORKSPACE>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<TARGET_MONITOR>
|
|
||||||
Target monitor index (zero-indexed)
|
|
||||||
|
|
||||||
<TARGET_WORKSPACE>
|
|
||||||
Workspace index on the target monitor (zero-indexed)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -13,7 +13,7 @@ Arguments:
|
|||||||
The number of window containers on-screen required to trigger this layout rule
|
The number of window containers on-screen required to trigger this layout rule
|
||||||
|
|
||||||
<LAYOUT>
|
<LAYOUT>
|
||||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Arguments:
|
|||||||
Target workspace name
|
Target workspace name
|
||||||
|
|
||||||
<VALUE>
|
<VALUE>
|
||||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe named-workspace-rule <IDENTIFIER> <ID> <WORKSPACE>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# promote-window
|
|
||||||
|
|
||||||
```
|
|
||||||
Promote the window in the specified direction
|
|
||||||
|
|
||||||
Usage: komorebic.exe promote-window <OPERATION_DIRECTION>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<OPERATION_DIRECTION>
|
|
||||||
[possible values: left, right, up, down]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe remove-title-bar <IDENTIFIER> <ID>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Arguments:
|
|||||||
The number of window containers on-screen required to trigger this layout rule
|
The number of window containers on-screen required to trigger this layout rule
|
||||||
|
|
||||||
<LAYOUT>
|
<LAYOUT>
|
||||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Arguments:
|
|||||||
Workspace index on the specified monitor (zero-indexed)
|
Workspace index on the specified monitor (zero-indexed)
|
||||||
|
|
||||||
<VALUE>
|
<VALUE>
|
||||||
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage: komorebic.exe workspace-rule <IDENTIFIER> <ID> <MONITOR> <WORKSPACE>
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<IDENTIFIER>
|
<IDENTIFIER>
|
||||||
[possible values: exe, class, title, path]
|
[possible values: exe, class, title]
|
||||||
|
|
||||||
<ID>
|
<ID>
|
||||||
Identifier as a string
|
Identifier as a string
|
||||||
|
|||||||
38
docs/common-workflows/active-window-border.md
Normal file
38
docs/common-workflows/active-window-border.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Active Window Border
|
||||||
|
|
||||||
|
If you would like to add a visual border around the currently focused window,
|
||||||
|
ensure the following options are defined in the `komorebi.json` configuration
|
||||||
|
file.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"active_window_border": true,
|
||||||
|
"active_window_border_colours": {
|
||||||
|
"single": {
|
||||||
|
"r": 66,
|
||||||
|
"g": 165,
|
||||||
|
"b": 245
|
||||||
|
},
|
||||||
|
"stack": {
|
||||||
|
"r": 256,
|
||||||
|
"g": 165,
|
||||||
|
"b": 66
|
||||||
|
},
|
||||||
|
"monocle": {
|
||||||
|
"r": 255,
|
||||||
|
"g": 51,
|
||||||
|
"b": 153
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
It is important to note that the active window border will only apply to
|
||||||
|
windows managed by `komorebi`.
|
||||||
|
|
||||||
|
This feature is not considered stable and you may encounter visual artifacts
|
||||||
|
from time to time.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=7_9D22t7KK4)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# AutoHotKey
|
# AutoHotKey
|
||||||
|
|
||||||
|
<!-- TODO: Update this completely -->
|
||||||
|
|
||||||
If you would like to use Autohotkey, please make sure you have AutoHotKey v2
|
If you would like to use Autohotkey, please make sure you have AutoHotKey v2
|
||||||
installed.
|
installed.
|
||||||
|
|
||||||
@@ -7,16 +9,22 @@ Generally, users who opt for AHK will have specific needs that can only be
|
|||||||
addressed by the advanced functionality of AHK, and so they are assumed to be
|
addressed by the advanced functionality of AHK, and so they are assumed to be
|
||||||
able to craft their own configuration files.
|
able to craft their own configuration files.
|
||||||
|
|
||||||
If you would like to try out AHK, here is a simple sample configuration which
|
If you would like to try out AHK, a simple sample configuration powered by
|
||||||
largely matches the `whkdrc` sample configuration.
|
`komorebic.lib.ahk` is provided as a starting point. This sample configuration
|
||||||
|
does not take into account the use of a static configuration file; if you
|
||||||
|
choose to use a static configuration file alongside AHK, you can remove all the
|
||||||
|
configuration options from your `komorebi.ahk` and use it solely to handle
|
||||||
|
hotkey bindings.
|
||||||
|
|
||||||
```
|
|
||||||
{% include "../komorebi.ahk" %}
|
```powershell
|
||||||
|
# save the latest generated komorebic library to ~/komorebic.lib.ahk
|
||||||
|
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/komorebic.lib.ahk -OutFile $Env:USERPROFILE\komorebic.lib.ahk
|
||||||
|
|
||||||
|
# save the latest generated app-specific config tweaks and fixes to ~/komorebi.generated.ahk
|
||||||
|
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/komorebi.generated.ahk -OutFile $Env:USERPROFILE\komorebi.generated.ahk
|
||||||
|
|
||||||
|
# save the sample komorebi configuration file to ~/komorebi.ahk
|
||||||
|
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/komorebi.sample.ahk -OutFile $Env:USERPROFILE\komorebi.ahk
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the `komorebi.ahk` file should be located in the `$Env:USERPROFILE`
|
|
||||||
directory, however, if `$Env:KOMOREBI_CONFIG_HOME` is set, it should be located
|
|
||||||
there.
|
|
||||||
|
|
||||||
Once the file is in place, you can stop komorebi and whkd by running `komorebic stop --whkd`,
|
|
||||||
and then start komorebi with Autohotkey by running `komorebic start --ahk`.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Borders
|
|
||||||
|
|
||||||
If you would like to add a visual border around both the currently focused window
|
|
||||||
and unfocused windows ensure the following options are defined in the `komorebi.json`
|
|
||||||
configuration file.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"border": true,
|
|
||||||
"border_width": 8,
|
|
||||||
"border_offset": -1,
|
|
||||||
"border_style": "System",
|
|
||||||
"border_colours": {
|
|
||||||
"single": "#42a5f5",
|
|
||||||
"stack": "#00a542",
|
|
||||||
"monocle": "#ff3399",
|
|
||||||
"unfocused": "#808080"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It is important to note that borders will only apply to windows managed by `komorebi`.
|
|
||||||
|
|
||||||
This feature is not considered stable, and you may encounter visual artifacts
|
|
||||||
from time to time.
|
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=7_9D22t7KK4)
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
❗️**NOTE**: A significant number of force-manage window rules for the most
|
❗️**NOTE**: A significant number of force-manage window rules for the most
|
||||||
common applications are [already generated for
|
common applications are [already generated for
|
||||||
you](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
|
you](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)
|
||||||
|
|
||||||
In some rare cases, a window may not automatically be registered to be managed
|
In some rare cases, a window may not automatically be registered to be managed
|
||||||
by `komorebi`. You can add rules to enforce this behaviour in the
|
by `komorebi`. You can add rules to enforce this behaviour in the
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
❗️**NOTE**: A significant number of ignored window rules for the most common
|
❗️**NOTE**: A significant number of ignored window rules for the most common
|
||||||
applications are [already generated for
|
applications are [already generated for
|
||||||
you](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
|
you](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)
|
||||||
|
|
||||||
Sometimes you will want a specific application to never be tiled, and instead
|
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
|
float all the time. You can add rules to enforce this behaviour in the
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
# Stackbar
|
|
||||||
|
|
||||||
If you would like to add a visual stackbar to show which windows are in a container
|
|
||||||
stack ensure the following options are defined in the `komorebi.json` configuration
|
|
||||||
file.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"stackbar": {
|
|
||||||
"height": 40,
|
|
||||||
"mode": "OnStack",
|
|
||||||
"tabs": {
|
|
||||||
"width": 300,
|
|
||||||
"focused_text": "#00a542",
|
|
||||||
"unfocused_text": "#b3b3b3",
|
|
||||||
"background": "#141414"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This feature is not considered stable, and you may encounter visual artifacts
|
|
||||||
from time to time.
|
|
||||||
@@ -101,16 +101,6 @@ monocle.
|
|||||||
+-------+-----+
|
+-------+-----+
|
||||||
```
|
```
|
||||||
|
|
||||||
#### RightMainVerticalStack
|
|
||||||
|
|
||||||
```
|
|
||||||
+-----+-------+
|
|
||||||
| | |
|
|
||||||
+-----+ |
|
|
||||||
| | |
|
|
||||||
+-----+-------+
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Horizontal Stack
|
#### Horizontal Stack
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -132,7 +122,6 @@ monocle.
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Rows
|
#### Rows
|
||||||
|
|
||||||
If you have a vertical monitor, I recommend using this layout.
|
If you have a vertical monitor, I recommend using this layout.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -144,7 +133,6 @@ If you have a vertical monitor, I recommend using this layout.
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Ultrawide Vertical Stack
|
#### Ultrawide Vertical Stack
|
||||||
|
|
||||||
If you have an ultrawide monitor, I recommend using this layout.
|
If you have an ultrawide monitor, I recommend using this layout.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -158,7 +146,6 @@ If you have an ultrawide monitor, I recommend using this layout.
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Grid
|
### Grid
|
||||||
|
|
||||||
If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layouts) this is almost exactly the same!
|
If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layouts) this is almost exactly the same!
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -175,8 +162,8 @@ If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layou
|
|||||||
## whkdrc
|
## whkdrc
|
||||||
|
|
||||||
`whkd` is a fairly basic piece of software with a simple configuration format:
|
`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
|
key bindings go to the left of the, 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
|
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,
|
on hotkey bindings that include the `Windows` key. If this is important to you,
|
||||||
@@ -196,8 +183,7 @@ which shell you use in your terminal.
|
|||||||
* `powershell` - set this if you are using the version of PowerShell that comes
|
* `powershell` - set this if you are using the version of PowerShell that comes
|
||||||
installed with Windows 10+ (the executable file for this is `powershell.exe`)
|
installed with Windows 10+ (the executable file for this is `powershell.exe`)
|
||||||
|
|
||||||
* `pwsh` - set this if you are using PowerShell 7+, which you have installed yourself either through the Windows Store
|
* `pwsh` - set this if you are using PowerShell 7+, which you have installed yourself either through the Windows Store or WinGet (the executable file for this is `pwsh.exe`)
|
||||||
or WinGet (the executable file for this is `pwsh.exe`)
|
|
||||||
|
|
||||||
* `cmd` - set this if you don't want to use PowerShell at all and instead you
|
* `cmd` - set this if you don't want to use PowerShell at all and instead you
|
||||||
want to call commands through the shell used by the old-school Command
|
want to call commands through the shell used by the old-school Command
|
||||||
|
|||||||
@@ -30,10 +30,9 @@ to manipulate the window manager, you use
|
|||||||
[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also built
|
[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also built
|
||||||
it from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.
|
it from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.
|
||||||
|
|
||||||
- [Scoop](#scoop)
|
- [Scoop](#scoop)
|
||||||
- [WinGet](#winget)
|
- [WinGet](#winget)
|
||||||
- [Building from source](#building-from-source)
|
- [Building from source](#building-from-source)
|
||||||
- [Offline](#offline)
|
|
||||||
|
|
||||||
## Long path support
|
## Long path support
|
||||||
|
|
||||||
@@ -45,12 +44,6 @@ running the following command in an Administrator Terminal before installing
|
|||||||
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Disabling Unnecessary System Animations
|
|
||||||
|
|
||||||
It is highly recommended that you enable the "Turn off all unnecessary animations (when possible)" option in
|
|
||||||
"Control Panel > Ease of Access > Ease of Access Centre / Make the computer easier to see" for the best performance with
|
|
||||||
komorebi.
|
|
||||||
|
|
||||||
## Scoop
|
## Scoop
|
||||||
|
|
||||||
Make sure you have installed [`scoop`](https://scoop.sh) and verified that
|
Make sure you have installed [`scoop`](https://scoop.sh) and verified that
|
||||||
@@ -114,17 +107,7 @@ Clone the git repository, enter the directory, and build the following binaries:
|
|||||||
cargo +stable install --path komorebi --locked
|
cargo +stable install --path komorebi --locked
|
||||||
cargo +stable install --path komorebic --locked
|
cargo +stable install --path komorebic --locked
|
||||||
cargo +stable install --path komorebic-no-console --locked
|
cargo +stable install --path komorebic-no-console --locked
|
||||||
cargo +stable install --path komorebi-gui --locked
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If the binaries have been built and added to your `$PATH` correctly, you should
|
If the binaries have been built and added to your `$PATH` correctly, you should
|
||||||
see some output when running `komorebi --help` and `komorebic --help`
|
see some output when running `komorebi --help` and `komorebic --help`
|
||||||
|
|
||||||
### Offline
|
|
||||||
|
|
||||||
Download the latest [komorebi](https://github.com/LGUG2Z/komorebi/releases)
|
|
||||||
and [whkd](https://github.com/LGUG2Z/whkd/releases) MSI installers on an internet-connected computer, then copy them to
|
|
||||||
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).
|
|
||||||
|
|||||||
@@ -1,60 +1,26 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.25/schema.json",
|
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.22/schema.json",
|
||||||
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
|
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
|
||||||
"window_hiding_behaviour": "Cloak",
|
"window_hiding_behaviour": "Cloak",
|
||||||
"cross_monitor_move_behaviour": "Insert",
|
"cross_monitor_move_behaviour": "Insert",
|
||||||
"default_workspace_padding": 20,
|
"default_workspace_padding": 20,
|
||||||
"default_container_padding": 20,
|
"default_container_padding": 20,
|
||||||
"border": true,
|
"border_padding": 8,
|
||||||
"border_width": 8,
|
|
||||||
"border_offset": -1,
|
"border_offset": -1,
|
||||||
"border_colours": {
|
"active_window_border": false,
|
||||||
|
"active_window_border_colours": {
|
||||||
"single": "#42a5f5",
|
"single": "#42a5f5",
|
||||||
"stack": "#00a542",
|
"stack": "#00a542",
|
||||||
"monocle": "#ff3399",
|
"monocle": "#ff3399"
|
||||||
"unfocused": "#808080"
|
|
||||||
},
|
|
||||||
"stackbar": {
|
|
||||||
"height": 40,
|
|
||||||
"mode": "OnStack",
|
|
||||||
"tabs": {
|
|
||||||
"width": 300,
|
|
||||||
"focused_text": "#00a542",
|
|
||||||
"unfocused_text": "#b3b3b3",
|
|
||||||
"background": "#141414"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"monitors": [
|
"monitors": [
|
||||||
{
|
{
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
{
|
{ "name": "I", "layout": "BSP" },
|
||||||
"name": "I",
|
{ "name": "II", "layout": "VerticalStack" },
|
||||||
"layout": "BSP"
|
{ "name": "III", "layout": "HorizontalStack" },
|
||||||
},
|
{ "name": "IV", "layout": "UltrawideVerticalStack" },
|
||||||
{
|
{ "name": "V", "layout": "Rows" }
|
||||||
"name": "II",
|
|
||||||
"layout": "VerticalStack"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "III",
|
|
||||||
"layout": "HorizontalStack"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "IV",
|
|
||||||
"layout": "UltrawideVerticalStack"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "V",
|
|
||||||
"layout": "Rows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "VI",
|
|
||||||
"layout": "Grid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "VII",
|
|
||||||
"layout": "RightMainVerticalStack"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
# Troubleshooting
|
|
||||||
|
|
||||||
## AutoHotKey executable not found
|
|
||||||
|
|
||||||
If you try to start komorebi with AHK using `komorebic start --ahk`, and you have
|
|
||||||
not installed AHK using `scoop`, you'll probably receive an error:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Error: could not find autohotkey, please make sure it is installed before using the --ahk flag
|
|
||||||
```
|
|
||||||
|
|
||||||
Depending on how AHK is installed the executable on your system may have a
|
|
||||||
different name. In order to account for this, you may set the `KOMOREBI_AHK_EXE`
|
|
||||||
environment variable in your
|
|
||||||
[PowerShell profile](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.4)
|
|
||||||
to match the name of the executable as it is found on your system.
|
|
||||||
|
|
||||||
After setting `KOMOREBI_AHK_EXE` make sure to either reload your PowerShell
|
|
||||||
profile or open a new terminal tab.
|
|
||||||
|
|
||||||
## Komorebi is unresponsive when the display wakes from sleep
|
|
||||||
|
|
||||||
This can happen in rare cases when your monitor state is not preserved after it
|
|
||||||
wakes from sleep.
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
|
|
||||||
Your hotkeys in _whkd_ work, but it feels as if _komorebi_ knows nothing about
|
|
||||||
the previous state (you can't control previous windows, although newly launched ones
|
|
||||||
can be manipulated as normal).
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
|
|
||||||
Some monitors, such as the Samsung G8/G9 (LED, Neo, OLED) have an _adaptive
|
|
||||||
sync_ or _variable refresh rate_ setting within the actual monitor OSD that can
|
|
||||||
disrupt how the device is persisted in the _komorebi_ state following suspension.
|
|
||||||
|
|
||||||
To fix this, please try to disable _Adaptive Sync_ or any other _VRR_ branded
|
|
||||||
alias by referring to the manufacturer's documentation.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
|
|
||||||
Disabling VRR within Windows (e.g. _Nvidia Control Panel_) may work and can indeed
|
|
||||||
change the configuration you see within your monitor's OSD, but some monitors
|
|
||||||
will re-enable the setting regardless following suspension.
|
|
||||||
|
|
||||||
### Reproducing
|
|
||||||
|
|
||||||
Ensure _komorebi_ is in an operational state by executing `komorebic start` as
|
|
||||||
normal.
|
|
||||||
|
|
||||||
If _komorebi_ is already unresponsive, then please restart _komorebi_ first by
|
|
||||||
running `komorebic stop` and `komorebic start`.
|
|
||||||
|
|
||||||
1. **`komorebic state`**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"monitors": {
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"id": 65537,
|
|
||||||
"name": "DISPLAY1",
|
|
||||||
"device": "SAM71AA",
|
|
||||||
"device_id": "SAM71AA-5&a1a3e88&0&UID24834",
|
|
||||||
"size": {
|
|
||||||
"left": 0,
|
|
||||||
"top": 0,
|
|
||||||
"right": 5120,
|
|
||||||
"bottom": 1440
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This appears to be fine -- _komorebi_ is aware of the device and associated
|
|
||||||
window handles.
|
|
||||||
|
|
||||||
2. **Let your display go to sleep.**
|
|
||||||
|
|
||||||
Simply turning the monitor off is not enough to reproduce the problem; you must
|
|
||||||
let Windows turn off the display itself.
|
|
||||||
|
|
||||||
To avoid waiting an eternity:
|
|
||||||
|
|
||||||
- _Control Panel_ -> _Hardware and Sound_ -> _Power Options_ -> _Edit Plan
|
|
||||||
Settings_
|
|
||||||
|
|
||||||
_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
|
|
||||||
complete.
|
|
||||||
|
|
||||||
3. **Wake your display again** by pressing any key.
|
|
||||||
|
|
||||||
_komorebi_ should now be unresponsive.
|
|
||||||
|
|
||||||
4. **`komorebic state`**
|
|
||||||
|
|
||||||
Don't stop _komorebi_ just yet.
|
|
||||||
|
|
||||||
Since it's unresponsive, you can open another shell instead to execute the above command.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"monitors": {
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"id": 65537,
|
|
||||||
"name": "DISPLAY1",
|
|
||||||
"device": null,
|
|
||||||
"device_id": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can see the _komorebi_ state is no longer associated with the previous
|
|
||||||
device: `null`, suggesting an issue when the display resumes from a suspended
|
|
||||||
state.
|
|
||||||
@@ -10,9 +10,6 @@ alt + shift + o : komorebic reload-configuration
|
|||||||
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
||||||
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
|
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
|
||||||
|
|
||||||
alt + q : komorebic close
|
|
||||||
alt + m : komorebic minimize
|
|
||||||
|
|
||||||
# Focus windows
|
# Focus windows
|
||||||
alt + h : komorebic focus left
|
alt + h : komorebic focus left
|
||||||
alt + j : komorebic focus down
|
alt + j : komorebic focus down
|
||||||
@@ -59,18 +56,8 @@ alt + y : komorebic flip-layout vertical
|
|||||||
alt + 1 : komorebic focus-workspace 0
|
alt + 1 : komorebic focus-workspace 0
|
||||||
alt + 2 : komorebic focus-workspace 1
|
alt + 2 : komorebic focus-workspace 1
|
||||||
alt + 3 : komorebic focus-workspace 2
|
alt + 3 : komorebic focus-workspace 2
|
||||||
alt + 4 : komorebic focus-workspace 3
|
|
||||||
alt + 5 : komorebic focus-workspace 4
|
|
||||||
alt + 6 : komorebic focus-workspace 5
|
|
||||||
alt + 7 : komorebic focus-workspace 6
|
|
||||||
alt + 8 : komorebic focus-workspace 7
|
|
||||||
|
|
||||||
# Move windows across workspaces
|
# Move windows across workspaces
|
||||||
alt + shift + 1 : komorebic move-to-workspace 0
|
alt + shift + 1 : komorebic move-to-workspace 0
|
||||||
alt + shift + 2 : komorebic move-to-workspace 1
|
alt + shift + 2 : komorebic move-to-workspace 1
|
||||||
alt + shift + 3 : komorebic move-to-workspace 2
|
alt + shift + 3 : komorebic move-to-workspace 2
|
||||||
alt + shift + 4 : komorebic move-to-workspace 3
|
|
||||||
alt + shift + 5 : komorebic move-to-workspace 4
|
|
||||||
alt + shift + 6 : komorebic move-to-workspace 5
|
|
||||||
alt + shift + 7 : komorebic move-to-workspace 6
|
|
||||||
alt + shift + 8 : komorebic move-to-workspace 7
|
|
||||||
|
|||||||
12
justfile
12
justfile
@@ -16,12 +16,19 @@ fmt:
|
|||||||
install-target target:
|
install-target target:
|
||||||
cargo +stable install --path {{ target }} --locked
|
cargo +stable install --path {{ target }} --locked
|
||||||
|
|
||||||
|
prepare:
|
||||||
|
komorebic ahk-asc '~/.config/komorebi/applications.yaml'
|
||||||
|
komorebic pwsh-asc '~/.config/komorebi/applications.yaml'
|
||||||
|
cat '~/.config/komorebi/komorebi.generated.ps1' >komorebi.generated.ps1
|
||||||
|
cat '~/.config/komorebi/komorebi.generated.ahk' >komorebi.generated.ahk
|
||||||
|
|
||||||
install:
|
install:
|
||||||
just install-target komorebic
|
just install-target komorebic
|
||||||
just install-target komorebic-no-console
|
just install-target komorebic-no-console
|
||||||
just install-target komorebi
|
just install-target komorebi
|
||||||
|
|
||||||
run:
|
run:
|
||||||
|
just install-target komorebic
|
||||||
cargo +stable run --bin komorebi --locked
|
cargo +stable run --bin komorebi --locked
|
||||||
|
|
||||||
warn $RUST_LOG="warn":
|
warn $RUST_LOG="warn":
|
||||||
@@ -37,12 +44,17 @@ trace $RUST_LOG="trace":
|
|||||||
just run
|
just run
|
||||||
|
|
||||||
deadlock $RUST_LOG="trace":
|
deadlock $RUST_LOG="trace":
|
||||||
|
just install-komorebic
|
||||||
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
||||||
|
|
||||||
docgen:
|
docgen:
|
||||||
komorebic docgen
|
komorebic docgen
|
||||||
Get-ChildItem -Path "docs/cli" -Recurse -File | ForEach-Object { (Get-Content $_.FullName) -replace 'Usage: ', 'Usage: komorebic.exe ' | Set-Content $_.FullName }
|
Get-ChildItem -Path "docs/cli" -Recurse -File | ForEach-Object { (Get-Content $_.FullName) -replace 'Usage: ', 'Usage: komorebic.exe ' | Set-Content $_.FullName }
|
||||||
|
|
||||||
|
exampledocs:
|
||||||
|
cp whkdrc.sample docs/whkdrc.sample
|
||||||
|
cp komorebi.example.json docs/komorebi.example.json
|
||||||
|
|
||||||
schemagen:
|
schemagen:
|
||||||
komorebic static-config-schema > schema.json
|
komorebic static-config-schema > schema.json
|
||||||
generate-schema-doc .\schema.json --config template_name=js_offline --config minify=false .\static-config-docs\
|
generate-schema-doc .\schema.json --config template_name=js_offline --config minify=false .\static-config-docs\
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-client"
|
name = "komorebi-client"
|
||||||
version = "0.1.27-dev.0"
|
version = "0.1.23-dev.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
pub use komorebi::colour::Colour;
|
|
||||||
pub use komorebi::colour::Rgb;
|
|
||||||
pub use komorebi::container::Container;
|
pub use komorebi::container::Container;
|
||||||
pub use komorebi::monitor::Monitor;
|
pub use komorebi::monitor::Monitor;
|
||||||
pub use komorebi::ring::Ring;
|
pub use komorebi::ring::Ring;
|
||||||
pub use komorebi::window::Window;
|
pub use komorebi::window::Window;
|
||||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||||
pub use komorebi::workspace::Workspace;
|
pub use komorebi::workspace::Workspace;
|
||||||
pub use komorebi::BorderColours;
|
|
||||||
pub use komorebi::GlobalState;
|
|
||||||
pub use komorebi::Notification;
|
pub use komorebi::Notification;
|
||||||
pub use komorebi::NotificationEvent;
|
pub use komorebi::NotificationEvent;
|
||||||
pub use komorebi::RuleDebug;
|
|
||||||
pub use komorebi::StackbarConfig;
|
|
||||||
pub use komorebi::State;
|
pub use komorebi::State;
|
||||||
pub use komorebi::StaticConfig;
|
|
||||||
pub use komorebi::TabsConfig;
|
|
||||||
pub use komorebi_core::Arrangement;
|
pub use komorebi_core::Arrangement;
|
||||||
pub use komorebi_core::Axis;
|
pub use komorebi_core::Axis;
|
||||||
pub use komorebi_core::BorderStyle;
|
|
||||||
pub use komorebi_core::CustomLayout;
|
pub use komorebi_core::CustomLayout;
|
||||||
pub use komorebi_core::CycleDirection;
|
pub use komorebi_core::CycleDirection;
|
||||||
pub use komorebi_core::DefaultLayout;
|
pub use komorebi_core::DefaultLayout;
|
||||||
@@ -29,9 +20,6 @@ pub use komorebi_core::Layout;
|
|||||||
pub use komorebi_core::OperationDirection;
|
pub use komorebi_core::OperationDirection;
|
||||||
pub use komorebi_core::Rect;
|
pub use komorebi_core::Rect;
|
||||||
pub use komorebi_core::SocketMessage;
|
pub use komorebi_core::SocketMessage;
|
||||||
pub use komorebi_core::StackbarLabel;
|
|
||||||
pub use komorebi_core::StackbarMode;
|
|
||||||
pub use komorebi_core::WindowKind;
|
|
||||||
|
|
||||||
use komorebi::DATA_DIR;
|
use komorebi::DATA_DIR;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-core"
|
name = "komorebi-core"
|
||||||
version = "0.1.27-dev.0"
|
version = "0.1.23-dev.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub trait Arrangement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Arrangement for DefaultLayout {
|
impl Arrangement for DefaultLayout {
|
||||||
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn calculate(
|
fn calculate(
|
||||||
&self,
|
&self,
|
||||||
area: &Rect,
|
area: &Rect,
|
||||||
@@ -44,56 +44,8 @@ impl Arrangement for DefaultLayout {
|
|||||||
layout_flip,
|
layout_flip,
|
||||||
calculate_resize_adjustments(resize_dimensions),
|
calculate_resize_adjustments(resize_dimensions),
|
||||||
),
|
),
|
||||||
Self::Columns => {
|
Self::Columns => columns(area, len),
|
||||||
let mut layouts = columns(area, len);
|
Self::Rows => rows(area, len),
|
||||||
|
|
||||||
let adjustment = calculate_columns_adjustment(resize_dimensions);
|
|
||||||
layouts
|
|
||||||
.iter_mut()
|
|
||||||
.zip(adjustment.iter())
|
|
||||||
.for_each(|(layout, adjustment)| {
|
|
||||||
layout.top += adjustment.top;
|
|
||||||
layout.bottom += adjustment.bottom;
|
|
||||||
layout.left += adjustment.left;
|
|
||||||
layout.right += adjustment.right;
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 2.. = len {
|
|
||||||
columns_reverse(&mut layouts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
|
||||||
}
|
|
||||||
Self::Rows => {
|
|
||||||
let mut layouts = rows(area, len);
|
|
||||||
|
|
||||||
let adjustment = calculate_rows_adjustment(resize_dimensions);
|
|
||||||
layouts
|
|
||||||
.iter_mut()
|
|
||||||
.zip(adjustment.iter())
|
|
||||||
.for_each(|(layout, adjustment)| {
|
|
||||||
layout.top += adjustment.top;
|
|
||||||
layout.bottom += adjustment.bottom;
|
|
||||||
layout.left += adjustment.left;
|
|
||||||
layout.right += adjustment.right;
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 2.. = len {
|
|
||||||
rows_reverse(&mut layouts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
|
||||||
}
|
|
||||||
Self::VerticalStack => {
|
Self::VerticalStack => {
|
||||||
let mut layouts: Vec<Rect> = vec![];
|
let mut layouts: Vec<Rect> = vec![];
|
||||||
|
|
||||||
@@ -102,8 +54,16 @@ impl Arrangement for DefaultLayout {
|
|||||||
_ => area.right / 2,
|
_ => area.right / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let main_left = area.left;
|
let mut main_left = area.left;
|
||||||
let stack_left = area.left + primary_right;
|
let mut stack_left = area.left + primary_right;
|
||||||
|
|
||||||
|
match layout_flip {
|
||||||
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
|
||||||
|
main_left = main_left + area.right - primary_right;
|
||||||
|
stack_left = area.left;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if len >= 1 {
|
if len >= 1 {
|
||||||
layouts.push(Rect {
|
layouts.push(Rect {
|
||||||
@@ -126,113 +86,6 @@ impl Arrangement for DefaultLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let adjustment = calculate_vertical_stack_adjustment(resize_dimensions);
|
|
||||||
layouts
|
|
||||||
.iter_mut()
|
|
||||||
.zip(adjustment.iter())
|
|
||||||
.for_each(|(layout, adjustment)| {
|
|
||||||
layout.top += adjustment.top;
|
|
||||||
layout.bottom += adjustment.bottom;
|
|
||||||
layout.left += adjustment.left;
|
|
||||||
layout.right += adjustment.right;
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 2.. = len {
|
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
|
||||||
let primary = &mut primary[0];
|
|
||||||
|
|
||||||
for rect in rest.iter_mut() {
|
|
||||||
rect.left = primary.left;
|
|
||||||
}
|
|
||||||
primary.left = rest[0].left + rest[0].right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 3.. = len {
|
|
||||||
rows_reverse(&mut layouts[1..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
|
||||||
}
|
|
||||||
Self::RightMainVerticalStack => {
|
|
||||||
// Shamelessly borrowed from LeftWM: https://github.com/leftwm/leftwm/commit/f673851745295ae7584a102535566f559d96a941
|
|
||||||
let mut layouts: Vec<Rect> = vec![];
|
|
||||||
|
|
||||||
let primary_width = match len {
|
|
||||||
1 => area.right,
|
|
||||||
_ => area.right / 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let primary_left = match len {
|
|
||||||
1 => 0,
|
|
||||||
_ => area.right - primary_width,
|
|
||||||
};
|
|
||||||
|
|
||||||
if len >= 1 {
|
|
||||||
layouts.push(Rect {
|
|
||||||
left: area.left + primary_left,
|
|
||||||
top: area.top,
|
|
||||||
right: primary_width,
|
|
||||||
bottom: area.bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
if len > 1 {
|
|
||||||
layouts.append(&mut rows(
|
|
||||||
&Rect {
|
|
||||||
left: area.left,
|
|
||||||
top: area.top,
|
|
||||||
right: primary_left,
|
|
||||||
bottom: area.bottom,
|
|
||||||
},
|
|
||||||
len - 1,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let adjustment = calculate_right_vertical_stack_adjustment(resize_dimensions);
|
|
||||||
layouts
|
|
||||||
.iter_mut()
|
|
||||||
.zip(adjustment.iter())
|
|
||||||
.for_each(|(layout, adjustment)| {
|
|
||||||
layout.top += adjustment.top;
|
|
||||||
layout.bottom += adjustment.bottom;
|
|
||||||
layout.left += adjustment.left;
|
|
||||||
layout.right += adjustment.right;
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 2.. = len {
|
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
|
||||||
let primary = &mut primary[0];
|
|
||||||
|
|
||||||
primary.left = rest[0].left;
|
|
||||||
for rect in rest.iter_mut() {
|
|
||||||
rect.left = primary.left + primary.right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 3.. = len {
|
|
||||||
rows_reverse(&mut layouts[1..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
}
|
}
|
||||||
Self::HorizontalStack => {
|
Self::HorizontalStack => {
|
||||||
@@ -243,8 +96,16 @@ impl Arrangement for DefaultLayout {
|
|||||||
_ => area.bottom / 2,
|
_ => area.bottom / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let main_top = area.top;
|
let mut main_top = area.top;
|
||||||
let stack_top = area.top + bottom;
|
let mut stack_top = area.top + bottom;
|
||||||
|
|
||||||
|
match layout_flip {
|
||||||
|
Some(Axis::Vertical | Axis::HorizontalAndVertical) if len > 1 => {
|
||||||
|
main_top = main_top + area.bottom - bottom;
|
||||||
|
stack_top = area.top;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if len >= 1 {
|
if len >= 1 {
|
||||||
layouts.push(Rect {
|
layouts.push(Rect {
|
||||||
@@ -267,155 +128,9 @@ impl Arrangement for DefaultLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let adjustment = calculate_horizontal_stack_adjustment(resize_dimensions);
|
|
||||||
layouts
|
|
||||||
.iter_mut()
|
|
||||||
.zip(adjustment.iter())
|
|
||||||
.for_each(|(layout, adjustment)| {
|
|
||||||
layout.top += adjustment.top;
|
|
||||||
layout.bottom += adjustment.bottom;
|
|
||||||
layout.left += adjustment.left;
|
|
||||||
layout.right += adjustment.right;
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 2.. = len {
|
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
|
||||||
let primary = &mut primary[0];
|
|
||||||
|
|
||||||
for rect in rest.iter_mut() {
|
|
||||||
rect.top = primary.top;
|
|
||||||
}
|
|
||||||
primary.top = rest[0].top + rest[0].bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 3.. = len {
|
|
||||||
columns_reverse(&mut layouts[1..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
|
||||||
}
|
|
||||||
Self::UltrawideVerticalStack => {
|
|
||||||
let mut layouts: Vec<Rect> = vec![];
|
|
||||||
|
|
||||||
let primary_right = match len {
|
|
||||||
1 => area.right,
|
|
||||||
_ => area.right / 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let secondary_right = match len {
|
|
||||||
1 => 0,
|
|
||||||
2 => area.right - primary_right,
|
|
||||||
_ => (area.right - primary_right) / 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (primary_left, secondary_left, stack_left) = match len {
|
|
||||||
1 => (area.left, 0, 0),
|
|
||||||
2 => {
|
|
||||||
let primary = area.left + secondary_right;
|
|
||||||
let secondary = area.left;
|
|
||||||
|
|
||||||
(primary, secondary, 0)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let primary = area.left + secondary_right;
|
|
||||||
let secondary = area.left;
|
|
||||||
let stack = area.left + primary_right + secondary_right;
|
|
||||||
|
|
||||||
(primary, secondary, stack)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if len >= 1 {
|
|
||||||
layouts.push(Rect {
|
|
||||||
left: primary_left,
|
|
||||||
top: area.top,
|
|
||||||
right: primary_right,
|
|
||||||
bottom: area.bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
if len >= 2 {
|
|
||||||
layouts.push(Rect {
|
|
||||||
left: secondary_left,
|
|
||||||
top: area.top,
|
|
||||||
right: secondary_right,
|
|
||||||
bottom: area.bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
if len > 2 {
|
|
||||||
layouts.append(&mut rows(
|
|
||||||
&Rect {
|
|
||||||
left: stack_left,
|
|
||||||
top: area.top,
|
|
||||||
right: secondary_right,
|
|
||||||
bottom: area.bottom,
|
|
||||||
},
|
|
||||||
len - 2,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let adjustment = calculate_ultrawide_adjustment(resize_dimensions);
|
|
||||||
layouts
|
|
||||||
.iter_mut()
|
|
||||||
.zip(adjustment.iter())
|
|
||||||
.for_each(|(layout, adjustment)| {
|
|
||||||
layout.top += adjustment.top;
|
|
||||||
layout.bottom += adjustment.bottom;
|
|
||||||
layout.left += adjustment.left;
|
|
||||||
layout.right += adjustment.right;
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
match len {
|
|
||||||
2 => {
|
|
||||||
let (primary, secondary) = layouts.split_at_mut(1);
|
|
||||||
let primary = &mut primary[0];
|
|
||||||
let secondary = &mut secondary[0];
|
|
||||||
|
|
||||||
primary.left = secondary.left;
|
|
||||||
secondary.left = primary.left + primary.right;
|
|
||||||
}
|
|
||||||
3.. => {
|
|
||||||
let (primary, rest) = layouts.split_at_mut(1);
|
|
||||||
let (secondary, tertiary) = rest.split_at_mut(1);
|
|
||||||
let primary = &mut primary[0];
|
|
||||||
let secondary = &mut secondary[0];
|
|
||||||
|
|
||||||
for rect in tertiary.iter_mut() {
|
|
||||||
rect.left = secondary.left;
|
|
||||||
}
|
|
||||||
primary.left = tertiary[0].left + tertiary[0].right;
|
|
||||||
secondary.left = primary.left + primary.right;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
layout_flip,
|
|
||||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
|
||||||
) {
|
|
||||||
if let 4.. = len {
|
|
||||||
rows_reverse(&mut layouts[2..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
}
|
}
|
||||||
|
Self::UltrawideVerticalStack => ultrawide(area, len, layout_flip, resize_dimensions),
|
||||||
#[allow(
|
#[allow(
|
||||||
clippy::cast_precision_loss,
|
clippy::cast_precision_loss,
|
||||||
clippy::cast_possible_truncation,
|
clippy::cast_possible_truncation,
|
||||||
@@ -606,6 +321,7 @@ impl Arrangement for CustomLayout {
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Axis {
|
pub enum Axis {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
@@ -654,22 +370,6 @@ fn rows(area: &Rect, len: usize) -> Vec<Rect> {
|
|||||||
layouts
|
layouts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn columns_reverse(columns: &mut [Rect]) {
|
|
||||||
let len = columns.len();
|
|
||||||
columns[len - 1].left = columns[0].left;
|
|
||||||
for i in (0..len - 1).rev() {
|
|
||||||
columns[i].left = columns[i + 1].left + columns[i + 1].right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rows_reverse(rows: &mut [Rect]) {
|
|
||||||
let len = rows.len();
|
|
||||||
rows[len - 1].top = rows[0].top;
|
|
||||||
for i in (0..len - 1).rev() {
|
|
||||||
rows[i].top = rows[i + 1].top + rows[i + 1].bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {
|
fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {
|
||||||
let mut resize_adjustments = resize_dimensions.to_vec();
|
let mut resize_adjustments = resize_dimensions.to_vec();
|
||||||
|
|
||||||
@@ -863,187 +563,6 @@ fn recursive_fibonacci(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_columns_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
let mut result = vec![Rect::default(); len];
|
|
||||||
match len {
|
|
||||||
0 | 1 => (),
|
|
||||||
_ => {
|
|
||||||
for (i, rect) in resize_dimensions.iter().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
if i != 0 {
|
|
||||||
resize_right(&mut result[i - 1], rect.left);
|
|
||||||
resize_left(&mut result[i], rect.left);
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != len - 1 {
|
|
||||||
resize_right(&mut result[i], rect.right);
|
|
||||||
resize_left(&mut result[i + 1], rect.right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_rows_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
let mut result = vec![Rect::default(); len];
|
|
||||||
match len {
|
|
||||||
0 | 1 => (),
|
|
||||||
_ => {
|
|
||||||
for (i, rect) in resize_dimensions.iter().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
if i != 0 {
|
|
||||||
resize_bottom(&mut result[i - 1], rect.top);
|
|
||||||
resize_top(&mut result[i], rect.top);
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != len - 1 {
|
|
||||||
resize_bottom(&mut result[i], rect.bottom);
|
|
||||||
resize_top(&mut result[i + 1], rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_vertical_stack_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
let mut result = vec![Rect::default(); len];
|
|
||||||
match len {
|
|
||||||
// One container can't be resized
|
|
||||||
0 | 1 => (),
|
|
||||||
_ => {
|
|
||||||
let (master, stack) = result.split_at_mut(1);
|
|
||||||
let primary = &mut master[0];
|
|
||||||
|
|
||||||
if let Some(resize) = resize_dimensions[0] {
|
|
||||||
resize_right(primary, resize.right);
|
|
||||||
for s in &mut *stack {
|
|
||||||
resize_left(s, resize.right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle stack on the right
|
|
||||||
for (i, rect) in resize_dimensions[1..].iter().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
resize_right(primary, rect.left);
|
|
||||||
stack
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|vertical_element| resize_left(vertical_element, rect.left));
|
|
||||||
|
|
||||||
// Containers in stack except first can be resized up displacing container
|
|
||||||
// above them
|
|
||||||
if i != 0 {
|
|
||||||
resize_bottom(&mut stack[i - 1], rect.top);
|
|
||||||
resize_top(&mut stack[i], rect.top);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Containers in stack except last can be resized down displacing container
|
|
||||||
// below them
|
|
||||||
if i != stack.len() - 1 {
|
|
||||||
resize_bottom(&mut stack[i], rect.bottom);
|
|
||||||
resize_top(&mut stack[i + 1], rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_right_vertical_stack_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
let mut result = vec![Rect::default(); len];
|
|
||||||
match len {
|
|
||||||
// One container can't be resized
|
|
||||||
0 | 1 => (),
|
|
||||||
_ => {
|
|
||||||
let (master, stack) = result.split_at_mut(1);
|
|
||||||
let primary = &mut master[0];
|
|
||||||
|
|
||||||
if let Some(resize) = resize_dimensions[0] {
|
|
||||||
resize_left(primary, resize.left);
|
|
||||||
for s in &mut *stack {
|
|
||||||
resize_right(s, resize.left);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle stack on the left
|
|
||||||
for (i, rect) in resize_dimensions[1..].iter().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
resize_left(primary, rect.right);
|
|
||||||
stack
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|vertical_element| resize_right(vertical_element, rect.right));
|
|
||||||
|
|
||||||
// Containers in stack except first can be resized up displacing container
|
|
||||||
// above them
|
|
||||||
if i != 0 {
|
|
||||||
resize_bottom(&mut stack[i - 1], rect.top);
|
|
||||||
resize_top(&mut stack[i], rect.top);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Containers in stack except last can be resized down displacing container
|
|
||||||
// below them
|
|
||||||
if i != stack.len() - 1 {
|
|
||||||
resize_bottom(&mut stack[i], rect.bottom);
|
|
||||||
resize_top(&mut stack[i + 1], rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_horizontal_stack_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
let mut result = vec![Rect::default(); len];
|
|
||||||
match len {
|
|
||||||
0 | 1 => (),
|
|
||||||
_ => {
|
|
||||||
let (primary, rest) = result.split_at_mut(1);
|
|
||||||
let primary = &mut primary[0];
|
|
||||||
if let Some(resize_primary) = resize_dimensions[0] {
|
|
||||||
resize_bottom(primary, resize_primary.bottom);
|
|
||||||
|
|
||||||
for horizontal_element in &mut *rest {
|
|
||||||
resize_top(horizontal_element, resize_primary.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, rect) in resize_dimensions[1..].iter().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
resize_bottom(primary, rect.top);
|
|
||||||
rest.iter_mut()
|
|
||||||
.for_each(|vertical_element| resize_top(vertical_element, rect.top));
|
|
||||||
|
|
||||||
if i != 0 {
|
|
||||||
resize_right(&mut rest[i - 1], rect.left);
|
|
||||||
resize_left(&mut rest[i], rect.left);
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != rest.len() - 1 {
|
|
||||||
resize_right(&mut rest[i], rect.right);
|
|
||||||
resize_left(&mut rest[i + 1], rect.right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
fn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
|
||||||
let len = resize_dimensions.len();
|
let len = resize_dimensions.len();
|
||||||
let mut result = vec![Rect::default(); len];
|
let mut result = vec![Rect::default(); len];
|
||||||
@@ -1134,3 +653,99 @@ fn resize_top(rect: &mut Rect, resize: i32) {
|
|||||||
fn resize_bottom(rect: &mut Rect, resize: i32) {
|
fn resize_bottom(rect: &mut Rect, resize: i32) {
|
||||||
rect.bottom += resize / 2;
|
rect.bottom += resize / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ultrawide(
|
||||||
|
area: &Rect,
|
||||||
|
len: usize,
|
||||||
|
layout_flip: Option<Axis>,
|
||||||
|
resize_dimensions: &[Option<Rect>],
|
||||||
|
) -> Vec<Rect> {
|
||||||
|
let mut layouts: Vec<Rect> = vec![];
|
||||||
|
|
||||||
|
let primary_right = match len {
|
||||||
|
1 => area.right,
|
||||||
|
_ => area.right / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let secondary_right = match len {
|
||||||
|
1 => 0,
|
||||||
|
2 => area.right - primary_right,
|
||||||
|
_ => (area.right - primary_right) / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (primary_left, secondary_left, stack_left) = match len {
|
||||||
|
1 => (area.left, 0, 0),
|
||||||
|
2 => {
|
||||||
|
let mut primary = area.left + secondary_right;
|
||||||
|
let mut secondary = area.left;
|
||||||
|
|
||||||
|
match layout_flip {
|
||||||
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
|
||||||
|
primary = area.left;
|
||||||
|
secondary = area.left + primary_right;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
(primary, secondary, 0)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let primary = area.left + secondary_right;
|
||||||
|
let mut secondary = area.left;
|
||||||
|
let mut stack = area.left + primary_right + secondary_right;
|
||||||
|
|
||||||
|
match layout_flip {
|
||||||
|
Some(Axis::Horizontal | Axis::HorizontalAndVertical) if len > 1 => {
|
||||||
|
secondary = area.left + primary_right + secondary_right;
|
||||||
|
stack = area.left;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
(primary, secondary, stack)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if len >= 1 {
|
||||||
|
layouts.push(Rect {
|
||||||
|
left: primary_left,
|
||||||
|
top: area.top,
|
||||||
|
right: primary_right,
|
||||||
|
bottom: area.bottom,
|
||||||
|
});
|
||||||
|
|
||||||
|
if len >= 2 {
|
||||||
|
layouts.push(Rect {
|
||||||
|
left: secondary_left,
|
||||||
|
top: area.top,
|
||||||
|
right: secondary_right,
|
||||||
|
bottom: area.bottom,
|
||||||
|
});
|
||||||
|
|
||||||
|
if len > 2 {
|
||||||
|
layouts.append(&mut rows(
|
||||||
|
&Rect {
|
||||||
|
left: stack_left,
|
||||||
|
top: area.top,
|
||||||
|
right: secondary_right,
|
||||||
|
bottom: area.bottom,
|
||||||
|
},
|
||||||
|
len - 2,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustment = calculate_ultrawide_adjustment(resize_dimensions);
|
||||||
|
layouts
|
||||||
|
.iter_mut()
|
||||||
|
.zip(adjustment.iter())
|
||||||
|
.for_each(|(layout, adjustment)| {
|
||||||
|
layout.top += adjustment.top;
|
||||||
|
layout.bottom += adjustment.bottom;
|
||||||
|
layout.left += adjustment.left;
|
||||||
|
layout.right += adjustment.right;
|
||||||
|
});
|
||||||
|
|
||||||
|
layouts
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ use crate::ApplicationIdentifier;
|
|||||||
pub enum ApplicationOptions {
|
pub enum ApplicationOptions {
|
||||||
ObjectNameChange,
|
ObjectNameChange,
|
||||||
Layered,
|
Layered,
|
||||||
|
BorderOverflow,
|
||||||
TrayAndMultiWindow,
|
TrayAndMultiWindow,
|
||||||
Force,
|
Force,
|
||||||
BorderOverflow,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationOptions {
|
impl ApplicationOptions {
|
||||||
@@ -31,15 +31,15 @@ impl ApplicationOptions {
|
|||||||
ApplicationOptions::Layered => {
|
ApplicationOptions::Layered => {
|
||||||
format!("komorebic.exe identify-layered-application {kind} \"{id}\"",)
|
format!("komorebic.exe identify-layered-application {kind} \"{id}\"",)
|
||||||
}
|
}
|
||||||
|
ApplicationOptions::BorderOverflow => {
|
||||||
|
format!("komorebic.exe identify-border-overflow-application {kind} \"{id}\"",)
|
||||||
|
}
|
||||||
ApplicationOptions::TrayAndMultiWindow => {
|
ApplicationOptions::TrayAndMultiWindow => {
|
||||||
format!("komorebic.exe identify-tray-application {kind} \"{id}\"",)
|
format!("komorebic.exe identify-tray-application {kind} \"{id}\"",)
|
||||||
}
|
}
|
||||||
ApplicationOptions::Force => {
|
ApplicationOptions::Force => {
|
||||||
format!("komorebic.exe manage-rule {kind} \"{id}\"")
|
format!("komorebic.exe manage-rule {kind} \"{id}\"")
|
||||||
}
|
}
|
||||||
ApplicationOptions::BorderOverflow => {
|
|
||||||
unreachable!("deprecated");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use strum::EnumString;
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum CycleDirection {
|
pub enum CycleDirection {
|
||||||
Previous,
|
Previous,
|
||||||
Next,
|
Next,
|
||||||
|
|||||||
@@ -10,18 +10,9 @@ use crate::Rect;
|
|||||||
use crate::Sizing;
|
use crate::Sizing;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
Copy,
|
|
||||||
Debug,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
Eq,
|
|
||||||
PartialEq,
|
|
||||||
Display,
|
|
||||||
EnumString,
|
|
||||||
ValueEnum,
|
|
||||||
JsonSchema,
|
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum DefaultLayout {
|
pub enum DefaultLayout {
|
||||||
BSP,
|
BSP,
|
||||||
Columns,
|
Columns,
|
||||||
@@ -30,7 +21,6 @@ pub enum DefaultLayout {
|
|||||||
HorizontalStack,
|
HorizontalStack,
|
||||||
UltrawideVerticalStack,
|
UltrawideVerticalStack,
|
||||||
Grid,
|
Grid,
|
||||||
RightMainVerticalStack,
|
|
||||||
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
|
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,16 +35,7 @@ impl DefaultLayout {
|
|||||||
sizing: Sizing,
|
sizing: Sizing,
|
||||||
delta: i32,
|
delta: i32,
|
||||||
) -> Option<Rect> {
|
) -> Option<Rect> {
|
||||||
if !matches!(
|
if !matches!(self, Self::BSP) && !matches!(self, Self::UltrawideVerticalStack) {
|
||||||
self,
|
|
||||||
Self::BSP
|
|
||||||
| Self::Columns
|
|
||||||
| Self::Rows
|
|
||||||
| Self::VerticalStack
|
|
||||||
| Self::RightMainVerticalStack
|
|
||||||
| Self::HorizontalStack
|
|
||||||
| Self::UltrawideVerticalStack
|
|
||||||
) {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,8 +137,7 @@ impl DefaultLayout {
|
|||||||
Self::VerticalStack => Self::HorizontalStack,
|
Self::VerticalStack => Self::HorizontalStack,
|
||||||
Self::HorizontalStack => Self::UltrawideVerticalStack,
|
Self::HorizontalStack => Self::UltrawideVerticalStack,
|
||||||
Self::UltrawideVerticalStack => Self::Grid,
|
Self::UltrawideVerticalStack => Self::Grid,
|
||||||
Self::Grid => Self::RightMainVerticalStack,
|
Self::Grid => Self::BSP,
|
||||||
Self::RightMainVerticalStack => Self::BSP,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +150,7 @@ impl DefaultLayout {
|
|||||||
Self::VerticalStack => Self::Rows,
|
Self::VerticalStack => Self::Rows,
|
||||||
Self::Rows => Self::Columns,
|
Self::Rows => Self::Columns,
|
||||||
Self::Columns => Self::Grid,
|
Self::Columns => Self::Grid,
|
||||||
Self::Grid => Self::RightMainVerticalStack,
|
Self::Grid => Self::BSP,
|
||||||
Self::RightMainVerticalStack => Self::BSP,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ impl Direction for DefaultLayout {
|
|||||||
Self::BSP => count > 2 && idx != 0 && idx != 1,
|
Self::BSP => count > 2 && idx != 0 && idx != 1,
|
||||||
Self::Columns => false,
|
Self::Columns => false,
|
||||||
Self::Rows | Self::HorizontalStack => idx != 0,
|
Self::Rows | Self::HorizontalStack => idx != 0,
|
||||||
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
|
Self::VerticalStack => idx != 0 && idx != 1,
|
||||||
Self::UltrawideVerticalStack => idx > 2,
|
Self::UltrawideVerticalStack => idx > 2,
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
||||||
},
|
},
|
||||||
@@ -103,7 +103,7 @@ impl Direction for DefaultLayout {
|
|||||||
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
|
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
|
||||||
Self::Columns => false,
|
Self::Columns => false,
|
||||||
Self::Rows => idx != count - 1,
|
Self::Rows => idx != count - 1,
|
||||||
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,
|
Self::VerticalStack => idx != 0 && idx != count - 1,
|
||||||
Self::HorizontalStack => idx == 0,
|
Self::HorizontalStack => idx == 0,
|
||||||
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
|
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
||||||
@@ -111,7 +111,6 @@ impl Direction for DefaultLayout {
|
|||||||
OperationDirection::Left => match self {
|
OperationDirection::Left => match self {
|
||||||
Self::BSP => count > 1 && idx != 0,
|
Self::BSP => count > 1 && idx != 0,
|
||||||
Self::Columns | Self::VerticalStack => idx != 0,
|
Self::Columns | Self::VerticalStack => idx != 0,
|
||||||
Self::RightMainVerticalStack => idx == 0,
|
|
||||||
Self::Rows => false,
|
Self::Rows => false,
|
||||||
Self::HorizontalStack => idx != 0 && idx != 1,
|
Self::HorizontalStack => idx != 0 && idx != 1,
|
||||||
Self::UltrawideVerticalStack => count > 1 && idx != 1,
|
Self::UltrawideVerticalStack => count > 1 && idx != 1,
|
||||||
@@ -122,7 +121,6 @@ impl Direction for DefaultLayout {
|
|||||||
Self::Columns => idx != count - 1,
|
Self::Columns => idx != count - 1,
|
||||||
Self::Rows => false,
|
Self::Rows => false,
|
||||||
Self::VerticalStack => idx == 0,
|
Self::VerticalStack => idx == 0,
|
||||||
Self::RightMainVerticalStack => idx != 0,
|
|
||||||
Self::HorizontalStack => idx != 0 && idx != count - 1,
|
Self::HorizontalStack => idx != 0 && idx != count - 1,
|
||||||
Self::UltrawideVerticalStack => match count {
|
Self::UltrawideVerticalStack => match count {
|
||||||
0 | 1 => false,
|
0 | 1 => false,
|
||||||
@@ -149,10 +147,7 @@ impl Direction for DefaultLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Columns => unreachable!(),
|
Self::Columns => unreachable!(),
|
||||||
Self::Rows
|
Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx - 1,
|
||||||
| Self::VerticalStack
|
|
||||||
| Self::UltrawideVerticalStack
|
|
||||||
| Self::RightMainVerticalStack => idx - 1,
|
|
||||||
Self::HorizontalStack => 0,
|
Self::HorizontalStack => 0,
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
Self::Grid => grid_neighbor(op_direction, idx, count),
|
||||||
}
|
}
|
||||||
@@ -165,11 +160,7 @@ impl Direction for DefaultLayout {
|
|||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::BSP
|
Self::BSP | Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx + 1,
|
||||||
| Self::Rows
|
|
||||||
| Self::VerticalStack
|
|
||||||
| Self::UltrawideVerticalStack
|
|
||||||
| Self::RightMainVerticalStack => idx + 1,
|
|
||||||
Self::Columns => unreachable!(),
|
Self::Columns => unreachable!(),
|
||||||
Self::HorizontalStack => 1,
|
Self::HorizontalStack => 1,
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
Self::Grid => grid_neighbor(op_direction, idx, count),
|
||||||
@@ -193,7 +184,6 @@ impl Direction for DefaultLayout {
|
|||||||
Self::Columns | Self::HorizontalStack => idx - 1,
|
Self::Columns | Self::HorizontalStack => idx - 1,
|
||||||
Self::Rows => unreachable!(),
|
Self::Rows => unreachable!(),
|
||||||
Self::VerticalStack => 0,
|
Self::VerticalStack => 0,
|
||||||
Self::RightMainVerticalStack => 1,
|
|
||||||
Self::UltrawideVerticalStack => match idx {
|
Self::UltrawideVerticalStack => match idx {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
1 => unreachable!(),
|
1 => unreachable!(),
|
||||||
@@ -213,7 +203,6 @@ impl Direction for DefaultLayout {
|
|||||||
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
||||||
Self::Rows => unreachable!(),
|
Self::Rows => unreachable!(),
|
||||||
Self::VerticalStack => 1,
|
Self::VerticalStack => 1,
|
||||||
Self::RightMainVerticalStack => 0,
|
|
||||||
Self::UltrawideVerticalStack => match idx {
|
Self::UltrawideVerticalStack => match idx {
|
||||||
1 => 0,
|
1 => 0,
|
||||||
0 => 2,
|
0 => 2,
|
||||||
|
|||||||
@@ -57,9 +57,7 @@ pub enum SocketMessage {
|
|||||||
SendContainerToWorkspaceNumber(usize),
|
SendContainerToWorkspaceNumber(usize),
|
||||||
CycleSendContainerToWorkspace(CycleDirection),
|
CycleSendContainerToWorkspace(CycleDirection),
|
||||||
SendContainerToMonitorWorkspaceNumber(usize, usize),
|
SendContainerToMonitorWorkspaceNumber(usize, usize),
|
||||||
MoveContainerToMonitorWorkspaceNumber(usize, usize),
|
|
||||||
SendContainerToNamedWorkspace(String),
|
SendContainerToNamedWorkspace(String),
|
||||||
CycleMoveWorkspaceToMonitor(CycleDirection),
|
|
||||||
MoveWorkspaceToMonitorNumber(usize),
|
MoveWorkspaceToMonitorNumber(usize),
|
||||||
SwapWorkspacesToMonitorNumber(usize),
|
SwapWorkspacesToMonitorNumber(usize),
|
||||||
ForceFocus,
|
ForceFocus,
|
||||||
@@ -67,7 +65,6 @@ pub enum SocketMessage {
|
|||||||
Minimize,
|
Minimize,
|
||||||
Promote,
|
Promote,
|
||||||
PromoteFocus,
|
PromoteFocus,
|
||||||
PromoteWindow(OperationDirection),
|
|
||||||
ToggleFloat,
|
ToggleFloat,
|
||||||
ToggleMonocle,
|
ToggleMonocle,
|
||||||
ToggleMaximize,
|
ToggleMaximize,
|
||||||
@@ -132,22 +129,11 @@ pub enum SocketMessage {
|
|||||||
WatchConfiguration(bool),
|
WatchConfiguration(bool),
|
||||||
CompleteConfiguration,
|
CompleteConfiguration,
|
||||||
AltFocusHack(bool),
|
AltFocusHack(bool),
|
||||||
#[serde(alias = "ActiveWindowBorder")]
|
ActiveWindowBorder(bool),
|
||||||
Border(bool),
|
ActiveWindowBorderColour(WindowKind, u32, u32, u32),
|
||||||
#[serde(alias = "ActiveWindowBorderColour")]
|
ActiveWindowBorderWidth(i32),
|
||||||
BorderColour(WindowKind, u32, u32, u32),
|
ActiveWindowBorderOffset(i32),
|
||||||
#[serde(alias = "ActiveWindowBorderStyle")]
|
|
||||||
BorderStyle(BorderStyle),
|
|
||||||
BorderWidth(i32),
|
|
||||||
BorderOffset(i32),
|
|
||||||
InvisibleBorders(Rect),
|
InvisibleBorders(Rect),
|
||||||
StackbarMode(StackbarMode),
|
|
||||||
StackbarLabel(StackbarLabel),
|
|
||||||
StackbarFocusedTextColour(u32, u32, u32),
|
|
||||||
StackbarUnfocusedTextColour(u32, u32, u32),
|
|
||||||
StackbarBackgroundColour(u32, u32, u32),
|
|
||||||
StackbarHeight(i32),
|
|
||||||
StackbarTabWidth(i32),
|
|
||||||
WorkAreaOffset(Rect),
|
WorkAreaOffset(Rect),
|
||||||
MonitorWorkAreaOffset(usize, Rect),
|
MonitorWorkAreaOffset(usize, Rect),
|
||||||
ResizeDelta(i32),
|
ResizeDelta(i32),
|
||||||
@@ -162,7 +148,6 @@ pub enum SocketMessage {
|
|||||||
IdentifyLayeredApplication(ApplicationIdentifier, String),
|
IdentifyLayeredApplication(ApplicationIdentifier, String),
|
||||||
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
|
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
|
||||||
State,
|
State,
|
||||||
GlobalState,
|
|
||||||
VisibleWindows,
|
VisibleWindows,
|
||||||
Query(StateQuery),
|
Query(StateQuery),
|
||||||
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
|
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
|
||||||
@@ -180,7 +165,6 @@ pub enum SocketMessage {
|
|||||||
SocketSchema,
|
SocketSchema,
|
||||||
StaticConfigSchema,
|
StaticConfigSchema,
|
||||||
GenerateStaticConfig,
|
GenerateStaticConfig,
|
||||||
DebugWindow(isize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SocketMessage {
|
impl SocketMessage {
|
||||||
@@ -197,48 +181,20 @@ impl FromStr for SocketMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub enum StackbarMode {
|
|
||||||
Always,
|
|
||||||
Never,
|
|
||||||
OnStack,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
|
||||||
)]
|
|
||||||
pub enum StackbarLabel {
|
|
||||||
#[default]
|
|
||||||
Process,
|
|
||||||
Title,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
|
||||||
)]
|
|
||||||
pub enum BorderStyle {
|
|
||||||
#[default]
|
|
||||||
/// Use the system border style
|
|
||||||
System,
|
|
||||||
/// Use the Windows 11-style rounded borders
|
|
||||||
Rounded,
|
|
||||||
/// Use the Windows 10-style square borders
|
|
||||||
Square,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum WindowKind {
|
pub enum WindowKind {
|
||||||
Single,
|
Single,
|
||||||
Stack,
|
Stack,
|
||||||
Monocle,
|
Monocle,
|
||||||
Unfocused,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum StateQuery {
|
pub enum StateQuery {
|
||||||
FocusedMonitorIndex,
|
FocusedMonitorIndex,
|
||||||
FocusedWorkspaceIndex,
|
FocusedWorkspaceIndex,
|
||||||
@@ -259,6 +215,7 @@ pub enum StateQuery {
|
|||||||
ValueEnum,
|
ValueEnum,
|
||||||
JsonSchema,
|
JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum ApplicationIdentifier {
|
pub enum ApplicationIdentifier {
|
||||||
#[serde(alias = "exe")]
|
#[serde(alias = "exe")]
|
||||||
Exe,
|
Exe,
|
||||||
@@ -273,6 +230,7 @@ pub enum ApplicationIdentifier {
|
|||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum FocusFollowsMouseImplementation {
|
pub enum FocusFollowsMouseImplementation {
|
||||||
/// A custom FFM implementation (slightly more CPU-intensive)
|
/// A custom FFM implementation (slightly more CPU-intensive)
|
||||||
Komorebi,
|
Komorebi,
|
||||||
@@ -283,6 +241,7 @@ pub enum FocusFollowsMouseImplementation {
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum WindowContainerBehaviour {
|
pub enum WindowContainerBehaviour {
|
||||||
/// Create a new container for each new window
|
/// Create a new container for each new window
|
||||||
Create,
|
Create,
|
||||||
@@ -293,18 +252,18 @@ pub enum WindowContainerBehaviour {
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum MoveBehaviour {
|
pub enum MoveBehaviour {
|
||||||
/// Swap the window container with the window container at the edge of the adjacent monitor
|
/// Swap the window container with the window container at the edge of the adjacent monitor
|
||||||
Swap,
|
Swap,
|
||||||
/// Insert the window container into the focused workspace on the adjacent monitor
|
/// Insert the window container into the focused workspace on the adjacent monitor
|
||||||
Insert,
|
Insert,
|
||||||
/// Do nothing if trying to move a window container in the direction of an adjacent monitor
|
|
||||||
NoOp,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum HidingBehaviour {
|
pub enum HidingBehaviour {
|
||||||
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)
|
||||||
Hide,
|
Hide,
|
||||||
@@ -317,6 +276,7 @@ pub enum HidingBehaviour {
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum OperationBehaviour {
|
pub enum OperationBehaviour {
|
||||||
/// Process komorebic commands on temporarily unmanaged/floated windows
|
/// Process komorebic commands on temporarily unmanaged/floated windows
|
||||||
Op,
|
Op,
|
||||||
@@ -327,6 +287,7 @@ pub enum OperationBehaviour {
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Sizing {
|
pub enum Sizing {
|
||||||
Increase,
|
Increase,
|
||||||
Decrease,
|
Decrease,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::Axis;
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||||
)]
|
)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum OperationDirection {
|
pub enum OperationDirection {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "komorebi-gui"
|
|
||||||
version = "0.1.27-dev.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
egui_extras = { version = "0.27" }
|
|
||||||
eframe = "0.27"
|
|
||||||
komorebi-client = { path = "../komorebi-client" }
|
|
||||||
serde_json = "1"
|
|
||||||
random_word = { version = "0.4.3", features = ["en"] }
|
|
||||||
windows = { workspace = true }
|
|
||||||
@@ -1,805 +0,0 @@
|
|||||||
use eframe::egui;
|
|
||||||
use eframe::egui::color_picker::Alpha;
|
|
||||||
use eframe::egui::Color32;
|
|
||||||
use eframe::egui::ViewportBuilder;
|
|
||||||
use komorebi_client::BorderStyle;
|
|
||||||
use komorebi_client::Colour;
|
|
||||||
use komorebi_client::DefaultLayout;
|
|
||||||
use komorebi_client::GlobalState;
|
|
||||||
use komorebi_client::Layout;
|
|
||||||
use komorebi_client::Rect;
|
|
||||||
use komorebi_client::Rgb;
|
|
||||||
use komorebi_client::RuleDebug;
|
|
||||||
use komorebi_client::SocketMessage;
|
|
||||||
use komorebi_client::StackbarLabel;
|
|
||||||
use komorebi_client::StackbarMode;
|
|
||||||
use komorebi_client::State;
|
|
||||||
use komorebi_client::Window;
|
|
||||||
use komorebi_client::WindowKind;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::time::Duration;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let native_options = eframe::NativeOptions {
|
|
||||||
viewport: ViewportBuilder::default()
|
|
||||||
.with_always_on_top()
|
|
||||||
.with_inner_size([320.0, 500.0]),
|
|
||||||
follow_system_theme: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = eframe::run_native(
|
|
||||||
"komorebi-gui",
|
|
||||||
native_options,
|
|
||||||
Box::new(|cc| Box::new(KomorebiGui::new(cc))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BorderColours {
|
|
||||||
single: Color32,
|
|
||||||
stack: Color32,
|
|
||||||
monocle: Color32,
|
|
||||||
unfocused: Color32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BorderConfig {
|
|
||||||
border_enabled: bool,
|
|
||||||
border_colours: BorderColours,
|
|
||||||
border_style: BorderStyle,
|
|
||||||
border_offset: i32,
|
|
||||||
border_width: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StackbarConfig {
|
|
||||||
mode: StackbarMode,
|
|
||||||
label: StackbarLabel,
|
|
||||||
height: i32,
|
|
||||||
width: i32,
|
|
||||||
focused_text_colour: Color32,
|
|
||||||
unfocused_text_colour: Color32,
|
|
||||||
background_colour: Color32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MonitorConfig {
|
|
||||||
size: Rect,
|
|
||||||
work_area_offset: Rect,
|
|
||||||
workspaces: Vec<WorkspaceConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&komorebi_client::Monitor> for MonitorConfig {
|
|
||||||
fn from(value: &komorebi_client::Monitor) -> Self {
|
|
||||||
let mut workspaces = vec![];
|
|
||||||
for ws in value.workspaces() {
|
|
||||||
workspaces.push(WorkspaceConfig::from(ws));
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
size: *value.size(),
|
|
||||||
work_area_offset: value.work_area_offset().unwrap_or_default(),
|
|
||||||
workspaces,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WorkspaceConfig {
|
|
||||||
name: String,
|
|
||||||
tile: bool,
|
|
||||||
layout: DefaultLayout,
|
|
||||||
container_padding: i32,
|
|
||||||
workspace_padding: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&komorebi_client::Workspace> for WorkspaceConfig {
|
|
||||||
fn from(value: &komorebi_client::Workspace) -> Self {
|
|
||||||
let layout = match value.layout() {
|
|
||||||
Layout::Default(layout) => *layout,
|
|
||||||
Layout::Custom(_) => DefaultLayout::BSP,
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = value
|
|
||||||
.name()
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
|
|
||||||
|
|
||||||
Self {
|
|
||||||
layout,
|
|
||||||
name,
|
|
||||||
tile: *value.tile(),
|
|
||||||
workspace_padding: value.workspace_padding().unwrap_or(20),
|
|
||||||
container_padding: value.container_padding().unwrap_or(20),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct KomorebiGui {
|
|
||||||
border_config: BorderConfig,
|
|
||||||
stackbar_config: StackbarConfig,
|
|
||||||
mouse_follows_focus: bool,
|
|
||||||
monitors: Vec<MonitorConfig>,
|
|
||||||
workspace_names: HashMap<usize, Vec<String>>,
|
|
||||||
debug_hwnd: isize,
|
|
||||||
debug_windows: Vec<Window>,
|
|
||||||
debug_rule: Option<RuleDebug>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn colour32(colour: Option<Colour>) -> Color32 {
|
|
||||||
match colour {
|
|
||||||
Some(Colour::Rgb(rgb)) => Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8),
|
|
||||||
Some(Colour::Hex(hex)) => {
|
|
||||||
let rgb = Rgb::from(hex);
|
|
||||||
Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8)
|
|
||||||
}
|
|
||||||
None => Color32::from_rgb(0, 0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KomorebiGui {
|
|
||||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
|
||||||
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
|
||||||
// Restore app state using cc.storage (requires the "persistence" feature).
|
|
||||||
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
|
||||||
// for e.g. egui::PaintCallback.
|
|
||||||
let global_state: GlobalState = serde_json::from_str(
|
|
||||||
&komorebi_client::send_query(&SocketMessage::GlobalState).unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let state: State =
|
|
||||||
serde_json::from_str(&komorebi_client::send_query(&SocketMessage::State).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let border_colours = BorderColours {
|
|
||||||
single: colour32(global_state.border_colours.single),
|
|
||||||
stack: colour32(global_state.border_colours.stack),
|
|
||||||
monocle: colour32(global_state.border_colours.monocle),
|
|
||||||
unfocused: colour32(global_state.border_colours.unfocused),
|
|
||||||
};
|
|
||||||
|
|
||||||
let border_config = BorderConfig {
|
|
||||||
border_enabled: global_state.border_enabled,
|
|
||||||
border_colours,
|
|
||||||
border_style: global_state.border_style,
|
|
||||||
border_offset: global_state.border_offset,
|
|
||||||
border_width: global_state.border_width,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut monitors = vec![];
|
|
||||||
for m in state.monitors.elements() {
|
|
||||||
monitors.push(MonitorConfig::from(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut workspace_names = HashMap::new();
|
|
||||||
|
|
||||||
for (monitor_idx, m) in monitors.iter().enumerate() {
|
|
||||||
for ws in &m.workspaces {
|
|
||||||
let names = workspace_names.entry(monitor_idx).or_insert_with(Vec::new);
|
|
||||||
names.push(ws.name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stackbar_config = StackbarConfig {
|
|
||||||
mode: global_state.stackbar_mode,
|
|
||||||
height: global_state.stackbar_height,
|
|
||||||
width: global_state.stackbar_tab_width,
|
|
||||||
label: global_state.stackbar_label,
|
|
||||||
focused_text_colour: colour32(Some(global_state.stackbar_focused_text_colour)),
|
|
||||||
unfocused_text_colour: colour32(Some(global_state.stackbar_unfocused_text_colour)),
|
|
||||||
background_colour: colour32(Some(global_state.stackbar_tab_background_colour)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut debug_windows = vec![];
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
EnumWindows(
|
|
||||||
Some(enum_window),
|
|
||||||
windows::Win32::Foundation::LPARAM(&mut debug_windows as *mut Vec<Window> as isize),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
border_config,
|
|
||||||
mouse_follows_focus: state.mouse_follows_focus,
|
|
||||||
monitors,
|
|
||||||
workspace_names,
|
|
||||||
debug_hwnd: 0,
|
|
||||||
debug_windows,
|
|
||||||
stackbar_config,
|
|
||||||
debug_rule: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "system" fn enum_window(
|
|
||||||
hwnd: windows::Win32::Foundation::HWND,
|
|
||||||
lparam: windows::Win32::Foundation::LPARAM,
|
|
||||||
) -> windows::Win32::Foundation::BOOL {
|
|
||||||
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
|
|
||||||
let window = Window { hwnd: hwnd.0 };
|
|
||||||
|
|
||||||
if window.is_window()
|
|
||||||
&& !window.is_miminized()
|
|
||||||
&& window.is_visible()
|
|
||||||
&& window.title().is_ok()
|
|
||||||
&& window.exe().is_ok()
|
|
||||||
{
|
|
||||||
windows.push(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_view_ui(ui: &mut egui::Ui, code: &str) {
|
|
||||||
let language = "json";
|
|
||||||
let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
|
|
||||||
egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for KomorebiGui {
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
ctx.set_pixels_per_point(2.0);
|
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
ui.set_width(ctx.screen_rect().width());
|
|
||||||
ui.collapsing("Debugging", |ui| {
|
|
||||||
ui.collapsing("Window Rules", |ui| {
|
|
||||||
let window = Window {
|
|
||||||
hwnd: self.debug_hwnd,
|
|
||||||
};
|
|
||||||
|
|
||||||
let label = if let (Ok(title), Ok(exe)) = (window.title(), window.exe()) {
|
|
||||||
format!("{title} ({exe})")
|
|
||||||
} else {
|
|
||||||
String::from("Select a Window")
|
|
||||||
};
|
|
||||||
|
|
||||||
if ui.button("Refresh Windows").clicked() {
|
|
||||||
let mut debug_windows = vec![];
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
EnumWindows(
|
|
||||||
Some(enum_window),
|
|
||||||
windows::Win32::Foundation::LPARAM(
|
|
||||||
&mut debug_windows as *mut Vec<Window> as isize,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.debug_windows = debug_windows;
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::ComboBox::from_label("Select a Window")
|
|
||||||
.selected_text(label)
|
|
||||||
.show_ui(ui, |ui| {
|
|
||||||
for w in &self.debug_windows {
|
|
||||||
if ui
|
|
||||||
.selectable_value(
|
|
||||||
&mut self.debug_hwnd,
|
|
||||||
w.hwnd,
|
|
||||||
format!(
|
|
||||||
"{} ({})",
|
|
||||||
w.title().unwrap(),
|
|
||||||
w.exe().unwrap()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
let debug_rule: RuleDebug = serde_json::from_str(
|
|
||||||
&komorebi_client::send_query(
|
|
||||||
&SocketMessage::DebugWindow(self.debug_hwnd),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.debug_rule = Some(debug_rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(debug_rule) = &self.debug_rule {
|
|
||||||
json_view_ui(ui, &serde_json::to_string_pretty(debug_rule).unwrap())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Mouse", |ui| {
|
|
||||||
if ui
|
|
||||||
.toggle_value(&mut self.mouse_follows_focus, "Mouse Follows Focus")
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
|
||||||
self.mouse_follows_focus,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Border", |ui| {
|
|
||||||
if ui
|
|
||||||
.toggle_value(&mut self.border_config.border_enabled, "Border")
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(&SocketMessage::Border(
|
|
||||||
self.border_config.border_enabled,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.collapsing("Colours", |ui| {
|
|
||||||
ui.collapsing("Single", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.border_config.border_colours.single,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
|
||||||
WindowKind::Single,
|
|
||||||
self.border_config.border_colours.single.r() as u32,
|
|
||||||
self.border_config.border_colours.single.g() as u32,
|
|
||||||
self.border_config.border_colours.single.b() as u32,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Stack", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.border_config.border_colours.stack,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
|
||||||
WindowKind::Stack,
|
|
||||||
self.border_config.border_colours.stack.r() as u32,
|
|
||||||
self.border_config.border_colours.stack.g() as u32,
|
|
||||||
self.border_config.border_colours.stack.b() as u32,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Monocle", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.border_config.border_colours.monocle,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
|
||||||
WindowKind::Monocle,
|
|
||||||
self.border_config.border_colours.monocle.r() as u32,
|
|
||||||
self.border_config.border_colours.monocle.g() as u32,
|
|
||||||
self.border_config.border_colours.monocle.b() as u32,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Unfocused", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.border_config.border_colours.unfocused,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
|
||||||
WindowKind::Unfocused,
|
|
||||||
self.border_config.border_colours.unfocused.r() as u32,
|
|
||||||
self.border_config.border_colours.unfocused.g() as u32,
|
|
||||||
self.border_config.border_colours.unfocused.b() as u32,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Style", |ui| {
|
|
||||||
for option in [
|
|
||||||
BorderStyle::System,
|
|
||||||
BorderStyle::Rounded,
|
|
||||||
BorderStyle::Square,
|
|
||||||
] {
|
|
||||||
if ui
|
|
||||||
.add(egui::SelectableLabel::new(
|
|
||||||
self.border_config.border_style == option,
|
|
||||||
option.to_string(),
|
|
||||||
))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
self.border_config.border_style = option;
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderStyle(
|
|
||||||
self.border_config.border_style,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
|
||||||
|
|
||||||
komorebi_client::send_message(&SocketMessage::Retile).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Width", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(egui::Slider::new(
|
|
||||||
&mut self.border_config.border_width,
|
|
||||||
-50..=50,
|
|
||||||
))
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderWidth(
|
|
||||||
self.border_config.border_width,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Offset", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(egui::Slider::new(
|
|
||||||
&mut self.border_config.border_offset,
|
|
||||||
-50..=50,
|
|
||||||
))
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(&SocketMessage::BorderOffset(
|
|
||||||
self.border_config.border_offset,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Stackbar", |ui| {
|
|
||||||
for option in [
|
|
||||||
StackbarMode::Never,
|
|
||||||
StackbarMode::OnStack,
|
|
||||||
StackbarMode::Always,
|
|
||||||
] {
|
|
||||||
if ui
|
|
||||||
.add(egui::SelectableLabel::new(
|
|
||||||
self.stackbar_config.mode == option,
|
|
||||||
option.to_string(),
|
|
||||||
))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
self.stackbar_config.mode = option;
|
|
||||||
komorebi_client::send_message(&SocketMessage::StackbarMode(
|
|
||||||
self.stackbar_config.mode,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
komorebi_client::send_message(&SocketMessage::Retile).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.collapsing("Label", |ui| {
|
|
||||||
for option in [StackbarLabel::Process, StackbarLabel::Title] {
|
|
||||||
if ui
|
|
||||||
.add(egui::SelectableLabel::new(
|
|
||||||
self.stackbar_config.label == option,
|
|
||||||
option.to_string(),
|
|
||||||
))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
self.stackbar_config.label = option;
|
|
||||||
komorebi_client::send_message(&SocketMessage::StackbarLabel(
|
|
||||||
self.stackbar_config.label,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Colours", |ui| {
|
|
||||||
ui.collapsing("Focused Text", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.stackbar_config.focused_text_colour,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::StackbarFocusedTextColour(
|
|
||||||
self.stackbar_config.focused_text_colour.r() as u32,
|
|
||||||
self.stackbar_config.focused_text_colour.g() as u32,
|
|
||||||
self.stackbar_config.focused_text_colour.b() as u32,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Unfocused Text", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.stackbar_config.unfocused_text_colour,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::StackbarUnfocusedTextColour(
|
|
||||||
self.stackbar_config.unfocused_text_colour.r() as u32,
|
|
||||||
self.stackbar_config.unfocused_text_colour.g() as u32,
|
|
||||||
self.stackbar_config.unfocused_text_colour.b() as u32,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Background", |ui| {
|
|
||||||
if egui::color_picker::color_picker_color32(
|
|
||||||
ui,
|
|
||||||
&mut self.stackbar_config.background_colour,
|
|
||||||
Alpha::Opaque,
|
|
||||||
) {
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::StackbarBackgroundColour(
|
|
||||||
self.stackbar_config.background_colour.r() as u32,
|
|
||||||
self.stackbar_config.background_colour.g() as u32,
|
|
||||||
self.stackbar_config.background_colour.b() as u32,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Width", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(egui::Slider::new(&mut self.stackbar_config.width, 0..=500))
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(&SocketMessage::StackbarTabWidth(
|
|
||||||
self.stackbar_config.width,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
komorebi_client::send_message(&SocketMessage::Retile).unwrap()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Height", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(egui::Slider::new(&mut self.stackbar_config.height, 0..=100))
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(&SocketMessage::StackbarHeight(
|
|
||||||
self.stackbar_config.height,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
komorebi_client::send_message(&SocketMessage::Retile).unwrap()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
for (monitor_idx, monitor) in self.monitors.iter_mut().enumerate() {
|
|
||||||
ui.collapsing(
|
|
||||||
format!(
|
|
||||||
"Monitor {monitor_idx} ({}x{})",
|
|
||||||
monitor.size.right, monitor.size.bottom
|
|
||||||
),
|
|
||||||
|ui| {
|
|
||||||
ui.collapsing("Work Area Offset", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(
|
|
||||||
egui::Slider::new(
|
|
||||||
&mut monitor.work_area_offset.left,
|
|
||||||
0..=500,
|
|
||||||
)
|
|
||||||
.text("Left"),
|
|
||||||
)
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::MonitorWorkAreaOffset(
|
|
||||||
monitor_idx,
|
|
||||||
monitor.work_area_offset,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
if ui
|
|
||||||
.add(
|
|
||||||
egui::Slider::new(
|
|
||||||
&mut monitor.work_area_offset.top,
|
|
||||||
0..=500,
|
|
||||||
)
|
|
||||||
.text("Top"),
|
|
||||||
)
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::MonitorWorkAreaOffset(
|
|
||||||
monitor_idx,
|
|
||||||
monitor.work_area_offset,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
if ui
|
|
||||||
.add(
|
|
||||||
egui::Slider::new(
|
|
||||||
&mut monitor.work_area_offset.right,
|
|
||||||
0..=500,
|
|
||||||
)
|
|
||||||
.text("Right"),
|
|
||||||
)
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::MonitorWorkAreaOffset(
|
|
||||||
monitor_idx,
|
|
||||||
monitor.work_area_offset,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
if ui
|
|
||||||
.add(
|
|
||||||
egui::Slider::new(
|
|
||||||
&mut monitor.work_area_offset.bottom,
|
|
||||||
0..=500,
|
|
||||||
)
|
|
||||||
.text("Bottom"),
|
|
||||||
)
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::MonitorWorkAreaOffset(
|
|
||||||
monitor_idx,
|
|
||||||
monitor.work_area_offset,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Workspaces", |ui| {
|
|
||||||
for (workspace_idx, workspace) in
|
|
||||||
monitor.workspaces.iter_mut().enumerate()
|
|
||||||
{
|
|
||||||
ui.collapsing(
|
|
||||||
format!("Workspace {workspace_idx} ({})", workspace.name),
|
|
||||||
|ui| {
|
|
||||||
if ui.button("Focus").clicked() {
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::MouseFollowsFocus(false),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::FocusMonitorWorkspaceNumber(
|
|
||||||
monitor_idx,
|
|
||||||
workspace_idx,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::MouseFollowsFocus(
|
|
||||||
self.mouse_follows_focus,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui
|
|
||||||
.toggle_value(&mut workspace.tile, "Tiling")
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::WorkspaceTiling(
|
|
||||||
monitor_idx,
|
|
||||||
workspace_idx,
|
|
||||||
workspace.tile,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.collapsing("Name", |ui| {
|
|
||||||
let monitor_workspaces = self
|
|
||||||
.workspace_names
|
|
||||||
.get_mut(&monitor_idx)
|
|
||||||
.unwrap();
|
|
||||||
let workspace_name =
|
|
||||||
&mut monitor_workspaces[workspace_idx];
|
|
||||||
if ui
|
|
||||||
.text_edit_singleline(workspace_name)
|
|
||||||
.lost_focus()
|
|
||||||
{
|
|
||||||
workspace.name = workspace_name.clone();
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::WorkspaceName(
|
|
||||||
monitor_idx,
|
|
||||||
workspace_idx,
|
|
||||||
workspace.name.clone(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Layout", |ui| {
|
|
||||||
for option in [
|
|
||||||
DefaultLayout::BSP,
|
|
||||||
DefaultLayout::Columns,
|
|
||||||
DefaultLayout::Rows,
|
|
||||||
DefaultLayout::VerticalStack,
|
|
||||||
DefaultLayout::HorizontalStack,
|
|
||||||
DefaultLayout::UltrawideVerticalStack,
|
|
||||||
DefaultLayout::Grid,
|
|
||||||
] {
|
|
||||||
if ui
|
|
||||||
.add(egui::SelectableLabel::new(
|
|
||||||
workspace.layout == option,
|
|
||||||
option.to_string(),
|
|
||||||
))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
workspace.layout = option;
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::WorkspaceLayout(
|
|
||||||
monitor_idx,
|
|
||||||
workspace_idx,
|
|
||||||
workspace.layout,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Container Padding", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(egui::Slider::new(
|
|
||||||
&mut workspace.container_padding,
|
|
||||||
0..=100,
|
|
||||||
))
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::ContainerPadding(
|
|
||||||
monitor_idx,
|
|
||||||
workspace_idx,
|
|
||||||
workspace.container_padding,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.collapsing("Workspace Padding", |ui| {
|
|
||||||
if ui
|
|
||||||
.add(egui::Slider::new(
|
|
||||||
&mut workspace.workspace_padding,
|
|
||||||
0..=100,
|
|
||||||
))
|
|
||||||
.drag_stopped()
|
|
||||||
{
|
|
||||||
komorebi_client::send_message(
|
|
||||||
&SocketMessage::WorkspacePadding(
|
|
||||||
monitor_idx,
|
|
||||||
workspace_idx,
|
|
||||||
workspace.workspace_padding,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi"
|
name = "komorebi"
|
||||||
version = "0.1.27-dev.0"
|
version = "0.1.23-dev.0"
|
||||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||||
description = "A tiling window manager for Windows"
|
description = "A tiling window manager for Windows"
|
||||||
categories = ["tiling-window-manager", "windows"]
|
categories = ["tiling-window-manager", "windows"]
|
||||||
@@ -13,7 +13,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
komorebi-core = { path = "../komorebi-core" }
|
komorebi-core = { path = "../komorebi-core" }
|
||||||
|
|
||||||
bitflags = { version = "2", features = ["serde"] }
|
bitflags = "2"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
color-eyre = { workspace = true }
|
color-eyre = { workspace = true }
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
@@ -24,7 +24,7 @@ getset = "0.1"
|
|||||||
hex_color = { version = "3", features = ["serde"] }
|
hex_color = { version = "3", features = ["serde"] }
|
||||||
hotwatch = "0.5"
|
hotwatch = "0.5"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
miow = "0.6"
|
miow = "0.5"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
net2 = "0.2"
|
net2 = "0.2"
|
||||||
os_info = "3.8"
|
os_info = "3.8"
|
||||||
@@ -35,7 +35,7 @@ schemars = "0.8"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
sysinfo = { workspace = true }
|
sysinfo = "0.30"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-appender = "0.2"
|
tracing-appender = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
@@ -48,7 +48,5 @@ windows-interface = { workspace = true }
|
|||||||
winput = "0.2"
|
winput = "0.2"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
|
||||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
deadlock_detection = []
|
deadlock_detection = []
|
||||||
|
|||||||
108
komorebi/src/border.rs
Normal file
108
komorebi/src/border.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use color_eyre::Result;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::FindWindowW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||||
|
|
||||||
|
use crate::window::Window;
|
||||||
|
use crate::windows_callbacks;
|
||||||
|
use crate::WindowsApi;
|
||||||
|
use crate::BORDER_HWND;
|
||||||
|
use crate::BORDER_OFFSET;
|
||||||
|
use crate::BORDER_RECT;
|
||||||
|
use crate::BORDER_WIDTH;
|
||||||
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Border {
|
||||||
|
pub(crate) hwnd: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<isize> for Border {
|
||||||
|
fn from(hwnd: isize) -> Self {
|
||||||
|
Self { hwnd }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Border {
|
||||||
|
pub const fn hwnd(self) -> HWND {
|
||||||
|
HWND(self.hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(name: &str) -> Result<()> {
|
||||||
|
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
|
||||||
|
let instance = WindowsApi::module_handle_w()?;
|
||||||
|
let class_name = PCWSTR(name.as_ptr());
|
||||||
|
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
|
||||||
|
let window_class = WNDCLASSW {
|
||||||
|
hInstance: instance.into(),
|
||||||
|
lpszClassName: class_name,
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
|
lpfnWndProc: Some(windows_callbacks::border_window),
|
||||||
|
hbrBackground: brush,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let _atom = WindowsApi::register_class_w(&window_class)?;
|
||||||
|
|
||||||
|
let name_cl = name.clone();
|
||||||
|
std::thread::spawn(move || -> Result<()> {
|
||||||
|
let hwnd = WindowsApi::create_border_window(PCWSTR(name_cl.as_ptr()), instance)?;
|
||||||
|
let border = Self::from(hwnd);
|
||||||
|
|
||||||
|
let mut message = MSG::default();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
while GetMessageW(&mut message, border.hwnd(), 0, 0).into() {
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut hwnd = HWND(0);
|
||||||
|
while hwnd == HWND(0) {
|
||||||
|
hwnd = unsafe { FindWindowW(PCWSTR(name.as_ptr()), PCWSTR::null()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
BORDER_HWND.store(hwnd.0, Ordering::SeqCst);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(self) -> Result<()> {
|
||||||
|
if self.hwnd == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
WindowsApi::hide_border_window(self.hwnd())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_position(self, window: Window, activate: bool) -> Result<()> {
|
||||||
|
if self.hwnd == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
if !WindowsApi::is_window(self.hwnd()) {
|
||||||
|
Self::create("komorebi-border-window")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rect = WindowsApi::window_rect(window.hwnd())?;
|
||||||
|
rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
|
||||||
|
rect.add_margin(border_width);
|
||||||
|
|
||||||
|
*BORDER_RECT.lock() = rect;
|
||||||
|
|
||||||
|
WindowsApi::position_border_window(self.hwnd(), &rect, activate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
use crate::border_manager::WindowKind;
|
|
||||||
use crate::border_manager::BORDER_OFFSET;
|
|
||||||
use crate::border_manager::BORDER_WIDTH;
|
|
||||||
use crate::border_manager::FOCUSED;
|
|
||||||
use crate::border_manager::FOCUS_STATE;
|
|
||||||
use crate::border_manager::MONOCLE;
|
|
||||||
use crate::border_manager::RECT_STATE;
|
|
||||||
use crate::border_manager::STACK;
|
|
||||||
use crate::border_manager::STYLE;
|
|
||||||
use crate::border_manager::UNFOCUSED;
|
|
||||||
use crate::border_manager::Z_ORDER;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
use crate::WINDOWS_11;
|
|
||||||
|
|
||||||
use komorebi_core::BorderStyle;
|
|
||||||
use komorebi_core::Rect;
|
|
||||||
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use windows::core::PCWSTR;
|
|
||||||
use windows::Win32::Foundation::BOOL;
|
|
||||||
use windows::Win32::Foundation::COLORREF;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
use windows::Win32::Foundation::LPARAM;
|
|
||||||
use windows::Win32::Foundation::LRESULT;
|
|
||||||
use windows::Win32::Foundation::WPARAM;
|
|
||||||
use windows::Win32::Graphics::Gdi::BeginPaint;
|
|
||||||
use windows::Win32::Graphics::Gdi::CreatePen;
|
|
||||||
use windows::Win32::Graphics::Gdi::EndPaint;
|
|
||||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
|
||||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
|
||||||
use windows::Win32::Graphics::Gdi::RoundRect;
|
|
||||||
use windows::Win32::Graphics::Gdi::SelectObject;
|
|
||||||
use windows::Win32::Graphics::Gdi::ValidateRect;
|
|
||||||
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
|
|
||||||
use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME;
|
|
||||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
|
||||||
|
|
||||||
pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|
||||||
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
|
||||||
|
|
||||||
if let Ok(class) = WindowsApi::real_window_class_w(hwnd) {
|
|
||||||
if class.starts_with("komoborder") {
|
|
||||||
hwnds.push(hwnd.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Border {
|
|
||||||
pub hwnd: isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<isize> for Border {
|
|
||||||
fn from(value: isize) -> Self {
|
|
||||||
Self { hwnd: value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Border {
|
|
||||||
pub const fn hwnd(&self) -> HWND {
|
|
||||||
HWND(self.hwnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create(id: &str) -> color_eyre::Result<Self> {
|
|
||||||
let name: Vec<u16> = format!("komoborder-{id}\0").encode_utf16().collect();
|
|
||||||
let class_name = PCWSTR(name.as_ptr());
|
|
||||||
|
|
||||||
let h_module = WindowsApi::module_handle_w()?;
|
|
||||||
|
|
||||||
let window_class = WNDCLASSW {
|
|
||||||
hInstance: h_module.into(),
|
|
||||||
lpszClassName: class_name,
|
|
||||||
style: CS_HREDRAW | CS_VREDRAW,
|
|
||||||
lpfnWndProc: Some(Self::callback),
|
|
||||||
hbrBackground: WindowsApi::create_solid_brush(0),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = WindowsApi::register_class_w(&window_class);
|
|
||||||
|
|
||||||
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
|
||||||
let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), h_module)?;
|
|
||||||
hwnd_sender.send(hwnd)?;
|
|
||||||
|
|
||||||
let mut message = MSG::default();
|
|
||||||
unsafe {
|
|
||||||
while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() {
|
|
||||||
TranslateMessage(&message);
|
|
||||||
DispatchMessageW(&message);
|
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
hwnd: hwnd_receiver.recv()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(&self) -> color_eyre::Result<()> {
|
|
||||||
WindowsApi::close_window(self.hwnd())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> {
|
|
||||||
// Make adjustments to the border
|
|
||||||
let mut rect = *rect;
|
|
||||||
rect.add_margin(BORDER_WIDTH.load(Ordering::SeqCst));
|
|
||||||
rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst));
|
|
||||||
|
|
||||||
// Store the border rect so that it can be used by the callback
|
|
||||||
{
|
|
||||||
let mut rects = RECT_STATE.lock();
|
|
||||||
rects.insert(self.hwnd, rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the position of the border if required
|
|
||||||
if !WindowsApi::window_rect(self.hwnd())?.eq(&rect) {
|
|
||||||
WindowsApi::set_border_pos(self.hwnd(), &rect, HWND((*Z_ORDER.lock()).into()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidate the rect to trigger the callback to update colours etc.
|
|
||||||
self.invalidate();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invalidate(&self) {
|
|
||||||
let _ = unsafe { InvalidateRect(self.hwnd(), None, false) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "system" fn callback(
|
|
||||||
window: HWND,
|
|
||||||
message: u32,
|
|
||||||
wparam: WPARAM,
|
|
||||||
lparam: LPARAM,
|
|
||||||
) -> LRESULT {
|
|
||||||
unsafe {
|
|
||||||
match message {
|
|
||||||
WM_PAINT => {
|
|
||||||
let rects = RECT_STATE.lock();
|
|
||||||
|
|
||||||
// With the rect that we stored in Self::update
|
|
||||||
if let Some(rect) = rects.get(&window.0).copied() {
|
|
||||||
// Grab the focus kind for this border
|
|
||||||
let focus_kind = {
|
|
||||||
FOCUS_STATE
|
|
||||||
.lock()
|
|
||||||
.get(&window.0)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(WindowKind::Unfocused)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set up the brush to draw the border
|
|
||||||
let mut ps = PAINTSTRUCT::default();
|
|
||||||
let hdc = BeginPaint(window, &mut ps);
|
|
||||||
let hpen = CreatePen(
|
|
||||||
PS_SOLID | PS_INSIDEFRAME,
|
|
||||||
BORDER_WIDTH.load(Ordering::SeqCst),
|
|
||||||
COLORREF(match focus_kind {
|
|
||||||
WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
|
|
||||||
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
|
|
||||||
WindowKind::Stack => STACK.load(Ordering::SeqCst),
|
|
||||||
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let hbrush = WindowsApi::create_solid_brush(0);
|
|
||||||
|
|
||||||
// Draw the border
|
|
||||||
SelectObject(hdc, hpen);
|
|
||||||
SelectObject(hdc, hbrush);
|
|
||||||
// TODO(raggi): this is approximately the correct curvature for
|
|
||||||
// the top left of a Windows 11 window (DWMWCP_DEFAULT), but
|
|
||||||
// often the bottom right has a different shape. Furthermore if
|
|
||||||
// the window was made with DWMWCP_ROUNDSMALL then this is the
|
|
||||||
// wrong size. In the future we should read the DWM properties
|
|
||||||
// of windows and attempt to match appropriately.
|
|
||||||
match *STYLE.lock() {
|
|
||||||
BorderStyle::System => {
|
|
||||||
if *WINDOWS_11 {
|
|
||||||
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
|
|
||||||
} else {
|
|
||||||
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BorderStyle::Rounded => {
|
|
||||||
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
|
|
||||||
}
|
|
||||||
BorderStyle::Square => {
|
|
||||||
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EndPaint(window, &ps);
|
|
||||||
ValidateRect(window, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
WM_DESTROY => {
|
|
||||||
PostQuitMessage(0);
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
_ => DefWindowProcW(window, message, wparam, lparam),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
||||||
|
|
||||||
mod border;
|
|
||||||
|
|
||||||
use crossbeam_channel::Receiver;
|
|
||||||
use crossbeam_channel::Sender;
|
|
||||||
use crossbeam_utils::atomic::AtomicConsume;
|
|
||||||
use komorebi_core::BorderStyle;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::atomic::AtomicI32;
|
|
||||||
use std::sync::atomic::AtomicU32;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::time::Instant;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
|
|
||||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
|
||||||
use crate::Colour;
|
|
||||||
use crate::Rect;
|
|
||||||
use crate::Rgb;
|
|
||||||
use crate::WindowManager;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
use border::border_hwnds;
|
|
||||||
use border::Border;
|
|
||||||
use komorebi_core::WindowKind;
|
|
||||||
|
|
||||||
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
|
|
||||||
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
|
|
||||||
|
|
||||||
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true);
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref Z_ORDER: Arc<Mutex<ZOrder>> = Arc::new(Mutex::new(ZOrder::Bottom));
|
|
||||||
pub static ref STYLE: Arc<Mutex<BorderStyle>> = Arc::new(Mutex::new(BorderStyle::System));
|
|
||||||
pub static ref FOCUSED: AtomicU32 =
|
|
||||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
|
|
||||||
pub static ref UNFOCUSED: AtomicU32 =
|
|
||||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(128, 128, 128))));
|
|
||||||
pub static ref MONOCLE: AtomicU32 =
|
|
||||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153))));
|
|
||||||
pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66))));
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref BORDERS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
|
|
||||||
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
|
|
||||||
static ref RECT_STATE: Mutex<HashMap<isize, Rect>> = Mutex::new(HashMap::new());
|
|
||||||
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Notification;
|
|
||||||
|
|
||||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
|
||||||
CHANNEL.get_or_init(crossbeam_channel::unbounded)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_tx() -> Sender<Notification> {
|
|
||||||
channel().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_rx() -> Receiver<Notification> {
|
|
||||||
channel().1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
|
||||||
let mut borders = BORDER_STATE.lock();
|
|
||||||
tracing::info!(
|
|
||||||
"purging known borders: {:?}",
|
|
||||||
borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
|
|
||||||
for (_, border) in borders.iter() {
|
|
||||||
border.destroy()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
borders.clear();
|
|
||||||
RECT_STATE.lock().clear();
|
|
||||||
BORDERS_MONITORS.lock().clear();
|
|
||||||
FOCUS_STATE.lock().clear();
|
|
||||||
|
|
||||||
let mut remaining_hwnds = vec![];
|
|
||||||
|
|
||||||
WindowsApi::enum_windows(
|
|
||||||
Some(border_hwnds),
|
|
||||||
&mut remaining_hwnds as *mut Vec<isize> as isize,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if !remaining_hwnds.is_empty() {
|
|
||||||
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
|
|
||||||
|
|
||||||
for hwnd in remaining_hwnds {
|
|
||||||
Border::from(hwnd).destroy()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
|
||||||
std::thread::spawn(move || loop {
|
|
||||||
match handle_notifications(wm.clone()) {
|
|
||||||
Ok(()) => {
|
|
||||||
tracing::warn!("restarting finished thread");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
tracing::warn!("restarting failed thread: {}", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
|
||||||
tracing::info!("listening");
|
|
||||||
|
|
||||||
let receiver = event_rx();
|
|
||||||
let mut instant: Option<Instant> = None;
|
|
||||||
event_tx().send(Notification)?;
|
|
||||||
|
|
||||||
'receiver: for _ in receiver {
|
|
||||||
if let Some(instant) = instant {
|
|
||||||
if instant.elapsed().lt(&Duration::from_millis(50)) {
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
instant = Some(Instant::now());
|
|
||||||
|
|
||||||
let mut borders = BORDER_STATE.lock();
|
|
||||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
|
||||||
|
|
||||||
// Check the wm state every time we receive a notification
|
|
||||||
let state = wm.lock();
|
|
||||||
|
|
||||||
// If borders are disabled
|
|
||||||
if !BORDER_ENABLED.load_consume()
|
|
||||||
// Or if the wm is paused
|
|
||||||
|| state.is_paused
|
|
||||||
// Or if we are handling an alt-tab across workspaces
|
|
||||||
|| ALT_TAB_HWND.load().is_some()
|
|
||||||
{
|
|
||||||
// Destroy the borders we know about
|
|
||||||
for (_, border) in borders.iter() {
|
|
||||||
border.destroy()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
borders.clear();
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
let focused_monitor_idx = state.focused_monitor_idx();
|
|
||||||
|
|
||||||
'monitors: for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
|
|
||||||
// Only operate on the focused workspace of each monitor
|
|
||||||
if let Some(ws) = m.focused_workspace() {
|
|
||||||
// Workspaces with tiling disabled don't have borders
|
|
||||||
if !ws.tile() {
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, border) in borders.iter() {
|
|
||||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
|
||||||
border.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
borders.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the monocle container separately
|
|
||||||
if let Some(monocle) = ws.monocle_container() {
|
|
||||||
let border = match borders.entry(monocle.id().clone()) {
|
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
if let Ok(border) = Border::create(monocle.id()) {
|
|
||||||
entry.insert(border)
|
|
||||||
} else {
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
borders_monitors.insert(monocle.id().clone(), monitor_idx);
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut focus_state = FOCUS_STATE.lock();
|
|
||||||
focus_state.insert(
|
|
||||||
border.hwnd,
|
|
||||||
if monitor_idx != focused_monitor_idx {
|
|
||||||
WindowKind::Unfocused
|
|
||||||
} else {
|
|
||||||
WindowKind::Monocle
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rect = WindowsApi::window_rect(
|
|
||||||
monocle.focused_window().copied().unwrap_or_default().hwnd(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
border.update(&rect)?;
|
|
||||||
|
|
||||||
let border_hwnd = border.hwnd;
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, b) in borders.iter() {
|
|
||||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
|
||||||
&& border_hwnd != b.hwnd
|
|
||||||
{
|
|
||||||
b.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
borders.remove(id);
|
|
||||||
}
|
|
||||||
continue 'monitors;
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
|
||||||
WindowsApi::foreground_window().unwrap_or_default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
if is_maximized {
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, border) in borders.iter() {
|
|
||||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
|
||||||
border.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
borders.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue 'monitors;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy any borders not associated with the focused workspace
|
|
||||||
let container_ids = ws
|
|
||||||
.containers()
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.id().clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, border) in borders.iter() {
|
|
||||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
|
||||||
&& !container_ids.contains(id)
|
|
||||||
{
|
|
||||||
border.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
borders.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (idx, c) in ws.containers().iter().enumerate() {
|
|
||||||
// Update border when moving or resizing with mouse
|
|
||||||
if state.pending_move_op.is_some() && idx == ws.focused_container_idx() {
|
|
||||||
let restore_z_order = *Z_ORDER.lock();
|
|
||||||
*Z_ORDER.lock() = ZOrder::TopMost;
|
|
||||||
|
|
||||||
let mut rect = WindowsApi::window_rect(
|
|
||||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
while WindowsApi::lbutton_is_pressed() {
|
|
||||||
let border = match borders.entry(c.id().clone()) {
|
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
if let Ok(border) = Border::create(c.id()) {
|
|
||||||
entry.insert(border)
|
|
||||||
} else {
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_rect = WindowsApi::window_rect(
|
|
||||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if rect != new_rect {
|
|
||||||
rect = new_rect;
|
|
||||||
border.update(&rect)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*Z_ORDER.lock() = restore_z_order;
|
|
||||||
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the border entry for this container from the map or create one
|
|
||||||
let border = match borders.entry(c.id().clone()) {
|
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
if let Ok(border) = Border::create(c.id()) {
|
|
||||||
entry.insert(border)
|
|
||||||
} else {
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
borders_monitors.insert(c.id().clone(), monitor_idx);
|
|
||||||
|
|
||||||
// Update the focused state for all containers on this workspace
|
|
||||||
{
|
|
||||||
let mut focus_state = FOCUS_STATE.lock();
|
|
||||||
focus_state.insert(
|
|
||||||
border.hwnd,
|
|
||||||
if idx != ws.focused_container_idx()
|
|
||||||
|| monitor_idx != focused_monitor_idx
|
|
||||||
{
|
|
||||||
WindowKind::Unfocused
|
|
||||||
} else if c.windows().len() > 1 {
|
|
||||||
WindowKind::Stack
|
|
||||||
} else {
|
|
||||||
WindowKind::Single
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rect = WindowsApi::window_rect(
|
|
||||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
border.update(&rect)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub enum ZOrder {
|
|
||||||
Top,
|
|
||||||
NoTopMost,
|
|
||||||
Bottom,
|
|
||||||
TopMost,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ZOrder> for isize {
|
|
||||||
fn from(val: ZOrder) -> Self {
|
|
||||||
match val {
|
|
||||||
ZOrder::Top => 0,
|
|
||||||
ZOrder::NoTopMost => -2,
|
|
||||||
ZOrder::Bottom => 1,
|
|
||||||
ZOrder::TopMost => -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -65,7 +65,7 @@ pub struct Rgb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Rgb {
|
impl Rgb {
|
||||||
pub const fn new(r: u32, g: u32, b: u32) -> Self {
|
pub fn new(r: u32, g: u32, b: u32) -> Self {
|
||||||
Self { r, g, b }
|
Self { r, g, b }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ use serde::Deserialize;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
|
use crate::stackbar::Stackbar;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
|
use crate::StackbarMode;
|
||||||
|
use crate::STACKBAR_MODE;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
#[getset(get = "pub")]
|
#[getset(get = "pub")]
|
||||||
id: String,
|
id: String,
|
||||||
windows: Ring<Window>,
|
windows: Ring<Window>,
|
||||||
|
#[getset(get = "pub", get_mut = "pub")]
|
||||||
|
stackbar: Option<Stackbar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_ring_elements!(Container, Window);
|
impl_ring_elements!(Container, Window);
|
||||||
@@ -23,6 +28,10 @@ impl Default for Container {
|
|||||||
Self {
|
Self {
|
||||||
id: nanoid!(),
|
id: nanoid!(),
|
||||||
windows: Ring::default(),
|
windows: Ring::default(),
|
||||||
|
stackbar: match *STACKBAR_MODE.lock() {
|
||||||
|
StackbarMode::Always => Stackbar::create().ok(),
|
||||||
|
StackbarMode::Never | StackbarMode::OnStack => None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,6 +44,10 @@ impl PartialEq for Container {
|
|||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn hide(&self, omit: Option<isize>) {
|
pub fn hide(&self, omit: Option<isize>) {
|
||||||
|
if let Some(stackbar) = self.stackbar() {
|
||||||
|
stackbar.hide();
|
||||||
|
}
|
||||||
|
|
||||||
for window in self.windows().iter().rev() {
|
for window in self.windows().iter().rev() {
|
||||||
let mut should_hide = omit.is_none();
|
let mut should_hide = omit.is_none();
|
||||||
|
|
||||||
@@ -53,6 +66,10 @@ impl Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&self) {
|
pub fn restore(&self) {
|
||||||
|
if let Some(stackbar) = self.stackbar() {
|
||||||
|
stackbar.restore();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(window) = self.focused_window() {
|
if let Some(window) = self.focused_window() {
|
||||||
window.restore();
|
window.restore();
|
||||||
}
|
}
|
||||||
@@ -105,6 +122,10 @@ impl Container {
|
|||||||
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
|
pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {
|
||||||
let window = self.windows_mut().remove(idx);
|
let window = self.windows_mut().remove(idx);
|
||||||
|
|
||||||
|
if matches!(*STACKBAR_MODE.lock(), StackbarMode::OnStack) && self.windows().len() <= 1 {
|
||||||
|
self.stackbar = None;
|
||||||
|
}
|
||||||
|
|
||||||
if idx != 0 {
|
if idx != 0 {
|
||||||
self.focus_window(idx - 1);
|
self.focus_window(idx - 1);
|
||||||
};
|
};
|
||||||
@@ -119,6 +140,14 @@ impl Container {
|
|||||||
|
|
||||||
pub fn add_window(&mut self, window: Window) {
|
pub fn add_window(&mut self, window: Window) {
|
||||||
self.windows_mut().push_back(window);
|
self.windows_mut().push_back(window);
|
||||||
|
|
||||||
|
if matches!(*STACKBAR_MODE.lock(), StackbarMode::OnStack)
|
||||||
|
&& self.windows().len() > 1
|
||||||
|
&& self.stackbar.is_none()
|
||||||
|
{
|
||||||
|
self.stackbar = Stackbar::create().ok();
|
||||||
|
}
|
||||||
|
|
||||||
self.focus_window(self.windows().len() - 1);
|
self.focus_window(self.windows().len() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
76
komorebi/src/hidden.rs
Normal file
76
komorebi/src/hidden.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use color_eyre::Result;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::FindWindowW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||||
|
|
||||||
|
use crate::windows_callbacks;
|
||||||
|
use crate::WindowsApi;
|
||||||
|
use crate::HIDDEN_HWND;
|
||||||
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Hidden {
|
||||||
|
pub(crate) hwnd: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<isize> for Hidden {
|
||||||
|
fn from(hwnd: isize) -> Self {
|
||||||
|
Self { hwnd }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hidden {
|
||||||
|
pub const fn hwnd(self) -> HWND {
|
||||||
|
HWND(self.hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(name: &str) -> Result<()> {
|
||||||
|
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
|
||||||
|
let instance = WindowsApi::module_handle_w()?;
|
||||||
|
let class_name = PCWSTR(name.as_ptr());
|
||||||
|
let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
|
||||||
|
let window_class = WNDCLASSW {
|
||||||
|
hInstance: instance.into(),
|
||||||
|
lpszClassName: class_name,
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
|
lpfnWndProc: Some(windows_callbacks::hidden_window),
|
||||||
|
hbrBackground: brush,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let _atom = WindowsApi::register_class_w(&window_class)?;
|
||||||
|
|
||||||
|
let name_cl = name.clone();
|
||||||
|
std::thread::spawn(move || -> Result<()> {
|
||||||
|
let hwnd = WindowsApi::create_hidden_window(PCWSTR(name_cl.as_ptr()), instance)?;
|
||||||
|
let hidden = Self::from(hwnd);
|
||||||
|
|
||||||
|
let mut message = MSG::default();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
while GetMessageW(&mut message, hidden.hwnd(), 0, 0).into() {
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut hwnd = HWND(0);
|
||||||
|
while hwnd == HWND(0) {
|
||||||
|
hwnd = unsafe { FindWindowW(PCWSTR(name.as_ptr()), PCWSTR::null()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
HIDDEN_HWND.store(hwnd.0, Ordering::SeqCst);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
pub mod border_manager;
|
pub mod border;
|
||||||
pub mod com;
|
pub mod com;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod ring;
|
pub mod ring;
|
||||||
pub mod colour;
|
pub mod colour;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
|
pub mod hidden;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
pub mod monitor_reconciliator;
|
|
||||||
pub mod process_command;
|
pub mod process_command;
|
||||||
pub mod process_event;
|
pub mod process_event;
|
||||||
pub mod process_movement;
|
pub mod process_movement;
|
||||||
pub mod reaper;
|
|
||||||
pub mod set_window_position;
|
pub mod set_window_position;
|
||||||
pub mod stackbar_manager;
|
pub mod stackbar;
|
||||||
pub mod static_config;
|
pub mod static_config;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
@@ -22,7 +21,6 @@ pub mod windows_callbacks;
|
|||||||
pub mod winevent;
|
pub mod winevent;
|
||||||
pub mod winevent_listener;
|
pub mod winevent_listener;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
pub mod workspace_reconciliator;
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -34,15 +32,16 @@ use std::path::PathBuf;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicI32;
|
use std::sync::atomic::AtomicI32;
|
||||||
|
use std::sync::atomic::AtomicIsize;
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use colour::*;
|
pub use hidden::*;
|
||||||
pub use process_command::*;
|
pub use process_command::*;
|
||||||
pub use process_event::*;
|
pub use process_event::*;
|
||||||
|
pub use stackbar::*;
|
||||||
pub use static_config::*;
|
pub use static_config::*;
|
||||||
pub use window::*;
|
|
||||||
pub use window_manager::*;
|
pub use window_manager::*;
|
||||||
pub use window_manager_event::*;
|
pub use window_manager_event::*;
|
||||||
pub use windows_api::WindowsApi;
|
pub use windows_api::WindowsApi;
|
||||||
@@ -150,6 +149,7 @@ lazy_static! {
|
|||||||
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||||
"Chrome_RenderWidgetHostHWND".to_string(),
|
"Chrome_RenderWidgetHostHWND".to_string(),
|
||||||
]));
|
]));
|
||||||
|
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));
|
||||||
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||||
"X410.exe".to_string(),
|
"X410.exe".to_string(),
|
||||||
"vcxsrv.exe".to_string(),
|
"vcxsrv.exe".to_string(),
|
||||||
@@ -194,10 +194,15 @@ lazy_static! {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static ref BORDER_RECT: Arc<Mutex<Rect>> =
|
||||||
|
Arc::new(Mutex::new(Rect::default()));
|
||||||
|
|
||||||
|
|
||||||
// Use app-specific titlebar removal options where possible
|
// Use app-specific titlebar removal options where possible
|
||||||
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
||||||
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
|
||||||
|
static ref STACKBAR_MODE: Arc<Mutex<StackbarMode >> = Arc::new(Mutex::new(StackbarMode::Never));
|
||||||
static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =
|
static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
@@ -209,9 +214,28 @@ pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
|||||||
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
||||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||||
|
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||||
|
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||||
|
pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0);
|
||||||
|
pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0);
|
||||||
|
pub static BORDER_COLOUR_MONOCLE: AtomicU32 = AtomicU32::new(0);
|
||||||
|
pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0);
|
||||||
|
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
|
||||||
|
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
|
||||||
|
|
||||||
|
// 0 0 0 aka pure black, I doubt anyone will want this as a border colour
|
||||||
|
pub const TRANSPARENCY_COLOUR: u32 = 0;
|
||||||
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||||
|
|
||||||
|
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
|
||||||
|
pub static STACKBAR_UNFOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(11776947); // gray text
|
||||||
|
pub static STACKBAR_TAB_BACKGROUND_COLOUR: AtomicU32 = AtomicU32::new(3355443); // gray
|
||||||
|
pub static STACKBAR_TAB_HEIGHT: AtomicI32 = AtomicI32::new(40);
|
||||||
|
pub static STACKBAR_TAB_WIDTH: AtomicI32 = AtomicI32::new(200);
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
|
|||||||
@@ -23,22 +23,17 @@ use tracing_appender::non_blocking::WorkerGuard;
|
|||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use komorebi::border_manager;
|
use komorebi::hidden::Hidden;
|
||||||
use komorebi::load_configuration;
|
use komorebi::load_configuration;
|
||||||
use komorebi::monitor_reconciliator;
|
|
||||||
use komorebi::process_command::listen_for_commands;
|
use komorebi::process_command::listen_for_commands;
|
||||||
use komorebi::process_command::listen_for_commands_tcp;
|
use komorebi::process_command::listen_for_commands_tcp;
|
||||||
use komorebi::process_event::listen_for_events;
|
use komorebi::process_event::listen_for_events;
|
||||||
use komorebi::process_movement::listen_for_movements;
|
use komorebi::process_movement::listen_for_movements;
|
||||||
use komorebi::reaper;
|
|
||||||
use komorebi::stackbar_manager;
|
|
||||||
use komorebi::static_config::StaticConfig;
|
use komorebi::static_config::StaticConfig;
|
||||||
use komorebi::window_manager::WindowManager;
|
use komorebi::window_manager::WindowManager;
|
||||||
use komorebi::windows_api::WindowsApi;
|
use komorebi::windows_api::WindowsApi;
|
||||||
use komorebi::winevent_listener;
|
use komorebi::winevent_listener;
|
||||||
use komorebi::workspace_reconciliator;
|
|
||||||
use komorebi::CUSTOM_FFM;
|
use komorebi::CUSTOM_FFM;
|
||||||
use komorebi::DATA_DIR;
|
|
||||||
use komorebi::HOME_DIR;
|
use komorebi::HOME_DIR;
|
||||||
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
||||||
use komorebi::SESSION_ID;
|
use komorebi::SESSION_ID;
|
||||||
@@ -54,8 +49,8 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
|||||||
std::env::set_var("RUST_LOG", "info");
|
std::env::set_var("RUST_LOG", "info");
|
||||||
}
|
}
|
||||||
|
|
||||||
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
|
let appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi_plaintext.log");
|
||||||
let color_appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi.log");
|
let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log");
|
||||||
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
||||||
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
|
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
|
||||||
|
|
||||||
@@ -190,6 +185,8 @@ fn main() -> Result<()> {
|
|||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
detect_deadlocks();
|
detect_deadlocks();
|
||||||
|
|
||||||
|
Hidden::create("komorebi-hidden")?;
|
||||||
|
|
||||||
let static_config = opts.config.map_or_else(
|
let static_config = opts.config.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
let komorebi_json = HOME_DIR.join("komorebi.json");
|
let komorebi_json = HOME_DIR.join("komorebi.json");
|
||||||
@@ -202,8 +199,6 @@ fn main() -> Result<()> {
|
|||||||
Option::from,
|
Option::from,
|
||||||
);
|
);
|
||||||
|
|
||||||
std::fs::create_dir_all(&*DATA_DIR)?;
|
|
||||||
|
|
||||||
let wm = if let Some(config) = &static_config {
|
let wm = if let Some(config) = &static_config {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"creating window manager from static configuration file: {}",
|
"creating window manager from static configuration file: {}",
|
||||||
@@ -255,12 +250,6 @@ fn main() -> Result<()> {
|
|||||||
listen_for_movements(wm.clone());
|
listen_for_movements(wm.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
border_manager::listen_for_notifications(wm.clone());
|
|
||||||
stackbar_manager::listen_for_notifications(wm.clone());
|
|
||||||
workspace_reconciliator::listen_for_notifications(wm.clone());
|
|
||||||
monitor_reconciliator::listen_for_notifications(wm.clone())?;
|
|
||||||
reaper::watch_for_orphans(wm.clone());
|
|
||||||
|
|
||||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
ctrlc_sender
|
ctrlc_sender
|
||||||
|
|||||||
@@ -27,19 +27,15 @@ pub struct Monitor {
|
|||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
name: String,
|
name: String,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
device: String,
|
device: Option<String>,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
device_id: String,
|
device_id: Option<String>,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
size: Rect,
|
size: Rect,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
work_area_size: Rect,
|
work_area_size: Rect,
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
work_area_offset: Option<Rect>,
|
work_area_offset: Option<Rect>,
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
|
||||||
window_based_work_area_offset: Option<Rect>,
|
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
|
||||||
window_based_work_area_offset_limit: isize,
|
|
||||||
workspaces: Ring<Workspace>,
|
workspaces: Ring<Workspace>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
@@ -50,27 +46,18 @@ pub struct Monitor {
|
|||||||
|
|
||||||
impl_ring_elements!(Monitor, Workspace);
|
impl_ring_elements!(Monitor, Workspace);
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(id: isize, size: Rect, work_area_size: Rect, name: String) -> Monitor {
|
||||||
id: isize,
|
|
||||||
size: Rect,
|
|
||||||
work_area_size: Rect,
|
|
||||||
name: String,
|
|
||||||
device: String,
|
|
||||||
device_id: String,
|
|
||||||
) -> Monitor {
|
|
||||||
let mut workspaces = Ring::default();
|
let mut workspaces = Ring::default();
|
||||||
workspaces.elements_mut().push_back(Workspace::default());
|
workspaces.elements_mut().push_back(Workspace::default());
|
||||||
|
|
||||||
Monitor {
|
Monitor {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
device,
|
device: None,
|
||||||
device_id,
|
device_id: None,
|
||||||
size,
|
size,
|
||||||
work_area_size,
|
work_area_size,
|
||||||
work_area_offset: None,
|
work_area_offset: None,
|
||||||
window_based_work_area_offset: None,
|
|
||||||
window_based_work_area_offset_limit: 1,
|
|
||||||
workspaces,
|
workspaces,
|
||||||
last_focused_workspace: None,
|
last_focused_workspace: None,
|
||||||
workspace_names: HashMap::default(),
|
workspace_names: HashMap::default(),
|
||||||
@@ -207,11 +194,6 @@ impl Monitor {
|
|||||||
|
|
||||||
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
|
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
|
||||||
let work_area = *self.work_area_size();
|
let work_area = *self.work_area_size();
|
||||||
let window_based_work_area_offset = (
|
|
||||||
self.window_based_work_area_offset_limit(),
|
|
||||||
self.window_based_work_area_offset(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let offset = if self.work_area_offset().is_some() {
|
let offset = if self.work_area_offset().is_some() {
|
||||||
self.work_area_offset()
|
self.work_area_offset()
|
||||||
} else {
|
} else {
|
||||||
@@ -220,7 +202,7 @@ impl Monitor {
|
|||||||
|
|
||||||
self.focused_workspace_mut()
|
self.focused_workspace_mut()
|
||||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||||
.update(&work_area, offset, window_based_work_area_offset)?;
|
.update(&work_area, offset)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
use windows::core::PCWSTR;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
use windows::Win32::Foundation::LPARAM;
|
|
||||||
use windows::Win32::Foundation::LRESULT;
|
|
||||||
use windows::Win32::Foundation::WPARAM;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_POWERBROADCAST;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_WTSSESSION_CHANGE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_LOCK;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_UNLOCK;
|
|
||||||
|
|
||||||
use crate::monitor_reconciliator;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
|
|
||||||
// This is a hidden window specifically spawned to listen to system-wide events related to monitors
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Hidden {
|
|
||||||
pub hwnd: isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<isize> for Hidden {
|
|
||||||
fn from(hwnd: isize) -> Self {
|
|
||||||
Self { hwnd }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hidden {
|
|
||||||
pub const fn hwnd(self) -> HWND {
|
|
||||||
HWND(self.hwnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create(name: &str) -> color_eyre::Result<Self> {
|
|
||||||
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
|
|
||||||
let class_name = PCWSTR(name.as_ptr());
|
|
||||||
|
|
||||||
let h_module = WindowsApi::module_handle_w()?;
|
|
||||||
let window_class = WNDCLASSW {
|
|
||||||
hInstance: h_module.into(),
|
|
||||||
lpszClassName: class_name,
|
|
||||||
style: CS_HREDRAW | CS_VREDRAW,
|
|
||||||
lpfnWndProc: Some(Self::callback),
|
|
||||||
hbrBackground: WindowsApi::create_solid_brush(0),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = WindowsApi::register_class_w(&window_class)?;
|
|
||||||
|
|
||||||
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
|
||||||
let hwnd = WindowsApi::create_hidden_window(PCWSTR(name.as_ptr()), h_module)?;
|
|
||||||
hwnd_sender.send(hwnd)?;
|
|
||||||
|
|
||||||
let mut message = MSG::default();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() {
|
|
||||||
TranslateMessage(&message);
|
|
||||||
DispatchMessageW(&message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
let hwnd = hwnd_receiver.recv()?;
|
|
||||||
|
|
||||||
WindowsApi::wts_register_session_notification(hwnd)?;
|
|
||||||
|
|
||||||
Ok(Self { hwnd })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "system" fn callback(
|
|
||||||
window: HWND,
|
|
||||||
message: u32,
|
|
||||||
wparam: WPARAM,
|
|
||||||
lparam: LPARAM,
|
|
||||||
) -> LRESULT {
|
|
||||||
unsafe {
|
|
||||||
match message {
|
|
||||||
WM_POWERBROADCAST => {
|
|
||||||
match wparam.0 as u32 {
|
|
||||||
// Automatic: System resumed itself from sleep or hibernation
|
|
||||||
// Suspend: User resumed system from sleep or hibernation
|
|
||||||
PBT_APMRESUMEAUTOMATIC | PBT_APMRESUMESUSPEND => {
|
|
||||||
tracing::debug!(
|
|
||||||
"WM_POWERBROADCAST event received - resume from suspend"
|
|
||||||
);
|
|
||||||
let _ = monitor_reconciliator::event_tx().send(
|
|
||||||
monitor_reconciliator::Notification::ResumingFromSuspendedState,
|
|
||||||
);
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
// Computer is entering a suspended state
|
|
||||||
PBT_APMSUSPEND => {
|
|
||||||
tracing::debug!(
|
|
||||||
"WM_POWERBROADCAST event received - entering suspended state"
|
|
||||||
);
|
|
||||||
let _ = monitor_reconciliator::event_tx()
|
|
||||||
.send(monitor_reconciliator::Notification::EnteringSuspendedState);
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
_ => LRESULT(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WM_WTSSESSION_CHANGE => {
|
|
||||||
match wparam.0 as u32 {
|
|
||||||
WTS_SESSION_LOCK => {
|
|
||||||
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked");
|
|
||||||
|
|
||||||
let _ = monitor_reconciliator::event_tx()
|
|
||||||
.send(monitor_reconciliator::Notification::SessionLocked);
|
|
||||||
}
|
|
||||||
WTS_SESSION_UNLOCK => {
|
|
||||||
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked");
|
|
||||||
|
|
||||||
let _ = monitor_reconciliator::event_tx()
|
|
||||||
.send(monitor_reconciliator::Notification::SessionUnlocked);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
// This event gets sent when:
|
|
||||||
// - The scaling factor on a display changes
|
|
||||||
// - The resolution on a display changes
|
|
||||||
// - A monitor is added
|
|
||||||
// - A monitor is removed
|
|
||||||
// Since WM_DEVICECHANGE also notifies on monitor changes, we only handle scaling
|
|
||||||
// and resolution changes here
|
|
||||||
WM_DISPLAYCHANGE => {
|
|
||||||
tracing::debug!(
|
|
||||||
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed", wparam.0
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = monitor_reconciliator::event_tx()
|
|
||||||
.send(monitor_reconciliator::Notification::ResolutionScalingChanged);
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
// Unfortunately this is the event sent with ButteryTaskbar which I use a lot
|
|
||||||
// Original idea from https://stackoverflow.com/a/33762334
|
|
||||||
WM_SETTINGCHANGE => {
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
if wparam.0 as u32 == SPI_SETWORKAREA.0 {
|
|
||||||
tracing::debug!(
|
|
||||||
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = monitor_reconciliator::event_tx()
|
|
||||||
.send(monitor_reconciliator::Notification::WorkAreaChanged);
|
|
||||||
}
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
// This event + wparam combo is sent 4 times when a monitor is added based on my testing on win11
|
|
||||||
// Original idea from https://stackoverflow.com/a/33762334
|
|
||||||
WM_DEVICECHANGE => {
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
|
||||||
tracing::debug!(
|
|
||||||
"WM_DEVICECHANGE event received with DBT_DEVNODES_CHANGED - display added or removed"
|
|
||||||
);
|
|
||||||
let _ = monitor_reconciliator::event_tx()
|
|
||||||
.send(monitor_reconciliator::Notification::DisplayConnectionChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
_ => DefWindowProcW(window, message, wparam, lparam),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
||||||
|
|
||||||
use crate::border_manager;
|
|
||||||
use crate::monitor;
|
|
||||||
use crate::monitor::Monitor;
|
|
||||||
use crate::monitor_reconciliator::hidden::Hidden;
|
|
||||||
use crate::MonitorConfig;
|
|
||||||
use crate::WindowManager;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
use crossbeam_channel::Receiver;
|
|
||||||
use crossbeam_channel::Sender;
|
|
||||||
use crossbeam_utils::atomic::AtomicConsume;
|
|
||||||
use komorebi_core::Rect;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
pub mod hidden;
|
|
||||||
|
|
||||||
pub enum Notification {
|
|
||||||
ResolutionScalingChanged,
|
|
||||||
WorkAreaChanged,
|
|
||||||
DisplayConnectionChange,
|
|
||||||
EnteringSuspendedState,
|
|
||||||
ResumingFromSuspendedState,
|
|
||||||
SessionLocked,
|
|
||||||
SessionUnlocked,
|
|
||||||
}
|
|
||||||
|
|
||||||
static ACTIVE: AtomicBool = AtomicBool::new(true);
|
|
||||||
|
|
||||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
|
||||||
|
|
||||||
static MONITOR_CACHE: OnceLock<Mutex<HashMap<String, MonitorConfig>>> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
|
||||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_tx() -> Sender<Notification> {
|
|
||||||
channel().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_rx() -> Receiver<Notification> {
|
|
||||||
channel().1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_in_monitor_cache(device_id: &str, config: MonitorConfig) {
|
|
||||||
let mut monitor_cache = MONITOR_CACHE
|
|
||||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
|
||||||
.lock();
|
|
||||||
|
|
||||||
monitor_cache.insert(device_id.to_string(), config);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
|
|
||||||
Ok(win32_display_data::connected_displays()
|
|
||||||
.flatten()
|
|
||||||
.map(|display| {
|
|
||||||
let path = display.device_path;
|
|
||||||
let mut split: Vec<_> = path.split('#').collect();
|
|
||||||
split.remove(0);
|
|
||||||
split.remove(split.len() - 1);
|
|
||||||
let device = split[0].to_string();
|
|
||||||
let device_id = split.join("-");
|
|
||||||
|
|
||||||
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
|
||||||
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
|
||||||
|
|
||||||
monitor::new(
|
|
||||||
display.hmonitor,
|
|
||||||
display.size.into(),
|
|
||||||
display.work_area_size.into(),
|
|
||||||
name,
|
|
||||||
device,
|
|
||||||
device_id,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
}
|
|
||||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
|
||||||
#[allow(clippy::expect_used)]
|
|
||||||
Hidden::create("komorebi-hidden")?;
|
|
||||||
|
|
||||||
tracing::info!("created hidden window to listen for monitor-related events");
|
|
||||||
|
|
||||||
std::thread::spawn(move || loop {
|
|
||||||
match handle_notifications(wm.clone()) {
|
|
||||||
Ok(()) => {
|
|
||||||
tracing::warn!("restarting finished thread");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
tracing::error!("restarting failed thread: {:?}", error)
|
|
||||||
} else {
|
|
||||||
tracing::error!("restarting failed thread: {}", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
|
||||||
tracing::info!("listening");
|
|
||||||
|
|
||||||
let receiver = event_rx();
|
|
||||||
|
|
||||||
'receiver: for notification in receiver {
|
|
||||||
if !ACTIVE.load_consume() {
|
|
||||||
if matches!(
|
|
||||||
notification,
|
|
||||||
Notification::ResumingFromSuspendedState | Notification::SessionUnlocked
|
|
||||||
) {
|
|
||||||
tracing::debug!(
|
|
||||||
"reactivating reconciliator - system has resumed from suspended state or session has been unlocked"
|
|
||||||
);
|
|
||||||
|
|
||||||
ACTIVE.store(true, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut wm = wm.lock();
|
|
||||||
|
|
||||||
match notification {
|
|
||||||
Notification::EnteringSuspendedState | Notification::SessionLocked => {
|
|
||||||
tracing::debug!(
|
|
||||||
"deactivating reconciliator until system resumes from suspended state or session is unlocked"
|
|
||||||
);
|
|
||||||
ACTIVE.store(false, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
Notification::ResumingFromSuspendedState | Notification::SessionUnlocked => {
|
|
||||||
// this is only handled above if the reconciliator is paused
|
|
||||||
}
|
|
||||||
Notification::WorkAreaChanged => {
|
|
||||||
tracing::debug!("handling work area changed notification");
|
|
||||||
let offset = wm.work_area_offset;
|
|
||||||
for monitor in wm.monitors_mut() {
|
|
||||||
let mut should_update = false;
|
|
||||||
|
|
||||||
// Update work areas as necessary
|
|
||||||
if let Ok(reference) = WindowsApi::monitor(monitor.id()) {
|
|
||||||
if reference.work_area_size() != monitor.work_area_size() {
|
|
||||||
monitor.set_work_area_size(Rect {
|
|
||||||
left: reference.work_area_size().left,
|
|
||||||
top: reference.work_area_size().top,
|
|
||||||
right: reference.work_area_size().right,
|
|
||||||
bottom: reference.work_area_size().bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
should_update = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_update {
|
|
||||||
tracing::info!("updated work area for {}", monitor.device_id());
|
|
||||||
monitor.update_focused_workspace(offset)?;
|
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
"work areas match, reconciliation not required for {}",
|
|
||||||
monitor.device_id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Notification::ResolutionScalingChanged => {
|
|
||||||
tracing::debug!("handling resolution/scaling changed notification");
|
|
||||||
let offset = wm.work_area_offset;
|
|
||||||
for monitor in wm.monitors_mut() {
|
|
||||||
let mut should_update = false;
|
|
||||||
|
|
||||||
// Update sizes and work areas as necessary
|
|
||||||
if let Ok(reference) = WindowsApi::monitor(monitor.id()) {
|
|
||||||
if reference.work_area_size() != monitor.work_area_size() {
|
|
||||||
monitor.set_work_area_size(Rect {
|
|
||||||
left: reference.work_area_size().left,
|
|
||||||
top: reference.work_area_size().top,
|
|
||||||
right: reference.work_area_size().right,
|
|
||||||
bottom: reference.work_area_size().bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
should_update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if reference.size() != monitor.size() {
|
|
||||||
monitor.set_size(Rect {
|
|
||||||
left: reference.size().left,
|
|
||||||
top: reference.size().top,
|
|
||||||
right: reference.size().right,
|
|
||||||
bottom: reference.size().bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
should_update = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_update {
|
|
||||||
tracing::info!(
|
|
||||||
"updated monitor resolution/scaling for {}",
|
|
||||||
monitor.device_id()
|
|
||||||
);
|
|
||||||
|
|
||||||
monitor.update_focused_workspace(offset)?;
|
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
"resolutions match, reconciliation not required for {}",
|
|
||||||
monitor.device_id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Notification::DisplayConnectionChange => {
|
|
||||||
tracing::debug!("handling display connection change notification");
|
|
||||||
let mut monitor_cache = MONITOR_CACHE
|
|
||||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
|
||||||
.lock();
|
|
||||||
|
|
||||||
let initial_monitor_count = wm.monitors().len();
|
|
||||||
|
|
||||||
// Get the currently attached display devices
|
|
||||||
let attached_devices = attached_display_devices()?;
|
|
||||||
|
|
||||||
// Make sure that in our state any attached displays have the latest Win32 data
|
|
||||||
for monitor in wm.monitors_mut() {
|
|
||||||
for attached in &attached_devices {
|
|
||||||
if attached.device_id().eq(monitor.device_id()) {
|
|
||||||
monitor.set_id(attached.id());
|
|
||||||
monitor.set_name(attached.name().clone());
|
|
||||||
monitor.set_size(*attached.size());
|
|
||||||
monitor.set_work_area_size(*attached.work_area_size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if initial_monitor_count == attached_devices.len() {
|
|
||||||
tracing::debug!("monitor counts match, reconciliation not required");
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
if attached_devices.is_empty() {
|
|
||||||
tracing::debug!(
|
|
||||||
"no devices found, skipping reconciliation to avoid breaking state"
|
|
||||||
);
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
if initial_monitor_count > attached_devices.len() {
|
|
||||||
tracing::info!(
|
|
||||||
"monitor count mismatch ({initial_monitor_count} vs {}), removing disconnected monitors",
|
|
||||||
attached_devices.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Gather all the containers that will be orphaned from disconnected and invalid displays
|
|
||||||
let mut orphaned_containers = vec![];
|
|
||||||
|
|
||||||
// Collect the ids in our state which aren't in the current attached display ids
|
|
||||||
// These are monitors that have been removed
|
|
||||||
let mut newly_removed_displays = vec![];
|
|
||||||
|
|
||||||
for m in wm.monitors().iter() {
|
|
||||||
if !attached_devices
|
|
||||||
.iter()
|
|
||||||
.any(|attached| attached.device_id().eq(m.device_id()))
|
|
||||||
{
|
|
||||||
newly_removed_displays.push(m.device_id().clone());
|
|
||||||
for workspace in m.workspaces() {
|
|
||||||
for container in workspace.containers() {
|
|
||||||
// Save the orphaned containers from the removed monitor
|
|
||||||
orphaned_containers.push(container.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's add their state to the cache for later
|
|
||||||
monitor_cache.insert(m.device_id().clone(), m.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !orphaned_containers.is_empty() {
|
|
||||||
tracing::info!(
|
|
||||||
"removed orphaned containers from: {newly_removed_displays:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !newly_removed_displays.is_empty() {
|
|
||||||
// After we have cached them, remove them from our state
|
|
||||||
wm.monitors_mut()
|
|
||||||
.retain(|m| !newly_removed_displays.contains(m.device_id()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let post_removal_monitor_count = wm.monitors().len();
|
|
||||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
|
||||||
if focused_monitor_idx >= post_removal_monitor_count {
|
|
||||||
wm.focus_monitor(0)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !orphaned_containers.is_empty() {
|
|
||||||
if let Some(primary) = wm.monitors_mut().front_mut() {
|
|
||||||
if let Some(focused_ws) = primary.focused_workspace_mut() {
|
|
||||||
let focused_container_idx = focused_ws.focused_container_idx();
|
|
||||||
|
|
||||||
// Put the orphaned containers somewhere visible
|
|
||||||
for container in orphaned_containers {
|
|
||||||
focused_ws.add_container(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gotta reset the focus or the movement will feel "off"
|
|
||||||
if initial_monitor_count != post_removal_monitor_count {
|
|
||||||
focused_ws.focus_container(focused_container_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = wm.work_area_offset;
|
|
||||||
|
|
||||||
for monitor in wm.monitors_mut() {
|
|
||||||
// If we have lost a monitor, update everything to filter out any jank
|
|
||||||
if initial_monitor_count != post_removal_monitor_count {
|
|
||||||
monitor.update_focused_workspace(offset)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let post_removal_monitor_count = wm.monitors().len();
|
|
||||||
|
|
||||||
// This is the list of device ids after we have removed detached displays
|
|
||||||
let post_removal_device_ids = wm
|
|
||||||
.monitors()
|
|
||||||
.iter()
|
|
||||||
.map(Monitor::device_id)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Check for and add any new monitors that may have been plugged in
|
|
||||||
// Monitor and display index preferences get applied in this function
|
|
||||||
WindowsApi::load_monitor_information(&mut wm.monitors)?;
|
|
||||||
|
|
||||||
let post_addition_monitor_count = wm.monitors().len();
|
|
||||||
|
|
||||||
if post_addition_monitor_count > post_removal_monitor_count {
|
|
||||||
tracing::info!(
|
|
||||||
"monitor count mismatch ({post_removal_monitor_count} vs {post_addition_monitor_count}), adding connected monitors",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Look in the updated state for new monitors
|
|
||||||
for m in wm.monitors_mut() {
|
|
||||||
let device_id = m.device_id().clone();
|
|
||||||
// We identify a new monitor when we encounter a new device id
|
|
||||||
if !post_removal_device_ids.contains(&device_id) {
|
|
||||||
let mut cache_hit = false;
|
|
||||||
// Check if that device id exists in the cache for this session
|
|
||||||
if let Some(cached) = monitor_cache.get(&device_id) {
|
|
||||||
cache_hit = true;
|
|
||||||
|
|
||||||
tracing::info!("found monitor and workspace configuration for {device_id} in the monitor cache, applying");
|
|
||||||
|
|
||||||
// If it does, load all the monitor settings from the cache entry
|
|
||||||
m.ensure_workspace_count(cached.workspaces.len());
|
|
||||||
m.set_work_area_offset(cached.work_area_offset);
|
|
||||||
m.set_window_based_work_area_offset(
|
|
||||||
cached.window_based_work_area_offset,
|
|
||||||
);
|
|
||||||
m.set_window_based_work_area_offset_limit(
|
|
||||||
cached.window_based_work_area_offset_limit.unwrap_or(1),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (w_idx, workspace) in m.workspaces_mut().iter_mut().enumerate()
|
|
||||||
{
|
|
||||||
if let Some(cached_workspace) = cached.workspaces.get(w_idx) {
|
|
||||||
workspace.load_static_config(cached_workspace)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entries in the cache should only be used once; remove the entry there was a cache hit
|
|
||||||
if cache_hit {
|
|
||||||
monitor_cache.remove(&device_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let final_count = wm.monitors().len();
|
|
||||||
|
|
||||||
if post_removal_monitor_count != final_count {
|
|
||||||
wm.retile_all(true)?;
|
|
||||||
// Second retile to fix DPI/resolution related jank
|
|
||||||
wm.retile_all(true)?;
|
|
||||||
// Border updates to fix DPI/resolution related jank
|
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -38,21 +38,27 @@ use komorebi_core::StateQuery;
|
|||||||
use komorebi_core::WindowContainerBehaviour;
|
use komorebi_core::WindowContainerBehaviour;
|
||||||
use komorebi_core::WindowKind;
|
use komorebi_core::WindowKind;
|
||||||
|
|
||||||
use crate::border_manager;
|
use crate::border::Border;
|
||||||
use crate::border_manager::STYLE;
|
|
||||||
use crate::colour::Rgb;
|
use crate::colour::Rgb;
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::notify_subscribers;
|
use crate::notify_subscribers;
|
||||||
use crate::stackbar_manager;
|
|
||||||
use crate::static_config::StaticConfig;
|
use crate::static_config::StaticConfig;
|
||||||
use crate::window::RuleDebug;
|
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::window_manager;
|
use crate::window_manager;
|
||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::GlobalState;
|
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
use crate::NotificationEvent;
|
use crate::NotificationEvent;
|
||||||
|
use crate::BORDER_COLOUR_CURRENT;
|
||||||
|
use crate::BORDER_COLOUR_MONOCLE;
|
||||||
|
use crate::BORDER_COLOUR_SINGLE;
|
||||||
|
use crate::BORDER_COLOUR_STACK;
|
||||||
|
use crate::BORDER_ENABLED;
|
||||||
|
use crate::BORDER_HIDDEN;
|
||||||
|
use crate::BORDER_HWND;
|
||||||
|
use crate::BORDER_OFFSET;
|
||||||
|
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||||
|
use crate::BORDER_WIDTH;
|
||||||
use crate::CUSTOM_FFM;
|
use crate::CUSTOM_FFM;
|
||||||
use crate::DATA_DIR;
|
use crate::DATA_DIR;
|
||||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||||
@@ -70,13 +76,6 @@ use crate::SUBSCRIPTION_SOCKETS;
|
|||||||
use crate::TCP_CONNECTIONS;
|
use crate::TCP_CONNECTIONS;
|
||||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||||
use crate::WORKSPACE_RULES;
|
use crate::WORKSPACE_RULES;
|
||||||
use stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
|
||||||
use stackbar_manager::STACKBAR_LABEL;
|
|
||||||
use stackbar_manager::STACKBAR_MODE;
|
|
||||||
use stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
|
||||||
use stackbar_manager::STACKBAR_TAB_HEIGHT;
|
|
||||||
use stackbar_manager::STACKBAR_TAB_WIDTH;
|
|
||||||
use stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||||
@@ -167,6 +166,21 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match message {
|
||||||
|
SocketMessage::CycleFocusMonitor(_)
|
||||||
|
| SocketMessage::CycleFocusWorkspace(_)
|
||||||
|
| SocketMessage::FocusMonitorNumber(_)
|
||||||
|
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
|
||||||
|
| SocketMessage::FocusWorkspaceNumber(_) => {
|
||||||
|
if self.focused_workspace()?.visible_windows().is_empty() {
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
border.hide()?;
|
||||||
|
BORDER_HIDDEN.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
|
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
|
||||||
if let Some(monitor) = self.focused_monitor_mut() {
|
if let Some(monitor) = self.focused_monitor_mut() {
|
||||||
@@ -187,10 +201,6 @@ impl WindowManager {
|
|||||||
match message {
|
match message {
|
||||||
SocketMessage::Promote => self.promote_container_to_front()?,
|
SocketMessage::Promote => self.promote_container_to_front()?,
|
||||||
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
|
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
|
||||||
SocketMessage::PromoteWindow(direction) => {
|
|
||||||
self.focus_container_in_direction(direction)?;
|
|
||||||
self.promote_container_to_front()?
|
|
||||||
}
|
|
||||||
SocketMessage::FocusWindow(direction) => {
|
SocketMessage::FocusWindow(direction) => {
|
||||||
self.focus_container_in_direction(direction)?;
|
self.focus_container_in_direction(direction)?;
|
||||||
}
|
}
|
||||||
@@ -215,18 +225,8 @@ impl WindowManager {
|
|||||||
WindowsApi::center_cursor_in_rect(&focused_window_rect)?;
|
WindowsApi::center_cursor_in_rect(&focused_window_rect)?;
|
||||||
WindowsApi::left_click();
|
WindowsApi::left_click();
|
||||||
}
|
}
|
||||||
SocketMessage::Close => {
|
SocketMessage::Close => self.focused_window()?.close()?,
|
||||||
Window {
|
SocketMessage::Minimize => self.focused_window()?.minimize(),
|
||||||
hwnd: WindowsApi::foreground_window()?,
|
|
||||||
}
|
|
||||||
.close()?;
|
|
||||||
}
|
|
||||||
SocketMessage::Minimize => {
|
|
||||||
Window {
|
|
||||||
hwnd: WindowsApi::foreground_window()?,
|
|
||||||
}
|
|
||||||
.minimize();
|
|
||||||
}
|
|
||||||
SocketMessage::ToggleFloat => self.toggle_float()?,
|
SocketMessage::ToggleFloat => self.toggle_float()?,
|
||||||
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
|
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
|
||||||
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
|
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
|
||||||
@@ -456,9 +456,6 @@ impl WindowManager {
|
|||||||
SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
|
SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
|
||||||
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), false)?;
|
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::MoveContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
|
|
||||||
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), true)?;
|
|
||||||
}
|
|
||||||
SocketMessage::SendContainerToNamedWorkspace(ref workspace) => {
|
SocketMessage::SendContainerToNamedWorkspace(ref workspace) => {
|
||||||
if let Some((monitor_idx, workspace_idx)) =
|
if let Some((monitor_idx, workspace_idx)) =
|
||||||
self.monitor_workspace_index_by_name(workspace)
|
self.monitor_workspace_index_by_name(workspace)
|
||||||
@@ -481,15 +478,6 @@ impl WindowManager {
|
|||||||
SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {
|
SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {
|
||||||
self.move_workspace_to_monitor(monitor_idx)?;
|
self.move_workspace_to_monitor(monitor_idx)?;
|
||||||
}
|
}
|
||||||
SocketMessage::CycleMoveWorkspaceToMonitor(direction) => {
|
|
||||||
let monitor_idx = direction.next_idx(
|
|
||||||
self.focused_monitor_idx(),
|
|
||||||
NonZeroUsize::new(self.monitors().len())
|
|
||||||
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.move_workspace_to_monitor(monitor_idx)?;
|
|
||||||
}
|
|
||||||
SocketMessage::TogglePause => {
|
SocketMessage::TogglePause => {
|
||||||
if self.is_paused {
|
if self.is_paused {
|
||||||
tracing::info!("resuming");
|
tracing::info!("resuming");
|
||||||
@@ -511,16 +499,13 @@ impl WindowManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.focus_monitor(monitor_idx)?;
|
self.focus_monitor(monitor_idx)?;
|
||||||
self.update_focused_workspace(self.mouse_follows_focus, true)?;
|
self.update_focused_workspace(self.mouse_follows_focus)?;
|
||||||
}
|
}
|
||||||
SocketMessage::FocusMonitorNumber(monitor_idx) => {
|
SocketMessage::FocusMonitorNumber(monitor_idx) => {
|
||||||
self.focus_monitor(monitor_idx)?;
|
self.focus_monitor(monitor_idx)?;
|
||||||
self.update_focused_workspace(self.mouse_follows_focus, true)?;
|
self.update_focused_workspace(self.mouse_follows_focus)?;
|
||||||
}
|
|
||||||
SocketMessage::Retile => {
|
|
||||||
border_manager::destroy_all_borders()?;
|
|
||||||
self.retile_all(false)?
|
|
||||||
}
|
}
|
||||||
|
SocketMessage::Retile => self.retile_all(false)?,
|
||||||
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
|
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
|
||||||
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
|
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
|
||||||
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
|
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
|
||||||
@@ -643,6 +628,10 @@ impl WindowManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.focus_workspace(workspace_idx)?;
|
self.focus_workspace(workspace_idx)?;
|
||||||
|
|
||||||
|
if BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
|
self.show_border()?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
SocketMessage::FocusLastWorkspace => {
|
SocketMessage::FocusLastWorkspace => {
|
||||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||||
@@ -666,6 +655,10 @@ impl WindowManager {
|
|||||||
self.focused_monitor_mut()
|
self.focused_monitor_mut()
|
||||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||||
.set_last_focused_workspace(Option::from(idx));
|
.set_last_focused_workspace(Option::from(idx));
|
||||||
|
|
||||||
|
if BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
|
self.show_border()?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
|
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
|
||||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||||
@@ -675,9 +668,11 @@ impl WindowManager {
|
|||||||
self.focus_monitor(monitor_idx)?;
|
self.focus_monitor(monitor_idx)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.focused_workspace_idx().unwrap_or_default() != workspace_idx {
|
self.focus_workspace(workspace_idx)?;
|
||||||
self.focus_workspace(workspace_idx)?;
|
|
||||||
}
|
if BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
|
self.show_border()?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
SocketMessage::FocusWorkspaceNumbers(workspace_idx) => {
|
SocketMessage::FocusWorkspaceNumbers(workspace_idx) => {
|
||||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||||
@@ -697,17 +692,14 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.focus_workspace(workspace_idx)?;
|
self.focus_workspace(workspace_idx)?;
|
||||||
|
|
||||||
|
if BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
|
self.show_border()?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
|
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
|
||||||
let focused_monitor_idx = self.focused_monitor_idx();
|
self.focus_monitor(monitor_idx)?;
|
||||||
let focused_workspace_idx = self.focused_workspace_idx().unwrap_or_default();
|
self.focus_workspace(workspace_idx)?;
|
||||||
|
|
||||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
|
||||||
|
|
||||||
if focused_pair != (monitor_idx, workspace_idx) {
|
|
||||||
self.focus_monitor(monitor_idx)?;
|
|
||||||
self.focus_workspace(workspace_idx)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SocketMessage::FocusNamedWorkspace(ref name) => {
|
SocketMessage::FocusNamedWorkspace(ref name) => {
|
||||||
if let Some((monitor_idx, workspace_idx)) =
|
if let Some((monitor_idx, workspace_idx)) =
|
||||||
@@ -716,6 +708,10 @@ impl WindowManager {
|
|||||||
self.focus_monitor(monitor_idx)?;
|
self.focus_monitor(monitor_idx)?;
|
||||||
self.focus_workspace(workspace_idx)?;
|
self.focus_workspace(workspace_idx)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
|
self.show_border()?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
SocketMessage::Stop => {
|
SocketMessage::Stop => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -770,25 +766,16 @@ impl WindowManager {
|
|||||||
|
|
||||||
tracing::info!("replying to state done");
|
tracing::info!("replying to state done");
|
||||||
}
|
}
|
||||||
SocketMessage::GlobalState => {
|
|
||||||
let state = match serde_json::to_string_pretty(&GlobalState::default()) {
|
|
||||||
Ok(state) => state,
|
|
||||||
Err(error) => error.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::info!("replying to global state");
|
|
||||||
|
|
||||||
reply.write_all(state.as_bytes())?;
|
|
||||||
|
|
||||||
tracing::info!("replying to global state done");
|
|
||||||
}
|
|
||||||
SocketMessage::VisibleWindows => {
|
SocketMessage::VisibleWindows => {
|
||||||
let mut monitor_visible_windows = HashMap::new();
|
let mut monitor_visible_windows = HashMap::new();
|
||||||
|
|
||||||
for monitor in self.monitors() {
|
for (index, monitor) in self.monitors().iter().enumerate() {
|
||||||
if let Some(ws) = monitor.focused_workspace() {
|
if let Some(ws) = monitor.focused_workspace() {
|
||||||
monitor_visible_windows.insert(
|
monitor_visible_windows.insert(
|
||||||
monitor.device_id().clone(),
|
monitor
|
||||||
|
.device_id()
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| format!("{index}")),
|
||||||
ws.visible_window_details().clone(),
|
ws.visible_window_details().clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -927,7 +914,7 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
|
SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
|
||||||
if !CUSTOM_FFM.load(Ordering::SeqCst) {
|
if !CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||||
@@ -1033,12 +1020,32 @@ impl WindowManager {
|
|||||||
SocketMessage::CompleteConfiguration => {
|
SocketMessage::CompleteConfiguration => {
|
||||||
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||||
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
|
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::WatchConfiguration(enable) => {
|
SocketMessage::WatchConfiguration(enable) => {
|
||||||
self.watch_configuration(enable)?;
|
self.watch_configuration(enable)?;
|
||||||
}
|
}
|
||||||
|
SocketMessage::IdentifyBorderOverflowApplication(identifier, ref id) => {
|
||||||
|
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||||
|
|
||||||
|
let mut should_push = true;
|
||||||
|
for i in &*identifiers {
|
||||||
|
if let MatchingRule::Simple(i) = i {
|
||||||
|
if i.id.eq(id) {
|
||||||
|
should_push = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_push {
|
||||||
|
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
||||||
|
kind: identifier,
|
||||||
|
id: id.clone(),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
|
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
|
||||||
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
||||||
|
|
||||||
@@ -1140,7 +1147,7 @@ impl WindowManager {
|
|||||||
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
|
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
|
||||||
|
|
||||||
workspace.set_resize_dimensions(resize);
|
workspace.set_resize_dimensions(resize);
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::Save(ref path) => {
|
SocketMessage::Save(ref path) => {
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
@@ -1163,7 +1170,7 @@ impl WindowManager {
|
|||||||
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
|
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
|
||||||
|
|
||||||
workspace.set_resize_dimensions(resize);
|
workspace.set_resize_dimensions(resize);
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::AddSubscriberSocket(ref socket) => {
|
SocketMessage::AddSubscriberSocket(ref socket) => {
|
||||||
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
||||||
@@ -1218,7 +1225,6 @@ impl WindowManager {
|
|||||||
MoveBehaviour::Insert => {
|
MoveBehaviour::Insert => {
|
||||||
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
|
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
|
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
|
||||||
@@ -1227,56 +1233,45 @@ impl WindowManager {
|
|||||||
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
|
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
|
||||||
self.unmanaged_window_operation_behaviour = behaviour;
|
self.unmanaged_window_operation_behaviour = behaviour;
|
||||||
}
|
}
|
||||||
SocketMessage::Border(enable) => {
|
SocketMessage::ActiveWindowBorder(enable) => {
|
||||||
border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst);
|
if enable {
|
||||||
}
|
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
|
||||||
SocketMessage::BorderColour(kind, r, g, b) => match kind {
|
Border::create("komorebi-border-window")?;
|
||||||
WindowKind::Single => {
|
}
|
||||||
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
|
||||||
|
BORDER_ENABLED.store(true, Ordering::SeqCst);
|
||||||
|
self.show_border()?;
|
||||||
|
} else {
|
||||||
|
BORDER_ENABLED.store(false, Ordering::SeqCst);
|
||||||
|
self.hide_border()?;
|
||||||
}
|
}
|
||||||
WindowKind::Stack => {
|
}
|
||||||
border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
|
||||||
|
match kind {
|
||||||
|
WindowKind::Single => {
|
||||||
|
BORDER_COLOUR_SINGLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||||
|
BORDER_COLOUR_CURRENT.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
WindowKind::Stack => {
|
||||||
|
BORDER_COLOUR_STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
WindowKind::Monocle => {
|
||||||
|
BORDER_COLOUR_MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
WindowKind::Monocle => {
|
|
||||||
border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
WindowsApi::invalidate_border_rect()?;
|
||||||
}
|
|
||||||
WindowKind::Unfocused => {
|
|
||||||
border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SocketMessage::BorderStyle(style) => {
|
|
||||||
let mut border_style = STYLE.lock();
|
|
||||||
*border_style = style;
|
|
||||||
}
|
}
|
||||||
SocketMessage::BorderWidth(width) => {
|
SocketMessage::ActiveWindowBorderWidth(width) => {
|
||||||
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
|
BORDER_WIDTH.store(width, Ordering::SeqCst);
|
||||||
|
WindowsApi::invalidate_border_rect()?;
|
||||||
}
|
}
|
||||||
SocketMessage::BorderOffset(offset) => {
|
SocketMessage::ActiveWindowBorderOffset(offset) => {
|
||||||
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
||||||
|
WindowsApi::invalidate_border_rect()?;
|
||||||
}
|
}
|
||||||
SocketMessage::StackbarMode(mode) => {
|
SocketMessage::AltFocusHack(_) => {
|
||||||
STACKBAR_MODE.store(mode);
|
tracing::info!("this action is deprecated");
|
||||||
}
|
|
||||||
SocketMessage::StackbarLabel(label) => {
|
|
||||||
STACKBAR_LABEL.store(label);
|
|
||||||
}
|
|
||||||
SocketMessage::StackbarFocusedTextColour(r, g, b) => {
|
|
||||||
let rgb = Rgb::new(r, g, b);
|
|
||||||
STACKBAR_FOCUSED_TEXT_COLOUR.store(rgb.into(), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
SocketMessage::StackbarUnfocusedTextColour(r, g, b) => {
|
|
||||||
let rgb = Rgb::new(r, g, b);
|
|
||||||
STACKBAR_UNFOCUSED_TEXT_COLOUR.store(rgb.into(), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
SocketMessage::StackbarBackgroundColour(r, g, b) => {
|
|
||||||
let rgb = Rgb::new(r, g, b);
|
|
||||||
STACKBAR_TAB_BACKGROUND_COLOUR.store(rgb.into(), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
SocketMessage::StackbarHeight(height) => {
|
|
||||||
STACKBAR_TAB_HEIGHT.store(height, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
SocketMessage::StackbarTabWidth(width) => {
|
|
||||||
STACKBAR_TAB_WIDTH.store(width, Ordering::SeqCst);
|
|
||||||
}
|
}
|
||||||
SocketMessage::ApplicationSpecificConfigurationSchema => {
|
SocketMessage::ApplicationSpecificConfigurationSchema => {
|
||||||
let asc = schema_for!(Vec<ApplicationConfiguration>);
|
let asc = schema_for!(Vec<ApplicationConfiguration>);
|
||||||
@@ -1323,35 +1318,136 @@ impl WindowManager {
|
|||||||
SocketMessage::ToggleTitleBars => {
|
SocketMessage::ToggleTitleBars => {
|
||||||
let current = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
let current = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
||||||
REMOVE_TITLEBARS.store(!current, Ordering::SeqCst);
|
REMOVE_TITLEBARS.store(!current, Ordering::SeqCst);
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::DebugWindow(hwnd) => {
|
|
||||||
let window = Window { hwnd };
|
|
||||||
let mut rule_debug = RuleDebug::default();
|
|
||||||
let _ = window.should_manage(None, &mut rule_debug);
|
|
||||||
let schema = serde_json::to_string_pretty(&rule_debug)?;
|
|
||||||
|
|
||||||
reply.write_all(schema.as_bytes())?;
|
|
||||||
}
|
|
||||||
// Deprecated commands
|
|
||||||
SocketMessage::AltFocusHack(_)
|
|
||||||
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let notification = Notification {
|
match message {
|
||||||
event: NotificationEvent::Socket(message.clone()),
|
SocketMessage::ToggleMonocle => {
|
||||||
state: self.as_ref().into(),
|
let current = BORDER_COLOUR_CURRENT.load(Ordering::SeqCst);
|
||||||
};
|
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
|
||||||
|
|
||||||
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
if monocle != 0 {
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
if current == monocle {
|
||||||
stackbar_manager::event_tx().send(stackbar_manager::Notification)?;
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SocketMessage::StackWindow(_) => {
|
||||||
|
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
|
||||||
|
if stack != 0 {
|
||||||
|
BORDER_COLOUR_CURRENT
|
||||||
|
.store(BORDER_COLOUR_STACK.load(Ordering::SeqCst), Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SocketMessage::UnstackWindow => {
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match message {
|
||||||
|
SocketMessage::ChangeLayout(_)
|
||||||
|
| SocketMessage::CycleLayout(_)
|
||||||
|
| SocketMessage::ChangeLayoutCustom(_)
|
||||||
|
| SocketMessage::FlipLayout(_)
|
||||||
|
| SocketMessage::ManageFocusedWindow
|
||||||
|
| SocketMessage::MoveWorkspaceToMonitorNumber(_)
|
||||||
|
| SocketMessage::MoveContainerToMonitorNumber(_)
|
||||||
|
| SocketMessage::MoveContainerToWorkspaceNumber(_)
|
||||||
|
| SocketMessage::ResizeWindowEdge(_, _)
|
||||||
|
| SocketMessage::ResizeWindowAxis(_, _)
|
||||||
|
| SocketMessage::ToggleFloat
|
||||||
|
| SocketMessage::ToggleMonocle
|
||||||
|
| SocketMessage::ToggleMaximize
|
||||||
|
| SocketMessage::Promote
|
||||||
|
| SocketMessage::PromoteFocus
|
||||||
|
| SocketMessage::StackWindow(_)
|
||||||
|
| SocketMessage::UnstackWindow
|
||||||
|
| SocketMessage::Retile
|
||||||
|
// Adding this one so that changes can be seen instantly after
|
||||||
|
// modifying the active window border offset
|
||||||
|
| SocketMessage::ActiveWindowBorderOffset(_)
|
||||||
|
// Adding this one because sometimes EVENT_SYSTEM_FOREGROUND isn't
|
||||||
|
// getting sent on FocusWindow, meaning the border won't be set
|
||||||
|
// when processing events
|
||||||
|
| SocketMessage::FocusWindow(_)
|
||||||
|
| SocketMessage::InvisibleBorders(_)
|
||||||
|
| SocketMessage::WorkAreaOffset(_)
|
||||||
|
| SocketMessage::CycleMoveWindow(_)
|
||||||
|
| SocketMessage::MoveWindow(_)
|
||||||
|
| SocketMessage::CycleFocusMonitor(_)
|
||||||
|
| SocketMessage::CycleFocusWorkspace(_)
|
||||||
|
| SocketMessage::FocusMonitorNumber(_)
|
||||||
|
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
|
||||||
|
| SocketMessage::FocusWorkspaceNumber(_) => {
|
||||||
|
let foreground = WindowsApi::foreground_window()?;
|
||||||
|
let foreground_window = Window { hwnd: foreground };
|
||||||
|
|
||||||
|
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
|
||||||
|
if monocle != 0 && self.focused_workspace()?.monocle_container().is_some() {
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
monocle,
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// it is not acceptable to fail here; we need to be able to send the event to
|
||||||
|
// subscribers
|
||||||
|
if self.focused_container().is_ok() {
|
||||||
|
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
|
||||||
|
if stack != 0 && self.focused_container()?.windows().len() > 1 {
|
||||||
|
BORDER_COLOUR_CURRENT
|
||||||
|
.store(stack, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
border.set_position(foreground_window, false)?;
|
||||||
|
}
|
||||||
|
SocketMessage::TogglePause => {
|
||||||
|
let is_paused = self.is_paused;
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
if is_paused {
|
||||||
|
border.hide()?;
|
||||||
|
} else {
|
||||||
|
let focused = self.focused_window()?;
|
||||||
|
border.set_position(*focused, true)?;
|
||||||
|
focused.focus(false)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SocketMessage::ToggleTiling | SocketMessage::WorkspaceTiling(..) => {
|
||||||
|
let tiling_enabled = *self.focused_workspace_mut()?.tile();
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
if tiling_enabled {
|
||||||
|
let focused = self.focused_window()?;
|
||||||
|
border.set_position(*focused, true)?;
|
||||||
|
focused.focus(false)?;
|
||||||
|
} else {
|
||||||
|
border.hide()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
tracing::info!("processed");
|
tracing::info!("processed");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self), level = "debug")]
|
#[tracing::instrument(skip(self))]
|
||||||
fn handle_initial_workspace_rules(
|
fn handle_initial_workspace_rules(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &String,
|
id: &String,
|
||||||
@@ -1363,7 +1459,7 @@ impl WindowManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self), level = "debug")]
|
#[tracing::instrument(skip(self))]
|
||||||
fn handle_definitive_workspace_rules(
|
fn handle_definitive_workspace_rules(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &String,
|
id: &String,
|
||||||
@@ -1375,7 +1471,7 @@ impl WindowManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self), level = "debug")]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn handle_workspace_rules(
|
pub fn handle_workspace_rules(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &String,
|
id: &String,
|
||||||
@@ -1410,10 +1506,9 @@ pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, mut stream: UnixStream)
|
|||||||
|
|
||||||
if wm.is_paused {
|
if wm.is_paused {
|
||||||
return match message {
|
return match message {
|
||||||
SocketMessage::TogglePause
|
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
|
||||||
| SocketMessage::State
|
Ok(wm.process_command(message, &mut stream)?)
|
||||||
| SocketMessage::GlobalState
|
}
|
||||||
| SocketMessage::Stop => Ok(wm.process_command(message, &mut stream)?),
|
|
||||||
_ => {
|
_ => {
|
||||||
tracing::trace!("ignoring while paused");
|
tracing::trace!("ignoring while paused");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1422,6 +1517,10 @@ pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, mut stream: UnixStream)
|
|||||||
}
|
}
|
||||||
|
|
||||||
wm.process_command(message.clone(), &mut stream)?;
|
wm.process_command(message.clone(), &mut stream)?;
|
||||||
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
|
event: NotificationEvent::Socket(message.clone()),
|
||||||
|
state: wm.as_ref().into(),
|
||||||
|
})?)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1456,10 +1555,9 @@ pub fn read_commands_tcp(
|
|||||||
|
|
||||||
if wm.is_paused {
|
if wm.is_paused {
|
||||||
return match message {
|
return match message {
|
||||||
SocketMessage::TogglePause
|
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
|
||||||
| SocketMessage::State
|
Ok(wm.process_command(message, stream)?)
|
||||||
| SocketMessage::GlobalState
|
}
|
||||||
| SocketMessage::Stop => Ok(wm.process_command(message, stream)?),
|
|
||||||
_ => {
|
_ => {
|
||||||
tracing::trace!("ignoring while paused");
|
tracing::trace!("ignoring while paused");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1468,6 +1566,10 @@ pub fn read_commands_tcp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
wm.process_command(message.clone(), &mut *stream)?;
|
wm.process_command(message.clone(), &mut *stream)?;
|
||||||
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
|
event: NotificationEvent::Socket(message.clone()),
|
||||||
|
state: wm.as_ref().into(),
|
||||||
|
})?)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use color_eyre::eyre::anyhow;
|
use color_eyre::eyre::anyhow;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use crossbeam_channel::select;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use komorebi_core::OperationDirection;
|
use komorebi_core::OperationDirection;
|
||||||
@@ -13,23 +12,24 @@ use komorebi_core::Rect;
|
|||||||
use komorebi_core::Sizing;
|
use komorebi_core::Sizing;
|
||||||
use komorebi_core::WindowContainerBehaviour;
|
use komorebi_core::WindowContainerBehaviour;
|
||||||
|
|
||||||
use crate::border_manager;
|
use crate::border::Border;
|
||||||
use crate::border_manager::BORDER_OFFSET;
|
|
||||||
use crate::border_manager::BORDER_WIDTH;
|
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::notify_subscribers;
|
use crate::notify_subscribers;
|
||||||
use crate::stackbar_manager;
|
|
||||||
use crate::window::should_act;
|
use crate::window::should_act;
|
||||||
use crate::window::RuleDebug;
|
|
||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::winevent::WinEvent;
|
|
||||||
use crate::workspace_reconciliator;
|
|
||||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
|
||||||
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
use crate::NotificationEvent;
|
use crate::NotificationEvent;
|
||||||
|
use crate::BORDER_COLOUR_CURRENT;
|
||||||
|
use crate::BORDER_COLOUR_MONOCLE;
|
||||||
|
use crate::BORDER_COLOUR_SINGLE;
|
||||||
|
use crate::BORDER_COLOUR_STACK;
|
||||||
|
use crate::BORDER_ENABLED;
|
||||||
|
use crate::BORDER_HIDDEN;
|
||||||
|
use crate::BORDER_HWND;
|
||||||
|
use crate::BORDER_OFFSET;
|
||||||
|
use crate::BORDER_WIDTH;
|
||||||
use crate::DATA_DIR;
|
use crate::DATA_DIR;
|
||||||
use crate::HIDDEN_HWNDS;
|
use crate::HIDDEN_HWNDS;
|
||||||
use crate::REGEX_IDENTIFIERS;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
@@ -42,14 +42,12 @@ pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
|||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
tracing::info!("listening");
|
tracing::info!("listening");
|
||||||
loop {
|
loop {
|
||||||
if let Ok(event) = receiver.recv() {
|
select! {
|
||||||
match wm.lock().process_event(event) {
|
recv(receiver) -> mut maybe_event => {
|
||||||
Ok(()) => {}
|
if let Ok(event) = maybe_event.as_mut() {
|
||||||
Err(error) => {
|
match wm.lock().process_event(event) {
|
||||||
if cfg!(debug_assertions) {
|
Ok(()) => {},
|
||||||
tracing::error!("{:?}", error)
|
Err(error) => tracing::error!("{}", error)
|
||||||
} else {
|
|
||||||
tracing::error!("{}", error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,22 +59,12 @@ pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
|||||||
impl WindowManager {
|
impl WindowManager {
|
||||||
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn process_event(&mut self, event: WindowManagerEvent) -> Result<()> {
|
pub fn process_event(&mut self, event: &mut WindowManagerEvent) -> Result<()> {
|
||||||
if self.is_paused {
|
if self.is_paused {
|
||||||
tracing::trace!("ignoring while paused");
|
tracing::trace!("ignoring while paused");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rule_debug = RuleDebug::default();
|
|
||||||
|
|
||||||
let should_manage = event.window().should_manage(Some(event), &mut rule_debug)?;
|
|
||||||
|
|
||||||
// All event handlers below this point should only be processed if the event is
|
|
||||||
// related to a window that should be managed by the WindowManager.
|
|
||||||
if !should_manage {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
|
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
|
||||||
if let Some(id) = current_virtual_desktop() {
|
if let Some(id) = current_virtual_desktop() {
|
||||||
if id != *virtual_desktop_id {
|
if id != *virtual_desktop_id {
|
||||||
@@ -93,38 +81,57 @@ impl WindowManager {
|
|||||||
match event {
|
match event {
|
||||||
WindowManagerEvent::FocusChange(_, window)
|
WindowManagerEvent::FocusChange(_, window)
|
||||||
| WindowManagerEvent::Show(_, window)
|
| WindowManagerEvent::Show(_, window)
|
||||||
|
| WindowManagerEvent::DisplayChange(window)
|
||||||
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||||
if let Some(monitor_idx) = self.monitor_idx_from_window(window) {
|
self.reconcile_monitors()?;
|
||||||
// This is a hidden window apparently associated with COM support mechanisms (based
|
|
||||||
// on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)
|
let monitor_idx = self.monitor_idx_from_window(*window)
|
||||||
//
|
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
|
||||||
// The hidden window, OLEChannelWnd, associated with this class (spawned by
|
|
||||||
// explorer.exe), after some debugging, is observed to always be tied to the primary
|
// This is a hidden window apparently associated with COM support mechanisms (based
|
||||||
// display monitor, or (usually) monitor 0 in the WindowManager state.
|
// on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)
|
||||||
//
|
//
|
||||||
// Due to this, at least one user in the Discord has witnessed behaviour where, when
|
// The hidden window, OLEChannelWnd, associated with this class (spawned by
|
||||||
// a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets
|
// explorer.exe), after some debugging, is observed to always be tied to the primary
|
||||||
// set repeatedly to 0, regardless of where the current foreground window is actually
|
// display monitor, or (usually) monitor 0 in the WindowManager state.
|
||||||
// located.
|
//
|
||||||
//
|
// Due to this, at least one user in the Discord has witnessed behaviour where, when
|
||||||
// This check ensures that we only update the focused monitor when the window
|
// a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets
|
||||||
// triggering monitor reconciliation is known to not be tied to a specific monitor.
|
// set repeatedly to 0, regardless of where the current foreground window is actually
|
||||||
if let Ok(class) = window.class() {
|
// located.
|
||||||
if class != "OleMainThreadWndClass"
|
//
|
||||||
&& self.focused_monitor_idx() != monitor_idx
|
// This check ensures that we only update the focused monitor when the window
|
||||||
{
|
// triggering monitor reconciliation is known to not be tied to a specific monitor.
|
||||||
self.focus_monitor(monitor_idx)?;
|
if window.class()? != "OleMainThreadWndClass"
|
||||||
}
|
&& self.focused_monitor_idx() != monitor_idx
|
||||||
}
|
{
|
||||||
|
self.focus_monitor(monitor_idx)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for monitor in self.monitors_mut() {
|
let offset = self.work_area_offset;
|
||||||
for workspace in monitor.workspaces_mut() {
|
|
||||||
if let WindowManagerEvent::FocusChange(_, window) = event {
|
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||||
let _ = workspace.focus_changed(window.hwnd);
|
let work_area = *monitor.work_area_size();
|
||||||
|
let offset = if monitor.work_area_offset().is_some() {
|
||||||
|
monitor.work_area_offset()
|
||||||
|
} else {
|
||||||
|
offset
|
||||||
|
};
|
||||||
|
|
||||||
|
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||||
|
let reaped_orphans = workspace.reap_orphans()?;
|
||||||
|
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
||||||
|
workspace.update(&work_area, offset)?;
|
||||||
|
tracing::info!(
|
||||||
|
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||||
|
reaped_orphans.0,
|
||||||
|
reaped_orphans.1,
|
||||||
|
i,
|
||||||
|
j
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,14 +151,12 @@ impl WindowManager {
|
|||||||
self.has_pending_raise_op = false;
|
self.has_pending_raise_op = false;
|
||||||
}
|
}
|
||||||
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
|
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
|
||||||
if self.focused_workspace()?.contains_window(window.hwnd) {
|
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
self.update_focused_workspace(false)?;
|
||||||
self.update_focused_workspace(false, false)?;
|
|
||||||
|
|
||||||
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
|
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
|
||||||
|
|
||||||
already_moved_window_handles.remove(&window.hwnd);
|
already_moved_window_handles.remove(&window.hwnd);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
WindowManagerEvent::Minimize(_, window) => {
|
WindowManagerEvent::Minimize(_, window) => {
|
||||||
let mut hide = false;
|
let mut hide = false;
|
||||||
@@ -165,7 +170,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
if hide {
|
if hide {
|
||||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowManagerEvent::Hide(_, window) => {
|
WindowManagerEvent::Hide(_, window) => {
|
||||||
@@ -194,8 +199,7 @@ impl WindowManager {
|
|||||||
path,
|
path,
|
||||||
&tray_and_multi_window_identifiers,
|
&tray_and_multi_window_identifiers,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
)
|
);
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if !window.is_window()
|
if !window.is_window()
|
||||||
|| should_act
|
|| should_act
|
||||||
@@ -207,7 +211,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
if hide {
|
if hide {
|
||||||
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
|
let mut already_moved_window_handles = self.already_moved_window_handles.lock();
|
||||||
@@ -215,8 +219,6 @@ impl WindowManager {
|
|||||||
already_moved_window_handles.remove(&window.hwnd);
|
already_moved_window_handles.remove(&window.hwnd);
|
||||||
}
|
}
|
||||||
WindowManagerEvent::FocusChange(_, window) => {
|
WindowManagerEvent::FocusChange(_, window) => {
|
||||||
self.update_focused_workspace(self.mouse_follows_focus, false)?;
|
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
if !workspace
|
if !workspace
|
||||||
.floating_windows()
|
.floating_windows()
|
||||||
@@ -239,57 +241,35 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowManagerEvent::Show(_, window)
|
WindowManagerEvent::Show(_, window) | WindowManagerEvent::Manage(window) => {
|
||||||
| WindowManagerEvent::Manage(window)
|
let mut switch_to = None;
|
||||||
| 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)?;
|
|
||||||
|
|
||||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
|
||||||
|
|
||||||
let mut needs_reconciliation = false;
|
|
||||||
|
|
||||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||||
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
||||||
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
|
if workspace.contains_window(window.hwnd) {
|
||||||
// At this point we know we are going to send a notification to the workspace reconciliator
|
switch_to = Some((i, j));
|
||||||
// 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::event_tx().send(
|
|
||||||
workspace_reconciliator::Notification {
|
|
||||||
monitor_idx: i,
|
|
||||||
workspace_idx: j,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
needs_reconciliation = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((known_monitor_idx, known_workspace_idx)) = switch_to {
|
||||||
|
if self.focused_monitor_idx() != known_monitor_idx
|
||||||
|
|| self
|
||||||
|
.focused_monitor()
|
||||||
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||||
|
.focused_workspace_idx()
|
||||||
|
!= known_workspace_idx
|
||||||
|
{
|
||||||
|
self.focus_monitor(known_monitor_idx)?;
|
||||||
|
self.focus_workspace(known_workspace_idx)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// There are some applications such as Firefox where, if they are focused when a
|
// 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
|
// 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
|
// 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
|
// 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.
|
// duplicates across multiple workspaces, as it results in ghost layout tiles.
|
||||||
let mut proceed = true;
|
|
||||||
|
|
||||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||||
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
||||||
if workspace.container_for_window(window.hwnd).is_some()
|
if workspace.container_for_window(window.hwnd).is_some()
|
||||||
@@ -301,45 +281,26 @@ impl WindowManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
window.hide();
|
window.hide();
|
||||||
proceed = false;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if proceed {
|
let behaviour = self.window_container_behaviour;
|
||||||
let behaviour = self.window_container_behaviour;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
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 {
|
if !workspace.contains_window(window.hwnd) {
|
||||||
match behaviour {
|
match behaviour {
|
||||||
WindowContainerBehaviour::Create => {
|
WindowContainerBehaviour::Create => {
|
||||||
workspace.new_container_for_window(window);
|
workspace.new_container_for_window(*window);
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(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)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
WindowContainerBehaviour::Append => {
|
||||||
|
workspace
|
||||||
if workspace_contains_window {
|
.focused_container_mut()
|
||||||
let mut monocle_window_event = false;
|
.ok_or_else(|| anyhow!("there is no focused container"))?
|
||||||
if let Some(ref monocle) = monocle_container {
|
.add_window(*window);
|
||||||
if let Some(monocle_window) = monocle.focused_window() {
|
self.update_focused_workspace(true)?;
|
||||||
if monocle_window.hwnd == window.hwnd {
|
|
||||||
monocle_window_event = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !monocle_window_event && monocle_container.is_some() {
|
|
||||||
window.hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,53 +339,38 @@ impl WindowManager {
|
|||||||
let new_window_behaviour = self.window_container_behaviour;
|
let new_window_behaviour = self.window_container_behaviour;
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
|
if !workspace
|
||||||
|
.floating_windows()
|
||||||
|
.iter()
|
||||||
|
.any(|w| w.hwnd == window.hwnd)
|
||||||
|
{
|
||||||
|
let focused_container_idx = workspace.focused_container_idx();
|
||||||
|
|
||||||
let focused_container_idx = workspace.focused_container_idx();
|
let new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||||
let new_position = WindowsApi::window_rect(window.hwnd())?;
|
|
||||||
let old_position = *workspace
|
|
||||||
.latest_layout()
|
|
||||||
.get(focused_container_idx)
|
|
||||||
// If the move was to another monitor with an empty workspace, the
|
|
||||||
// workspace here will refer to that empty workspace, which won't
|
|
||||||
// have any latest layout set. We fall back to a Default for Rect
|
|
||||||
// which allows us to make a reasonable guess that the drag has taken
|
|
||||||
// place across a monitor boundary to an empty workspace
|
|
||||||
.unwrap_or(&Rect::default());
|
|
||||||
|
|
||||||
// This will be true if we have moved to an empty workspace on another monitor
|
let old_position = *workspace
|
||||||
let mut moved_across_monitors = old_position == Rect::default();
|
.latest_layout()
|
||||||
if let Some((origin_monitor_idx, origin_workspace_idx, _)) = pending {
|
.get(focused_container_idx)
|
||||||
// If we didn't move to another monitor with an empty workspace, it is
|
// If the move was to another monitor with an empty workspace, the
|
||||||
// still possible that we moved to another monitor with a populated workspace
|
// workspace here will refer to that empty workspace, which won't
|
||||||
if !moved_across_monitors {
|
// have any latest layout set. We fall back to a Default for Rect
|
||||||
// So we'll check if the origin monitor index and the target monitor index
|
// which allows us to make a reasonable guess that the drag has taken
|
||||||
// are different, if they are, we can set the override
|
// place across a monitor boundary to an empty workspace
|
||||||
moved_across_monitors = origin_monitor_idx != target_monitor_idx;
|
.unwrap_or(&Rect::default());
|
||||||
|
|
||||||
if moved_across_monitors {
|
// This will be true if we have moved to an empty workspace on another monitor
|
||||||
// Want to make sure that we exclude unmanaged windows from cross-monitor
|
let mut moved_across_monitors = old_position == Rect::default();
|
||||||
// moves with a mouse, otherwise the currently focused idx container will
|
|
||||||
// be moved when we just want to drag an unmanaged window
|
|
||||||
let origin_workspace = self
|
|
||||||
.monitors()
|
|
||||||
.get(origin_monitor_idx)
|
|
||||||
.ok_or_else(|| anyhow!("cannot get monitor idx"))?
|
|
||||||
.workspaces()
|
|
||||||
.get(origin_workspace_idx)
|
|
||||||
.ok_or_else(|| anyhow!("cannot get workspace idx"))?;
|
|
||||||
|
|
||||||
let managed_window =
|
if let Some((origin_monitor_idx, _, _)) = pending {
|
||||||
origin_workspace.contains_managed_window(window.hwnd);
|
// If we didn't move to another monitor with an empty workspace, it is
|
||||||
|
// still possible that we moved to another monitor with a populated workspace
|
||||||
if !managed_window {
|
if !moved_across_monitors {
|
||||||
moved_across_monitors = false;
|
// So we'll check if the origin monitor index and the target monitor index
|
||||||
}
|
// are different, if they are, we can set the override
|
||||||
|
moved_across_monitors = origin_monitor_idx != target_monitor_idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
|
||||||
if workspace.contains_managed_window(window.hwnd) || moved_across_monitors {
|
|
||||||
let resize = Rect {
|
let resize = Rect {
|
||||||
left: new_position.left - old_position.left,
|
left: new_position.left - old_position.left,
|
||||||
top: new_position.top - old_position.top,
|
top: new_position.top - old_position.top,
|
||||||
@@ -434,7 +380,10 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If we have moved across the monitors, use that override, otherwise determine
|
// If we have moved across the monitors, use that override, otherwise determine
|
||||||
// if a move has taken place by ruling out a resize
|
// if a move has taken place by ruling out a resize
|
||||||
let right_bottom_constant = 0;
|
let right_bottom_constant = ((BORDER_WIDTH.load(Ordering::SeqCst)
|
||||||
|
+ BORDER_OFFSET.load(Ordering::SeqCst))
|
||||||
|
* 2)
|
||||||
|
.abs();
|
||||||
|
|
||||||
let is_move = moved_across_monitors
|
let is_move = moved_across_monitors
|
||||||
|| resize.right.abs() == right_bottom_constant
|
|| resize.right.abs() == right_bottom_constant
|
||||||
@@ -486,11 +435,11 @@ impl WindowManager {
|
|||||||
// the origin monitor's focused workspace
|
// the origin monitor's focused workspace
|
||||||
self.focus_monitor(origin_monitor_idx)?;
|
self.focus_monitor(origin_monitor_idx)?;
|
||||||
self.focus_workspace(origin_workspace_idx)?;
|
self.focus_workspace(origin_workspace_idx)?;
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
|
|
||||||
self.focus_monitor(target_monitor_idx)?;
|
self.focus_monitor(target_monitor_idx)?;
|
||||||
self.focus_workspace(target_workspace_idx)?;
|
self.focus_workspace(target_workspace_idx)?;
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
// Here we handle a simple move on the same monitor which is treated as
|
// Here we handle a simple move on the same monitor which is treated as
|
||||||
// a container swap
|
// a container swap
|
||||||
@@ -501,12 +450,11 @@ impl WindowManager {
|
|||||||
Some(target_idx) => {
|
Some(target_idx) => {
|
||||||
workspace
|
workspace
|
||||||
.swap_containers(focused_container_idx, target_idx);
|
.swap_containers(focused_container_idx, target_idx);
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.update_focused_workspace(
|
self.update_focused_workspace(
|
||||||
self.mouse_follows_focus,
|
self.mouse_follows_focus,
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,12 +463,11 @@ impl WindowManager {
|
|||||||
match workspace.container_idx_from_current_point() {
|
match workspace.container_idx_from_current_point() {
|
||||||
Some(target_idx) => {
|
Some(target_idx) => {
|
||||||
workspace.move_window_to_container(target_idx)?;
|
workspace.move_window_to_container(target_idx)?;
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.update_focused_workspace(
|
self.update_focused_workspace(
|
||||||
self.mouse_follows_focus,
|
self.mouse_follows_focus,
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,20 +514,119 @@ impl WindowManager {
|
|||||||
self.resize_window(edge, sizing, delta, true)?;
|
self.resize_window(edge, sizing, delta, true)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowManagerEvent::MouseCapture(..)
|
WindowManagerEvent::ForceUpdate(_) => {
|
||||||
|
self.update_focused_workspace(false)?;
|
||||||
|
}
|
||||||
|
WindowManagerEvent::DisplayChange(..)
|
||||||
|
| WindowManagerEvent::MouseCapture(..)
|
||||||
| WindowManagerEvent::Cloak(..)
|
| WindowManagerEvent::Cloak(..)
|
||||||
| WindowManagerEvent::TitleUpdate(..) => {}
|
| WindowManagerEvent::Uncloak(..) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !self.focused_workspace()?.tile() {
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
border.hide()?;
|
||||||
|
BORDER_HIDDEN.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
|
match event {
|
||||||
|
WindowManagerEvent::MoveResizeStart(_, _) => {
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
border.hide()?;
|
||||||
|
BORDER_HIDDEN.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
WindowManagerEvent::MoveResizeEnd(_, window)
|
||||||
|
| WindowManagerEvent::Show(_, window)
|
||||||
|
| WindowManagerEvent::FocusChange(_, window)
|
||||||
|
| WindowManagerEvent::Hide(_, window)
|
||||||
|
| WindowManagerEvent::Uncloak(_, window)
|
||||||
|
| WindowManagerEvent::Minimize(_, window) => {
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
let mut target_window = None;
|
||||||
|
let mut target_window_is_monocle = false;
|
||||||
|
if self
|
||||||
|
.focused_workspace()?
|
||||||
|
.floating_windows()
|
||||||
|
.iter()
|
||||||
|
.any(|w| w.hwnd == window.hwnd)
|
||||||
|
{
|
||||||
|
target_window = Option::from(*window);
|
||||||
|
WindowsApi::raise_window(border.hwnd())?;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(monocle_container) = self.focused_workspace()?.monocle_container() {
|
||||||
|
if let Some(window) = monocle_container.focused_window() {
|
||||||
|
target_window = Option::from(*window);
|
||||||
|
target_window_is_monocle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_window.is_none() {
|
||||||
|
match self.focused_container() {
|
||||||
|
// if there is no focused container, the desktop is empty
|
||||||
|
Err(..) => {
|
||||||
|
WindowsApi::hide_border_window(border.hwnd())?;
|
||||||
|
}
|
||||||
|
Ok(container) => {
|
||||||
|
if !(matches!(event, WindowManagerEvent::Minimize(_, _))
|
||||||
|
&& container.windows().len() == 1)
|
||||||
|
{
|
||||||
|
let container_size = self.focused_container()?.windows().len();
|
||||||
|
target_window = Option::from(*self.focused_window()?);
|
||||||
|
|
||||||
|
if target_window_is_monocle {
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
} else if container_size > 1 {
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
BORDER_COLOUR_STACK.load(Ordering::SeqCst),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target_window) = target_window {
|
||||||
|
let activate = BORDER_HIDDEN.load(Ordering::SeqCst);
|
||||||
|
|
||||||
|
WindowsApi::invalidate_border_rect()?;
|
||||||
|
border.set_position(target_window, activate)?;
|
||||||
|
|
||||||
|
if activate {
|
||||||
|
BORDER_HIDDEN.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
|
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
|
||||||
if let WindowManagerEvent::Unmanage(window) = event {
|
if let WindowManagerEvent::Unmanage(window) = event {
|
||||||
window.center(&self.focused_monitor_work_area()?)?;
|
window.center(&self.focused_monitor_work_area()?)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are no more windows on the workspace, we shouldn't show the border window
|
||||||
|
if self.focused_workspace()?.containers().is_empty() {
|
||||||
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
|
border.hide()?;
|
||||||
|
BORDER_HIDDEN.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
tracing::trace!("updating list of known hwnds");
|
tracing::trace!("updating list of known hwnds");
|
||||||
let mut known_hwnds = vec![];
|
let mut known_hwnds = vec![];
|
||||||
for monitor in self.monitors() {
|
for monitor in self.monitors() {
|
||||||
@@ -601,26 +647,12 @@ impl WindowManager {
|
|||||||
.open(hwnd_json)?;
|
.open(hwnd_json)?;
|
||||||
|
|
||||||
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
||||||
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
let notification = Notification {
|
event: NotificationEvent::WindowManager(*event),
|
||||||
event: NotificationEvent::WindowManager(event),
|
|
||||||
state: self.as_ref().into(),
|
state: self.as_ref().into(),
|
||||||
};
|
})?)?;
|
||||||
|
|
||||||
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
|
||||||
stackbar_manager::event_tx().send(stackbar_manager::Notification)?;
|
|
||||||
|
|
||||||
// Too many spammy OBJECT_NAMECHANGE events from JetBrains IDEs
|
|
||||||
if !matches!(
|
|
||||||
event,
|
|
||||||
WindowManagerEvent::Show(WinEvent::ObjectNameChange, _)
|
|
||||||
) {
|
|
||||||
tracing::info!("processed: {}", event.window().to_string());
|
|
||||||
} else {
|
|
||||||
tracing::trace!("processed: {}", event.window().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
tracing::info!("processed: {}", event.window().to_string());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
||||||
|
|
||||||
use crate::border_manager;
|
|
||||||
use crate::WindowManager;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
|
|
||||||
std::thread::spawn(move || loop {
|
|
||||||
match find_orphans(wm.clone()) {
|
|
||||||
Ok(()) => {
|
|
||||||
tracing::warn!("restarting finished thread");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
tracing::error!("restarting failed thread: {:?}", error)
|
|
||||||
} else {
|
|
||||||
tracing::error!("restarting failed thread: {}", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
|
||||||
tracing::info!("watching");
|
|
||||||
|
|
||||||
let arc = wm.clone();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
|
||||||
|
|
||||||
let mut wm = arc.lock();
|
|
||||||
let offset = wm.work_area_offset;
|
|
||||||
|
|
||||||
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
|
||||||
let work_area = *monitor.work_area_size();
|
|
||||||
let window_based_work_area_offset = (
|
|
||||||
monitor.window_based_work_area_offset_limit(),
|
|
||||||
monitor.window_based_work_area_offset(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let offset = if monitor.work_area_offset().is_some() {
|
|
||||||
monitor.work_area_offset()
|
|
||||||
} else {
|
|
||||||
offset
|
|
||||||
};
|
|
||||||
|
|
||||||
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
|
||||||
let reaped_orphans = workspace.reap_orphans()?;
|
|
||||||
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
|
||||||
workspace.update(&work_area, offset, window_based_work_area_offset)?;
|
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
|
||||||
tracing::info!(
|
|
||||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
|
||||||
reaped_orphans.0,
|
|
||||||
reaped_orphans.1,
|
|
||||||
i,
|
|
||||||
j
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
294
komorebi/src/stackbar.rs
Normal file
294
komorebi/src/stackbar.rs
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::Win32::Foundation::COLORREF;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::Foundation::LPARAM;
|
||||||
|
use windows::Win32::Foundation::LRESULT;
|
||||||
|
use windows::Win32::Foundation::WPARAM;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreateFontIndirectW;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreatePen;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
||||||
|
use windows::Win32::Graphics::Gdi::DrawTextW;
|
||||||
|
use windows::Win32::Graphics::Gdi::GetDC;
|
||||||
|
use windows::Win32::Graphics::Gdi::ReleaseDC;
|
||||||
|
use windows::Win32::Graphics::Gdi::SelectObject;
|
||||||
|
use windows::Win32::Graphics::Gdi::SetBkColor;
|
||||||
|
use windows::Win32::Graphics::Gdi::SetTextColor;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_CENTER;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_VCENTER;
|
||||||
|
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
|
||||||
|
use windows::Win32::Graphics::Gdi::FW_BOLD;
|
||||||
|
use windows::Win32::Graphics::Gdi::LOGFONTW;
|
||||||
|
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
|
||||||
|
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SW_SHOW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
|
||||||
|
|
||||||
|
use komorebi_core::Rect;
|
||||||
|
|
||||||
|
use crate::window::Window;
|
||||||
|
use crate::windows_api::WindowsApi;
|
||||||
|
use crate::winevent::WinEvent;
|
||||||
|
use crate::winevent_listener;
|
||||||
|
use crate::WindowManagerEvent;
|
||||||
|
use crate::DEFAULT_CONTAINER_PADDING;
|
||||||
|
use crate::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||||
|
use crate::STACKBAR_TAB_HEIGHT;
|
||||||
|
use crate::STACKBAR_TAB_WIDTH;
|
||||||
|
use crate::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
|
use crate::WINDOWS_BY_BAR_HWNDS;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct Stackbar {
|
||||||
|
pub(crate) hwnd: isize,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub is_cloned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Stackbar {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.is_cloned {
|
||||||
|
let _ = WindowsApi::close_window(self.hwnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Stackbar {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
hwnd: self.hwnd,
|
||||||
|
is_cloned: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Stackbar {
|
||||||
|
unsafe extern "system" fn window_proc(
|
||||||
|
hwnd: HWND,
|
||||||
|
msg: u32,
|
||||||
|
w_param: WPARAM,
|
||||||
|
l_param: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
match msg {
|
||||||
|
WM_LBUTTONDOWN => {
|
||||||
|
let win_hwnds_by_topbar = WINDOWS_BY_BAR_HWNDS.lock();
|
||||||
|
if let Some(win_hwnds) = win_hwnds_by_topbar.get(&hwnd.0) {
|
||||||
|
let x = l_param.0 as i32 & 0xFFFF;
|
||||||
|
let y = (l_param.0 as i32 >> 16) & 0xFFFF;
|
||||||
|
|
||||||
|
let width = STACKBAR_TAB_WIDTH.load(Ordering::SeqCst);
|
||||||
|
let height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
|
||||||
|
let gap = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
|
||||||
|
|
||||||
|
for (index, win_hwnd) in win_hwnds.iter().enumerate() {
|
||||||
|
let left = gap + (index as i32 * (width + gap));
|
||||||
|
let right = left + width;
|
||||||
|
let top = 0;
|
||||||
|
let bottom = height;
|
||||||
|
|
||||||
|
if x >= left && x <= right && y >= top && y <= bottom {
|
||||||
|
let window = Window { hwnd: *win_hwnd };
|
||||||
|
let event_sender = winevent_listener::event_tx();
|
||||||
|
let _ = event_sender.send(WindowManagerEvent::FocusChange(
|
||||||
|
WinEvent::ObjectFocus,
|
||||||
|
window,
|
||||||
|
));
|
||||||
|
let _ = event_sender.send(WindowManagerEvent::ForceUpdate(window));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WINDOWS_BY_BAR_HWNDS.force_unlock();
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
WM_DESTROY => {
|
||||||
|
PostQuitMessage(0);
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
_ => DefWindowProcW(hwnd, msg, w_param, l_param),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn hwnd(&self) -> HWND {
|
||||||
|
HWND(self.hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create() -> Result<Stackbar> {
|
||||||
|
let name: Vec<u16> = "komorebi_stackbar\0".encode_utf16().collect();
|
||||||
|
let class_name = PCWSTR(name.as_ptr());
|
||||||
|
|
||||||
|
let h_module = WindowsApi::module_handle_w()?;
|
||||||
|
|
||||||
|
let wnd_class = WNDCLASSW {
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
|
lpfnWndProc: Some(Self::window_proc),
|
||||||
|
hInstance: h_module.into(),
|
||||||
|
lpszClassName: class_name,
|
||||||
|
hbrBackground: WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
RegisterClassW(&wnd_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (hwnd_sender, hwnd_receiver) = crossbeam_channel::bounded::<HWND>(1);
|
||||||
|
|
||||||
|
let name_cl = name.clone();
|
||||||
|
std::thread::spawn(move || -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let hwnd = CreateWindowExW(
|
||||||
|
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
|
||||||
|
PCWSTR(name_cl.as_ptr()),
|
||||||
|
PCWSTR(name_cl.as_ptr()),
|
||||||
|
WS_POPUP | WS_VISIBLE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
h_module,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY)?;
|
||||||
|
hwnd_sender.send(hwnd)?;
|
||||||
|
|
||||||
|
let mut msg = MSG::default();
|
||||||
|
while GetMessageW(&mut msg, hwnd, 0, 0).into() {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageW(&msg);
|
||||||
|
std::thread::sleep(Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
hwnd: hwnd_receiver.recv()?.0,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_position(&self, layout: &Rect, top: bool) -> Result<()> {
|
||||||
|
WindowsApi::position_window(self.hwnd(), layout, top)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_position_from_container_layout(&self, layout: &Rect) -> Rect {
|
||||||
|
Rect {
|
||||||
|
bottom: STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst),
|
||||||
|
..*layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self, windows: &VecDeque<Window>, focused_hwnd: isize) -> Result<()> {
|
||||||
|
let width = STACKBAR_TAB_WIDTH.load(Ordering::SeqCst);
|
||||||
|
let height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
|
||||||
|
let gap = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
|
||||||
|
let background = STACKBAR_TAB_BACKGROUND_COLOUR.load(Ordering::SeqCst);
|
||||||
|
let focused_text_colour = STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst);
|
||||||
|
let unfocused_text_colour = STACKBAR_UNFOCUSED_TEXT_COLOUR.load(Ordering::SeqCst);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let hdc = GetDC(self.hwnd());
|
||||||
|
|
||||||
|
let hpen = CreatePen(PS_SOLID, 0, COLORREF(background));
|
||||||
|
let hbrush = CreateSolidBrush(COLORREF(background));
|
||||||
|
|
||||||
|
SelectObject(hdc, hpen);
|
||||||
|
SelectObject(hdc, hbrush);
|
||||||
|
SetBkColor(hdc, COLORREF(background));
|
||||||
|
|
||||||
|
let hfont = CreateFontIndirectW(&LOGFONTW {
|
||||||
|
lfWeight: FW_BOLD.0 as i32,
|
||||||
|
lfQuality: FONT_QUALITY(PROOF_QUALITY.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
SelectObject(hdc, hfont);
|
||||||
|
|
||||||
|
for (i, window) in windows.iter().enumerate() {
|
||||||
|
if window.hwnd == focused_hwnd {
|
||||||
|
SetTextColor(hdc, COLORREF(focused_text_colour));
|
||||||
|
|
||||||
|
window.focus(false)?;
|
||||||
|
} else {
|
||||||
|
SetTextColor(hdc, COLORREF(unfocused_text_colour));
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = gap + (i as i32 * (width + gap));
|
||||||
|
let mut tab_box = Rect {
|
||||||
|
top: 0,
|
||||||
|
left,
|
||||||
|
right: left + width,
|
||||||
|
bottom: height,
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowsApi::round_rect(hdc, &tab_box, 8);
|
||||||
|
|
||||||
|
let exe = window.exe()?;
|
||||||
|
let exe_trimmed = exe.trim_end_matches(".exe");
|
||||||
|
let mut tab_title: Vec<u16> = exe_trimmed.encode_utf16().collect();
|
||||||
|
|
||||||
|
tab_box.left_padding(10);
|
||||||
|
tab_box.right_padding(10);
|
||||||
|
|
||||||
|
DrawTextW(
|
||||||
|
hdc,
|
||||||
|
&mut tab_title,
|
||||||
|
&mut tab_box.into(),
|
||||||
|
DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseDC(self.hwnd(), hdc);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut windows_hwdns: VecDeque<isize> = VecDeque::new();
|
||||||
|
for window in windows {
|
||||||
|
windows_hwdns.push_back(window.hwnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
WINDOWS_BY_BAR_HWNDS.lock().insert(self.hwnd, windows_hwdns);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(&self) {
|
||||||
|
WindowsApi::hide_window(self.hwnd())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(&self) {
|
||||||
|
WindowsApi::show_window(self.hwnd(), SW_SHOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
mod stackbar;
|
|
||||||
|
|
||||||
use crate::container::Container;
|
|
||||||
use crate::stackbar_manager::stackbar::Stackbar;
|
|
||||||
use crate::WindowManager;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
use crate::DEFAULT_CONTAINER_PADDING;
|
|
||||||
use crossbeam_channel::Receiver;
|
|
||||||
use crossbeam_channel::Sender;
|
|
||||||
use crossbeam_utils::atomic::AtomicCell;
|
|
||||||
use crossbeam_utils::atomic::AtomicConsume;
|
|
||||||
use komorebi_core::StackbarLabel;
|
|
||||||
use komorebi_core::StackbarMode;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::atomic::AtomicI32;
|
|
||||||
use std::sync::atomic::AtomicU32;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
|
|
||||||
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
|
|
||||||
pub static STACKBAR_UNFOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(11776947); // gray text
|
|
||||||
pub static STACKBAR_TAB_BACKGROUND_COLOUR: AtomicU32 = AtomicU32::new(3355443); // gray
|
|
||||||
pub static STACKBAR_TAB_HEIGHT: AtomicI32 = AtomicI32::new(40);
|
|
||||||
pub static STACKBAR_TAB_WIDTH: AtomicI32 = AtomicI32::new(200);
|
|
||||||
pub static STACKBAR_LABEL: AtomicCell<StackbarLabel> = AtomicCell::new(StackbarLabel::Process);
|
|
||||||
pub static STACKBAR_MODE: AtomicCell<StackbarMode> = AtomicCell::new(StackbarMode::OnStack);
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref STACKBAR_STATE: Mutex<HashMap<String, Stackbar>> = Mutex::new(HashMap::new());
|
|
||||||
static ref STACKBARS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
|
|
||||||
static ref STACKBARS_CONTAINERS: Mutex<HashMap<isize, Container>> = Mutex::new(HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Notification;
|
|
||||||
|
|
||||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
|
||||||
CHANNEL.get_or_init(crossbeam_channel::unbounded)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_tx() -> Sender<Notification> {
|
|
||||||
channel().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_rx() -> Receiver<Notification> {
|
|
||||||
channel().1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_have_stackbar(window_count: usize) -> bool {
|
|
||||||
match STACKBAR_MODE.load() {
|
|
||||||
StackbarMode::Always => true,
|
|
||||||
StackbarMode::OnStack => window_count > 1,
|
|
||||||
StackbarMode::Never => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
|
||||||
std::thread::spawn(move || loop {
|
|
||||||
match handle_notifications(wm.clone()) {
|
|
||||||
Ok(()) => {
|
|
||||||
tracing::warn!("restarting finished thread");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
tracing::warn!("restarting failed thread: {}", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
|
||||||
tracing::info!("listening");
|
|
||||||
|
|
||||||
let receiver = event_rx();
|
|
||||||
|
|
||||||
'receiver: for _ in receiver {
|
|
||||||
let mut stackbars = STACKBAR_STATE.lock();
|
|
||||||
let mut stackbars_monitors = STACKBARS_MONITORS.lock();
|
|
||||||
|
|
||||||
// Check the wm state every time we receive a notification
|
|
||||||
let mut state = wm.lock();
|
|
||||||
|
|
||||||
// If stackbars are disabled
|
|
||||||
if matches!(STACKBAR_MODE.load(), StackbarMode::Never) {
|
|
||||||
for (_, stackbar) in stackbars.iter() {
|
|
||||||
stackbar.destroy()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
stackbars.clear();
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (monitor_idx, m) in state.monitors_mut().iter_mut().enumerate() {
|
|
||||||
// Only operate on the focused workspace of each monitor
|
|
||||||
if let Some(ws) = m.focused_workspace_mut() {
|
|
||||||
// Workspaces with tiling disabled don't have stackbars
|
|
||||||
if !ws.tile() {
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, border) in stackbars.iter() {
|
|
||||||
if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
|
||||||
border.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
stackbars.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
|
||||||
WindowsApi::foreground_window().unwrap_or_default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Handle the monocle container separately
|
|
||||||
if ws.monocle_container().is_some() || is_maximized {
|
|
||||||
// Destroy any stackbars associated with the focused workspace
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, stackbar) in stackbars.iter() {
|
|
||||||
if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
|
||||||
stackbar.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
stackbars.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy any stackbars not associated with the focused workspace
|
|
||||||
let container_ids = ws
|
|
||||||
.containers()
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.id().clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, stackbar) in stackbars.iter() {
|
|
||||||
if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
|
||||||
&& !container_ids.contains(id)
|
|
||||||
{
|
|
||||||
stackbar.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
stackbars.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let container_padding = ws
|
|
||||||
.container_padding()
|
|
||||||
.unwrap_or_else(|| DEFAULT_CONTAINER_PADDING.load_consume());
|
|
||||||
|
|
||||||
'containers: for container in ws.containers_mut() {
|
|
||||||
let should_add_stackbar = match STACKBAR_MODE.load() {
|
|
||||||
StackbarMode::Always => true,
|
|
||||||
StackbarMode::OnStack => container.windows().len() > 1,
|
|
||||||
StackbarMode::Never => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !should_add_stackbar {
|
|
||||||
if let Some(stackbar) = stackbars.get(container.id()) {
|
|
||||||
stackbar.destroy()?
|
|
||||||
}
|
|
||||||
|
|
||||||
stackbars.remove(container.id());
|
|
||||||
stackbars_monitors.remove(container.id());
|
|
||||||
continue 'containers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the stackbar entry for this container from the map or create one
|
|
||||||
let stackbar = match stackbars.entry(container.id().clone()) {
|
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
if let Ok(stackbar) = Stackbar::create(container.id()) {
|
|
||||||
entry.insert(stackbar)
|
|
||||||
} else {
|
|
||||||
continue 'receiver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
stackbars_monitors.insert(container.id().clone(), monitor_idx);
|
|
||||||
|
|
||||||
let rect = WindowsApi::window_rect(
|
|
||||||
container
|
|
||||||
.focused_window()
|
|
||||||
.copied()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.hwnd(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
stackbar.update(container_padding, container, &rect)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
use crate::border_manager::BORDER_OFFSET;
|
|
||||||
use crate::border_manager::BORDER_WIDTH;
|
|
||||||
use crate::border_manager::STYLE;
|
|
||||||
use crate::container::Container;
|
|
||||||
use crate::stackbar_manager::STACKBARS_CONTAINERS;
|
|
||||||
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::stackbar_manager::STACKBAR_LABEL;
|
|
||||||
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::WindowsApi;
|
|
||||||
use crate::DEFAULT_CONTAINER_PADDING;
|
|
||||||
use crate::WINDOWS_11;
|
|
||||||
use crossbeam_utils::atomic::AtomicConsume;
|
|
||||||
use komorebi_core::BorderStyle;
|
|
||||||
use komorebi_core::Rect;
|
|
||||||
use komorebi_core::StackbarLabel;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use windows::core::PCWSTR;
|
|
||||||
use windows::Win32::Foundation::COLORREF;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
use windows::Win32::Foundation::LPARAM;
|
|
||||||
use windows::Win32::Foundation::LRESULT;
|
|
||||||
use windows::Win32::Foundation::WPARAM;
|
|
||||||
use windows::Win32::Graphics::Gdi::CreateFontIndirectW;
|
|
||||||
use windows::Win32::Graphics::Gdi::CreatePen;
|
|
||||||
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
|
||||||
use windows::Win32::Graphics::Gdi::DrawTextW;
|
|
||||||
use windows::Win32::Graphics::Gdi::GetDC;
|
|
||||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
|
||||||
use windows::Win32::Graphics::Gdi::ReleaseDC;
|
|
||||||
use windows::Win32::Graphics::Gdi::RoundRect;
|
|
||||||
use windows::Win32::Graphics::Gdi::SelectObject;
|
|
||||||
use windows::Win32::Graphics::Gdi::SetBkColor;
|
|
||||||
use windows::Win32::Graphics::Gdi::SetTextColor;
|
|
||||||
use windows::Win32::Graphics::Gdi::DT_CENTER;
|
|
||||||
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
|
|
||||||
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
|
|
||||||
use windows::Win32::Graphics::Gdi::DT_VCENTER;
|
|
||||||
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
|
|
||||||
use windows::Win32::Graphics::Gdi::FW_BOLD;
|
|
||||||
use windows::Win32::Graphics::Gdi::LOGFONTW;
|
|
||||||
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
|
|
||||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Stackbar {
|
|
||||||
pub hwnd: isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<isize> for Stackbar {
|
|
||||||
fn from(value: isize) -> Self {
|
|
||||||
Self { hwnd: value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stackbar {
|
|
||||||
pub const fn hwnd(&self) -> HWND {
|
|
||||||
HWND(self.hwnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create(id: &str) -> color_eyre::Result<Self> {
|
|
||||||
let name: Vec<u16> = format!("komostackbar-{id}\0").encode_utf16().collect();
|
|
||||||
let class_name = PCWSTR(name.as_ptr());
|
|
||||||
|
|
||||||
let h_module = WindowsApi::module_handle_w()?;
|
|
||||||
|
|
||||||
let window_class = WNDCLASSW {
|
|
||||||
style: CS_HREDRAW | CS_VREDRAW,
|
|
||||||
lpfnWndProc: Some(Self::callback),
|
|
||||||
hInstance: h_module.into(),
|
|
||||||
lpszClassName: class_name,
|
|
||||||
hbrBackground: WindowsApi::create_solid_brush(0),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = WindowsApi::register_class_w(&window_class);
|
|
||||||
|
|
||||||
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
let name_cl = name.clone();
|
|
||||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
|
||||||
unsafe {
|
|
||||||
let hwnd = CreateWindowExW(
|
|
||||||
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
|
|
||||||
PCWSTR(name_cl.as_ptr()),
|
|
||||||
PCWSTR(name_cl.as_ptr()),
|
|
||||||
WS_POPUP | WS_VISIBLE,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
h_module,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?;
|
|
||||||
hwnd_sender.send(hwnd)?;
|
|
||||||
|
|
||||||
let mut msg = MSG::default();
|
|
||||||
while GetMessageW(&mut msg, hwnd, 0, 0).into() {
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessageW(&msg);
|
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
hwnd: hwnd_receiver.recv()?.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(&self) -> color_eyre::Result<()> {
|
|
||||||
WindowsApi::close_window(self.hwnd())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(
|
|
||||||
&self,
|
|
||||||
container_padding: i32,
|
|
||||||
container: &mut Container,
|
|
||||||
layout: &Rect,
|
|
||||||
) -> color_eyre::Result<()> {
|
|
||||||
let width = STACKBAR_TAB_WIDTH.load_consume();
|
|
||||||
let height = STACKBAR_TAB_HEIGHT.load_consume();
|
|
||||||
let gap = DEFAULT_CONTAINER_PADDING.load_consume();
|
|
||||||
let background = STACKBAR_TAB_BACKGROUND_COLOUR.load_consume();
|
|
||||||
let focused_text_colour = STACKBAR_FOCUSED_TEXT_COLOUR.load_consume();
|
|
||||||
let unfocused_text_colour = STACKBAR_UNFOCUSED_TEXT_COLOUR.load_consume();
|
|
||||||
|
|
||||||
let mut stackbars_containers = STACKBARS_CONTAINERS.lock();
|
|
||||||
stackbars_containers.insert(self.hwnd, container.clone());
|
|
||||||
|
|
||||||
let mut layout = *layout;
|
|
||||||
let workspace_specific_offset =
|
|
||||||
BORDER_WIDTH.load_consume() + BORDER_OFFSET.load_consume() + container_padding;
|
|
||||||
|
|
||||||
layout.top -= workspace_specific_offset + STACKBAR_TAB_HEIGHT.load_consume();
|
|
||||||
layout.left -= workspace_specific_offset;
|
|
||||||
|
|
||||||
WindowsApi::position_window(self.hwnd(), &layout, false)?;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let hdc = GetDC(self.hwnd());
|
|
||||||
|
|
||||||
let hpen = CreatePen(PS_SOLID, 0, COLORREF(background));
|
|
||||||
let hbrush = CreateSolidBrush(COLORREF(background));
|
|
||||||
|
|
||||||
SelectObject(hdc, hpen);
|
|
||||||
SelectObject(hdc, hbrush);
|
|
||||||
SetBkColor(hdc, COLORREF(background));
|
|
||||||
|
|
||||||
let hfont = CreateFontIndirectW(&LOGFONTW {
|
|
||||||
lfWeight: FW_BOLD.0 as i32,
|
|
||||||
lfQuality: FONT_QUALITY(PROOF_QUALITY.0),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
SelectObject(hdc, hfont);
|
|
||||||
|
|
||||||
for (i, window) in container.windows().iter().enumerate() {
|
|
||||||
if window.hwnd == container.focused_window().copied().unwrap_or_default().hwnd {
|
|
||||||
SetTextColor(hdc, COLORREF(focused_text_colour));
|
|
||||||
} else {
|
|
||||||
SetTextColor(hdc, COLORREF(unfocused_text_colour));
|
|
||||||
}
|
|
||||||
|
|
||||||
let left = gap + (i as i32 * (width + gap));
|
|
||||||
let mut rect = Rect {
|
|
||||||
top: 0,
|
|
||||||
left,
|
|
||||||
right: left + width,
|
|
||||||
bottom: height,
|
|
||||||
};
|
|
||||||
|
|
||||||
match *STYLE.lock() {
|
|
||||||
BorderStyle::System => {
|
|
||||||
if *WINDOWS_11 {
|
|
||||||
RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, 20, 20);
|
|
||||||
} else {
|
|
||||||
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BorderStyle::Rounded => {
|
|
||||||
RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, 20, 20);
|
|
||||||
}
|
|
||||||
BorderStyle::Square => {
|
|
||||||
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let label = match STACKBAR_LABEL.load() {
|
|
||||||
StackbarLabel::Process => {
|
|
||||||
let exe = window.exe()?;
|
|
||||||
exe.trim_end_matches(".exe").to_string()
|
|
||||||
}
|
|
||||||
StackbarLabel::Title => window.title()?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tab_title: Vec<u16> = label.encode_utf16().collect();
|
|
||||||
|
|
||||||
rect.left_padding(10);
|
|
||||||
rect.right_padding(10);
|
|
||||||
|
|
||||||
DrawTextW(
|
|
||||||
hdc,
|
|
||||||
&mut tab_title,
|
|
||||||
&mut rect.into(),
|
|
||||||
DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReleaseDC(self.hwnd(), hdc);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_position_from_container_layout(&self, layout: &Rect) -> Rect {
|
|
||||||
Rect {
|
|
||||||
bottom: STACKBAR_TAB_HEIGHT.load_consume(),
|
|
||||||
..*layout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "system" fn callback(
|
|
||||||
hwnd: HWND,
|
|
||||||
msg: u32,
|
|
||||||
w_param: WPARAM,
|
|
||||||
l_param: LPARAM,
|
|
||||||
) -> LRESULT {
|
|
||||||
unsafe {
|
|
||||||
match msg {
|
|
||||||
WM_LBUTTONDOWN => {
|
|
||||||
let stackbars_containers = STACKBARS_CONTAINERS.lock();
|
|
||||||
if let Some(container) = stackbars_containers.get(&hwnd.0) {
|
|
||||||
let x = l_param.0 as i32 & 0xFFFF;
|
|
||||||
let y = (l_param.0 as i32 >> 16) & 0xFFFF;
|
|
||||||
|
|
||||||
let width = STACKBAR_TAB_WIDTH.load_consume();
|
|
||||||
let height = STACKBAR_TAB_HEIGHT.load_consume();
|
|
||||||
let gap = DEFAULT_CONTAINER_PADDING.load_consume();
|
|
||||||
|
|
||||||
let focused_window_idx = container.focused_window_idx();
|
|
||||||
let focused_window_rect = WindowsApi::window_rect(
|
|
||||||
container
|
|
||||||
.focused_window()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.hwnd(),
|
|
||||||
)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
for (index, window) in container.windows().iter().enumerate() {
|
|
||||||
let left = gap + (index as i32 * (width + gap));
|
|
||||||
let right = left + width;
|
|
||||||
let top = 0;
|
|
||||||
let bottom = height;
|
|
||||||
|
|
||||||
if x >= left && x <= right && y >= top && y <= bottom {
|
|
||||||
// If we are focusing a window that isn't currently focused in the
|
|
||||||
// stackbar, make sure we update its location so that it doesn't render
|
|
||||||
// on top of other tiles before eventually ending up in the correct
|
|
||||||
// tile
|
|
||||||
if index != focused_window_idx {
|
|
||||||
if let Err(err) =
|
|
||||||
window.set_position(&focused_window_rect, false)
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
|
|
||||||
*window,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the window corresponding to the tab we have clicked
|
|
||||||
window.restore();
|
|
||||||
if let Err(err) = window.focus(false) {
|
|
||||||
tracing::error!(
|
|
||||||
"stackbar WMLBUTTONDOWN focus error: hwnd {} ({})",
|
|
||||||
*window,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Hide any windows in the stack that don't correspond to the window
|
|
||||||
// we have clicked
|
|
||||||
window.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
WM_DESTROY => {
|
|
||||||
PostQuitMessage(0);
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
_ => DefWindowProcW(hwnd, msg, w_param, l_param),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
use crate::border_manager;
|
use crate::border::Border;
|
||||||
use crate::border_manager::ZOrder;
|
|
||||||
use crate::border_manager::STYLE;
|
|
||||||
use crate::border_manager::Z_ORDER;
|
|
||||||
use crate::colour::Colour;
|
use crate::colour::Colour;
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::monitor::Monitor;
|
use crate::monitor::Monitor;
|
||||||
use crate::monitor_reconciliator;
|
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::stackbar_manager::STACKBAR_LABEL;
|
|
||||||
use crate::stackbar_manager::STACKBAR_MODE;
|
|
||||||
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
|
||||||
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
|
||||||
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
|
|
||||||
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
|
use crate::BORDER_COLOUR_CURRENT;
|
||||||
|
use crate::BORDER_COLOUR_MONOCLE;
|
||||||
|
use crate::BORDER_COLOUR_SINGLE;
|
||||||
|
use crate::BORDER_COLOUR_STACK;
|
||||||
|
use crate::BORDER_ENABLED;
|
||||||
|
use crate::BORDER_HWND;
|
||||||
|
use crate::BORDER_OFFSET;
|
||||||
|
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||||
|
use crate::BORDER_WIDTH;
|
||||||
use crate::DATA_DIR;
|
use crate::DATA_DIR;
|
||||||
use crate::DEFAULT_CONTAINER_PADDING;
|
use crate::DEFAULT_CONTAINER_PADDING;
|
||||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||||
@@ -29,10 +27,14 @@ use crate::MANAGE_IDENTIFIERS;
|
|||||||
use crate::MONITOR_INDEX_PREFERENCES;
|
use crate::MONITOR_INDEX_PREFERENCES;
|
||||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||||
use crate::REGEX_IDENTIFIERS;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
|
use crate::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::STACKBAR_MODE;
|
||||||
|
use crate::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||||
|
use crate::STACKBAR_TAB_HEIGHT;
|
||||||
|
use crate::STACKBAR_TAB_WIDTH;
|
||||||
|
use crate::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||||
use crate::WORKSPACE_RULES;
|
use crate::WORKSPACE_RULES;
|
||||||
use komorebi_core::StackbarLabel;
|
|
||||||
use komorebi_core::StackbarMode;
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
@@ -46,7 +48,6 @@ use komorebi_core::config_generation::MatchingRule;
|
|||||||
use komorebi_core::config_generation::MatchingStrategy;
|
use komorebi_core::config_generation::MatchingStrategy;
|
||||||
use komorebi_core::resolve_home_path;
|
use komorebi_core::resolve_home_path;
|
||||||
use komorebi_core::ApplicationIdentifier;
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi_core::BorderStyle;
|
|
||||||
use komorebi_core::DefaultLayout;
|
use komorebi_core::DefaultLayout;
|
||||||
use komorebi_core::FocusFollowsMouseImplementation;
|
use komorebi_core::FocusFollowsMouseImplementation;
|
||||||
use komorebi_core::HidingBehaviour;
|
use komorebi_core::HidingBehaviour;
|
||||||
@@ -72,18 +73,16 @@ use uds_windows::UnixListener;
|
|||||||
use uds_windows::UnixStream;
|
use uds_windows::UnixStream;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct BorderColours {
|
pub struct ActiveWindowBorderColours {
|
||||||
/// Border colour when the container contains a single window
|
/// Border colour when the container contains a single window
|
||||||
pub single: Option<Colour>,
|
pub single: Colour,
|
||||||
/// Border colour when the container contains multiple windows
|
/// Border colour when the container contains multiple windows
|
||||||
pub stack: Option<Colour>,
|
pub stack: Colour,
|
||||||
/// Border colour when the container is in monocle mode
|
/// Border colour when the container is in monocle mode
|
||||||
pub monocle: Option<Colour>,
|
pub monocle: Colour,
|
||||||
/// Border colour when the container is unfocused
|
|
||||||
pub unfocused: Option<Colour>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct WorkspaceConfig {
|
pub struct WorkspaceConfig {
|
||||||
/// Name
|
/// Name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -197,19 +196,13 @@ impl From<&Workspace> for WorkspaceConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct MonitorConfig {
|
pub struct MonitorConfig {
|
||||||
/// Workspace configurations
|
/// Workspace configurations
|
||||||
pub workspaces: Vec<WorkspaceConfig>,
|
pub workspaces: Vec<WorkspaceConfig>,
|
||||||
/// Monitor-specific work area offset (default: None)
|
/// Monitor-specific work area offset (default: None)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub work_area_offset: Option<Rect>,
|
pub work_area_offset: Option<Rect>,
|
||||||
/// Window based work area offset (default: None)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub window_based_work_area_offset: Option<Rect>,
|
|
||||||
/// Open window limit after which the window based work area offset will no longer be applied (default: 1)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub window_based_work_area_offset_limit: Option<isize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Monitor> for MonitorConfig {
|
impl From<&Monitor> for MonitorConfig {
|
||||||
@@ -222,14 +215,12 @@ impl From<&Monitor> for MonitorConfig {
|
|||||||
Self {
|
Self {
|
||||||
workspaces,
|
workspaces,
|
||||||
work_area_offset: value.work_area_offset(),
|
work_area_offset: value.work_area_offset(),
|
||||||
window_based_work_area_offset: value.window_based_work_area_offset(),
|
|
||||||
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
/// The `komorebi.json` static configuration file reference for `v0.1.26`
|
/// The `komorebi.json` static configuration file reference for `v0.1.20`
|
||||||
pub struct StaticConfig {
|
pub struct StaticConfig {
|
||||||
/// DEPRECATED from v0.1.22: no longer required
|
/// DEPRECATED from v0.1.22: no longer required
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -265,19 +256,10 @@ pub struct StaticConfig {
|
|||||||
pub border_offset: Option<i32>,
|
pub border_offset: Option<i32>,
|
||||||
/// Display an active window border (default: false)
|
/// Display an active window border (default: false)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(alias = "active_window_border")]
|
pub active_window_border: Option<bool>,
|
||||||
pub border: Option<bool>,
|
|
||||||
/// Active window border colours for different container types
|
/// Active window border colours for different container types
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(alias = "active_window_border_colours")]
|
pub active_window_border_colours: Option<ActiveWindowBorderColours>,
|
||||||
pub border_colours: Option<BorderColours>,
|
|
||||||
/// Active window border style (default: System)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[serde(alias = "active_window_border_style")]
|
|
||||||
pub border_style: Option<BorderStyle>,
|
|
||||||
/// Active window border z-order (default: System)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub border_z_order: Option<ZOrder>,
|
|
||||||
/// Global default workspace padding (default: 10)
|
/// Global default workspace padding (default: 10)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub default_workspace_padding: Option<i32>,
|
pub default_workspace_padding: Option<i32>,
|
||||||
@@ -317,74 +299,30 @@ pub struct StaticConfig {
|
|||||||
/// Set display index preferences
|
/// Set display index preferences
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub display_index_preferences: Option<HashMap<usize, String>>,
|
pub display_index_preferences: Option<HashMap<usize, String>>,
|
||||||
/// Stackbar configuration options
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub stackbar: Option<StackbarConfig>,
|
pub stackbar: Option<StackbarConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticConfig {
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
pub fn aliases(raw: &str) {
|
pub enum StackbarMode {
|
||||||
let mut map = HashMap::new();
|
Always,
|
||||||
map.insert("border", ["active_window_border"]);
|
Never,
|
||||||
map.insert("border_width", ["active_window_border_width"]);
|
OnStack,
|
||||||
map.insert("border_offset", ["active_window_border_offset"]);
|
|
||||||
map.insert("border_colours", ["active_window_border_colours"]);
|
|
||||||
map.insert("border_style", ["active_window_border_style"]);
|
|
||||||
|
|
||||||
let mut display = false;
|
|
||||||
|
|
||||||
for (_, aliases) in &map {
|
|
||||||
for a in aliases {
|
|
||||||
if raw.contains(a) {
|
|
||||||
display = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if display {
|
|
||||||
println!("\nYour configuration file contains some options that have been renamed or deprecated:\n");
|
|
||||||
for (canonical, aliases) in map {
|
|
||||||
for alias in aliases {
|
|
||||||
if raw.contains(alias) {
|
|
||||||
println!(r#""{alias}" is now "{canonical}""#);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deprecated(raw: &str) {
|
|
||||||
let deprecated = ["invisible_borders"];
|
|
||||||
|
|
||||||
for option in deprecated {
|
|
||||||
if raw.contains(option) {
|
|
||||||
println!(r#""{option}" is deprecated and can be removed"#);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TabsConfig {
|
pub struct TabsConfig {
|
||||||
/// Width of a stackbar tab
|
|
||||||
width: Option<i32>,
|
width: Option<i32>,
|
||||||
/// Focused tab text colour
|
|
||||||
focused_text: Option<Colour>,
|
focused_text: Option<Colour>,
|
||||||
/// Unfocused tab text colour
|
|
||||||
unfocused_text: Option<Colour>,
|
unfocused_text: Option<Colour>,
|
||||||
/// Tab background colour
|
|
||||||
background: Option<Colour>,
|
background: Option<Colour>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct StackbarConfig {
|
pub struct StackbarConfig {
|
||||||
/// Stackbar height
|
height: Option<i32>,
|
||||||
pub height: Option<i32>,
|
mode: Option<StackbarMode>,
|
||||||
/// Stackbar height
|
tabs: Option<TabsConfig>,
|
||||||
pub label: Option<StackbarLabel>,
|
|
||||||
/// Stackbar mode
|
|
||||||
pub mode: Option<StackbarMode>,
|
|
||||||
/// Stackbar tab configuration options
|
|
||||||
pub tabs: Option<TabsConfig>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&WindowManager> for StaticConfig {
|
impl From<&WindowManager> for StaticConfig {
|
||||||
@@ -440,16 +378,21 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 {
|
let border_colours = if BORDER_COLOUR_SINGLE.load(Ordering::SeqCst) == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Option::from(BorderColours {
|
Option::from(ActiveWindowBorderColours {
|
||||||
single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))),
|
single: Colour::from(BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)),
|
||||||
stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))),
|
stack: Colour::from(if BORDER_COLOUR_STACK.load(Ordering::SeqCst) == 0 {
|
||||||
monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))),
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
|
||||||
unfocused: Option::from(Colour::from(
|
} else {
|
||||||
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
BORDER_COLOUR_STACK.load(Ordering::SeqCst)
|
||||||
)),
|
}),
|
||||||
|
monocle: Colour::from(if BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst) == 0 {
|
||||||
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
|
||||||
|
} else {
|
||||||
|
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -464,12 +407,10 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
focus_follows_mouse: value.focus_follows_mouse,
|
focus_follows_mouse: value.focus_follows_mouse,
|
||||||
mouse_follows_focus: Option::from(value.mouse_follows_focus),
|
mouse_follows_focus: Option::from(value.mouse_follows_focus),
|
||||||
app_specific_configuration_path: None,
|
app_specific_configuration_path: None,
|
||||||
border_width: Option::from(border_manager::BORDER_WIDTH.load(Ordering::SeqCst)),
|
border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)),
|
||||||
border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)),
|
border_offset: Option::from(BORDER_OFFSET.load(Ordering::SeqCst)),
|
||||||
border: Option::from(border_manager::BORDER_ENABLED.load(Ordering::SeqCst)),
|
active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)),
|
||||||
border_colours,
|
active_window_border_colours: border_colours,
|
||||||
border_style: Option::from(*STYLE.lock()),
|
|
||||||
border_z_order: Option::from(*Z_ORDER.lock()),
|
|
||||||
default_workspace_padding: Option::from(
|
default_workspace_padding: Option::from(
|
||||||
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
||||||
),
|
),
|
||||||
@@ -518,38 +459,29 @@ impl StaticConfig {
|
|||||||
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst);
|
self.border_width.map_or_else(
|
||||||
border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
|| {
|
||||||
|
BORDER_WIDTH.store(8, Ordering::SeqCst);
|
||||||
|
},
|
||||||
|
|width| {
|
||||||
|
BORDER_WIDTH.store(width, Ordering::SeqCst);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(enabled) = &self.border {
|
BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
||||||
border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst);
|
|
||||||
|
if let Some(colours) = &self.active_window_border_colours {
|
||||||
|
BORDER_COLOUR_SINGLE.store(u32::from(colours.single), Ordering::SeqCst);
|
||||||
|
BORDER_COLOUR_CURRENT.store(u32::from(colours.single), Ordering::SeqCst);
|
||||||
|
BORDER_COLOUR_STACK.store(u32::from(colours.stack), Ordering::SeqCst);
|
||||||
|
BORDER_COLOUR_MONOCLE.store(u32::from(colours.monocle), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(colours) = &self.border_colours {
|
|
||||||
if let Some(single) = colours.single {
|
|
||||||
border_manager::FOCUSED.store(u32::from(single), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stack) = colours.stack {
|
|
||||||
border_manager::STACK.store(u32::from(stack), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(monocle) = colours.monocle {
|
|
||||||
border_manager::MONOCLE.store(u32::from(monocle), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(unfocused) = colours.unfocused {
|
|
||||||
border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let border_style = self.border_style.unwrap_or_default();
|
|
||||||
*STYLE.lock() = border_style;
|
|
||||||
|
|
||||||
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||||
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
|
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
|
||||||
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||||
let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
||||||
|
let mut border_overflow_identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||||
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
||||||
let mut layered_identifiers = LAYERED_WHITELIST.lock();
|
let mut layered_identifiers = LAYERED_WHITELIST.lock();
|
||||||
|
|
||||||
@@ -573,6 +505,14 @@ impl StaticConfig {
|
|||||||
populate_rules(rules, &mut layered_identifiers, &mut regex_identifiers)?;
|
populate_rules(rules, &mut layered_identifiers, &mut regex_identifiers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(rules) = &mut self.border_overflow_applications {
|
||||||
|
populate_rules(
|
||||||
|
rules,
|
||||||
|
&mut border_overflow_identifiers,
|
||||||
|
&mut regex_identifiers,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(rules) = &mut self.tray_and_multi_window_applications {
|
if let Some(rules) = &mut self.tray_and_multi_window_applications {
|
||||||
populate_rules(
|
populate_rules(
|
||||||
rules,
|
rules,
|
||||||
@@ -585,15 +525,10 @@ impl StaticConfig {
|
|||||||
if let Some(height) = &stackbar.height {
|
if let Some(height) = &stackbar.height {
|
||||||
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
|
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(label) = &stackbar.label {
|
|
||||||
STACKBAR_LABEL.store(*label);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mode) = &stackbar.mode {
|
if let Some(mode) = &stackbar.mode {
|
||||||
STACKBAR_MODE.store(*mode);
|
let mut stackbar_mode = STACKBAR_MODE.lock();
|
||||||
|
*stackbar_mode = *mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tabs) = &stackbar.tabs {
|
if let Some(tabs) = &stackbar.tabs {
|
||||||
if let Some(background) = &tabs.background {
|
if let Some(background) = &tabs.background {
|
||||||
STACKBAR_TAB_BACKGROUND_COLOUR.store((*background).into(), Ordering::SeqCst);
|
STACKBAR_TAB_BACKGROUND_COLOUR.store((*background).into(), Ordering::SeqCst);
|
||||||
@@ -641,6 +576,13 @@ impl StaticConfig {
|
|||||||
&mut regex_identifiers,
|
&mut regex_identifiers,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
ApplicationOptions::BorderOverflow => {
|
||||||
|
populate_option(
|
||||||
|
&mut entry,
|
||||||
|
&mut border_overflow_identifiers,
|
||||||
|
&mut regex_identifiers,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
ApplicationOptions::TrayAndMultiWindow => {
|
ApplicationOptions::TrayAndMultiWindow => {
|
||||||
populate_option(
|
populate_option(
|
||||||
&mut entry,
|
&mut entry,
|
||||||
@@ -655,7 +597,6 @@ impl StaticConfig {
|
|||||||
&mut regex_identifiers,
|
&mut regex_identifiers,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
ApplicationOptions::BorderOverflow => {} // deprecated
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -691,6 +632,7 @@ impl StaticConfig {
|
|||||||
|
|
||||||
let mut wm = WindowManager {
|
let mut wm = WindowManager {
|
||||||
monitors: Ring::default(),
|
monitors: Ring::default(),
|
||||||
|
monitor_cache: HashMap::new(),
|
||||||
incoming_events: incoming,
|
incoming_events: incoming,
|
||||||
command_listener: listener,
|
command_listener: listener,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
@@ -748,20 +690,9 @@ impl StaticConfig {
|
|||||||
|
|
||||||
if let Some(monitors) = value.monitors {
|
if let Some(monitors) = value.monitors {
|
||||||
for (i, monitor) in monitors.iter().enumerate() {
|
for (i, monitor) in monitors.iter().enumerate() {
|
||||||
{
|
|
||||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
|
||||||
if let Some(device_id) = display_index_preferences.get(&i) {
|
|
||||||
monitor_reconciliator::insert_in_monitor_cache(device_id, monitor.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
||||||
m.ensure_workspace_count(monitor.workspaces.len());
|
m.ensure_workspace_count(monitor.workspaces.len());
|
||||||
m.set_work_area_offset(monitor.work_area_offset);
|
m.set_work_area_offset(monitor.work_area_offset);
|
||||||
m.set_window_based_work_area_offset(monitor.window_based_work_area_offset);
|
|
||||||
m.set_window_based_work_area_offset_limit(
|
|
||||||
monitor.window_based_work_area_offset_limit.unwrap_or(1),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
||||||
ws.load_static_config(
|
ws.load_static_config(
|
||||||
@@ -789,8 +720,13 @@ impl StaticConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.border == Some(true) {
|
if value.active_window_border == Some(true) {
|
||||||
border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst);
|
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
|
||||||
|
Border::create("komorebi-border-window")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
BORDER_ENABLED.store(true, Ordering::SeqCst);
|
||||||
|
wm.show_border()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -807,10 +743,6 @@ impl StaticConfig {
|
|||||||
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
||||||
m.ensure_workspace_count(monitor.workspaces.len());
|
m.ensure_workspace_count(monitor.workspaces.len());
|
||||||
m.set_work_area_offset(monitor.work_area_offset);
|
m.set_work_area_offset(monitor.work_area_offset);
|
||||||
m.set_window_based_work_area_offset(monitor.window_based_work_area_offset);
|
|
||||||
m.set_window_based_work_area_offset_limit(
|
|
||||||
monitor.window_based_work_area_offset_limit.unwrap_or(1),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
||||||
ws.load_static_config(
|
ws.load_static_config(
|
||||||
@@ -838,8 +770,16 @@ impl StaticConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(enabled) = value.border {
|
if value.active_window_border == Some(true) {
|
||||||
border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst);
|
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
|
||||||
|
Border::create("komorebi-border-window")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
BORDER_ENABLED.store(true, Ordering::SeqCst);
|
||||||
|
wm.show_border()?;
|
||||||
|
} else {
|
||||||
|
BORDER_ENABLED.store(false, Ordering::SeqCst);
|
||||||
|
wm.hide_border()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(val) = value.window_container_behaviour {
|
if let Some(val) = value.window_container_behaviour {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
|
use windows::Win32::UI::WindowsAndMessaging::WS_BORDER;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
|
use windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
|
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
|
||||||
@@ -58,7 +56,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;
|
|||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
|
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Default)]
|
||||||
pub struct WindowStyle: u32 {
|
pub struct WindowStyle: u32 {
|
||||||
const BORDER = WS_BORDER.0;
|
const BORDER = WS_BORDER.0;
|
||||||
const CAPTION = WS_CAPTION.0;
|
const CAPTION = WS_CAPTION.0;
|
||||||
@@ -92,7 +90,7 @@ bitflags! {
|
|||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
|
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Default)]
|
||||||
pub struct ExtendedWindowStyle: u32 {
|
pub struct ExtendedWindowStyle: u32 {
|
||||||
const ACCEPTFILES = WS_EX_ACCEPTFILES.0;
|
const ACCEPTFILES = WS_EX_ACCEPTFILES.0;
|
||||||
const APPWINDOW = WS_EX_APPWINDOW.0;
|
const APPWINDOW = WS_EX_APPWINDOW.0;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use komorebi_core::config_generation::MatchingRule;
|
|||||||
use komorebi_core::config_generation::MatchingStrategy;
|
use komorebi_core::config_generation::MatchingStrategy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use serde::ser::Error;
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -38,9 +39,9 @@ use crate::PERMAIGNORE_CLASSES;
|
|||||||
use crate::REGEX_IDENTIFIERS;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
use crate::WSL2_UI_PROCESSES;
|
use crate::WSL2_UI_PROCESSES;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub hwnd: isize,
|
pub(crate) hwnd: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
@@ -96,23 +97,24 @@ impl Serialize for Window {
|
|||||||
"title",
|
"title",
|
||||||
&self
|
&self
|
||||||
.title()
|
.title()
|
||||||
.unwrap_or_else(|_| String::from("could not get window title")),
|
.map_err(|_| S::Error::custom("could not get window title"))?,
|
||||||
)?;
|
)?;
|
||||||
state.serialize_field(
|
state.serialize_field(
|
||||||
"exe",
|
"exe",
|
||||||
&self
|
&self
|
||||||
.exe()
|
.exe()
|
||||||
.unwrap_or_else(|_| String::from("could not get window exe")),
|
.map_err(|_| S::Error::custom("could not get window exe"))?,
|
||||||
)?;
|
)?;
|
||||||
state.serialize_field(
|
state.serialize_field(
|
||||||
"class",
|
"class",
|
||||||
&self
|
&self
|
||||||
.class()
|
.class()
|
||||||
.unwrap_or_else(|_| String::from("could not get window class")),
|
.map_err(|_| S::Error::custom("could not get window class"))?,
|
||||||
)?;
|
)?;
|
||||||
state.serialize_field(
|
state.serialize_field(
|
||||||
"rect",
|
"rect",
|
||||||
&WindowsApi::window_rect(self.hwnd()).unwrap_or_default(),
|
&WindowsApi::window_rect(self.hwnd())
|
||||||
|
.map_err(|_| S::Error::custom("could not get window rect"))?,
|
||||||
)?;
|
)?;
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,7 @@ impl Window {
|
|||||||
HWND(self.hwnd)
|
HWND(self.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center(&self, work_area: &Rect) -> Result<()> {
|
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
|
||||||
let half_width = work_area.right / 2;
|
let half_width = work_area.right / 2;
|
||||||
let half_weight = work_area.bottom / 2;
|
let half_weight = work_area.bottom / 2;
|
||||||
|
|
||||||
@@ -138,11 +140,7 @@ impl Window {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_position(&self, layout: &Rect, top: bool) -> Result<()> {
|
pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
|
||||||
if WindowsApi::window_rect(self.hwnd())?.eq(layout) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let rect = *layout;
|
let rect = *layout;
|
||||||
WindowsApi::position_window(self.hwnd(), &rect, top)
|
WindowsApi::position_window(self.hwnd(), &rect, top)
|
||||||
}
|
}
|
||||||
@@ -155,10 +153,6 @@ impl Window {
|
|||||||
WindowsApi::is_iconic(self.hwnd())
|
WindowsApi::is_iconic(self.hwnd())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_visible(self) -> bool {
|
|
||||||
WindowsApi::is_window_visible(self.hwnd())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hide(self) {
|
pub fn hide(self) {
|
||||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||||
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
|
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
|
||||||
@@ -224,25 +218,77 @@ impl Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
|
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
|
||||||
// If the target window is already focused, do nothing.
|
// Attach komorebi thread to Window thread
|
||||||
if let Ok(ihwnd) = WindowsApi::foreground_window() {
|
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||||
if HWND(ihwnd) == self.hwnd() {
|
let current_thread_id = WindowsApi::current_thread_id();
|
||||||
// Center cursor in Window
|
|
||||||
if mouse_follows_focus {
|
|
||||||
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
|
||||||
|
// hook has been installed
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
|
||||||
|
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(
|
||||||
|
"could not attach to window thread input processing mechanism, but continuing execution of focus(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
WindowsApi::raise_and_focus_window(self.hwnd())?;
|
// Raise Window to foreground
|
||||||
|
let mut foregrounded = false;
|
||||||
|
let mut tried_resetting_foreground_access = false;
|
||||||
|
let mut max_attempts = 10;
|
||||||
|
|
||||||
|
while !foregrounded && max_attempts > 0 {
|
||||||
|
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||||
|
Ok(()) => {
|
||||||
|
foregrounded = true;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
max_attempts -= 1;
|
||||||
|
tracing::error!(
|
||||||
|
"could not set as foreground window, but continuing execution of focus(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this still doesn't work then maybe try https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-locksetforegroundwindow
|
||||||
|
if !tried_resetting_foreground_access {
|
||||||
|
let process_id = WindowsApi::current_process_id();
|
||||||
|
if WindowsApi::allow_set_foreground_window(process_id).is_ok() {
|
||||||
|
tried_resetting_foreground_access = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Center cursor in Window
|
// Center cursor in Window
|
||||||
if mouse_follows_focus {
|
if mouse_follows_focus {
|
||||||
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This isn't really needed when the above command works as expected via AHK
|
||||||
|
match WindowsApi::set_focus(self.hwnd()) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(
|
||||||
|
"could not set focus, but continuing execution of focus(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, false) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(
|
||||||
|
"could not detach from window thread input processing mechanism, but continuing execution of focus(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,27 +370,18 @@ impl Window {
|
|||||||
self.update_style(&style)
|
self.update_style(&style)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(fields(exe, title), skip(debug))]
|
#[tracing::instrument(fields(exe, title))]
|
||||||
pub fn should_manage(
|
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
|
||||||
self,
|
if let Some(WindowManagerEvent::DisplayChange(_)) = event {
|
||||||
event: Option<WindowManagerEvent>,
|
return Ok(true);
|
||||||
debug: &mut RuleDebug,
|
|
||||||
) -> Result<bool> {
|
|
||||||
if !self.is_window() {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.is_window = true;
|
#[allow(clippy::question_mark)]
|
||||||
|
|
||||||
if self.title().is_err() {
|
if self.title().is_err() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.has_title = true;
|
let is_cloaked = self.is_cloaked()?;
|
||||||
|
|
||||||
let is_cloaked = self.is_cloaked().unwrap_or_default();
|
|
||||||
|
|
||||||
debug.is_cloaked = is_cloaked;
|
|
||||||
|
|
||||||
let mut allow_cloaked = false;
|
let mut allow_cloaked = false;
|
||||||
|
|
||||||
@@ -357,28 +394,13 @@ impl Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.allow_cloaked = allow_cloaked;
|
|
||||||
|
|
||||||
match (allow_cloaked, is_cloaked) {
|
match (allow_cloaked, is_cloaked) {
|
||||||
// If allowing cloaked windows, we don't need to check the cloaked status
|
// If allowing cloaked windows, we don't need to check the cloaked status
|
||||||
(true, _) |
|
(true, _) |
|
||||||
// If not allowing cloaked windows, we need to ensure the window is not cloaked
|
// If not allowing cloaked windows, we need to ensure the window is not cloaked
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = (self.title(), self.exe(), self.class(), self.path()) {
|
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = (self.title(), self.exe(), self.class(), self.path()) {
|
||||||
debug.title = Some(title.clone());
|
return Ok(window_is_eligible(&title, &exe_name, &class, &path, &self.style()?, &self.ex_style()?, event));
|
||||||
debug.exe_name = Some(exe_name.clone());
|
|
||||||
debug.class = Some(class.clone());
|
|
||||||
debug.path = Some(path.clone());
|
|
||||||
// calls for styles can fail quite often for events with windows that aren't really "windows"
|
|
||||||
// since we have moved up calls of should_manage to the beginning of the process_event handler,
|
|
||||||
// we should handle failures here gracefully to be able to continue the execution of process_event
|
|
||||||
if let (Ok(style), Ok(ex_style)) = (&self.style(), &self.ex_style()) {
|
|
||||||
debug.window_style = Some(*style);
|
|
||||||
debug.extended_window_style = Some(*ex_style);
|
|
||||||
let eligible = window_is_eligible(&title, &exe_name, &class, &path, style, ex_style, event, debug);
|
|
||||||
debug.should_manage = eligible;
|
|
||||||
return Ok(eligible);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -388,28 +410,6 @@ impl Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
||||||
pub struct RuleDebug {
|
|
||||||
pub should_manage: bool,
|
|
||||||
pub is_window: bool,
|
|
||||||
pub has_title: bool,
|
|
||||||
pub is_cloaked: bool,
|
|
||||||
pub allow_cloaked: bool,
|
|
||||||
pub window_style: Option<WindowStyle>,
|
|
||||||
pub extended_window_style: Option<ExtendedWindowStyle>,
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub exe_name: Option<String>,
|
|
||||||
pub class: Option<String>,
|
|
||||||
pub path: Option<String>,
|
|
||||||
pub matches_permaignore_class: Option<String>,
|
|
||||||
pub matches_float_identifier: Option<MatchingRule>,
|
|
||||||
pub matches_managed_override: Option<MatchingRule>,
|
|
||||||
pub matches_layered_whitelist: Option<MatchingRule>,
|
|
||||||
pub matches_wsl2_gui: Option<String>,
|
|
||||||
pub matches_no_titlebar: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn window_is_eligible(
|
fn window_is_eligible(
|
||||||
title: &String,
|
title: &String,
|
||||||
exe_name: &String,
|
exe_name: &String,
|
||||||
@@ -418,12 +418,10 @@ fn window_is_eligible(
|
|||||||
style: &WindowStyle,
|
style: &WindowStyle,
|
||||||
ex_style: &ExtendedWindowStyle,
|
ex_style: &ExtendedWindowStyle,
|
||||||
event: Option<WindowManagerEvent>,
|
event: Option<WindowManagerEvent>,
|
||||||
debug: &mut RuleDebug,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
{
|
{
|
||||||
let permaignore_classes = PERMAIGNORE_CLASSES.lock();
|
let permaignore_classes = PERMAIGNORE_CLASSES.lock();
|
||||||
if permaignore_classes.contains(class) {
|
if permaignore_classes.contains(class) {
|
||||||
debug.matches_permaignore_class = Some(class.clone());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,65 +429,45 @@ fn window_is_eligible(
|
|||||||
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
||||||
|
|
||||||
let float_identifiers = FLOAT_IDENTIFIERS.lock();
|
let float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||||
let should_float = if let Some(rule) = should_act(
|
let should_float = should_act(
|
||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
path,
|
||||||
&float_identifiers,
|
&float_identifiers,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
) {
|
);
|
||||||
debug.matches_float_identifier = Some(rule);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||||
let managed_override = if let Some(rule) = should_act(
|
let managed_override = should_act(
|
||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
path,
|
||||||
&manage_identifiers,
|
&manage_identifiers,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
) {
|
);
|
||||||
debug.matches_managed_override = Some(rule);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_float && !managed_override {
|
if should_float && !managed_override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layered_whitelist = LAYERED_WHITELIST.lock();
|
let layered_whitelist = LAYERED_WHITELIST.lock();
|
||||||
let allow_layered = if let Some(rule) = should_act(
|
let allow_layered = should_act(
|
||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
path,
|
||||||
&layered_whitelist,
|
&layered_whitelist,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
) {
|
);
|
||||||
debug.matches_layered_whitelist = Some(rule);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: might need this for transparency
|
// TODO: might need this for transparency
|
||||||
// let allow_layered = true;
|
// let allow_layered = true;
|
||||||
|
|
||||||
let allow_wsl2_gui = {
|
let allow_wsl2_gui = {
|
||||||
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
|
let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();
|
||||||
let allow = wsl2_ui_processes.contains(exe_name);
|
wsl2_ui_processes.contains(exe_name)
|
||||||
if allow {
|
|
||||||
debug.matches_wsl2_gui = Some(exe_name.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
allow
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let allow_titlebar_removed = {
|
let allow_titlebar_removed = {
|
||||||
@@ -531,10 +509,10 @@ pub fn should_act(
|
|||||||
path: &str,
|
path: &str,
|
||||||
identifiers: &[MatchingRule],
|
identifiers: &[MatchingRule],
|
||||||
regex_identifiers: &HashMap<String, Regex>,
|
regex_identifiers: &HashMap<String, Regex>,
|
||||||
) -> Option<MatchingRule> {
|
) -> bool {
|
||||||
let mut matching_rule = None;
|
let mut should_act = false;
|
||||||
for rule in identifiers {
|
for identifier in identifiers {
|
||||||
match rule {
|
match identifier {
|
||||||
MatchingRule::Simple(identifier) => {
|
MatchingRule::Simple(identifier) => {
|
||||||
if should_act_individual(
|
if should_act_individual(
|
||||||
title,
|
title,
|
||||||
@@ -544,7 +522,7 @@ pub fn should_act(
|
|||||||
identifier,
|
identifier,
|
||||||
regex_identifiers,
|
regex_identifiers,
|
||||||
) {
|
) {
|
||||||
matching_rule = Some(rule.clone());
|
should_act = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
MatchingRule::Composite(identifiers) => {
|
MatchingRule::Composite(identifiers) => {
|
||||||
@@ -561,13 +539,13 @@ pub fn should_act(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if composite_results.iter().all(|&x| x) {
|
if composite_results.iter().all(|&x| x) {
|
||||||
matching_rule = Some(rule.clone());
|
should_act = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matching_rule
|
should_act
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_act_individual(
|
pub fn should_act_individual(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,8 @@ pub enum WindowManagerEvent {
|
|||||||
Manage(Window),
|
Manage(Window),
|
||||||
Unmanage(Window),
|
Unmanage(Window),
|
||||||
Raise(Window),
|
Raise(Window),
|
||||||
TitleUpdate(WinEvent, Window),
|
DisplayChange(Window),
|
||||||
|
ForceUpdate(Window),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for WindowManagerEvent {
|
impl Display for WindowManagerEvent {
|
||||||
@@ -75,8 +76,11 @@ impl Display for WindowManagerEvent {
|
|||||||
Self::Raise(window) => {
|
Self::Raise(window) => {
|
||||||
write!(f, "Raise (Window: {window})")
|
write!(f, "Raise (Window: {window})")
|
||||||
}
|
}
|
||||||
Self::TitleUpdate(winevent, window) => {
|
Self::DisplayChange(window) => {
|
||||||
write!(f, "TitleUpdate (WinEvent: {winevent}, Window: {window})")
|
write!(f, "DisplayChange (Window: {window})")
|
||||||
|
}
|
||||||
|
Self::ForceUpdate(window) => {
|
||||||
|
write!(f, "ForceUpdate (Window: {window})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,8 +101,9 @@ impl WindowManagerEvent {
|
|||||||
| Self::MouseCapture(_, window)
|
| Self::MouseCapture(_, window)
|
||||||
| Self::Raise(window)
|
| Self::Raise(window)
|
||||||
| Self::Manage(window)
|
| Self::Manage(window)
|
||||||
|
| Self::DisplayChange(window)
|
||||||
| Self::Unmanage(window)
|
| Self::Unmanage(window)
|
||||||
| Self::TitleUpdate(_, window) => window,
|
| Self::ForceUpdate(window) => window,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,20 +146,19 @@ impl WindowManagerEvent {
|
|||||||
let class = &window.class().ok()?;
|
let class = &window.class().ok()?;
|
||||||
let path = &window.path().ok()?;
|
let path = &window.path().ok()?;
|
||||||
|
|
||||||
let should_trigger_show = should_act(
|
let should_trigger = should_act(
|
||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
path,
|
||||||
&object_name_change_on_launch,
|
&object_name_change_on_launch,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
)
|
);
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if should_trigger_show {
|
if should_trigger {
|
||||||
Option::from(Self::Show(winevent, window))
|
Option::from(Self::Show(winevent, window))
|
||||||
} else {
|
} else {
|
||||||
Option::from(Self::TitleUpdate(winevent, window))
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::mem::size_of;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use color_eyre::eyre::anyhow;
|
use color_eyre::eyre::anyhow;
|
||||||
use color_eyre::eyre::bail;
|
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use widestring::U16CStr;
|
||||||
use windows::core::Result as WindowsCrateResult;
|
use windows::core::Result as WindowsCrateResult;
|
||||||
use windows::core::PCWSTR;
|
use windows::core::PCWSTR;
|
||||||
use windows::core::PWSTR;
|
use windows::core::PWSTR;
|
||||||
@@ -30,12 +30,15 @@ use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
|||||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||||
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
||||||
|
use windows::Win32::Graphics::Gdi::EnumDisplayDevicesW;
|
||||||
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
||||||
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
||||||
|
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
||||||
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
||||||
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||||
use windows::Win32::Graphics::Gdi::RoundRect;
|
use windows::Win32::Graphics::Gdi::RoundRect;
|
||||||
|
use windows::Win32::Graphics::Gdi::DISPLAY_DEVICEW;
|
||||||
use windows::Win32::Graphics::Gdi::HBRUSH;
|
use windows::Win32::Graphics::Gdi::HBRUSH;
|
||||||
use windows::Win32::Graphics::Gdi::HDC;
|
use windows::Win32::Graphics::Gdi::HDC;
|
||||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||||
@@ -44,8 +47,9 @@ use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
|||||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||||
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
|
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
|
||||||
use windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification;
|
use windows::Win32::System::Threading::AttachThreadInput;
|
||||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||||
|
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||||
use windows::Win32::System::Threading::OpenProcess;
|
use windows::Win32::System::Threading::OpenProcess;
|
||||||
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||||
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
||||||
@@ -57,6 +61,7 @@ use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
|||||||
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
|
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
|
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
|
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
|
||||||
|
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT;
|
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;
|
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
|
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
|
||||||
@@ -94,9 +99,12 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
|
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
|
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
|
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
|
||||||
@@ -107,9 +115,6 @@ use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
|
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
|
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
|
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
|
||||||
@@ -137,9 +142,8 @@ use crate::monitor::Monitor;
|
|||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::set_window_position::SetWindowPosition;
|
use crate::set_window_position::SetWindowPosition;
|
||||||
use crate::windows_callbacks;
|
use crate::windows_callbacks;
|
||||||
use crate::Window;
|
use crate::BORDER_HWND;
|
||||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
use crate::MONITOR_INDEX_PREFERENCES;
|
|
||||||
|
|
||||||
pub enum WindowsResult<T, E> {
|
pub enum WindowsResult<T, E> {
|
||||||
Err(E),
|
Err(E),
|
||||||
@@ -220,74 +224,58 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
|
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
|
||||||
Ok(win32_display_data::connected_displays()
|
let mut monitors: Vec<(String, isize)> = vec![];
|
||||||
.flatten()
|
let monitors_ref: &mut Vec<(String, isize)> = monitors.as_mut();
|
||||||
.map(|d| {
|
Self::enum_display_monitors(
|
||||||
let name = d.device_name.trim_start_matches(r"\\.\").to_string();
|
Some(windows_callbacks::valid_display_monitors),
|
||||||
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
monitors_ref as *mut Vec<(String, isize)> as isize,
|
||||||
|
)?;
|
||||||
|
|
||||||
(name, d.hmonitor)
|
Ok(monitors)
|
||||||
})
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||||
'read: for display in win32_display_data::connected_displays().flatten() {
|
Self::enum_display_monitors(
|
||||||
let path = display.device_path.clone();
|
Some(windows_callbacks::enum_display_monitor),
|
||||||
let mut split: Vec<_> = path.split('#').collect();
|
monitors as *mut Ring<Monitor> as isize,
|
||||||
split.remove(0);
|
)?;
|
||||||
split.remove(split.len() - 1);
|
|
||||||
let device = split[0].to_string();
|
|
||||||
let device_id = split.join("-");
|
|
||||||
|
|
||||||
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
Ok(())
|
||||||
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
}
|
||||||
|
|
||||||
for monitor in monitors.elements() {
|
pub fn enum_display_devices(
|
||||||
if device_id.eq(monitor.device_id()) {
|
index: u32,
|
||||||
continue 'read;
|
lp_device: Option<*const u16>,
|
||||||
}
|
) -> Result<DISPLAY_DEVICEW> {
|
||||||
}
|
#[allow(clippy::option_if_let_else)]
|
||||||
|
let lp_device = match lp_device {
|
||||||
|
None => PCWSTR::null(),
|
||||||
|
Some(lp_device) => PCWSTR(lp_device),
|
||||||
|
};
|
||||||
|
|
||||||
let m = monitor::new(
|
let mut display_device = DISPLAY_DEVICEW {
|
||||||
display.hmonitor,
|
cb: u32::try_from(std::mem::size_of::<DISPLAY_DEVICEW>())?,
|
||||||
display.size.into(),
|
..Default::default()
|
||||||
display.work_area_size.into(),
|
};
|
||||||
name,
|
|
||||||
device,
|
|
||||||
device_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut index_preference = None;
|
match unsafe {
|
||||||
let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
|
EnumDisplayDevicesW(
|
||||||
for (index, monitor_size) in &*monitor_index_preferences {
|
lp_device,
|
||||||
if m.size() == monitor_size {
|
index,
|
||||||
index_preference = Option::from(index);
|
std::ptr::addr_of_mut!(display_device),
|
||||||
}
|
EDD_GET_DEVICE_INTERFACE_NAME,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
.ok()
|
||||||
for (index, id) in &*display_index_preferences {
|
{
|
||||||
if id.eq(m.device_id()) {
|
Ok(()) => {}
|
||||||
index_preference = Option::from(index);
|
Err(error) => {
|
||||||
}
|
tracing::error!("enum_display_devices: {}", error);
|
||||||
}
|
return Err(error.into());
|
||||||
|
|
||||||
if monitors.elements().is_empty() {
|
|
||||||
monitors.elements_mut().push_back(m);
|
|
||||||
} else if let Some(preference) = index_preference {
|
|
||||||
let current_len = monitors.elements().len();
|
|
||||||
while *preference > current_len {
|
|
||||||
monitors.elements_mut().reserve(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
monitors.elements_mut().insert(*preference, m);
|
|
||||||
} else {
|
|
||||||
monitors.elements_mut().push_back(m);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(display_device)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
|
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
|
||||||
@@ -359,19 +347,12 @@ impl WindowsApi {
|
|||||||
/// the layout to account for any window shadow borders (the window painted
|
/// the layout to account for any window shadow borders (the window painted
|
||||||
/// region will match layout on completion).
|
/// region will match layout on completion).
|
||||||
pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
|
pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> {
|
||||||
let mut flags = SetWindowPosition::NO_ACTIVATE
|
let flags = SetWindowPosition::NO_ACTIVATE
|
||||||
| SetWindowPosition::NO_SEND_CHANGING
|
| SetWindowPosition::NO_SEND_CHANGING
|
||||||
| SetWindowPosition::NO_COPY_BITS
|
| SetWindowPosition::NO_COPY_BITS
|
||||||
| SetWindowPosition::FRAME_CHANGED;
|
| SetWindowPosition::FRAME_CHANGED;
|
||||||
|
|
||||||
// If the request is to place the window on top, then HWND_TOP will take
|
let shadow_rect = Self::shadow_rect(hwnd)?;
|
||||||
// effect, otherwise pass NO_Z_ORDER that will cause set_window_pos to
|
|
||||||
// ignore the z-order paramter.
|
|
||||||
if !top {
|
|
||||||
flags |= SetWindowPosition::NO_Z_ORDER;
|
|
||||||
}
|
|
||||||
|
|
||||||
let shadow_rect = Self::shadow_rect(hwnd).unwrap_or_default();
|
|
||||||
let rect = Rect {
|
let rect = Rect {
|
||||||
left: layout.left + shadow_rect.left,
|
left: layout.left + shadow_rect.left,
|
||||||
top: layout.top + shadow_rect.top,
|
top: layout.top + shadow_rect.top,
|
||||||
@@ -379,39 +360,51 @@ impl WindowsApi {
|
|||||||
bottom: layout.bottom + shadow_rect.bottom,
|
bottom: layout.bottom + shadow_rect.bottom,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: earlier code had set HWND_TOPMOST here, but we should not do
|
let position = if top { HWND_TOP } else { HWND_NOTOPMOST };
|
||||||
// that. HWND_TOPMOST is a sticky z-order change, rather than a regular
|
Self::set_window_pos(hwnd, &rect, position, flags.bits())
|
||||||
// z-order reordering. Programs will use TOPMOST themselves to do things
|
|
||||||
// such as making sure that their tool windows or dialog pop-ups are
|
|
||||||
// above their main window. If any such windows are unmanaged, they must
|
|
||||||
// still remian topmost, so we set HWND_TOP here, which will cause the
|
|
||||||
// managed window to come to the front, but if the managed window has a
|
|
||||||
// child that is TOPMOST it will still be rendered above, in the proper
|
|
||||||
// order expected by the application. It's also important to understand
|
|
||||||
// that TOPMOST is somewhat viral, in that when you set a window to
|
|
||||||
// TOPMOST all of its owned windows are also made TOPMOST.
|
|
||||||
// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos#remarks
|
|
||||||
Self::set_window_pos(hwnd, &rect, HWND_TOP, flags.bits())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
|
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
|
||||||
unsafe { BringWindowToTop(hwnd) }.process()
|
unsafe { BringWindowToTop(hwnd) }.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raise the window to the top of the Z order, but do not activate or focus
|
|
||||||
// it. Use raise_and_focus_window to activate and focus a window.
|
|
||||||
pub fn raise_window(hwnd: HWND) -> Result<()> {
|
pub fn raise_window(hwnd: HWND) -> Result<()> {
|
||||||
let flags = SetWindowPosition::NO_MOVE | SetWindowPosition::NO_ACTIVATE;
|
let flags = SetWindowPosition::NO_MOVE;
|
||||||
|
|
||||||
let position = HWND_TOP;
|
let position = HWND_TOP;
|
||||||
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_border_pos(hwnd: HWND, layout: &Rect, position: HWND) -> Result<()> {
|
pub fn position_border_window(hwnd: HWND, layout: &Rect, activate: bool) -> Result<()> {
|
||||||
let flags = { SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE };
|
let flags = if activate {
|
||||||
|
SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE
|
||||||
|
} else {
|
||||||
|
SetWindowPosition::NO_ACTIVATE
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(raggi): This leaves the window behind the active window, which
|
||||||
|
// can result e.g. single pixel window borders being invisible in the
|
||||||
|
// case of opaque window borders (e.g. EPIC Games Launcher). Ideally
|
||||||
|
// we'd be able to pass a parent window to place ourselves just in front
|
||||||
|
// of, however the SetWindowPos API explicitly ignores that parameter
|
||||||
|
// unless the window being positioned is being activated - and we don't
|
||||||
|
// want to activate the border window here. We can hopefully find a
|
||||||
|
// better workaround in the future.
|
||||||
|
// The trade-off chosen prevents the border window from sitting over the
|
||||||
|
// top of other pop-up dialogs such as a file picker dialog from
|
||||||
|
// Firefox. When adjusting this in the future, it's important to check
|
||||||
|
// those dialog cases.
|
||||||
|
let position = HWND_NOTOPMOST;
|
||||||
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hide_border_window(hwnd: HWND) -> Result<()> {
|
||||||
|
let flags = SetWindowPosition::HIDE_WINDOW;
|
||||||
|
|
||||||
|
let position = HWND_BOTTOM;
|
||||||
|
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||||
|
}
|
||||||
|
|
||||||
/// set_window_pos calls SetWindowPos without any accounting for Window decorations.
|
/// set_window_pos calls SetWindowPos without any accounting for Window decorations.
|
||||||
fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
|
fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -469,31 +462,8 @@ impl WindowsApi {
|
|||||||
unsafe { GetForegroundWindow() }.process()
|
unsafe { GetForegroundWindow() }.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn raise_and_focus_window(hwnd: HWND) -> Result<()> {
|
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
|
||||||
let event = [INPUT {
|
unsafe { SetForegroundWindow(hwnd) }.ok().process()
|
||||||
r#type: INPUT_MOUSE,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// Send an input event to our own process first so that we pass the
|
|
||||||
// foreground lock check
|
|
||||||
SendInput(&event, size_of::<INPUT>() as i32);
|
|
||||||
// Error ignored, as the operation is not always necessary.
|
|
||||||
let _ = SetWindowPos(
|
|
||||||
hwnd,
|
|
||||||
HWND_TOP,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW,
|
|
||||||
)
|
|
||||||
.process();
|
|
||||||
SetForegroundWindow(hwnd)
|
|
||||||
}
|
|
||||||
.ok()
|
|
||||||
.process()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -510,16 +480,6 @@ impl WindowsApi {
|
|||||||
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
|
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alt_tab_windows() -> Result<Vec<Window>> {
|
|
||||||
let mut hwnds = vec![];
|
|
||||||
Self::enum_windows(
|
|
||||||
Some(windows_callbacks::alt_tab_windows),
|
|
||||||
&mut hwnds as *mut Vec<Window> as isize,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(hwnds)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn top_visible_window() -> Result<isize> {
|
pub fn top_visible_window() -> Result<isize> {
|
||||||
let hwnd = Self::top_window()?;
|
let hwnd = Self::top_window()?;
|
||||||
@@ -623,6 +583,10 @@ impl WindowsApi {
|
|||||||
(process_id, thread_id)
|
(process_id, thread_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_thread_id() -> u32 {
|
||||||
|
unsafe { GetCurrentThreadId() }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current_process_id() -> u32 {
|
pub fn current_process_id() -> u32 {
|
||||||
unsafe { GetCurrentProcessId() }
|
unsafe { GetCurrentProcessId() }
|
||||||
}
|
}
|
||||||
@@ -640,6 +604,16 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attach_thread_input(thread_id: u32, target_thread_id: u32, attach: bool) -> Result<()> {
|
||||||
|
unsafe { AttachThreadInput(thread_id, target_thread_id, attach) }
|
||||||
|
.ok()
|
||||||
|
.process()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_focus(hwnd: HWND) -> Result<()> {
|
||||||
|
unsafe { SetFocus(hwnd) }.process().map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn set_window_long_ptr_w(
|
fn set_window_long_ptr_w(
|
||||||
hwnd: HWND,
|
hwnd: HWND,
|
||||||
@@ -791,32 +765,20 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
|
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
|
||||||
for display in win32_display_data::connected_displays().flatten() {
|
let ex_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
|
||||||
if display.hmonitor == hmonitor {
|
let name = U16CStr::from_slice_truncate(&ex_info.szDevice)
|
||||||
let path = display.device_path;
|
.expect("monitor name was not a valid u16 c string")
|
||||||
let mut split: Vec<_> = path.split('#').collect();
|
.to_ustring()
|
||||||
split.remove(0);
|
.to_string_lossy()
|
||||||
split.remove(split.len() - 1);
|
.trim_start_matches(r"\\.\")
|
||||||
let device = split[0].to_string();
|
.to_string();
|
||||||
let device_id = split.join("-");
|
|
||||||
|
|
||||||
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
Ok(monitor::new(
|
||||||
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
hmonitor,
|
||||||
|
ex_info.monitorInfo.rcMonitor.into(),
|
||||||
let monitor = monitor::new(
|
ex_info.monitorInfo.rcWork.into(),
|
||||||
hmonitor,
|
name,
|
||||||
display.size.into(),
|
))
|
||||||
display.work_area_size.into(),
|
|
||||||
name,
|
|
||||||
device,
|
|
||||||
device_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(monitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("could not find device_id for hmonitor: {hmonitor}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_process_dpi_awareness_context() -> Result<()> {
|
pub fn set_process_dpi_awareness_context() -> Result<()> {
|
||||||
@@ -973,7 +935,7 @@ impl WindowsApi {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?;
|
SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY)?;
|
||||||
|
|
||||||
hwnd
|
hwnd
|
||||||
}
|
}
|
||||||
@@ -1010,6 +972,12 @@ impl WindowsApi {
|
|||||||
.process()
|
.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn invalidate_border_rect() -> Result<()> {
|
||||||
|
unsafe { InvalidateRect(HWND(BORDER_HWND.load(Ordering::SeqCst)), None, false) }
|
||||||
|
.ok()
|
||||||
|
.process()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn alt_is_pressed() -> bool {
|
pub fn alt_is_pressed() -> bool {
|
||||||
let state = unsafe { GetKeyState(i32::from(VK_MENU.0)) };
|
let state = unsafe { GetKeyState(i32::from(VK_MENU.0)) };
|
||||||
#[allow(clippy::cast_sign_loss)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
@@ -1059,8 +1027,4 @@ impl WindowsApi {
|
|||||||
SendInput(&inputs, std::mem::size_of::<INPUT>() as i32)
|
SendInput(&inputs, std::mem::size_of::<INPUT>() as i32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wts_register_session_notification(hwnd: isize) -> Result<()> {
|
|
||||||
unsafe { WTSRegisterSessionNotification(HWND(hwnd), 1) }.process()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,148 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use widestring::U16CStr;
|
||||||
|
|
||||||
use windows::Win32::Foundation::BOOL;
|
use windows::Win32::Foundation::BOOL;
|
||||||
|
use windows::Win32::Foundation::COLORREF;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::Foundation::LPARAM;
|
use windows::Win32::Foundation::LPARAM;
|
||||||
|
use windows::Win32::Foundation::LRESULT;
|
||||||
|
use windows::Win32::Foundation::RECT;
|
||||||
|
use windows::Win32::Foundation::WPARAM;
|
||||||
|
use windows::Win32::Graphics::Gdi::BeginPaint;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreatePen;
|
||||||
|
use windows::Win32::Graphics::Gdi::EndPaint;
|
||||||
|
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||||
|
use windows::Win32::Graphics::Gdi::RoundRect;
|
||||||
|
use windows::Win32::Graphics::Gdi::SelectObject;
|
||||||
|
use windows::Win32::Graphics::Gdi::ValidateRect;
|
||||||
|
use windows::Win32::Graphics::Gdi::HDC;
|
||||||
|
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||||
|
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
|
||||||
|
use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME;
|
||||||
|
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||||
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SPI_ICONVERTICALSPACING;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE;
|
||||||
|
|
||||||
use crate::container::Container;
|
use crate::container::Container;
|
||||||
use crate::window::RuleDebug;
|
use crate::monitor::Monitor;
|
||||||
|
use crate::ring::Ring;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::winevent::WinEvent;
|
use crate::winevent::WinEvent;
|
||||||
use crate::winevent_listener;
|
use crate::winevent_listener;
|
||||||
|
use crate::BORDER_COLOUR_CURRENT;
|
||||||
|
use crate::BORDER_RECT;
|
||||||
|
use crate::BORDER_WIDTH;
|
||||||
|
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||||
|
use crate::MONITOR_INDEX_PREFERENCES;
|
||||||
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
|
use crate::WINDOWS_11;
|
||||||
|
|
||||||
|
pub extern "system" fn valid_display_monitors(
|
||||||
|
hmonitor: HMONITOR,
|
||||||
|
_: HDC,
|
||||||
|
_: *mut RECT,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> BOOL {
|
||||||
|
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<(String, isize)>) };
|
||||||
|
if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
|
||||||
|
monitors.push((m.name().to_string(), hmonitor.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
true.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "system" fn enum_display_monitor(
|
||||||
|
hmonitor: HMONITOR,
|
||||||
|
_: HDC,
|
||||||
|
_: *mut RECT,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> BOOL {
|
||||||
|
let monitors = unsafe { &mut *(lparam.0 as *mut Ring<Monitor>) };
|
||||||
|
|
||||||
|
// Don't duplicate a monitor that is already being managed
|
||||||
|
for monitor in monitors.elements() {
|
||||||
|
if monitor.id() == hmonitor.0 {
|
||||||
|
return true.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_index = monitors.elements().len();
|
||||||
|
|
||||||
|
if let Ok(mut m) = WindowsApi::monitor(hmonitor.0) {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
if let Ok(d) = WindowsApi::enum_display_devices(current_index as u32, None) {
|
||||||
|
let name = U16CStr::from_slice_truncate(d.DeviceName.as_ref())
|
||||||
|
.expect("display device name was not a valid u16 c string")
|
||||||
|
.to_ustring()
|
||||||
|
.to_string_lossy()
|
||||||
|
.trim_start_matches(r"\\.\")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if name.eq(m.name()) {
|
||||||
|
if let Ok(device) = WindowsApi::enum_display_devices(0, Some(d.DeviceName.as_ptr()))
|
||||||
|
{
|
||||||
|
let id = U16CStr::from_slice_truncate(device.DeviceID.as_ref())
|
||||||
|
.expect("display device id was not a valid u16 c string")
|
||||||
|
.to_ustring()
|
||||||
|
.to_string_lossy()
|
||||||
|
.trim_start_matches(r"\\?\")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut split: Vec<_> = id.split('#').collect();
|
||||||
|
split.remove(0);
|
||||||
|
split.remove(split.len() - 1);
|
||||||
|
|
||||||
|
m.set_device(Option::from(split[0].to_string()));
|
||||||
|
m.set_device_id(Option::from(split.join("-")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
|
||||||
|
let mut index_preference = None;
|
||||||
|
for (index, monitor_size) in &*monitor_index_preferences {
|
||||||
|
if m.size() == monitor_size {
|
||||||
|
index_preference = Option::from(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||||
|
for (index, device) in &*display_index_preferences {
|
||||||
|
if let Some(known_device) = m.device_id() {
|
||||||
|
if device == known_device {
|
||||||
|
index_preference = Option::from(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if monitors.elements().is_empty() {
|
||||||
|
monitors.elements_mut().push_back(m);
|
||||||
|
} else if let Some(preference) = index_preference {
|
||||||
|
let current_len = monitors.elements().len();
|
||||||
|
if *preference > current_len {
|
||||||
|
monitors.elements_mut().reserve(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
monitors.elements_mut().insert(*preference, m);
|
||||||
|
} else {
|
||||||
|
monitors.elements_mut().push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true.into()
|
||||||
|
}
|
||||||
|
|
||||||
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||||
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
||||||
@@ -24,7 +155,7 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|||||||
if is_visible && is_window && !is_minimized {
|
if is_visible && is_window && !is_minimized {
|
||||||
let window = Window { hwnd: hwnd.0 };
|
let window = Window { hwnd: hwnd.0 };
|
||||||
|
|
||||||
if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) {
|
if let Ok(should_manage) = window.should_manage(None) {
|
||||||
if should_manage {
|
if should_manage {
|
||||||
if is_maximized {
|
if is_maximized {
|
||||||
WindowsApi::restore_window(hwnd);
|
WindowsApi::restore_window(hwnd);
|
||||||
@@ -40,26 +171,6 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|||||||
true.into()
|
true.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|
||||||
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
|
|
||||||
|
|
||||||
let is_visible = WindowsApi::is_window_visible(hwnd);
|
|
||||||
let is_window = WindowsApi::is_window(hwnd);
|
|
||||||
let is_minimized = WindowsApi::is_iconic(hwnd);
|
|
||||||
|
|
||||||
if is_visible && is_window && !is_minimized {
|
|
||||||
let window = Window { hwnd: hwnd.0 };
|
|
||||||
|
|
||||||
if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) {
|
|
||||||
if should_manage {
|
|
||||||
windows.push(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "system" fn win_event_hook(
|
pub extern "system" fn win_event_hook(
|
||||||
_h_win_event_hook: HWINEVENTHOOK,
|
_h_win_event_hook: HWINEVENTHOOK,
|
||||||
event: u32,
|
event: u32,
|
||||||
@@ -80,23 +191,107 @@ pub extern "system" fn win_event_hook(
|
|||||||
Ok(event) => event,
|
Ok(event) => event,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
|
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
|
||||||
None => {
|
None => return,
|
||||||
tracing::trace!(
|
|
||||||
"Unhandled WinEvent: {winevent} (hwnd: {}, exe: {}, title: {}, class: {})",
|
|
||||||
window.hwnd,
|
|
||||||
window.exe().unwrap_or_default(),
|
|
||||||
window.title().unwrap_or_default(),
|
|
||||||
window.class().unwrap_or_default()
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Some(event) => event,
|
Some(event) => event,
|
||||||
};
|
};
|
||||||
|
|
||||||
winevent_listener::event_tx()
|
if let Ok(should_manage) = window.should_manage(Option::from(event_type)) {
|
||||||
.send(event_type)
|
if should_manage {
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
winevent_listener::event_tx()
|
||||||
|
.send(event_type)
|
||||||
|
.expect("could not send message on winevent_listener::event_tx");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "system" fn border_window(
|
||||||
|
window: HWND,
|
||||||
|
message: u32,
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
unsafe {
|
||||||
|
match message {
|
||||||
|
WM_PAINT => {
|
||||||
|
let border_rect = *BORDER_RECT.lock();
|
||||||
|
let mut ps = PAINTSTRUCT::default();
|
||||||
|
let hdc = BeginPaint(window, &mut ps);
|
||||||
|
let hpen = CreatePen(
|
||||||
|
PS_SOLID | PS_INSIDEFRAME,
|
||||||
|
BORDER_WIDTH.load(Ordering::SeqCst),
|
||||||
|
COLORREF(BORDER_COLOUR_CURRENT.load(Ordering::SeqCst)),
|
||||||
|
);
|
||||||
|
let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR);
|
||||||
|
|
||||||
|
SelectObject(hdc, hpen);
|
||||||
|
SelectObject(hdc, hbrush);
|
||||||
|
// TODO(raggi): this is approximately the correct curvature for
|
||||||
|
// the top left of a Windows 11 window (DWMWCP_DEFAULT), but
|
||||||
|
// often the bottom right has a different shape. Furthermore if
|
||||||
|
// the window was made with DWMWCP_ROUNDSMALL then this is the
|
||||||
|
// wrong size. In the future we should read the DWM properties
|
||||||
|
// of windows and attempt to match appropriately.
|
||||||
|
if *WINDOWS_11 {
|
||||||
|
RoundRect(hdc, 0, 0, border_rect.right, border_rect.bottom, 20, 20);
|
||||||
|
} else {
|
||||||
|
Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom);
|
||||||
|
}
|
||||||
|
EndPaint(window, &ps);
|
||||||
|
ValidateRect(window, None);
|
||||||
|
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
WM_DESTROY => {
|
||||||
|
PostQuitMessage(0);
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
_ => DefWindowProcW(window, message, wparam, lparam),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "system" fn hidden_window(
|
||||||
|
window: HWND,
|
||||||
|
message: u32,
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
unsafe {
|
||||||
|
match message {
|
||||||
|
WM_DISPLAYCHANGE => {
|
||||||
|
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||||
|
winevent_listener::event_tx()
|
||||||
|
.send(event_type)
|
||||||
|
.expect("could not send message on winevent_listener::event_tx");
|
||||||
|
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
// Added based on this https://stackoverflow.com/a/33762334
|
||||||
|
WM_SETTINGCHANGE => {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
if wparam.0 as u32 == SPI_SETWORKAREA.0
|
||||||
|
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
|
||||||
|
{
|
||||||
|
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||||
|
winevent_listener::event_tx()
|
||||||
|
.send(event_type)
|
||||||
|
.expect("could not send message on winevent_listener::event_tx");
|
||||||
|
}
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
// Added based on this https://stackoverflow.com/a/33762334
|
||||||
|
WM_DEVICECHANGE => {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
||||||
|
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||||
|
winevent_listener::event_tx()
|
||||||
|
.send(event_type)
|
||||||
|
.expect("could not send message on winevent_listener::event_tx");
|
||||||
|
}
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
_ => DefWindowProcW(window, message, wparam, lparam),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,21 +20,20 @@ use komorebi_core::Layout;
|
|||||||
use komorebi_core::OperationDirection;
|
use komorebi_core::OperationDirection;
|
||||||
use komorebi_core::Rect;
|
use komorebi_core::Rect;
|
||||||
|
|
||||||
use crate::border_manager::BORDER_OFFSET;
|
|
||||||
use crate::border_manager::BORDER_WIDTH;
|
|
||||||
use crate::container::Container;
|
use crate::container::Container;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::stackbar_manager;
|
|
||||||
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
|
||||||
use crate::static_config::WorkspaceConfig;
|
use crate::static_config::WorkspaceConfig;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::window::WindowDetails;
|
use crate::window::WindowDetails;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
|
use crate::BORDER_OFFSET;
|
||||||
|
use crate::BORDER_WIDTH;
|
||||||
use crate::DEFAULT_CONTAINER_PADDING;
|
use crate::DEFAULT_CONTAINER_PADDING;
|
||||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||||
use crate::NO_TITLEBAR;
|
use crate::NO_TITLEBAR;
|
||||||
use crate::REMOVE_TITLEBARS;
|
use crate::REMOVE_TITLEBARS;
|
||||||
|
use crate::STACKBAR_TAB_HEIGHT;
|
||||||
|
|
||||||
#[allow(clippy::struct_field_names)]
|
#[allow(clippy::struct_field_names)]
|
||||||
#[derive(
|
#[derive(
|
||||||
@@ -132,8 +131,6 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.set_layout_rules(all_rules);
|
self.set_layout_rules(all_rules);
|
||||||
|
|
||||||
self.tile = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(layout_rules) = &config.custom_layout_rules {
|
if let Some(layout_rules) = &config.custom_layout_rules {
|
||||||
@@ -142,8 +139,6 @@ impl Workspace {
|
|||||||
let rule = CustomLayout::from_path(pathbuf)?;
|
let rule = CustomLayout::from_path(pathbuf)?;
|
||||||
rules.push((*count, Layout::Custom(rule)));
|
rules.push((*count, Layout::Custom(rule)));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tile = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -180,14 +175,6 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||||
if let Some(container) = self.monocle_container_mut() {
|
|
||||||
if let Some(window) = container.focused_window() {
|
|
||||||
container.restore();
|
|
||||||
window.focus(mouse_follows_focus)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.focused_container_idx();
|
let idx = self.focused_container_idx();
|
||||||
let mut to_focus = None;
|
let mut to_focus = None;
|
||||||
|
|
||||||
@@ -205,6 +192,10 @@ impl Workspace {
|
|||||||
container.restore();
|
container.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(container) = self.monocle_container_mut() {
|
||||||
|
container.restore();
|
||||||
|
}
|
||||||
|
|
||||||
for window in self.floating_windows() {
|
for window in self.floating_windows() {
|
||||||
window.restore();
|
window.restore();
|
||||||
}
|
}
|
||||||
@@ -224,21 +215,13 @@ impl Workspace {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(
|
pub fn update(&mut self, work_area: &Rect, offset: Option<Rect>) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
work_area: &Rect,
|
|
||||||
work_area_offset: Option<Rect>,
|
|
||||||
window_based_work_area_offset: (isize, Option<Rect>),
|
|
||||||
) -> Result<()> {
|
|
||||||
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (window_based_work_area_offset_limit, window_based_work_area_offset) =
|
|
||||||
window_based_work_area_offset;
|
|
||||||
|
|
||||||
let container_padding = self.container_padding();
|
let container_padding = self.container_padding();
|
||||||
let mut adjusted_work_area = work_area_offset.map_or_else(
|
let mut adjusted_work_area = offset.map_or_else(
|
||||||
|| *work_area,
|
|| *work_area,
|
||||||
|offset| {
|
|offset| {
|
||||||
let mut with_offset = *work_area;
|
let mut with_offset = *work_area;
|
||||||
@@ -251,21 +234,6 @@ impl Workspace {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.containers().len() <= window_based_work_area_offset_limit as usize {
|
|
||||||
adjusted_work_area = window_based_work_area_offset.map_or_else(
|
|
||||||
|| adjusted_work_area,
|
|
||||||
|offset| {
|
|
||||||
let mut with_offset = adjusted_work_area;
|
|
||||||
with_offset.left += offset.left;
|
|
||||||
with_offset.top += offset.top;
|
|
||||||
with_offset.right -= offset.right;
|
|
||||||
with_offset.bottom -= offset.bottom;
|
|
||||||
|
|
||||||
with_offset
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
|
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
|
||||||
|
|
||||||
self.enforce_resize_constraints();
|
self.enforce_resize_constraints();
|
||||||
@@ -305,7 +273,7 @@ impl Workspace {
|
|||||||
} else if let Some(window) = self.maximized_window_mut() {
|
} else if let Some(window) = self.maximized_window_mut() {
|
||||||
window.maximize();
|
window.maximize();
|
||||||
} else if !self.containers().is_empty() {
|
} else if !self.containers().is_empty() {
|
||||||
let mut layouts = self.layout().as_boxed_arrangement().calculate(
|
let layouts = self.layout().as_boxed_arrangement().calculate(
|
||||||
&adjusted_work_area,
|
&adjusted_work_area,
|
||||||
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
|
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
@@ -320,14 +288,22 @@ impl Workspace {
|
|||||||
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
||||||
let no_titlebar = NO_TITLEBAR.lock().clone();
|
let no_titlebar = NO_TITLEBAR.lock().clone();
|
||||||
|
|
||||||
|
let focused_hwnd = self
|
||||||
|
.focused_container()
|
||||||
|
.ok_or_else(|| anyhow!("couldn't find a focused container"))?
|
||||||
|
.focused_window()
|
||||||
|
.ok_or_else(|| anyhow!("couldn't find a focused window"))?
|
||||||
|
.hwnd;
|
||||||
|
|
||||||
let container_padding = self.container_padding().unwrap_or(0);
|
let container_padding = self.container_padding().unwrap_or(0);
|
||||||
let containers = self.containers_mut();
|
let containers = self.containers_mut();
|
||||||
|
|
||||||
for (i, container) in containers.iter_mut().enumerate() {
|
for (i, container) in containers.iter_mut().enumerate() {
|
||||||
let window_count = container.windows().len();
|
let container_windows = container.windows().clone();
|
||||||
|
let container_topbar = container.stackbar().clone();
|
||||||
|
|
||||||
if let (Some(window), Some(layout)) =
|
if let (Some(window), Some(layout)) =
|
||||||
(container.focused_window_mut(), layouts.get_mut(i))
|
(container.focused_window_mut(), layouts.get(i))
|
||||||
{
|
{
|
||||||
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
|
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
|
||||||
window.remove_title_bar()?;
|
window.remove_title_bar()?;
|
||||||
@@ -341,23 +317,30 @@ impl Workspace {
|
|||||||
WindowsApi::restore_window(window.hwnd());
|
WindowsApi::restore_window(window.hwnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut rect = *layout;
|
||||||
{
|
{
|
||||||
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
||||||
layout.add_padding(border_offset);
|
rect.add_padding(border_offset);
|
||||||
|
|
||||||
let width = BORDER_WIDTH.load(Ordering::SeqCst);
|
let width = BORDER_WIDTH.load(Ordering::SeqCst);
|
||||||
layout.add_padding(width);
|
rect.add_padding(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if stackbar_manager::should_have_stackbar(window_count) {
|
if let Some(stackbar) = container_topbar {
|
||||||
|
stackbar.set_position(
|
||||||
|
&stackbar.get_position_from_container_layout(layout),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
stackbar.update(&container_windows, focused_hwnd)?;
|
||||||
let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
|
let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
|
||||||
let total_height = tab_height + container_padding;
|
let total_height = tab_height + container_padding;
|
||||||
|
|
||||||
layout.top += total_height;
|
rect.top += total_height;
|
||||||
layout.bottom -= total_height;
|
rect.bottom -= total_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.set_position(layout, false)?;
|
window.set_position(&rect, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,26 +357,6 @@ impl Workspace {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus_changed performs updates in response to the fact that a focus
|
|
||||||
// change event has occurred. The focus change is assumed to be valid, and
|
|
||||||
// should not result in a new focus change - the intent here is to update
|
|
||||||
// focus-reactive elements, such as the stackbar.
|
|
||||||
pub fn focus_changed(&mut self, hwnd: isize) -> Result<()> {
|
|
||||||
if !self.tile() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let containers = self.containers_mut();
|
|
||||||
|
|
||||||
for container in containers.iter_mut() {
|
|
||||||
if let Some(idx) = container.idx_for_window(hwnd) {
|
|
||||||
container.focus_window(idx);
|
|
||||||
container.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
|
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
|
||||||
let mut hwnds = vec![];
|
let mut hwnds = vec![];
|
||||||
let mut floating_hwnds = vec![];
|
let mut floating_hwnds = vec![];
|
||||||
@@ -660,10 +623,6 @@ impl Workspace {
|
|||||||
self.set_monocle_container_restore_idx(None);
|
self.set_monocle_container_restore_idx(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in self.containers() {
|
|
||||||
c.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,10 +843,6 @@ impl Workspace {
|
|||||||
if container.windows().is_empty() {
|
if container.windows().is_empty() {
|
||||||
self.containers_mut().remove(focused_idx);
|
self.containers_mut().remove(focused_idx);
|
||||||
self.resize_dimensions_mut().remove(focused_idx);
|
self.resize_dimensions_mut().remove(focused_idx);
|
||||||
|
|
||||||
if focused_idx == self.containers().len() && focused_idx != 0 {
|
|
||||||
self.focus_container(focused_idx - 1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
container.load_focused_window();
|
container.load_focused_window();
|
||||||
}
|
}
|
||||||
@@ -903,17 +858,6 @@ impl Workspace {
|
|||||||
fn enforce_resize_constraints(&mut self) {
|
fn enforce_resize_constraints(&mut self) {
|
||||||
match self.layout {
|
match self.layout {
|
||||||
Layout::Default(DefaultLayout::BSP) => self.enforce_resize_constraints_for_bsp(),
|
Layout::Default(DefaultLayout::BSP) => self.enforce_resize_constraints_for_bsp(),
|
||||||
Layout::Default(DefaultLayout::Columns) => self.enforce_resize_for_columns(),
|
|
||||||
Layout::Default(DefaultLayout::Rows) => self.enforce_resize_for_rows(),
|
|
||||||
Layout::Default(DefaultLayout::VerticalStack) => {
|
|
||||||
self.enforce_resize_for_vertical_stack();
|
|
||||||
}
|
|
||||||
Layout::Default(DefaultLayout::RightMainVerticalStack) => {
|
|
||||||
self.enforce_resize_for_right_vertical_stack();
|
|
||||||
}
|
|
||||||
Layout::Default(DefaultLayout::HorizontalStack) => {
|
|
||||||
self.enforce_resize_for_horizontal_stack();
|
|
||||||
}
|
|
||||||
Layout::Default(DefaultLayout::UltrawideVerticalStack) => {
|
Layout::Default(DefaultLayout::UltrawideVerticalStack) => {
|
||||||
self.enforce_resize_for_ultrawide();
|
self.enforce_resize_for_ultrawide();
|
||||||
}
|
}
|
||||||
@@ -947,146 +891,6 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enforce_resize_for_columns(&mut self) {
|
|
||||||
let resize_dimensions = self.resize_dimensions_mut();
|
|
||||||
match resize_dimensions.len() {
|
|
||||||
0 | 1 => self.enforce_no_resize(),
|
|
||||||
_ => {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
for (i, rect) in resize_dimensions.iter_mut().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
rect.top = 0;
|
|
||||||
rect.bottom = 0;
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
rect.left = 0;
|
|
||||||
}
|
|
||||||
if i == len - 1 {
|
|
||||||
rect.right = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_resize_for_rows(&mut self) {
|
|
||||||
let resize_dimensions = self.resize_dimensions_mut();
|
|
||||||
match resize_dimensions.len() {
|
|
||||||
0 | 1 => self.enforce_no_resize(),
|
|
||||||
_ => {
|
|
||||||
let len = resize_dimensions.len();
|
|
||||||
for (i, rect) in resize_dimensions.iter_mut().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
rect.left = 0;
|
|
||||||
rect.right = 0;
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
rect.top = 0;
|
|
||||||
}
|
|
||||||
if i == len - 1 {
|
|
||||||
rect.bottom = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_resize_for_vertical_stack(&mut self) {
|
|
||||||
let resize_dimensions = self.resize_dimensions_mut();
|
|
||||||
match resize_dimensions.len() {
|
|
||||||
// Single window can not be resized at all
|
|
||||||
0 | 1 => self.enforce_no_resize(),
|
|
||||||
_ => {
|
|
||||||
// Zero is actually on the left
|
|
||||||
if let Some(mut left) = resize_dimensions[0] {
|
|
||||||
left.top = 0;
|
|
||||||
left.bottom = 0;
|
|
||||||
left.left = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle stack on the right
|
|
||||||
let stack_size = resize_dimensions[1..].len();
|
|
||||||
for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
// No containers can resize to the right
|
|
||||||
rect.right = 0;
|
|
||||||
|
|
||||||
// First container in stack cant resize up
|
|
||||||
if i == 0 {
|
|
||||||
rect.top = 0;
|
|
||||||
} else if i == stack_size - 1 {
|
|
||||||
// Last cant be resized to the bottom
|
|
||||||
rect.bottom = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_resize_for_right_vertical_stack(&mut self) {
|
|
||||||
let resize_dimensions = self.resize_dimensions_mut();
|
|
||||||
match resize_dimensions.len() {
|
|
||||||
// Single window can not be resized at all
|
|
||||||
0 | 1 => self.enforce_no_resize(),
|
|
||||||
_ => {
|
|
||||||
// Zero is actually on the right
|
|
||||||
if let Some(mut left) = resize_dimensions[1] {
|
|
||||||
left.top = 0;
|
|
||||||
left.bottom = 0;
|
|
||||||
left.right = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle stack on the right
|
|
||||||
let stack_size = resize_dimensions[1..].len();
|
|
||||||
for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
// No containers can resize to the left
|
|
||||||
rect.left = 0;
|
|
||||||
|
|
||||||
// First container in stack cant resize up
|
|
||||||
if i == 0 {
|
|
||||||
rect.top = 0;
|
|
||||||
} else if i == stack_size - 1 {
|
|
||||||
// Last cant be resized to the bottom
|
|
||||||
rect.bottom = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_resize_for_horizontal_stack(&mut self) {
|
|
||||||
let resize_dimensions = self.resize_dimensions_mut();
|
|
||||||
match resize_dimensions.len() {
|
|
||||||
0 | 1 => self.enforce_no_resize(),
|
|
||||||
_ => {
|
|
||||||
if let Some(mut left) = resize_dimensions[0] {
|
|
||||||
left.top = 0;
|
|
||||||
left.left = 0;
|
|
||||||
left.right = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stack_size = resize_dimensions[1..].len();
|
|
||||||
for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {
|
|
||||||
if let Some(rect) = rect {
|
|
||||||
rect.bottom = 0;
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
rect.left = 0;
|
|
||||||
}
|
|
||||||
if i == stack_size - 1 {
|
|
||||||
rect.right = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_resize_for_ultrawide(&mut self) {
|
fn enforce_resize_for_ultrawide(&mut self) {
|
||||||
let resize_dimensions = self.resize_dimensions_mut();
|
let resize_dimensions = self.resize_dimensions_mut();
|
||||||
match resize_dimensions.len() {
|
match resize_dimensions.len() {
|
||||||
@@ -1187,7 +991,7 @@ impl Workspace {
|
|||||||
.ok_or_else(|| anyhow!("there is no monocle container"))?;
|
.ok_or_else(|| anyhow!("there is no monocle container"))?;
|
||||||
|
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
if restore_idx >= self.containers().len() {
|
if restore_idx > self.containers().len() - 1 {
|
||||||
self.containers_mut()
|
self.containers_mut()
|
||||||
.resize(restore_idx, Container::default());
|
.resize(restore_idx, Container::default());
|
||||||
}
|
}
|
||||||
@@ -1381,7 +1185,7 @@ impl Workspace {
|
|||||||
vec
|
vec
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus_previous_container(&mut self) {
|
fn focus_previous_container(&mut self) {
|
||||||
let focused_idx = self.focused_container_idx();
|
let focused_idx = self.focused_container_idx();
|
||||||
|
|
||||||
if focused_idx != 0 {
|
if focused_idx != 0 {
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
||||||
|
|
||||||
use crate::border_manager;
|
|
||||||
use crate::WindowManager;
|
|
||||||
use crossbeam_channel::Receiver;
|
|
||||||
use crossbeam_channel::Sender;
|
|
||||||
use crossbeam_utils::atomic::AtomicCell;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Notification {
|
|
||||||
pub monitor_idx: usize,
|
|
||||||
pub workspace_idx: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static ALT_TAB_HWND: AtomicCell<Option<isize>> = AtomicCell::new(None);
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref ALT_TAB_HWND_INSTANT: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
|
||||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_tx() -> Sender<Notification> {
|
|
||||||
channel().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_rx() -> Receiver<Notification> {
|
|
||||||
channel().1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
|
||||||
std::thread::spawn(move || loop {
|
|
||||||
match handle_notifications(wm.clone()) {
|
|
||||||
Ok(()) => {
|
|
||||||
tracing::warn!("restarting finished thread");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
tracing::error!("restarting failed thread: {:?}", error)
|
|
||||||
} else {
|
|
||||||
tracing::error!("restarting failed thread: {}", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
|
||||||
tracing::info!("listening");
|
|
||||||
|
|
||||||
let receiver = event_rx();
|
|
||||||
let arc = wm.clone();
|
|
||||||
|
|
||||||
for notification in receiver {
|
|
||||||
tracing::info!("running reconciliation");
|
|
||||||
|
|
||||||
let mut wm = wm.lock();
|
|
||||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
|
||||||
let focused_workspace_idx =
|
|
||||||
wm.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
|
||||||
|
|
||||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
|
||||||
let updated_pair = (notification.monitor_idx, notification.workspace_idx);
|
|
||||||
|
|
||||||
if focused_pair != updated_pair {
|
|
||||||
wm.focus_monitor(notification.monitor_idx)?;
|
|
||||||
let mouse_follows_focus = wm.mouse_follows_focus;
|
|
||||||
|
|
||||||
if let Some(monitor) = wm.focused_monitor_mut() {
|
|
||||||
monitor.focus_workspace(notification.workspace_idx)?;
|
|
||||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop our lock on the window manager state here to not slow down updates
|
|
||||||
drop(wm);
|
|
||||||
|
|
||||||
// Check if there was an alt-tab across workspaces in the last second
|
|
||||||
if let Some(hwnd) = ALT_TAB_HWND.load() {
|
|
||||||
if ALT_TAB_HWND_INSTANT
|
|
||||||
.lock()
|
|
||||||
.elapsed()
|
|
||||||
.lt(&Duration::from_secs(1))
|
|
||||||
{
|
|
||||||
// Sleep for 100 millis to let other events pass
|
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
|
||||||
tracing::info!("focusing alt-tabbed window");
|
|
||||||
|
|
||||||
// Take a new lock on the wm and try to focus the container with
|
|
||||||
// the recorded HWND from the alt-tab
|
|
||||||
let mut wm = arc.lock();
|
|
||||||
if let Ok(workspace) = wm.focused_workspace_mut() {
|
|
||||||
// Regardless of if this fails, we need to get past this part
|
|
||||||
// to unblock the border manager below
|
|
||||||
let _ = workspace.focus_container_by_window(hwnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unblock the border manager
|
|
||||||
ALT_TAB_HWND.store(None);
|
|
||||||
// Send a notification to the border manager to update the borders
|
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebic-no-console"
|
name = "komorebic-no-console"
|
||||||
version = "0.1.27-dev.0"
|
version = "0.1.23-dev.0"
|
||||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||||
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
|
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
|
||||||
categories = ["cli", "tiling-window-manager", "windows"]
|
categories = ["cli", "tiling-window-manager", "windows"]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#Requires AutoHotkey v2.0.2
|
; Generated by komorebic.exe
|
||||||
|
|
||||||
Start(ffm, await_configuration, tcp_port) {
|
Start(ffm, await_configuration, tcp_port) {
|
||||||
RunWait("komorebic.exe start " ffm " --await-configuration " await_configuration " --tcp-port " tcp_port, , "Hide")
|
RunWait("komorebic.exe start " ffm " --await-configuration " await_configuration " --tcp-port " tcp_port, , "Hide")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebic"
|
name = "komorebic"
|
||||||
version = "0.1.27-dev.0"
|
version = "0.1.23-dev.0"
|
||||||
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
|
||||||
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
||||||
categories = ["cli", "tiling-window-manager", "windows"]
|
categories = ["cli", "tiling-window-manager", "windows"]
|
||||||
@@ -13,28 +13,26 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
derive-ahk = { path = "../derive-ahk" }
|
derive-ahk = { path = "../derive-ahk" }
|
||||||
komorebi-core = { path = "../komorebi-core" }
|
komorebi-core = { path = "../komorebi-core" }
|
||||||
komorebi-client = { path = "../komorebi-client" }
|
|
||||||
|
|
||||||
clap = { version = "4", features = ["derive", "wrap_help"] }
|
clap = { version = "4", features = ["derive", "wrap_help"] }
|
||||||
chrono = "0.4"
|
|
||||||
color-eyre = { workspace = true }
|
color-eyre = { workspace = true }
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
fs-tail = "0.1"
|
fs-tail = "0.1"
|
||||||
heck = "0.5"
|
heck = "0.4"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
miette = { version = "7", features = ["fancy"] }
|
miette = { version = "7", features = ["fancy"] }
|
||||||
paste = "1"
|
paste = "1"
|
||||||
powershell_script = "1.0"
|
powershell_script = "1.0"
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
reqwest = { version = "0.11", features = ["blocking"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sysinfo = { workspace = true }
|
sysinfo = "0.30"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
uds_windows = "1"
|
uds_windows = "1"
|
||||||
which = "6"
|
which = "6"
|
||||||
windows = { workspace = true }
|
windows = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
reqwest = { version = "0.11", features = ["blocking"] }
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
use chrono::Local;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
@@ -41,7 +40,6 @@ use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
|||||||
|
|
||||||
use derive_ahk::AhkFunction;
|
use derive_ahk::AhkFunction;
|
||||||
use derive_ahk::AhkLibrary;
|
use derive_ahk::AhkLibrary;
|
||||||
use komorebi_client::StaticConfig;
|
|
||||||
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
||||||
use komorebi_core::ApplicationIdentifier;
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi_core::Axis;
|
use komorebi_core::Axis;
|
||||||
@@ -162,7 +160,6 @@ gen_enum_subcommand_args! {
|
|||||||
CycleMoveToMonitor: CycleDirection,
|
CycleMoveToMonitor: CycleDirection,
|
||||||
CycleMonitor: CycleDirection,
|
CycleMonitor: CycleDirection,
|
||||||
CycleWorkspace: CycleDirection,
|
CycleWorkspace: CycleDirection,
|
||||||
CycleMoveWorkspaceToMonitor: CycleDirection,
|
|
||||||
Stack: OperationDirection,
|
Stack: OperationDirection,
|
||||||
CycleStack: CycleDirection,
|
CycleStack: CycleDirection,
|
||||||
FlipLayout: Axis,
|
FlipLayout: Axis,
|
||||||
@@ -174,7 +171,6 @@ gen_enum_subcommand_args! {
|
|||||||
WindowHidingBehaviour: HidingBehaviour,
|
WindowHidingBehaviour: HidingBehaviour,
|
||||||
CrossMonitorMoveBehaviour: MoveBehaviour,
|
CrossMonitorMoveBehaviour: MoveBehaviour,
|
||||||
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
||||||
PromoteWindow: OperationDirection,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! gen_target_subcommand_args {
|
macro_rules! gen_target_subcommand_args {
|
||||||
@@ -482,14 +478,6 @@ pub struct SendToMonitorWorkspace {
|
|||||||
target_workspace: usize,
|
target_workspace: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
|
||||||
pub struct MoveToMonitorWorkspace {
|
|
||||||
/// Target monitor index (zero-indexed)
|
|
||||||
target_monitor: usize,
|
|
||||||
/// Workspace index on the target monitor (zero-indexed)
|
|
||||||
target_workspace: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! gen_focused_workspace_padding_subcommand_args {
|
macro_rules! gen_focused_workspace_padding_subcommand_args {
|
||||||
// SubCommand Pattern
|
// SubCommand Pattern
|
||||||
( $( $name:ident ),+ $(,)? ) => {
|
( $( $name:ident ),+ $(,)? ) => {
|
||||||
@@ -655,13 +643,13 @@ struct FocusFollowsMouse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct Border {
|
struct ActiveWindowBorder {
|
||||||
#[clap(value_enum)]
|
#[clap(value_enum)]
|
||||||
boolean_state: BooleanState,
|
boolean_state: BooleanState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct BorderColour {
|
struct ActiveWindowBorderColour {
|
||||||
#[clap(value_enum, short, long, default_value = "single")]
|
#[clap(value_enum, short, long, default_value = "single")]
|
||||||
window_kind: WindowKind,
|
window_kind: WindowKind,
|
||||||
/// Red
|
/// Red
|
||||||
@@ -673,14 +661,14 @@ struct BorderColour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct BorderWidth {
|
struct ActiveWindowBorderWidth {
|
||||||
/// Desired width of the window border
|
/// Desired width of the active window border
|
||||||
width: i32,
|
width: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct BorderOffset {
|
struct ActiveWindowBorderOffset {
|
||||||
/// Desired offset of the window border
|
/// Desired offset of the active window border
|
||||||
offset: i32,
|
offset: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,10 +815,6 @@ enum SubCommand {
|
|||||||
Whkdrc,
|
Whkdrc,
|
||||||
/// Show a JSON representation of the current window manager state
|
/// Show a JSON representation of the current window manager state
|
||||||
State,
|
State,
|
||||||
/// Show a JSON representation of the current global state
|
|
||||||
GlobalState,
|
|
||||||
/// Launch the komorebi-gui debugging tool
|
|
||||||
Gui,
|
|
||||||
/// Show a JSON representation of visible windows
|
/// Show a JSON representation of visible windows
|
||||||
VisibleWindows,
|
VisibleWindows,
|
||||||
/// Query the current window manager state
|
/// Query the current window manager state
|
||||||
@@ -932,9 +916,6 @@ enum SubCommand {
|
|||||||
/// Send the focused window to the specified monitor workspace
|
/// Send the focused window to the specified monitor workspace
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
SendToMonitorWorkspace(SendToMonitorWorkspace),
|
SendToMonitorWorkspace(SendToMonitorWorkspace),
|
||||||
/// Move the focused window to the specified monitor workspace
|
|
||||||
#[clap(arg_required_else_help = true)]
|
|
||||||
MoveToMonitorWorkspace(MoveToMonitorWorkspace),
|
|
||||||
/// Focus the specified monitor
|
/// Focus the specified monitor
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
FocusMonitor(FocusMonitor),
|
FocusMonitor(FocusMonitor),
|
||||||
@@ -961,9 +942,6 @@ enum SubCommand {
|
|||||||
/// Move the focused workspace to the specified monitor
|
/// Move the focused workspace to the specified monitor
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
||||||
/// Move the focused workspace monitor in the given cycle direction
|
|
||||||
#[clap(arg_required_else_help = true)]
|
|
||||||
CycleMoveWorkspaceToMonitor(CycleMoveWorkspaceToMonitor),
|
|
||||||
/// Swap focused monitor workspaces with specified monitor
|
/// Swap focused monitor workspaces with specified monitor
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
SwapWorkspacesWithMonitor(SwapWorkspacesWithMonitor),
|
SwapWorkspacesWithMonitor(SwapWorkspacesWithMonitor),
|
||||||
@@ -1009,8 +987,6 @@ enum SubCommand {
|
|||||||
Promote,
|
Promote,
|
||||||
/// Promote the user focus to the top of the tree
|
/// Promote the user focus to the top of the tree
|
||||||
PromoteFocus,
|
PromoteFocus,
|
||||||
/// Promote the window in the specified direction
|
|
||||||
PromoteWindow(PromoteWindow),
|
|
||||||
/// Force the retiling of all managed windows
|
/// Force the retiling of all managed windows
|
||||||
Retile,
|
Retile,
|
||||||
/// Set the monitor index preference for a monitor identified using its size
|
/// Set the monitor index preference for a monitor identified using its size
|
||||||
@@ -1149,25 +1125,21 @@ enum SubCommand {
|
|||||||
/// Toggle title bars for whitelisted applications
|
/// Toggle title bars for whitelisted applications
|
||||||
ToggleTitleBars,
|
ToggleTitleBars,
|
||||||
/// Identify an application that has overflowing borders
|
/// Identify an application that has overflowing borders
|
||||||
#[clap(hide = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
#[clap(alias = "identify-border-overflow")]
|
#[clap(alias = "identify-border-overflow")]
|
||||||
IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),
|
IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),
|
||||||
/// Enable or disable borders
|
/// Enable or disable the active window border
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
#[clap(alias = "active-window-border")]
|
ActiveWindowBorder(ActiveWindowBorder),
|
||||||
Border(Border),
|
/// Set the colour for the active window border
|
||||||
/// Set the colour for a window border kind
|
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
#[clap(alias = "active-window-border-colour")]
|
ActiveWindowBorderColour(ActiveWindowBorderColour),
|
||||||
BorderColour(BorderColour),
|
/// Set the width for the active window border
|
||||||
/// Set the border width
|
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
#[clap(alias = "active-window-border-width")]
|
ActiveWindowBorderWidth(ActiveWindowBorderWidth),
|
||||||
BorderWidth(BorderWidth),
|
/// Set the offset for the active window border
|
||||||
/// Set the border offset
|
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
#[clap(alias = "active-window-border-offset")]
|
ActiveWindowBorderOffset(ActiveWindowBorderOffset),
|
||||||
BorderOffset(BorderOffset),
|
|
||||||
/// Enable or disable focus follows mouse for the operating system
|
/// Enable or disable focus follows mouse for the operating system
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
FocusFollowsMouse(FocusFollowsMouse),
|
FocusFollowsMouse(FocusFollowsMouse),
|
||||||
@@ -1272,15 +1244,9 @@ fn main() -> Result<()> {
|
|||||||
let subcommands = cli.get_subcommands_mut();
|
let subcommands = cli.get_subcommands_mut();
|
||||||
std::fs::create_dir_all("docs/cli")?;
|
std::fs::create_dir_all("docs/cli")?;
|
||||||
|
|
||||||
let ignore = [
|
|
||||||
"docgen",
|
|
||||||
"alt-focus-hack",
|
|
||||||
"identify-border-overflow-application",
|
|
||||||
];
|
|
||||||
|
|
||||||
for cmd in subcommands {
|
for cmd in subcommands {
|
||||||
let name = cmd.get_name().to_string();
|
let name = cmd.get_name().to_string();
|
||||||
if !ignore.contains(&name.as_str()) {
|
if name != "docgen" {
|
||||||
let help_text = cmd.render_long_help().to_string();
|
let help_text = cmd.render_long_help().to_string();
|
||||||
let outpath = format!("docs/cli/{name}.md");
|
let outpath = format!("docs/cli/{name}.md");
|
||||||
let markdown = format!("# {name}\n\n```\n{help_text}\n```");
|
let markdown = format!("# {name}\n\n```\n{help_text}\n```");
|
||||||
@@ -1423,11 +1389,6 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that this file adheres to the schema static config schema as the last step,
|
|
||||||
// so that more basic errors above can be shown to the error before schema-specific
|
|
||||||
// errors
|
|
||||||
let _ = serde_json::from_str::<komorebi_client::StaticConfig>(&config_source)?;
|
|
||||||
|
|
||||||
if config_whkd.exists() {
|
if config_whkd.exists() {
|
||||||
println!("Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n", config_whkd.to_string_lossy());
|
println!("Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n", config_whkd.to_string_lossy());
|
||||||
} else {
|
} else {
|
||||||
@@ -1494,8 +1455,7 @@ fn main() -> Result<()> {
|
|||||||
println!("\n#Include komorebic.lib.ahk");
|
println!("\n#Include komorebic.lib.ahk");
|
||||||
}
|
}
|
||||||
SubCommand::Log => {
|
SubCommand::Log => {
|
||||||
let timestamp = Local::now().format("%Y-%m-%d").to_string();
|
let color_log = std::env::temp_dir().join("komorebi.log");
|
||||||
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
|
|
||||||
let file = TailedFile::new(File::open(color_log)?);
|
let file = TailedFile::new(File::open(color_log)?);
|
||||||
let locked = file.lock();
|
let locked = file.lock();
|
||||||
#[allow(clippy::significant_drop_in_scrutinee, clippy::lines_filter_map_ok)]
|
#[allow(clippy::significant_drop_in_scrutinee, clippy::lines_filter_map_ok)]
|
||||||
@@ -1521,9 +1481,6 @@ fn main() -> Result<()> {
|
|||||||
SubCommand::PromoteFocus => {
|
SubCommand::PromoteFocus => {
|
||||||
send_message(&SocketMessage::PromoteFocus.as_bytes()?)?;
|
send_message(&SocketMessage::PromoteFocus.as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::PromoteWindow(arg) => {
|
|
||||||
send_message(&SocketMessage::PromoteWindow(arg.operation_direction).as_bytes()?)?;
|
|
||||||
}
|
|
||||||
SubCommand::TogglePause => {
|
SubCommand::TogglePause => {
|
||||||
send_message(&SocketMessage::TogglePause.as_bytes()?)?;
|
send_message(&SocketMessage::TogglePause.as_bytes()?)?;
|
||||||
}
|
}
|
||||||
@@ -1586,23 +1543,9 @@ fn main() -> Result<()> {
|
|||||||
.as_bytes()?,
|
.as_bytes()?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
SubCommand::MoveToMonitorWorkspace(arg) => {
|
|
||||||
send_message(
|
|
||||||
&SocketMessage::MoveContainerToMonitorWorkspaceNumber(
|
|
||||||
arg.target_monitor,
|
|
||||||
arg.target_workspace,
|
|
||||||
)
|
|
||||||
.as_bytes()?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
SubCommand::MoveWorkspaceToMonitor(arg) => {
|
SubCommand::MoveWorkspaceToMonitor(arg) => {
|
||||||
send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
|
send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::CycleMoveWorkspaceToMonitor(arg) => {
|
|
||||||
send_message(
|
|
||||||
&SocketMessage::CycleMoveWorkspaceToMonitor(arg.cycle_direction).as_bytes()?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
SubCommand::SwapWorkspacesWithMonitor(arg) => {
|
SubCommand::SwapWorkspacesWithMonitor(arg) => {
|
||||||
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(arg.target).as_bytes()?)?;
|
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(arg.target).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
@@ -1830,7 +1773,7 @@ fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut flags = vec![];
|
let mut flags = vec![];
|
||||||
if let Some(config) = &arg.config {
|
if let Some(config) = arg.config {
|
||||||
let path = resolve_home_path(config)?;
|
let path = resolve_home_path(config)?;
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
bail!("could not find file: {}", path.display());
|
bail!("could not find file: {}", path.display());
|
||||||
@@ -1865,10 +1808,9 @@ fn main() -> Result<()> {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut attempts = 0;
|
|
||||||
let mut running = false;
|
let mut running = false;
|
||||||
|
|
||||||
while !running && attempts <= 2 {
|
while !running {
|
||||||
match powershell_script::run(&script) {
|
match powershell_script::run(&script) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("{script}");
|
println!("{script}");
|
||||||
@@ -1889,27 +1831,9 @@ fn main() -> Result<()> {
|
|||||||
running = true;
|
running = true;
|
||||||
} else {
|
} else {
|
||||||
println!("komorebi.exe did not start... Trying again");
|
println!("komorebi.exe did not start... Trying again");
|
||||||
attempts += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !running {
|
|
||||||
println!("\nRunning komorebi.exe directly for detailed error output\n");
|
|
||||||
if let Some(config) = arg.config {
|
|
||||||
let path = resolve_home_path(config)?;
|
|
||||||
if let Ok(output) = Command::new("komorebi.exe")
|
|
||||||
.arg(format!("'--config=\"{}\"'", path.display()))
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
println!("{}", String::from_utf8(output.stderr)?);
|
|
||||||
}
|
|
||||||
} else if let Ok(output) = Command::new("komorebi.exe").output() {
|
|
||||||
println!("{}", String::from_utf8(output.stderr)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if arg.whkd {
|
if arg.whkd {
|
||||||
let script = r"
|
let script = r"
|
||||||
if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
||||||
@@ -1947,33 +1871,6 @@ if (!(Get-Process whkd -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!(
|
|
||||||
"* Subscribe to https://youtube.com/@LGUG2Z - Live dev videos and feature previews"
|
|
||||||
);
|
|
||||||
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
|
|
||||||
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
|
|
||||||
|
|
||||||
let static_config = arg.config.map_or_else(
|
|
||||||
|| {
|
|
||||||
let komorebi_json = HOME_DIR.join("komorebi.json");
|
|
||||||
if komorebi_json.is_file() {
|
|
||||||
Option::from(komorebi_json)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Option::from,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(config) = static_config {
|
|
||||||
let path = resolve_home_path(config)?;
|
|
||||||
let raw = std::fs::read_to_string(path)?;
|
|
||||||
StaticConfig::aliases(&raw);
|
|
||||||
StaticConfig::deprecated(&raw);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SubCommand::Stop(arg) => {
|
SubCommand::Stop(arg) => {
|
||||||
if arg.whkd {
|
if arg.whkd {
|
||||||
@@ -1991,34 +1888,6 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
|||||||
}
|
}
|
||||||
|
|
||||||
send_message(&SocketMessage::Stop.as_bytes()?)?;
|
send_message(&SocketMessage::Stop.as_bytes()?)?;
|
||||||
let mut system = sysinfo::System::new_all();
|
|
||||||
system.refresh_processes();
|
|
||||||
|
|
||||||
if system.processes_by_name("komorebi.exe").count() >= 1 {
|
|
||||||
println!("komorebi is still running, attempting to force-quit");
|
|
||||||
|
|
||||||
let script = r"
|
|
||||||
Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|
||||||
";
|
|
||||||
match powershell_script::run(script) {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("{script}");
|
|
||||||
|
|
||||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
|
||||||
|
|
||||||
let file = File::open(hwnd_json)?;
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
let hwnds: Vec<isize> = serde_json::from_reader(reader)?;
|
|
||||||
|
|
||||||
for hwnd in hwnds {
|
|
||||||
restore_window(HWND(hwnd));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
println!("Error: {error}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SubCommand::FloatRule(arg) => {
|
SubCommand::FloatRule(arg) => {
|
||||||
send_message(&SocketMessage::FloatRule(arg.identifier, arg.id).as_bytes()?)?;
|
send_message(&SocketMessage::FloatRule(arg.identifier, arg.id).as_bytes()?)?;
|
||||||
@@ -2149,12 +2018,6 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|||||||
SubCommand::State => {
|
SubCommand::State => {
|
||||||
print_query(&SocketMessage::State.as_bytes()?);
|
print_query(&SocketMessage::State.as_bytes()?);
|
||||||
}
|
}
|
||||||
SubCommand::GlobalState => {
|
|
||||||
print_query(&SocketMessage::GlobalState.as_bytes()?);
|
|
||||||
}
|
|
||||||
SubCommand::Gui => {
|
|
||||||
Command::new("komorebi-gui").spawn()?;
|
|
||||||
}
|
|
||||||
SubCommand::VisibleWindows => {
|
SubCommand::VisibleWindows => {
|
||||||
print_query(&SocketMessage::VisibleWindows.as_bytes()?);
|
print_query(&SocketMessage::VisibleWindows.as_bytes()?);
|
||||||
}
|
}
|
||||||
@@ -2193,6 +2056,9 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|||||||
SubCommand::CompleteConfiguration => {
|
SubCommand::CompleteConfiguration => {
|
||||||
send_message(&SocketMessage::CompleteConfiguration.as_bytes()?)?;
|
send_message(&SocketMessage::CompleteConfiguration.as_bytes()?)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::AltFocusHack(arg) => {
|
||||||
|
send_message(&SocketMessage::AltFocusHack(arg.boolean_state.into()).as_bytes()?)?;
|
||||||
|
}
|
||||||
SubCommand::IdentifyObjectNameChangeApplication(target) => {
|
SubCommand::IdentifyObjectNameChangeApplication(target) => {
|
||||||
send_message(
|
send_message(
|
||||||
&SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)
|
&SocketMessage::IdentifyObjectNameChangeApplication(target.identifier, target.id)
|
||||||
@@ -2210,6 +2076,12 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|||||||
.as_bytes()?,
|
.as_bytes()?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::IdentifyBorderOverflowApplication(target) => {
|
||||||
|
send_message(
|
||||||
|
&SocketMessage::IdentifyBorderOverflowApplication(target.identifier, target.id)
|
||||||
|
.as_bytes()?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
SubCommand::RemoveTitleBar(target) => {
|
SubCommand::RemoveTitleBar(target) => {
|
||||||
match target.identifier {
|
match target.identifier {
|
||||||
ApplicationIdentifier::Exe => {}
|
ApplicationIdentifier::Exe => {}
|
||||||
@@ -2259,19 +2131,20 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|||||||
SubCommand::MouseFollowsFocus(arg) => {
|
SubCommand::MouseFollowsFocus(arg) => {
|
||||||
send_message(&SocketMessage::MouseFollowsFocus(arg.boolean_state.into()).as_bytes()?)?;
|
send_message(&SocketMessage::MouseFollowsFocus(arg.boolean_state.into()).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::Border(arg) => {
|
SubCommand::ActiveWindowBorder(arg) => {
|
||||||
send_message(&SocketMessage::Border(arg.boolean_state.into()).as_bytes()?)?;
|
send_message(&SocketMessage::ActiveWindowBorder(arg.boolean_state.into()).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::BorderColour(arg) => {
|
SubCommand::ActiveWindowBorderColour(arg) => {
|
||||||
send_message(
|
send_message(
|
||||||
&SocketMessage::BorderColour(arg.window_kind, arg.r, arg.g, arg.b).as_bytes()?,
|
&SocketMessage::ActiveWindowBorderColour(arg.window_kind, arg.r, arg.g, arg.b)
|
||||||
|
.as_bytes()?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
SubCommand::BorderWidth(arg) => {
|
SubCommand::ActiveWindowBorderWidth(arg) => {
|
||||||
send_message(&SocketMessage::BorderWidth(arg.width).as_bytes()?)?;
|
send_message(&SocketMessage::ActiveWindowBorderWidth(arg.width).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::BorderOffset(arg) => {
|
SubCommand::ActiveWindowBorderOffset(arg) => {
|
||||||
send_message(&SocketMessage::BorderOffset(arg.offset).as_bytes()?)?;
|
send_message(&SocketMessage::ActiveWindowBorderOffset(arg.offset).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::ResizeDelta(arg) => {
|
SubCommand::ResizeDelta(arg) => {
|
||||||
send_message(&SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
|
send_message(&SocketMessage::ResizeDelta(arg.pixels).as_bytes()?)?;
|
||||||
@@ -2400,10 +2273,6 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|||||||
SubCommand::GenerateStaticConfig => {
|
SubCommand::GenerateStaticConfig => {
|
||||||
print_query(&SocketMessage::GenerateStaticConfig.as_bytes()?);
|
print_query(&SocketMessage::GenerateStaticConfig.as_bytes()?);
|
||||||
}
|
}
|
||||||
// Deprecated
|
|
||||||
SubCommand::AltFocusHack(_) | SubCommand::IdentifyBorderOverflowApplication(_) => {
|
|
||||||
println!("Command deprecated - this is now automatically handled by komorebi! 🎉");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
314
mkdocs.yml
314
mkdocs.yml
@@ -39,176 +39,166 @@ theme:
|
|||||||
- search.share
|
- search.share
|
||||||
- search.suggest
|
- search.suggest
|
||||||
- toc.follow
|
- toc.follow
|
||||||
markdown_extensions:
|
|
||||||
- admonition
|
|
||||||
- pymdownx.highlight
|
|
||||||
- pymdownx.superfences
|
|
||||||
plugins:
|
plugins:
|
||||||
- macros
|
- macros
|
||||||
- search
|
- search
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Komorebi:
|
- Komorebi:
|
||||||
- index.md
|
- index.md
|
||||||
- Design: design.md
|
- Design: design.md
|
||||||
- Getting started:
|
- Getting started:
|
||||||
- Installation: installation.md
|
- Installation: installation.md
|
||||||
- Example configurations: example-configurations.md
|
- Example configurations: example-configurations.md
|
||||||
- Troubleshooting: troubleshooting.md
|
|
||||||
- Common workflows:
|
- Common workflows:
|
||||||
- common-workflows/komorebi-config-home.md
|
- common-workflows/komorebi-config-home.md
|
||||||
- common-workflows/borders.md
|
- common-workflows/active-window-border.md
|
||||||
- common-workflows/stackbar.md
|
- common-workflows/remove-gaps.md
|
||||||
- common-workflows/remove-gaps.md
|
- common-workflows/ignore-windows.md
|
||||||
- common-workflows/ignore-windows.md
|
- common-workflows/force-manage-windows.md
|
||||||
- common-workflows/force-manage-windows.md
|
- common-workflows/tray-and-multi-window-applications.md
|
||||||
- common-workflows/tray-and-multi-window-applications.md
|
- common-workflows/focus-follows-mouse.md
|
||||||
- common-workflows/focus-follows-mouse.md
|
- common-workflows/mouse-follows-focus.md
|
||||||
- common-workflows/mouse-follows-focus.md
|
- common-workflows/custom-layouts.md
|
||||||
- common-workflows/custom-layouts.md
|
- common-workflows/dynamic-layout-switching.md
|
||||||
- common-workflows/dynamic-layout-switching.md
|
# - common-workflows/autohotkey.md
|
||||||
- common-workflows/autohotkey.md
|
|
||||||
- Release notes:
|
- Release notes:
|
||||||
- release/v0-1-22.md
|
- release/v0-1-22.md
|
||||||
- Configuration reference: https://komorebi.lgug2z.com/schema
|
- Configuration reference: https://komorebi.lgug2z.com/schema
|
||||||
- CLI reference:
|
- CLI reference:
|
||||||
- cli/quickstart.md
|
- cli/quickstart.md
|
||||||
- cli/start.md
|
- cli/start.md
|
||||||
- cli/stop.md
|
- cli/stop.md
|
||||||
- cli/check.md
|
- cli/check.md
|
||||||
- cli/configuration.md
|
- cli/configuration.md
|
||||||
- cli/whkdrc.md
|
- cli/whkdrc.md
|
||||||
- cli/state.md
|
- cli/state.md
|
||||||
- cli/global-state.md
|
- cli/visible-windows.md
|
||||||
- cli/gui.md
|
- cli/query.md
|
||||||
- cli/visible-windows.md
|
- cli/subscribe-socket.md
|
||||||
- cli/query.md
|
- cli/unsubscribe-socket.md
|
||||||
- cli/subscribe-socket.md
|
- cli/subscribe-pipe.md
|
||||||
- cli/unsubscribe-socket.md
|
- cli/unsubscribe-pipe.md
|
||||||
- cli/subscribe-pipe.md
|
- cli/log.md
|
||||||
- cli/unsubscribe-pipe.md
|
- cli/quick-save-resize.md
|
||||||
- cli/log.md
|
- cli/quick-load-resize.md
|
||||||
- cli/quick-save-resize.md
|
- cli/save-resize.md
|
||||||
- cli/quick-load-resize.md
|
- cli/load-resize.md
|
||||||
- cli/save-resize.md
|
- cli/focus.md
|
||||||
- cli/load-resize.md
|
- cli/move.md
|
||||||
- cli/focus.md
|
- cli/minimize.md
|
||||||
- cli/move.md
|
- cli/close.md
|
||||||
- cli/minimize.md
|
- cli/force-focus.md
|
||||||
- cli/close.md
|
- cli/cycle-focus.md
|
||||||
- cli/force-focus.md
|
- cli/cycle-move.md
|
||||||
- cli/cycle-focus.md
|
- cli/stack.md
|
||||||
- cli/cycle-move.md
|
- cli/resize-edge.md
|
||||||
- cli/stack.md
|
- cli/resize-axis.md
|
||||||
- cli/resize-edge.md
|
- cli/unstack.md
|
||||||
- cli/resize-axis.md
|
- cli/cycle-stack.md
|
||||||
- cli/unstack.md
|
- cli/move-to-monitor.md
|
||||||
- cli/cycle-stack.md
|
- cli/cycle-move-to-monitor.md
|
||||||
- cli/move-to-monitor.md
|
- cli/move-to-workspace.md
|
||||||
- cli/cycle-move-to-monitor.md
|
- cli/move-to-named-workspace.md
|
||||||
- cli/move-to-workspace.md
|
- cli/cycle-move-to-workspace.md
|
||||||
- cli/move-to-named-workspace.md
|
- cli/send-to-monitor.md
|
||||||
- cli/cycle-move-to-workspace.md
|
- cli/cycle-send-to-monitor.md
|
||||||
- cli/send-to-monitor.md
|
- cli/send-to-workspace.md
|
||||||
- cli/cycle-send-to-monitor.md
|
- cli/send-to-named-workspace.md
|
||||||
- cli/send-to-workspace.md
|
- cli/cycle-send-to-workspace.md
|
||||||
- cli/send-to-named-workspace.md
|
- cli/send-to-monitor-workspace.md
|
||||||
- cli/cycle-send-to-workspace.md
|
- cli/focus-monitor.md
|
||||||
- cli/send-to-monitor-workspace.md
|
- cli/focus-last-workspace.md
|
||||||
- cli/move-to-monitor-workspace.md
|
- cli/focus-workspace.md
|
||||||
- cli/focus-monitor.md
|
- cli/focus-workspaces.md
|
||||||
- cli/focus-last-workspace.md
|
- cli/focus-monitor-workspace.md
|
||||||
- cli/focus-workspace.md
|
- cli/focus-named-workspace.md
|
||||||
- cli/focus-workspaces.md
|
- cli/cycle-monitor.md
|
||||||
- cli/focus-monitor-workspace.md
|
- cli/cycle-workspace.md
|
||||||
- cli/focus-named-workspace.md
|
- cli/move-workspace-to-monitor.md
|
||||||
- cli/cycle-monitor.md
|
- cli/swap-workspaces-with-monitor.md
|
||||||
- cli/cycle-workspace.md
|
- cli/new-workspace.md
|
||||||
- cli/move-workspace-to-monitor.md
|
- cli/resize-delta.md
|
||||||
- cli/cycle-move-workspace-to-monitor.md
|
- cli/invisible-borders.md
|
||||||
- cli/swap-workspaces-with-monitor.md
|
- cli/global-work-area-offset.md
|
||||||
- cli/new-workspace.md
|
- cli/monitor-work-area-offset.md
|
||||||
- cli/resize-delta.md
|
- cli/focused-workspace-container-padding.md
|
||||||
- cli/invisible-borders.md
|
- cli/focused-workspace-padding.md
|
||||||
- cli/global-work-area-offset.md
|
- cli/adjust-container-padding.md
|
||||||
- cli/monitor-work-area-offset.md
|
- cli/adjust-workspace-padding.md
|
||||||
- cli/focused-workspace-container-padding.md
|
- cli/change-layout.md
|
||||||
- cli/focused-workspace-padding.md
|
- cli/cycle-layout.md
|
||||||
- cli/adjust-container-padding.md
|
- cli/load-custom-layout.md
|
||||||
- cli/adjust-workspace-padding.md
|
- cli/flip-layout.md
|
||||||
- cli/change-layout.md
|
- cli/promote.md
|
||||||
- cli/cycle-layout.md
|
- cli/promote-focus.md
|
||||||
- cli/load-custom-layout.md
|
- cli/retile.md
|
||||||
- cli/flip-layout.md
|
- cli/monitor-index-preference.md
|
||||||
- cli/promote.md
|
- cli/display-index-preference.md
|
||||||
- cli/promote-focus.md
|
- cli/ensure-workspaces.md
|
||||||
- cli/promote-window.md
|
- cli/ensure-named-workspaces.md
|
||||||
- cli/retile.md
|
- cli/container-padding.md
|
||||||
- cli/monitor-index-preference.md
|
- cli/named-workspace-container-padding.md
|
||||||
- cli/display-index-preference.md
|
- cli/workspace-padding.md
|
||||||
- cli/ensure-workspaces.md
|
- cli/named-workspace-padding.md
|
||||||
- cli/ensure-named-workspaces.md
|
- cli/workspace-layout.md
|
||||||
- cli/container-padding.md
|
- cli/named-workspace-layout.md
|
||||||
- cli/named-workspace-container-padding.md
|
- cli/workspace-custom-layout.md
|
||||||
- cli/workspace-padding.md
|
- cli/named-workspace-custom-layout.md
|
||||||
- cli/named-workspace-padding.md
|
- cli/workspace-layout-rule.md
|
||||||
- cli/workspace-layout.md
|
- cli/named-workspace-layout-rule.md
|
||||||
- cli/named-workspace-layout.md
|
- cli/workspace-custom-layout-rule.md
|
||||||
- cli/workspace-custom-layout.md
|
- cli/named-workspace-custom-layout-rule.md
|
||||||
- cli/named-workspace-custom-layout.md
|
- cli/clear-workspace-layout-rules.md
|
||||||
- cli/workspace-layout-rule.md
|
- cli/clear-named-workspace-layout-rules.md
|
||||||
- cli/named-workspace-layout-rule.md
|
- cli/workspace-tiling.md
|
||||||
- cli/workspace-custom-layout-rule.md
|
- cli/named-workspace-tiling.md
|
||||||
- cli/named-workspace-custom-layout-rule.md
|
- cli/workspace-name.md
|
||||||
- cli/clear-workspace-layout-rules.md
|
- cli/toggle-window-container-behaviour.md
|
||||||
- cli/clear-named-workspace-layout-rules.md
|
- cli/toggle-pause.md
|
||||||
- cli/workspace-tiling.md
|
- cli/toggle-tiling.md
|
||||||
- cli/named-workspace-tiling.md
|
- cli/toggle-float.md
|
||||||
- cli/workspace-name.md
|
- cli/toggle-monocle.md
|
||||||
- cli/toggle-window-container-behaviour.md
|
- cli/toggle-maximize.md
|
||||||
- cli/toggle-pause.md
|
- cli/restore-windows.md
|
||||||
- cli/toggle-tiling.md
|
- cli/manage.md
|
||||||
- cli/toggle-float.md
|
- cli/unmanage.md
|
||||||
- cli/toggle-monocle.md
|
- cli/reload-configuration.md
|
||||||
- cli/toggle-maximize.md
|
- cli/watch-configuration.md
|
||||||
- cli/restore-windows.md
|
- cli/complete-configuration.md
|
||||||
- cli/manage.md
|
- cli/window-hiding-behaviour.md
|
||||||
- cli/unmanage.md
|
- cli/cross-monitor-move-behaviour.md
|
||||||
- cli/reload-configuration.md
|
- cli/toggle-cross-monitor-move-behaviour.md
|
||||||
- cli/watch-configuration.md
|
- cli/unmanaged-window-operation-behaviour.md
|
||||||
- cli/complete-configuration.md
|
- cli/float-rule.md
|
||||||
- cli/window-hiding-behaviour.md
|
- cli/manage-rule.md
|
||||||
- cli/cross-monitor-move-behaviour.md
|
- cli/initial-workspace-rule.md
|
||||||
- cli/toggle-cross-monitor-move-behaviour.md
|
- cli/initial-named-workspace-rule.md
|
||||||
- cli/unmanaged-window-operation-behaviour.md
|
- cli/workspace-rule.md
|
||||||
- cli/float-rule.md
|
- cli/named-workspace-rule.md
|
||||||
- cli/manage-rule.md
|
- cli/identify-object-name-change-application.md
|
||||||
- cli/initial-workspace-rule.md
|
- cli/identify-tray-application.md
|
||||||
- cli/initial-named-workspace-rule.md
|
- cli/identify-layered-application.md
|
||||||
- cli/workspace-rule.md
|
- cli/remove-title-bar.md
|
||||||
- cli/named-workspace-rule.md
|
- cli/toggle-title-bars.md
|
||||||
- cli/identify-object-name-change-application.md
|
- cli/identify-border-overflow-application.md
|
||||||
- cli/identify-tray-application.md
|
- cli/active-window-border.md
|
||||||
- cli/identify-layered-application.md
|
- cli/active-window-border-colour.md
|
||||||
- cli/remove-title-bar.md
|
- cli/active-window-border-width.md
|
||||||
- cli/toggle-title-bars.md
|
- cli/active-window-border-offset.md
|
||||||
- cli/border.md
|
- cli/focus-follows-mouse.md
|
||||||
- cli/border-colour.md
|
- cli/toggle-focus-follows-mouse.md
|
||||||
- cli/border-width.md
|
- cli/mouse-follows-focus.md
|
||||||
- cli/border-offset.md
|
- cli/toggle-mouse-follows-focus.md
|
||||||
- cli/focus-follows-mouse.md
|
- cli/ahk-library.md
|
||||||
- cli/toggle-focus-follows-mouse.md
|
- cli/ahk-app-specific-configuration.md
|
||||||
- cli/mouse-follows-focus.md
|
- cli/pwsh-app-specific-configuration.md
|
||||||
- cli/toggle-mouse-follows-focus.md
|
- cli/format-app-specific-configuration.md
|
||||||
- cli/ahk-library.md
|
- cli/fetch-app-specific-configuration.md
|
||||||
- cli/ahk-app-specific-configuration.md
|
- cli/application-specific-configuration-schema.md
|
||||||
- cli/pwsh-app-specific-configuration.md
|
- cli/notification-schema.md
|
||||||
- cli/format-app-specific-configuration.md
|
- cli/socket-schema.md
|
||||||
- cli/fetch-app-specific-configuration.md
|
- cli/static-config-schema.md
|
||||||
- cli/application-specific-configuration-schema.md
|
- cli/generate-static-config.md
|
||||||
- cli/notification-schema.md
|
- cli/enable-autostart.md
|
||||||
- cli/socket-schema.md
|
- cli/disable-autostart.md
|
||||||
- cli/static-config-schema.md
|
|
||||||
- cli/generate-static-config.md
|
|
||||||
- cli/enable-autostart.md
|
|
||||||
- cli/disable-autostart.md
|
|
||||||
|
|||||||
789
schema.json
789
schema.json
@@ -1,20 +1,21 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "StaticConfig",
|
"title": "StaticConfig",
|
||||||
"description": "The `komorebi.json` static configuration file reference for `v0.1.26`",
|
"description": "The `komorebi.json` static configuration file reference for `v0.1.20`",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"app_specific_configuration_path": {
|
"active_window_border": {
|
||||||
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"description": "Display an active window border (default: false)",
|
"description": "Display an active window border (default: false)",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"border_colours": {
|
"active_window_border_colours": {
|
||||||
"description": "Active window border colours for different container types",
|
"description": "Active window border colours for different container types",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"monocle",
|
||||||
|
"single",
|
||||||
|
"stack"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"monocle": {
|
"monocle": {
|
||||||
"description": "Border colour when the container is in monocle mode",
|
"description": "Border colour when the container is in monocle mode",
|
||||||
@@ -129,47 +130,13 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"unfocused": {
|
|
||||||
"description": "Border colour when the container is unfocused",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"description": "Colour represented as RGB",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"b",
|
|
||||||
"g",
|
|
||||||
"r"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"b": {
|
|
||||||
"description": "Blue",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"g": {
|
|
||||||
"description": "Green",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"r": {
|
|
||||||
"description": "Red",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Colour represented as Hex",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app_specific_configuration_path": {
|
||||||
|
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"border_offset": {
|
"border_offset": {
|
||||||
"description": "Offset of the window border (default: -1)",
|
"description": "Offset of the window border (default: -1)",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -179,126 +146,43 @@
|
|||||||
"description": "Identify border overflow applications",
|
"description": "Identify border overflow applications",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"type": "object",
|
"id",
|
||||||
"required": [
|
"kind"
|
||||||
"id",
|
],
|
||||||
"kind"
|
"properties": {
|
||||||
],
|
"id": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Exe",
|
|
||||||
"Class",
|
|
||||||
"Title",
|
|
||||||
"Path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"enum": [
|
||||||
"type": "object",
|
"Exe",
|
||||||
"required": [
|
"Class",
|
||||||
"id",
|
"Title",
|
||||||
"kind"
|
"Path"
|
||||||
],
|
]
|
||||||
"properties": {
|
},
|
||||||
"id": {
|
"matching_strategy": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": [
|
||||||
"kind": {
|
"Legacy",
|
||||||
"type": "string",
|
"Equals",
|
||||||
"enum": [
|
"StartsWith",
|
||||||
"Exe",
|
"EndsWith",
|
||||||
"Class",
|
"Contains",
|
||||||
"Title",
|
"Regex"
|
||||||
"Path"
|
]
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"border_style": {
|
|
||||||
"description": "Active window border style (default: System)",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "Use the system border style",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"System"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Use the Windows 11-style rounded borders",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Rounded"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Use the Windows 10-style square borders",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Square"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"border_width": {
|
"border_width": {
|
||||||
"description": "Width of the window border (default: 8)",
|
"description": "Width of the window border (default: 8)",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
},
|
},
|
||||||
"border_z_order": {
|
|
||||||
"description": "Active window border z-order (default: System)",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Top",
|
|
||||||
"NoTopMost",
|
|
||||||
"Bottom",
|
|
||||||
"TopMost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cross_monitor_move_behaviour": {
|
"cross_monitor_move_behaviour": {
|
||||||
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
|
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
@@ -315,13 +199,6 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Insert"
|
"Insert"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Do nothing if trying to move a window container in the direction of an adjacent monitor",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"NoOp"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -346,83 +223,36 @@
|
|||||||
"description": "Individual window floating rules",
|
"description": "Individual window floating rules",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"type": "object",
|
"id",
|
||||||
"required": [
|
"kind"
|
||||||
"id",
|
],
|
||||||
"kind"
|
"properties": {
|
||||||
],
|
"id": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Exe",
|
|
||||||
"Class",
|
|
||||||
"Title",
|
|
||||||
"Path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"enum": [
|
||||||
"type": "object",
|
"Exe",
|
||||||
"required": [
|
"Class",
|
||||||
"id",
|
"Title",
|
||||||
"kind"
|
"Path"
|
||||||
],
|
]
|
||||||
"properties": {
|
},
|
||||||
"id": {
|
"matching_strategy": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": [
|
||||||
"kind": {
|
"Legacy",
|
||||||
"type": "string",
|
"Equals",
|
||||||
"enum": [
|
"StartsWith",
|
||||||
"Exe",
|
"EndsWith",
|
||||||
"Class",
|
"Contains",
|
||||||
"Title",
|
"Regex"
|
||||||
"Path"
|
]
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"focus_follows_mouse": {
|
"focus_follows_mouse": {
|
||||||
@@ -512,166 +342,72 @@
|
|||||||
"description": "Identify applications that have the WS_EX_LAYERED extended window style",
|
"description": "Identify applications that have the WS_EX_LAYERED extended window style",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"type": "object",
|
"id",
|
||||||
"required": [
|
"kind"
|
||||||
"id",
|
],
|
||||||
"kind"
|
"properties": {
|
||||||
],
|
"id": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Exe",
|
|
||||||
"Class",
|
|
||||||
"Title",
|
|
||||||
"Path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"enum": [
|
||||||
"type": "object",
|
"Exe",
|
||||||
"required": [
|
"Class",
|
||||||
"id",
|
"Title",
|
||||||
"kind"
|
"Path"
|
||||||
],
|
]
|
||||||
"properties": {
|
},
|
||||||
"id": {
|
"matching_strategy": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": [
|
||||||
"kind": {
|
"Legacy",
|
||||||
"type": "string",
|
"Equals",
|
||||||
"enum": [
|
"StartsWith",
|
||||||
"Exe",
|
"EndsWith",
|
||||||
"Class",
|
"Contains",
|
||||||
"Title",
|
"Regex"
|
||||||
"Path"
|
]
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"manage_rules": {
|
"manage_rules": {
|
||||||
"description": "Individual window force-manage rules",
|
"description": "Individual window force-manage rules",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"type": "object",
|
"id",
|
||||||
"required": [
|
"kind"
|
||||||
"id",
|
],
|
||||||
"kind"
|
"properties": {
|
||||||
],
|
"id": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Exe",
|
|
||||||
"Class",
|
|
||||||
"Title",
|
|
||||||
"Path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"enum": [
|
||||||
"type": "object",
|
"Exe",
|
||||||
"required": [
|
"Class",
|
||||||
"id",
|
"Title",
|
||||||
"kind"
|
"Path"
|
||||||
],
|
]
|
||||||
"properties": {
|
},
|
||||||
"id": {
|
"matching_strategy": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": [
|
||||||
"kind": {
|
"Legacy",
|
||||||
"type": "string",
|
"Equals",
|
||||||
"enum": [
|
"StartsWith",
|
||||||
"Exe",
|
"EndsWith",
|
||||||
"Class",
|
"Contains",
|
||||||
"Title",
|
"Regex"
|
||||||
"Path"
|
]
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"monitor_index_preferences": {
|
"monitor_index_preferences": {
|
||||||
@@ -718,43 +454,6 @@
|
|||||||
"workspaces"
|
"workspaces"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"window_based_work_area_offset": {
|
|
||||||
"description": "Window based work area offset (default: None)",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"bottom",
|
|
||||||
"left",
|
|
||||||
"right",
|
|
||||||
"top"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"bottom": {
|
|
||||||
"description": "The bottom point in a Win32 Rect",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"left": {
|
|
||||||
"description": "The left point in a Win32 Rect",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"description": "The right point in a Win32 Rect",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"top": {
|
|
||||||
"description": "The top point in a Win32 Rect",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"window_based_work_area_offset_limit": {
|
|
||||||
"description": "Open window limit after which the window based work area offset will no longer be applied (default: 1)",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int"
|
|
||||||
},
|
|
||||||
"work_area_offset": {
|
"work_area_offset": {
|
||||||
"description": "Monitor-specific work area offset (default: None)",
|
"description": "Monitor-specific work area offset (default: None)",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -842,11 +541,7 @@
|
|||||||
"StartsWith",
|
"StartsWith",
|
||||||
"EndsWith",
|
"EndsWith",
|
||||||
"Contains",
|
"Contains",
|
||||||
"Regex",
|
"Regex"
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -862,8 +557,7 @@
|
|||||||
"VerticalStack",
|
"VerticalStack",
|
||||||
"HorizontalStack",
|
"HorizontalStack",
|
||||||
"UltrawideVerticalStack",
|
"UltrawideVerticalStack",
|
||||||
"Grid",
|
"Grid"
|
||||||
"RightMainVerticalStack"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"layout_rules": {
|
"layout_rules": {
|
||||||
@@ -878,8 +572,7 @@
|
|||||||
"VerticalStack",
|
"VerticalStack",
|
||||||
"HorizontalStack",
|
"HorizontalStack",
|
||||||
"UltrawideVerticalStack",
|
"UltrawideVerticalStack",
|
||||||
"Grid",
|
"Grid"
|
||||||
"RightMainVerticalStack"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -922,11 +615,7 @@
|
|||||||
"StartsWith",
|
"StartsWith",
|
||||||
"EndsWith",
|
"EndsWith",
|
||||||
"Contains",
|
"Contains",
|
||||||
"Regex",
|
"Regex"
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -946,83 +635,36 @@
|
|||||||
"description": "Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)",
|
"description": "Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"type": "object",
|
"id",
|
||||||
"required": [
|
"kind"
|
||||||
"id",
|
],
|
||||||
"kind"
|
"properties": {
|
||||||
],
|
"id": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Exe",
|
|
||||||
"Class",
|
|
||||||
"Title",
|
|
||||||
"Path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"enum": [
|
||||||
"type": "object",
|
"Exe",
|
||||||
"required": [
|
"Class",
|
||||||
"id",
|
"Title",
|
||||||
"kind"
|
"Path"
|
||||||
],
|
]
|
||||||
"properties": {
|
},
|
||||||
"id": {
|
"matching_strategy": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": [
|
||||||
"kind": {
|
"Legacy",
|
||||||
"type": "string",
|
"Equals",
|
||||||
"enum": [
|
"StartsWith",
|
||||||
"Exe",
|
"EndsWith",
|
||||||
"Class",
|
"Contains",
|
||||||
"Title",
|
"Regex"
|
||||||
"Path"
|
]
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resize_delta": {
|
"resize_delta": {
|
||||||
@@ -1031,24 +673,13 @@
|
|||||||
"format": "int32"
|
"format": "int32"
|
||||||
},
|
},
|
||||||
"stackbar": {
|
"stackbar": {
|
||||||
"description": "Stackbar configuration options",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"height": {
|
"height": {
|
||||||
"description": "Stackbar height",
|
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
},
|
},
|
||||||
"label": {
|
|
||||||
"description": "Stackbar height",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Process",
|
|
||||||
"Title"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mode": {
|
"mode": {
|
||||||
"description": "Stackbar mode",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Always",
|
"Always",
|
||||||
@@ -1057,11 +688,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"description": "Stackbar tab configuration options",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"background": {
|
"background": {
|
||||||
"description": "Tab background colour",
|
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "Colour represented as RGB",
|
"description": "Colour represented as RGB",
|
||||||
@@ -1099,7 +728,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"focused_text": {
|
"focused_text": {
|
||||||
"description": "Focused tab text colour",
|
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "Colour represented as RGB",
|
"description": "Colour represented as RGB",
|
||||||
@@ -1137,7 +765,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"unfocused_text": {
|
"unfocused_text": {
|
||||||
"description": "Unfocused tab text colour",
|
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "Colour represented as RGB",
|
"description": "Colour represented as RGB",
|
||||||
@@ -1175,7 +802,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"description": "Width of a stackbar tab",
|
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
@@ -1187,83 +813,36 @@
|
|||||||
"description": "Identify tray and multi-window applications",
|
"description": "Identify tray and multi-window applications",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"type": "object",
|
"id",
|
||||||
"required": [
|
"kind"
|
||||||
"id",
|
],
|
||||||
"kind"
|
"properties": {
|
||||||
],
|
"id": {
|
||||||
"properties": {
|
"type": "string"
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Exe",
|
|
||||||
"Class",
|
|
||||||
"Title",
|
|
||||||
"Path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"enum": [
|
||||||
"type": "object",
|
"Exe",
|
||||||
"required": [
|
"Class",
|
||||||
"id",
|
"Title",
|
||||||
"kind"
|
"Path"
|
||||||
],
|
]
|
||||||
"properties": {
|
},
|
||||||
"id": {
|
"matching_strategy": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"enum": [
|
||||||
"kind": {
|
"Legacy",
|
||||||
"type": "string",
|
"Equals",
|
||||||
"enum": [
|
"StartsWith",
|
||||||
"Exe",
|
"EndsWith",
|
||||||
"Class",
|
"Contains",
|
||||||
"Title",
|
"Regex"
|
||||||
"Path"
|
]
|
||||||
]
|
|
||||||
},
|
|
||||||
"matching_strategy": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Legacy",
|
|
||||||
"Equals",
|
|
||||||
"StartsWith",
|
|
||||||
"EndsWith",
|
|
||||||
"Contains",
|
|
||||||
"Regex",
|
|
||||||
"DoesNotEndWith",
|
|
||||||
"DoesNotStartWith",
|
|
||||||
"DoesNotEqual",
|
|
||||||
"DoesNotContain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"unmanaged_window_operation_behaviour": {
|
"unmanaged_window_operation_behaviour": {
|
||||||
|
|||||||
11
shell.nix
11
shell.nix
@@ -1,11 +0,0 @@
|
|||||||
{pkgs ? import <nixpkgs> {}}:
|
|
||||||
with pkgs;
|
|
||||||
mkShell {
|
|
||||||
name = "komorebi";
|
|
||||||
|
|
||||||
buildInputs = [
|
|
||||||
python311Packages.mkdocs-material
|
|
||||||
python311Packages.mkdocs-macros
|
|
||||||
python311Packages.setuptools
|
|
||||||
];
|
|
||||||
}
|
|
||||||
BIN
wix/License.rtf
BIN
wix/License.rtf
Binary file not shown.
@@ -95,9 +95,6 @@
|
|||||||
<Component Id='binary2' Guid='*'>
|
<Component Id='binary2' Guid='*'>
|
||||||
<File Id='exe2' Name='komorebic-no-console.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic-no-console.exe' KeyPath='yes' />
|
<File Id='exe2' Name='komorebic-no-console.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebic-no-console.exe' KeyPath='yes' />
|
||||||
</Component>
|
</Component>
|
||||||
<Component Id='binary3' Guid='*'>
|
|
||||||
<File Id='exe3' Name='komorebi-gui.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-gui.exe' KeyPath='yes' />
|
|
||||||
</Component>
|
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
@@ -116,8 +113,6 @@
|
|||||||
|
|
||||||
<ComponentRef Id='binary2' />
|
<ComponentRef Id='binary2' />
|
||||||
|
|
||||||
<ComponentRef Id='binary3' />
|
|
||||||
|
|
||||||
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
|
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
|
||||||
<ComponentRef Id='Path' />
|
<ComponentRef Id='Path' />
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|||||||
Reference in New Issue
Block a user