mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-16 06:16:43 +01:00
Compare commits
9 Commits
feature/st
...
feature/mk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1f443f05 | ||
|
|
10301d57d1 | ||
|
|
2e0c7430ef | ||
|
|
9a6f831d51 | ||
|
|
326a29e3ee | ||
|
|
daf9eeded1 | ||
|
|
cc4e448741 | ||
|
|
9d3e0a01b9 | ||
|
|
6f08ec1cb3 |
16
.github/workflows/windows.yaml
vendored
16
.github/workflows/windows.yaml
vendored
@@ -73,11 +73,6 @@ jobs:
|
|||||||
- name: Install the target
|
- name: Install the target
|
||||||
run: |
|
run: |
|
||||||
rustup target install ${{ matrix.target }}
|
rustup target install ${{ matrix.target }}
|
||||||
- name: Run Cargo checks
|
|
||||||
run: |
|
|
||||||
cargo fmt --check
|
|
||||||
cargo check
|
|
||||||
cargo clippy
|
|
||||||
- name: Run a full build
|
- name: Run a full build
|
||||||
run: |
|
run: |
|
||||||
cargo build --locked --release --target ${{ matrix.target }}
|
cargo build --locked --release --target ${{ matrix.target }}
|
||||||
@@ -92,17 +87,10 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
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/komorebi.pdb
|
target/${{ matrix.target }}/release/komorebi.pdb
|
||||||
target/${{ matrix.target }}/release/komorebic.pdb
|
target/${{ matrix.target }}/release/komorebic.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
|
|
||||||
uses: goreleaser/goreleaser-action@v3
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: build --skip=validate --clean
|
|
||||||
|
|
||||||
# Release
|
# Release
|
||||||
- name: Generate changelog
|
- name: Generate changelog
|
||||||
@@ -116,12 +104,12 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --skip=validate --clean --release-notes=CHANGELOG.md
|
args: release --skip-validate --clean --release-notes=CHANGELOG.md
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
|
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
|
||||||
- name: Add MSI to release
|
- name: Add MSI to release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
with:
|
with:
|
||||||
files: "target/wix/komorebi-*.msi"
|
files: "target/wix/komorebi-*.msi"
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,5 +3,4 @@
|
|||||||
/target
|
/target
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
dummy.go
|
dummy.go
|
||||||
komorebi.ahk
|
komorebi.ahk
|
||||||
komorebic/applications.yaml
|
|
||||||
@@ -34,7 +34,7 @@ builds:
|
|||||||
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"
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"
|
- name_template: "{{ .ProjectName }}-{{ .Version }}-x86_64-pc-windows-msvc"
|
||||||
|
|||||||
687
Cargo.lock
generated
687
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,22 +4,20 @@ resolver = "2"
|
|||||||
members = [
|
members = [
|
||||||
"derive-ahk",
|
"derive-ahk",
|
||||||
"komorebi",
|
"komorebi",
|
||||||
"komorebi-client",
|
|
||||||
"komorebi-core",
|
"komorebi-core",
|
||||||
"komorebic",
|
"komorebic",
|
||||||
"komorebic-no-console",
|
"komorebic-no-console",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
windows-interface = { version = "0.53" }
|
windows-interface = { version = "0.52" }
|
||||||
windows-implement = { version = "0.53" }
|
windows-implement = { version = "0.52" }
|
||||||
dunce = "1"
|
dunce = "1"
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
serde_json = { package = "serde_json_lenient", version = "0.1" }
|
|
||||||
|
|
||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
version = "0.54"
|
version = "0.52"
|
||||||
features = [
|
features = [
|
||||||
"implement",
|
"implement",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
|
|||||||
202
README.md
202
README.md
@@ -82,8 +82,6 @@ A [detailed installation and quickstart
|
|||||||
guide](https://lgug2z.github.io/komorebi/installation.html) is available which shows how to get started
|
guide](https://lgug2z.github.io/komorebi/installation.html) is available which shows how to get started
|
||||||
using `scoop`, `winget` or building from source.
|
using `scoop`, `winget` or building from source.
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=H9-_c1egQ4g)
|
|
||||||
|
|
||||||
# Demonstrations
|
# Demonstrations
|
||||||
|
|
||||||
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
|
[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows
|
||||||
@@ -101,76 +99,20 @@ widget enabled. The original video can be viewed
|
|||||||
|
|
||||||
https://user-images.githubusercontent.com/13164844/163496414-a9cde3d1-b8a7-4a7a-96fb-a8985380bc70.mp4
|
https://user-images.githubusercontent.com/13164844/163496414-a9cde3d1-b8a7-4a7a-96fb-a8985380bc70.mp4
|
||||||
|
|
||||||
# Contribution Guidelines
|
# Development
|
||||||
|
|
||||||
If you would like to contribute to `komorebi` please take the time to carefully read the guidelines below.
|
If you would like to contribute code to this repository, there are a few requests that I have to ensure a foundation of
|
||||||
|
code quality, consistency and commit hygiene:
|
||||||
## Commit hygiene
|
|
||||||
|
|
||||||
- Flatten all `use` statements
|
- Flatten all `use` statements
|
||||||
- Run `cargo +stable clippy` and ensure that all lints and suggestions have been addressed before committing
|
- Run `cargo +nightly clippy` and ensure that all lints and suggestions have been addressed before committing
|
||||||
- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing
|
- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing
|
||||||
- Use `git cz` with
|
- Use `git cz` with
|
||||||
the [Commitizen CLI](https://github.com/commitizen/cz-cli#conventional-commit-messages-as-a-global-utility) to prepare
|
the [Commitizen CLI](https://github.com/commitizen/cz-cli#conventional-commit-messages-as-a-global-utility) to prepare
|
||||||
commit messages
|
commit messages
|
||||||
- Provide **at least** one short sentence or paragraph in your commit message body to describe your thought process for the
|
- Provide at least one short sentence or paragraph in your commit message body to describe your thought process for the
|
||||||
changes being committed
|
changes being committed
|
||||||
|
|
||||||
## PRs should contain only a single feature or bug fix
|
|
||||||
|
|
||||||
It is very difficult to review pull requests which touch multiple unrelated features and parts of the codebase.
|
|
||||||
|
|
||||||
Please do not submit pull requests like this; you will be asked to separate them into smaller PRs that deal only with
|
|
||||||
one feature or bug fix at a time.
|
|
||||||
|
|
||||||
If you are working on multiple features and bug fixes, I suggest that you cut a branch called `local-trunk`
|
|
||||||
from `master` which you keep up to date, and rebase the various independent branches you are working on onto that branch
|
|
||||||
if you want to test them together or create a build with everything integrated.
|
|
||||||
|
|
||||||
## Refactors to the codebase must have prior approval
|
|
||||||
|
|
||||||
`komorebi` is a mature codebase with an internal consistency and structure that has developed organically over close to
|
|
||||||
half a decade.
|
|
||||||
|
|
||||||
There are [countless hours of live coding videos](https://youtube.com/@LGUG2Z) demonstrating work on this project and
|
|
||||||
showing new contributors how to do everything from basic tasks like implementing new `komorebic` commands to
|
|
||||||
distinguishing monitors by manufacturer hardware identifiers and video card ports.
|
|
||||||
|
|
||||||
Refactors to the structure of the codebase are not taken lightly and require prior discussion and approval.
|
|
||||||
|
|
||||||
Please do not start refactoring the codebase with the expectation of having your changes integrated until you receive an
|
|
||||||
explicit approval or a request to do so.
|
|
||||||
|
|
||||||
Similarly, when implementing features and bug fixes, please stick to the structure of the codebase as much as possible
|
|
||||||
and do not take this as an opportunity to do some "refactoring along the way".
|
|
||||||
|
|
||||||
It is extremely difficult to review PRs for features and bug fixes if they are lost in sweeping changes to the structure
|
|
||||||
of the codebase.
|
|
||||||
|
|
||||||
## Breaking changes to user-facing interfaces are unacceptable
|
|
||||||
|
|
||||||
This includes but is not limited to:
|
|
||||||
|
|
||||||
- All `komorebic` commands
|
|
||||||
- The `komorebi.json` schema
|
|
||||||
- The [`komorebi-application-specific-configuration`](https://github.com/LGUG2Z/komorebi-application-specific-configuration)
|
|
||||||
schema
|
|
||||||
|
|
||||||
No user should ever find that their configuration file has stopped working after upgrading to a new version
|
|
||||||
of `komorebi`.
|
|
||||||
|
|
||||||
More often than not there are ways to reformulate changes that may initially seem like they require breaking user-facing
|
|
||||||
interfaces into additive changes.
|
|
||||||
|
|
||||||
For some inspiration please take a look
|
|
||||||
at [this commit](https://github.com/LGUG2Z/komorebi/commit/e7d928a065eb63bb4ea1fb864c69c1cae8cc763b) which added the
|
|
||||||
ability for users to specify colours in `komorebi.json` in Hex format alongside RGB.
|
|
||||||
|
|
||||||
There is also a process in place for graceful, non-breaking, deprecation of configuration options that are no longer
|
|
||||||
required.
|
|
||||||
|
|
||||||
# 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
|
||||||
the IDE for completions and navigation:
|
the IDE for completions and navigation:
|
||||||
|
|
||||||
@@ -209,21 +151,20 @@ found, information about it will appear in the log which can be shared when open
|
|||||||
# Window Manager State and Integrations
|
# Window Manager State and Integrations
|
||||||
|
|
||||||
The current state of the window manager can be queried using the `komorebic state` command, which returns a JSON
|
The current state of the window manager can be queried using the `komorebic state` command, which returns a JSON
|
||||||
representation of the `State` struct.
|
representation of the `State` struct, which includes the current state of `WindowManager`.
|
||||||
|
|
||||||
This may also be polled to build further integrations and widgets on top of.
|
This may also be polled to build further integrations and widgets on top of (if you ever wanted to build something
|
||||||
|
like [Stackline](https://github.com/AdamWagner/stackline) for Windows, you could do it by polling this command).
|
||||||
|
|
||||||
# Window Manager Event Subscriptions
|
# Window Manager Event Subscriptions
|
||||||
|
|
||||||
## Named Pipes
|
It is also possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
||||||
|
|
||||||
It is possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
|
||||||
by `komorebi` using [Named Pipes](https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes).
|
by `komorebi` using [Named Pipes](https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes).
|
||||||
|
|
||||||
First, your application must create a named pipe. Once the named pipe has been created, run the following command:
|
First, your application must create a named pipe. Once the named pipe has been created, run the following command:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
komorebic.exe subscribe-pipe <your pipe name>
|
komorebic.exe subscribe <your pipe name>
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that you do not have to include the full path of the named pipe, just the name.
|
Note that you do not have to include the full path of the named pipe, just the name.
|
||||||
@@ -247,125 +188,12 @@ You may then filter on the `type` key to listen to the events that you are inter
|
|||||||
notification types, refer to the enum variants of `WindowManagerEvent` in `komorebi` and `SocketMessage`
|
notification types, refer to the enum variants of `WindowManagerEvent` in `komorebi` and `SocketMessage`
|
||||||
in `komorebi-core`.
|
in `komorebi-core`.
|
||||||
|
|
||||||
Below is an example of how you can subscribe to and filter on events using a named pipe in `nodejs`.
|
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Python
|
||||||
|
by [@denBot](https://github.com/denBot) can be
|
||||||
|
found [here](https://gist.github.com/denBot/4136279812f87819f86d99eba77c1ee0).
|
||||||
|
|
||||||
```javascript
|
An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Rust can also be found
|
||||||
const { exec } = require("child_process");
|
in the [`komokana`](https://github.com/LGUG2Z/komokana) repository.
|
||||||
const net = require("net");
|
|
||||||
|
|
||||||
const pipeName = "\\\\.\\pipe\\komorebi-js";
|
|
||||||
const server = net.createServer((stream) => {
|
|
||||||
console.log("Client connected");
|
|
||||||
|
|
||||||
// Every time there is a workspace-related event, let's log the names of all
|
|
||||||
// workspaces on the currently focused monitor, and then log the name of the
|
|
||||||
// currently focused workspace on that monitor
|
|
||||||
|
|
||||||
stream.on("data", (data) => {
|
|
||||||
let json = JSON.parse(data.toString());
|
|
||||||
let event = json.event;
|
|
||||||
|
|
||||||
if (event.type.includes("Workspace")) {
|
|
||||||
let monitors = json.state.monitors;
|
|
||||||
let current_monitor = monitors.elements[monitors.focused];
|
|
||||||
let workspaces = monitors.elements[monitors.focused].workspaces;
|
|
||||||
let current_workspace = workspaces.elements[workspaces.focused];
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
workspaces.elements
|
|
||||||
.map((workspace) => workspace.name)
|
|
||||||
.filter((name) => name !== null)
|
|
||||||
);
|
|
||||||
console.log(current_workspace.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on("end", () => {
|
|
||||||
console.log("Client disconnected");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(pipeName, () => {
|
|
||||||
console.log("Named pipe server listening");
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = "komorebic subscribe-pipe komorebi-js";
|
|
||||||
|
|
||||||
exec(command, (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(`Error executing command: ${error}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unix Domain Sockets
|
|
||||||
|
|
||||||
It is possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled
|
|
||||||
by `komorebi` using [Unix Domain Sockets](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/).
|
|
||||||
|
|
||||||
UDS are also the only mode of communication between `komorebi` and `komorebic`.
|
|
||||||
|
|
||||||
First, your application must create a socket in `$ENV:LocalAppData\komorebi`. Once the socket has been created, run the
|
|
||||||
following command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
komorebic.exe subscribe-socket <your socket name>
|
|
||||||
```
|
|
||||||
|
|
||||||
If the socket exists, komorebi will start pushing JSON data of successfully handled events and messages as in the
|
|
||||||
example above in the Named Pipes section.
|
|
||||||
|
|
||||||
## Rust Client
|
|
||||||
|
|
||||||
As of `v0.1.22` it is possible to use the `komorebi-client` crate to subscribe to notifications of
|
|
||||||
every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust codebase.
|
|
||||||
|
|
||||||
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.22"}
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use komorebi_client::Notification;
|
|
||||||
use komorebi_client::NotificationEvent;
|
|
||||||
use komorebi_client::UnixListener;
|
|
||||||
use komorebi_client::WindowManagerEvent;
|
|
||||||
use std::io::BufRead;
|
|
||||||
use std::io::BufReader;
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
|
||||||
let socket = komorebi_client::subscribe(NAME)?;
|
|
||||||
|
|
||||||
for incoming in socket.incoming() {
|
|
||||||
match incoming {
|
|
||||||
Ok(data) => {
|
|
||||||
let reader = BufReader::new(data.try_clone()?);
|
|
||||||
|
|
||||||
for line in reader.lines().flatten() {
|
|
||||||
let notification: Notification = match serde_json::from_str(&line) {
|
|
||||||
Ok(notification) => notification,
|
|
||||||
Err(error) => {
|
|
||||||
log::debug!("discarding malformed komorebi notification: {error}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// match and filter on desired notifications
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
log::debug!("{error}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A read-world example can be found
|
|
||||||
in [komokana](https://github.com/LGUG2Z/komokana/blob/feature/komorebi-uds/src/main.rs).
|
|
||||||
|
|
||||||
## Subscription Event Notification Schema
|
## Subscription Event Notification Schema
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# alt-focus-hack
|
# alt-focus-hack
|
||||||
|
|
||||||
```
|
```
|
||||||
DEPRECATED since v0.1.22
|
Enable or disable a hack simulating ALT key presses to ensure focus changes succeed
|
||||||
|
|
||||||
Usage: komorebic.exe alt-focus-hack <BOOLEAN_STATE>
|
Usage: komorebic.exe alt-focus-hack <BOOLEAN_STATE>
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# check
|
# check
|
||||||
|
|
||||||
```
|
```
|
||||||
Check komorebi configuration and related files for common errors
|
Output various important komorebi-related environment values
|
||||||
|
|
||||||
Usage: komorebic.exe check
|
Usage: komorebic.exe check
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# configuration
|
|
||||||
|
|
||||||
```
|
|
||||||
Show the path to komorebi.json
|
|
||||||
|
|
||||||
Usage: komorebic.exe configuration
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
10
docs/cli/docgen.md
Normal file
10
docs/cli/docgen.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# docgen
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: komorebic.exe docgen
|
||||||
|
|
||||||
|
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]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
|
||||||
|
|
||||||
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]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# subscribe-socket
|
|
||||||
|
|
||||||
```
|
|
||||||
Subscribe to komorebi events using a Unix Domain Socket
|
|
||||||
|
|
||||||
Usage: komorebic.exe subscribe-socket <SOCKET>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<SOCKET>
|
|
||||||
Name of the socket to send event notifications to
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# subscribe-pipe
|
# subscribe
|
||||||
|
|
||||||
```
|
```
|
||||||
Subscribe to komorebi events using a Named Pipe
|
Subscribe to komorebi events
|
||||||
|
|
||||||
Usage: komorebic.exe subscribe-pipe <NAMED_PIPE>
|
Usage: komorebic.exe subscribe <NAMED_PIPE>
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<NAMED_PIPE>
|
<NAMED_PIPE>
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# unsubscribe-socket
|
|
||||||
|
|
||||||
```
|
|
||||||
Unsubscribe from komorebi events
|
|
||||||
|
|
||||||
Usage: komorebic.exe unsubscribe-socket <SOCKET>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<SOCKET>
|
|
||||||
Name of the socket to stop sending event notifications to
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# unsubscribe-pipe
|
# unsubscribe
|
||||||
|
|
||||||
```
|
```
|
||||||
Unsubscribe from komorebi events
|
Unsubscribe from komorebi events
|
||||||
|
|
||||||
Usage: komorebic.exe unsubscribe-pipe <NAMED_PIPE>
|
Usage: komorebic.exe unsubscribe <NAMED_PIPE>
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<NAMED_PIPE>
|
<NAMED_PIPE>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# whkdrc
|
|
||||||
|
|
||||||
```
|
|
||||||
Show the path to whkdrc
|
|
||||||
|
|
||||||
Usage: komorebic.exe whkdrc
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -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]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
|
||||||
|
|
||||||
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]
|
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -34,5 +34,7 @@ windows managed by `komorebi`.
|
|||||||
This feature is not considered stable and you may encounter visual artifacts
|
This feature is not considered stable and you may encounter visual artifacts
|
||||||
from time to time.
|
from time to time.
|
||||||
|
|
||||||
|
<!-- TODO: Record a new video -->
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=7_9D22t7KK4)
|
video](https://img.youtube.com/vi/ywiAvoMV_gE/hqdefault.jpg)](https://www.youtube.com/watch?v=ywiAvoMV_gE)
|
||||||
|
|||||||
@@ -25,6 +25,3 @@ If you already have configuration files that you wish to keep, move them to the
|
|||||||
|
|
||||||
The next time you run `komorebic start`, any files created by or loaded by
|
The next time you run `komorebic start`, any files created by or loaded by
|
||||||
_komorebi_ will be placed or expected to exist in this folder.
|
_komorebi_ will be placed or expected to exist in this folder.
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=C_KWUqQ6kko)
|
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
|
|
||||||
If you would like to remove all gaps by default, both between windows
|
If you would like to remove all gaps by default, both between windows
|
||||||
themselves, and between the monitor edges and the windows, you can set the
|
themselves, and between the monitor edges and the windows, you can set the
|
||||||
following configuration options to `0` and `-1` in the `komorebi.json`
|
following two configuration options to `0` in the `komorebi.json` configuration
|
||||||
configuration file.
|
file.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"default_workspace_padding": 0,
|
"default_workspace_padding": 0,
|
||||||
"default_container_padding": 0,
|
"default_container_padding": 0
|
||||||
"border_width": 0,
|
|
||||||
"border_offset": -1
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
A restart of `komorebi` is required after changing these settings.
|
<!-- TODO: Record a new video -->
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=6QYLao953XE)
|
[](https://www.youtube.com/watch?v=eGr07mymgWE)
|
||||||
|
|||||||
@@ -16,12 +16,6 @@ the example files have been downloaded. For most new users this will be in the
|
|||||||
komorebic quickstart
|
komorebic quickstart
|
||||||
```
|
```
|
||||||
|
|
||||||
With the example configurations downloaded, you can now start `komorebi` and `whkd.
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
komorebic start --whkd
|
|
||||||
```
|
|
||||||
|
|
||||||
## komorebi.json
|
## komorebi.json
|
||||||
|
|
||||||
The example window manager configuration sets some sane defaults and provides
|
The example window manager configuration sets some sane defaults and provides
|
||||||
@@ -145,19 +139,6 @@ If you have an ultrawide monitor, I recommend using this layout.
|
|||||||
+-----+-----------+-----+
|
+-----+-----------+-----+
|
||||||
```
|
```
|
||||||
|
|
||||||
### Grid
|
|
||||||
If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layouts) this is almost exactly the same!
|
|
||||||
|
|
||||||
```
|
|
||||||
+-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
|
|
||||||
| | | | | | | | | | | | | | |
|
|
||||||
| | | | | | | | | | | | | +---+
|
|
||||||
+-----+-----+ | +---+---+ +---+---+---+ +---+---| |
|
|
||||||
| | | | | | | | | | | | | +---+
|
|
||||||
| | | | | | | | | | | | | | |
|
|
||||||
+-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
|
|
||||||
4 windows 5 windows 6 windows 7 windows
|
|
||||||
```
|
|
||||||
|
|
||||||
## whkdrc
|
## whkdrc
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.22/schema.json",
|
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/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_padding": 8,
|
|
||||||
"border_offset": -1,
|
|
||||||
"active_window_border": false,
|
"active_window_border": false,
|
||||||
"active_window_border_colours": {
|
"active_window_border_colours": {
|
||||||
"single": "#42a5f5",
|
"single": { "r": 66, "g": 165, "b": 245 },
|
||||||
"stack": "#00a542",
|
"stack": { "r": 256, "g": 165, "b": 66 },
|
||||||
"monocle": "#ff3399"
|
"monocle": { "r": 255, "g": 51, "b": 153 }
|
||||||
},
|
},
|
||||||
"monitors": [
|
"monitors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
In addition to the [changelog](https://github.com/LGUG2Z/komorebi/releases/tag/v0.1.22) of new features and fixes,
|
|
||||||
please note the following changes from `v0.1.21` to adjust your configuration files accordingly.
|
|
||||||
|
|
||||||
## tl;dr
|
|
||||||
|
|
||||||
The way windows are sized and drawn has been improved to remove the need to manually specify and remove invisible
|
|
||||||
borders for applications that overflow them. If you use the active window border, the first time you launch `v0.1.22`
|
|
||||||
you may end up with a _huge_ border due to these changes.
|
|
||||||
|
|
||||||
`active_window_border_width` and `active_window_border_offset` have been renamed to `border_width` and `border_offset`
|
|
||||||
as they now also apply outside the context of the active window border.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"active_window_border": true,
|
|
||||||
"border_width": 8,
|
|
||||||
"border_offset": -1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Users of the active window border should start from these settings and read the notes below before making further
|
|
||||||
adjustments.
|
|
||||||
|
|
||||||
## Changes to `active_window_border`, and window sizing:
|
|
||||||
|
|
||||||
- The border no longer creates a second drop-shadow around the active window
|
|
||||||
- Windows are now sized to fill the layout region entirely, ignoring window decorations such as drop shadows
|
|
||||||
- Border offset now starts exactly at the paint edge of the window on all sides
|
|
||||||
- Windows are sized such that the border offset and border width are taken into account
|
|
||||||
|
|
||||||
## Recommended patterns
|
|
||||||
|
|
||||||
### Gapless
|
|
||||||
|
|
||||||
- Disable "transparency effects" Personalization > Colors
|
|
||||||
- Set the following settings in `komorebi.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"default_workspace_padding": 0,
|
|
||||||
"default_container_padding": 0,
|
|
||||||
"border_offset": -1,
|
|
||||||
"border_width": 0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1px border
|
|
||||||
|
|
||||||
A 1px border is drawn around the window edge. Users may see a gap for a single pixel, if the system theme has a
|
|
||||||
transparent edge - this is the windows themed edge, and is not present for all applications.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"border_offset": 0,
|
|
||||||
"border_width": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "komorebi-client"
|
|
||||||
version = "0.1.23-dev.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
komorebi = { path = "../komorebi" }
|
|
||||||
komorebi-core = { path = "../komorebi-core" }
|
|
||||||
uds_windows = "1"
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
|
||||||
#![allow(clippy::missing_errors_doc)]
|
|
||||||
|
|
||||||
pub use komorebi::container::Container;
|
|
||||||
pub use komorebi::monitor::Monitor;
|
|
||||||
pub use komorebi::ring::Ring;
|
|
||||||
pub use komorebi::window::Window;
|
|
||||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
|
||||||
pub use komorebi::workspace::Workspace;
|
|
||||||
pub use komorebi::Notification;
|
|
||||||
pub use komorebi::NotificationEvent;
|
|
||||||
pub use komorebi::State;
|
|
||||||
pub use komorebi_core::Arrangement;
|
|
||||||
pub use komorebi_core::Axis;
|
|
||||||
pub use komorebi_core::CustomLayout;
|
|
||||||
pub use komorebi_core::CycleDirection;
|
|
||||||
pub use komorebi_core::DefaultLayout;
|
|
||||||
pub use komorebi_core::Direction;
|
|
||||||
pub use komorebi_core::Layout;
|
|
||||||
pub use komorebi_core::OperationDirection;
|
|
||||||
pub use komorebi_core::Rect;
|
|
||||||
pub use komorebi_core::SocketMessage;
|
|
||||||
|
|
||||||
use komorebi::DATA_DIR;
|
|
||||||
|
|
||||||
use std::io::BufReader;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::net::Shutdown;
|
|
||||||
pub use uds_windows::UnixListener;
|
|
||||||
use uds_windows::UnixStream;
|
|
||||||
|
|
||||||
const KOMOREBI: &str = "komorebi.sock";
|
|
||||||
|
|
||||||
pub fn send_message(message: &SocketMessage) -> std::io::Result<()> {
|
|
||||||
let socket = DATA_DIR.join(KOMOREBI);
|
|
||||||
let mut connected = false;
|
|
||||||
while !connected {
|
|
||||||
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
|
||||||
connected = true;
|
|
||||||
stream.write_all(serde_json::to_string(message)?.as_bytes())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn send_query(message: &SocketMessage) -> std::io::Result<String> {
|
|
||||||
let socket = DATA_DIR.join(KOMOREBI);
|
|
||||||
|
|
||||||
let mut stream = UnixStream::connect(socket)?;
|
|
||||||
stream.write_all(serde_json::to_string(message)?.as_bytes())?;
|
|
||||||
stream.shutdown(Shutdown::Write)?;
|
|
||||||
|
|
||||||
let mut reader = BufReader::new(stream);
|
|
||||||
let mut response = String::new();
|
|
||||||
reader.read_to_string(&mut response)?;
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe(name: &str) -> std::io::Result<UnixListener> {
|
|
||||||
let socket = DATA_DIR.join(name);
|
|
||||||
|
|
||||||
match std::fs::remove_file(&socket) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(error) => match error.kind() {
|
|
||||||
std::io::ErrorKind::NotFound => {}
|
|
||||||
_ => {
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let listener = UnixListener::bind(&socket)?;
|
|
||||||
|
|
||||||
send_message(&SocketMessage::AddSubscriberSocket(name.to_string()))?;
|
|
||||||
|
|
||||||
Ok(listener)
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi-core"
|
name = "komorebi-core"
|
||||||
version = "0.1.23-dev.0"
|
version = "0.1.20"
|
||||||
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
|
||||||
@@ -8,7 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = "1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
|||||||
@@ -131,65 +131,11 @@ impl Arrangement for DefaultLayout {
|
|||||||
layouts
|
layouts
|
||||||
}
|
}
|
||||||
Self::UltrawideVerticalStack => ultrawide(area, len, layout_flip, resize_dimensions),
|
Self::UltrawideVerticalStack => ultrawide(area, len, layout_flip, resize_dimensions),
|
||||||
#[allow(
|
|
||||||
clippy::cast_precision_loss,
|
|
||||||
clippy::cast_possible_truncation,
|
|
||||||
clippy::cast_possible_wrap
|
|
||||||
)]
|
|
||||||
Self::Grid => {
|
|
||||||
// Shamelessly lifted from LeftWM
|
|
||||||
// https://github.com/leftwm/leftwm/blob/18675067b8450e520ef75db2ebbb0d973aa1199e/leftwm-core/src/layouts/grid_horizontal.rs
|
|
||||||
let mut layouts: Vec<Rect> = vec![];
|
|
||||||
layouts.resize(len, Rect::default());
|
|
||||||
|
|
||||||
let len = len as i32;
|
|
||||||
|
|
||||||
let num_cols = (len as f32).sqrt().ceil() as i32;
|
|
||||||
let mut iter = layouts.iter_mut().enumerate().peekable();
|
|
||||||
|
|
||||||
for col in 0..num_cols {
|
|
||||||
let iter_peek = iter.peek().map(|x| x.0).unwrap_or_default() as i32;
|
|
||||||
let remaining_windows = len - iter_peek;
|
|
||||||
let remaining_columns = num_cols - col;
|
|
||||||
let num_rows_in_this_col = remaining_windows / remaining_columns;
|
|
||||||
|
|
||||||
let win_height = area.bottom / num_rows_in_this_col;
|
|
||||||
let win_width = area.right / num_cols;
|
|
||||||
|
|
||||||
for row in 0..num_rows_in_this_col {
|
|
||||||
if let Some((_idx, win)) = iter.next() {
|
|
||||||
let mut left = area.left + win_width * col;
|
|
||||||
let mut top = area.top + win_height * row;
|
|
||||||
|
|
||||||
match layout_flip {
|
|
||||||
Some(Axis::Horizontal) => {
|
|
||||||
left = area.right - win_width * (col + 1) + area.left;
|
|
||||||
}
|
|
||||||
Some(Axis::Vertical) => {
|
|
||||||
top = area.bottom - win_height * (row + 1) + area.top;
|
|
||||||
}
|
|
||||||
Some(Axis::HorizontalAndVertical) => {
|
|
||||||
left = area.right - win_width * (col + 1) + area.left;
|
|
||||||
top = area.bottom - win_height * (row + 1) + area.top;
|
|
||||||
}
|
|
||||||
None => {} // No flip
|
|
||||||
}
|
|
||||||
|
|
||||||
win.bottom = win_height;
|
|
||||||
win.right = win_width;
|
|
||||||
win.left = left;
|
|
||||||
win.top = top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layouts
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dimensions
|
dimensions
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|l| l.add_padding(container_padding.unwrap_or_default()));
|
.for_each(|l| l.add_padding(container_padding));
|
||||||
|
|
||||||
dimensions
|
dimensions
|
||||||
}
|
}
|
||||||
@@ -312,7 +258,7 @@ impl Arrangement for CustomLayout {
|
|||||||
|
|
||||||
dimensions
|
dimensions
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|l| l.add_padding(container_padding.unwrap_or_default()));
|
.for_each(|l| l.add_padding(container_padding));
|
||||||
|
|
||||||
dimensions
|
dimensions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ use strum::EnumString;
|
|||||||
|
|
||||||
use crate::ApplicationIdentifier;
|
use crate::ApplicationIdentifier;
|
||||||
|
|
||||||
#[derive(
|
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema)]
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
|
||||||
)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ApplicationOptions {
|
pub enum ApplicationOptions {
|
||||||
@@ -52,13 +50,6 @@ impl ApplicationOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum MatchingRule {
|
|
||||||
Simple(IdWithIdentifier),
|
|
||||||
Composite(Vec<IdWithIdentifier>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct IdWithIdentifier {
|
pub struct IdWithIdentifier {
|
||||||
pub kind: ApplicationIdentifier,
|
pub kind: ApplicationIdentifier,
|
||||||
@@ -75,10 +66,6 @@ pub enum MatchingStrategy {
|
|||||||
EndsWith,
|
EndsWith,
|
||||||
Contains,
|
Contains,
|
||||||
Regex,
|
Regex,
|
||||||
DoesNotEndWith,
|
|
||||||
DoesNotStartWith,
|
|
||||||
DoesNotEqual,
|
|
||||||
DoesNotContain,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
@@ -108,14 +95,14 @@ pub struct ApplicationConfiguration {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub options: Option<Vec<ApplicationOptions>>,
|
pub options: Option<Vec<ApplicationOptions>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub float_identifiers: Option<Vec<MatchingRule>>,
|
pub float_identifiers: Option<Vec<IdWithIdentifierAndComment>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationConfiguration {
|
impl ApplicationConfiguration {
|
||||||
pub fn populate_default_matching_strategies(&mut self) {
|
pub fn populate_default_matching_strategies(&mut self) {
|
||||||
if self.identifier.matching_strategy.is_none() {
|
if self.identifier.matching_strategy.is_none() {
|
||||||
match self.identifier.kind {
|
match self.identifier.kind {
|
||||||
ApplicationIdentifier::Exe | ApplicationIdentifier::Path => {
|
ApplicationIdentifier::Exe => {
|
||||||
self.identifier.matching_strategy = Option::from(MatchingStrategy::Equals);
|
self.identifier.matching_strategy = Option::from(MatchingStrategy::Equals);
|
||||||
}
|
}
|
||||||
ApplicationIdentifier::Class | ApplicationIdentifier::Title => {}
|
ApplicationIdentifier::Class | ApplicationIdentifier::Title => {}
|
||||||
@@ -194,21 +181,19 @@ impl ApplicationConfigurationGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(float_identifiers) = app.float_identifiers {
|
if let Some(float_identifiers) = app.float_identifiers {
|
||||||
for matching_rule in float_identifiers {
|
for float in float_identifiers {
|
||||||
if let MatchingRule::Simple(float) = matching_rule {
|
let float_rule =
|
||||||
let float_rule =
|
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
|
||||||
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
|
|
||||||
|
|
||||||
// Don't want to send duped signals especially as configs get larger
|
// Don't want to send duped signals especially as configs get larger
|
||||||
if !float_rules.contains(&float_rule) {
|
if !float_rules.contains(&float_rule) {
|
||||||
float_rules.push(float_rule.clone());
|
float_rules.push(float_rule.clone());
|
||||||
|
|
||||||
// if let Some(comment) = float.comment {
|
if let Some(comment) = float.comment {
|
||||||
// lines.push(format!("# {comment}"));
|
lines.push(format!("# {comment}"));
|
||||||
// };
|
};
|
||||||
|
|
||||||
lines.push(float_rule);
|
lines.push(float_rule);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,23 +230,21 @@ impl ApplicationConfigurationGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(float_identifiers) = app.float_identifiers {
|
if let Some(float_identifiers) = app.float_identifiers {
|
||||||
for matching_rule in float_identifiers {
|
for float in float_identifiers {
|
||||||
if let MatchingRule::Simple(float) = matching_rule {
|
let float_rule = format!(
|
||||||
let float_rule = format!(
|
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
|
||||||
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
|
float.kind, float.id
|
||||||
float.kind, float.id
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// Don't want to send duped signals especially as configs get larger
|
// Don't want to send duped signals especially as configs get larger
|
||||||
if !float_rules.contains(&float_rule) {
|
if !float_rules.contains(&float_rule) {
|
||||||
float_rules.push(float_rule.clone());
|
float_rules.push(float_rule.clone());
|
||||||
|
|
||||||
// if let Some(comment) = float.comment {
|
if let Some(comment) = float.comment {
|
||||||
// lines.push(format!("; {comment}"));
|
lines.push(format!("; {comment}"));
|
||||||
// };
|
};
|
||||||
|
|
||||||
lines.push(float_rule);
|
lines.push(float_rule);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ pub enum DefaultLayout {
|
|||||||
VerticalStack,
|
VerticalStack,
|
||||||
HorizontalStack,
|
HorizontalStack,
|
||||||
UltrawideVerticalStack,
|
UltrawideVerticalStack,
|
||||||
Grid,
|
|
||||||
// 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`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,8 +135,7 @@ impl DefaultLayout {
|
|||||||
Self::Rows => Self::VerticalStack,
|
Self::Rows => Self::VerticalStack,
|
||||||
Self::VerticalStack => Self::HorizontalStack,
|
Self::VerticalStack => Self::HorizontalStack,
|
||||||
Self::HorizontalStack => Self::UltrawideVerticalStack,
|
Self::HorizontalStack => Self::UltrawideVerticalStack,
|
||||||
Self::UltrawideVerticalStack => Self::Grid,
|
Self::UltrawideVerticalStack => Self::BSP,
|
||||||
Self::Grid => Self::BSP,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +147,7 @@ impl DefaultLayout {
|
|||||||
Self::HorizontalStack => Self::VerticalStack,
|
Self::HorizontalStack => Self::VerticalStack,
|
||||||
Self::VerticalStack => Self::Rows,
|
Self::VerticalStack => Self::Rows,
|
||||||
Self::Rows => Self::Columns,
|
Self::Rows => Self::Columns,
|
||||||
Self::Columns => Self::Grid,
|
Self::Columns => Self::BSP,
|
||||||
Self::Grid => Self::BSP,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,30 +19,10 @@ pub trait Direction {
|
|||||||
idx: usize,
|
idx: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
fn up_index(
|
fn up_index(&self, idx: usize) -> usize;
|
||||||
&self,
|
fn down_index(&self, idx: usize) -> usize;
|
||||||
op_direction: Option<OperationDirection>,
|
fn left_index(&self, idx: usize) -> usize;
|
||||||
idx: usize,
|
fn right_index(&self, idx: usize) -> usize;
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize;
|
|
||||||
fn down_index(
|
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize;
|
|
||||||
fn left_index(
|
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize;
|
|
||||||
fn right_index(
|
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Direction for DefaultLayout {
|
impl Direction for DefaultLayout {
|
||||||
@@ -55,28 +35,28 @@ impl Direction for DefaultLayout {
|
|||||||
match op_direction {
|
match op_direction {
|
||||||
OperationDirection::Left => {
|
OperationDirection::Left => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.left_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.left_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Right => {
|
OperationDirection::Right => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.right_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.right_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Up => {
|
OperationDirection::Up => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.up_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.up_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Down => {
|
OperationDirection::Down => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.down_index(Some(op_direction), idx, Some(count)))
|
Option::from(self.down_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -97,7 +77,6 @@ impl Direction for DefaultLayout {
|
|||||||
Self::Rows | Self::HorizontalStack => idx != 0,
|
Self::Rows | Self::HorizontalStack => idx != 0,
|
||||||
Self::VerticalStack => 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),
|
|
||||||
},
|
},
|
||||||
OperationDirection::Down => match self {
|
OperationDirection::Down => match self {
|
||||||
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
|
Self::BSP => count > 2 && idx != count - 1 && idx % 2 != 0,
|
||||||
@@ -106,7 +85,6 @@ impl Direction for DefaultLayout {
|
|||||||
Self::VerticalStack => 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),
|
|
||||||
},
|
},
|
||||||
OperationDirection::Left => match self {
|
OperationDirection::Left => match self {
|
||||||
Self::BSP => count > 1 && idx != 0,
|
Self::BSP => count > 1 && idx != 0,
|
||||||
@@ -114,7 +92,6 @@ impl Direction for DefaultLayout {
|
|||||||
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,
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
|
||||||
},
|
},
|
||||||
OperationDirection::Right => match self {
|
OperationDirection::Right => match self {
|
||||||
Self::BSP => count > 1 && idx % 2 == 0 && idx != count - 1,
|
Self::BSP => count > 1 && idx % 2 == 0 && idx != count - 1,
|
||||||
@@ -127,17 +104,11 @@ impl Direction for DefaultLayout {
|
|||||||
2 => idx != 0,
|
2 => idx != 0,
|
||||||
_ => idx < 2,
|
_ => idx < 2,
|
||||||
},
|
},
|
||||||
Self::Grid => !is_grid_edge(op_direction, idx, count),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up_index(
|
fn up_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
match self {
|
match self {
|
||||||
Self::BSP => {
|
Self::BSP => {
|
||||||
if idx % 2 == 0 {
|
if idx % 2 == 0 {
|
||||||
@@ -149,30 +120,18 @@ impl Direction for DefaultLayout {
|
|||||||
Self::Columns => unreachable!(),
|
Self::Columns => unreachable!(),
|
||||||
Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx - 1,
|
Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx - 1,
|
||||||
Self::HorizontalStack => 0,
|
Self::HorizontalStack => 0,
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn down_index(
|
fn down_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
match self {
|
match self {
|
||||||
Self::BSP | Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx + 1,
|
Self::BSP | Self::Rows | Self::VerticalStack | Self::UltrawideVerticalStack => idx + 1,
|
||||||
Self::Columns => unreachable!(),
|
Self::Columns => unreachable!(),
|
||||||
Self::HorizontalStack => 1,
|
Self::HorizontalStack => 1,
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn left_index(
|
fn left_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
match self {
|
match self {
|
||||||
Self::BSP => {
|
Self::BSP => {
|
||||||
if idx % 2 == 0 {
|
if idx % 2 == 0 {
|
||||||
@@ -189,16 +148,10 @@ impl Direction for DefaultLayout {
|
|||||||
1 => unreachable!(),
|
1 => unreachable!(),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
},
|
},
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn right_index(
|
fn right_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
match self {
|
match self {
|
||||||
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,
|
||||||
Self::Rows => unreachable!(),
|
Self::Rows => unreachable!(),
|
||||||
@@ -208,126 +161,10 @@ impl Direction for DefaultLayout {
|
|||||||
0 => 2,
|
0 => 2,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
Self::Grid => grid_neighbor(op_direction, idx, count),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GridItem {
|
|
||||||
state: GridItemState,
|
|
||||||
row: usize,
|
|
||||||
num_rows: usize,
|
|
||||||
touching_edges: GridTouchingEdges,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GridItemState {
|
|
||||||
Valid,
|
|
||||||
Invalid,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
|
||||||
struct GridTouchingEdges {
|
|
||||||
left: bool,
|
|
||||||
right: bool,
|
|
||||||
up: bool,
|
|
||||||
down: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(
|
|
||||||
clippy::cast_possible_truncation,
|
|
||||||
clippy::cast_precision_loss,
|
|
||||||
clippy::cast_sign_loss
|
|
||||||
)]
|
|
||||||
fn get_grid_item(idx: usize, count: usize) -> GridItem {
|
|
||||||
let num_cols = (count as f32).sqrt().ceil() as usize;
|
|
||||||
let mut iter = 0;
|
|
||||||
|
|
||||||
for col in 0..num_cols {
|
|
||||||
let remaining_windows = count - iter;
|
|
||||||
let remaining_columns = num_cols - col;
|
|
||||||
let num_rows_in_this_col = remaining_windows / remaining_columns;
|
|
||||||
|
|
||||||
for row in 0..num_rows_in_this_col {
|
|
||||||
if iter == idx {
|
|
||||||
return GridItem {
|
|
||||||
state: GridItemState::Valid,
|
|
||||||
row: row + 1,
|
|
||||||
num_rows: num_rows_in_this_col,
|
|
||||||
touching_edges: GridTouchingEdges {
|
|
||||||
left: col == 0,
|
|
||||||
right: col == num_cols - 1,
|
|
||||||
up: row == 0,
|
|
||||||
down: row == num_rows_in_this_col - 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
iter += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GridItem {
|
|
||||||
state: GridItemState::Invalid,
|
|
||||||
row: 0,
|
|
||||||
num_rows: 0,
|
|
||||||
touching_edges: GridTouchingEdges {
|
|
||||||
left: true,
|
|
||||||
right: true,
|
|
||||||
up: true,
|
|
||||||
down: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_grid_edge(op_direction: OperationDirection, idx: usize, count: usize) -> bool {
|
|
||||||
let item = get_grid_item(idx, count);
|
|
||||||
|
|
||||||
match item.state {
|
|
||||||
GridItemState::Invalid => false,
|
|
||||||
GridItemState::Valid => match op_direction {
|
|
||||||
OperationDirection::Left => item.touching_edges.left,
|
|
||||||
OperationDirection::Right => item.touching_edges.right,
|
|
||||||
OperationDirection::Up => item.touching_edges.up,
|
|
||||||
OperationDirection::Down => item.touching_edges.down,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn grid_neighbor(
|
|
||||||
op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
let Some(op_direction) = op_direction else {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(count) = count else {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
let item = get_grid_item(idx, count);
|
|
||||||
|
|
||||||
match op_direction {
|
|
||||||
OperationDirection::Left => {
|
|
||||||
let item_from_prev_col = get_grid_item(idx - item.row, count);
|
|
||||||
|
|
||||||
if item.touching_edges.up && item.num_rows != item_from_prev_col.num_rows {
|
|
||||||
return idx - (item.num_rows - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.num_rows != item_from_prev_col.num_rows && !item.touching_edges.down {
|
|
||||||
return idx - (item.num_rows - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
idx - item.num_rows
|
|
||||||
}
|
|
||||||
OperationDirection::Right => idx + item.num_rows,
|
|
||||||
OperationDirection::Up => idx - 1,
|
|
||||||
OperationDirection::Down => idx + 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Direction for CustomLayout {
|
impl Direction for CustomLayout {
|
||||||
fn index_in_direction(
|
fn index_in_direction(
|
||||||
&self,
|
&self,
|
||||||
@@ -342,28 +179,28 @@ impl Direction for CustomLayout {
|
|||||||
match op_direction {
|
match op_direction {
|
||||||
OperationDirection::Left => {
|
OperationDirection::Left => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.left_index(None, idx, None))
|
Option::from(self.left_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Right => {
|
OperationDirection::Right => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.right_index(None, idx, None))
|
Option::from(self.right_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Up => {
|
OperationDirection::Up => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.up_index(None, idx, None))
|
Option::from(self.up_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OperationDirection::Down => {
|
OperationDirection::Down => {
|
||||||
if self.is_valid_direction(op_direction, idx, count) {
|
if self.is_valid_direction(op_direction, idx, count) {
|
||||||
Option::from(self.down_index(None, idx, None))
|
Option::from(self.down_index(idx))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -417,30 +254,15 @@ impl Direction for CustomLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up_index(
|
fn up_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
_op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
_count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
idx - 1
|
idx - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn down_index(
|
fn down_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
_op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
_count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
idx + 1
|
idx + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn left_index(
|
fn left_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
_op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
_count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
let column_idx = self.column_for_container_idx(idx);
|
let column_idx = self.column_for_container_idx(idx);
|
||||||
if column_idx - 1 == 0 {
|
if column_idx - 1 == 0 {
|
||||||
0
|
0
|
||||||
@@ -449,12 +271,7 @@ impl Direction for CustomLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn right_index(
|
fn right_index(&self, idx: usize) -> usize {
|
||||||
&self,
|
|
||||||
_op_direction: Option<OperationDirection>,
|
|
||||||
idx: usize,
|
|
||||||
_count: Option<usize>,
|
|
||||||
) -> usize {
|
|
||||||
let column_idx = self.column_for_container_idx(idx);
|
let column_idx = self.column_for_container_idx(idx);
|
||||||
self.first_container_idx(column_idx + 1)
|
self.first_container_idx(column_idx + 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,10 +156,8 @@ pub enum SocketMessage {
|
|||||||
ToggleMouseFollowsFocus,
|
ToggleMouseFollowsFocus,
|
||||||
RemoveTitleBar(ApplicationIdentifier, String),
|
RemoveTitleBar(ApplicationIdentifier, String),
|
||||||
ToggleTitleBars,
|
ToggleTitleBars,
|
||||||
AddSubscriberSocket(String),
|
AddSubscriber(String),
|
||||||
RemoveSubscriberSocket(String),
|
RemoveSubscriber(String),
|
||||||
AddSubscriberPipe(String),
|
|
||||||
RemoveSubscriberPipe(String),
|
|
||||||
ApplicationSpecificConfigurationSchema,
|
ApplicationSpecificConfigurationSchema,
|
||||||
NotificationSchema,
|
NotificationSchema,
|
||||||
SocketSchema,
|
SocketSchema,
|
||||||
@@ -223,8 +221,6 @@ pub enum ApplicationIdentifier {
|
|||||||
Class,
|
Class,
|
||||||
#[serde(alias = "title")]
|
#[serde(alias = "title")]
|
||||||
Title,
|
Title,
|
||||||
#[serde(alias = "path")]
|
|
||||||
Path,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|||||||
@@ -26,24 +26,9 @@ impl From<RECT> for Rect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Rect> for RECT {
|
|
||||||
fn from(rect: Rect) -> Self {
|
|
||||||
Self {
|
|
||||||
left: rect.left,
|
|
||||||
top: rect.top,
|
|
||||||
right: rect.right,
|
|
||||||
bottom: rect.bottom,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
/// decrease the size of self by the padding amount.
|
pub fn add_padding(&mut self, padding: Option<i32>) {
|
||||||
pub fn add_padding<T>(&mut self, padding: T)
|
if let Some(padding) = padding {
|
||||||
where
|
|
||||||
T: Into<Option<i32>>,
|
|
||||||
{
|
|
||||||
if let Some(padding) = padding.into() {
|
|
||||||
self.left += padding;
|
self.left += padding;
|
||||||
self.top += padding;
|
self.top += padding;
|
||||||
self.right -= padding * 2;
|
self.right -= padding * 2;
|
||||||
@@ -51,22 +36,6 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// increase the size of self by the margin amount.
|
|
||||||
pub fn add_margin(&mut self, margin: i32) {
|
|
||||||
self.left -= margin;
|
|
||||||
self.top -= margin;
|
|
||||||
self.right += margin * 2;
|
|
||||||
self.bottom += margin * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn left_padding(&mut self, padding: i32) {
|
|
||||||
self.left += padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn right_padding(&mut self, padding: i32) {
|
|
||||||
self.right -= padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn contains_point(&self, point: (i32, i32)) -> bool {
|
pub const fn contains_point(&self, point: (i32, i32)) -> bool {
|
||||||
point.0 >= self.left
|
point.0 >= self.left
|
||||||
@@ -74,14 +43,4 @@ impl Rect {
|
|||||||
&& point.1 >= self.top
|
&& point.1 >= self.top
|
||||||
&& point.1 <= self.top + self.bottom
|
&& point.1 <= self.top + self.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub const fn scale(&self, system_dpi: i32, rect_dpi: i32) -> Rect {
|
|
||||||
Rect {
|
|
||||||
left: (self.left * rect_dpi) / system_dpi,
|
|
||||||
top: (self.top * rect_dpi) / system_dpi,
|
|
||||||
right: (self.right * rect_dpi) / system_dpi,
|
|
||||||
bottom: (self.bottom * rect_dpi) / system_dpi,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
komorebi.example.json
Normal file
25
komorebi.example.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/schema.json",
|
||||||
|
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
|
||||||
|
"window_hiding_behaviour": "Cloak",
|
||||||
|
"cross_monitor_move_behaviour": "Insert",
|
||||||
|
"default_workspace_padding": 20,
|
||||||
|
"default_container_padding": 20,
|
||||||
|
"active_window_border": false,
|
||||||
|
"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 }
|
||||||
|
},
|
||||||
|
"monitors": [
|
||||||
|
{
|
||||||
|
"workspaces": [
|
||||||
|
{ "name": "I", "layout": "BSP" },
|
||||||
|
{ "name": "II", "layout": "VerticalStack" },
|
||||||
|
{ "name": "III", "layout": "HorizontalStack" },
|
||||||
|
{ "name": "IV", "layout": "UltrawideVerticalStack" },
|
||||||
|
{ "name": "V", "layout": "Rows" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebi"
|
name = "komorebi"
|
||||||
version = "0.1.23-dev.0"
|
version = "0.1.20"
|
||||||
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"]
|
||||||
@@ -15,38 +15,37 @@ komorebi-core = { path = "../komorebi-core" }
|
|||||||
|
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
color-eyre = { workspace = true }
|
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
crossbeam-utils = "0.8"
|
crossbeam-utils = "0.8"
|
||||||
ctrlc = "3"
|
ctrlc = "3"
|
||||||
dirs = { workspace = true }
|
|
||||||
getset = "0.1"
|
getset = "0.1"
|
||||||
hex_color = { version = "3", features = ["serde"] }
|
hotwatch = "0.4"
|
||||||
hotwatch = "0.5"
|
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
miow = "0.5"
|
miow = "0.5"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
net2 = "0.2"
|
net2 = "0.2"
|
||||||
os_info = "3.8"
|
os_info = "3.7"
|
||||||
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
|
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
|
||||||
paste = "1"
|
paste = "1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = "1"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
sysinfo = "0.30"
|
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"] }
|
||||||
uds_windows = "1"
|
uds_windows = "1"
|
||||||
which = "6"
|
which = "5"
|
||||||
widestring = "1"
|
|
||||||
windows = { workspace = true }
|
|
||||||
windows-implement = { workspace = true }
|
|
||||||
windows-interface = { workspace = true }
|
|
||||||
winput = "0.2"
|
winput = "0.2"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
windows-interface = { workspace = true }
|
||||||
|
windows-implement = { workspace = true }
|
||||||
|
windows = { workspace = true }
|
||||||
|
color-eyre = { workspace = true }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
widestring = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
deadlock_detection = []
|
deadlock_detection = []
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use windows::core::PCWSTR;
|
use windows::core::PCWSTR;
|
||||||
@@ -11,14 +12,19 @@ use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||||
|
|
||||||
|
use komorebi_core::Rect;
|
||||||
|
|
||||||
|
use crate::window::should_act;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::windows_callbacks;
|
use crate::windows_callbacks;
|
||||||
use crate::WindowsApi;
|
use crate::WindowsApi;
|
||||||
use crate::BORDER_HWND;
|
use crate::BORDER_HWND;
|
||||||
use crate::BORDER_OFFSET;
|
use crate::BORDER_OFFSET;
|
||||||
|
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||||
use crate::BORDER_RECT;
|
use crate::BORDER_RECT;
|
||||||
use crate::BORDER_WIDTH;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
use crate::TRANSPARENCY_COLOUR;
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
|
use crate::WINDOWS_11;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Border {
|
pub struct Border {
|
||||||
@@ -62,6 +68,7 @@ impl Border {
|
|||||||
unsafe {
|
unsafe {
|
||||||
while GetMessageW(&mut message, border.hwnd(), 0, 0).into() {
|
while GetMessageW(&mut message, border.hwnd(), 0, 0).into() {
|
||||||
DispatchMessageW(&message);
|
DispatchMessageW(&message);
|
||||||
|
std::thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +82,10 @@ impl Border {
|
|||||||
|
|
||||||
BORDER_HWND.store(hwnd.0, Ordering::SeqCst);
|
BORDER_HWND.store(hwnd.0, Ordering::SeqCst);
|
||||||
|
|
||||||
|
if *WINDOWS_11 {
|
||||||
|
WindowsApi::round_corners(hwnd.0)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +97,12 @@ impl Border {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_position(self, window: Window, activate: bool) -> Result<()> {
|
pub fn set_position(
|
||||||
|
self,
|
||||||
|
window: Window,
|
||||||
|
invisible_borders: &Rect,
|
||||||
|
activate: bool,
|
||||||
|
) -> Result<()> {
|
||||||
if self.hwnd == 0 {
|
if self.hwnd == 0 {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -95,10 +111,38 @@ impl Border {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut rect = WindowsApi::window_rect(window.hwnd())?;
|
let mut rect = WindowsApi::window_rect(window.hwnd())?;
|
||||||
rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst));
|
rect.top -= invisible_borders.bottom;
|
||||||
|
rect.bottom += invisible_borders.bottom;
|
||||||
|
|
||||||
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
|
let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||||
rect.add_margin(border_width);
|
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
||||||
|
|
||||||
|
let title = &window.title()?;
|
||||||
|
let exe_name = &window.exe()?;
|
||||||
|
let class = &window.class()?;
|
||||||
|
|
||||||
|
let should_expand_border = should_act(
|
||||||
|
title,
|
||||||
|
exe_name,
|
||||||
|
class,
|
||||||
|
&border_overflows,
|
||||||
|
®ex_identifiers,
|
||||||
|
);
|
||||||
|
|
||||||
|
if should_expand_border {
|
||||||
|
rect.left -= invisible_borders.left;
|
||||||
|
rect.top -= invisible_borders.top;
|
||||||
|
rect.right += invisible_borders.right;
|
||||||
|
rect.bottom += invisible_borders.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
let border_offset = BORDER_OFFSET.lock();
|
||||||
|
if let Some(border_offset) = *border_offset {
|
||||||
|
rect.left -= border_offset.left;
|
||||||
|
rect.top -= border_offset.top;
|
||||||
|
rect.right += border_offset.right;
|
||||||
|
rect.bottom += border_offset.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
*BORDER_RECT.lock() = rect;
|
*BORDER_RECT.lock() = rect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
use hex_color::HexColor;
|
|
||||||
use schemars::gen::SchemaGenerator;
|
|
||||||
use schemars::schema::InstanceType;
|
|
||||||
use schemars::schema::Schema;
|
|
||||||
use schemars::schema::SchemaObject;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum Colour {
|
|
||||||
/// Colour represented as RGB
|
|
||||||
Rgb(Rgb),
|
|
||||||
/// Colour represented as Hex
|
|
||||||
Hex(Hex),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rgb> for Colour {
|
|
||||||
fn from(value: Rgb) -> Self {
|
|
||||||
Self::Rgb(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for Colour {
|
|
||||||
fn from(value: u32) -> Self {
|
|
||||||
Self::Rgb(Rgb::from(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Hex(HexColor);
|
|
||||||
|
|
||||||
impl JsonSchema for Hex {
|
|
||||||
fn schema_name() -> String {
|
|
||||||
String::from("Hex")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
|
||||||
SchemaObject {
|
|
||||||
instance_type: Some(InstanceType::String.into()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Colour> for u32 {
|
|
||||||
fn from(value: Colour) -> Self {
|
|
||||||
match value {
|
|
||||||
Colour::Rgb(val) => val.into(),
|
|
||||||
Colour::Hex(val) => (Rgb::from(val)).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct Rgb {
|
|
||||||
/// Red
|
|
||||||
pub r: u32,
|
|
||||||
/// Green
|
|
||||||
pub g: u32,
|
|
||||||
/// Blue
|
|
||||||
pub b: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rgb {
|
|
||||||
pub fn new(r: u32, g: u32, b: u32) -> Self {
|
|
||||||
Self { r, g, b }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Hex> for Rgb {
|
|
||||||
fn from(value: Hex) -> Self {
|
|
||||||
value.0.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HexColor> for Rgb {
|
|
||||||
fn from(value: HexColor) -> Self {
|
|
||||||
Self {
|
|
||||||
r: value.r as u32,
|
|
||||||
g: value.g as u32,
|
|
||||||
b: value.b as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rgb> for u32 {
|
|
||||||
fn from(value: Rgb) -> Self {
|
|
||||||
value.r | (value.g << 8) | (value.b << 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for Rgb {
|
|
||||||
fn from(value: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
r: value & 0xff,
|
|
||||||
g: value >> 8 & 0xff,
|
|
||||||
b: value >> 16 & 0xff,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ use interfaces::IServiceProvider;
|
|||||||
|
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
use windows::core::ComInterface;
|
||||||
use windows::core::Interface;
|
use windows::core::Interface;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::System::Com::CoCreateInstance;
|
use windows::Win32::System::Com::CoCreateInstance;
|
||||||
|
|||||||
@@ -3,22 +3,17 @@ use std::collections::VecDeque;
|
|||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
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, Getters, JsonSchema)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
#[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);
|
||||||
@@ -28,10 +23,6 @@ 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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,38 +34,6 @@ impl PartialEq for Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn hide(&self, omit: Option<isize>) {
|
|
||||||
if let Some(stackbar) = self.stackbar() {
|
|
||||||
stackbar.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
for window in self.windows().iter().rev() {
|
|
||||||
let mut should_hide = omit.is_none();
|
|
||||||
|
|
||||||
if !should_hide {
|
|
||||||
if let Some(omit) = omit {
|
|
||||||
if omit != window.hwnd {
|
|
||||||
should_hide = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_hide {
|
|
||||||
window.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore(&self) {
|
|
||||||
if let Some(stackbar) = self.stackbar() {
|
|
||||||
stackbar.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(window) = self.focused_window() {
|
|
||||||
window.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_focused_window(&mut self) {
|
pub fn load_focused_window(&mut self) {
|
||||||
let focused_idx = self.focused_window_idx();
|
let focused_idx = self.focused_window_idx();
|
||||||
for (i, window) in self.windows_mut().iter_mut().enumerate() {
|
for (i, window) in self.windows_mut().iter_mut().enumerate() {
|
||||||
@@ -122,10 +81,6 @@ 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);
|
||||||
};
|
};
|
||||||
@@ -140,14 +95,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use windows::core::PCWSTR;
|
use windows::core::PCWSTR;
|
||||||
@@ -58,6 +59,7 @@ impl Hidden {
|
|||||||
unsafe {
|
unsafe {
|
||||||
while GetMessageW(&mut message, hidden.hwnd(), 0, 0).into() {
|
while GetMessageW(&mut message, hidden.hwnd(), 0, 0).into() {
|
||||||
DispatchMessageW(&message);
|
DispatchMessageW(&message);
|
||||||
|
std::thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,374 +0,0 @@
|
|||||||
pub mod border;
|
|
||||||
pub mod com;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod ring;
|
|
||||||
pub mod colour;
|
|
||||||
pub mod container;
|
|
||||||
pub mod hidden;
|
|
||||||
pub mod monitor;
|
|
||||||
pub mod process_command;
|
|
||||||
pub mod process_event;
|
|
||||||
pub mod process_movement;
|
|
||||||
pub mod set_window_position;
|
|
||||||
pub mod stackbar;
|
|
||||||
pub mod static_config;
|
|
||||||
pub mod styles;
|
|
||||||
pub mod window;
|
|
||||||
pub mod window_manager;
|
|
||||||
pub mod window_manager_event;
|
|
||||||
pub mod windows_api;
|
|
||||||
pub mod windows_callbacks;
|
|
||||||
pub mod winevent;
|
|
||||||
pub mod winevent_listener;
|
|
||||||
pub mod workspace;
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::net::TcpStream;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::atomic::AtomicI32;
|
|
||||||
use std::sync::atomic::AtomicIsize;
|
|
||||||
use std::sync::atomic::AtomicU32;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use hidden::*;
|
|
||||||
pub use process_command::*;
|
|
||||||
pub use process_event::*;
|
|
||||||
pub use stackbar::*;
|
|
||||||
pub use static_config::*;
|
|
||||||
pub use window_manager::*;
|
|
||||||
pub use window_manager_event::*;
|
|
||||||
pub use windows_api::WindowsApi;
|
|
||||||
pub use windows_api::*;
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use komorebi_core::config_generation::IdWithIdentifier;
|
|
||||||
use komorebi_core::config_generation::MatchingRule;
|
|
||||||
use komorebi_core::config_generation::MatchingStrategy;
|
|
||||||
use komorebi_core::ApplicationIdentifier;
|
|
||||||
use komorebi_core::HidingBehaviour;
|
|
||||||
use komorebi_core::Rect;
|
|
||||||
use komorebi_core::SocketMessage;
|
|
||||||
use os_info::Version;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use regex::Regex;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use uds_windows::UnixStream;
|
|
||||||
use which::which;
|
|
||||||
use winreg::enums::HKEY_CURRENT_USER;
|
|
||||||
use winreg::RegKey;
|
|
||||||
|
|
||||||
type WorkspaceRule = (usize, usize, bool);
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
|
||||||
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("steam.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
]));
|
|
||||||
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> =
|
|
||||||
Arc::new(Mutex::new(vec![
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("explorer.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("firefox.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("chrome.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("idea64.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("ApplicationFrameHost.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("steam.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("firefox.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Exe,
|
|
||||||
id: String::from("idea64.exe"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
]));
|
|
||||||
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref DISPLAY_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, String>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, WorkspaceRule>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));
|
|
||||||
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
|
|
||||||
// mstsc.exe creates these on Windows 11 when a WSL process is launched
|
|
||||||
// https://github.com/LGUG2Z/komorebi/issues/74
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Class,
|
|
||||||
id: String::from("OPContainerClass"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
}),
|
|
||||||
MatchingRule::Simple(IdWithIdentifier {
|
|
||||||
kind: ApplicationIdentifier::Class,
|
|
||||||
id: String::from("IHWindowClass"),
|
|
||||||
matching_strategy: Option::from(MatchingStrategy::Equals),
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
|
|
||||||
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
|
||||||
"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![
|
|
||||||
"X410.exe".to_string(),
|
|
||||||
"vcxsrv.exe".to_string(),
|
|
||||||
]));
|
|
||||||
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
|
||||||
Arc::new(Mutex::new(HidingBehaviour::Minimize));
|
|
||||||
pub static ref HOME_DIR: PathBuf = {
|
|
||||||
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(|_| dirs::home_dir().expect("there is no home directory"), |home_path| {
|
|
||||||
let home = PathBuf::from(&home_path);
|
|
||||||
|
|
||||||
if home.as_path().is_dir() {
|
|
||||||
home
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
pub static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
|
|
||||||
pub static ref AHK_EXE: String = {
|
|
||||||
let mut ahk: String = String::from("autohotkey.exe");
|
|
||||||
|
|
||||||
if let Ok(komorebi_ahk_exe) = std::env::var("KOMOREBI_AHK_EXE") {
|
|
||||||
if which(&komorebi_ahk_exe).is_ok() {
|
|
||||||
ahk = komorebi_ahk_exe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ahk
|
|
||||||
};
|
|
||||||
static ref WINDOWS_11: bool = {
|
|
||||||
matches!(
|
|
||||||
os_info::get().version(),
|
|
||||||
Version::Semantic(_, _, x) if x >= &22000
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
static ref BORDER_RECT: Arc<Mutex<Rect>> =
|
|
||||||
Arc::new(Mutex::new(Rect::default()));
|
|
||||||
|
|
||||||
|
|
||||||
// Use app-specific titlebar removal options where possible
|
|
||||||
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
|
||||||
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>>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
|
|
||||||
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
|
||||||
|
|
||||||
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
|
||||||
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
|
||||||
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
|
||||||
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 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]
|
|
||||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
|
||||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
|
||||||
|
|
||||||
// This is the path on Windows 10
|
|
||||||
let mut current = hkcu
|
|
||||||
.open_subkey(format!(
|
|
||||||
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
|
|
||||||
SESSION_ID.load(Ordering::SeqCst)
|
|
||||||
))
|
|
||||||
.ok()
|
|
||||||
.and_then(
|
|
||||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
|
||||||
Ok(current) => Option::from(current.bytes),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is the path on Windows 11
|
|
||||||
if current.is_none() {
|
|
||||||
current = hkcu
|
|
||||||
.open_subkey(r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops")
|
|
||||||
.ok()
|
|
||||||
.and_then(
|
|
||||||
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
|
||||||
Ok(current) => Option::from(current.bytes),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
|
|
||||||
// exist until one has been created in the task view
|
|
||||||
|
|
||||||
// The registry value will also not exist on user login if virtual desktops have been created
|
|
||||||
// but the task view has not been initiated
|
|
||||||
|
|
||||||
// In both of these cases, we return None, and the virtual desktop validation will never run. In
|
|
||||||
// the latter case, if the user desires this validation after initiating the task view, komorebi
|
|
||||||
// should be restarted, and then when this // fn runs again for the first time, it will pick up
|
|
||||||
// the value of CurrentVirtualDesktop and validate against it accordingly
|
|
||||||
current
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum NotificationEvent {
|
|
||||||
WindowManager(WindowManagerEvent),
|
|
||||||
Socket(SocketMessage),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct Notification {
|
|
||||||
pub event: NotificationEvent,
|
|
||||||
pub state: State,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notify_subscribers(notification: &str) -> Result<()> {
|
|
||||||
let mut stale_sockets = vec![];
|
|
||||||
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
|
||||||
|
|
||||||
for (socket, path) in &mut *sockets {
|
|
||||||
match UnixStream::connect(path) {
|
|
||||||
Ok(mut stream) => {
|
|
||||||
tracing::debug!("pushed notification to subscriber: {socket}");
|
|
||||||
stream.write_all(notification.as_bytes())?;
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
stale_sockets.push(socket.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for socket in stale_sockets {
|
|
||||||
tracing::warn!("removing stale subscription: {socket}");
|
|
||||||
sockets.remove(&socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut stale_pipes = vec![];
|
|
||||||
let mut pipes = SUBSCRIPTION_PIPES.lock();
|
|
||||||
for (subscriber, pipe) in &mut *pipes {
|
|
||||||
match writeln!(pipe, "{notification}") {
|
|
||||||
Ok(()) => {
|
|
||||||
tracing::debug!("pushed notification to subscriber: {subscriber}");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
// ERROR_FILE_NOT_FOUND
|
|
||||||
// 2 (0x2)
|
|
||||||
// The system cannot find the file specified.
|
|
||||||
|
|
||||||
// ERROR_NO_DATA
|
|
||||||
// 232 (0xE8)
|
|
||||||
// The pipe is being closed.
|
|
||||||
|
|
||||||
// Remove the subscription; the process will have to subscribe again
|
|
||||||
if let Some(2 | 232) = error.raw_os_error() {
|
|
||||||
stale_pipes.push(subscriber.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for subscriber in stale_pipes {
|
|
||||||
tracing::warn!("removing stale subscription: {}", subscriber);
|
|
||||||
pipes.remove(&subscriber);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_configuration() -> Result<()> {
|
|
||||||
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
|
||||||
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
|
||||||
|
|
||||||
if config_pwsh.exists() {
|
|
||||||
let powershell_exe = if which("pwsh.exe").is_ok() {
|
|
||||||
"pwsh.exe"
|
|
||||||
} else {
|
|
||||||
"powershell.exe"
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::info!("loading configuration file: {}", config_pwsh.display());
|
|
||||||
|
|
||||||
Command::new(powershell_exe)
|
|
||||||
.arg(config_pwsh.as_os_str())
|
|
||||||
.output()?;
|
|
||||||
} else if config_ahk.exists() && which(&*AHK_EXE).is_ok() {
|
|
||||||
tracing::info!("loading configuration file: {}", config_ahk.display());
|
|
||||||
|
|
||||||
Command::new(&*AHK_EXE)
|
|
||||||
.arg(config_ahk.as_os_str())
|
|
||||||
.output()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,16 @@
|
|||||||
clippy::significant_drop_in_scrutinee
|
clippy::significant_drop_in_scrutinee
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::net::TcpStream;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::atomic::AtomicI32;
|
||||||
|
use std::sync::atomic::AtomicIsize;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
@@ -14,29 +23,222 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
use crossbeam_utils::Backoff;
|
use crossbeam_utils::Backoff;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use os_info::Version;
|
||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
use parking_lot::deadlock;
|
use parking_lot::deadlock;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use regex::Regex;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Serialize;
|
||||||
use sysinfo::Process;
|
use sysinfo::Process;
|
||||||
use tracing_appender::non_blocking::WorkerGuard;
|
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 which::which;
|
||||||
|
use winreg::enums::HKEY_CURRENT_USER;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
use komorebi::hidden::Hidden;
|
use crate::hidden::Hidden;
|
||||||
use komorebi::load_configuration;
|
use komorebi_core::config_generation::IdWithIdentifier;
|
||||||
use komorebi::process_command::listen_for_commands;
|
use komorebi_core::config_generation::MatchingStrategy;
|
||||||
use komorebi::process_command::listen_for_commands_tcp;
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi::process_event::listen_for_events;
|
use komorebi_core::HidingBehaviour;
|
||||||
use komorebi::process_movement::listen_for_movements;
|
use komorebi_core::Rect;
|
||||||
use komorebi::static_config::StaticConfig;
|
use komorebi_core::SocketMessage;
|
||||||
use komorebi::window_manager::WindowManager;
|
|
||||||
use komorebi::windows_api::WindowsApi;
|
use crate::process_command::listen_for_commands;
|
||||||
use komorebi::winevent_listener;
|
use crate::process_command::listen_for_commands_tcp;
|
||||||
use komorebi::CUSTOM_FFM;
|
use crate::process_event::listen_for_events;
|
||||||
use komorebi::HOME_DIR;
|
use crate::process_movement::listen_for_movements;
|
||||||
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
use crate::static_config::StaticConfig;
|
||||||
use komorebi::SESSION_ID;
|
use crate::window_manager::State;
|
||||||
|
use crate::window_manager::WindowManager;
|
||||||
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
|
use crate::windows_api::WindowsApi;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod ring;
|
||||||
|
|
||||||
|
mod border;
|
||||||
|
mod com;
|
||||||
|
mod container;
|
||||||
|
mod hidden;
|
||||||
|
mod monitor;
|
||||||
|
mod process_command;
|
||||||
|
mod process_event;
|
||||||
|
mod process_movement;
|
||||||
|
mod set_window_position;
|
||||||
|
mod static_config;
|
||||||
|
mod styles;
|
||||||
|
mod window;
|
||||||
|
mod window_manager;
|
||||||
|
mod window_manager_event;
|
||||||
|
mod windows_api;
|
||||||
|
mod windows_callbacks;
|
||||||
|
mod winevent;
|
||||||
|
mod winevent_listener;
|
||||||
|
mod workspace;
|
||||||
|
|
||||||
|
type WorkspaceRule = (usize, usize, bool);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("steam.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
]));
|
||||||
|
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> =
|
||||||
|
Arc::new(Mutex::new(vec![
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("explorer.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("firefox.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("chrome.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("idea64.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("ApplicationFrameHost.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("steam.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("firefox.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Exe,
|
||||||
|
id: String::from("idea64.exe"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
]));
|
||||||
|
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref DISPLAY_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, String>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref WORKSPACE_RULES: Arc<Mutex<HashMap<String, WorkspaceRule>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
|
||||||
|
// mstsc.exe creates these on Windows 11 when a WSL process is launched
|
||||||
|
// https://github.com/LGUG2Z/komorebi/issues/74
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Class,
|
||||||
|
id: String::from("OPContainerClass"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
},
|
||||||
|
IdWithIdentifier {
|
||||||
|
kind: ApplicationIdentifier::Class,
|
||||||
|
id: String::from("IHWindowClass"),
|
||||||
|
matching_strategy: Option::from(MatchingStrategy::Equals),
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||||
|
"Chrome_RenderWidgetHostHWND".to_string(),
|
||||||
|
]));
|
||||||
|
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
|
||||||
|
"X410.exe".to_string(),
|
||||||
|
"vcxsrv.exe".to_string(),
|
||||||
|
]));
|
||||||
|
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
|
||||||
|
Arc::new(Mutex::new(HidingBehaviour::Minimize));
|
||||||
|
static ref HOME_DIR: PathBuf = {
|
||||||
|
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(|_| dirs::home_dir().expect("there is no home directory"), |home_path| {
|
||||||
|
let home = PathBuf::from(&home_path);
|
||||||
|
|
||||||
|
if home.as_path().is_dir() {
|
||||||
|
home
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect("there is no local data directory").join("komorebi");
|
||||||
|
static ref AHK_EXE: String = {
|
||||||
|
let mut ahk: String = String::from("autohotkey.exe");
|
||||||
|
|
||||||
|
if let Ok(komorebi_ahk_exe) = std::env::var("KOMOREBI_AHK_EXE") {
|
||||||
|
if which(&komorebi_ahk_exe).is_ok() {
|
||||||
|
ahk = komorebi_ahk_exe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ahk
|
||||||
|
};
|
||||||
|
static ref WINDOWS_11: bool = {
|
||||||
|
matches!(
|
||||||
|
os_info::get().version(),
|
||||||
|
Version::Semantic(_, _, x) if x >= &22000
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
static ref BORDER_RECT: Arc<Mutex<Rect>> =
|
||||||
|
Arc::new(Mutex::new(Rect::default()));
|
||||||
|
|
||||||
|
static ref BORDER_OFFSET: Arc<Mutex<Option<Rect>>> =
|
||||||
|
Arc::new(Mutex::new(None));
|
||||||
|
|
||||||
|
// Use app-specific titlebar removal options where possible
|
||||||
|
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
||||||
|
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||||
|
pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);
|
||||||
|
|
||||||
|
pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
|
||||||
|
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
|
||||||
|
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
||||||
|
pub static ALT_FOCUS_HACK: AtomicBool = AtomicBool::new(false);
|
||||||
|
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(20);
|
||||||
|
// 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 HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||||
|
|
||||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||||
@@ -101,6 +303,124 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
|||||||
Ok((guard, color_guard))
|
Ok((guard, color_guard))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_configuration() -> Result<()> {
|
||||||
|
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
||||||
|
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
||||||
|
|
||||||
|
if config_pwsh.exists() {
|
||||||
|
let powershell_exe = if which("pwsh.exe").is_ok() {
|
||||||
|
"pwsh.exe"
|
||||||
|
} else {
|
||||||
|
"powershell.exe"
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("loading configuration file: {}", config_pwsh.display());
|
||||||
|
|
||||||
|
Command::new(powershell_exe)
|
||||||
|
.arg(config_pwsh.as_os_str())
|
||||||
|
.output()?;
|
||||||
|
} else if config_ahk.exists() && which(&*AHK_EXE).is_ok() {
|
||||||
|
tracing::info!("loading configuration file: {}", config_ahk.display());
|
||||||
|
|
||||||
|
Command::new(&*AHK_EXE)
|
||||||
|
.arg(config_ahk.as_os_str())
|
||||||
|
.output()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||||
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
|
|
||||||
|
// This is the path on Windows 10
|
||||||
|
let mut current = hkcu
|
||||||
|
.open_subkey(format!(
|
||||||
|
r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\{}\VirtualDesktops"#,
|
||||||
|
SESSION_ID.load(Ordering::SeqCst)
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
.and_then(
|
||||||
|
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||||
|
Ok(current) => Option::from(current.bytes),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is the path on Windows 11
|
||||||
|
if current.is_none() {
|
||||||
|
current = hkcu
|
||||||
|
.open_subkey(r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops")
|
||||||
|
.ok()
|
||||||
|
.and_then(
|
||||||
|
|desktops| match desktops.get_raw_value("CurrentVirtualDesktop") {
|
||||||
|
Ok(current) => Option::from(current.bytes),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not
|
||||||
|
// exist until one has been created in the task view
|
||||||
|
|
||||||
|
// The registry value will also not exist on user login if virtual desktops have been created
|
||||||
|
// but the task view has not been initiated
|
||||||
|
|
||||||
|
// In both of these cases, we return None, and the virtual desktop validation will never run. In
|
||||||
|
// the latter case, if the user desires this validation after initiating the task view, komorebi
|
||||||
|
// should be restarted, and then when this // fn runs again for the first time, it will pick up
|
||||||
|
// the value of CurrentVirtualDesktop and validate against it accordingly
|
||||||
|
current
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, JsonSchema)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum NotificationEvent {
|
||||||
|
WindowManager(WindowManagerEvent),
|
||||||
|
Socket(SocketMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, JsonSchema)]
|
||||||
|
pub struct Notification {
|
||||||
|
pub event: NotificationEvent,
|
||||||
|
pub state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notify_subscribers(notification: &str) -> Result<()> {
|
||||||
|
let mut stale_subscriptions = vec![];
|
||||||
|
let mut subscriptions = SUBSCRIPTION_PIPES.lock();
|
||||||
|
for (subscriber, pipe) in &mut *subscriptions {
|
||||||
|
match writeln!(pipe, "{notification}") {
|
||||||
|
Ok(()) => {
|
||||||
|
tracing::debug!("pushed notification to subscriber: {}", subscriber);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
// ERROR_FILE_NOT_FOUND
|
||||||
|
// 2 (0x2)
|
||||||
|
// The system cannot find the file specified.
|
||||||
|
|
||||||
|
// ERROR_NO_DATA
|
||||||
|
// 232 (0xE8)
|
||||||
|
// The pipe is being closed.
|
||||||
|
|
||||||
|
// Remove the subscription; the process will have to subscribe again
|
||||||
|
if let Some(2 | 232) = error.raw_os_error() {
|
||||||
|
let subscriber_cl = subscriber.clone();
|
||||||
|
stale_subscriptions.push(subscriber_cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for subscriber in stale_subscriptions {
|
||||||
|
tracing::warn!("removing stale subscription: {}", subscriber);
|
||||||
|
subscriptions.remove(&subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
fn detect_deadlocks() {
|
fn detect_deadlocks() {
|
||||||
@@ -162,8 +482,8 @@ fn main() -> Result<()> {
|
|||||||
if matched_procs.len() > 1 {
|
if matched_procs.len() > 1 {
|
||||||
let mut len = matched_procs.len();
|
let mut len = matched_procs.len();
|
||||||
for proc in matched_procs {
|
for proc in matched_procs {
|
||||||
if let Some(executable_path) = proc.exe() {
|
if let Some(root) = proc.root() {
|
||||||
if executable_path.to_string_lossy().contains("shims") {
|
if root.ends_with("shims") {
|
||||||
len -= 1;
|
len -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,11 +500,15 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
WindowsApi::foreground_lock_timeout()?;
|
WindowsApi::foreground_lock_timeout()?;
|
||||||
|
|
||||||
winevent_listener::start();
|
|
||||||
|
|
||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
detect_deadlocks();
|
detect_deadlocks();
|
||||||
|
|
||||||
|
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||||
|
crossbeam_channel::unbounded();
|
||||||
|
|
||||||
|
let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing)));
|
||||||
|
winevent_listener.start();
|
||||||
|
|
||||||
Hidden::create("komorebi-hidden")?;
|
Hidden::create("komorebi-hidden")?;
|
||||||
|
|
||||||
let static_config = opts.config.map_or_else(
|
let static_config = opts.config.map_or_else(
|
||||||
@@ -207,12 +531,12 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
Arc::new(Mutex::new(StaticConfig::preload(
|
Arc::new(Mutex::new(StaticConfig::preload(
|
||||||
config,
|
config,
|
||||||
winevent_listener::event_rx(),
|
Arc::new(Mutex::new(incoming)),
|
||||||
)?))
|
)?))
|
||||||
} else {
|
} else {
|
||||||
Arc::new(Mutex::new(WindowManager::new(
|
Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new(
|
||||||
winevent_listener::event_rx(),
|
incoming,
|
||||||
)?))
|
)))?))
|
||||||
};
|
};
|
||||||
|
|
||||||
wm.lock().init()?;
|
wm.lock().init()?;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use getset::Getters;
|
|||||||
use getset::MutGetters;
|
use getset::MutGetters;
|
||||||
use getset::Setters;
|
use getset::Setters;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use komorebi_core::Rect;
|
use komorebi_core::Rect;
|
||||||
@@ -18,9 +17,7 @@ use crate::container::Container;
|
|||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
|
|
||||||
#[derive(
|
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
|
|
||||||
)]
|
|
||||||
pub struct Monitor {
|
pub struct Monitor {
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
id: isize,
|
id: isize,
|
||||||
@@ -37,9 +34,10 @@ pub struct Monitor {
|
|||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
work_area_offset: Option<Rect>,
|
work_area_offset: Option<Rect>,
|
||||||
workspaces: Ring<Workspace>,
|
workspaces: Ring<Workspace>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing)]
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
last_focused_workspace: Option<usize>,
|
last_focused_workspace: Option<usize>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
#[getset(get_mut = "pub")]
|
#[getset(get_mut = "pub")]
|
||||||
workspace_names: HashMap<usize, String>,
|
workspace_names: HashMap<usize, String>,
|
||||||
}
|
}
|
||||||
@@ -71,7 +69,7 @@ impl Monitor {
|
|||||||
if i == focused_idx {
|
if i == focused_idx {
|
||||||
workspace.restore(mouse_follows_focus)?;
|
workspace.restore(mouse_follows_focus)?;
|
||||||
} else {
|
} else {
|
||||||
workspace.hide(None);
|
workspace.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +190,11 @@ impl Monitor {
|
|||||||
self.workspaces().len()
|
self.workspaces().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
|
pub fn update_focused_workspace(
|
||||||
|
&mut self,
|
||||||
|
offset: Option<Rect>,
|
||||||
|
invisible_borders: &Rect,
|
||||||
|
) -> Result<()> {
|
||||||
let work_area = *self.work_area_size();
|
let work_area = *self.work_area_size();
|
||||||
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()
|
||||||
@@ -202,7 +204,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)?;
|
.update(&work_area, offset, invisible_borders)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::fs::OpenOptions;
|
|||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
@@ -23,7 +24,6 @@ use uds_windows::UnixStream;
|
|||||||
|
|
||||||
use komorebi_core::config_generation::ApplicationConfiguration;
|
use komorebi_core::config_generation::ApplicationConfiguration;
|
||||||
use komorebi_core::config_generation::IdWithIdentifier;
|
use komorebi_core::config_generation::IdWithIdentifier;
|
||||||
use komorebi_core::config_generation::MatchingRule;
|
|
||||||
use komorebi_core::config_generation::MatchingStrategy;
|
use komorebi_core::config_generation::MatchingStrategy;
|
||||||
use komorebi_core::ApplicationIdentifier;
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi_core::Axis;
|
use komorebi_core::Axis;
|
||||||
@@ -39,7 +39,6 @@ use komorebi_core::WindowContainerBehaviour;
|
|||||||
use komorebi_core::WindowKind;
|
use komorebi_core::WindowKind;
|
||||||
|
|
||||||
use crate::border::Border;
|
use crate::border::Border;
|
||||||
use crate::colour::Rgb;
|
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::notify_subscribers;
|
use crate::notify_subscribers;
|
||||||
use crate::static_config::StaticConfig;
|
use crate::static_config::StaticConfig;
|
||||||
@@ -49,6 +48,7 @@ use crate::window_manager::WindowManager;
|
|||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
use crate::NotificationEvent;
|
use crate::NotificationEvent;
|
||||||
|
use crate::ALT_FOCUS_HACK;
|
||||||
use crate::BORDER_COLOUR_CURRENT;
|
use crate::BORDER_COLOUR_CURRENT;
|
||||||
use crate::BORDER_COLOUR_MONOCLE;
|
use crate::BORDER_COLOUR_MONOCLE;
|
||||||
use crate::BORDER_COLOUR_SINGLE;
|
use crate::BORDER_COLOUR_SINGLE;
|
||||||
@@ -72,7 +72,6 @@ use crate::NO_TITLEBAR;
|
|||||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||||
use crate::REMOVE_TITLEBARS;
|
use crate::REMOVE_TITLEBARS;
|
||||||
use crate::SUBSCRIPTION_PIPES;
|
use crate::SUBSCRIPTION_PIPES;
|
||||||
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;
|
||||||
@@ -145,15 +144,8 @@ pub fn listen_for_commands_tcp(wm: Arc<Mutex<WindowManager>>, port: usize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WindowManager {
|
impl WindowManager {
|
||||||
// TODO(raggi): wrap reply in a newtype that can decorate a human friendly
|
#[tracing::instrument(skip(self))]
|
||||||
// name for the peer, such as getting the pid of the komorebic process for
|
pub fn process_command(&mut self, message: SocketMessage) -> Result<()> {
|
||||||
// the UDS or the IP:port for TCP.
|
|
||||||
#[tracing::instrument(skip(self, reply))]
|
|
||||||
pub fn process_command(
|
|
||||||
&mut self,
|
|
||||||
message: SocketMessage,
|
|
||||||
mut reply: impl std::io::Write,
|
|
||||||
) -> Result<()> {
|
|
||||||
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 {
|
||||||
@@ -217,7 +209,6 @@ impl WindowManager {
|
|||||||
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
|
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
|
||||||
SocketMessage::CycleStack(direction) => {
|
SocketMessage::CycleStack(direction) => {
|
||||||
self.cycle_container_window_in_direction(direction)?;
|
self.cycle_container_window_in_direction(direction)?;
|
||||||
self.focused_window()?.focus(self.mouse_follows_focus)?;
|
|
||||||
}
|
}
|
||||||
SocketMessage::ForceFocus => {
|
SocketMessage::ForceFocus => {
|
||||||
let focused_window = self.focused_window()?;
|
let focused_window = self.focused_window()?;
|
||||||
@@ -275,19 +266,17 @@ impl WindowManager {
|
|||||||
|
|
||||||
let mut should_push = true;
|
let mut should_push = true;
|
||||||
for m in &*manage_identifiers {
|
for m in &*manage_identifiers {
|
||||||
if let MatchingRule::Simple(m) = m {
|
if m.id.eq(id) {
|
||||||
if m.id.eq(id) {
|
should_push = false;
|
||||||
should_push = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_push {
|
if should_push {
|
||||||
manage_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
manage_identifiers.push(IdWithIdentifier {
|
||||||
kind: identifier,
|
kind: identifier,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::FloatRule(identifier, ref id) => {
|
SocketMessage::FloatRule(identifier, ref id) => {
|
||||||
@@ -295,21 +284,20 @@ impl WindowManager {
|
|||||||
|
|
||||||
let mut should_push = true;
|
let mut should_push = true;
|
||||||
for f in &*float_identifiers {
|
for f in &*float_identifiers {
|
||||||
if let MatchingRule::Simple(f) = f {
|
if f.id.eq(id) {
|
||||||
if f.id.eq(id) {
|
should_push = false;
|
||||||
should_push = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_push {
|
if should_push {
|
||||||
float_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
float_identifiers.push(IdWithIdentifier {
|
||||||
kind: identifier,
|
kind: identifier,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
|
||||||
let mut hwnds_to_purge = vec![];
|
let mut hwnds_to_purge = vec![];
|
||||||
@@ -321,11 +309,6 @@ impl WindowManager {
|
|||||||
{
|
{
|
||||||
for window in container.windows() {
|
for window in container.windows() {
|
||||||
match identifier {
|
match identifier {
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if window.path()? == *id {
|
|
||||||
hwnds_to_purge.push((i, window.hwnd));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
ApplicationIdentifier::Exe => {
|
||||||
if window.exe()? == *id {
|
if window.exe()? == *id {
|
||||||
hwnds_to_purge.push((i, window.hwnd));
|
hwnds_to_purge.push((i, window.hwnd));
|
||||||
@@ -357,7 +340,7 @@ impl WindowManager {
|
|||||||
.ok_or_else(|| anyhow!("there is no focused workspace"))?
|
.ok_or_else(|| anyhow!("there is no focused workspace"))?
|
||||||
.remove_window(hwnd)?;
|
.remove_window(hwnd)?;
|
||||||
|
|
||||||
monitor.update_focused_workspace(offset)?;
|
monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::FocusedWorkspaceContainerPadding(adjustment) => {
|
SocketMessage::FocusedWorkspaceContainerPadding(adjustment) => {
|
||||||
@@ -760,11 +743,15 @@ impl WindowManager {
|
|||||||
Err(error) => error.to_string(),
|
Err(error) => error.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::info!("replying to state");
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
reply.write_all(state.as_bytes())?;
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
tracing::info!("replying to state done");
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(state.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::VisibleWindows => {
|
SocketMessage::VisibleWindows => {
|
||||||
let mut monitor_visible_windows = HashMap::new();
|
let mut monitor_visible_windows = HashMap::new();
|
||||||
@@ -787,7 +774,15 @@ impl WindowManager {
|
|||||||
Err(error) => error.to_string(),
|
Err(error) => error.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
reply.write_all(visible_windows_state.as_bytes())?;
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(visible_windows_state.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketMessage::Query(query) => {
|
SocketMessage::Query(query) => {
|
||||||
@@ -806,7 +801,15 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
reply.write_all(response.as_bytes())?;
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(response.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::ResizeWindowEdge(direction, sizing) => {
|
SocketMessage::ResizeWindowEdge(direction, sizing) => {
|
||||||
self.resize_window(direction, sizing, self.resize_delta, true)?;
|
self.resize_window(direction, sizing, self.resize_delta, true)?;
|
||||||
@@ -1031,19 +1034,17 @@ impl WindowManager {
|
|||||||
|
|
||||||
let mut should_push = true;
|
let mut should_push = true;
|
||||||
for i in &*identifiers {
|
for i in &*identifiers {
|
||||||
if let MatchingRule::Simple(i) = i {
|
if i.id.eq(id) {
|
||||||
if i.id.eq(id) {
|
should_push = false;
|
||||||
should_push = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_push {
|
if should_push {
|
||||||
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
identifiers.push(IdWithIdentifier {
|
||||||
kind: identifier,
|
kind: identifier,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
|
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
|
||||||
@@ -1051,38 +1052,34 @@ impl WindowManager {
|
|||||||
|
|
||||||
let mut should_push = true;
|
let mut should_push = true;
|
||||||
for i in &*identifiers {
|
for i in &*identifiers {
|
||||||
if let MatchingRule::Simple(i) = i {
|
if i.id.eq(id) {
|
||||||
if i.id.eq(id) {
|
should_push = false;
|
||||||
should_push = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_push {
|
if should_push {
|
||||||
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
identifiers.push(IdWithIdentifier {
|
||||||
kind: identifier,
|
kind: identifier,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::IdentifyTrayApplication(identifier, ref id) => {
|
SocketMessage::IdentifyTrayApplication(identifier, ref id) => {
|
||||||
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
||||||
let mut should_push = true;
|
let mut should_push = true;
|
||||||
for i in &*identifiers {
|
for i in &*identifiers {
|
||||||
if let MatchingRule::Simple(i) = i {
|
if i.id.eq(id) {
|
||||||
if i.id.eq(id) {
|
should_push = false;
|
||||||
should_push = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_push {
|
if should_push {
|
||||||
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
identifiers.push(IdWithIdentifier {
|
||||||
kind: identifier,
|
kind: identifier,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::IdentifyLayeredApplication(identifier, ref id) => {
|
SocketMessage::IdentifyLayeredApplication(identifier, ref id) => {
|
||||||
@@ -1090,19 +1087,17 @@ impl WindowManager {
|
|||||||
|
|
||||||
let mut should_push = true;
|
let mut should_push = true;
|
||||||
for i in &*identifiers {
|
for i in &*identifiers {
|
||||||
if let MatchingRule::Simple(i) = i {
|
if i.id.eq(id) {
|
||||||
if i.id.eq(id) {
|
should_push = false;
|
||||||
should_push = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_push {
|
if should_push {
|
||||||
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
|
identifiers.push(IdWithIdentifier {
|
||||||
kind: identifier,
|
kind: identifier,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
matching_strategy: Option::from(MatchingStrategy::Legacy),
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::ManageFocusedWindow => {
|
SocketMessage::ManageFocusedWindow => {
|
||||||
@@ -1111,7 +1106,10 @@ impl WindowManager {
|
|||||||
SocketMessage::UnmanageFocusedWindow => {
|
SocketMessage::UnmanageFocusedWindow => {
|
||||||
self.unmanage_focused_window()?;
|
self.unmanage_focused_window()?;
|
||||||
}
|
}
|
||||||
SocketMessage::InvisibleBorders(_rect) => {}
|
SocketMessage::InvisibleBorders(rect) => {
|
||||||
|
self.invisible_borders = rect;
|
||||||
|
self.retile_all(false)?;
|
||||||
|
}
|
||||||
SocketMessage::WorkAreaOffset(rect) => {
|
SocketMessage::WorkAreaOffset(rect) => {
|
||||||
self.work_area_offset = Option::from(rect);
|
self.work_area_offset = Option::from(rect);
|
||||||
self.retile_all(false)?;
|
self.retile_all(false)?;
|
||||||
@@ -1172,16 +1170,7 @@ impl WindowManager {
|
|||||||
workspace.set_resize_dimensions(resize);
|
workspace.set_resize_dimensions(resize);
|
||||||
self.update_focused_workspace(false)?;
|
self.update_focused_workspace(false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::AddSubscriberSocket(ref socket) => {
|
SocketMessage::AddSubscriber(ref subscriber) => {
|
||||||
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
|
||||||
let socket_path = DATA_DIR.join(socket);
|
|
||||||
sockets.insert(socket.clone(), socket_path);
|
|
||||||
}
|
|
||||||
SocketMessage::RemoveSubscriberSocket(ref socket) => {
|
|
||||||
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
|
|
||||||
sockets.remove(socket);
|
|
||||||
}
|
|
||||||
SocketMessage::AddSubscriberPipe(ref subscriber) => {
|
|
||||||
let mut pipes = SUBSCRIPTION_PIPES.lock();
|
let mut pipes = SUBSCRIPTION_PIPES.lock();
|
||||||
let pipe_path = format!(r"\\.\pipe\{subscriber}");
|
let pipe_path = format!(r"\\.\pipe\{subscriber}");
|
||||||
let pipe = connect(&pipe_path).map_err(|_| {
|
let pipe = connect(&pipe_path).map_err(|_| {
|
||||||
@@ -1190,7 +1179,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
pipes.insert(subscriber.clone(), pipe);
|
pipes.insert(subscriber.clone(), pipe);
|
||||||
}
|
}
|
||||||
SocketMessage::RemoveSubscriberPipe(ref subscriber) => {
|
SocketMessage::RemoveSubscriber(ref subscriber) => {
|
||||||
let mut pipes = SUBSCRIPTION_PIPES.lock();
|
let mut pipes = SUBSCRIPTION_PIPES.lock();
|
||||||
pipes.remove(subscriber);
|
pipes.remove(subscriber);
|
||||||
}
|
}
|
||||||
@@ -1249,14 +1238,14 @@ impl WindowManager {
|
|||||||
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
|
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
|
||||||
match kind {
|
match kind {
|
||||||
WindowKind::Single => {
|
WindowKind::Single => {
|
||||||
BORDER_COLOUR_SINGLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
BORDER_COLOUR_SINGLE.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
|
||||||
BORDER_COLOUR_CURRENT.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
BORDER_COLOUR_CURRENT.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
WindowKind::Stack => {
|
WindowKind::Stack => {
|
||||||
BORDER_COLOUR_STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
BORDER_COLOUR_STACK.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
WindowKind::Monocle => {
|
WindowKind::Monocle => {
|
||||||
BORDER_COLOUR_MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
BORDER_COLOUR_MONOCLE.store(r | (g << 8) | (b << 16), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,29 +1256,60 @@ impl WindowManager {
|
|||||||
WindowsApi::invalidate_border_rect()?;
|
WindowsApi::invalidate_border_rect()?;
|
||||||
}
|
}
|
||||||
SocketMessage::ActiveWindowBorderOffset(offset) => {
|
SocketMessage::ActiveWindowBorderOffset(offset) => {
|
||||||
BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
let mut current_border_offset = BORDER_OFFSET.lock();
|
||||||
|
|
||||||
|
let new_border_offset = Rect {
|
||||||
|
left: offset,
|
||||||
|
top: offset,
|
||||||
|
right: offset * 2,
|
||||||
|
bottom: offset * 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
*current_border_offset = Option::from(new_border_offset);
|
||||||
|
|
||||||
WindowsApi::invalidate_border_rect()?;
|
WindowsApi::invalidate_border_rect()?;
|
||||||
}
|
}
|
||||||
SocketMessage::AltFocusHack(_) => {
|
SocketMessage::AltFocusHack(enable) => {
|
||||||
tracing::info!("this action is deprecated");
|
ALT_FOCUS_HACK.store(enable, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
SocketMessage::ApplicationSpecificConfigurationSchema => {
|
SocketMessage::ApplicationSpecificConfigurationSchema => {
|
||||||
let asc = schema_for!(Vec<ApplicationConfiguration>);
|
let asc = schema_for!(Vec<ApplicationConfiguration>);
|
||||||
let schema = serde_json::to_string_pretty(&asc)?;
|
let schema = serde_json::to_string_pretty(&asc)?;
|
||||||
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
reply.write_all(schema.as_bytes())?;
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(schema.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::NotificationSchema => {
|
SocketMessage::NotificationSchema => {
|
||||||
let notification = schema_for!(Notification);
|
let notification = schema_for!(Notification);
|
||||||
let schema = serde_json::to_string_pretty(¬ification)?;
|
let schema = serde_json::to_string_pretty(¬ification)?;
|
||||||
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
reply.write_all(schema.as_bytes())?;
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(schema.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::SocketSchema => {
|
SocketMessage::SocketSchema => {
|
||||||
let socket_message = schema_for!(SocketMessage);
|
let socket_message = schema_for!(SocketMessage);
|
||||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||||
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
reply.write_all(schema.as_bytes())?;
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(schema.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::StaticConfigSchema => {
|
SocketMessage::StaticConfigSchema => {
|
||||||
let settings = SchemaSettings::default().with(|s| {
|
let settings = SchemaSettings::default().with(|s| {
|
||||||
@@ -1301,13 +1321,27 @@ impl WindowManager {
|
|||||||
let gen = settings.into_generator();
|
let gen = settings.into_generator();
|
||||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||||
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
reply.write_all(schema.as_bytes())?;
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(schema.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::GenerateStaticConfig => {
|
SocketMessage::GenerateStaticConfig => {
|
||||||
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
|
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
|
||||||
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
reply.write_all(config.as_bytes())?;
|
let mut connected = false;
|
||||||
|
while !connected {
|
||||||
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(config.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::RemoveTitleBar(_, ref id) => {
|
SocketMessage::RemoveTitleBar(_, ref id) => {
|
||||||
let mut identifiers = NO_TITLEBAR.lock();
|
let mut identifiers = NO_TITLEBAR.lock();
|
||||||
@@ -1394,6 +1428,9 @@ impl WindowManager {
|
|||||||
| SocketMessage::FocusWorkspaceNumber(_) => {
|
| SocketMessage::FocusWorkspaceNumber(_) => {
|
||||||
let foreground = WindowsApi::foreground_window()?;
|
let foreground = WindowsApi::foreground_window()?;
|
||||||
let foreground_window = Window { hwnd: foreground };
|
let foreground_window = Window { hwnd: foreground };
|
||||||
|
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
|
||||||
|
rect.top -= self.invisible_borders.bottom;
|
||||||
|
rect.bottom += self.invisible_borders.bottom;
|
||||||
|
|
||||||
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
|
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
|
||||||
if monocle != 0 && self.focused_workspace()?.monocle_container().is_some() {
|
if monocle != 0 && self.focused_workspace()?.monocle_container().is_some() {
|
||||||
@@ -1403,18 +1440,14 @@ impl WindowManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// it is not acceptable to fail here; we need to be able to send the event to
|
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
|
||||||
// subscribers
|
if stack != 0 && self.focused_container()?.windows().len() > 1 {
|
||||||
if self.focused_container().is_ok() {
|
BORDER_COLOUR_CURRENT
|
||||||
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
|
.store(stack, 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));
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
border.set_position(foreground_window, false)?;
|
border.set_position(foreground_window, &self.invisible_borders, false)?;
|
||||||
}
|
}
|
||||||
SocketMessage::TogglePause => {
|
SocketMessage::TogglePause => {
|
||||||
let is_paused = self.is_paused;
|
let is_paused = self.is_paused;
|
||||||
@@ -1424,7 +1457,7 @@ impl WindowManager {
|
|||||||
border.hide()?;
|
border.hide()?;
|
||||||
} else {
|
} else {
|
||||||
let focused = self.focused_window()?;
|
let focused = self.focused_window()?;
|
||||||
border.set_position(*focused, true)?;
|
border.set_position(*focused, &self.invisible_borders, true)?;
|
||||||
focused.focus(false)?;
|
focused.focus(false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1434,7 +1467,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
if tiling_enabled {
|
if tiling_enabled {
|
||||||
let focused = self.focused_window()?;
|
let focused = self.focused_window()?;
|
||||||
border.set_position(*focused, true)?;
|
border.set_position(*focused, &self.invisible_borders, true)?;
|
||||||
focused.focus(false)?;
|
focused.focus(false)?;
|
||||||
} else {
|
} else {
|
||||||
border.hide()?;
|
border.hide()?;
|
||||||
@@ -1493,13 +1526,9 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, mut stream: UnixStream) -> Result<()> {
|
pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, stream: UnixStream) -> Result<()> {
|
||||||
let reader = BufReader::new(stream.try_clone()?);
|
let stream = BufReader::new(stream);
|
||||||
// TODO(raggi): while this processes more than one command, if there are
|
for line in stream.lines() {
|
||||||
// replies there is no clearly defined protocol for framing yet - it's
|
|
||||||
// perhaps whole-json objects for now, but termination is signalled by
|
|
||||||
// socket shutdown.
|
|
||||||
for line in reader.lines() {
|
|
||||||
let message = SocketMessage::from_str(&line?)?;
|
let message = SocketMessage::from_str(&line?)?;
|
||||||
|
|
||||||
let mut wm = wm.lock();
|
let mut wm = wm.lock();
|
||||||
@@ -1507,7 +1536,7 @@ 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::State | SocketMessage::Stop => {
|
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
|
||||||
Ok(wm.process_command(message, &mut stream)?)
|
Ok(wm.process_command(message)?)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::trace!("ignoring while paused");
|
tracing::trace!("ignoring while paused");
|
||||||
@@ -1516,7 +1545,7 @@ pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, mut stream: UnixStream)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
wm.process_command(message.clone(), &mut stream)?;
|
wm.process_command(message.clone())?;
|
||||||
notify_subscribers(&serde_json::to_string(&Notification {
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
event: NotificationEvent::Socket(message.clone()),
|
event: NotificationEvent::Socket(message.clone()),
|
||||||
state: wm.as_ref().into(),
|
state: wm.as_ref().into(),
|
||||||
@@ -1531,11 +1560,11 @@ pub fn read_commands_tcp(
|
|||||||
stream: &mut TcpStream,
|
stream: &mut TcpStream,
|
||||||
addr: &str,
|
addr: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut reader = BufReader::new(stream.try_clone()?);
|
let mut stream = BufReader::new(stream);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut buf = vec![0; 1024];
|
let mut buf = vec![0; 1024];
|
||||||
match reader.read(&mut buf) {
|
match stream.read(&mut buf) {
|
||||||
Err(..) => {
|
Err(..) => {
|
||||||
tracing::warn!("removing disconnected tcp client: {addr}");
|
tracing::warn!("removing disconnected tcp client: {addr}");
|
||||||
let mut connections = TCP_CONNECTIONS.lock();
|
let mut connections = TCP_CONNECTIONS.lock();
|
||||||
@@ -1556,7 +1585,7 @@ pub fn read_commands_tcp(
|
|||||||
if wm.is_paused {
|
if wm.is_paused {
|
||||||
return match message {
|
return match message {
|
||||||
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
|
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
|
||||||
Ok(wm.process_command(message, stream)?)
|
Ok(wm.process_command(message)?)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::trace!("ignoring while paused");
|
tracing::trace!("ignoring while paused");
|
||||||
@@ -1565,7 +1594,7 @@ pub fn read_commands_tcp(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
wm.process_command(message.clone(), &mut *stream)?;
|
wm.process_command(message.clone())?;
|
||||||
notify_subscribers(&serde_json::to_string(&Notification {
|
notify_subscribers(&serde_json::to_string(&Notification {
|
||||||
event: NotificationEvent::Socket(message.clone()),
|
event: NotificationEvent::Socket(message.clone()),
|
||||||
state: wm.as_ref().into(),
|
state: wm.as_ref().into(),
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ use crate::BORDER_COLOUR_STACK;
|
|||||||
use crate::BORDER_ENABLED;
|
use crate::BORDER_ENABLED;
|
||||||
use crate::BORDER_HIDDEN;
|
use crate::BORDER_HIDDEN;
|
||||||
use crate::BORDER_HWND;
|
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;
|
||||||
@@ -37,7 +35,7 @@ use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
|||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||||
let receiver = wm.lock().incoming_events.clone();
|
let receiver = wm.lock().incoming_events.lock().clone();
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
tracing::info!("listening");
|
tracing::info!("listening");
|
||||||
@@ -111,6 +109,7 @@ impl WindowManager {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
|
||||||
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||||
@@ -124,7 +123,7 @@ impl WindowManager {
|
|||||||
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||||
let reaped_orphans = workspace.reap_orphans()?;
|
let reaped_orphans = workspace.reap_orphans()?;
|
||||||
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||||
reaped_orphans.0,
|
reaped_orphans.0,
|
||||||
@@ -147,7 +146,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
match event {
|
match event {
|
||||||
WindowManagerEvent::Raise(window) => {
|
WindowManagerEvent::Raise(window) => {
|
||||||
window.focus(false)?;
|
window.raise();
|
||||||
self.has_pending_raise_op = false;
|
self.has_pending_raise_op = false;
|
||||||
}
|
}
|
||||||
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
|
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
|
||||||
@@ -187,7 +186,6 @@ impl WindowManager {
|
|||||||
let title = &window.title()?;
|
let title = &window.title()?;
|
||||||
let exe_name = &window.exe()?;
|
let exe_name = &window.exe()?;
|
||||||
let class = &window.class()?;
|
let class = &window.class()?;
|
||||||
let path = &window.path()?;
|
|
||||||
|
|
||||||
// We don't want to purge windows that have been deliberately hidden by us, eg. when
|
// We don't want to purge windows that have been deliberately hidden by us, eg. when
|
||||||
// they are not on the top of a container stack.
|
// they are not on the top of a container stack.
|
||||||
@@ -196,7 +194,6 @@ impl WindowManager {
|
|||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
|
||||||
&tray_and_multi_window_identifiers,
|
&tray_and_multi_window_identifiers,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
);
|
);
|
||||||
@@ -306,24 +303,21 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowManagerEvent::MoveResizeStart(_, window) => {
|
WindowManagerEvent::MoveResizeStart(_, window) => {
|
||||||
if *self.focused_workspace()?.tile() {
|
let monitor_idx = self.focused_monitor_idx();
|
||||||
let monitor_idx = self.focused_monitor_idx();
|
let workspace_idx = self
|
||||||
let workspace_idx = self
|
.focused_monitor()
|
||||||
.focused_monitor()
|
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
||||||
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
.focused_workspace_idx();
|
||||||
.focused_workspace_idx();
|
let container_idx = self
|
||||||
let container_idx = self
|
.focused_monitor()
|
||||||
.focused_monitor()
|
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
||||||
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
.focused_workspace()
|
||||||
.focused_workspace()
|
.ok_or_else(|| anyhow!("there is no workspace with this idx"))?
|
||||||
.ok_or_else(|| anyhow!("there is no workspace with this idx"))?
|
.focused_container_idx();
|
||||||
.focused_container_idx();
|
|
||||||
|
|
||||||
WindowsApi::bring_window_to_top(window.hwnd())?;
|
WindowsApi::bring_window_to_top(window.hwnd())?;
|
||||||
|
|
||||||
self.pending_move_op =
|
self.pending_move_op = Option::from((monitor_idx, workspace_idx, container_idx));
|
||||||
Option::from((monitor_idx, workspace_idx, container_idx));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
WindowManagerEvent::MoveResizeEnd(_, window) => {
|
WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||||
// We need this because if the event ends on a different monitor,
|
// We need this because if the event ends on a different monitor,
|
||||||
@@ -337,6 +331,7 @@ impl WindowManager {
|
|||||||
.ok_or_else(|| anyhow!("cannot get monitor idx from current position"))?;
|
.ok_or_else(|| anyhow!("cannot get monitor idx from current position"))?;
|
||||||
|
|
||||||
let new_window_behaviour = self.window_container_behaviour;
|
let new_window_behaviour = self.window_container_behaviour;
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
if !workspace
|
if !workspace
|
||||||
@@ -346,7 +341,7 @@ impl WindowManager {
|
|||||||
{
|
{
|
||||||
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 mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||||
|
|
||||||
let old_position = *workspace
|
let old_position = *workspace
|
||||||
.latest_layout()
|
.latest_layout()
|
||||||
@@ -371,6 +366,12 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust for the invisible borders
|
||||||
|
new_position.left += invisible_borders.left;
|
||||||
|
new_position.top += invisible_borders.top;
|
||||||
|
new_position.right -= invisible_borders.right;
|
||||||
|
new_position.bottom -= invisible_borders.bottom;
|
||||||
|
|
||||||
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,
|
||||||
@@ -380,14 +381,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 = ((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 == 0 && resize.bottom == 0
|
||||||
&& resize.bottom.abs() == right_bottom_constant;
|
|| resize.right.abs() == invisible_borders.right
|
||||||
|
&& resize.bottom.abs() == invisible_borders.bottom;
|
||||||
|
|
||||||
if is_move {
|
if is_move {
|
||||||
tracing::info!("moving with mouse");
|
tracing::info!("moving with mouse");
|
||||||
@@ -479,17 +476,17 @@ impl WindowManager {
|
|||||||
let mut ops = vec![];
|
let mut ops = vec![];
|
||||||
|
|
||||||
macro_rules! resize_op {
|
macro_rules! resize_op {
|
||||||
($coordinate:expr, $comparator:tt, $direction:expr) => {{
|
($coordinate:expr, $comparator:tt, $direction:expr) => {{
|
||||||
let adjusted = $coordinate * 2;
|
let adjusted = $coordinate * 2;
|
||||||
let sizing = if adjusted $comparator 0 {
|
let sizing = if adjusted $comparator 0 {
|
||||||
Sizing::Decrease
|
Sizing::Decrease
|
||||||
} else {
|
} else {
|
||||||
Sizing::Increase
|
Sizing::Increase
|
||||||
};
|
};
|
||||||
|
|
||||||
($direction, sizing, adjusted.abs())
|
($direction, sizing, adjusted.abs())
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if resize.left != 0 {
|
if resize.left != 0 {
|
||||||
ops.push(resize_op!(resize.left, >, OperationDirection::Left));
|
ops.push(resize_op!(resize.left, >, OperationDirection::Left));
|
||||||
@@ -499,14 +496,11 @@ impl WindowManager {
|
|||||||
ops.push(resize_op!(resize.top, >, OperationDirection::Up));
|
ops.push(resize_op!(resize.top, >, OperationDirection::Up));
|
||||||
}
|
}
|
||||||
|
|
||||||
let top_left_constant = BORDER_WIDTH.load(Ordering::SeqCst)
|
if resize.right != 0 && resize.left == 0 {
|
||||||
+ BORDER_OFFSET.load(Ordering::SeqCst);
|
|
||||||
|
|
||||||
if resize.right != 0 && resize.left == top_left_constant {
|
|
||||||
ops.push(resize_op!(resize.right, <, OperationDirection::Right));
|
ops.push(resize_op!(resize.right, <, OperationDirection::Right));
|
||||||
}
|
}
|
||||||
|
|
||||||
if resize.bottom != 0 && resize.top == top_left_constant {
|
if resize.bottom != 0 && resize.top == 0 {
|
||||||
ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));
|
ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,21 +512,12 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowManagerEvent::ForceUpdate(_) => {
|
|
||||||
self.update_focused_workspace(false)?;
|
|
||||||
}
|
|
||||||
WindowManagerEvent::DisplayChange(..)
|
WindowManagerEvent::DisplayChange(..)
|
||||||
| WindowManagerEvent::MouseCapture(..)
|
| WindowManagerEvent::MouseCapture(..)
|
||||||
| WindowManagerEvent::Cloak(..)
|
| WindowManagerEvent::Cloak(..)
|
||||||
| WindowManagerEvent::Uncloak(..) => {}
|
| 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) {
|
if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) {
|
||||||
match event {
|
match event {
|
||||||
WindowManagerEvent::MoveResizeStart(_, _) => {
|
WindowManagerEvent::MoveResizeStart(_, _) => {
|
||||||
@@ -544,7 +529,6 @@ impl WindowManager {
|
|||||||
| WindowManagerEvent::Show(_, window)
|
| WindowManagerEvent::Show(_, window)
|
||||||
| WindowManagerEvent::FocusChange(_, window)
|
| WindowManagerEvent::FocusChange(_, window)
|
||||||
| WindowManagerEvent::Hide(_, window)
|
| WindowManagerEvent::Hide(_, window)
|
||||||
| WindowManagerEvent::Uncloak(_, window)
|
|
||||||
| WindowManagerEvent::Minimize(_, window) => {
|
| WindowManagerEvent::Minimize(_, window) => {
|
||||||
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
let mut target_window = None;
|
let mut target_window = None;
|
||||||
@@ -601,10 +585,15 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(target_window) = target_window {
|
if let Some(target_window) = target_window {
|
||||||
|
let window = target_window;
|
||||||
|
let mut rect = WindowsApi::window_rect(window.hwnd())?;
|
||||||
|
rect.top -= self.invisible_borders.bottom;
|
||||||
|
rect.bottom += self.invisible_borders.bottom;
|
||||||
|
|
||||||
let activate = BORDER_HIDDEN.load(Ordering::SeqCst);
|
let activate = BORDER_HIDDEN.load(Ordering::SeqCst);
|
||||||
|
|
||||||
WindowsApi::invalidate_border_rect()?;
|
WindowsApi::invalidate_border_rect()?;
|
||||||
border.set_position(target_window, activate)?;
|
border.set_position(target_window, &self.invisible_borders, activate)?;
|
||||||
|
|
||||||
if activate {
|
if activate {
|
||||||
BORDER_HIDDEN.store(false, Ordering::SeqCst);
|
BORDER_HIDDEN.store(false, Ordering::SeqCst);
|
||||||
@@ -617,7 +606,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
|
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
|
||||||
if let WindowManagerEvent::Unmanage(window) = event {
|
if let WindowManagerEvent::Unmanage(window) = event {
|
||||||
window.center(&self.focused_monitor_work_area()?)?;
|
window.center(&self.focused_monitor_work_area()?, &invisible_borders)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no more windows on the workspace, we shouldn't show the border window
|
// If there are no more windows on the workspace, we shouldn't show the border window
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||||
pub struct Ring<T> {
|
pub struct Ring<T> {
|
||||||
elements: VecDeque<T>,
|
elements: VecDeque<T>,
|
||||||
focused: usize,
|
focused: usize,
|
||||||
|
|||||||
@@ -1,294 +0,0 @@
|
|||||||
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,5 +1,4 @@
|
|||||||
use crate::border::Border;
|
use crate::border::Border;
|
||||||
use crate::colour::Colour;
|
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::monitor::Monitor;
|
use crate::monitor::Monitor;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
@@ -27,24 +26,15 @@ 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 color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use hotwatch::EventKind;
|
use hotwatch::notify::DebouncedEvent;
|
||||||
use hotwatch::Hotwatch;
|
use hotwatch::Hotwatch;
|
||||||
use komorebi_core::config_generation::ApplicationConfiguration;
|
|
||||||
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
|
||||||
use komorebi_core::config_generation::ApplicationOptions;
|
use komorebi_core::config_generation::ApplicationOptions;
|
||||||
use komorebi_core::config_generation::IdWithIdentifier;
|
use komorebi_core::config_generation::IdWithIdentifier;
|
||||||
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;
|
||||||
@@ -72,14 +62,34 @@ use std::sync::Arc;
|
|||||||
use uds_windows::UnixListener;
|
use uds_windows::UnixListener;
|
||||||
use uds_windows::UnixStream;
|
use uds_windows::UnixStream;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct Rgb {
|
||||||
|
/// Red
|
||||||
|
pub r: u32,
|
||||||
|
/// Green
|
||||||
|
pub g: u32,
|
||||||
|
/// Blue
|
||||||
|
pub b: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Rgb {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
r: value & 0xff,
|
||||||
|
g: value >> 8 & 0xff,
|
||||||
|
b: value >> 16 & 0xff,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct ActiveWindowBorderColours {
|
pub struct ActiveWindowBorderColours {
|
||||||
/// Border colour when the container contains a single window
|
/// Border colour when the container contains a single window
|
||||||
pub single: Colour,
|
pub single: Rgb,
|
||||||
/// Border colour when the container contains multiple windows
|
/// Border colour when the container contains multiple windows
|
||||||
pub stack: Colour,
|
pub stack: Rgb,
|
||||||
/// Border colour when the container is in monocle mode
|
/// Border colour when the container is in monocle mode
|
||||||
pub monocle: Colour,
|
pub monocle: Rgb,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
@@ -222,7 +232,7 @@ impl From<&Monitor> for MonitorConfig {
|
|||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
/// The `komorebi.json` static configuration file reference for `v0.1.20`
|
/// 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
|
/// Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub invisible_borders: Option<Rect>,
|
pub invisible_borders: Option<Rect>,
|
||||||
/// Delta to resize windows by (default 50)
|
/// Delta to resize windows by (default 50)
|
||||||
@@ -246,14 +256,12 @@ pub struct StaticConfig {
|
|||||||
/// Path to applications.yaml from komorebi-application-specific-configurations (default: None)
|
/// Path to applications.yaml from komorebi-application-specific-configurations (default: None)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub app_specific_configuration_path: Option<PathBuf>,
|
pub app_specific_configuration_path: Option<PathBuf>,
|
||||||
/// Width of the window border (default: 8)
|
/// Width of the active window border (default: 20)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(alias = "active_window_border_width")]
|
pub active_window_border_width: Option<i32>,
|
||||||
pub border_width: Option<i32>,
|
/// Offset of the active window border (default: None)
|
||||||
/// Offset of the window border (default: -1)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(alias = "active_window_border_offset")]
|
pub active_window_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")]
|
||||||
pub active_window_border: Option<bool>,
|
pub active_window_border: Option<bool>,
|
||||||
@@ -269,6 +277,10 @@ pub struct StaticConfig {
|
|||||||
/// Monitor and workspace configurations
|
/// Monitor and workspace configurations
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub monitors: Option<Vec<MonitorConfig>>,
|
pub monitors: Option<Vec<MonitorConfig>>,
|
||||||
|
/// DEPRECATED from v0.1.20: no longer required
|
||||||
|
#[schemars(skip)]
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub alt_focus_hack: Option<bool>,
|
||||||
/// Which Windows signal to use when hiding windows (default: minimize)
|
/// Which Windows signal to use when hiding windows (default: minimize)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub window_hiding_behaviour: Option<HidingBehaviour>,
|
pub window_hiding_behaviour: Option<HidingBehaviour>,
|
||||||
@@ -277,57 +289,40 @@ pub struct StaticConfig {
|
|||||||
pub global_work_area_offset: Option<Rect>,
|
pub global_work_area_offset: Option<Rect>,
|
||||||
/// Individual window floating rules
|
/// Individual window floating rules
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub float_rules: Option<Vec<MatchingRule>>,
|
pub float_rules: Option<Vec<IdWithIdentifier>>,
|
||||||
/// Individual window force-manage rules
|
/// Individual window force-manage rules
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub manage_rules: Option<Vec<MatchingRule>>,
|
pub manage_rules: Option<Vec<IdWithIdentifier>>,
|
||||||
/// Identify border overflow applications
|
/// Identify border overflow applications
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub border_overflow_applications: Option<Vec<MatchingRule>>,
|
pub border_overflow_applications: Option<Vec<IdWithIdentifier>>,
|
||||||
/// Identify tray and multi-window applications
|
/// Identify tray and multi-window applications
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub tray_and_multi_window_applications: Option<Vec<MatchingRule>>,
|
pub tray_and_multi_window_applications: Option<Vec<IdWithIdentifier>>,
|
||||||
/// Identify applications that have the WS_EX_LAYERED extended window style
|
/// Identify applications that have the WS_EX_LAYERED extended window style
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub layered_applications: Option<Vec<MatchingRule>>,
|
pub layered_applications: Option<Vec<IdWithIdentifier>>,
|
||||||
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
|
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub object_name_change_applications: Option<Vec<MatchingRule>>,
|
pub object_name_change_applications: Option<Vec<IdWithIdentifier>>,
|
||||||
/// Set monitor index preferences
|
/// Set monitor index preferences
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub monitor_index_preferences: Option<HashMap<usize, Rect>>,
|
pub monitor_index_preferences: Option<HashMap<usize, Rect>>,
|
||||||
/// 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>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub stackbar: Option<StackbarConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub enum StackbarMode {
|
|
||||||
Always,
|
|
||||||
Never,
|
|
||||||
OnStack,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct TabsConfig {
|
|
||||||
width: Option<i32>,
|
|
||||||
focused_text: Option<Colour>,
|
|
||||||
unfocused_text: Option<Colour>,
|
|
||||||
background: Option<Colour>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct StackbarConfig {
|
|
||||||
height: Option<i32>,
|
|
||||||
mode: Option<StackbarMode>,
|
|
||||||
tabs: Option<TabsConfig>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&WindowManager> for StaticConfig {
|
impl From<&WindowManager> for StaticConfig {
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn from(value: &WindowManager) -> Self {
|
fn from(value: &WindowManager) -> Self {
|
||||||
|
let default_invisible_borders = Rect {
|
||||||
|
left: 7,
|
||||||
|
top: 0,
|
||||||
|
right: 14,
|
||||||
|
bottom: 7,
|
||||||
|
};
|
||||||
|
|
||||||
let mut monitors = vec![];
|
let mut monitors = vec![];
|
||||||
for m in value.monitors() {
|
for m in value.monitors() {
|
||||||
monitors.push(MonitorConfig::from(m));
|
monitors.push(MonitorConfig::from(m));
|
||||||
@@ -382,13 +377,13 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Option::from(ActiveWindowBorderColours {
|
Option::from(ActiveWindowBorderColours {
|
||||||
single: Colour::from(BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)),
|
single: Rgb::from(BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)),
|
||||||
stack: Colour::from(if BORDER_COLOUR_STACK.load(Ordering::SeqCst) == 0 {
|
stack: Rgb::from(if BORDER_COLOUR_STACK.load(Ordering::SeqCst) == 0 {
|
||||||
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
|
||||||
} else {
|
} else {
|
||||||
BORDER_COLOUR_STACK.load(Ordering::SeqCst)
|
BORDER_COLOUR_STACK.load(Ordering::SeqCst)
|
||||||
}),
|
}),
|
||||||
monocle: Colour::from(if BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst) == 0 {
|
monocle: Rgb::from(if BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst) == 0 {
|
||||||
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
|
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)
|
||||||
} else {
|
} else {
|
||||||
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst)
|
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst)
|
||||||
@@ -397,7 +392,11 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
invisible_borders: None,
|
invisible_borders: if value.invisible_borders == default_invisible_borders {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Option::from(value.invisible_borders)
|
||||||
|
},
|
||||||
resize_delta: Option::from(value.resize_delta),
|
resize_delta: Option::from(value.resize_delta),
|
||||||
window_container_behaviour: Option::from(value.window_container_behaviour),
|
window_container_behaviour: Option::from(value.window_container_behaviour),
|
||||||
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
|
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
|
||||||
@@ -407,8 +406,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_WIDTH.load(Ordering::SeqCst)),
|
active_window_border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)),
|
||||||
border_offset: Option::from(BORDER_OFFSET.load(Ordering::SeqCst)),
|
active_window_border_offset: BORDER_OFFSET
|
||||||
|
.lock()
|
||||||
|
.map_or(None, |offset| Option::from(offset.left)),
|
||||||
active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)),
|
active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)),
|
||||||
active_window_border_colours: border_colours,
|
active_window_border_colours: border_colours,
|
||||||
default_workspace_padding: Option::from(
|
default_workspace_padding: Option::from(
|
||||||
@@ -418,6 +419,7 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
|
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
|
||||||
),
|
),
|
||||||
monitors: Option::from(monitors),
|
monitors: Option::from(monitors),
|
||||||
|
alt_focus_hack: None,
|
||||||
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
|
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
|
||||||
global_work_area_offset: value.work_area_offset,
|
global_work_area_offset: value.work_area_offset,
|
||||||
float_rules: None,
|
float_rules: None,
|
||||||
@@ -428,7 +430,6 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
object_name_change_applications: None,
|
object_name_change_applications: None,
|
||||||
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
|
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
|
||||||
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()),
|
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()),
|
||||||
stackbar: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,22 +460,49 @@ impl StaticConfig {
|
|||||||
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.border_width.map_or_else(
|
self.active_window_border_width.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
BORDER_WIDTH.store(8, Ordering::SeqCst);
|
BORDER_WIDTH.store(20, Ordering::SeqCst);
|
||||||
},
|
},
|
||||||
|width| {
|
|width| {
|
||||||
BORDER_WIDTH.store(width, Ordering::SeqCst);
|
BORDER_WIDTH.store(width, Ordering::SeqCst);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
self.active_window_border_offset.map_or_else(
|
||||||
|
|| {
|
||||||
|
let mut border_offset = BORDER_OFFSET.lock();
|
||||||
|
*border_offset = None;
|
||||||
|
},
|
||||||
|
|offset| {
|
||||||
|
let new_border_offset = Rect {
|
||||||
|
left: offset,
|
||||||
|
top: offset,
|
||||||
|
right: offset * 2,
|
||||||
|
bottom: offset * 2,
|
||||||
|
};
|
||||||
|
|
||||||
BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
let mut border_offset = BORDER_OFFSET.lock();
|
||||||
|
*border_offset = Some(new_border_offset);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(colours) = &self.active_window_border_colours {
|
if let Some(colours) = &self.active_window_border_colours {
|
||||||
BORDER_COLOUR_SINGLE.store(u32::from(colours.single), Ordering::SeqCst);
|
BORDER_COLOUR_SINGLE.store(
|
||||||
BORDER_COLOUR_CURRENT.store(u32::from(colours.single), Ordering::SeqCst);
|
colours.single.r | (colours.single.g << 8) | (colours.single.b << 16),
|
||||||
BORDER_COLOUR_STACK.store(u32::from(colours.stack), Ordering::SeqCst);
|
Ordering::SeqCst,
|
||||||
BORDER_COLOUR_MONOCLE.store(u32::from(colours.monocle), Ordering::SeqCst);
|
);
|
||||||
|
BORDER_COLOUR_CURRENT.store(
|
||||||
|
colours.single.r | (colours.single.g << 8) | (colours.single.b << 16),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
BORDER_COLOUR_STACK.store(
|
||||||
|
colours.stack.r | (colours.stack.g << 8) | (colours.stack.b << 16),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
|
BORDER_COLOUR_MONOCLE.store(
|
||||||
|
colours.monocle.r | (colours.monocle.g << 8) | (colours.monocle.b << 16),
|
||||||
|
Ordering::SeqCst,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||||
@@ -485,65 +513,104 @@ impl StaticConfig {
|
|||||||
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();
|
||||||
|
|
||||||
if let Some(rules) = &mut self.float_rules {
|
if let Some(float) = &mut self.float_rules {
|
||||||
populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?;
|
for identifier in float {
|
||||||
}
|
if identifier.matching_strategy.is_none() {
|
||||||
|
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(rules) = &mut self.manage_rules {
|
if !float_identifiers.contains(identifier) {
|
||||||
populate_rules(rules, &mut manage_identifiers, &mut regex_identifiers)?;
|
float_identifiers.push(identifier.clone());
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rules) = &mut self.object_name_change_applications {
|
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
|
||||||
populate_rules(
|
let re = Regex::new(&identifier.id)?;
|
||||||
rules,
|
regex_identifiers.insert(identifier.id.clone(), re);
|
||||||
&mut object_name_change_identifiers,
|
}
|
||||||
&mut regex_identifiers,
|
}
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rules) = &mut self.layered_applications {
|
|
||||||
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 {
|
|
||||||
populate_rules(
|
|
||||||
rules,
|
|
||||||
&mut tray_and_multi_window_identifiers,
|
|
||||||
&mut regex_identifiers,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stackbar) = &self.stackbar {
|
|
||||||
if let Some(height) = &stackbar.height {
|
|
||||||
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
|
|
||||||
}
|
}
|
||||||
if let Some(mode) = &stackbar.mode {
|
}
|
||||||
let mut stackbar_mode = STACKBAR_MODE.lock();
|
|
||||||
*stackbar_mode = *mode;
|
if let Some(manage) = &mut self.manage_rules {
|
||||||
|
for identifier in manage {
|
||||||
|
if identifier.matching_strategy.is_none() {
|
||||||
|
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !manage_identifiers.contains(identifier) {
|
||||||
|
manage_identifiers.push(identifier.clone());
|
||||||
|
|
||||||
|
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
|
||||||
|
let re = Regex::new(&identifier.id)?;
|
||||||
|
regex_identifiers.insert(identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(tabs) = &stackbar.tabs {
|
}
|
||||||
if let Some(background) = &tabs.background {
|
|
||||||
STACKBAR_TAB_BACKGROUND_COLOUR.store((*background).into(), Ordering::SeqCst);
|
if let Some(identifiers) = &mut self.object_name_change_applications {
|
||||||
|
for identifier in identifiers {
|
||||||
|
if identifier.matching_strategy.is_none() {
|
||||||
|
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(colour) = &tabs.focused_text {
|
if !object_name_change_identifiers.contains(identifier) {
|
||||||
STACKBAR_FOCUSED_TEXT_COLOUR.store((*colour).into(), Ordering::SeqCst);
|
object_name_change_identifiers.push(identifier.clone());
|
||||||
|
|
||||||
|
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
|
||||||
|
let re = Regex::new(&identifier.id)?;
|
||||||
|
regex_identifiers.insert(identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(identifiers) = &mut self.layered_applications {
|
||||||
|
for identifier in identifiers {
|
||||||
|
if identifier.matching_strategy.is_none() {
|
||||||
|
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(colour) = &tabs.unfocused_text {
|
if !layered_identifiers.contains(identifier) {
|
||||||
STACKBAR_UNFOCUSED_TEXT_COLOUR.store((*colour).into(), Ordering::SeqCst);
|
layered_identifiers.push(identifier.clone());
|
||||||
|
|
||||||
|
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
|
||||||
|
let re = Regex::new(&identifier.id)?;
|
||||||
|
regex_identifiers.insert(identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(identifiers) = &mut self.border_overflow_applications {
|
||||||
|
for identifier in identifiers {
|
||||||
|
if identifier.matching_strategy.is_none() {
|
||||||
|
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(width) = &tabs.width {
|
if !border_overflow_identifiers.contains(identifier) {
|
||||||
STACKBAR_TAB_WIDTH.store(*width, Ordering::SeqCst);
|
border_overflow_identifiers.push(identifier.clone());
|
||||||
|
|
||||||
|
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
|
||||||
|
let re = Regex::new(&identifier.id)?;
|
||||||
|
regex_identifiers.insert(identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(identifiers) = &mut self.tray_and_multi_window_applications {
|
||||||
|
for identifier in identifiers {
|
||||||
|
if identifier.matching_strategy.is_none() {
|
||||||
|
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tray_and_multi_window_identifiers.contains(identifier) {
|
||||||
|
tray_and_multi_window_identifiers.push(identifier.clone());
|
||||||
|
|
||||||
|
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
|
||||||
|
let re = Regex::new(&identifier.id)?;
|
||||||
|
regex_identifiers.insert(identifier.id.clone(), re);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,48 +621,120 @@ impl StaticConfig {
|
|||||||
let asc = ApplicationConfigurationGenerator::load(&content)?;
|
let asc = ApplicationConfigurationGenerator::load(&content)?;
|
||||||
|
|
||||||
for mut entry in asc {
|
for mut entry in asc {
|
||||||
if let Some(rules) = &mut entry.float_identifiers {
|
if let Some(float) = entry.float_identifiers {
|
||||||
populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?;
|
for f in float {
|
||||||
}
|
let mut without_comment: IdWithIdentifier = f.into();
|
||||||
|
if without_comment.matching_strategy.is_none() {
|
||||||
|
without_comment.matching_strategy =
|
||||||
|
Option::from(MatchingStrategy::Legacy);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref options) = entry.options {
|
if !float_identifiers.contains(&without_comment) {
|
||||||
let options = options.clone();
|
float_identifiers.push(without_comment.clone());
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
without_comment.matching_strategy,
|
||||||
|
Some(MatchingStrategy::Regex)
|
||||||
|
) {
|
||||||
|
let re = Regex::new(&without_comment.id)?;
|
||||||
|
regex_identifiers.insert(without_comment.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(options) = entry.options {
|
||||||
for o in options {
|
for o in options {
|
||||||
match o {
|
match o {
|
||||||
ApplicationOptions::ObjectNameChange => {
|
ApplicationOptions::ObjectNameChange => {
|
||||||
populate_option(
|
if entry.identifier.matching_strategy.is_none() {
|
||||||
&mut entry,
|
entry.identifier.matching_strategy =
|
||||||
&mut object_name_change_identifiers,
|
Option::from(MatchingStrategy::Legacy);
|
||||||
&mut regex_identifiers,
|
}
|
||||||
)?;
|
|
||||||
|
if !object_name_change_identifiers.contains(&entry.identifier) {
|
||||||
|
object_name_change_identifiers.push(entry.identifier.clone());
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
entry.identifier.matching_strategy,
|
||||||
|
Some(MatchingStrategy::Regex)
|
||||||
|
) {
|
||||||
|
let re = Regex::new(&entry.identifier.id)?;
|
||||||
|
regex_identifiers.insert(entry.identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationOptions::Layered => {
|
ApplicationOptions::Layered => {
|
||||||
populate_option(
|
if entry.identifier.matching_strategy.is_none() {
|
||||||
&mut entry,
|
entry.identifier.matching_strategy =
|
||||||
&mut layered_identifiers,
|
Option::from(MatchingStrategy::Legacy);
|
||||||
&mut regex_identifiers,
|
}
|
||||||
)?;
|
|
||||||
|
if !layered_identifiers.contains(&entry.identifier) {
|
||||||
|
layered_identifiers.push(entry.identifier.clone());
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
entry.identifier.matching_strategy,
|
||||||
|
Some(MatchingStrategy::Regex)
|
||||||
|
) {
|
||||||
|
let re = Regex::new(&entry.identifier.id)?;
|
||||||
|
regex_identifiers.insert(entry.identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationOptions::BorderOverflow => {
|
ApplicationOptions::BorderOverflow => {
|
||||||
populate_option(
|
if entry.identifier.matching_strategy.is_none() {
|
||||||
&mut entry,
|
entry.identifier.matching_strategy =
|
||||||
&mut border_overflow_identifiers,
|
Option::from(MatchingStrategy::Legacy);
|
||||||
&mut regex_identifiers,
|
}
|
||||||
)?;
|
|
||||||
|
if !border_overflow_identifiers.contains(&entry.identifier) {
|
||||||
|
border_overflow_identifiers.push(entry.identifier.clone());
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
entry.identifier.matching_strategy,
|
||||||
|
Some(MatchingStrategy::Regex)
|
||||||
|
) {
|
||||||
|
let re = Regex::new(&entry.identifier.id)?;
|
||||||
|
regex_identifiers.insert(entry.identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationOptions::TrayAndMultiWindow => {
|
ApplicationOptions::TrayAndMultiWindow => {
|
||||||
populate_option(
|
if entry.identifier.matching_strategy.is_none() {
|
||||||
&mut entry,
|
entry.identifier.matching_strategy =
|
||||||
&mut tray_and_multi_window_identifiers,
|
Option::from(MatchingStrategy::Legacy);
|
||||||
&mut regex_identifiers,
|
}
|
||||||
)?;
|
|
||||||
|
if !tray_and_multi_window_identifiers.contains(&entry.identifier) {
|
||||||
|
tray_and_multi_window_identifiers
|
||||||
|
.push(entry.identifier.clone());
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
entry.identifier.matching_strategy,
|
||||||
|
Some(MatchingStrategy::Regex)
|
||||||
|
) {
|
||||||
|
let re = Regex::new(&entry.identifier.id)?;
|
||||||
|
regex_identifiers.insert(entry.identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationOptions::Force => {
|
ApplicationOptions::Force => {
|
||||||
populate_option(
|
if entry.identifier.matching_strategy.is_none() {
|
||||||
&mut entry,
|
entry.identifier.matching_strategy =
|
||||||
&mut manage_identifiers,
|
Option::from(MatchingStrategy::Legacy);
|
||||||
&mut regex_identifiers,
|
}
|
||||||
)?;
|
|
||||||
|
if !manage_identifiers.contains(&entry.identifier) {
|
||||||
|
manage_identifiers.push(entry.identifier.clone());
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
entry.identifier.matching_strategy,
|
||||||
|
Some(MatchingStrategy::Regex)
|
||||||
|
) {
|
||||||
|
let re = Regex::new(&entry.identifier.id)?;
|
||||||
|
regex_identifiers.insert(entry.identifier.id.clone(), re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -609,7 +748,7 @@ impl StaticConfig {
|
|||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn preload(
|
pub fn preload(
|
||||||
path: &PathBuf,
|
path: &PathBuf,
|
||||||
incoming: Receiver<WindowManagerEvent>,
|
incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||||
) -> Result<WindowManager> {
|
) -> Result<WindowManager> {
|
||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
let mut value: Self = serde_json::from_str(&content)?;
|
let mut value: Self = serde_json::from_str(&content)?;
|
||||||
@@ -636,6 +775,12 @@ impl StaticConfig {
|
|||||||
incoming_events: incoming,
|
incoming_events: incoming,
|
||||||
command_listener: listener,
|
command_listener: listener,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
|
invisible_borders: value.invisible_borders.unwrap_or(Rect {
|
||||||
|
left: 7,
|
||||||
|
top: 0,
|
||||||
|
right: 14,
|
||||||
|
bottom: 7,
|
||||||
|
}),
|
||||||
virtual_desktop_id: current_virtual_desktop(),
|
virtual_desktop_id: current_virtual_desktop(),
|
||||||
work_area_offset: value.global_work_area_offset,
|
work_area_offset: value.global_work_area_offset,
|
||||||
window_container_behaviour: value
|
window_container_behaviour: value
|
||||||
@@ -666,10 +811,10 @@ impl StaticConfig {
|
|||||||
|
|
||||||
let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;
|
let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;
|
||||||
|
|
||||||
wm.hotwatch.watch(path, move |event| match event.kind {
|
wm.hotwatch.watch(path, move |event| match event {
|
||||||
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
||||||
// a NoticeRemove, presumably because of the use of swap files?
|
// a NoticeRemove, presumably because of the use of swap files?
|
||||||
EventKind::Modify(_) | EventKind::Remove(_) => {
|
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
|
||||||
let socket = DATA_DIR.join("komorebi.sock");
|
let socket = DATA_DIR.join("komorebi.sock");
|
||||||
let mut stream =
|
let mut stream =
|
||||||
UnixStream::connect(socket).expect("could not connect to komorebi.sock");
|
UnixStream::connect(socket).expect("could not connect to komorebi.sock");
|
||||||
@@ -782,6 +927,10 @@ impl StaticConfig {
|
|||||||
wm.hide_border()?;
|
wm.hide_border()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(val) = value.invisible_borders {
|
||||||
|
wm.invisible_borders = val;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(val) = value.window_container_behaviour {
|
if let Some(val) = value.window_container_behaviour {
|
||||||
wm.window_container_behaviour = val;
|
wm.window_container_behaviour = val;
|
||||||
}
|
}
|
||||||
@@ -814,76 +963,6 @@ impl StaticConfig {
|
|||||||
|
|
||||||
wm.focus_follows_mouse = value.focus_follows_mouse;
|
wm.focus_follows_mouse = value.focus_follows_mouse;
|
||||||
|
|
||||||
let monitor_count = wm.monitors().len();
|
|
||||||
|
|
||||||
for i in 0..monitor_count {
|
|
||||||
wm.update_focused_workspace_by_monitor_idx(i)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_option(
|
|
||||||
entry: &mut ApplicationConfiguration,
|
|
||||||
identifiers: &mut Vec<MatchingRule>,
|
|
||||||
regex_identifiers: &mut HashMap<String, Regex>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if entry.identifier.matching_strategy.is_none() {
|
|
||||||
entry.identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rule = MatchingRule::Simple(entry.identifier.clone());
|
|
||||||
|
|
||||||
if !identifiers.contains(&rule) {
|
|
||||||
identifiers.push(rule);
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
entry.identifier.matching_strategy,
|
|
||||||
Some(MatchingStrategy::Regex)
|
|
||||||
) {
|
|
||||||
let re = Regex::new(&entry.identifier.id)?;
|
|
||||||
regex_identifiers.insert(entry.identifier.id.clone(), re);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_rules(
|
|
||||||
matching_rules: &mut Vec<MatchingRule>,
|
|
||||||
identifiers: &mut Vec<MatchingRule>,
|
|
||||||
regex_identifiers: &mut HashMap<String, Regex>,
|
|
||||||
) -> Result<()> {
|
|
||||||
for matching_rule in matching_rules {
|
|
||||||
if !identifiers.contains(matching_rule) {
|
|
||||||
match matching_rule {
|
|
||||||
MatchingRule::Simple(simple) => {
|
|
||||||
if simple.matching_strategy.is_none() {
|
|
||||||
simple.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(simple.matching_strategy, Some(MatchingStrategy::Regex)) {
|
|
||||||
let re = Regex::new(&simple.id)?;
|
|
||||||
regex_identifiers.insert(simple.id.clone(), re);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MatchingRule::Composite(composite) => {
|
|
||||||
for rule in composite {
|
|
||||||
if rule.matching_strategy.is_none() {
|
|
||||||
rule.matching_strategy = Option::from(MatchingStrategy::Legacy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(rule.matching_strategy, Some(MatchingStrategy::Regex)) {
|
|
||||||
let re = Regex::new(&rule.id)?;
|
|
||||||
regex_identifiers.insert(rule.id.clone(), re);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
identifiers.push(matching_rule.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,22 +4,23 @@ use std::convert::TryFrom;
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::time::Duration;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use color_eyre::eyre::anyhow;
|
use color_eyre::eyre::anyhow;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use komorebi_core::config_generation::IdWithIdentifier;
|
use komorebi_core::config_generation::IdWithIdentifier;
|
||||||
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::Error;
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::Serializer;
|
use serde::Serializer;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use winput::press;
|
||||||
|
use winput::release;
|
||||||
|
use winput::Vk;
|
||||||
|
|
||||||
use komorebi_core::ApplicationIdentifier;
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi_core::HidingBehaviour;
|
use komorebi_core::HidingBehaviour;
|
||||||
@@ -29,6 +30,8 @@ use crate::styles::ExtendedWindowStyle;
|
|||||||
use crate::styles::WindowStyle;
|
use crate::styles::WindowStyle;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
|
use crate::ALT_FOCUS_HACK;
|
||||||
|
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||||
use crate::FLOAT_IDENTIFIERS;
|
use crate::FLOAT_IDENTIFIERS;
|
||||||
use crate::HIDDEN_HWNDS;
|
use crate::HIDDEN_HWNDS;
|
||||||
use crate::HIDING_BEHAVIOUR;
|
use crate::HIDING_BEHAVIOUR;
|
||||||
@@ -39,7 +42,7 @@ use crate::PERMAIGNORE_CLASSES;
|
|||||||
use crate::REGEX_IDENTIFIERS;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
use crate::WSL2_UI_PROCESSES;
|
use crate::WSL2_UI_PROCESSES;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Copy, JsonSchema)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub(crate) hwnd: isize,
|
pub(crate) hwnd: isize,
|
||||||
}
|
}
|
||||||
@@ -125,7 +128,7 @@ impl Window {
|
|||||||
HWND(self.hwnd)
|
HWND(self.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
|
pub fn center(&mut self, work_area: &Rect, invisible_borders: &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;
|
||||||
|
|
||||||
@@ -136,23 +139,45 @@ impl Window {
|
|||||||
right: half_width,
|
right: half_width,
|
||||||
bottom: half_weight,
|
bottom: half_weight,
|
||||||
},
|
},
|
||||||
|
invisible_borders,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> {
|
pub fn set_position(
|
||||||
let rect = *layout;
|
&mut self,
|
||||||
|
layout: &Rect,
|
||||||
|
invisible_borders: &Rect,
|
||||||
|
top: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut rect = *layout;
|
||||||
|
|
||||||
|
let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
|
||||||
|
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
||||||
|
|
||||||
|
let title = &self.title()?;
|
||||||
|
let class = &self.class()?;
|
||||||
|
let exe_name = &self.exe()?;
|
||||||
|
|
||||||
|
let should_remove_border = !should_act(
|
||||||
|
title,
|
||||||
|
exe_name,
|
||||||
|
class,
|
||||||
|
&border_overflows,
|
||||||
|
®ex_identifiers,
|
||||||
|
);
|
||||||
|
|
||||||
|
if should_remove_border {
|
||||||
|
// Remove the invisible borders
|
||||||
|
rect.left -= invisible_borders.left;
|
||||||
|
rect.top -= invisible_borders.top;
|
||||||
|
rect.right += invisible_borders.right;
|
||||||
|
rect.bottom += invisible_borders.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
WindowsApi::position_window(self.hwnd(), &rect, top)
|
WindowsApi::position_window(self.hwnd(), &rect, top)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_maximized(self) -> bool {
|
|
||||||
WindowsApi::is_zoomed(self.hwnd())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_miminized(self) -> bool {
|
|
||||||
WindowsApi::is_iconic(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) {
|
||||||
@@ -217,6 +242,57 @@ impl Window {
|
|||||||
WindowsApi::unmaximize_window(self.hwnd());
|
WindowsApi::unmaximize_window(self.hwnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raise(self) {
|
||||||
|
// Attach komorebi thread to Window thread
|
||||||
|
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||||
|
let current_thread_id = WindowsApi::current_thread_id();
|
||||||
|
|
||||||
|
// 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 raise(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Raise Window to foreground
|
||||||
|
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(
|
||||||
|
"could not set as foreground window, but continuing execution of raise(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 raise(): {}",
|
||||||
|
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 raise(): {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
|
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
|
||||||
// Attach komorebi thread to Window thread
|
// Attach komorebi thread to Window thread
|
||||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||||
@@ -240,7 +316,12 @@ impl Window {
|
|||||||
let mut tried_resetting_foreground_access = false;
|
let mut tried_resetting_foreground_access = false;
|
||||||
let mut max_attempts = 10;
|
let mut max_attempts = 10;
|
||||||
|
|
||||||
|
let hotkey_uses_alt = WindowsApi::alt_is_pressed();
|
||||||
while !foregrounded && max_attempts > 0 {
|
while !foregrounded && max_attempts > 0 {
|
||||||
|
if ALT_FOCUS_HACK.load(Ordering::SeqCst) {
|
||||||
|
press(Vk::Alt);
|
||||||
|
}
|
||||||
|
|
||||||
match WindowsApi::set_foreground_window(self.hwnd()) {
|
match WindowsApi::set_foreground_window(self.hwnd()) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
foregrounded = true;
|
foregrounded = true;
|
||||||
@@ -261,6 +342,10 @@ impl Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ALT_FOCUS_HACK.load(Ordering::SeqCst) && !hotkey_uses_alt {
|
||||||
|
release(Vk::Alt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center cursor in Window
|
// Center cursor in Window
|
||||||
@@ -328,14 +413,6 @@ impl Window {
|
|||||||
WindowsApi::window_text_w(self.hwnd())
|
WindowsApi::window_text_w(self.hwnd())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(self) -> Result<String> {
|
|
||||||
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
|
|
||||||
let handle = WindowsApi::process_handle(process_id)?;
|
|
||||||
let path = WindowsApi::exe_path(handle);
|
|
||||||
WindowsApi::close_process(handle)?;
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exe(self) -> Result<String> {
|
pub fn exe(self) -> Result<String> {
|
||||||
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
|
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||||
let handle = WindowsApi::process_handle(process_id)?;
|
let handle = WindowsApi::process_handle(process_id)?;
|
||||||
@@ -399,8 +476,8 @@ impl Window {
|
|||||||
(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)) = (self.title(), self.exe(), self.class()) {
|
||||||
return Ok(window_is_eligible(&title, &exe_name, &class, &path, &self.style()?, &self.ex_style()?, event));
|
return Ok(window_is_eligible(&title, &exe_name, &class, &self.style()?, &self.ex_style()?, event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -414,7 +491,6 @@ fn window_is_eligible(
|
|||||||
title: &String,
|
title: &String,
|
||||||
exe_name: &String,
|
exe_name: &String,
|
||||||
class: &String,
|
class: &String,
|
||||||
path: &str,
|
|
||||||
style: &WindowStyle,
|
style: &WindowStyle,
|
||||||
ex_style: &ExtendedWindowStyle,
|
ex_style: &ExtendedWindowStyle,
|
||||||
event: Option<WindowManagerEvent>,
|
event: Option<WindowManagerEvent>,
|
||||||
@@ -433,7 +509,6 @@ fn window_is_eligible(
|
|||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
|
||||||
&float_identifiers,
|
&float_identifiers,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
);
|
);
|
||||||
@@ -443,7 +518,6 @@ fn window_is_eligible(
|
|||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
|
||||||
&manage_identifiers,
|
&manage_identifiers,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
);
|
);
|
||||||
@@ -457,7 +531,6 @@ fn window_is_eligible(
|
|||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
|
||||||
&layered_whitelist,
|
&layered_whitelist,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
);
|
);
|
||||||
@@ -475,10 +548,6 @@ fn window_is_eligible(
|
|||||||
titlebars_removed.contains(exe_name)
|
titlebars_removed.contains(exe_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
if exe_name.contains("firefox") {
|
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
|
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
|
||||||
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
|
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
|
||||||
// Get a lot of dupe events coming through that make the redrawing go crazy
|
// Get a lot of dupe events coming through that make the redrawing go crazy
|
||||||
@@ -489,13 +558,8 @@ fn window_is_eligible(
|
|||||||
|| managed_override
|
|| managed_override
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
} else if let Some(event) = event {
|
} else if event.is_some() {
|
||||||
tracing::debug!(
|
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
|
||||||
"ignoring (exe: {}, title: {}, event: {})",
|
|
||||||
exe_name,
|
|
||||||
title,
|
|
||||||
event
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
@@ -506,291 +570,125 @@ pub fn should_act(
|
|||||||
title: &str,
|
title: &str,
|
||||||
exe_name: &str,
|
exe_name: &str,
|
||||||
class: &str,
|
class: &str,
|
||||||
path: &str,
|
identifiers: &[IdWithIdentifier],
|
||||||
identifiers: &[MatchingRule],
|
|
||||||
regex_identifiers: &HashMap<String, Regex>,
|
regex_identifiers: &HashMap<String, Regex>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut should_act = false;
|
let mut should_act = false;
|
||||||
for identifier in identifiers {
|
for identifier in identifiers {
|
||||||
match identifier {
|
match identifier.matching_strategy {
|
||||||
MatchingRule::Simple(identifier) => {
|
None => {
|
||||||
if should_act_individual(
|
panic!("there is no matching strategy identified for this rule");
|
||||||
title,
|
|
||||||
exe_name,
|
|
||||||
class,
|
|
||||||
path,
|
|
||||||
identifier,
|
|
||||||
regex_identifiers,
|
|
||||||
) {
|
|
||||||
should_act = true
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
MatchingRule::Composite(identifiers) => {
|
Some(MatchingStrategy::Legacy) => match identifier.kind {
|
||||||
let mut composite_results = vec![];
|
ApplicationIdentifier::Title => {
|
||||||
for identifier in identifiers {
|
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
|
||||||
composite_results.push(should_act_individual(
|
should_act = true;
|
||||||
title,
|
}
|
||||||
exe_name,
|
|
||||||
class,
|
|
||||||
path,
|
|
||||||
identifier,
|
|
||||||
regex_identifiers,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
ApplicationIdentifier::Class => {
|
||||||
if composite_results.iter().all(|&x| x) {
|
if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {
|
||||||
should_act = true;
|
should_act = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
ApplicationIdentifier::Exe => {
|
||||||
|
if exe_name.eq(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(MatchingStrategy::Equals) => match identifier.kind {
|
||||||
|
ApplicationIdentifier::Title => {
|
||||||
|
if title.eq(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Class => {
|
||||||
|
if class.eq(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Exe => {
|
||||||
|
if exe_name.eq(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(MatchingStrategy::StartsWith) => match identifier.kind {
|
||||||
|
ApplicationIdentifier::Title => {
|
||||||
|
if title.starts_with(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Class => {
|
||||||
|
if class.starts_with(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Exe => {
|
||||||
|
if exe_name.starts_with(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(MatchingStrategy::EndsWith) => match identifier.kind {
|
||||||
|
ApplicationIdentifier::Title => {
|
||||||
|
if title.ends_with(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Class => {
|
||||||
|
if class.ends_with(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Exe => {
|
||||||
|
if exe_name.ends_with(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(MatchingStrategy::Contains) => match identifier.kind {
|
||||||
|
ApplicationIdentifier::Title => {
|
||||||
|
if title.contains(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Class => {
|
||||||
|
if class.contains(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Exe => {
|
||||||
|
if exe_name.contains(&identifier.id) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(MatchingStrategy::Regex) => match identifier.kind {
|
||||||
|
ApplicationIdentifier::Title => {
|
||||||
|
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
||||||
|
if re.is_match(title) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Class => {
|
||||||
|
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
||||||
|
if re.is_match(class) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplicationIdentifier::Exe => {
|
||||||
|
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
||||||
|
if re.is_match(exe_name) {
|
||||||
|
should_act = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
should_act
|
should_act
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_act_individual(
|
|
||||||
title: &str,
|
|
||||||
exe_name: &str,
|
|
||||||
class: &str,
|
|
||||||
path: &str,
|
|
||||||
identifier: &IdWithIdentifier,
|
|
||||||
regex_identifiers: &HashMap<String, Regex>,
|
|
||||||
) -> bool {
|
|
||||||
let mut should_act = false;
|
|
||||||
|
|
||||||
match identifier.matching_strategy {
|
|
||||||
None => {
|
|
||||||
panic!("there is no matching strategy identified for this rule");
|
|
||||||
}
|
|
||||||
Some(MatchingStrategy::Legacy) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if exe_name.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if path.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::Equals) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if title.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if class.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if exe_name.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if path.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::DoesNotEqual) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if !title.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if !class.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if !exe_name.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if !path.eq(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::StartsWith) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if title.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if class.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if exe_name.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if path.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::DoesNotStartWith) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if !title.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if !class.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if !exe_name.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if !path.starts_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::EndsWith) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if title.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if class.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if exe_name.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if path.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::DoesNotEndWith) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if !title.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if !class.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if !exe_name.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if !path.ends_with(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::Contains) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if title.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if class.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if exe_name.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if path.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::DoesNotContain) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if !title.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if !class.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if !exe_name.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if !path.contains(&identifier.id) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MatchingStrategy::Regex) => match identifier.kind {
|
|
||||||
ApplicationIdentifier::Title => {
|
|
||||||
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
|
||||||
if re.is_match(title) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Class => {
|
|
||||||
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
|
||||||
if re.is_match(class) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Exe => {
|
|
||||||
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
|
||||||
if re.is_match(exe_name) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationIdentifier::Path => {
|
|
||||||
if let Some(re) = regex_identifiers.get(&identifier.id) {
|
|
||||||
if re.is_match(path) {
|
|
||||||
should_act = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
should_act
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,16 +12,14 @@ use color_eyre::eyre::anyhow;
|
|||||||
use color_eyre::eyre::bail;
|
use color_eyre::eyre::bail;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use hotwatch::notify::ErrorKind as NotifyErrorKind;
|
use hotwatch::notify::DebouncedEvent;
|
||||||
use hotwatch::EventKind;
|
|
||||||
use hotwatch::Hotwatch;
|
use hotwatch::Hotwatch;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uds_windows::UnixListener;
|
use uds_windows::UnixListener;
|
||||||
|
|
||||||
use komorebi_core::config_generation::MatchingRule;
|
use komorebi_core::config_generation::IdWithIdentifier;
|
||||||
use komorebi_core::custom_layout::CustomLayout;
|
use komorebi_core::custom_layout::CustomLayout;
|
||||||
use komorebi_core::Arrangement;
|
use komorebi_core::Arrangement;
|
||||||
use komorebi_core::Axis;
|
use komorebi_core::Axis;
|
||||||
@@ -46,7 +44,7 @@ use crate::static_config::StaticConfig;
|
|||||||
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_listener;
|
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
use crate::BORDER_HWND;
|
use crate::BORDER_HWND;
|
||||||
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
||||||
@@ -67,9 +65,10 @@ use crate::WORKSPACE_RULES;
|
|||||||
pub struct WindowManager {
|
pub struct WindowManager {
|
||||||
pub monitors: Ring<Monitor>,
|
pub monitors: Ring<Monitor>,
|
||||||
pub monitor_cache: HashMap<usize, Monitor>,
|
pub monitor_cache: HashMap<usize, Monitor>,
|
||||||
pub incoming_events: Receiver<WindowManagerEvent>,
|
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||||
pub command_listener: UnixListener,
|
pub command_listener: UnixListener,
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
|
pub invisible_borders: Rect,
|
||||||
pub work_area_offset: Option<Rect>,
|
pub work_area_offset: Option<Rect>,
|
||||||
pub resize_delta: i32,
|
pub resize_delta: i32,
|
||||||
pub window_container_behaviour: WindowContainerBehaviour,
|
pub window_container_behaviour: WindowContainerBehaviour,
|
||||||
@@ -85,10 +84,11 @@ pub struct WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, JsonSchema)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub monitors: Ring<Monitor>,
|
pub monitors: Ring<Monitor>,
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
|
pub invisible_borders: Rect,
|
||||||
pub resize_delta: i32,
|
pub resize_delta: i32,
|
||||||
pub new_window_behaviour: WindowContainerBehaviour,
|
pub new_window_behaviour: WindowContainerBehaviour,
|
||||||
pub cross_monitor_move_behaviour: MoveBehaviour,
|
pub cross_monitor_move_behaviour: MoveBehaviour,
|
||||||
@@ -97,12 +97,12 @@ pub struct State {
|
|||||||
pub mouse_follows_focus: bool,
|
pub mouse_follows_focus: bool,
|
||||||
pub has_pending_raise_op: bool,
|
pub has_pending_raise_op: bool,
|
||||||
pub remove_titlebars: bool,
|
pub remove_titlebars: bool,
|
||||||
pub float_identifiers: Vec<MatchingRule>,
|
pub float_identifiers: Vec<IdWithIdentifier>,
|
||||||
pub manage_identifiers: Vec<MatchingRule>,
|
pub manage_identifiers: Vec<IdWithIdentifier>,
|
||||||
pub layered_whitelist: Vec<MatchingRule>,
|
pub layered_whitelist: Vec<IdWithIdentifier>,
|
||||||
pub tray_and_multi_window_identifiers: Vec<MatchingRule>,
|
pub tray_and_multi_window_identifiers: Vec<IdWithIdentifier>,
|
||||||
pub border_overflow_identifiers: Vec<MatchingRule>,
|
pub border_overflow_identifiers: Vec<IdWithIdentifier>,
|
||||||
pub name_change_on_launch_identifiers: Vec<MatchingRule>,
|
pub name_change_on_launch_identifiers: Vec<IdWithIdentifier>,
|
||||||
pub monitor_index_preferences: HashMap<usize, Rect>,
|
pub monitor_index_preferences: HashMap<usize, Rect>,
|
||||||
pub display_index_preferences: HashMap<usize, String>,
|
pub display_index_preferences: HashMap<usize, String>,
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,7 @@ impl From<&WindowManager> for State {
|
|||||||
Self {
|
Self {
|
||||||
monitors: wm.monitors.clone(),
|
monitors: wm.monitors.clone(),
|
||||||
is_paused: wm.is_paused,
|
is_paused: wm.is_paused,
|
||||||
|
invisible_borders: wm.invisible_borders,
|
||||||
work_area_offset: wm.work_area_offset,
|
work_area_offset: wm.work_area_offset,
|
||||||
resize_delta: wm.resize_delta,
|
resize_delta: wm.resize_delta,
|
||||||
new_window_behaviour: wm.window_container_behaviour,
|
new_window_behaviour: wm.window_container_behaviour,
|
||||||
@@ -166,7 +167,7 @@ impl EnforceWorkspaceRuleOp {
|
|||||||
|
|
||||||
impl WindowManager {
|
impl WindowManager {
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn new(incoming: Receiver<WindowManagerEvent>) -> Result<Self> {
|
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
|
||||||
let socket = DATA_DIR.join("komorebi.sock");
|
let socket = DATA_DIR.join("komorebi.sock");
|
||||||
|
|
||||||
match std::fs::remove_file(&socket) {
|
match std::fs::remove_file(&socket) {
|
||||||
@@ -188,6 +189,12 @@ impl WindowManager {
|
|||||||
incoming_events: incoming,
|
incoming_events: incoming,
|
||||||
command_listener: listener,
|
command_listener: listener,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
|
invisible_borders: Rect {
|
||||||
|
left: 7,
|
||||||
|
top: 0,
|
||||||
|
right: 14,
|
||||||
|
bottom: 7,
|
||||||
|
},
|
||||||
virtual_desktop_id: current_virtual_desktop(),
|
virtual_desktop_id: current_virtual_desktop(),
|
||||||
work_area_offset: None,
|
work_area_offset: None,
|
||||||
window_container_behaviour: WindowContainerBehaviour::Create,
|
window_container_behaviour: WindowContainerBehaviour::Create,
|
||||||
@@ -212,16 +219,15 @@ impl WindowManager {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn show_border(&self) -> Result<()> {
|
pub fn show_border(&self) -> Result<()> {
|
||||||
if self.focused_container().is_ok() {
|
let foreground = WindowsApi::foreground_window()?;
|
||||||
let foreground = WindowsApi::foreground_window()?;
|
let foreground_window = Window { hwnd: foreground };
|
||||||
let foreground_window = Window { hwnd: foreground };
|
let mut rect = WindowsApi::window_rect(foreground_window.hwnd())?;
|
||||||
|
rect.top -= self.invisible_borders.bottom;
|
||||||
|
rect.bottom += self.invisible_borders.bottom;
|
||||||
|
|
||||||
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
|
||||||
border.set_position(foreground_window, true)?;
|
border.set_position(foreground_window, &self.invisible_borders, true)?;
|
||||||
WindowsApi::invalidate_border_rect()?;
|
WindowsApi::invalidate_border_rect()
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -266,18 +272,18 @@ impl WindowManager {
|
|||||||
match self.hotwatch.unwatch(&config) {
|
match self.hotwatch.unwatch(&config) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
hotwatch::Error::Notify(ref notify_error) => match notify_error.kind {
|
hotwatch::Error::Notify(error) => match error {
|
||||||
NotifyErrorKind::WatchNotFound => {}
|
hotwatch::notify::Error::WatchNotFound => {}
|
||||||
_ => return Err(error.into()),
|
error => return Err(error.into()),
|
||||||
},
|
},
|
||||||
error @ hotwatch::Error::Io(_) => return Err(error.into()),
|
error @ hotwatch::Error::Io(_) => return Err(error.into()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
self.hotwatch.watch(config, |event| match event.kind {
|
self.hotwatch.watch(config, |event| match event {
|
||||||
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
||||||
// a NoticeRemove, presumably because of the use of swap files?
|
// a NoticeRemove, presumably because of the use of swap files?
|
||||||
EventKind::Modify(_) | EventKind::Remove(_) => {
|
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| {
|
||||||
load_configuration().expect("could not load configuration");
|
load_configuration().expect("could not load configuration");
|
||||||
});
|
});
|
||||||
@@ -396,6 +402,7 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
|
||||||
for monitor in self.monitors_mut() {
|
for monitor in self.monitors_mut() {
|
||||||
@@ -426,7 +433,7 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if should_update {
|
if should_update {
|
||||||
monitor.update_focused_workspace(offset)?;
|
monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,6 +635,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn retile_all(&mut self, preserve_resize_dimensions: bool) -> Result<()> {
|
pub fn retile_all(&mut self, preserve_resize_dimensions: bool) -> Result<()> {
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
|
||||||
for monitor in self.monitors_mut() {
|
for monitor in self.monitors_mut() {
|
||||||
@@ -649,7 +657,7 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -659,56 +667,75 @@ impl WindowManager {
|
|||||||
pub fn manage_focused_window(&mut self) -> Result<()> {
|
pub fn manage_focused_window(&mut self) -> Result<()> {
|
||||||
let hwnd = WindowsApi::foreground_window()?;
|
let hwnd = WindowsApi::foreground_window()?;
|
||||||
let event = WindowManagerEvent::Manage(Window { hwnd });
|
let event = WindowManagerEvent::Manage(Window { hwnd });
|
||||||
Ok(winevent_listener::event_tx().send(event)?)
|
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn unmanage_focused_window(&mut self) -> Result<()> {
|
pub fn unmanage_focused_window(&mut self) -> Result<()> {
|
||||||
let hwnd = WindowsApi::foreground_window()?;
|
let hwnd = WindowsApi::foreground_window()?;
|
||||||
let event = WindowManagerEvent::Unmanage(Window { hwnd });
|
let event = WindowManagerEvent::Unmanage(Window { hwnd });
|
||||||
Ok(winevent_listener::event_tx().send(event)?)
|
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn raise_window_at_cursor_pos(&mut self) -> Result<()> {
|
pub fn raise_window_at_cursor_pos(&mut self) -> Result<()> {
|
||||||
let mut hwnd = None;
|
let mut hwnd = WindowsApi::window_at_cursor_pos()?;
|
||||||
|
|
||||||
for monitor in self.monitors() {
|
if self.has_pending_raise_op
|
||||||
for workspace in monitor.workspaces() {
|
|| self.focused_window()?.hwnd == hwnd
|
||||||
if let Some(container_idx) = workspace.container_idx_from_current_point() {
|
// Sometimes we need this check, because the focus may have been given by a click
|
||||||
if let Some(container) = workspace.containers().get(container_idx) {
|
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
|
||||||
if let Some(window) = container.focused_window() {
|
// the focused window of the workspace is not actually focused by the OS at that point
|
||||||
hwnd = Some(window.hwnd);
|
|| WindowsApi::foreground_window()? == hwnd
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let mut known_hwnd = false;
|
||||||
|
for monitor in self.monitors() {
|
||||||
|
for workspace in monitor.workspaces() {
|
||||||
|
if workspace.contains_window(hwnd) {
|
||||||
|
known_hwnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Not sure if this needs to be made configurable just yet...
|
||||||
|
let overlay_classes = [
|
||||||
|
// Chromium/Electron
|
||||||
|
"Chrome_RenderWidgetHostHWND".to_string(),
|
||||||
|
// Explorer
|
||||||
|
"DirectUIHWND".to_string(),
|
||||||
|
"SysTreeView32".to_string(),
|
||||||
|
"ToolbarWindow32".to_string(),
|
||||||
|
"NetUIHWND".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if !known_hwnd {
|
||||||
|
let class = Window { hwnd }.class()?;
|
||||||
|
// Some applications (Electron/Chromium-based, explorer) have (invisible?) overlays
|
||||||
|
// windows that we need to look beyond to find the actual window to raise
|
||||||
|
if overlay_classes.contains(&class) {
|
||||||
|
for monitor in self.monitors() {
|
||||||
|
for workspace in monitor.workspaces() {
|
||||||
|
if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?)
|
||||||
|
{
|
||||||
|
hwnd = exe_hwnd;
|
||||||
|
known_hwnd = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(hwnd) = hwnd {
|
if known_hwnd {
|
||||||
if self.has_pending_raise_op
|
let event = WindowManagerEvent::Raise(Window { hwnd });
|
||||||
|| self.focused_window()?.hwnd == hwnd
|
self.has_pending_raise_op = true;
|
||||||
// Sometimes we need this check, because the focus may have been given by a click
|
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
||||||
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
|
} else {
|
||||||
// the focused window of the workspace is not actually focused by the OS at that point
|
tracing::debug!("not raising unknown window: {}", Window { hwnd });
|
||||||
|| WindowsApi::foreground_window()? == hwnd
|
Ok(())
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let event = WindowManagerEvent::Raise(Window { hwnd });
|
|
||||||
self.has_pending_raise_op = true;
|
|
||||||
winevent_listener::event_tx().send(event)?;
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
"not raising unknown window: {}",
|
|
||||||
Window {
|
|
||||||
hwnd: WindowsApi::window_at_cursor_pos()?
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -802,11 +829,12 @@ impl WindowManager {
|
|||||||
pub fn update_focused_workspace(&mut self, follow_focus: bool) -> Result<()> {
|
pub fn update_focused_workspace(&mut self, follow_focus: bool) -> Result<()> {
|
||||||
tracing::info!("updating");
|
tracing::info!("updating");
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
|
||||||
self.focused_monitor_mut()
|
self.focused_monitor_mut()
|
||||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||||
.update_focused_workspace(offset)?;
|
.update_focused_workspace(offset, &invisible_borders)?;
|
||||||
|
|
||||||
if follow_focus {
|
if follow_focus {
|
||||||
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
||||||
@@ -837,30 +865,6 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we passed false for follow_focus and there is a container on the workspace
|
|
||||||
if !follow_focus && self.focused_container_mut().is_ok() {
|
|
||||||
// and we have a stack with >1 windows
|
|
||||||
if self.focused_container_mut()?.windows().len() > 1
|
|
||||||
// and we don't have a maxed window
|
|
||||||
&& self.focused_workspace()?.maximized_window().is_none()
|
|
||||||
// and we don't have a monocle container
|
|
||||||
&& self.focused_workspace()?.monocle_container().is_none()
|
|
||||||
{
|
|
||||||
if let Ok(window) = self.focused_window_mut() {
|
|
||||||
window.focus(self.mouse_follows_focus)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to correctly restore and focus when switching to a workspace which
|
|
||||||
// contains a managed maximized window
|
|
||||||
if !follow_focus {
|
|
||||||
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
|
||||||
window.restore();
|
|
||||||
window.focus(self.mouse_follows_focus)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,12 +999,13 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {
|
pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
|
||||||
self.monitors_mut()
|
self.monitors_mut()
|
||||||
.get_mut(idx)
|
.get_mut(idx)
|
||||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||||
.update_focused_workspace(offset)
|
.update_focused_workspace(offset, &invisible_borders)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -1090,6 +1095,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
tracing::info!("moving container");
|
tracing::info!("moving container");
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
let mouse_follows_focus = self.mouse_follows_focus;
|
let mouse_follows_focus = self.mouse_follows_focus;
|
||||||
|
|
||||||
@@ -1108,7 +1114,7 @@ impl WindowManager {
|
|||||||
.remove_focused_container()
|
.remove_focused_container()
|
||||||
.ok_or_else(|| anyhow!("there is no container"))?;
|
.ok_or_else(|| anyhow!("there is no container"))?;
|
||||||
|
|
||||||
monitor.update_focused_workspace(offset)?;
|
monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||||
|
|
||||||
let target_monitor = self
|
let target_monitor = self
|
||||||
.monitors_mut()
|
.monitors_mut()
|
||||||
@@ -1117,7 +1123,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
target_monitor.add_container(container, workspace_idx)?;
|
target_monitor.add_container(container, workspace_idx)?;
|
||||||
target_monitor.load_focused_workspace(mouse_follows_focus)?;
|
target_monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||||
target_monitor.update_focused_workspace(offset)?;
|
target_monitor.update_focused_workspace(offset, &invisible_borders)?;
|
||||||
|
|
||||||
if follow {
|
if follow {
|
||||||
self.focus_monitor(monitor_idx)?;
|
self.focus_monitor(monitor_idx)?;
|
||||||
@@ -1197,21 +1203,7 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When switching workspaces and landing focus on a window that is not stack, but a stack
|
self.focused_window_mut()?.focus(self.mouse_follows_focus)?;
|
||||||
// exists, and there is a stackbar visible, when changing focus to that container stack,
|
|
||||||
// the focused text colour will not be applied until the stack has been cycled at least once
|
|
||||||
//
|
|
||||||
// With this piece of code, we check if we have changed focus to a container stack with
|
|
||||||
// a stackbar, and if we have, we run a quick update to make sure the focused text colour
|
|
||||||
// has been applied
|
|
||||||
let focused_window = self.focused_window_mut()?;
|
|
||||||
let focused_window_hwnd = focused_window.hwnd;
|
|
||||||
focused_window.focus(self.mouse_follows_focus)?;
|
|
||||||
|
|
||||||
let focused_container = self.focused_container()?;
|
|
||||||
if let Some(stackbar) = focused_container.stackbar() {
|
|
||||||
stackbar.update(focused_container.windows(), focused_window_hwnd)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1313,11 +1305,12 @@ impl WindowManager {
|
|||||||
// make sure to update the origin monitor workspace layout because it is no
|
// make sure to update the origin monitor workspace layout because it is no
|
||||||
// longer focused so it won't get updated at the end of this fn
|
// longer focused so it won't get updated at the end of this fn
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
|
|
||||||
self.monitors_mut()
|
self.monitors_mut()
|
||||||
.get_mut(origin_monitor_idx)
|
.get_mut(origin_monitor_idx)
|
||||||
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
|
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
|
||||||
.update_focused_workspace(offset)?;
|
.update_focused_workspace(offset, &invisible_borders)?;
|
||||||
|
|
||||||
let a = self
|
let a = self
|
||||||
.focused_monitor()
|
.focused_monitor()
|
||||||
@@ -1450,9 +1443,7 @@ impl WindowManager {
|
|||||||
anyhow!("this is not a valid direction from the current position")
|
anyhow!("this is not a valid direction from the current position")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let adjusted_new_index = if new_idx > current_container_idx
|
let adjusted_new_index = if new_idx > current_container_idx {
|
||||||
&& !matches!(workspace.layout(), Layout::Default(DefaultLayout::Grid))
|
|
||||||
{
|
|
||||||
new_idx - 1
|
new_idx - 1
|
||||||
} else {
|
} else {
|
||||||
new_idx
|
new_idx
|
||||||
@@ -1469,15 +1460,9 @@ impl WindowManager {
|
|||||||
pub fn promote_container_to_front(&mut self) -> Result<()> {
|
pub fn promote_container_to_front(&mut self) -> Result<()> {
|
||||||
self.handle_unmanaged_window_behaviour()?;
|
self.handle_unmanaged_window_behaviour()?;
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
|
||||||
|
|
||||||
if matches!(workspace.layout(), Layout::Default(DefaultLayout::Grid)) {
|
|
||||||
tracing::debug!("ignoring promote command for grid layout");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!("promoting container");
|
tracing::info!("promoting container");
|
||||||
|
|
||||||
|
let workspace = self.focused_workspace_mut()?;
|
||||||
workspace.promote_container()?;
|
workspace.promote_container()?;
|
||||||
self.update_focused_workspace(self.mouse_follows_focus)
|
self.update_focused_workspace(self.mouse_follows_focus)
|
||||||
}
|
}
|
||||||
@@ -1486,15 +1471,9 @@ impl WindowManager {
|
|||||||
pub fn promote_focus_to_front(&mut self) -> Result<()> {
|
pub fn promote_focus_to_front(&mut self) -> Result<()> {
|
||||||
self.handle_unmanaged_window_behaviour()?;
|
self.handle_unmanaged_window_behaviour()?;
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
|
||||||
|
|
||||||
if matches!(workspace.layout(), Layout::Default(DefaultLayout::Grid)) {
|
|
||||||
tracing::info!("ignoring promote focus command for grid layout");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!("promoting focus");
|
tracing::info!("promoting focus");
|
||||||
|
|
||||||
|
let workspace = self.focused_workspace_mut()?;
|
||||||
let target_idx = match workspace.layout() {
|
let target_idx = match workspace.layout() {
|
||||||
Layout::Default(_) => 0,
|
Layout::Default(_) => 0,
|
||||||
Layout::Custom(custom) => custom
|
Layout::Custom(custom) => custom
|
||||||
@@ -1555,6 +1534,7 @@ impl WindowManager {
|
|||||||
tracing::info!("floating window");
|
tracing::info!("floating window");
|
||||||
|
|
||||||
let work_area = self.focused_monitor_work_area()?;
|
let work_area = self.focused_monitor_work_area()?;
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
workspace.new_floating_window()?;
|
workspace.new_floating_window()?;
|
||||||
@@ -1564,7 +1544,7 @@ impl WindowManager {
|
|||||||
.last_mut()
|
.last_mut()
|
||||||
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
||||||
|
|
||||||
window.center(&work_area)?;
|
window.center(&work_area, &invisible_borders)?;
|
||||||
window.focus(self.mouse_follows_focus)?;
|
window.focus(self.mouse_follows_focus)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1640,10 +1620,10 @@ impl WindowManager {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn flip_layout(&mut self, layout_flip: Axis) -> Result<()> {
|
pub fn flip_layout(&mut self, layout_flip: Axis) -> Result<()> {
|
||||||
let workspace = self.focused_workspace_mut()?;
|
|
||||||
|
|
||||||
tracing::info!("flipping layout");
|
tracing::info!("flipping layout");
|
||||||
|
|
||||||
|
let workspace = self.focused_workspace_mut()?;
|
||||||
|
|
||||||
#[allow(clippy::match_same_arms)]
|
#[allow(clippy::match_same_arms)]
|
||||||
match workspace.layout_flip() {
|
match workspace.layout_flip() {
|
||||||
None => {
|
None => {
|
||||||
@@ -1821,6 +1801,7 @@ impl WindowManager {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("setting workspace layout");
|
tracing::info!("setting workspace layout");
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
let focused_monitor_idx = self.focused_monitor_idx();
|
let focused_monitor_idx = self.focused_monitor_idx();
|
||||||
|
|
||||||
@@ -1849,7 +1830,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If this is the focused workspace on a non-focused screen, let's update it
|
// If this is the focused workspace on a non-focused screen, let's update it
|
||||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(self.update_focused_workspace(false)?)
|
Ok(self.update_focused_workspace(false)?)
|
||||||
@@ -1869,6 +1850,7 @@ impl WindowManager {
|
|||||||
{
|
{
|
||||||
tracing::info!("setting workspace layout");
|
tracing::info!("setting workspace layout");
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
let focused_monitor_idx = self.focused_monitor_idx();
|
let focused_monitor_idx = self.focused_monitor_idx();
|
||||||
|
|
||||||
@@ -1899,7 +1881,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If this is the focused workspace on a non-focused screen, let's update it
|
// If this is the focused workspace on a non-focused screen, let's update it
|
||||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(self.update_focused_workspace(false)?)
|
Ok(self.update_focused_workspace(false)?)
|
||||||
@@ -1914,6 +1896,7 @@ impl WindowManager {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("setting workspace layout");
|
tracing::info!("setting workspace layout");
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
let focused_monitor_idx = self.focused_monitor_idx();
|
let focused_monitor_idx = self.focused_monitor_idx();
|
||||||
|
|
||||||
@@ -1940,7 +1923,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If this is the focused workspace on a non-focused screen, let's update it
|
// If this is the focused workspace on a non-focused screen, let's update it
|
||||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(self.update_focused_workspace(false)?)
|
Ok(self.update_focused_workspace(false)?)
|
||||||
@@ -1956,6 +1939,7 @@ impl WindowManager {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("setting workspace layout");
|
tracing::info!("setting workspace layout");
|
||||||
|
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
let focused_monitor_idx = self.focused_monitor_idx();
|
let focused_monitor_idx = self.focused_monitor_idx();
|
||||||
|
|
||||||
@@ -1981,7 +1965,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If this is the focused workspace on a non-focused screen, let's update it
|
// If this is the focused workspace on a non-focused screen, let's update it
|
||||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(self.update_focused_workspace(false)?)
|
Ok(self.update_focused_workspace(false)?)
|
||||||
@@ -2000,6 +1984,7 @@ impl WindowManager {
|
|||||||
{
|
{
|
||||||
tracing::info!("setting workspace layout");
|
tracing::info!("setting workspace layout");
|
||||||
let layout = CustomLayout::from_path(path)?;
|
let layout = CustomLayout::from_path(path)?;
|
||||||
|
let invisible_borders = self.invisible_borders;
|
||||||
let offset = self.work_area_offset;
|
let offset = self.work_area_offset;
|
||||||
let focused_monitor_idx = self.focused_monitor_idx();
|
let focused_monitor_idx = self.focused_monitor_idx();
|
||||||
|
|
||||||
@@ -2026,7 +2011,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// If this is the focused workspace on a non-focused screen, let's update it
|
// If this is the focused workspace on a non-focused screen, let's update it
|
||||||
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
||||||
workspace.update(&work_area, offset)?;
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(self.update_focused_workspace(false)?)
|
Ok(self.update_focused_workspace(false)?)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::fmt::Display;
|
|||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::window::should_act;
|
use crate::window::should_act;
|
||||||
@@ -11,7 +10,7 @@ use crate::winevent::WinEvent;
|
|||||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||||
use crate::REGEX_IDENTIFIERS;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Copy, Clone, Serialize, JsonSchema)]
|
||||||
#[serde(tag = "type", content = "content")]
|
#[serde(tag = "type", content = "content")]
|
||||||
pub enum WindowManagerEvent {
|
pub enum WindowManagerEvent {
|
||||||
Destroy(WinEvent, Window),
|
Destroy(WinEvent, Window),
|
||||||
@@ -28,7 +27,6 @@ pub enum WindowManagerEvent {
|
|||||||
Unmanage(Window),
|
Unmanage(Window),
|
||||||
Raise(Window),
|
Raise(Window),
|
||||||
DisplayChange(Window),
|
DisplayChange(Window),
|
||||||
ForceUpdate(Window),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for WindowManagerEvent {
|
impl Display for WindowManagerEvent {
|
||||||
@@ -79,9 +77,6 @@ impl Display for WindowManagerEvent {
|
|||||||
Self::DisplayChange(window) => {
|
Self::DisplayChange(window) => {
|
||||||
write!(f, "DisplayChange (Window: {window})")
|
write!(f, "DisplayChange (Window: {window})")
|
||||||
}
|
}
|
||||||
Self::ForceUpdate(window) => {
|
|
||||||
write!(f, "ForceUpdate (Window: {window})")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,8 +97,7 @@ impl WindowManagerEvent {
|
|||||||
| Self::Raise(window)
|
| Self::Raise(window)
|
||||||
| Self::Manage(window)
|
| Self::Manage(window)
|
||||||
| Self::DisplayChange(window)
|
| Self::DisplayChange(window)
|
||||||
| Self::Unmanage(window)
|
| Self::Unmanage(window) => window,
|
||||||
| Self::ForceUpdate(window) => window,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,13 +138,11 @@ impl WindowManagerEvent {
|
|||||||
let title = &window.title().ok()?;
|
let title = &window.title().ok()?;
|
||||||
let exe_name = &window.exe().ok()?;
|
let exe_name = &window.exe().ok()?;
|
||||||
let class = &window.class().ok()?;
|
let class = &window.class().ok()?;
|
||||||
let path = &window.path().ok()?;
|
|
||||||
|
|
||||||
let should_trigger = should_act(
|
let should_trigger = should_act(
|
||||||
title,
|
title,
|
||||||
exe_name,
|
exe_name,
|
||||||
class,
|
class,
|
||||||
path,
|
|
||||||
&object_name_change_on_launch,
|
&object_name_change_on_launch,
|
||||||
®ex_identifiers,
|
®ex_identifiers,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ use windows::Win32::Foundation::WPARAM;
|
|||||||
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||||
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
||||||
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||||
use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
|
|
||||||
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
|
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
|
||||||
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
|
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
|
||||||
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
|
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
|
||||||
@@ -36,8 +35,6 @@ use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
|||||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
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::RoundRect;
|
|
||||||
use windows::Win32::Graphics::Gdi::DISPLAY_DEVICEW;
|
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;
|
||||||
@@ -68,7 +65,6 @@ use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
|
|||||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
|
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
|
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
|
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
|
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
|
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
|
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
|
||||||
@@ -86,7 +82,6 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
|
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
|
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
|
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::IsZoomed;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
|
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
|
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
|
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
|
||||||
@@ -105,7 +100,7 @@ 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_BOTTOM;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
|
use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
|
use windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST;
|
||||||
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;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
||||||
@@ -130,7 +125,8 @@ use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||||
|
|
||||||
@@ -268,7 +264,7 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
.ok()
|
.ok()
|
||||||
{
|
{
|
||||||
Ok(()) => {}
|
Ok(_) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!("enum_display_devices: {}", error);
|
tracing::error!("enum_display_devices: {}", error);
|
||||||
return Err(error.into());
|
return Err(error.into());
|
||||||
@@ -343,25 +339,14 @@ impl WindowsApi {
|
|||||||
unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }.0
|
unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// position window resizes the target window to the given layout, adjusting
|
|
||||||
/// the layout to account for any window shadow borders (the window painted
|
|
||||||
/// 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 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;
|
||||||
|
|
||||||
let shadow_rect = Self::shadow_rect(hwnd)?;
|
let position = if top { HWND_TOPMOST } else { HWND_BOTTOM };
|
||||||
let rect = Rect {
|
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
||||||
left: layout.left + shadow_rect.left,
|
|
||||||
top: layout.top + shadow_rect.top,
|
|
||||||
right: layout.right + shadow_rect.right,
|
|
||||||
bottom: layout.bottom + shadow_rect.bottom,
|
|
||||||
};
|
|
||||||
|
|
||||||
let position = if top { HWND_TOP } else { HWND_NOTOPMOST };
|
|
||||||
Self::set_window_pos(hwnd, &rect, position, flags.bits())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
|
pub fn bring_window_to_top(hwnd: HWND) -> Result<()> {
|
||||||
@@ -371,7 +356,7 @@ impl WindowsApi {
|
|||||||
pub fn raise_window(hwnd: HWND) -> Result<()> {
|
pub fn raise_window(hwnd: HWND) -> Result<()> {
|
||||||
let flags = SetWindowPosition::NO_MOVE;
|
let flags = SetWindowPosition::NO_MOVE;
|
||||||
|
|
||||||
let position = HWND_TOP;
|
let position = HWND_TOPMOST;
|
||||||
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,18 +367,6 @@ impl WindowsApi {
|
|||||||
SetWindowPosition::NO_ACTIVATE
|
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;
|
let position = HWND_NOTOPMOST;
|
||||||
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
Self::set_window_pos(hwnd, layout, position, flags.bits())
|
||||||
}
|
}
|
||||||
@@ -405,8 +378,7 @@ impl WindowsApi {
|
|||||||
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set_window_pos calls SetWindowPos without any accounting for Window decorations.
|
pub 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 {
|
||||||
SetWindowPos(
|
SetWindowPos(
|
||||||
hwnd,
|
hwnd,
|
||||||
@@ -421,7 +393,7 @@ impl WindowsApi {
|
|||||||
.process()
|
.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
|
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
|
||||||
// BOOL is returned but does not signify whether or not the operation was succesful
|
// BOOL is returned but does not signify whether or not the operation was succesful
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
||||||
unsafe { ShowWindow(hwnd, command) };
|
unsafe { ShowWindow(hwnd, command) };
|
||||||
@@ -498,56 +470,11 @@ impl WindowsApi {
|
|||||||
|
|
||||||
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
|
pub fn window_rect(hwnd: HWND) -> Result<Rect> {
|
||||||
let mut rect = unsafe { std::mem::zeroed() };
|
let mut rect = unsafe { std::mem::zeroed() };
|
||||||
|
unsafe { GetWindowRect(hwnd, &mut rect) }.process()?;
|
||||||
|
|
||||||
if Self::dwm_get_window_attribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &mut rect).is_ok() {
|
Ok(Rect::from(rect))
|
||||||
// TODO(raggi): once we declare DPI awareness, we will need to scale the rect.
|
|
||||||
// let window_scale = unsafe { GetDpiForWindow(hwnd) };
|
|
||||||
// let system_scale = unsafe { GetDpiForSystem() };
|
|
||||||
// Ok(Rect::from(rect).scale(system_scale.try_into()?, window_scale.try_into()?))
|
|
||||||
Ok(Rect::from(rect))
|
|
||||||
} else {
|
|
||||||
unsafe { GetWindowRect(hwnd, &mut rect) }.process()?;
|
|
||||||
Ok(Rect::from(rect))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// shadow_rect computes the offset of the shadow position of the window to
|
|
||||||
/// the window painted region. The four values in the returned Rect can be
|
|
||||||
/// added to a position rect to compute a size for set_window_pos that will
|
|
||||||
/// fill the target area, ignoring shadows.
|
|
||||||
fn shadow_rect(hwnd: HWND) -> Result<Rect> {
|
|
||||||
let window_rect = Self::window_rect(hwnd)?;
|
|
||||||
|
|
||||||
let mut srect = Default::default();
|
|
||||||
unsafe { GetWindowRect(hwnd, &mut srect) }.process()?;
|
|
||||||
let shadow_rect = Rect::from(srect);
|
|
||||||
|
|
||||||
Ok(Rect {
|
|
||||||
left: shadow_rect.left - window_rect.left,
|
|
||||||
top: shadow_rect.top - window_rect.top,
|
|
||||||
right: shadow_rect.right - window_rect.right,
|
|
||||||
bottom: shadow_rect.bottom - window_rect.bottom,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn round_rect(hdc: HDC, rect: &Rect, border_radius: i32) {
|
|
||||||
unsafe {
|
|
||||||
RoundRect(
|
|
||||||
hdc,
|
|
||||||
rect.left,
|
|
||||||
rect.top,
|
|
||||||
rect.right,
|
|
||||||
rect.bottom,
|
|
||||||
border_radius,
|
|
||||||
border_radius,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn rectangle(hdc: HDC, rect: &Rect) {
|
|
||||||
unsafe {
|
|
||||||
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
|
fn set_cursor_pos(x: i32, y: i32) -> Result<()> {
|
||||||
unsafe { SetCursorPos(x, y) }.process()
|
unsafe { SetCursorPos(x, y) }.process()
|
||||||
}
|
}
|
||||||
@@ -750,10 +677,6 @@ impl WindowsApi {
|
|||||||
unsafe { IsIconic(hwnd) }.into()
|
unsafe { IsIconic(hwnd) }.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_zoomed(hwnd: HWND) -> bool {
|
|
||||||
unsafe { IsZoomed(hwnd) }.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFOEXW> {
|
pub fn monitor_info_w(hmonitor: HMONITOR) -> Result<MONITORINFOEXW> {
|
||||||
let mut ex_info = MONITORINFOEXW::default();
|
let mut ex_info = MONITORINFOEXW::default();
|
||||||
ex_info.monitorInfo.cbSize = u32::try_from(std::mem::size_of::<MONITORINFOEXW>())?;
|
ex_info.monitorInfo.cbSize = u32::try_from(std::mem::size_of::<MONITORINFOEXW>())?;
|
||||||
@@ -921,10 +844,10 @@ impl WindowsApi {
|
|||||||
pub fn create_border_window(name: PCWSTR, instance: HMODULE) -> Result<isize> {
|
pub fn create_border_window(name: PCWSTR, instance: HMODULE) -> Result<isize> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let hwnd = CreateWindowExW(
|
let hwnd = CreateWindowExW(
|
||||||
WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
|
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
|
||||||
name,
|
name,
|
||||||
name,
|
name,
|
||||||
WS_POPUP | WS_SYSMENU,
|
WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
|
||||||
CW_USEDEFAULT,
|
CW_USEDEFAULT,
|
||||||
CW_USEDEFAULT,
|
CW_USEDEFAULT,
|
||||||
CW_USEDEFAULT,
|
CW_USEDEFAULT,
|
||||||
@@ -985,13 +908,6 @@ impl WindowsApi {
|
|||||||
actual != 0
|
actual != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lbutton_is_pressed() -> bool {
|
|
||||||
let state = unsafe { GetKeyState(i32::from(VK_LBUTTON.0)) };
|
|
||||||
#[allow(clippy::cast_sign_loss)]
|
|
||||||
let actual = (state as u16) & 0x8000;
|
|
||||||
actual != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn left_click() -> u32 {
|
pub fn left_click() -> u32 {
|
||||||
let inputs = [
|
let inputs = [
|
||||||
INPUT {
|
INPUT {
|
||||||
|
|||||||
@@ -13,13 +13,11 @@ use windows::Win32::Graphics::Gdi::BeginPaint;
|
|||||||
use windows::Win32::Graphics::Gdi::CreatePen;
|
use windows::Win32::Graphics::Gdi::CreatePen;
|
||||||
use windows::Win32::Graphics::Gdi::EndPaint;
|
use windows::Win32::Graphics::Gdi::EndPaint;
|
||||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||||
use windows::Win32::Graphics::Gdi::RoundRect;
|
|
||||||
use windows::Win32::Graphics::Gdi::SelectObject;
|
use windows::Win32::Graphics::Gdi::SelectObject;
|
||||||
use windows::Win32::Graphics::Gdi::ValidateRect;
|
use windows::Win32::Graphics::Gdi::ValidateRect;
|
||||||
use windows::Win32::Graphics::Gdi::HDC;
|
use windows::Win32::Graphics::Gdi::HDC;
|
||||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||||
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
|
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
|
||||||
use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME;
|
|
||||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
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::DefWindowProcW;
|
||||||
@@ -39,15 +37,13 @@ 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_listener::WINEVENT_CALLBACK_CHANNEL;
|
||||||
use crate::winevent_listener;
|
|
||||||
use crate::BORDER_COLOUR_CURRENT;
|
use crate::BORDER_COLOUR_CURRENT;
|
||||||
use crate::BORDER_RECT;
|
use crate::BORDER_RECT;
|
||||||
use crate::BORDER_WIDTH;
|
use crate::BORDER_WIDTH;
|
||||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||||
use crate::MONITOR_INDEX_PREFERENCES;
|
use crate::MONITOR_INDEX_PREFERENCES;
|
||||||
use crate::TRANSPARENCY_COLOUR;
|
use crate::TRANSPARENCY_COLOUR;
|
||||||
use crate::WINDOWS_11;
|
|
||||||
|
|
||||||
pub extern "system" fn valid_display_monitors(
|
pub extern "system" fn valid_display_monitors(
|
||||||
hmonitor: HMONITOR,
|
hmonitor: HMONITOR,
|
||||||
@@ -150,17 +146,12 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|||||||
let is_visible = WindowsApi::is_window_visible(hwnd);
|
let is_visible = WindowsApi::is_window_visible(hwnd);
|
||||||
let is_window = WindowsApi::is_window(hwnd);
|
let is_window = WindowsApi::is_window(hwnd);
|
||||||
let is_minimized = WindowsApi::is_iconic(hwnd);
|
let is_minimized = WindowsApi::is_iconic(hwnd);
|
||||||
let is_maximized = WindowsApi::is_zoomed(hwnd);
|
|
||||||
|
|
||||||
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) {
|
if let Ok(should_manage) = window.should_manage(None) {
|
||||||
if should_manage {
|
if should_manage {
|
||||||
if is_maximized {
|
|
||||||
WindowsApi::restore_window(hwnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut container = Container::default();
|
let mut container = Container::default();
|
||||||
container.windows_mut().push_back(window);
|
container.windows_mut().push_back(window);
|
||||||
containers.push_back(container);
|
containers.push_back(container);
|
||||||
@@ -187,10 +178,7 @@ pub extern "system" fn win_event_hook(
|
|||||||
|
|
||||||
let window = Window { hwnd: hwnd.0 };
|
let window = Window { hwnd: hwnd.0 };
|
||||||
|
|
||||||
let winevent = match WinEvent::try_from(event) {
|
let winevent = unsafe { ::std::mem::transmute(event) };
|
||||||
Ok(event) => event,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
|
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
|
||||||
None => return,
|
None => return,
|
||||||
Some(event) => event,
|
Some(event) => event,
|
||||||
@@ -198,9 +186,11 @@ pub extern "system" fn win_event_hook(
|
|||||||
|
|
||||||
if let Ok(should_manage) = window.should_manage(Option::from(event_type)) {
|
if let Ok(should_manage) = window.should_manage(Option::from(event_type)) {
|
||||||
if should_manage {
|
if should_manage {
|
||||||
winevent_listener::event_tx()
|
WINEVENT_CALLBACK_CHANNEL
|
||||||
|
.lock()
|
||||||
|
.0
|
||||||
.send(event_type)
|
.send(event_type)
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +208,7 @@ pub extern "system" fn border_window(
|
|||||||
let mut ps = PAINTSTRUCT::default();
|
let mut ps = PAINTSTRUCT::default();
|
||||||
let hdc = BeginPaint(window, &mut ps);
|
let hdc = BeginPaint(window, &mut ps);
|
||||||
let hpen = CreatePen(
|
let hpen = CreatePen(
|
||||||
PS_SOLID | PS_INSIDEFRAME,
|
PS_SOLID,
|
||||||
BORDER_WIDTH.load(Ordering::SeqCst),
|
BORDER_WIDTH.load(Ordering::SeqCst),
|
||||||
COLORREF(BORDER_COLOUR_CURRENT.load(Ordering::SeqCst)),
|
COLORREF(BORDER_COLOUR_CURRENT.load(Ordering::SeqCst)),
|
||||||
);
|
);
|
||||||
@@ -226,17 +216,7 @@ pub extern "system" fn border_window(
|
|||||||
|
|
||||||
SelectObject(hdc, hpen);
|
SelectObject(hdc, hpen);
|
||||||
SelectObject(hdc, hbrush);
|
SelectObject(hdc, hbrush);
|
||||||
// TODO(raggi): this is approximately the correct curvature for
|
Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom);
|
||||||
// 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);
|
EndPaint(window, &ps);
|
||||||
ValidateRect(window, None);
|
ValidateRect(window, None);
|
||||||
|
|
||||||
@@ -261,9 +241,11 @@ pub extern "system" fn hidden_window(
|
|||||||
match message {
|
match message {
|
||||||
WM_DISPLAYCHANGE => {
|
WM_DISPLAYCHANGE => {
|
||||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||||
winevent_listener::event_tx()
|
WINEVENT_CALLBACK_CHANNEL
|
||||||
|
.lock()
|
||||||
|
.0
|
||||||
.send(event_type)
|
.send(event_type)
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||||
|
|
||||||
LRESULT(0)
|
LRESULT(0)
|
||||||
}
|
}
|
||||||
@@ -274,9 +256,11 @@ pub extern "system" fn hidden_window(
|
|||||||
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
|
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
|
||||||
{
|
{
|
||||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||||
winevent_listener::event_tx()
|
WINEVENT_CALLBACK_CHANNEL
|
||||||
|
.lock()
|
||||||
|
.0
|
||||||
.send(event_type)
|
.send(event_type)
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||||
}
|
}
|
||||||
LRESULT(0)
|
LRESULT(0)
|
||||||
}
|
}
|
||||||
@@ -285,9 +269,11 @@ pub extern "system" fn hidden_window(
|
|||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
||||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
||||||
winevent_listener::event_tx()
|
WINEVENT_CALLBACK_CHANNEL
|
||||||
|
.lock()
|
||||||
|
.0
|
||||||
.send(event_type)
|
.send(event_type)
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
|
||||||
}
|
}
|
||||||
LRESULT(0)
|
LRESULT(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#![allow(clippy::use_self)]
|
#![allow(clippy::use_self)]
|
||||||
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;
|
||||||
@@ -89,7 +88,7 @@ use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display, JsonSchema)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Display, JsonSchema)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum WinEvent {
|
pub enum WinEvent {
|
||||||
@@ -178,100 +177,3 @@ pub enum WinEvent {
|
|||||||
UiaPropIdSEnd = EVENT_UIA_PROPID_END,
|
UiaPropIdSEnd = EVENT_UIA_PROPID_END,
|
||||||
UiaPropIdStart = EVENT_UIA_PROPID_START,
|
UiaPropIdStart = EVENT_UIA_PROPID_START,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u32> for WinEvent {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
EVENT_AIA_END => Ok(Self::AiaEnd),
|
|
||||||
EVENT_AIA_START => Ok(Self::AiaStart),
|
|
||||||
EVENT_CONSOLE_CARET => Ok(Self::ConsoleCaret),
|
|
||||||
EVENT_CONSOLE_END => Ok(Self::ConsoleEnd),
|
|
||||||
EVENT_CONSOLE_END_APPLICATION => Ok(Self::ConsoleEndApplication),
|
|
||||||
EVENT_CONSOLE_LAYOUT => Ok(Self::ConsoleLayout),
|
|
||||||
EVENT_CONSOLE_START_APPLICATION => Ok(Self::ConsoleStartApplication),
|
|
||||||
EVENT_CONSOLE_UPDATE_REGION => Ok(Self::ConsoleUpdateRegion),
|
|
||||||
EVENT_CONSOLE_UPDATE_SCROLL => Ok(Self::ConsoleUpdateScroll),
|
|
||||||
EVENT_CONSOLE_UPDATE_SIMPLE => Ok(Self::ConsoleUpdateSimple),
|
|
||||||
EVENT_OBJECT_ACCELERATORCHANGE => Ok(Self::ObjectAcceleratorChange),
|
|
||||||
EVENT_OBJECT_CLOAKED => Ok(Self::ObjectCloaked),
|
|
||||||
EVENT_OBJECT_CONTENTSCROLLED => Ok(Self::ObjectContentScrolled),
|
|
||||||
EVENT_OBJECT_CREATE => Ok(Self::ObjectCreate),
|
|
||||||
EVENT_OBJECT_DEFACTIONCHANGE => Ok(Self::ObjectDefActionChange),
|
|
||||||
EVENT_OBJECT_DESCRIPTIONCHANGE => Ok(Self::ObjectDescriptionChange),
|
|
||||||
EVENT_OBJECT_DESTROY => Ok(Self::ObjectDestroy),
|
|
||||||
EVENT_OBJECT_DRAGCANCEL => Ok(Self::ObjectDragCancel),
|
|
||||||
EVENT_OBJECT_DRAGCOMPLETE => Ok(Self::ObjectDragComplete),
|
|
||||||
EVENT_OBJECT_DRAGDROPPED => Ok(Self::ObjectDragDropped),
|
|
||||||
EVENT_OBJECT_DRAGENTER => Ok(Self::ObjectDragEnter),
|
|
||||||
EVENT_OBJECT_DRAGLEAVE => Ok(Self::ObjectDragLeave),
|
|
||||||
EVENT_OBJECT_DRAGSTART => Ok(Self::ObjectDragStart),
|
|
||||||
EVENT_OBJECT_END => Ok(Self::ObjectEnd),
|
|
||||||
EVENT_OBJECT_FOCUS => Ok(Self::ObjectFocus),
|
|
||||||
EVENT_OBJECT_HELPCHANGE => Ok(Self::ObjectHelpChange),
|
|
||||||
EVENT_OBJECT_HIDE => Ok(Self::ObjectHide),
|
|
||||||
EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED => Ok(Self::ObjectHostedObjectsInvalidated),
|
|
||||||
EVENT_OBJECT_IME_CHANGE => Ok(Self::ObjectImeChange),
|
|
||||||
EVENT_OBJECT_IME_HIDE => Ok(Self::ObjectImeHide),
|
|
||||||
EVENT_OBJECT_IME_SHOW => Ok(Self::ObjectImeShow),
|
|
||||||
EVENT_OBJECT_INVOKED => Ok(Self::ObjectInvoked),
|
|
||||||
EVENT_OBJECT_LIVEREGIONCHANGED => Ok(Self::ObjectLiveRegionChanged),
|
|
||||||
EVENT_OBJECT_LOCATIONCHANGE => Ok(Self::ObjectLocationChange),
|
|
||||||
EVENT_OBJECT_NAMECHANGE => Ok(Self::ObjectNameChange),
|
|
||||||
EVENT_OBJECT_PARENTCHANGE => Ok(Self::ObjectParentChange),
|
|
||||||
EVENT_OBJECT_REORDER => Ok(Self::ObjectReorder),
|
|
||||||
EVENT_OBJECT_SELECTION => Ok(Self::ObjectSelection),
|
|
||||||
EVENT_OBJECT_SELECTIONADD => Ok(Self::ObjectSelectionAdd),
|
|
||||||
EVENT_OBJECT_SELECTIONREMOVE => Ok(Self::ObjectSelectionRemove),
|
|
||||||
EVENT_OBJECT_SELECTIONWITHIN => Ok(Self::ObjectSelectionWithin),
|
|
||||||
EVENT_OBJECT_SHOW => Ok(Self::ObjectShow),
|
|
||||||
EVENT_OBJECT_STATECHANGE => Ok(Self::ObjectStateChange),
|
|
||||||
EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED => {
|
|
||||||
Ok(Self::ObjectTextEditConversionTargetChanged)
|
|
||||||
}
|
|
||||||
EVENT_OBJECT_TEXTSELECTIONCHANGED => Ok(Self::ObjectTextSelectionChanged),
|
|
||||||
EVENT_OBJECT_UNCLOAKED => Ok(Self::ObjectUncloaked),
|
|
||||||
EVENT_OBJECT_VALUECHANGE => Ok(Self::ObjectValueChange),
|
|
||||||
EVENT_OEM_DEFINED_END => Ok(Self::OemDefinedEnd),
|
|
||||||
EVENT_OEM_DEFINED_START => Ok(Self::OemDefinedStart),
|
|
||||||
EVENT_SYSTEM_ALERT => Ok(Self::SystemAlert),
|
|
||||||
EVENT_SYSTEM_ARRANGMENTPREVIEW => Ok(Self::SystemArrangementPreview),
|
|
||||||
EVENT_SYSTEM_CAPTUREEND => Ok(Self::SystemCaptureEnd),
|
|
||||||
EVENT_SYSTEM_CAPTURESTART => Ok(Self::SystemCaptureStart),
|
|
||||||
EVENT_SYSTEM_CONTEXTHELPEND => Ok(Self::SystemContextHelpEnd),
|
|
||||||
EVENT_SYSTEM_CONTEXTHELPSTART => Ok(Self::SystemContextHelpStart),
|
|
||||||
EVENT_SYSTEM_DESKTOPSWITCH => Ok(Self::SystemDesktopSwitch),
|
|
||||||
EVENT_SYSTEM_DIALOGEND => Ok(Self::SystemDialogEnd),
|
|
||||||
EVENT_SYSTEM_DIALOGSTART => Ok(Self::SystemDialogStart),
|
|
||||||
EVENT_SYSTEM_DRAGDROPEND => Ok(Self::SystemDragDropEnd),
|
|
||||||
EVENT_SYSTEM_DRAGDROPSTART => Ok(Self::SystemDragDropStart),
|
|
||||||
EVENT_SYSTEM_END => Ok(Self::SystemEnd),
|
|
||||||
EVENT_SYSTEM_FOREGROUND => Ok(Self::SystemForeground),
|
|
||||||
EVENT_SYSTEM_IME_KEY_NOTIFICATION => Ok(Self::SystemImeKeyNotification),
|
|
||||||
EVENT_SYSTEM_MENUEND => Ok(Self::SystemMenuEnd),
|
|
||||||
EVENT_SYSTEM_MENUPOPUPEND => Ok(Self::SystemMenuPopupEnd),
|
|
||||||
EVENT_SYSTEM_MENUPOPUPSTART => Ok(Self::SystemMenuPopupStart),
|
|
||||||
EVENT_SYSTEM_MENUSTART => Ok(Self::SystemMenuStart),
|
|
||||||
EVENT_SYSTEM_MINIMIZEEND => Ok(Self::SystemMinimizeEnd),
|
|
||||||
EVENT_SYSTEM_MINIMIZESTART => Ok(Self::SystemMinimizeStart),
|
|
||||||
EVENT_SYSTEM_MOVESIZEEND => Ok(Self::SystemMoveSizeEnd),
|
|
||||||
EVENT_SYSTEM_MOVESIZESTART => Ok(Self::SystemMoveSizeStart),
|
|
||||||
EVENT_SYSTEM_SCROLLINGEND => Ok(Self::SystemScrollingEnd),
|
|
||||||
EVENT_SYSTEM_SCROLLINGSTART => Ok(Self::SystemScrollingStart),
|
|
||||||
EVENT_SYSTEM_SOUND => Ok(Self::SystemSound),
|
|
||||||
EVENT_SYSTEM_SWITCHEND => Ok(Self::SystemSwitchEnd),
|
|
||||||
EVENT_SYSTEM_SWITCHER_APPDROPPED => Ok(Self::SystemSwitcherAppDropped),
|
|
||||||
EVENT_SYSTEM_SWITCHER_APPGRABBED => Ok(Self::SystemSwitcherAppGrabbed),
|
|
||||||
EVENT_SYSTEM_SWITCHER_APPOVERTARGET => Ok(Self::SystemSwitcherAppOverTarget),
|
|
||||||
EVENT_SYSTEM_SWITCHER_CANCELLED => Ok(Self::SystemSwitcherCancelled),
|
|
||||||
EVENT_SYSTEM_SWITCHSTART => Ok(Self::SystemSwitchStart),
|
|
||||||
EVENT_UIA_EVENTID_END => Ok(Self::UiaEventIdSEnd),
|
|
||||||
EVENT_UIA_EVENTID_START => Ok(Self::UiaEventIdStart),
|
|
||||||
EVENT_UIA_PROPID_END => Ok(Self::UiaPropIdSEnd),
|
|
||||||
EVENT_UIA_PROPID_START => Ok(Self::UiaPropIdStart),
|
|
||||||
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,62 +1,105 @@
|
|||||||
use std::sync::OnceLock;
|
use std::sync::atomic::AtomicIsize;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::UI::Accessibility::SetWinEventHook;
|
use windows::Win32::UI::Accessibility::SetWinEventHook;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
use windows::Win32::UI::WindowsAndMessaging::PeekMessageW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
|
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PM_REMOVE;
|
||||||
|
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_callbacks;
|
use crate::windows_callbacks;
|
||||||
|
|
||||||
static CHANNEL: OnceLock<(Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>)> =
|
lazy_static! {
|
||||||
OnceLock::new();
|
pub static ref WINEVENT_CALLBACK_CHANNEL: Arc<Mutex<(Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>)>> =
|
||||||
|
Arc::new(Mutex::new(crossbeam_channel::unbounded()));
|
||||||
|
}
|
||||||
|
|
||||||
static EVENT_PUMP: OnceLock<std::thread::JoinHandle<()>> = OnceLock::new();
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WinEventListener {
|
||||||
|
hook: Arc<AtomicIsize>,
|
||||||
|
outgoing_events: Arc<Mutex<Sender<WindowManagerEvent>>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start() {
|
pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener {
|
||||||
EVENT_PUMP.get_or_init(|| {
|
WinEventListener {
|
||||||
std::thread::spawn(move || {
|
hook: Arc::new(AtomicIsize::new(0)),
|
||||||
|
outgoing_events: outgoing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WinEventListener {
|
||||||
|
pub fn start(self) {
|
||||||
|
let hook = self.hook.clone();
|
||||||
|
let outgoing = self.outgoing_events.lock().clone();
|
||||||
|
|
||||||
|
std::thread::spawn(move || unsafe {
|
||||||
|
let hook_ref = SetWinEventHook(
|
||||||
|
EVENT_MIN,
|
||||||
|
EVENT_MAX,
|
||||||
|
None,
|
||||||
|
Some(windows_callbacks::win_event_hook),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
hook.store(hook_ref.0, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// The code in the callback doesn't work in its own loop, needs to be within
|
||||||
|
// the MessageLoop callback for the winevent callback to even fire
|
||||||
|
MessageLoop::start(10, |_msg| {
|
||||||
|
if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().1.try_recv() {
|
||||||
|
match outgoing.send(event) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!("{}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct MessageLoop;
|
||||||
|
|
||||||
|
impl MessageLoop {
|
||||||
|
pub fn start(sleep: u64, cb: impl Fn(Option<MSG>) -> bool) {
|
||||||
|
Self::start_with_sleep(sleep, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_with_sleep(sleep: u64, cb: impl Fn(Option<MSG>) -> bool) {
|
||||||
|
let mut msg: MSG = MSG::default();
|
||||||
|
loop {
|
||||||
|
let mut value: Option<MSG> = None;
|
||||||
unsafe {
|
unsafe {
|
||||||
SetWinEventHook(
|
if !bool::from(!PeekMessageW(&mut msg, HWND(0), 0, 0, PM_REMOVE)) {
|
||||||
EVENT_MIN,
|
|
||||||
EVENT_MAX,
|
|
||||||
None,
|
|
||||||
Some(windows_callbacks::win_event_hook),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut msg: MSG = MSG::default();
|
|
||||||
unsafe {
|
|
||||||
if !GetMessageW(&mut msg, HWND(0), 0, 0).as_bool() {
|
|
||||||
tracing::info!("windows event processing shutdown");
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
DispatchMessageW(&msg);
|
DispatchMessageW(&msg);
|
||||||
|
|
||||||
|
value = Some(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn channel() -> &'static (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) {
|
std::thread::sleep(Duration::from_millis(sleep));
|
||||||
CHANNEL.get_or_init(crossbeam_channel::unbounded)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_tx() -> Sender<WindowManagerEvent> {
|
if !cb(value) {
|
||||||
channel().0.clone()
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn event_rx() -> Receiver<WindowManagerEvent> {
|
}
|
||||||
channel().1.clone()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use getset::Getters;
|
|||||||
use getset::MutGetters;
|
use getset::MutGetters;
|
||||||
use getset::Setters;
|
use getset::Setters;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use komorebi_core::Axis;
|
use komorebi_core::Axis;
|
||||||
@@ -26,31 +25,25 @@ 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)]
|
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema)]
|
||||||
#[derive(
|
|
||||||
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
|
|
||||||
)]
|
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
containers: Ring<Container>,
|
containers: Ring<Container>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
monocle_container: Option<Container>,
|
monocle_container: Option<Container>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing)]
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
monocle_container_restore_idx: Option<usize>,
|
monocle_container_restore_idx: Option<usize>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
maximized_window: Option<Window>,
|
maximized_window: Option<Window>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing)]
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
maximized_window_restore_idx: Option<usize>,
|
maximized_window_restore_idx: Option<usize>,
|
||||||
#[getset(get = "pub", get_mut = "pub")]
|
#[getset(get = "pub", get_mut = "pub")]
|
||||||
@@ -65,6 +58,7 @@ pub struct Workspace {
|
|||||||
workspace_padding: Option<i32>,
|
workspace_padding: Option<i32>,
|
||||||
#[getset(get_copy = "pub", set = "pub")]
|
#[getset(get_copy = "pub", set = "pub")]
|
||||||
container_padding: Option<i32>,
|
container_padding: Option<i32>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
latest_layout: Vec<Rect>,
|
latest_layout: Vec<Rect>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
@@ -144,66 +138,55 @@ impl Workspace {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide(&mut self, omit: Option<isize>) {
|
pub fn hide(&mut self) {
|
||||||
for window in self.floating_windows_mut().iter_mut().rev() {
|
for container in self.containers_mut() {
|
||||||
let mut should_hide = omit.is_none();
|
for window in container.windows_mut() {
|
||||||
|
|
||||||
if !should_hide {
|
|
||||||
if let Some(omit) = omit {
|
|
||||||
if omit != window.hwnd {
|
|
||||||
should_hide = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_hide {
|
|
||||||
window.hide();
|
window.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for container in self.containers_mut() {
|
|
||||||
container.hide(omit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(window) = self.maximized_window() {
|
if let Some(window) = self.maximized_window() {
|
||||||
window.hide();
|
window.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(container) = self.monocle_container_mut() {
|
if let Some(container) = self.monocle_container_mut() {
|
||||||
container.hide(omit)
|
for window in container.windows_mut() {
|
||||||
|
window.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for window in self.floating_windows() {
|
||||||
|
window.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||||
let idx = self.focused_container_idx();
|
let idx = self.focused_container_idx();
|
||||||
let mut to_focus = None;
|
let mut to_focus = None;
|
||||||
|
|
||||||
for (i, container) in self.containers_mut().iter_mut().enumerate() {
|
for (i, container) in self.containers_mut().iter_mut().enumerate() {
|
||||||
if let Some(window) = container.focused_window_mut() {
|
if let Some(window) = container.focused_window_mut() {
|
||||||
|
window.restore();
|
||||||
|
|
||||||
if idx == i {
|
if idx == i {
|
||||||
to_focus = Option::from(*window);
|
to_focus = Option::from(*window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for container in self.containers_mut() {
|
if let Some(window) = self.maximized_window() {
|
||||||
container.restore();
|
window.maximize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(container) = self.monocle_container_mut() {
|
if let Some(container) = self.monocle_container_mut() {
|
||||||
container.restore();
|
for window in container.windows_mut() {
|
||||||
|
window.restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for window in self.floating_windows() {
|
for window in self.floating_windows() {
|
||||||
window.restore();
|
window.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(container) = self.focused_container_mut() {
|
|
||||||
container.focus_window(container.focused_window_idx());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do this here to make sure that an error doesn't stop the restoration of other windows
|
// Do this here to make sure that an error doesn't stop the restoration of other windows
|
||||||
// Maximised windows should always be drawn at the top of the Z order
|
// Maximised windows should always be drawn at the top of the Z order
|
||||||
if let Some(window) = to_focus {
|
if let Some(window) = to_focus {
|
||||||
@@ -215,7 +198,12 @@ impl Workspace {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, work_area: &Rect, offset: Option<Rect>) -> Result<()> {
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
work_area: &Rect,
|
||||||
|
offset: Option<Rect>,
|
||||||
|
invisible_borders: &Rect,
|
||||||
|
) -> Result<()> {
|
||||||
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -234,7 +222,7 @@ impl Workspace {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
|
adjusted_work_area.add_padding(self.workspace_padding());
|
||||||
|
|
||||||
self.enforce_resize_constraints();
|
self.enforce_resize_constraints();
|
||||||
|
|
||||||
@@ -256,19 +244,11 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let managed_maximized_window = self.maximized_window().is_some();
|
|
||||||
|
|
||||||
if *self.tile() {
|
if *self.tile() {
|
||||||
if let Some(container) = self.monocle_container_mut() {
|
if let Some(container) = self.monocle_container_mut() {
|
||||||
if let Some(window) = container.focused_window_mut() {
|
if let Some(window) = container.focused_window_mut() {
|
||||||
adjusted_work_area.add_padding(container_padding.unwrap_or_default());
|
adjusted_work_area.add_padding(container_padding);
|
||||||
{
|
window.set_position(&adjusted_work_area, invisible_borders, true)?;
|
||||||
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
|
||||||
adjusted_work_area.add_padding(border_offset);
|
|
||||||
let width = BORDER_WIDTH.load(Ordering::SeqCst);
|
|
||||||
adjusted_work_area.add_padding(width);
|
|
||||||
}
|
|
||||||
window.set_position(&adjusted_work_area, true)?;
|
|
||||||
};
|
};
|
||||||
} else if let Some(window) = self.maximized_window_mut() {
|
} else if let Some(window) = self.maximized_window_mut() {
|
||||||
window.maximize();
|
window.maximize();
|
||||||
@@ -288,59 +268,16 @@ 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
|
let windows = self.visible_windows_mut();
|
||||||
.focused_container()
|
for (i, window) in windows.into_iter().enumerate() {
|
||||||
.ok_or_else(|| anyhow!("couldn't find a focused container"))?
|
if let (Some(window), Some(layout)) = (window, layouts.get(i)) {
|
||||||
.focused_window()
|
|
||||||
.ok_or_else(|| anyhow!("couldn't find a focused window"))?
|
|
||||||
.hwnd;
|
|
||||||
|
|
||||||
let container_padding = self.container_padding().unwrap_or(0);
|
|
||||||
let containers = self.containers_mut();
|
|
||||||
|
|
||||||
for (i, container) in containers.iter_mut().enumerate() {
|
|
||||||
let container_windows = container.windows().clone();
|
|
||||||
let container_topbar = container.stackbar().clone();
|
|
||||||
|
|
||||||
if let (Some(window), Some(layout)) =
|
|
||||||
(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()?;
|
||||||
} else if no_titlebar.contains(&window.exe()?) {
|
} else if no_titlebar.contains(&window.exe()?) {
|
||||||
window.add_title_bar()?;
|
window.add_title_bar()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a window has been unmaximized via toggle-maximize, this block
|
window.set_position(layout, invisible_borders, false)?;
|
||||||
// will make sure that it is unmaximized via restore_window
|
|
||||||
if window.is_maximized() && !managed_maximized_window {
|
|
||||||
WindowsApi::restore_window(window.hwnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rect = *layout;
|
|
||||||
{
|
|
||||||
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
|
||||||
rect.add_padding(border_offset);
|
|
||||||
|
|
||||||
let width = BORDER_WIDTH.load(Ordering::SeqCst);
|
|
||||||
rect.add_padding(width);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 total_height = tab_height + container_padding;
|
|
||||||
|
|
||||||
rect.top += total_height;
|
|
||||||
rect.bottom -= total_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.set_position(&rect, false)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,18 +352,7 @@ impl Workspace {
|
|||||||
.idx_for_window(hwnd)
|
.idx_for_window(hwnd)
|
||||||
.ok_or_else(|| anyhow!("there is no window"))?;
|
.ok_or_else(|| anyhow!("there is no window"))?;
|
||||||
|
|
||||||
let mut should_load = false;
|
|
||||||
|
|
||||||
if container.focused_window_idx() != window_idx {
|
|
||||||
should_load = true
|
|
||||||
}
|
|
||||||
|
|
||||||
container.focus_window(window_idx);
|
container.focus_window(window_idx);
|
||||||
|
|
||||||
if should_load {
|
|
||||||
container.load_focused_window();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.focus_container(container_idx);
|
self.focus_container(container_idx);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebic-no-console"
|
name = "komorebic-no-console"
|
||||||
version = "0.1.23-dev.0"
|
version = "0.1.19"
|
||||||
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,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "komorebic"
|
name = "komorebic"
|
||||||
version = "0.1.23-dev.0"
|
version = "0.1.20"
|
||||||
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"]
|
||||||
@@ -21,18 +21,15 @@ dunce = { workspace = true }
|
|||||||
fs-tail = "0.1"
|
fs-tail = "0.1"
|
||||||
heck = "0.4"
|
heck = "0.4"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
miette = { version = "7", features = ["fancy"] }
|
miette = { version = "5", features = ["fancy"] }
|
||||||
paste = "1"
|
paste = "1"
|
||||||
powershell_script = "1.0"
|
powershell_script = "1.0"
|
||||||
reqwest = { version = "0.11", features = ["blocking"] }
|
reqwest = { version = "0.11", features = ["blocking"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = "1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.30"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
uds_windows = "1"
|
uds_windows = "1"
|
||||||
which = "6"
|
which = "5"
|
||||||
windows = { workspace = true }
|
windows = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
reqwest = { version = "0.11", features = ["blocking"] }
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
if std::fs::metadata("applications.yaml").is_err() {
|
|
||||||
let applications_yaml = reqwest::blocking::get(
|
|
||||||
"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml"
|
|
||||||
).unwrap().text().unwrap();
|
|
||||||
std::fs::write("applications.yaml", applications_yaml).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,8 @@ use std::fs::File;
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::ErrorKind;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::net::Shutdown;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -21,7 +20,6 @@ use clap::ValueEnum;
|
|||||||
use color_eyre::eyre::anyhow;
|
use color_eyre::eyre::anyhow;
|
||||||
use color_eyre::eyre::bail;
|
use color_eyre::eyre::bail;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use dirs::data_local_dir;
|
|
||||||
use fs_tail::TailedFile;
|
use fs_tail::TailedFile;
|
||||||
use heck::ToKebabCase;
|
use heck::ToKebabCase;
|
||||||
use komorebi_core::resolve_home_path;
|
use komorebi_core::resolve_home_path;
|
||||||
@@ -31,6 +29,7 @@ use miette::Report;
|
|||||||
use miette::SourceOffset;
|
use miette::SourceOffset;
|
||||||
use miette::SourceSpan;
|
use miette::SourceSpan;
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
|
use uds_windows::UnixListener;
|
||||||
use uds_windows::UnixStream;
|
use uds_windows::UnixStream;
|
||||||
use which::which;
|
use which::which;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
@@ -114,7 +113,7 @@ trait AhkFunction {
|
|||||||
struct ConfigurationError {
|
struct ConfigurationError {
|
||||||
message: String,
|
message: String,
|
||||||
#[source_code]
|
#[source_code]
|
||||||
src: NamedSource<String>,
|
src: NamedSource,
|
||||||
#[label("This bit here")]
|
#[label("This bit here")]
|
||||||
bad_bit: SourceSpan,
|
bad_bit: SourceSpan,
|
||||||
}
|
}
|
||||||
@@ -721,25 +720,13 @@ struct LoadCustomLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct SubscribeSocket {
|
struct Subscribe {
|
||||||
/// Name of the socket to send event notifications to
|
|
||||||
socket: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
|
||||||
struct UnsubscribeSocket {
|
|
||||||
/// Name of the socket to stop sending event notifications to
|
|
||||||
socket: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
|
||||||
struct SubscribePipe {
|
|
||||||
/// Name of the pipe to send event notifications to (without "\\.\pipe\" prepended)
|
/// Name of the pipe to send event notifications to (without "\\.\pipe\" prepended)
|
||||||
named_pipe: String,
|
named_pipe: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct UnsubscribePipe {
|
struct Unsubscribe {
|
||||||
/// Name of the pipe to stop sending event notifications to (without "\\.\pipe\" prepended)
|
/// Name of the pipe to stop sending event notifications to (without "\\.\pipe\" prepended)
|
||||||
named_pipe: String,
|
named_pipe: String,
|
||||||
}
|
}
|
||||||
@@ -805,14 +792,8 @@ enum SubCommand {
|
|||||||
Start(Start),
|
Start(Start),
|
||||||
/// Stop the komorebi.exe process and restore all hidden windows
|
/// Stop the komorebi.exe process and restore all hidden windows
|
||||||
Stop(Stop),
|
Stop(Stop),
|
||||||
/// Check komorebi configuration and related files for common errors
|
/// Output various important komorebi-related environment values
|
||||||
Check,
|
Check,
|
||||||
/// Show the path to komorebi.json
|
|
||||||
#[clap(alias = "config")]
|
|
||||||
Configuration,
|
|
||||||
/// Show the path to whkdrc
|
|
||||||
#[clap(alias = "whkd")]
|
|
||||||
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 visible windows
|
/// Show a JSON representation of visible windows
|
||||||
@@ -820,20 +801,12 @@ enum SubCommand {
|
|||||||
/// Query the current window manager state
|
/// Query the current window manager state
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
Query(Query),
|
Query(Query),
|
||||||
/// Subscribe to komorebi events using a Unix Domain Socket
|
/// Subscribe to komorebi events
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
SubscribeSocket(SubscribeSocket),
|
Subscribe(Subscribe),
|
||||||
/// Unsubscribe from komorebi events
|
/// Unsubscribe from komorebi events
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
UnsubscribeSocket(UnsubscribeSocket),
|
Unsubscribe(Unsubscribe),
|
||||||
/// Subscribe to komorebi events using a Named Pipe
|
|
||||||
#[clap(arg_required_else_help = true)]
|
|
||||||
#[clap(alias = "subscribe")]
|
|
||||||
SubscribePipe(SubscribePipe),
|
|
||||||
/// Unsubscribe from komorebi events
|
|
||||||
#[clap(arg_required_else_help = true)]
|
|
||||||
#[clap(alias = "unsubscribe")]
|
|
||||||
UnsubscribePipe(UnsubscribePipe),
|
|
||||||
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||||
Log,
|
Log,
|
||||||
/// Quicksave the current resize layout dimensions
|
/// Quicksave the current resize layout dimensions
|
||||||
@@ -1077,9 +1050,8 @@ enum SubCommand {
|
|||||||
WatchConfiguration(WatchConfiguration),
|
WatchConfiguration(WatchConfiguration),
|
||||||
/// Signal that the final configuration option has been sent
|
/// Signal that the final configuration option has been sent
|
||||||
CompleteConfiguration,
|
CompleteConfiguration,
|
||||||
/// DEPRECATED since v0.1.22
|
/// Enable or disable a hack simulating ALT key presses to ensure focus changes succeed
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
#[clap(hide = true)]
|
|
||||||
AltFocusHack(AltFocusHack),
|
AltFocusHack(AltFocusHack),
|
||||||
/// Set the window behaviour when switching workspaces / cycling stacks
|
/// Set the window behaviour when switching workspaces / cycling stacks
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
@@ -1188,31 +1160,46 @@ enum SubCommand {
|
|||||||
pub fn send_message(bytes: &[u8]) -> Result<()> {
|
pub fn send_message(bytes: &[u8]) -> Result<()> {
|
||||||
let socket = DATA_DIR.join("komorebi.sock");
|
let socket = DATA_DIR.join("komorebi.sock");
|
||||||
|
|
||||||
let mut stream = UnixStream::connect(socket)?;
|
let mut connected = false;
|
||||||
stream.write_all(bytes)?;
|
while !connected {
|
||||||
Ok(stream.shutdown(Shutdown::Write)?)
|
if let Ok(mut stream) = UnixStream::connect(&socket) {
|
||||||
|
connected = true;
|
||||||
|
stream.write_all(bytes)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_query(bytes: &[u8]) -> Result<String> {
|
fn with_komorebic_socket<F: Fn() -> Result<()>>(f: F) -> Result<()> {
|
||||||
let socket = DATA_DIR.join("komorebi.sock");
|
let socket = DATA_DIR.join("komorebic.sock");
|
||||||
|
|
||||||
let mut stream = UnixStream::connect(socket)?;
|
match std::fs::remove_file(&socket) {
|
||||||
stream.write_all(bytes)?;
|
Ok(()) => {}
|
||||||
stream.shutdown(Shutdown::Write)?;
|
Err(error) => match error.kind() {
|
||||||
|
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
||||||
|
ErrorKind::NotFound => {}
|
||||||
|
_ => {
|
||||||
|
return Err(error.into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let mut reader = BufReader::new(stream);
|
f()?;
|
||||||
let mut response = String::new();
|
|
||||||
reader.read_to_string(&mut response)?;
|
|
||||||
|
|
||||||
Ok(response)
|
let listener = UnixListener::bind(socket)?;
|
||||||
}
|
match listener.accept() {
|
||||||
|
Ok(incoming) => {
|
||||||
|
let stream = BufReader::new(incoming.0);
|
||||||
|
for line in stream.lines() {
|
||||||
|
println!("{}", line?);
|
||||||
|
}
|
||||||
|
|
||||||
// print_query is a helper that queries komorebi and prints the response.
|
Ok(())
|
||||||
// panics on error.
|
}
|
||||||
fn print_query(bytes: &[u8]) {
|
Err(error) => {
|
||||||
match send_query(bytes) {
|
panic!("{}", error);
|
||||||
Ok(response) => println!("{response}"),
|
}
|
||||||
Err(error) => panic!("{}", error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1256,20 +1243,27 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubCommand::Quickstart => {
|
SubCommand::Quickstart => {
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
let home_dir = dirs::home_dir().expect("could not find home dir");
|
let home_dir = dirs::home_dir().expect("could not find home dir");
|
||||||
let config_dir = home_dir.join(".config");
|
let config_dir = home_dir.join(".config");
|
||||||
let local_appdata_dir = data_local_dir().expect("could not find localdata dir");
|
|
||||||
let data_dir = local_appdata_dir.join("komorebi");
|
|
||||||
std::fs::create_dir_all(&config_dir)?;
|
std::fs::create_dir_all(&config_dir)?;
|
||||||
std::fs::create_dir_all(data_dir)?;
|
|
||||||
|
|
||||||
let komorebi_json = include_str!("../../docs/komorebi.example.json");
|
let komorebi_json = reqwest::blocking::get(
|
||||||
|
format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/v{version}/komorebi.example.json")
|
||||||
|
)?.text()?;
|
||||||
std::fs::write(HOME_DIR.join("komorebi.json"), komorebi_json)?;
|
std::fs::write(HOME_DIR.join("komorebi.json"), komorebi_json)?;
|
||||||
|
|
||||||
let applications_yaml = include_str!("../applications.yaml");
|
let applications_yaml = reqwest::blocking::get(
|
||||||
|
"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml"
|
||||||
|
)?
|
||||||
|
.text()?;
|
||||||
std::fs::write(HOME_DIR.join("applications.yaml"), applications_yaml)?;
|
std::fs::write(HOME_DIR.join("applications.yaml"), applications_yaml)?;
|
||||||
|
|
||||||
let whkdrc = include_str!("../../docs/whkdrc.sample");
|
let whkdrc = reqwest::blocking::get(format!(
|
||||||
|
"https://raw.githubusercontent.com/LGUG2Z/komorebi/v{version}/whkdrc.sample"
|
||||||
|
))?
|
||||||
|
.text()?;
|
||||||
std::fs::write(config_dir.join("whkdrc"), whkdrc)?;
|
std::fs::write(config_dir.join("whkdrc"), whkdrc)?;
|
||||||
|
|
||||||
println!("Example ~/komorebi.json, ~/.config/whkdrc and latest ~/applications.yaml files downloaded");
|
println!("Example ~/komorebi.json, ~/.config/whkdrc and latest ~/applications.yaml files downloaded");
|
||||||
@@ -1357,7 +1351,7 @@ fn main() -> Result<()> {
|
|||||||
let diagnostic = ConfigurationError {
|
let diagnostic = ConfigurationError {
|
||||||
message: msgs[0].to_string(),
|
message: msgs[0].to_string(),
|
||||||
src: NamedSource::new("komorebi.json", config_source.clone()),
|
src: NamedSource::new("komorebi.json", config_source.clone()),
|
||||||
bad_bit: SourceSpan::new(offset, 2),
|
bad_bit: SourceSpan::new(offset, 2.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{:?}", Report::new(diagnostic));
|
println!("{:?}", Report::new(diagnostic));
|
||||||
@@ -1411,20 +1405,6 @@ fn main() -> Result<()> {
|
|||||||
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
|
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubCommand::Configuration => {
|
|
||||||
let static_config = HOME_DIR.join("komorebi.json");
|
|
||||||
|
|
||||||
if static_config.exists() {
|
|
||||||
println!("{}", static_config.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SubCommand::Whkdrc => {
|
|
||||||
let whkdrc = WHKD_CONFIG_DIR.join("whkdrc");
|
|
||||||
|
|
||||||
if whkdrc.exists() {
|
|
||||||
println!("{}", whkdrc.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SubCommand::AhkLibrary => {
|
SubCommand::AhkLibrary => {
|
||||||
let library = HOME_DIR.join("komorebic.lib.ahk");
|
let library = HOME_DIR.join("komorebic.lib.ahk");
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
@@ -1458,7 +1438,7 @@ fn main() -> Result<()> {
|
|||||||
let color_log = std::env::temp_dir().join("komorebi.log");
|
let color_log = std::env::temp_dir().join("komorebi.log");
|
||||||
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)]
|
||||||
for line in locked.lines().flatten() {
|
for line in locked.lines().flatten() {
|
||||||
println!("{line}");
|
println!("{line}");
|
||||||
}
|
}
|
||||||
@@ -1755,7 +1735,7 @@ fn main() -> Result<()> {
|
|||||||
let exec = if let Ok(output) = Command::new("where.exe").arg("komorebi.ps1").output() {
|
let exec = if let Ok(output) = Command::new("where.exe").arg("komorebi.ps1").output() {
|
||||||
let stdout = String::from_utf8(output.stdout)?;
|
let stdout = String::from_utf8(output.stdout)?;
|
||||||
match stdout.trim() {
|
match stdout.trim() {
|
||||||
"" => None,
|
stdout if stdout.is_empty() => None,
|
||||||
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
|
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
|
||||||
stdout if !stdout.contains("scoop") => None,
|
stdout if !stdout.contains("scoop") => None,
|
||||||
stdout => {
|
stdout => {
|
||||||
@@ -2016,13 +1996,15 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
SubCommand::State => {
|
SubCommand::State => {
|
||||||
print_query(&SocketMessage::State.as_bytes()?);
|
with_komorebic_socket(|| send_message(&SocketMessage::State.as_bytes()?))?;
|
||||||
}
|
}
|
||||||
SubCommand::VisibleWindows => {
|
SubCommand::VisibleWindows => {
|
||||||
print_query(&SocketMessage::VisibleWindows.as_bytes()?);
|
with_komorebic_socket(|| send_message(&SocketMessage::VisibleWindows.as_bytes()?))?;
|
||||||
}
|
}
|
||||||
SubCommand::Query(arg) => {
|
SubCommand::Query(arg) => {
|
||||||
print_query(&SocketMessage::Query(arg.state_query).as_bytes()?);
|
with_komorebic_socket(|| {
|
||||||
|
send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
SubCommand::RestoreWindows => {
|
SubCommand::RestoreWindows => {
|
||||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||||
@@ -2113,17 +2095,11 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
|||||||
SubCommand::LoadResize(arg) => {
|
SubCommand::LoadResize(arg) => {
|
||||||
send_message(&SocketMessage::Load(resolve_home_path(arg.path)?).as_bytes()?)?;
|
send_message(&SocketMessage::Load(resolve_home_path(arg.path)?).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::SubscribeSocket(arg) => {
|
SubCommand::Subscribe(arg) => {
|
||||||
send_message(&SocketMessage::AddSubscriberSocket(arg.socket).as_bytes()?)?;
|
send_message(&SocketMessage::AddSubscriber(arg.named_pipe).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::UnsubscribeSocket(arg) => {
|
SubCommand::Unsubscribe(arg) => {
|
||||||
send_message(&SocketMessage::RemoveSubscriberSocket(arg.socket).as_bytes()?)?;
|
send_message(&SocketMessage::RemoveSubscriber(arg.named_pipe).as_bytes()?)?;
|
||||||
}
|
|
||||||
SubCommand::SubscribePipe(arg) => {
|
|
||||||
send_message(&SocketMessage::AddSubscriberPipe(arg.named_pipe).as_bytes()?)?;
|
|
||||||
}
|
|
||||||
SubCommand::UnsubscribePipe(arg) => {
|
|
||||||
send_message(&SocketMessage::RemoveSubscriberPipe(arg.named_pipe).as_bytes()?)?;
|
|
||||||
}
|
}
|
||||||
SubCommand::ToggleMouseFollowsFocus => {
|
SubCommand::ToggleMouseFollowsFocus => {
|
||||||
send_message(&SocketMessage::ToggleMouseFollowsFocus.as_bytes()?)?;
|
send_message(&SocketMessage::ToggleMouseFollowsFocus.as_bytes()?)?;
|
||||||
@@ -2259,19 +2235,23 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
SubCommand::ApplicationSpecificConfigurationSchema => {
|
SubCommand::ApplicationSpecificConfigurationSchema => {
|
||||||
print_query(&SocketMessage::ApplicationSpecificConfigurationSchema.as_bytes()?);
|
with_komorebic_socket(|| {
|
||||||
|
send_message(&SocketMessage::ApplicationSpecificConfigurationSchema.as_bytes()?)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
SubCommand::NotificationSchema => {
|
SubCommand::NotificationSchema => {
|
||||||
print_query(&SocketMessage::NotificationSchema.as_bytes()?);
|
with_komorebic_socket(|| send_message(&SocketMessage::NotificationSchema.as_bytes()?))?;
|
||||||
}
|
}
|
||||||
SubCommand::SocketSchema => {
|
SubCommand::SocketSchema => {
|
||||||
print_query(&SocketMessage::SocketSchema.as_bytes()?);
|
with_komorebic_socket(|| send_message(&SocketMessage::SocketSchema.as_bytes()?))?;
|
||||||
}
|
}
|
||||||
SubCommand::StaticConfigSchema => {
|
SubCommand::StaticConfigSchema => {
|
||||||
print_query(&SocketMessage::StaticConfigSchema.as_bytes()?);
|
with_komorebic_socket(|| send_message(&SocketMessage::StaticConfigSchema.as_bytes()?))?;
|
||||||
}
|
}
|
||||||
SubCommand::GenerateStaticConfig => {
|
SubCommand::GenerateStaticConfig => {
|
||||||
print_query(&SocketMessage::GenerateStaticConfig.as_bytes()?);
|
with_komorebic_socket(|| {
|
||||||
|
send_message(&SocketMessage::GenerateStaticConfig.as_bytes()?)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
mkdocs.yml
11
mkdocs.yml
@@ -62,23 +62,17 @@ nav:
|
|||||||
- 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/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/whkdrc.md
|
|
||||||
- cli/state.md
|
- cli/state.md
|
||||||
- cli/visible-windows.md
|
- cli/visible-windows.md
|
||||||
- cli/query.md
|
- cli/query.md
|
||||||
- cli/subscribe-socket.md
|
- cli/subscribe.md
|
||||||
- cli/unsubscribe-socket.md
|
- cli/unsubscribe.md
|
||||||
- cli/subscribe-pipe.md
|
|
||||||
- cli/unsubscribe-pipe.md
|
|
||||||
- cli/log.md
|
- cli/log.md
|
||||||
- cli/quick-save-resize.md
|
- cli/quick-save-resize.md
|
||||||
- cli/quick-load-resize.md
|
- cli/quick-load-resize.md
|
||||||
@@ -166,6 +160,7 @@ nav:
|
|||||||
- cli/reload-configuration.md
|
- cli/reload-configuration.md
|
||||||
- cli/watch-configuration.md
|
- cli/watch-configuration.md
|
||||||
- cli/complete-configuration.md
|
- cli/complete-configuration.md
|
||||||
|
- cli/alt-focus-hack.md
|
||||||
- cli/window-hiding-behaviour.md
|
- cli/window-hiding-behaviour.md
|
||||||
- cli/cross-monitor-move-behaviour.md
|
- cli/cross-monitor-move-behaviour.md
|
||||||
- cli/toggle-cross-monitor-move-behaviour.md
|
- cli/toggle-cross-monitor-move-behaviour.md
|
||||||
|
|||||||
@@ -44,8 +44,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ApplicationOptions": {
|
"ApplicationOptions": {
|
||||||
|
|||||||
360
schema.json
360
schema.json
@@ -19,129 +19,107 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"monocle": {
|
"monocle": {
|
||||||
"description": "Border colour when the container is in monocle mode",
|
"description": "Border colour when the container is in monocle mode",
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"description": "Colour represented as RGB",
|
"b",
|
||||||
"type": "object",
|
"g",
|
||||||
"required": [
|
"r"
|
||||||
"b",
|
],
|
||||||
"g",
|
"properties": {
|
||||||
"r"
|
"b": {
|
||||||
],
|
"description": "Blue",
|
||||||
"properties": {
|
"type": "integer",
|
||||||
"b": {
|
"format": "uint32",
|
||||||
"description": "Blue",
|
"minimum": 0.0
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"g": {
|
||||||
"description": "Colour represented as Hex",
|
"description": "Green",
|
||||||
"type": "string"
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"r": {
|
||||||
|
"description": "Red",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"single": {
|
"single": {
|
||||||
"description": "Border colour when the container contains a single window",
|
"description": "Border colour when the container contains a single window",
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"description": "Colour represented as RGB",
|
"b",
|
||||||
"type": "object",
|
"g",
|
||||||
"required": [
|
"r"
|
||||||
"b",
|
],
|
||||||
"g",
|
"properties": {
|
||||||
"r"
|
"b": {
|
||||||
],
|
"description": "Blue",
|
||||||
"properties": {
|
"type": "integer",
|
||||||
"b": {
|
"format": "uint32",
|
||||||
"description": "Blue",
|
"minimum": 0.0
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"g": {
|
||||||
"description": "Colour represented as Hex",
|
"description": "Green",
|
||||||
"type": "string"
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"r": {
|
||||||
|
"description": "Red",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"stack": {
|
"stack": {
|
||||||
"description": "Border colour when the container contains multiple windows",
|
"description": "Border colour when the container contains multiple windows",
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{
|
"required": [
|
||||||
"description": "Colour represented as RGB",
|
"b",
|
||||||
"type": "object",
|
"g",
|
||||||
"required": [
|
"r"
|
||||||
"b",
|
],
|
||||||
"g",
|
"properties": {
|
||||||
"r"
|
"b": {
|
||||||
],
|
"description": "Blue",
|
||||||
"properties": {
|
"type": "integer",
|
||||||
"b": {
|
"format": "uint32",
|
||||||
"description": "Blue",
|
"minimum": 0.0
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"g": {
|
||||||
"description": "Colour represented as Hex",
|
"description": "Green",
|
||||||
"type": "string"
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"r": {
|
||||||
|
"description": "Red",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"active_window_border_offset": {
|
||||||
|
"description": "Offset of the active window border (default: None)",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"active_window_border_width": {
|
||||||
|
"description": "Width of the active window border (default: 20)",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
"app_specific_configuration_path": {
|
"app_specific_configuration_path": {
|
||||||
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"border_offset": {
|
|
||||||
"description": "Offset of the window border (default: -1)",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"border_overflow_applications": {
|
"border_overflow_applications": {
|
||||||
"description": "Identify border overflow applications",
|
"description": "Identify border overflow applications",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -160,8 +138,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -178,11 +155,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"border_width": {
|
|
||||||
"description": "Width of the window border (default: 8)",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"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": [
|
||||||
@@ -237,8 +209,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -307,7 +278,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"invisible_borders": {
|
"invisible_borders": {
|
||||||
"description": "DEPRECATED from v0.1.22: no longer required",
|
"description": "Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"bottom",
|
"bottom",
|
||||||
@@ -356,8 +327,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -392,8 +362,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -529,8 +498,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -556,8 +524,7 @@
|
|||||||
"Rows",
|
"Rows",
|
||||||
"VerticalStack",
|
"VerticalStack",
|
||||||
"HorizontalStack",
|
"HorizontalStack",
|
||||||
"UltrawideVerticalStack",
|
"UltrawideVerticalStack"
|
||||||
"Grid"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"layout_rules": {
|
"layout_rules": {
|
||||||
@@ -571,8 +538,7 @@
|
|||||||
"Rows",
|
"Rows",
|
||||||
"VerticalStack",
|
"VerticalStack",
|
||||||
"HorizontalStack",
|
"HorizontalStack",
|
||||||
"UltrawideVerticalStack",
|
"UltrawideVerticalStack"
|
||||||
"Grid"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -603,8 +569,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -649,8 +614,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
@@ -672,143 +636,6 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
},
|
},
|
||||||
"stackbar": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"height": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"mode": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Always",
|
|
||||||
"Never",
|
|
||||||
"OnStack"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"tabs": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"background": {
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"focused_text": {
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unfocused_text": {
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"width": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tray_and_multi_window_applications": {
|
"tray_and_multi_window_applications": {
|
||||||
"description": "Identify tray and multi-window applications",
|
"description": "Identify tray and multi-window applications",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -827,8 +654,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Exe",
|
"Exe",
|
||||||
"Class",
|
"Class",
|
||||||
"Title",
|
"Title"
|
||||||
"Path"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"matching_strategy": {
|
"matching_strategy": {
|
||||||
|
|||||||
63
whkdrc.sample
Normal file
63
whkdrc.sample
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
.shell powershell
|
||||||
|
|
||||||
|
# Reload whkd configuration
|
||||||
|
# alt + o : taskkill /f /im whkd.exe && start /b whkd # if shell is cmd
|
||||||
|
alt + o : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell
|
||||||
|
alt + shift + o : komorebic reload-configuration
|
||||||
|
|
||||||
|
# App shortcuts - these require shell to be pwsh / powershell
|
||||||
|
# The apps will be focused if open, or launched if not open
|
||||||
|
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
||||||
|
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
|
||||||
|
|
||||||
|
# Focus windows
|
||||||
|
alt + h : komorebic focus left
|
||||||
|
alt + j : komorebic focus down
|
||||||
|
alt + k : komorebic focus up
|
||||||
|
alt + l : komorebic focus right
|
||||||
|
alt + shift + oem_4 : komorebic cycle-focus previous # oem_4 is [
|
||||||
|
alt + shift + oem_6 : komorebic cycle-focus next # oem_6 is ]
|
||||||
|
|
||||||
|
# Move windows
|
||||||
|
alt + shift + h : komorebic move left
|
||||||
|
alt + shift + j : komorebic move down
|
||||||
|
alt + shift + k : komorebic move up
|
||||||
|
alt + shift + l : komorebic move right
|
||||||
|
alt + shift + return : komorebic promote
|
||||||
|
|
||||||
|
# Stack windows
|
||||||
|
alt + left : komorebic stack left
|
||||||
|
alt + down : komorebic stack down
|
||||||
|
alt + up : komorebic stack up
|
||||||
|
alt + right : komorebic stack right
|
||||||
|
alt + oem_1 : komorebic unstack # oem_1 is ;
|
||||||
|
alt + oem_4 : komorebic cycle-stack previous # oem_4 is [
|
||||||
|
alt + oem_6 : komorebic cycle-stack next # oem_6 is ]
|
||||||
|
|
||||||
|
# Resize
|
||||||
|
alt + oem_plus : komorebic resize-axis horizontal increase
|
||||||
|
alt + oem_minus : komorebic resize-axis horizontal decrease
|
||||||
|
alt + shift + oem_plus : komorebic resize-axis vertical increase
|
||||||
|
alt + shift + oem_minus : komorebic resize-axis vertical decrease
|
||||||
|
|
||||||
|
# Manipulate windows
|
||||||
|
alt + t : komorebic toggle-float
|
||||||
|
alt + shift + f : komorebic toggle-monocle
|
||||||
|
|
||||||
|
# Window manager options
|
||||||
|
alt + shift + r : komorebic retile
|
||||||
|
alt + p : komorebic toggle-pause
|
||||||
|
|
||||||
|
# Layouts
|
||||||
|
alt + x : komorebic flip-layout horizontal
|
||||||
|
alt + y : komorebic flip-layout vertical
|
||||||
|
|
||||||
|
# Workspaces
|
||||||
|
alt + 1 : komorebic focus-workspace 0
|
||||||
|
alt + 2 : komorebic focus-workspace 1
|
||||||
|
alt + 3 : komorebic focus-workspace 2
|
||||||
|
|
||||||
|
# Move windows across workspaces
|
||||||
|
alt + shift + 1 : komorebic move-to-workspace 0
|
||||||
|
alt + shift + 2 : komorebic move-to-workspace 1
|
||||||
|
alt + shift + 3 : komorebic move-to-workspace 2
|
||||||
Reference in New Issue
Block a user