mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-02 17:00:03 +01:00
Compare commits
13 Commits
feature/cm
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd045a6cbc | ||
|
|
a29ab4cfb3 | ||
|
|
27cd1736aa | ||
|
|
d2470b1f08 | ||
|
|
835472d739 | ||
|
|
bceb28de37 | ||
|
|
fff7b5c147 | ||
|
|
1e63947ae3 | ||
|
|
d5f4f916be | ||
|
|
77fc3973b6 | ||
|
|
29b1794409 | ||
|
|
1420334c94 | ||
|
|
82aa2edf8f |
82
Cargo.lock
generated
82
Cargo.lock
generated
@@ -26,6 +26,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.13"
|
version = "0.6.13"
|
||||||
@@ -167,6 +182,20 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.4"
|
version = "4.5.4"
|
||||||
@@ -723,6 +752,29 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -781,6 +833,15 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@@ -830,6 +891,7 @@ dependencies = [
|
|||||||
"uds_windows",
|
"uds_windows",
|
||||||
"which",
|
"which",
|
||||||
"widestring",
|
"widestring",
|
||||||
|
"win32-display-data",
|
||||||
"windows 0.54.0",
|
"windows 0.54.0",
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
@@ -867,6 +929,7 @@ dependencies = [
|
|||||||
name = "komorebic"
|
name = "komorebic"
|
||||||
version = "0.1.26-dev.0"
|
version = "0.1.26-dev.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"derive-ahk",
|
"derive-ahk",
|
||||||
@@ -1157,6 +1220,15 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -2403,6 +2475,16 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
|
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-display-data"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/LGUG2Z/win32-display-data#2c47b9f1ca1f359ba2481d0b6ea8667ccd9d075c"
|
||||||
|
dependencies = [
|
||||||
|
"itertools",
|
||||||
|
"thiserror",
|
||||||
|
"windows 0.54.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
# active-window-border-colour
|
|
||||||
|
|
||||||
```
|
|
||||||
Set the colour for the active window border
|
|
||||||
|
|
||||||
Usage: komorebic.exe active-window-border-colour [OPTIONS] <R> <G> <B>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<R>
|
|
||||||
Red
|
|
||||||
|
|
||||||
<G>
|
|
||||||
Green
|
|
||||||
|
|
||||||
<B>
|
|
||||||
Blue
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-w, --window-kind <WINDOW_KIND>
|
|
||||||
[default: single]
|
|
||||||
[possible values: single, stack, monocle]
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# active-window-border-offset
|
|
||||||
|
|
||||||
```
|
|
||||||
Set the offset for the active window border
|
|
||||||
|
|
||||||
Usage: komorebic.exe active-window-border-offset <OFFSET>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<OFFSET>
|
|
||||||
Desired offset of the active window border
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# active-window-border-width
|
|
||||||
|
|
||||||
```
|
|
||||||
Set the width for the active window border
|
|
||||||
|
|
||||||
Usage: komorebic.exe active-window-border-width <WIDTH>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<WIDTH>
|
|
||||||
Desired width of the active window border
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help
|
|
||||||
Print help
|
|
||||||
|
|
||||||
```
|
|
||||||
26
docs/cli/border-colour.md
Normal file
26
docs/cli/border-colour.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# border-colour
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the colour for a window border kind
|
||||||
|
|
||||||
|
Usage: komorebic.exe border-colour [OPTIONS] <R> <G> <B>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<R>
|
||||||
|
Red
|
||||||
|
|
||||||
|
<G>
|
||||||
|
Green
|
||||||
|
|
||||||
|
<B>
|
||||||
|
Blue
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-w, --window-kind <WINDOW_KIND>
|
||||||
|
[default: single]
|
||||||
|
[possible values: single, stack, monocle, unfocused]
|
||||||
|
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
16
docs/cli/border-offset.md
Normal file
16
docs/cli/border-offset.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# border-offset
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the border offset
|
||||||
|
|
||||||
|
Usage: komorebic.exe border-offset <OFFSET>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<OFFSET>
|
||||||
|
Desired offset of the window border
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
16
docs/cli/border-width.md
Normal file
16
docs/cli/border-width.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# border-width
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the border width
|
||||||
|
|
||||||
|
Usage: komorebic.exe border-width <WIDTH>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<WIDTH>
|
||||||
|
Desired width of the window border
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# active-window-border
|
# border
|
||||||
|
|
||||||
```
|
```
|
||||||
Enable or disable the active window border
|
Enable or disable borders
|
||||||
|
|
||||||
Usage: komorebic.exe active-window-border <BOOLEAN_STATE>
|
Usage: komorebic.exe border <BOOLEAN_STATE>
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<BOOLEAN_STATE>
|
<BOOLEAN_STATE>
|
||||||
@@ -10,6 +10,7 @@ Arguments:
|
|||||||
Possible values:
|
Possible values:
|
||||||
- swap: Swap the window container with the window container at the edge of the adjacent monitor
|
- swap: Swap the window container with the window container at the edge of the adjacent monitor
|
||||||
- insert: Insert the window container into the focused workspace on the adjacent monitor
|
- insert: Insert the window container into the focused workspace on the adjacent monitor
|
||||||
|
- no-op: Do nothing if trying to move a window container in the direction of an adjacent monitor
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help
|
-h, --help
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
# Active Window Border
|
|
||||||
|
|
||||||
If you would like to add a visual border around the currently focused window,
|
|
||||||
ensure the following options are defined in the `komorebi.json` configuration
|
|
||||||
file.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"active_window_border": true,
|
|
||||||
"active_window_border_style": "Rounded",
|
|
||||||
"active_window_border_colours": {
|
|
||||||
"single": {
|
|
||||||
"r": 66,
|
|
||||||
"g": 165,
|
|
||||||
"b": 245
|
|
||||||
},
|
|
||||||
"stack": {
|
|
||||||
"r": 256,
|
|
||||||
"g": 165,
|
|
||||||
"b": 66
|
|
||||||
},
|
|
||||||
"monocle": {
|
|
||||||
"r": 255,
|
|
||||||
"g": 51,
|
|
||||||
"b": 153
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
It is important to note that the active window border will only apply to
|
|
||||||
windows managed by `komorebi`.
|
|
||||||
|
|
||||||
This feature is not considered stable, and you may encounter visual artifacts
|
|
||||||
from time to time.
|
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=7_9D22t7KK4)
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
# AutoHotKey
|
# AutoHotKey
|
||||||
|
|
||||||
<!-- TODO: Update this completely -->
|
|
||||||
|
|
||||||
If you would like to use Autohotkey, please make sure you have AutoHotKey v2
|
If you would like to use Autohotkey, please make sure you have AutoHotKey v2
|
||||||
installed.
|
installed.
|
||||||
|
|
||||||
@@ -9,19 +7,16 @@ Generally, users who opt for AHK will have specific needs that can only be
|
|||||||
addressed by the advanced functionality of AHK, and so they are assumed to be
|
addressed by the advanced functionality of AHK, and so they are assumed to be
|
||||||
able to craft their own configuration files.
|
able to craft their own configuration files.
|
||||||
|
|
||||||
If you would like to try out AHK, a simple sample configuration powered by
|
If you would like to try out AHK, here is a simple sample configuration which
|
||||||
`komorebic.lib.ahk` is provided as a starting point. This sample configuration
|
largely matches the `whkdrc` sample configuration.
|
||||||
does not take into account the use of a static configuration file; if you
|
|
||||||
choose to use a static configuration file alongside AHK, you can remove all the
|
|
||||||
configuration options from your `komorebi.ahk` and use it solely to handle
|
|
||||||
hotkey bindings.
|
|
||||||
|
|
||||||
|
```
|
||||||
```powershell
|
{% include "../komorebi.ahk" %}
|
||||||
# save the latest generated komorebic library to ~/komorebic.lib.ahk
|
|
||||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/komorebic.lib.ahk -OutFile $Env:USERPROFILE\komorebic.lib.ahk
|
|
||||||
|
|
||||||
# save the sample komorebi configuration file to ~/komorebi.ahk
|
|
||||||
iwr https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/komorebi.sample.ahk -OutFile $Env:USERPROFILE\komorebi.ahk
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default, the `komorebi.ahk` file should be located in the `$Env:USERPROFILE`
|
||||||
|
directory, however, if `$Env:KOMOREBI_CONFIG_HOME` is set, it should be located
|
||||||
|
there.
|
||||||
|
|
||||||
|
Once the file is in place, you can stop komorebi and whkd by running `komorebic stop --whkd`,
|
||||||
|
and then start komorebi with Autohotkey by running `komorebic start --ahk`.
|
||||||
28
docs/common-workflows/borders.md
Normal file
28
docs/common-workflows/borders.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Borders
|
||||||
|
|
||||||
|
If you would like to add a visual border around both the currently focused window
|
||||||
|
and unfocused windows ensure the following options are defined in the `komorebi.json`
|
||||||
|
configuration file.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"border": true,
|
||||||
|
"border_width": 8,
|
||||||
|
"border_offset": -1,
|
||||||
|
"border_style": "System",
|
||||||
|
"border_colours": {
|
||||||
|
"single": "#42a5f5",
|
||||||
|
"stack": "#00a542",
|
||||||
|
"monocle": "#ff3399",
|
||||||
|
"unfocused": "#808080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is important to note that borders will only apply to windows managed by `komorebi`.
|
||||||
|
|
||||||
|
This feature is not considered stable, and you may encounter visual artifacts
|
||||||
|
from time to time.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=7_9D22t7KK4)
|
||||||
@@ -30,10 +30,10 @@ to manipulate the window manager, you use
|
|||||||
[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also built
|
[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also built
|
||||||
it from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.
|
it from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.
|
||||||
|
|
||||||
- [Scoop](#scoop)
|
- [Scoop](#scoop)
|
||||||
- [WinGet](#winget)
|
- [WinGet](#winget)
|
||||||
- [Building from source](#building-from-source)
|
- [Building from source](#building-from-source)
|
||||||
- [Offline](#offline)
|
- [Offline](#offline)
|
||||||
|
|
||||||
## Long path support
|
## Long path support
|
||||||
|
|
||||||
@@ -45,6 +45,12 @@ running the following command in an Administrator Terminal before installing
|
|||||||
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disabling Unnecessary System Animations
|
||||||
|
|
||||||
|
It is highly recommended that you enable the "Turn off all unnecessary animations (when possible)" option in
|
||||||
|
"Control Panel > Ease of Access > Ease of Access Centre / Make the computer easier to see" for the best performance with
|
||||||
|
komorebi.
|
||||||
|
|
||||||
## Scoop
|
## Scoop
|
||||||
|
|
||||||
Make sure you have installed [`scoop`](https://scoop.sh) and verified that
|
Make sure you have installed [`scoop`](https://scoop.sh) and verified that
|
||||||
|
|||||||
@@ -5,17 +5,18 @@
|
|||||||
"cross_monitor_move_behaviour": "Insert",
|
"cross_monitor_move_behaviour": "Insert",
|
||||||
"default_workspace_padding": 20,
|
"default_workspace_padding": 20,
|
||||||
"default_container_padding": 20,
|
"default_container_padding": 20,
|
||||||
|
"border": true,
|
||||||
"border_width": 8,
|
"border_width": 8,
|
||||||
"border_offset": -1,
|
"border_offset": -1,
|
||||||
"active_window_border": false,
|
"border_colours": {
|
||||||
"active_window_border_colours": {
|
|
||||||
"single": "#42a5f5",
|
"single": "#42a5f5",
|
||||||
"stack": "#00a542",
|
"stack": "#00a542",
|
||||||
"monocle": "#ff3399"
|
"monocle": "#ff3399",
|
||||||
|
"unfocused": "#808080"
|
||||||
},
|
},
|
||||||
"stackbar": {
|
"stackbar": {
|
||||||
"height": 40,
|
"height": 40,
|
||||||
"mode": "Never",
|
"mode": "OnStack",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"focused_text": "#00a542",
|
"focused_text": "#00a542",
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ alt + shift + o : komorebic reload-configuration
|
|||||||
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
# alt + f : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }
|
||||||
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
|
# alt + b : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }
|
||||||
|
|
||||||
|
alt + q : komorebic close
|
||||||
|
alt + m : komorebic minimize
|
||||||
|
|
||||||
# Focus windows
|
# Focus windows
|
||||||
alt + h : komorebic focus left
|
alt + h : komorebic focus left
|
||||||
alt + j : komorebic focus down
|
alt + j : komorebic focus down
|
||||||
@@ -56,8 +59,18 @@ alt + y : komorebic flip-layout vertical
|
|||||||
alt + 1 : komorebic focus-workspace 0
|
alt + 1 : komorebic focus-workspace 0
|
||||||
alt + 2 : komorebic focus-workspace 1
|
alt + 2 : komorebic focus-workspace 1
|
||||||
alt + 3 : komorebic focus-workspace 2
|
alt + 3 : komorebic focus-workspace 2
|
||||||
|
alt + 4 : komorebic focus-workspace 3
|
||||||
|
alt + 5 : komorebic focus-workspace 4
|
||||||
|
alt + 6 : komorebic focus-workspace 5
|
||||||
|
alt + 7 : komorebic focus-workspace 6
|
||||||
|
alt + 8 : komorebic focus-workspace 7
|
||||||
|
|
||||||
# Move windows across workspaces
|
# Move windows across workspaces
|
||||||
alt + shift + 1 : komorebic move-to-workspace 0
|
alt + shift + 1 : komorebic move-to-workspace 0
|
||||||
alt + shift + 2 : komorebic move-to-workspace 1
|
alt + shift + 2 : komorebic move-to-workspace 1
|
||||||
alt + shift + 3 : komorebic move-to-workspace 2
|
alt + shift + 3 : komorebic move-to-workspace 2
|
||||||
|
alt + shift + 4 : komorebic move-to-workspace 3
|
||||||
|
alt + shift + 5 : komorebic move-to-workspace 4
|
||||||
|
alt + shift + 6 : komorebic move-to-workspace 5
|
||||||
|
alt + shift + 7 : komorebic move-to-workspace 6
|
||||||
|
alt + shift + 8 : komorebic move-to-workspace 7
|
||||||
|
|||||||
6
justfile
6
justfile
@@ -16,12 +16,6 @@ fmt:
|
|||||||
install-target target:
|
install-target target:
|
||||||
cargo +stable install --path {{ target }} --locked
|
cargo +stable install --path {{ target }} --locked
|
||||||
|
|
||||||
prepare:
|
|
||||||
komorebic ahk-asc '~/.config/komorebi/applications.yaml'
|
|
||||||
komorebic pwsh-asc '~/.config/komorebi/applications.yaml'
|
|
||||||
cat '~/.config/komorebi/komorebi.generated.ps1' >komorebi.generated.ps1
|
|
||||||
cat '~/.config/komorebi/komorebi.generated.ahk' >komorebi.generated.ahk
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
just install-target komorebic
|
just install-target komorebic
|
||||||
just install-target komorebic-no-console
|
just install-target komorebic-no-console
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub use komorebi::ring::Ring;
|
|||||||
pub use komorebi::window::Window;
|
pub use komorebi::window::Window;
|
||||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||||
pub use komorebi::workspace::Workspace;
|
pub use komorebi::workspace::Workspace;
|
||||||
pub use komorebi::ActiveWindowBorderColours;
|
pub use komorebi::BorderColours;
|
||||||
pub use komorebi::GlobalState;
|
pub use komorebi::GlobalState;
|
||||||
pub use komorebi::Notification;
|
pub use komorebi::Notification;
|
||||||
pub use komorebi::NotificationEvent;
|
pub use komorebi::NotificationEvent;
|
||||||
@@ -18,9 +18,9 @@ pub use komorebi::StackbarConfig;
|
|||||||
pub use komorebi::State;
|
pub use komorebi::State;
|
||||||
pub use komorebi::StaticConfig;
|
pub use komorebi::StaticConfig;
|
||||||
pub use komorebi::TabsConfig;
|
pub use komorebi::TabsConfig;
|
||||||
pub use komorebi_core::ActiveWindowBorderStyle;
|
|
||||||
pub use komorebi_core::Arrangement;
|
pub use komorebi_core::Arrangement;
|
||||||
pub use komorebi_core::Axis;
|
pub use komorebi_core::Axis;
|
||||||
|
pub use komorebi_core::BorderStyle;
|
||||||
pub use komorebi_core::CustomLayout;
|
pub use komorebi_core::CustomLayout;
|
||||||
pub use komorebi_core::CycleDirection;
|
pub use komorebi_core::CycleDirection;
|
||||||
pub use komorebi_core::DefaultLayout;
|
pub use komorebi_core::DefaultLayout;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub enum SocketMessage {
|
|||||||
SendContainerToMonitorWorkspaceNumber(usize, usize),
|
SendContainerToMonitorWorkspaceNumber(usize, usize),
|
||||||
MoveContainerToMonitorWorkspaceNumber(usize, usize),
|
MoveContainerToMonitorWorkspaceNumber(usize, usize),
|
||||||
SendContainerToNamedWorkspace(String),
|
SendContainerToNamedWorkspace(String),
|
||||||
|
CycleMoveWorkspaceToMonitor(CycleDirection),
|
||||||
MoveWorkspaceToMonitorNumber(usize),
|
MoveWorkspaceToMonitorNumber(usize),
|
||||||
SwapWorkspacesToMonitorNumber(usize),
|
SwapWorkspacesToMonitorNumber(usize),
|
||||||
ForceFocus,
|
ForceFocus,
|
||||||
@@ -66,6 +67,7 @@ pub enum SocketMessage {
|
|||||||
Minimize,
|
Minimize,
|
||||||
Promote,
|
Promote,
|
||||||
PromoteFocus,
|
PromoteFocus,
|
||||||
|
PromoteWindow(OperationDirection),
|
||||||
ToggleFloat,
|
ToggleFloat,
|
||||||
ToggleMonocle,
|
ToggleMonocle,
|
||||||
ToggleMaximize,
|
ToggleMaximize,
|
||||||
@@ -130,13 +132,17 @@ pub enum SocketMessage {
|
|||||||
WatchConfiguration(bool),
|
WatchConfiguration(bool),
|
||||||
CompleteConfiguration,
|
CompleteConfiguration,
|
||||||
AltFocusHack(bool),
|
AltFocusHack(bool),
|
||||||
ActiveWindowBorder(bool),
|
#[serde(alias = "ActiveWindowBorder")]
|
||||||
ActiveWindowBorderColour(WindowKind, u32, u32, u32),
|
Border(bool),
|
||||||
ActiveWindowBorderStyle(ActiveWindowBorderStyle),
|
#[serde(alias = "ActiveWindowBorderColour")]
|
||||||
|
BorderColour(WindowKind, u32, u32, u32),
|
||||||
|
#[serde(alias = "ActiveWindowBorderStyle")]
|
||||||
|
BorderStyle(BorderStyle),
|
||||||
BorderWidth(i32),
|
BorderWidth(i32),
|
||||||
BorderOffset(i32),
|
BorderOffset(i32),
|
||||||
InvisibleBorders(Rect),
|
InvisibleBorders(Rect),
|
||||||
StackbarMode(StackbarMode),
|
StackbarMode(StackbarMode),
|
||||||
|
StackbarLabel(StackbarLabel),
|
||||||
StackbarFocusedTextColour(u32, u32, u32),
|
StackbarFocusedTextColour(u32, u32, u32),
|
||||||
StackbarUnfocusedTextColour(u32, u32, u32),
|
StackbarUnfocusedTextColour(u32, u32, u32),
|
||||||
StackbarBackgroundColour(u32, u32, u32),
|
StackbarBackgroundColour(u32, u32, u32),
|
||||||
@@ -198,10 +204,19 @@ pub enum StackbarMode {
|
|||||||
OnStack,
|
OnStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
||||||
|
)]
|
||||||
|
pub enum StackbarLabel {
|
||||||
|
#[default]
|
||||||
|
Process,
|
||||||
|
Title,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
||||||
)]
|
)]
|
||||||
pub enum ActiveWindowBorderStyle {
|
pub enum BorderStyle {
|
||||||
#[default]
|
#[default]
|
||||||
/// Use the system border style
|
/// Use the system border style
|
||||||
System,
|
System,
|
||||||
|
|||||||
@@ -48,5 +48,7 @@ windows-interface = { workspace = true }
|
|||||||
winput = "0.2"
|
winput = "0.2"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
|
||||||
|
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
deadlock_detection = []
|
deadlock_detection = []
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::border_manager::Z_ORDER;
|
|||||||
use crate::WindowsApi;
|
use crate::WindowsApi;
|
||||||
use crate::WINDOWS_11;
|
use crate::WINDOWS_11;
|
||||||
|
|
||||||
use komorebi_core::ActiveWindowBorderStyle;
|
use komorebi_core::BorderStyle;
|
||||||
use komorebi_core::Rect;
|
use komorebi_core::Rect;
|
||||||
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
@@ -195,17 +195,17 @@ impl Border {
|
|||||||
// wrong size. In the future we should read the DWM properties
|
// wrong size. In the future we should read the DWM properties
|
||||||
// of windows and attempt to match appropriately.
|
// of windows and attempt to match appropriately.
|
||||||
match *STYLE.lock() {
|
match *STYLE.lock() {
|
||||||
ActiveWindowBorderStyle::System => {
|
BorderStyle::System => {
|
||||||
if *WINDOWS_11 {
|
if *WINDOWS_11 {
|
||||||
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
|
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
|
||||||
} else {
|
} else {
|
||||||
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveWindowBorderStyle::Rounded => {
|
BorderStyle::Rounded => {
|
||||||
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
|
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
|
||||||
}
|
}
|
||||||
ActiveWindowBorderStyle::Square => {
|
BorderStyle::Square => {
|
||||||
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ mod border;
|
|||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossbeam_utils::atomic::AtomicConsume;
|
use crossbeam_utils::atomic::AtomicConsume;
|
||||||
use komorebi_core::ActiveWindowBorderStyle;
|
use komorebi_core::BorderStyle;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
@@ -39,8 +39,7 @@ pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true);
|
|||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref Z_ORDER: Arc<Mutex<ZOrder>> = Arc::new(Mutex::new(ZOrder::Bottom));
|
pub static ref Z_ORDER: Arc<Mutex<ZOrder>> = Arc::new(Mutex::new(ZOrder::Bottom));
|
||||||
pub static ref STYLE: Arc<Mutex<ActiveWindowBorderStyle>> =
|
pub static ref STYLE: Arc<Mutex<BorderStyle>> = Arc::new(Mutex::new(BorderStyle::System));
|
||||||
Arc::new(Mutex::new(ActiveWindowBorderStyle::System));
|
|
||||||
pub static ref FOCUSED: AtomicU32 =
|
pub static ref FOCUSED: AtomicU32 =
|
||||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
|
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
|
||||||
pub static ref UNFOCUSED: AtomicU32 =
|
pub static ref UNFOCUSED: AtomicU32 =
|
||||||
@@ -160,7 +159,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
|
|
||||||
let focused_monitor_idx = state.focused_monitor_idx();
|
let focused_monitor_idx = state.focused_monitor_idx();
|
||||||
|
|
||||||
for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
|
'monitors: for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
|
||||||
// Only operate on the focused workspace of each monitor
|
// Only operate on the focused workspace of each monitor
|
||||||
if let Some(ws) = m.focused_workspace() {
|
if let Some(ws) = m.focused_workspace() {
|
||||||
// Workspaces with tiling disabled don't have borders
|
// Workspaces with tiling disabled don't have borders
|
||||||
@@ -182,18 +181,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
|
|
||||||
// Handle the monocle container separately
|
// Handle the monocle container separately
|
||||||
if let Some(monocle) = ws.monocle_container() {
|
if let Some(monocle) = ws.monocle_container() {
|
||||||
let mut to_remove = vec![];
|
|
||||||
for (id, border) in borders.iter() {
|
|
||||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
|
||||||
border.destroy()?;
|
|
||||||
to_remove.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in &to_remove {
|
|
||||||
borders.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let border = match borders.entry(monocle.id().clone()) {
|
let border = match borders.entry(monocle.id().clone()) {
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
@@ -209,7 +196,14 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut focus_state = FOCUS_STATE.lock();
|
let mut focus_state = FOCUS_STATE.lock();
|
||||||
focus_state.insert(border.hwnd, WindowKind::Monocle);
|
focus_state.insert(
|
||||||
|
border.hwnd,
|
||||||
|
if monitor_idx != focused_monitor_idx {
|
||||||
|
WindowKind::Unfocused
|
||||||
|
} else {
|
||||||
|
WindowKind::Monocle
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rect = WindowsApi::window_rect(
|
let rect = WindowsApi::window_rect(
|
||||||
@@ -217,7 +211,22 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
border.update(&rect)?;
|
border.update(&rect)?;
|
||||||
continue 'receiver;
|
|
||||||
|
let border_hwnd = border.hwnd;
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
for (id, b) in borders.iter() {
|
||||||
|
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||||
|
&& border_hwnd != b.hwnd
|
||||||
|
{
|
||||||
|
b.destroy()?;
|
||||||
|
to_remove.push(id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in &to_remove {
|
||||||
|
borders.remove(id);
|
||||||
|
}
|
||||||
|
continue 'monitors;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||||
@@ -237,7 +246,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
|||||||
borders.remove(id);
|
borders.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue 'receiver;
|
continue 'monitors;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy any borders not associated with the focused workspace
|
// Destroy any borders not associated with the focused workspace
|
||||||
|
|||||||
@@ -7,20 +7,13 @@ 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::WindowsApi;
|
|
||||||
use crate::STACKBAR_MODE;
|
|
||||||
use komorebi_core::StackbarMode;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
#[getset(get = "pub")]
|
#[getset(get = "pub")]
|
||||||
id: String,
|
id: String,
|
||||||
windows: Ring<Window>,
|
windows: Ring<Window>,
|
||||||
#[serde(skip)]
|
|
||||||
#[getset(get = "pub", get_mut = "pub")]
|
|
||||||
stackbar: Option<Stackbar>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_ring_elements!(Container, Window);
|
impl_ring_elements!(Container, Window);
|
||||||
@@ -30,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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,10 +35,6 @@ impl PartialEq for Container {
|
|||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn hide(&self, omit: Option<isize>) {
|
pub fn hide(&self, omit: Option<isize>) {
|
||||||
if let Some(stackbar) = self.stackbar() {
|
|
||||||
stackbar.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
for window in self.windows().iter().rev() {
|
for window in self.windows().iter().rev() {
|
||||||
let mut should_hide = omit.is_none();
|
let mut should_hide = omit.is_none();
|
||||||
|
|
||||||
@@ -68,10 +53,6 @@ impl Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&self) {
|
pub fn restore(&self) {
|
||||||
if let Some(stackbar) = self.stackbar() {
|
|
||||||
stackbar.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(window) = self.focused_window() {
|
if let Some(window) = self.focused_window() {
|
||||||
window.restore();
|
window.restore();
|
||||||
}
|
}
|
||||||
@@ -124,13 +105,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 {
|
|
||||||
if let Some(stackbar) = &self.stackbar {
|
|
||||||
let _ = WindowsApi::close_window(stackbar.hwnd());
|
|
||||||
self.stackbar = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx != 0 {
|
if idx != 0 {
|
||||||
self.focus_window(idx - 1);
|
self.focus_window(idx - 1);
|
||||||
};
|
};
|
||||||
@@ -145,14 +119,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,41 +127,4 @@ impl Container {
|
|||||||
tracing::info!("focusing window");
|
tracing::info!("focusing window");
|
||||||
self.windows.focus(idx);
|
self.windows.focus(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stackbar_mode(&mut self, mode: StackbarMode) {
|
|
||||||
match mode {
|
|
||||||
StackbarMode::Always => {
|
|
||||||
if self.stackbar.is_none() {
|
|
||||||
self.stackbar = Stackbar::create().ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StackbarMode::Never => {
|
|
||||||
if let Some(stackbar) = &self.stackbar {
|
|
||||||
let _ = WindowsApi::close_window(stackbar.hwnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.stackbar = None
|
|
||||||
}
|
|
||||||
StackbarMode::OnStack => {
|
|
||||||
if self.windows().len() > 1 && self.stackbar().is_none() {
|
|
||||||
self.stackbar = Stackbar::create().ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stackbar) = &self.stackbar {
|
|
||||||
if self.windows().len() == 1 {
|
|
||||||
let _ = WindowsApi::close_window(stackbar.hwnd());
|
|
||||||
self.stackbar = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn renew_stackbar(&mut self) {
|
|
||||||
if let Some(stackbar) = &self.stackbar {
|
|
||||||
if !WindowsApi::is_window(stackbar.hwnd()) {
|
|
||||||
self.stackbar = Stackbar::create().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
use std::sync::atomic::Ordering;
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use windows::core::PCWSTR;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::FindWindowW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
|
||||||
|
|
||||||
use crate::windows_callbacks;
|
|
||||||
use crate::WindowsApi;
|
|
||||||
use crate::HIDDEN_HWND;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Hidden {
|
|
||||||
pub(crate) hwnd: isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<isize> for Hidden {
|
|
||||||
fn from(hwnd: isize) -> Self {
|
|
||||||
Self { hwnd }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hidden {
|
|
||||||
pub const fn hwnd(self) -> HWND {
|
|
||||||
HWND(self.hwnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create(name: &str) -> Result<()> {
|
|
||||||
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
|
|
||||||
let instance = WindowsApi::module_handle_w()?;
|
|
||||||
let class_name = PCWSTR(name.as_ptr());
|
|
||||||
let brush = WindowsApi::create_solid_brush(0);
|
|
||||||
let window_class = WNDCLASSW {
|
|
||||||
hInstance: instance.into(),
|
|
||||||
lpszClassName: class_name,
|
|
||||||
style: CS_HREDRAW | CS_VREDRAW,
|
|
||||||
lpfnWndProc: Some(windows_callbacks::hidden_window),
|
|
||||||
hbrBackground: brush,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let _atom = WindowsApi::register_class_w(&window_class)?;
|
|
||||||
|
|
||||||
let name_cl = name.clone();
|
|
||||||
std::thread::spawn(move || -> Result<()> {
|
|
||||||
let hwnd = WindowsApi::create_hidden_window(PCWSTR(name_cl.as_ptr()), instance)?;
|
|
||||||
let hidden = Self::from(hwnd);
|
|
||||||
|
|
||||||
let mut message = MSG::default();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
while GetMessageW(&mut message, hidden.hwnd(), 0, 0).into() {
|
|
||||||
DispatchMessageW(&message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut hwnd = HWND(0);
|
|
||||||
while hwnd == HWND(0) {
|
|
||||||
hwnd = unsafe { FindWindowW(PCWSTR(name.as_ptr()), PCWSTR::null()) };
|
|
||||||
}
|
|
||||||
|
|
||||||
HIDDEN_HWND.store(hwnd.0, Ordering::SeqCst);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,13 @@ pub mod com;
|
|||||||
pub mod ring;
|
pub mod ring;
|
||||||
pub mod colour;
|
pub mod colour;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod hidden;
|
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
|
pub mod monitor_reconciliator;
|
||||||
pub mod process_command;
|
pub mod process_command;
|
||||||
pub mod process_event;
|
pub mod process_event;
|
||||||
pub mod process_movement;
|
pub mod process_movement;
|
||||||
pub mod set_window_position;
|
pub mod set_window_position;
|
||||||
pub mod stackbar;
|
pub mod stackbar_manager;
|
||||||
pub mod static_config;
|
pub mod static_config;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
@@ -33,16 +33,13 @@ use std::path::PathBuf;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicI32;
|
use std::sync::atomic::AtomicI32;
|
||||||
use std::sync::atomic::AtomicIsize;
|
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use colour::*;
|
pub use colour::*;
|
||||||
pub use hidden::*;
|
|
||||||
pub use process_command::*;
|
pub use process_command::*;
|
||||||
pub use process_event::*;
|
pub use process_event::*;
|
||||||
pub use stackbar::*;
|
|
||||||
pub use static_config::*;
|
pub use static_config::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
pub use window_manager::*;
|
pub use window_manager::*;
|
||||||
@@ -58,7 +55,6 @@ use komorebi_core::ApplicationIdentifier;
|
|||||||
use komorebi_core::HidingBehaviour;
|
use komorebi_core::HidingBehaviour;
|
||||||
use komorebi_core::Rect;
|
use komorebi_core::Rect;
|
||||||
use komorebi_core::SocketMessage;
|
use komorebi_core::SocketMessage;
|
||||||
use komorebi_core::StackbarMode;
|
|
||||||
use os_info::Version;
|
use os_info::Version;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -201,7 +197,6 @@ lazy_static! {
|
|||||||
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
// eg. Windows Terminal, IntelliJ IDEA, Firefox
|
||||||
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
|
||||||
static ref STACKBAR_MODE: Arc<Mutex<StackbarMode >> = Arc::new(Mutex::new(StackbarMode::Never));
|
|
||||||
static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =
|
static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
@@ -216,14 +211,6 @@ pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
|
|||||||
|
|
||||||
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0);
|
|
||||||
|
|
||||||
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
|
|
||||||
pub static STACKBAR_UNFOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(11776947); // gray text
|
|
||||||
pub static STACKBAR_TAB_BACKGROUND_COLOUR: AtomicU32 = AtomicU32::new(3355443); // gray
|
|
||||||
pub static STACKBAR_TAB_HEIGHT: AtomicI32 = AtomicI32::new(40);
|
|
||||||
pub static STACKBAR_TAB_WIDTH: AtomicI32 = AtomicI32::new(200);
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ use tracing_subscriber::layer::SubscriberExt;
|
|||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use komorebi::border_manager;
|
use komorebi::border_manager;
|
||||||
use komorebi::hidden::Hidden;
|
|
||||||
use komorebi::load_configuration;
|
use komorebi::load_configuration;
|
||||||
|
use komorebi::monitor_reconciliator;
|
||||||
use komorebi::process_command::listen_for_commands;
|
use komorebi::process_command::listen_for_commands;
|
||||||
use komorebi::process_command::listen_for_commands_tcp;
|
use komorebi::process_command::listen_for_commands_tcp;
|
||||||
use komorebi::process_event::listen_for_events;
|
use komorebi::process_event::listen_for_events;
|
||||||
use komorebi::process_movement::listen_for_movements;
|
use komorebi::process_movement::listen_for_movements;
|
||||||
|
use komorebi::stackbar_manager;
|
||||||
use komorebi::static_config::StaticConfig;
|
use komorebi::static_config::StaticConfig;
|
||||||
use komorebi::window_manager::WindowManager;
|
use komorebi::window_manager::WindowManager;
|
||||||
use komorebi::windows_api::WindowsApi;
|
use komorebi::windows_api::WindowsApi;
|
||||||
@@ -52,8 +53,8 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
|||||||
std::env::set_var("RUST_LOG", "info");
|
std::env::set_var("RUST_LOG", "info");
|
||||||
}
|
}
|
||||||
|
|
||||||
let appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi_plaintext.log");
|
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
|
||||||
let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log");
|
let color_appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi.log");
|
||||||
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
|
||||||
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
|
let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);
|
||||||
|
|
||||||
@@ -188,8 +189,6 @@ fn main() -> Result<()> {
|
|||||||
#[cfg(feature = "deadlock_detection")]
|
#[cfg(feature = "deadlock_detection")]
|
||||||
detect_deadlocks();
|
detect_deadlocks();
|
||||||
|
|
||||||
Hidden::create("komorebi-hidden")?;
|
|
||||||
|
|
||||||
let static_config = opts.config.map_or_else(
|
let static_config = opts.config.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
let komorebi_json = HOME_DIR.join("komorebi.json");
|
let komorebi_json = HOME_DIR.join("komorebi.json");
|
||||||
@@ -256,7 +255,9 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
border_manager::listen_for_notifications(wm.clone());
|
border_manager::listen_for_notifications(wm.clone());
|
||||||
|
stackbar_manager::listen_for_notifications(wm.clone());
|
||||||
workspace_reconciliator::listen_for_notifications(wm.clone());
|
workspace_reconciliator::listen_for_notifications(wm.clone());
|
||||||
|
monitor_reconciliator::listen_for_notifications(wm.clone())?;
|
||||||
|
|
||||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ pub struct Monitor {
|
|||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
name: String,
|
name: String,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
device: Option<String>,
|
device: String,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
device_id: Option<String>,
|
device_id: String,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
size: Rect,
|
size: Rect,
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
@@ -50,15 +50,22 @@ pub struct Monitor {
|
|||||||
|
|
||||||
impl_ring_elements!(Monitor, Workspace);
|
impl_ring_elements!(Monitor, Workspace);
|
||||||
|
|
||||||
pub fn new(id: isize, size: Rect, work_area_size: Rect, name: String) -> Monitor {
|
pub fn new(
|
||||||
|
id: isize,
|
||||||
|
size: Rect,
|
||||||
|
work_area_size: Rect,
|
||||||
|
name: String,
|
||||||
|
device: String,
|
||||||
|
device_id: String,
|
||||||
|
) -> Monitor {
|
||||||
let mut workspaces = Ring::default();
|
let mut workspaces = Ring::default();
|
||||||
workspaces.elements_mut().push_back(Workspace::default());
|
workspaces.elements_mut().push_back(Workspace::default());
|
||||||
|
|
||||||
Monitor {
|
Monitor {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
device: None,
|
device,
|
||||||
device_id: None,
|
device_id,
|
||||||
size,
|
size,
|
||||||
work_area_size,
|
work_area_size,
|
||||||
work_area_offset: None,
|
work_area_offset: None,
|
||||||
|
|||||||
190
komorebi/src/monitor_reconciliator/hidden.rs
Normal file
190
komorebi/src/monitor_reconciliator/hidden.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::Foundation::LPARAM;
|
||||||
|
use windows::Win32::Foundation::LRESULT;
|
||||||
|
use windows::Win32::Foundation::WPARAM;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_POWERBROADCAST;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_WTSSESSION_CHANGE;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_LOCK;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_UNLOCK;
|
||||||
|
|
||||||
|
use crate::monitor_reconciliator;
|
||||||
|
use crate::WindowsApi;
|
||||||
|
|
||||||
|
// This is a hidden window specifically spawned to listen to system-wide events related to monitors
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Hidden {
|
||||||
|
pub hwnd: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<isize> for Hidden {
|
||||||
|
fn from(hwnd: isize) -> Self {
|
||||||
|
Self { hwnd }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hidden {
|
||||||
|
pub const fn hwnd(self) -> HWND {
|
||||||
|
HWND(self.hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(name: &str) -> color_eyre::Result<Self> {
|
||||||
|
let name: Vec<u16> = format!("{name}\0").encode_utf16().collect();
|
||||||
|
let class_name = PCWSTR(name.as_ptr());
|
||||||
|
|
||||||
|
let h_module = WindowsApi::module_handle_w()?;
|
||||||
|
let window_class = WNDCLASSW {
|
||||||
|
hInstance: h_module.into(),
|
||||||
|
lpszClassName: class_name,
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
|
lpfnWndProc: Some(Self::callback),
|
||||||
|
hbrBackground: WindowsApi::create_solid_brush(0),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = WindowsApi::register_class_w(&window_class)?;
|
||||||
|
|
||||||
|
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
|
||||||
|
|
||||||
|
std::thread::spawn(move || -> color_eyre::Result<()> {
|
||||||
|
let hwnd = WindowsApi::create_hidden_window(PCWSTR(name.as_ptr()), h_module)?;
|
||||||
|
hwnd_sender.send(hwnd)?;
|
||||||
|
|
||||||
|
let mut message = MSG::default();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() {
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let hwnd = hwnd_receiver.recv()?;
|
||||||
|
|
||||||
|
WindowsApi::wts_register_session_notification(hwnd)?;
|
||||||
|
|
||||||
|
Ok(Self { hwnd })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "system" fn callback(
|
||||||
|
window: HWND,
|
||||||
|
message: u32,
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
unsafe {
|
||||||
|
match message {
|
||||||
|
WM_POWERBROADCAST => {
|
||||||
|
match wparam.0 as u32 {
|
||||||
|
// Automatic: System resumed itself from sleep or hibernation
|
||||||
|
// Suspend: User resumed system from sleep or hibernation
|
||||||
|
PBT_APMRESUMEAUTOMATIC | PBT_APMRESUMESUSPEND => {
|
||||||
|
tracing::debug!(
|
||||||
|
"WM_POWERBROADCAST event received - resume from suspend"
|
||||||
|
);
|
||||||
|
let _ = monitor_reconciliator::event_tx().send(
|
||||||
|
monitor_reconciliator::Notification::ResumingFromSuspendedState,
|
||||||
|
);
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
// Computer is entering a suspended state
|
||||||
|
PBT_APMSUSPEND => {
|
||||||
|
tracing::debug!(
|
||||||
|
"WM_POWERBROADCAST event received - entering suspended state"
|
||||||
|
);
|
||||||
|
let _ = monitor_reconciliator::event_tx()
|
||||||
|
.send(monitor_reconciliator::Notification::EnteringSuspendedState);
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
_ => LRESULT(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WM_WTSSESSION_CHANGE => {
|
||||||
|
match wparam.0 as u32 {
|
||||||
|
WTS_SESSION_LOCK => {
|
||||||
|
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked");
|
||||||
|
|
||||||
|
let _ = monitor_reconciliator::event_tx()
|
||||||
|
.send(monitor_reconciliator::Notification::SessionLocked);
|
||||||
|
}
|
||||||
|
WTS_SESSION_UNLOCK => {
|
||||||
|
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked");
|
||||||
|
|
||||||
|
let _ = monitor_reconciliator::event_tx()
|
||||||
|
.send(monitor_reconciliator::Notification::SessionUnlocked);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
// This event gets sent when:
|
||||||
|
// - The scaling factor on a display changes
|
||||||
|
// - The resolution on a display changes
|
||||||
|
// - A monitor is added
|
||||||
|
// - A monitor is removed
|
||||||
|
// Since WM_DEVICECHANGE also notifies on monitor changes, we only handle scaling
|
||||||
|
// and resolution changes here
|
||||||
|
WM_DISPLAYCHANGE => {
|
||||||
|
tracing::debug!(
|
||||||
|
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed", wparam.0
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = monitor_reconciliator::event_tx()
|
||||||
|
.send(monitor_reconciliator::Notification::ResolutionScalingChanged);
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
// Unfortunately this is the event sent with ButteryTaskbar which I use a lot
|
||||||
|
// Original idea from https://stackoverflow.com/a/33762334
|
||||||
|
WM_SETTINGCHANGE => {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
if wparam.0 as u32 == SPI_SETWORKAREA.0 {
|
||||||
|
tracing::debug!(
|
||||||
|
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = monitor_reconciliator::event_tx()
|
||||||
|
.send(monitor_reconciliator::Notification::WorkAreaChanged);
|
||||||
|
}
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
// This event + wparam combo is sent 4 times when a monitor is added based on my testing on win11
|
||||||
|
// Original idea from https://stackoverflow.com/a/33762334
|
||||||
|
WM_DEVICECHANGE => {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
||||||
|
tracing::debug!(
|
||||||
|
"WM_DEVICECHANGE event received with DBT_DEVNODES_CHANGED - display added or removed"
|
||||||
|
);
|
||||||
|
let _ = monitor_reconciliator::event_tx()
|
||||||
|
.send(monitor_reconciliator::Notification::DisplayConnectionChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
_ => DefWindowProcW(window, message, wparam, lparam),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
404
komorebi/src/monitor_reconciliator/mod.rs
Normal file
404
komorebi/src/monitor_reconciliator/mod.rs
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||||
|
|
||||||
|
use crate::border_manager;
|
||||||
|
use crate::monitor;
|
||||||
|
use crate::monitor::Monitor;
|
||||||
|
use crate::monitor_reconciliator::hidden::Hidden;
|
||||||
|
use crate::MonitorConfig;
|
||||||
|
use crate::WindowManager;
|
||||||
|
use crate::WindowsApi;
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
|
use crossbeam_utils::atomic::AtomicConsume;
|
||||||
|
use komorebi_core::Rect;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
pub mod hidden;
|
||||||
|
|
||||||
|
pub enum Notification {
|
||||||
|
ResolutionScalingChanged,
|
||||||
|
WorkAreaChanged,
|
||||||
|
DisplayConnectionChange,
|
||||||
|
EnteringSuspendedState,
|
||||||
|
ResumingFromSuspendedState,
|
||||||
|
SessionLocked,
|
||||||
|
SessionUnlocked,
|
||||||
|
}
|
||||||
|
|
||||||
|
static ACTIVE: AtomicBool = AtomicBool::new(true);
|
||||||
|
|
||||||
|
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||||
|
|
||||||
|
static MONITOR_CACHE: OnceLock<Mutex<HashMap<String, MonitorConfig>>> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||||
|
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_tx() -> Sender<Notification> {
|
||||||
|
channel().0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_rx() -> Receiver<Notification> {
|
||||||
|
channel().1.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_in_monitor_cache(device_id: &str, config: MonitorConfig) {
|
||||||
|
let mut monitor_cache = MONITOR_CACHE
|
||||||
|
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||||
|
.lock();
|
||||||
|
|
||||||
|
monitor_cache.insert(device_id.to_string(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
|
||||||
|
Ok(win32_display_data::connected_displays()
|
||||||
|
.flatten()
|
||||||
|
.map(|display| {
|
||||||
|
let path = display.device_path;
|
||||||
|
let mut split: Vec<_> = path.split('#').collect();
|
||||||
|
split.remove(0);
|
||||||
|
split.remove(split.len() - 1);
|
||||||
|
let device = split[0].to_string();
|
||||||
|
let device_id = split.join("-");
|
||||||
|
|
||||||
|
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
||||||
|
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
||||||
|
|
||||||
|
monitor::new(
|
||||||
|
display.hmonitor,
|
||||||
|
display.size.into(),
|
||||||
|
display.work_area_size.into(),
|
||||||
|
name,
|
||||||
|
device,
|
||||||
|
device_id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
}
|
||||||
|
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
Hidden::create("komorebi-hidden")?;
|
||||||
|
|
||||||
|
tracing::info!("created hidden window to listen for monitor-related events");
|
||||||
|
|
||||||
|
std::thread::spawn(move || loop {
|
||||||
|
match handle_notifications(wm.clone()) {
|
||||||
|
Ok(()) => {
|
||||||
|
tracing::warn!("restarting finished thread");
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
tracing::error!("restarting failed thread: {:?}", error)
|
||||||
|
} else {
|
||||||
|
tracing::error!("restarting failed thread: {}", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||||
|
tracing::info!("listening");
|
||||||
|
|
||||||
|
let receiver = event_rx();
|
||||||
|
|
||||||
|
'receiver: for notification in receiver {
|
||||||
|
if !ACTIVE.load_consume() {
|
||||||
|
if matches!(
|
||||||
|
notification,
|
||||||
|
Notification::ResumingFromSuspendedState | Notification::SessionUnlocked
|
||||||
|
) {
|
||||||
|
tracing::debug!(
|
||||||
|
"reactivating reconciliator - system has resumed from suspended state or session has been unlocked"
|
||||||
|
);
|
||||||
|
|
||||||
|
ACTIVE.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wm = wm.lock();
|
||||||
|
|
||||||
|
match notification {
|
||||||
|
Notification::EnteringSuspendedState | Notification::SessionLocked => {
|
||||||
|
tracing::debug!(
|
||||||
|
"deactivating reconciliator until system resumes from suspended state or session is unlocked"
|
||||||
|
);
|
||||||
|
ACTIVE.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
Notification::ResumingFromSuspendedState | Notification::SessionUnlocked => {
|
||||||
|
// this is only handled above if the reconciliator is paused
|
||||||
|
}
|
||||||
|
Notification::WorkAreaChanged => {
|
||||||
|
tracing::debug!("handling work area changed notification");
|
||||||
|
let offset = wm.work_area_offset;
|
||||||
|
for monitor in wm.monitors_mut() {
|
||||||
|
let mut should_update = false;
|
||||||
|
|
||||||
|
// Update work areas as necessary
|
||||||
|
if let Ok(reference) = WindowsApi::monitor(monitor.id()) {
|
||||||
|
if reference.work_area_size() != monitor.work_area_size() {
|
||||||
|
monitor.set_work_area_size(Rect {
|
||||||
|
left: reference.work_area_size().left,
|
||||||
|
top: reference.work_area_size().top,
|
||||||
|
right: reference.work_area_size().right,
|
||||||
|
bottom: reference.work_area_size().bottom,
|
||||||
|
});
|
||||||
|
|
||||||
|
should_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_update {
|
||||||
|
tracing::info!("updated work area for {}", monitor.device_id());
|
||||||
|
monitor.update_focused_workspace(offset)?;
|
||||||
|
border_manager::event_tx().send(border_manager::Notification)?;
|
||||||
|
} else {
|
||||||
|
tracing::debug!(
|
||||||
|
"work areas match, reconciliation not required for {}",
|
||||||
|
monitor.device_id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Notification::ResolutionScalingChanged => {
|
||||||
|
tracing::debug!("handling resolution/scaling changed notification");
|
||||||
|
let offset = wm.work_area_offset;
|
||||||
|
for monitor in wm.monitors_mut() {
|
||||||
|
let mut should_update = false;
|
||||||
|
|
||||||
|
// Update sizes and work areas as necessary
|
||||||
|
if let Ok(reference) = WindowsApi::monitor(monitor.id()) {
|
||||||
|
if reference.work_area_size() != monitor.work_area_size() {
|
||||||
|
monitor.set_work_area_size(Rect {
|
||||||
|
left: reference.work_area_size().left,
|
||||||
|
top: reference.work_area_size().top,
|
||||||
|
right: reference.work_area_size().right,
|
||||||
|
bottom: reference.work_area_size().bottom,
|
||||||
|
});
|
||||||
|
|
||||||
|
should_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if reference.size() != monitor.size() {
|
||||||
|
monitor.set_size(Rect {
|
||||||
|
left: reference.size().left,
|
||||||
|
top: reference.size().top,
|
||||||
|
right: reference.size().right,
|
||||||
|
bottom: reference.size().bottom,
|
||||||
|
});
|
||||||
|
|
||||||
|
should_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_update {
|
||||||
|
tracing::info!(
|
||||||
|
"updated monitor resolution/scaling for {}",
|
||||||
|
monitor.device_id()
|
||||||
|
);
|
||||||
|
|
||||||
|
monitor.update_focused_workspace(offset)?;
|
||||||
|
border_manager::event_tx().send(border_manager::Notification)?;
|
||||||
|
} else {
|
||||||
|
tracing::debug!(
|
||||||
|
"resolutions match, reconciliation not required for {}",
|
||||||
|
monitor.device_id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Notification::DisplayConnectionChange => {
|
||||||
|
tracing::debug!("handling display connection change notification");
|
||||||
|
let mut monitor_cache = MONITOR_CACHE
|
||||||
|
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||||
|
.lock();
|
||||||
|
|
||||||
|
let initial_monitor_count = wm.monitors().len();
|
||||||
|
|
||||||
|
// Get the currently attached display devices
|
||||||
|
let attached_devices = attached_display_devices()?;
|
||||||
|
|
||||||
|
// Make sure that in our state any attached displays have the latest Win32 data
|
||||||
|
for monitor in wm.monitors_mut() {
|
||||||
|
for attached in &attached_devices {
|
||||||
|
if attached.device_id().eq(monitor.device_id()) {
|
||||||
|
monitor.set_id(attached.id());
|
||||||
|
monitor.set_name(attached.name().clone());
|
||||||
|
monitor.set_size(*attached.size());
|
||||||
|
monitor.set_work_area_size(*attached.work_area_size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if initial_monitor_count == attached_devices.len() {
|
||||||
|
tracing::debug!("monitor counts match, reconciliation not required");
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if attached_devices.is_empty() {
|
||||||
|
tracing::debug!(
|
||||||
|
"no devices found, skipping reconciliation to avoid breaking state"
|
||||||
|
);
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if initial_monitor_count > attached_devices.len() {
|
||||||
|
tracing::info!(
|
||||||
|
"monitor count mismatch ({initial_monitor_count} vs {}), removing disconnected monitors",
|
||||||
|
attached_devices.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Gather all the containers that will be orphaned from disconnected and invalid displays
|
||||||
|
let mut orphaned_containers = vec![];
|
||||||
|
|
||||||
|
// Collect the ids in our state which aren't in the current attached display ids
|
||||||
|
// These are monitors that have been removed
|
||||||
|
let mut newly_removed_displays = vec![];
|
||||||
|
|
||||||
|
for m in wm.monitors().iter() {
|
||||||
|
if !attached_devices
|
||||||
|
.iter()
|
||||||
|
.any(|attached| attached.device_id().eq(m.device_id()))
|
||||||
|
{
|
||||||
|
newly_removed_displays.push(m.device_id().clone());
|
||||||
|
for workspace in m.workspaces() {
|
||||||
|
for container in workspace.containers() {
|
||||||
|
// Save the orphaned containers from the removed monitor
|
||||||
|
orphaned_containers.push(container.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's add their state to the cache for later
|
||||||
|
monitor_cache.insert(m.device_id().clone(), m.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !orphaned_containers.is_empty() {
|
||||||
|
tracing::info!(
|
||||||
|
"removed orphaned containers from: {newly_removed_displays:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !newly_removed_displays.is_empty() {
|
||||||
|
// After we have cached them, remove them from our state
|
||||||
|
wm.monitors_mut()
|
||||||
|
.retain(|m| !newly_removed_displays.contains(m.device_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let post_removal_monitor_count = wm.monitors().len();
|
||||||
|
let focused_monitor_idx = wm.focused_monitor_idx();
|
||||||
|
if focused_monitor_idx >= post_removal_monitor_count {
|
||||||
|
wm.focus_monitor(0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !orphaned_containers.is_empty() {
|
||||||
|
if let Some(primary) = wm.monitors_mut().front_mut() {
|
||||||
|
if let Some(focused_ws) = primary.focused_workspace_mut() {
|
||||||
|
let focused_container_idx = focused_ws.focused_container_idx();
|
||||||
|
|
||||||
|
// Put the orphaned containers somewhere visible
|
||||||
|
for container in orphaned_containers {
|
||||||
|
focused_ws.add_container(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gotta reset the focus or the movement will feel "off"
|
||||||
|
if initial_monitor_count != post_removal_monitor_count {
|
||||||
|
focused_ws.focus_container(focused_container_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = wm.work_area_offset;
|
||||||
|
|
||||||
|
for monitor in wm.monitors_mut() {
|
||||||
|
// If we have lost a monitor, update everything to filter out any jank
|
||||||
|
if initial_monitor_count != post_removal_monitor_count {
|
||||||
|
monitor.update_focused_workspace(offset)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let post_removal_monitor_count = wm.monitors().len();
|
||||||
|
|
||||||
|
// This is the list of device ids after we have removed detached displays
|
||||||
|
let post_removal_device_ids = wm
|
||||||
|
.monitors()
|
||||||
|
.iter()
|
||||||
|
.map(Monitor::device_id)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Check for and add any new monitors that may have been plugged in
|
||||||
|
// Monitor and display index preferences get applied in this function
|
||||||
|
WindowsApi::load_monitor_information(&mut wm.monitors)?;
|
||||||
|
|
||||||
|
let post_addition_monitor_count = wm.monitors().len();
|
||||||
|
|
||||||
|
if post_addition_monitor_count > post_removal_monitor_count {
|
||||||
|
tracing::info!(
|
||||||
|
"monitor count mismatch ({post_removal_monitor_count} vs {post_addition_monitor_count}), adding connected monitors",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Look in the updated state for new monitors
|
||||||
|
for m in wm.monitors_mut() {
|
||||||
|
let device_id = m.device_id().clone();
|
||||||
|
// We identify a new monitor when we encounter a new device id
|
||||||
|
if !post_removal_device_ids.contains(&device_id) {
|
||||||
|
let mut cache_hit = false;
|
||||||
|
// Check if that device id exists in the cache for this session
|
||||||
|
if let Some(cached) = monitor_cache.get(&device_id) {
|
||||||
|
cache_hit = true;
|
||||||
|
|
||||||
|
tracing::info!("found monitor and workspace configuration for {device_id} in the monitor cache, applying");
|
||||||
|
|
||||||
|
// If it does, load all the monitor settings from the cache entry
|
||||||
|
m.ensure_workspace_count(cached.workspaces.len());
|
||||||
|
m.set_work_area_offset(cached.work_area_offset);
|
||||||
|
m.set_window_based_work_area_offset(
|
||||||
|
cached.window_based_work_area_offset,
|
||||||
|
);
|
||||||
|
m.set_window_based_work_area_offset_limit(
|
||||||
|
cached.window_based_work_area_offset_limit.unwrap_or(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (w_idx, workspace) in m.workspaces_mut().iter_mut().enumerate()
|
||||||
|
{
|
||||||
|
if let Some(cached_workspace) = cached.workspaces.get(w_idx) {
|
||||||
|
workspace.load_static_config(cached_workspace)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries in the cache should only be used once; remove the entry there was a cache hit
|
||||||
|
if cache_hit {
|
||||||
|
monitor_cache.remove(&device_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let final_count = wm.monitors().len();
|
||||||
|
|
||||||
|
if post_removal_monitor_count != final_count {
|
||||||
|
wm.retile_all(true)?;
|
||||||
|
// Second retile to fix DPI/resolution related jank
|
||||||
|
wm.retile_all(true)?;
|
||||||
|
// Border updates to fix DPI/resolution related jank
|
||||||
|
border_manager::event_tx().send(border_manager::Notification)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ use crate::border_manager::STYLE;
|
|||||||
use crate::colour::Rgb;
|
use crate::colour::Rgb;
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::notify_subscribers;
|
use crate::notify_subscribers;
|
||||||
|
use crate::stackbar_manager;
|
||||||
use crate::static_config::StaticConfig;
|
use crate::static_config::StaticConfig;
|
||||||
use crate::window::RuleDebug;
|
use crate::window::RuleDebug;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
@@ -64,17 +65,18 @@ use crate::MONITOR_INDEX_PREFERENCES;
|
|||||||
use crate::NO_TITLEBAR;
|
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::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::SUBSCRIPTION_PIPES;
|
use crate::SUBSCRIPTION_PIPES;
|
||||||
use crate::SUBSCRIPTION_SOCKETS;
|
use crate::SUBSCRIPTION_SOCKETS;
|
||||||
use crate::TCP_CONNECTIONS;
|
use crate::TCP_CONNECTIONS;
|
||||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||||
use crate::WORKSPACE_RULES;
|
use crate::WORKSPACE_RULES;
|
||||||
|
use stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||||
|
use stackbar_manager::STACKBAR_LABEL;
|
||||||
|
use stackbar_manager::STACKBAR_MODE;
|
||||||
|
use stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||||
|
use stackbar_manager::STACKBAR_TAB_HEIGHT;
|
||||||
|
use stackbar_manager::STACKBAR_TAB_WIDTH;
|
||||||
|
use stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||||
@@ -185,6 +187,10 @@ impl WindowManager {
|
|||||||
match message {
|
match message {
|
||||||
SocketMessage::Promote => self.promote_container_to_front()?,
|
SocketMessage::Promote => self.promote_container_to_front()?,
|
||||||
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
|
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
|
||||||
|
SocketMessage::PromoteWindow(direction) => {
|
||||||
|
self.focus_container_in_direction(direction)?;
|
||||||
|
self.promote_container_to_front()?
|
||||||
|
}
|
||||||
SocketMessage::FocusWindow(direction) => {
|
SocketMessage::FocusWindow(direction) => {
|
||||||
self.focus_container_in_direction(direction)?;
|
self.focus_container_in_direction(direction)?;
|
||||||
}
|
}
|
||||||
@@ -465,6 +471,15 @@ impl WindowManager {
|
|||||||
SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {
|
SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {
|
||||||
self.move_workspace_to_monitor(monitor_idx)?;
|
self.move_workspace_to_monitor(monitor_idx)?;
|
||||||
}
|
}
|
||||||
|
SocketMessage::CycleMoveWorkspaceToMonitor(direction) => {
|
||||||
|
let monitor_idx = direction.next_idx(
|
||||||
|
self.focused_monitor_idx(),
|
||||||
|
NonZeroUsize::new(self.monitors().len())
|
||||||
|
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.move_workspace_to_monitor(monitor_idx)?;
|
||||||
|
}
|
||||||
SocketMessage::TogglePause => {
|
SocketMessage::TogglePause => {
|
||||||
if self.is_paused {
|
if self.is_paused {
|
||||||
tracing::info!("resuming");
|
tracing::info!("resuming");
|
||||||
@@ -760,13 +775,10 @@ impl WindowManager {
|
|||||||
SocketMessage::VisibleWindows => {
|
SocketMessage::VisibleWindows => {
|
||||||
let mut monitor_visible_windows = HashMap::new();
|
let mut monitor_visible_windows = HashMap::new();
|
||||||
|
|
||||||
for (index, monitor) in self.monitors().iter().enumerate() {
|
for monitor in self.monitors() {
|
||||||
if let Some(ws) = monitor.focused_workspace() {
|
if let Some(ws) = monitor.focused_workspace() {
|
||||||
monitor_visible_windows.insert(
|
monitor_visible_windows.insert(
|
||||||
monitor
|
monitor.device_id().clone(),
|
||||||
.device_id()
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| format!("{index}")),
|
|
||||||
ws.visible_window_details().clone(),
|
ws.visible_window_details().clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1205,10 +1217,10 @@ impl WindowManager {
|
|||||||
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
|
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
|
||||||
self.unmanaged_window_operation_behaviour = behaviour;
|
self.unmanaged_window_operation_behaviour = behaviour;
|
||||||
}
|
}
|
||||||
SocketMessage::ActiveWindowBorder(enable) => {
|
SocketMessage::Border(enable) => {
|
||||||
border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst);
|
border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => match kind {
|
SocketMessage::BorderColour(kind, r, g, b) => match kind {
|
||||||
WindowKind::Single => {
|
WindowKind::Single => {
|
||||||
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
@@ -1222,9 +1234,9 @@ impl WindowManager {
|
|||||||
border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SocketMessage::ActiveWindowBorderStyle(style) => {
|
SocketMessage::BorderStyle(style) => {
|
||||||
let mut active_window_border_style = STYLE.lock();
|
let mut border_style = STYLE.lock();
|
||||||
*active_window_border_style = style;
|
*border_style = style;
|
||||||
}
|
}
|
||||||
SocketMessage::BorderWidth(width) => {
|
SocketMessage::BorderWidth(width) => {
|
||||||
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
|
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
|
||||||
@@ -1233,16 +1245,10 @@ impl WindowManager {
|
|||||||
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
SocketMessage::StackbarMode(mode) => {
|
SocketMessage::StackbarMode(mode) => {
|
||||||
let mut stackbar_mode = STACKBAR_MODE.lock();
|
STACKBAR_MODE.store(mode);
|
||||||
*stackbar_mode = mode;
|
}
|
||||||
|
SocketMessage::StackbarLabel(label) => {
|
||||||
for m in self.monitors_mut() {
|
STACKBAR_LABEL.store(label);
|
||||||
for w in m.workspaces_mut() {
|
|
||||||
for c in w.containers_mut() {
|
|
||||||
c.set_stackbar_mode(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SocketMessage::StackbarFocusedTextColour(r, g, b) => {
|
SocketMessage::StackbarFocusedTextColour(r, g, b) => {
|
||||||
let rgb = Rgb::new(r, g, b);
|
let rgb = Rgb::new(r, g, b);
|
||||||
@@ -1329,6 +1335,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
border_manager::event_tx().send(border_manager::Notification)?;
|
||||||
|
stackbar_manager::event_tx().send(stackbar_manager::Notification)?;
|
||||||
|
|
||||||
tracing::info!("processed");
|
tracing::info!("processed");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use crate::border_manager::BORDER_OFFSET;
|
|||||||
use crate::border_manager::BORDER_WIDTH;
|
use crate::border_manager::BORDER_WIDTH;
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::notify_subscribers;
|
use crate::notify_subscribers;
|
||||||
|
use crate::stackbar_manager;
|
||||||
use crate::window::should_act;
|
use crate::window::should_act;
|
||||||
use crate::window::RuleDebug;
|
use crate::window::RuleDebug;
|
||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
@@ -71,7 +72,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
// All event handlers below this point should only be processed if the event is
|
// All event handlers below this point should only be processed if the event is
|
||||||
// related to a window that should be managed by the WindowManager.
|
// related to a window that should be managed by the WindowManager.
|
||||||
if !should_manage && !matches!(event, WindowManagerEvent::DisplayChange(_)) {
|
if !should_manage {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,31 +92,29 @@ impl WindowManager {
|
|||||||
match event {
|
match event {
|
||||||
WindowManagerEvent::FocusChange(_, window)
|
WindowManagerEvent::FocusChange(_, window)
|
||||||
| WindowManagerEvent::Show(_, window)
|
| WindowManagerEvent::Show(_, window)
|
||||||
| WindowManagerEvent::DisplayChange(window)
|
|
||||||
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
| WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||||
self.reconcile_monitors()?;
|
if let Some(monitor_idx) = self.monitor_idx_from_window(window) {
|
||||||
|
// This is a hidden window apparently associated with COM support mechanisms (based
|
||||||
let monitor_idx = self.monitor_idx_from_window(window)
|
// on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)
|
||||||
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
|
//
|
||||||
|
// The hidden window, OLEChannelWnd, associated with this class (spawned by
|
||||||
// This is a hidden window apparently associated with COM support mechanisms (based
|
// explorer.exe), after some debugging, is observed to always be tied to the primary
|
||||||
// on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)
|
// display monitor, or (usually) monitor 0 in the WindowManager state.
|
||||||
//
|
//
|
||||||
// The hidden window, OLEChannelWnd, associated with this class (spawned by
|
// Due to this, at least one user in the Discord has witnessed behaviour where, when
|
||||||
// explorer.exe), after some debugging, is observed to always be tied to the primary
|
// a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets
|
||||||
// display monitor, or (usually) monitor 0 in the WindowManager state.
|
// set repeatedly to 0, regardless of where the current foreground window is actually
|
||||||
//
|
// located.
|
||||||
// Due to this, at least one user in the Discord has witnessed behaviour where, when
|
//
|
||||||
// a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets
|
// This check ensures that we only update the focused monitor when the window
|
||||||
// set repeatedly to 0, regardless of where the current foreground window is actually
|
// triggering monitor reconciliation is known to not be tied to a specific monitor.
|
||||||
// located.
|
if let Ok(class) = window.class() {
|
||||||
//
|
if class != "OleMainThreadWndClass"
|
||||||
// This check ensures that we only update the focused monitor when the window
|
&& self.focused_monitor_idx() != monitor_idx
|
||||||
// triggering monitor reconciliation is known to not be tied to a specific monitor.
|
{
|
||||||
if window.class()? != "OleMainThreadWndClass"
|
self.focus_monitor(monitor_idx)?;
|
||||||
&& self.focused_monitor_idx() != monitor_idx
|
}
|
||||||
{
|
}
|
||||||
self.focus_monitor(monitor_idx)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -443,10 +442,7 @@ 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)
|
let right_bottom_constant = 0;
|
||||||
+ BORDER_OFFSET.load(Ordering::SeqCst))
|
|
||||||
* 2)
|
|
||||||
.abs();
|
|
||||||
|
|
||||||
let is_move = moved_across_monitors
|
let is_move = moved_across_monitors
|
||||||
|| resize.right.abs() == right_bottom_constant
|
|| resize.right.abs() == right_bottom_constant
|
||||||
@@ -586,9 +582,7 @@ impl WindowManager {
|
|||||||
WindowManagerEvent::ForceUpdate(_) => {
|
WindowManagerEvent::ForceUpdate(_) => {
|
||||||
self.update_focused_workspace(false, true)?;
|
self.update_focused_workspace(false, true)?;
|
||||||
}
|
}
|
||||||
WindowManagerEvent::DisplayChange(..)
|
WindowManagerEvent::MouseCapture(..) | WindowManagerEvent::Cloak(..) => {}
|
||||||
| WindowManagerEvent::MouseCapture(..)
|
|
||||||
| WindowManagerEvent::Cloak(..) => {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
||||||
@@ -624,6 +618,7 @@ impl WindowManager {
|
|||||||
|
|
||||||
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
notify_subscribers(&serde_json::to_string(¬ification)?)?;
|
||||||
border_manager::event_tx().send(border_manager::Notification)?;
|
border_manager::event_tx().send(border_manager::Notification)?;
|
||||||
|
stackbar_manager::event_tx().send(stackbar_manager::Notification)?;
|
||||||
|
|
||||||
tracing::info!("processed: {}", event.window().to_string());
|
tracing::info!("processed: {}", event.window().to_string());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,267 +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::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::WINDOWS_BY_BAR_HWNDS;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct Stackbar {
|
|
||||||
pub(crate) hwnd: isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 };
|
|
||||||
window.restore();
|
|
||||||
if let Err(err) = window.focus(false) {
|
|
||||||
tracing::error!("Stackbar focus error: HWND:{} {}", *win_hwnd, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(0),
|
|
||||||
..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(0), 0, LWA_COLORKEY)?;
|
|
||||||
hwnd_sender.send(hwnd)?;
|
|
||||||
|
|
||||||
let mut msg = MSG::default();
|
|
||||||
while GetMessageW(&mut msg, hwnd, 0, 0).into() {
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessageW(&msg);
|
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
hwnd: hwnd_receiver.recv()?.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn 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));
|
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
189
komorebi/src/stackbar_manager/mod.rs
Normal file
189
komorebi/src/stackbar_manager/mod.rs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
mod stackbar;
|
||||||
|
|
||||||
|
use crate::container::Container;
|
||||||
|
use crate::stackbar_manager::stackbar::Stackbar;
|
||||||
|
use crate::WindowManager;
|
||||||
|
use crate::WindowsApi;
|
||||||
|
use crate::DEFAULT_CONTAINER_PADDING;
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
|
use crossbeam_utils::atomic::AtomicCell;
|
||||||
|
use crossbeam_utils::atomic::AtomicConsume;
|
||||||
|
use komorebi_core::StackbarLabel;
|
||||||
|
use komorebi_core::StackbarMode;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::atomic::AtomicI32;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
|
||||||
|
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
|
||||||
|
pub static STACKBAR_UNFOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(11776947); // gray text
|
||||||
|
pub static STACKBAR_TAB_BACKGROUND_COLOUR: AtomicU32 = AtomicU32::new(3355443); // gray
|
||||||
|
pub static STACKBAR_TAB_HEIGHT: AtomicI32 = AtomicI32::new(40);
|
||||||
|
pub static STACKBAR_TAB_WIDTH: AtomicI32 = AtomicI32::new(200);
|
||||||
|
pub static STACKBAR_LABEL: AtomicCell<StackbarLabel> = AtomicCell::new(StackbarLabel::Process);
|
||||||
|
pub static STACKBAR_MODE: AtomicCell<StackbarMode> = AtomicCell::new(StackbarMode::OnStack);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref STACKBAR_STATE: Mutex<HashMap<String, Stackbar>> = Mutex::new(HashMap::new());
|
||||||
|
static ref STACKBARS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
|
||||||
|
static ref STACKBARS_CONTAINERS: Mutex<HashMap<isize, Container>> = Mutex::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Notification;
|
||||||
|
|
||||||
|
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||||
|
CHANNEL.get_or_init(crossbeam_channel::unbounded)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_tx() -> Sender<Notification> {
|
||||||
|
channel().0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_rx() -> Receiver<Notification> {
|
||||||
|
channel().1.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_have_stackbar(window_count: usize) -> bool {
|
||||||
|
match STACKBAR_MODE.load() {
|
||||||
|
StackbarMode::Always => true,
|
||||||
|
StackbarMode::OnStack => window_count > 1,
|
||||||
|
StackbarMode::Never => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||||
|
std::thread::spawn(move || loop {
|
||||||
|
match handle_notifications(wm.clone()) {
|
||||||
|
Ok(()) => {
|
||||||
|
tracing::warn!("restarting finished thread");
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!("restarting failed thread: {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||||
|
tracing::info!("listening");
|
||||||
|
|
||||||
|
let receiver = event_rx();
|
||||||
|
|
||||||
|
'receiver: for _ in receiver {
|
||||||
|
let mut stackbars = STACKBAR_STATE.lock();
|
||||||
|
let mut stackbars_monitors = STACKBARS_MONITORS.lock();
|
||||||
|
|
||||||
|
// Check the wm state every time we receive a notification
|
||||||
|
let mut state = wm.lock();
|
||||||
|
|
||||||
|
// If stackbars are disabled
|
||||||
|
if matches!(STACKBAR_MODE.load(), StackbarMode::Never) {
|
||||||
|
for (_, stackbar) in stackbars.iter() {
|
||||||
|
stackbar.destroy()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
stackbars.clear();
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (monitor_idx, m) in state.monitors_mut().iter_mut().enumerate() {
|
||||||
|
// Only operate on the focused workspace of each monitor
|
||||||
|
if let Some(ws) = m.focused_workspace_mut() {
|
||||||
|
// Workspaces with tiling disabled don't have stackbars
|
||||||
|
if !ws.tile() {
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
for (id, border) in stackbars.iter() {
|
||||||
|
if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
||||||
|
border.destroy()?;
|
||||||
|
to_remove.push(id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in &to_remove {
|
||||||
|
stackbars.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||||
|
WindowsApi::foreground_window().unwrap_or_default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Handle the monocle container separately
|
||||||
|
if ws.monocle_container().is_some() || is_maximized {
|
||||||
|
// Destroy any stackbars associated with the focused workspace
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
for (id, stackbar) in stackbars.iter() {
|
||||||
|
if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
||||||
|
stackbar.destroy()?;
|
||||||
|
to_remove.push(id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in &to_remove {
|
||||||
|
stackbars.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
let container_padding = ws
|
||||||
|
.container_padding()
|
||||||
|
.unwrap_or_else(|| DEFAULT_CONTAINER_PADDING.load_consume());
|
||||||
|
|
||||||
|
'containers: for container in ws.containers_mut() {
|
||||||
|
let should_add_stackbar = match STACKBAR_MODE.load() {
|
||||||
|
StackbarMode::Always => true,
|
||||||
|
StackbarMode::OnStack => container.windows().len() > 1,
|
||||||
|
StackbarMode::Never => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !should_add_stackbar {
|
||||||
|
if let Some(stackbar) = stackbars.get(container.id()) {
|
||||||
|
stackbar.destroy()?
|
||||||
|
}
|
||||||
|
|
||||||
|
stackbars.remove(container.id());
|
||||||
|
stackbars_monitors.remove(container.id());
|
||||||
|
continue 'containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stackbar entry for this container from the map or create one
|
||||||
|
let stackbar = match stackbars.entry(container.id().clone()) {
|
||||||
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
if let Ok(stackbar) = Stackbar::create(container.id()) {
|
||||||
|
entry.insert(stackbar)
|
||||||
|
} else {
|
||||||
|
continue 'receiver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
stackbars_monitors.insert(container.id().clone(), monitor_idx);
|
||||||
|
|
||||||
|
let rect = WindowsApi::window_rect(
|
||||||
|
container
|
||||||
|
.focused_window()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.hwnd(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
stackbar.update(container_padding, container, &rect)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
328
komorebi/src/stackbar_manager/stackbar.rs
Normal file
328
komorebi/src/stackbar_manager/stackbar.rs
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
use crate::border_manager::BORDER_OFFSET;
|
||||||
|
use crate::border_manager::BORDER_WIDTH;
|
||||||
|
use crate::border_manager::STYLE;
|
||||||
|
use crate::container::Container;
|
||||||
|
use crate::stackbar_manager::STACKBARS_CONTAINERS;
|
||||||
|
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::stackbar_manager::STACKBAR_LABEL;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
|
||||||
|
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::WindowsApi;
|
||||||
|
use crate::DEFAULT_CONTAINER_PADDING;
|
||||||
|
use crate::WINDOWS_11;
|
||||||
|
use crossbeam_utils::atomic::AtomicConsume;
|
||||||
|
use komorebi_core::BorderStyle;
|
||||||
|
use komorebi_core::Rect;
|
||||||
|
use komorebi_core::StackbarLabel;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::Win32::Foundation::COLORREF;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::Foundation::LPARAM;
|
||||||
|
use windows::Win32::Foundation::LRESULT;
|
||||||
|
use windows::Win32::Foundation::WPARAM;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreateFontIndirectW;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreatePen;
|
||||||
|
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
||||||
|
use windows::Win32::Graphics::Gdi::DrawTextW;
|
||||||
|
use windows::Win32::Graphics::Gdi::GetDC;
|
||||||
|
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||||
|
use windows::Win32::Graphics::Gdi::ReleaseDC;
|
||||||
|
use windows::Win32::Graphics::Gdi::RoundRect;
|
||||||
|
use windows::Win32::Graphics::Gdi::SelectObject;
|
||||||
|
use windows::Win32::Graphics::Gdi::SetBkColor;
|
||||||
|
use windows::Win32::Graphics::Gdi::SetTextColor;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_CENTER;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
|
||||||
|
use windows::Win32::Graphics::Gdi::DT_VCENTER;
|
||||||
|
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
|
||||||
|
use windows::Win32::Graphics::Gdi::FW_BOLD;
|
||||||
|
use windows::Win32::Graphics::Gdi::LOGFONTW;
|
||||||
|
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
|
||||||
|
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stackbar {
|
||||||
|
pub hwnd: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<isize> for Stackbar {
|
||||||
|
fn from(value: isize) -> Self {
|
||||||
|
Self { hwnd: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stackbar {
|
||||||
|
pub const fn hwnd(&self) -> HWND {
|
||||||
|
HWND(self.hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(id: &str) -> color_eyre::Result<Self> {
|
||||||
|
let name: Vec<u16> = format!("komostackbar-{id}\0").encode_utf16().collect();
|
||||||
|
let class_name = PCWSTR(name.as_ptr());
|
||||||
|
|
||||||
|
let h_module = WindowsApi::module_handle_w()?;
|
||||||
|
|
||||||
|
let window_class = WNDCLASSW {
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
|
lpfnWndProc: Some(Self::callback),
|
||||||
|
hInstance: h_module.into(),
|
||||||
|
lpszClassName: class_name,
|
||||||
|
hbrBackground: WindowsApi::create_solid_brush(0),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = WindowsApi::register_class_w(&window_class);
|
||||||
|
|
||||||
|
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
|
||||||
|
|
||||||
|
let name_cl = name.clone();
|
||||||
|
std::thread::spawn(move || -> color_eyre::Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let hwnd = CreateWindowExW(
|
||||||
|
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
|
||||||
|
PCWSTR(name_cl.as_ptr()),
|
||||||
|
PCWSTR(name_cl.as_ptr()),
|
||||||
|
WS_POPUP | WS_VISIBLE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
h_module,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?;
|
||||||
|
hwnd_sender.send(hwnd)?;
|
||||||
|
|
||||||
|
let mut msg = MSG::default();
|
||||||
|
while GetMessageW(&mut msg, hwnd, 0, 0).into() {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageW(&msg);
|
||||||
|
std::thread::sleep(Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
hwnd: hwnd_receiver.recv()?.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&self) -> color_eyre::Result<()> {
|
||||||
|
WindowsApi::close_window(self.hwnd())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
&self,
|
||||||
|
container_padding: i32,
|
||||||
|
container: &mut Container,
|
||||||
|
layout: &Rect,
|
||||||
|
) -> color_eyre::Result<()> {
|
||||||
|
let width = STACKBAR_TAB_WIDTH.load_consume();
|
||||||
|
let height = STACKBAR_TAB_HEIGHT.load_consume();
|
||||||
|
let gap = DEFAULT_CONTAINER_PADDING.load_consume();
|
||||||
|
let background = STACKBAR_TAB_BACKGROUND_COLOUR.load_consume();
|
||||||
|
let focused_text_colour = STACKBAR_FOCUSED_TEXT_COLOUR.load_consume();
|
||||||
|
let unfocused_text_colour = STACKBAR_UNFOCUSED_TEXT_COLOUR.load_consume();
|
||||||
|
|
||||||
|
let mut stackbars_containers = STACKBARS_CONTAINERS.lock();
|
||||||
|
stackbars_containers.insert(self.hwnd, container.clone());
|
||||||
|
|
||||||
|
let mut layout = *layout;
|
||||||
|
let workspace_specific_offset =
|
||||||
|
BORDER_WIDTH.load_consume() + BORDER_OFFSET.load_consume() + container_padding;
|
||||||
|
|
||||||
|
layout.top -= workspace_specific_offset + STACKBAR_TAB_HEIGHT.load_consume();
|
||||||
|
layout.left -= workspace_specific_offset;
|
||||||
|
|
||||||
|
WindowsApi::position_window(self.hwnd(), &layout, false)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let hdc = GetDC(self.hwnd());
|
||||||
|
|
||||||
|
let hpen = CreatePen(PS_SOLID, 0, COLORREF(background));
|
||||||
|
let hbrush = CreateSolidBrush(COLORREF(background));
|
||||||
|
|
||||||
|
SelectObject(hdc, hpen);
|
||||||
|
SelectObject(hdc, hbrush);
|
||||||
|
SetBkColor(hdc, COLORREF(background));
|
||||||
|
|
||||||
|
let hfont = CreateFontIndirectW(&LOGFONTW {
|
||||||
|
lfWeight: FW_BOLD.0 as i32,
|
||||||
|
lfQuality: FONT_QUALITY(PROOF_QUALITY.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
SelectObject(hdc, hfont);
|
||||||
|
|
||||||
|
for (i, window) in container.windows().iter().enumerate() {
|
||||||
|
if window.hwnd == container.focused_window().copied().unwrap_or_default().hwnd {
|
||||||
|
SetTextColor(hdc, COLORREF(focused_text_colour));
|
||||||
|
} else {
|
||||||
|
SetTextColor(hdc, COLORREF(unfocused_text_colour));
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = gap + (i as i32 * (width + gap));
|
||||||
|
let mut rect = Rect {
|
||||||
|
top: 0,
|
||||||
|
left,
|
||||||
|
right: left + width,
|
||||||
|
bottom: height,
|
||||||
|
};
|
||||||
|
|
||||||
|
match *STYLE.lock() {
|
||||||
|
BorderStyle::System => {
|
||||||
|
if *WINDOWS_11 {
|
||||||
|
RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, 20, 20);
|
||||||
|
} else {
|
||||||
|
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BorderStyle::Rounded => {
|
||||||
|
RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, 20, 20);
|
||||||
|
}
|
||||||
|
BorderStyle::Square => {
|
||||||
|
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let label = match STACKBAR_LABEL.load() {
|
||||||
|
StackbarLabel::Process => {
|
||||||
|
let exe = window.exe()?;
|
||||||
|
exe.trim_end_matches(".exe").to_string()
|
||||||
|
}
|
||||||
|
StackbarLabel::Title => window.title()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tab_title: Vec<u16> = label.encode_utf16().collect();
|
||||||
|
|
||||||
|
rect.left_padding(10);
|
||||||
|
rect.right_padding(10);
|
||||||
|
|
||||||
|
DrawTextW(
|
||||||
|
hdc,
|
||||||
|
&mut tab_title,
|
||||||
|
&mut rect.into(),
|
||||||
|
DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseDC(self.hwnd(), hdc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_position_from_container_layout(&self, layout: &Rect) -> Rect {
|
||||||
|
Rect {
|
||||||
|
bottom: STACKBAR_TAB_HEIGHT.load_consume(),
|
||||||
|
..*layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "system" fn callback(
|
||||||
|
hwnd: HWND,
|
||||||
|
msg: u32,
|
||||||
|
w_param: WPARAM,
|
||||||
|
l_param: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
unsafe {
|
||||||
|
match msg {
|
||||||
|
WM_LBUTTONDOWN => {
|
||||||
|
let stackbars_containers = STACKBARS_CONTAINERS.lock();
|
||||||
|
if let Some(container) = stackbars_containers.get(&hwnd.0) {
|
||||||
|
let x = l_param.0 as i32 & 0xFFFF;
|
||||||
|
let y = (l_param.0 as i32 >> 16) & 0xFFFF;
|
||||||
|
|
||||||
|
let width = STACKBAR_TAB_WIDTH.load_consume();
|
||||||
|
let height = STACKBAR_TAB_HEIGHT.load_consume();
|
||||||
|
let gap = DEFAULT_CONTAINER_PADDING.load_consume();
|
||||||
|
|
||||||
|
let focused_window_idx = container.focused_window_idx();
|
||||||
|
let focused_window_rect = WindowsApi::window_rect(
|
||||||
|
container
|
||||||
|
.focused_window()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.hwnd(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
for (index, window) in container.windows().iter().enumerate() {
|
||||||
|
let left = gap + (index as i32 * (width + gap));
|
||||||
|
let right = left + width;
|
||||||
|
let top = 0;
|
||||||
|
let bottom = height;
|
||||||
|
|
||||||
|
if x >= left && x <= right && y >= top && y <= bottom {
|
||||||
|
// If we are focusing a window that isn't currently focused in the
|
||||||
|
// stackbar, make sure we update its location so that it doesn't render
|
||||||
|
// on top of other tiles before eventually ending up in the correct
|
||||||
|
// tile
|
||||||
|
if index != focused_window_idx {
|
||||||
|
if let Err(err) =
|
||||||
|
window.set_position(&focused_window_rect, false)
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
|
||||||
|
*window,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the window corresponding to the tab we have clicked
|
||||||
|
window.restore();
|
||||||
|
if let Err(err) = window.focus(false) {
|
||||||
|
tracing::error!(
|
||||||
|
"stackbar WMLBUTTONDOWN focus error: hwnd {} ({})",
|
||||||
|
*window,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Hide any windows in the stack that don't correspond to the window
|
||||||
|
// we have clicked
|
||||||
|
window.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
WM_DESTROY => {
|
||||||
|
PostQuitMessage(0);
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
_ => DefWindowProcW(hwnd, msg, w_param, l_param),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,15 @@ use crate::border_manager::Z_ORDER;
|
|||||||
use crate::colour::Colour;
|
use crate::colour::Colour;
|
||||||
use crate::current_virtual_desktop;
|
use crate::current_virtual_desktop;
|
||||||
use crate::monitor::Monitor;
|
use crate::monitor::Monitor;
|
||||||
|
use crate::monitor_reconciliator;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
|
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::stackbar_manager::STACKBAR_LABEL;
|
||||||
|
use crate::stackbar_manager::STACKBAR_MODE;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
|
||||||
|
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
@@ -21,14 +29,9 @@ use crate::MANAGE_IDENTIFIERS;
|
|||||||
use crate::MONITOR_INDEX_PREFERENCES;
|
use crate::MONITOR_INDEX_PREFERENCES;
|
||||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||||
use crate::REGEX_IDENTIFIERS;
|
use crate::REGEX_IDENTIFIERS;
|
||||||
use crate::STACKBAR_FOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::STACKBAR_MODE;
|
|
||||||
use crate::STACKBAR_TAB_BACKGROUND_COLOUR;
|
|
||||||
use crate::STACKBAR_TAB_HEIGHT;
|
|
||||||
use crate::STACKBAR_TAB_WIDTH;
|
|
||||||
use crate::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||||
use crate::WORKSPACE_RULES;
|
use crate::WORKSPACE_RULES;
|
||||||
|
use komorebi_core::StackbarLabel;
|
||||||
use komorebi_core::StackbarMode;
|
use komorebi_core::StackbarMode;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -42,8 +45,8 @@ use komorebi_core::config_generation::IdWithIdentifier;
|
|||||||
use komorebi_core::config_generation::MatchingRule;
|
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::ActiveWindowBorderStyle;
|
|
||||||
use komorebi_core::ApplicationIdentifier;
|
use komorebi_core::ApplicationIdentifier;
|
||||||
|
use komorebi_core::BorderStyle;
|
||||||
use komorebi_core::DefaultLayout;
|
use komorebi_core::DefaultLayout;
|
||||||
use komorebi_core::FocusFollowsMouseImplementation;
|
use komorebi_core::FocusFollowsMouseImplementation;
|
||||||
use komorebi_core::HidingBehaviour;
|
use komorebi_core::HidingBehaviour;
|
||||||
@@ -69,7 +72,7 @@ use uds_windows::UnixListener;
|
|||||||
use uds_windows::UnixStream;
|
use uds_windows::UnixStream;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct ActiveWindowBorderColours {
|
pub struct BorderColours {
|
||||||
/// Border colour when the container contains a single window
|
/// Border colour when the container contains a single window
|
||||||
pub single: Option<Colour>,
|
pub single: Option<Colour>,
|
||||||
/// Border colour when the container contains multiple windows
|
/// Border colour when the container contains multiple windows
|
||||||
@@ -80,7 +83,7 @@ pub struct ActiveWindowBorderColours {
|
|||||||
pub unfocused: Option<Colour>,
|
pub unfocused: Option<Colour>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct WorkspaceConfig {
|
pub struct WorkspaceConfig {
|
||||||
/// Name
|
/// Name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -194,7 +197,7 @@ impl From<&Workspace> for WorkspaceConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct MonitorConfig {
|
pub struct MonitorConfig {
|
||||||
/// Workspace configurations
|
/// Workspace configurations
|
||||||
pub workspaces: Vec<WorkspaceConfig>,
|
pub workspaces: Vec<WorkspaceConfig>,
|
||||||
@@ -262,16 +265,19 @@ pub struct StaticConfig {
|
|||||||
pub border_offset: Option<i32>,
|
pub border_offset: Option<i32>,
|
||||||
/// Display an active window border (default: false)
|
/// Display an active window border (default: false)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub active_window_border: Option<bool>,
|
#[serde(alias = "active_window_border")]
|
||||||
|
pub border: Option<bool>,
|
||||||
/// Active window border colours for different container types
|
/// Active window border colours for different container types
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub active_window_border_colours: Option<ActiveWindowBorderColours>,
|
#[serde(alias = "active_window_border_colours")]
|
||||||
|
pub border_colours: Option<BorderColours>,
|
||||||
/// Active window border style (default: System)
|
/// Active window border style (default: System)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub active_window_border_style: Option<ActiveWindowBorderStyle>,
|
#[serde(alias = "active_window_border_style")]
|
||||||
|
pub border_style: Option<BorderStyle>,
|
||||||
/// Active window border z-order (default: System)
|
/// Active window border z-order (default: System)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub active_window_border_z_order: Option<ZOrder>,
|
pub border_z_order: Option<ZOrder>,
|
||||||
/// Global default workspace padding (default: 10)
|
/// Global default workspace padding (default: 10)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub default_workspace_padding: Option<i32>,
|
pub default_workspace_padding: Option<i32>,
|
||||||
@@ -327,11 +333,12 @@ pub struct TabsConfig {
|
|||||||
/// Tab background colour
|
/// Tab background colour
|
||||||
background: Option<Colour>,
|
background: Option<Colour>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct StackbarConfig {
|
pub struct StackbarConfig {
|
||||||
/// Stackbar height
|
/// Stackbar height
|
||||||
pub height: Option<i32>,
|
pub height: Option<i32>,
|
||||||
|
/// Stackbar height
|
||||||
|
pub label: Option<StackbarLabel>,
|
||||||
/// Stackbar mode
|
/// Stackbar mode
|
||||||
pub mode: Option<StackbarMode>,
|
pub mode: Option<StackbarMode>,
|
||||||
/// Stackbar tab configuration options
|
/// Stackbar tab configuration options
|
||||||
@@ -394,7 +401,7 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 {
|
let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Option::from(ActiveWindowBorderColours {
|
Option::from(BorderColours {
|
||||||
single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))),
|
single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))),
|
||||||
stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))),
|
stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))),
|
||||||
monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))),
|
monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))),
|
||||||
@@ -417,12 +424,10 @@ impl From<&WindowManager> for StaticConfig {
|
|||||||
app_specific_configuration_path: None,
|
app_specific_configuration_path: None,
|
||||||
border_width: Option::from(border_manager::BORDER_WIDTH.load(Ordering::SeqCst)),
|
border_width: Option::from(border_manager::BORDER_WIDTH.load(Ordering::SeqCst)),
|
||||||
border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)),
|
border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)),
|
||||||
active_window_border: Option::from(
|
border: Option::from(border_manager::BORDER_ENABLED.load(Ordering::SeqCst)),
|
||||||
border_manager::BORDER_ENABLED.load(Ordering::SeqCst),
|
border_colours,
|
||||||
),
|
border_style: Option::from(*STYLE.lock()),
|
||||||
active_window_border_colours: border_colours,
|
border_z_order: Option::from(*Z_ORDER.lock()),
|
||||||
active_window_border_style: Option::from(*STYLE.lock()),
|
|
||||||
active_window_border_z_order: Option::from(*Z_ORDER.lock()),
|
|
||||||
default_workspace_padding: Option::from(
|
default_workspace_padding: Option::from(
|
||||||
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
||||||
),
|
),
|
||||||
@@ -474,11 +479,11 @@ impl StaticConfig {
|
|||||||
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst);
|
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst);
|
||||||
border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
||||||
|
|
||||||
if let Some(enabled) = &self.active_window_border {
|
if let Some(enabled) = &self.border {
|
||||||
border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst);
|
border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(colours) = &self.active_window_border_colours {
|
if let Some(colours) = &self.border_colours {
|
||||||
if let Some(single) = colours.single {
|
if let Some(single) = colours.single {
|
||||||
border_manager::FOCUSED.store(u32::from(single), Ordering::SeqCst);
|
border_manager::FOCUSED.store(u32::from(single), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
@@ -496,8 +501,8 @@ impl StaticConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_window_border_style = self.active_window_border_style.unwrap_or_default();
|
let border_style = self.border_style.unwrap_or_default();
|
||||||
*STYLE.lock() = active_window_border_style;
|
*STYLE.lock() = border_style;
|
||||||
|
|
||||||
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
|
||||||
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
|
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
|
||||||
@@ -539,9 +544,12 @@ impl StaticConfig {
|
|||||||
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
|
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(label) = &stackbar.label {
|
||||||
|
STACKBAR_LABEL.store(*label);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(mode) = &stackbar.mode {
|
if let Some(mode) = &stackbar.mode {
|
||||||
let mut stackbar_mode = STACKBAR_MODE.lock();
|
STACKBAR_MODE.store(*mode);
|
||||||
*stackbar_mode = *mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tabs) = &stackbar.tabs {
|
if let Some(tabs) = &stackbar.tabs {
|
||||||
@@ -641,7 +649,6 @@ impl StaticConfig {
|
|||||||
|
|
||||||
let mut wm = WindowManager {
|
let mut wm = WindowManager {
|
||||||
monitors: Ring::default(),
|
monitors: Ring::default(),
|
||||||
monitor_cache: HashMap::new(),
|
|
||||||
incoming_events: incoming,
|
incoming_events: incoming,
|
||||||
command_listener: listener,
|
command_listener: listener,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
@@ -699,6 +706,13 @@ impl StaticConfig {
|
|||||||
|
|
||||||
if let Some(monitors) = value.monitors {
|
if let Some(monitors) = value.monitors {
|
||||||
for (i, monitor) in monitors.iter().enumerate() {
|
for (i, monitor) in monitors.iter().enumerate() {
|
||||||
|
{
|
||||||
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||||
|
if let Some(device_id) = display_index_preferences.get(&i) {
|
||||||
|
monitor_reconciliator::insert_in_monitor_cache(device_id, monitor.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
||||||
m.ensure_workspace_count(monitor.workspaces.len());
|
m.ensure_workspace_count(monitor.workspaces.len());
|
||||||
m.set_work_area_offset(monitor.work_area_offset);
|
m.set_work_area_offset(monitor.work_area_offset);
|
||||||
@@ -733,7 +747,7 @@ impl StaticConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.active_window_border == Some(true) {
|
if value.border == Some(true) {
|
||||||
border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst);
|
border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,16 +760,6 @@ impl StaticConfig {
|
|||||||
|
|
||||||
value.apply_globals()?;
|
value.apply_globals()?;
|
||||||
|
|
||||||
let stackbar_mode = *STACKBAR_MODE.lock();
|
|
||||||
|
|
||||||
for m in wm.monitors_mut() {
|
|
||||||
for w in m.workspaces_mut() {
|
|
||||||
for c in w.containers_mut() {
|
|
||||||
c.set_stackbar_mode(stackbar_mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(monitors) = value.monitors {
|
if let Some(monitors) = value.monitors {
|
||||||
for (i, monitor) in monitors.iter().enumerate() {
|
for (i, monitor) in monitors.iter().enumerate() {
|
||||||
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
if let Some(m) = wm.monitors_mut().get_mut(i) {
|
||||||
@@ -792,7 +796,7 @@ impl StaticConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(enabled) = value.active_window_border {
|
if let Some(enabled) = value.border {
|
||||||
border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst);
|
border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,11 @@ impl Window {
|
|||||||
// If the target window is already focused, do nothing.
|
// If the target window is already focused, do nothing.
|
||||||
if let Ok(ihwnd) = WindowsApi::foreground_window() {
|
if let Ok(ihwnd) = WindowsApi::foreground_window() {
|
||||||
if HWND(ihwnd) == self.hwnd() {
|
if HWND(ihwnd) == self.hwnd() {
|
||||||
|
// Center cursor in Window
|
||||||
|
if mouse_follows_focus {
|
||||||
|
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?;
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ use uds_windows::UnixListener;
|
|||||||
|
|
||||||
use komorebi_core::config_generation::MatchingRule;
|
use komorebi_core::config_generation::MatchingRule;
|
||||||
use komorebi_core::custom_layout::CustomLayout;
|
use komorebi_core::custom_layout::CustomLayout;
|
||||||
use komorebi_core::ActiveWindowBorderStyle;
|
|
||||||
use komorebi_core::Arrangement;
|
use komorebi_core::Arrangement;
|
||||||
use komorebi_core::Axis;
|
use komorebi_core::Axis;
|
||||||
|
use komorebi_core::BorderStyle;
|
||||||
use komorebi_core::CycleDirection;
|
use komorebi_core::CycleDirection;
|
||||||
use komorebi_core::DefaultLayout;
|
use komorebi_core::DefaultLayout;
|
||||||
use komorebi_core::FocusFollowsMouseImplementation;
|
use komorebi_core::FocusFollowsMouseImplementation;
|
||||||
@@ -46,13 +46,19 @@ use crate::current_virtual_desktop;
|
|||||||
use crate::load_configuration;
|
use crate::load_configuration;
|
||||||
use crate::monitor::Monitor;
|
use crate::monitor::Monitor;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
|
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||||
|
use crate::stackbar_manager::STACKBAR_MODE;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
|
||||||
|
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||||
use crate::static_config::StaticConfig;
|
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;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
use crate::ActiveWindowBorderColours;
|
use crate::BorderColours;
|
||||||
use crate::Colour;
|
use crate::Colour;
|
||||||
use crate::Rgb;
|
use crate::Rgb;
|
||||||
use crate::WorkspaceRule;
|
use crate::WorkspaceRule;
|
||||||
@@ -68,12 +74,6 @@ use crate::MONITOR_INDEX_PREFERENCES;
|
|||||||
use crate::NO_TITLEBAR;
|
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::STACKBAR_FOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::STACKBAR_MODE;
|
|
||||||
use crate::STACKBAR_TAB_BACKGROUND_COLOUR;
|
|
||||||
use crate::STACKBAR_TAB_HEIGHT;
|
|
||||||
use crate::STACKBAR_TAB_WIDTH;
|
|
||||||
use crate::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
|
||||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||||
use crate::WORKSPACE_RULES;
|
use crate::WORKSPACE_RULES;
|
||||||
use komorebi_core::StackbarMode;
|
use komorebi_core::StackbarMode;
|
||||||
@@ -81,7 +81,6 @@ use komorebi_core::StackbarMode;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WindowManager {
|
pub struct WindowManager {
|
||||||
pub monitors: Ring<Monitor>,
|
pub monitors: Ring<Monitor>,
|
||||||
pub monitor_cache: HashMap<usize, Monitor>,
|
|
||||||
pub incoming_events: Receiver<WindowManagerEvent>,
|
pub incoming_events: Receiver<WindowManagerEvent>,
|
||||||
pub command_listener: UnixListener,
|
pub command_listener: UnixListener,
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
@@ -117,9 +116,9 @@ pub struct State {
|
|||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct GlobalState {
|
pub struct GlobalState {
|
||||||
pub active_window_border_enabled: bool,
|
pub border_enabled: bool,
|
||||||
pub active_window_border_colours: ActiveWindowBorderColours,
|
pub border_colours: BorderColours,
|
||||||
pub active_window_border_style: ActiveWindowBorderStyle,
|
pub border_style: BorderStyle,
|
||||||
pub border_offset: i32,
|
pub border_offset: i32,
|
||||||
pub border_width: i32,
|
pub border_width: i32,
|
||||||
pub stackbar_mode: StackbarMode,
|
pub stackbar_mode: StackbarMode,
|
||||||
@@ -146,8 +145,8 @@ pub struct GlobalState {
|
|||||||
impl Default for GlobalState {
|
impl Default for GlobalState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
active_window_border_enabled: border_manager::BORDER_ENABLED.load(Ordering::SeqCst),
|
border_enabled: border_manager::BORDER_ENABLED.load(Ordering::SeqCst),
|
||||||
active_window_border_colours: ActiveWindowBorderColours {
|
border_colours: BorderColours {
|
||||||
single: Option::from(Colour::Rgb(Rgb::from(
|
single: Option::from(Colour::Rgb(Rgb::from(
|
||||||
border_manager::FOCUSED.load(Ordering::SeqCst),
|
border_manager::FOCUSED.load(Ordering::SeqCst),
|
||||||
))),
|
))),
|
||||||
@@ -161,10 +160,10 @@ impl Default for GlobalState {
|
|||||||
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
||||||
))),
|
))),
|
||||||
},
|
},
|
||||||
active_window_border_style: *STYLE.lock(),
|
border_style: *STYLE.lock(),
|
||||||
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
|
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
|
||||||
border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst),
|
border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst),
|
||||||
stackbar_mode: *STACKBAR_MODE.lock(),
|
stackbar_mode: STACKBAR_MODE.load(),
|
||||||
stackbar_focused_text_colour: Colour::Rgb(Rgb::from(
|
stackbar_focused_text_colour: Colour::Rgb(Rgb::from(
|
||||||
STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
|
STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),
|
||||||
)),
|
)),
|
||||||
@@ -262,7 +261,6 @@ impl WindowManager {
|
|||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
monitors: Ring::default(),
|
monitors: Ring::default(),
|
||||||
monitor_cache: HashMap::new(),
|
|
||||||
incoming_events: incoming,
|
incoming_events: incoming,
|
||||||
command_listener: listener,
|
command_listener: listener,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
@@ -385,155 +383,6 @@ impl WindowManager {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
pub fn reconcile_monitors(&mut self) -> Result<()> {
|
|
||||||
if self.pending_move_op.is_some() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid_hmonitors = WindowsApi::valid_hmonitors()?;
|
|
||||||
let mut valid_names = vec![];
|
|
||||||
let before_count = self.monitors().len();
|
|
||||||
|
|
||||||
for monitor in self.monitors_mut() {
|
|
||||||
for (valid_name, valid_id) in &valid_hmonitors {
|
|
||||||
let actual_name = monitor.name().clone();
|
|
||||||
if actual_name == *valid_name {
|
|
||||||
monitor.set_id(*valid_id);
|
|
||||||
valid_names.push(actual_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut orphaned_containers = vec![];
|
|
||||||
let mut invalid_indices = vec![];
|
|
||||||
|
|
||||||
for (i, invalid) in self
|
|
||||||
.monitors()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, m)| !valid_names.contains(m.name()))
|
|
||||||
{
|
|
||||||
invalid_indices.push(i);
|
|
||||||
for workspace in invalid.workspaces() {
|
|
||||||
for container in workspace.containers() {
|
|
||||||
// Save the orphaned containers from an invalid monitor
|
|
||||||
// (which has most likely been disconnected)
|
|
||||||
orphaned_containers.push(container.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in invalid_indices {
|
|
||||||
if let Some(monitor) = self.monitors().get(i) {
|
|
||||||
self.monitor_cache.insert(i, monitor.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any invalid monitors from our state
|
|
||||||
self.monitors_mut()
|
|
||||||
.retain(|m| valid_names.contains(m.name()));
|
|
||||||
|
|
||||||
let after_count = self.monitors().len();
|
|
||||||
|
|
||||||
if let Some(primary) = self.monitors_mut().front_mut() {
|
|
||||||
if let Some(focused_ws) = primary.focused_workspace_mut() {
|
|
||||||
let focused_container_idx = focused_ws.focused_container_idx();
|
|
||||||
|
|
||||||
// Put the orphaned containers somewhere visible
|
|
||||||
for container in orphaned_containers {
|
|
||||||
focused_ws.add_container(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gotta reset the focus or the movement will feel "off"
|
|
||||||
if before_count != after_count {
|
|
||||||
focused_ws.focus_container(focused_container_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = self.work_area_offset;
|
|
||||||
|
|
||||||
for monitor in self.monitors_mut() {
|
|
||||||
// If we have lost a monitor, update everything to filter out any jank
|
|
||||||
let mut should_update = before_count != after_count;
|
|
||||||
|
|
||||||
let reference = WindowsApi::monitor(monitor.id())?;
|
|
||||||
if reference.work_area_size() != monitor.work_area_size() {
|
|
||||||
monitor.set_work_area_size(Rect {
|
|
||||||
left: reference.work_area_size().left,
|
|
||||||
top: reference.work_area_size().top,
|
|
||||||
right: reference.work_area_size().right,
|
|
||||||
bottom: reference.work_area_size().bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
should_update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if reference.size() != monitor.size() {
|
|
||||||
monitor.set_size(Rect {
|
|
||||||
left: reference.size().left,
|
|
||||||
top: reference.size().top,
|
|
||||||
right: reference.size().right,
|
|
||||||
bottom: reference.size().bottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
should_update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_update {
|
|
||||||
monitor.update_focused_workspace(offset)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::needless_collect)]
|
|
||||||
let old_sizes = self
|
|
||||||
.monitors()
|
|
||||||
.iter()
|
|
||||||
.map(Monitor::size)
|
|
||||||
.copied()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Check for and add any new monitors that may have been plugged in
|
|
||||||
WindowsApi::load_monitor_information(&mut self.monitors)?;
|
|
||||||
|
|
||||||
let mut check_cache = vec![];
|
|
||||||
|
|
||||||
for (i, m) in self.monitors().iter().enumerate() {
|
|
||||||
if !old_sizes.contains(m.size()) {
|
|
||||||
check_cache.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in check_cache {
|
|
||||||
if let Some(cached) = self.monitor_cache.get(&i).cloned() {
|
|
||||||
if let Some(monitor) = self.monitors_mut().get_mut(i) {
|
|
||||||
for (w_idx, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
|
||||||
if let Some(cached_workspace) = cached.workspaces().get(w_idx) {
|
|
||||||
workspace.set_layout(cached_workspace.layout().clone());
|
|
||||||
workspace.set_layout_rules(cached_workspace.layout_rules().clone());
|
|
||||||
workspace.set_layout_flip(cached_workspace.layout_flip());
|
|
||||||
workspace.set_workspace_padding(cached_workspace.workspace_padding());
|
|
||||||
workspace.set_container_padding(cached_workspace.container_padding());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let final_count = self.monitors().len();
|
|
||||||
if after_count != final_count {
|
|
||||||
self.retile_all(true)?;
|
|
||||||
// Second retile to fix DPI/resolution related jank when a window
|
|
||||||
// moves between monitors with different resolutions - this doesn't
|
|
||||||
// really get seen by the user since the screens are flickering anyway
|
|
||||||
// as a result of the display connections / disconnections
|
|
||||||
self.retile_all(true)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[tracing::instrument(skip(self), level = "debug")]
|
#[tracing::instrument(skip(self), level = "debug")]
|
||||||
fn add_window_handle_to_move_based_on_workspace_rule(
|
fn add_window_handle_to_move_based_on_workspace_rule(
|
||||||
@@ -1282,6 +1131,8 @@ impl WindowManager {
|
|||||||
|
|
||||||
let new_idx = workspace.new_idx_for_direction(direction);
|
let new_idx = workspace.new_idx_for_direction(direction);
|
||||||
|
|
||||||
|
let mut cross_monitor_monocle = false;
|
||||||
|
|
||||||
// if there is no container in that direction for this workspace
|
// if there is no container in that direction for this workspace
|
||||||
match new_idx {
|
match new_idx {
|
||||||
None => {
|
None => {
|
||||||
@@ -1290,6 +1141,19 @@ impl WindowManager {
|
|||||||
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
|
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
|
||||||
|
|
||||||
self.focus_monitor(monitor_idx)?;
|
self.focus_monitor(monitor_idx)?;
|
||||||
|
|
||||||
|
if let Ok(focused_workspace) = self.focused_workspace() {
|
||||||
|
if let Some(monocle) = focused_workspace.monocle_container() {
|
||||||
|
if let Some(window) = monocle.focused_window() {
|
||||||
|
window.focus(self.mouse_follows_focus)?;
|
||||||
|
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(
|
||||||
|
window.hwnd(),
|
||||||
|
)?)?;
|
||||||
|
|
||||||
|
cross_monitor_monocle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
@@ -1297,20 +1161,10 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When switching workspaces and landing focus on a window that is not stack, but a stack
|
if !cross_monitor_monocle {
|
||||||
// exists, and there is a stackbar visible, when changing focus to that container stack,
|
if let Ok(focused_window) = self.focused_window_mut() {
|
||||||
// the focused text colour will not be applied until the stack has been cycled at least once
|
focused_window.focus(self.mouse_follows_focus)?;
|
||||||
//
|
}
|
||||||
// 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(())
|
||||||
@@ -1361,7 +1215,19 @@ impl WindowManager {
|
|||||||
// focus the target monitor
|
// focus the target monitor
|
||||||
self.focus_monitor(target_monitor_idx)?;
|
self.focus_monitor(target_monitor_idx)?;
|
||||||
|
|
||||||
// get the focused workspace on the target monitor
|
// unset monocle container on target workspace if there is one
|
||||||
|
let mut target_workspace_has_monocle = false;
|
||||||
|
if let Ok(target_workspace) = self.focused_workspace() {
|
||||||
|
if target_workspace.monocle_container().is_some() {
|
||||||
|
target_workspace_has_monocle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_workspace_has_monocle {
|
||||||
|
self.toggle_monocle()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a mutable ref to the focused workspace on the target monitor
|
||||||
let target_workspace = self.focused_workspace_mut()?;
|
let target_workspace = self.focused_workspace_mut()?;
|
||||||
|
|
||||||
// insert the origin container into the focused workspace on the target monitor
|
// insert the origin container into the focused workspace on the target monitor
|
||||||
@@ -1558,8 +1424,11 @@ impl WindowManager {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
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))
|
&& !matches!(
|
||||||
{
|
workspace.layout(),
|
||||||
|
Layout::Default(DefaultLayout::Grid)
|
||||||
|
| Layout::Default(DefaultLayout::UltrawideVerticalStack)
|
||||||
|
) {
|
||||||
new_idx - 1
|
new_idx - 1
|
||||||
} else {
|
} else {
|
||||||
new_idx
|
new_idx
|
||||||
@@ -1697,16 +1566,6 @@ impl WindowManager {
|
|||||||
|
|
||||||
self.update_focused_workspace(true, true)?;
|
self.update_focused_workspace(true, true)?;
|
||||||
|
|
||||||
// TODO: fix this ugly hack to restore stackbar after monocle is toggled off
|
|
||||||
let workspace = self.focused_workspace()?;
|
|
||||||
if workspace.monocle_container().is_none() {
|
|
||||||
if let Some(container) = workspace.focused_container() {
|
|
||||||
if container.stackbar().is_some() {
|
|
||||||
self.retile_all(true)?;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1715,7 +1574,13 @@ impl WindowManager {
|
|||||||
tracing::info!("enabling monocle");
|
tracing::info!("enabling monocle");
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
workspace.new_monocle_container()
|
workspace.new_monocle_container()?;
|
||||||
|
|
||||||
|
for container in workspace.containers_mut() {
|
||||||
|
container.hide(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -1723,6 +1588,11 @@ impl WindowManager {
|
|||||||
tracing::info!("disabling monocle");
|
tracing::info!("disabling monocle");
|
||||||
|
|
||||||
let workspace = self.focused_workspace_mut()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
|
|
||||||
|
for container in workspace.containers_mut() {
|
||||||
|
container.restore();
|
||||||
|
}
|
||||||
|
|
||||||
workspace.reintegrate_monocle_container()
|
workspace.reintegrate_monocle_container()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2327,10 +2197,21 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// our hmonitor might be stale, so if we didn't return above, try querying via the latest
|
||||||
|
// info taken from win32_display_data and update our hmonitor while we're at it
|
||||||
|
if let Ok(latest) = WindowsApi::monitor(hmonitor) {
|
||||||
|
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||||
|
if monitor.device_id() == latest.device_id() {
|
||||||
|
monitor.set_id(latest.id());
|
||||||
|
return Option::from(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn monitor_idx_from_current_pos(&self) -> Option<usize> {
|
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
|
||||||
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
|
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
|
||||||
|
|
||||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||||
@@ -2339,6 +2220,17 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// our hmonitor might be stale, so if we didn't return above, try querying via the latest
|
||||||
|
// info taken from win32_display_data and update our hmonitor while we're at it
|
||||||
|
if let Ok(latest) = WindowsApi::monitor(hmonitor) {
|
||||||
|
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
||||||
|
if monitor.device_id() == latest.device_id() {
|
||||||
|
monitor.set_id(latest.id());
|
||||||
|
return Option::from(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ pub enum WindowManagerEvent {
|
|||||||
Manage(Window),
|
Manage(Window),
|
||||||
Unmanage(Window),
|
Unmanage(Window),
|
||||||
Raise(Window),
|
Raise(Window),
|
||||||
DisplayChange(Window),
|
|
||||||
ForceUpdate(Window),
|
ForceUpdate(Window),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +75,6 @@ impl Display for WindowManagerEvent {
|
|||||||
Self::Raise(window) => {
|
Self::Raise(window) => {
|
||||||
write!(f, "Raise (Window: {window})")
|
write!(f, "Raise (Window: {window})")
|
||||||
}
|
}
|
||||||
Self::DisplayChange(window) => {
|
|
||||||
write!(f, "DisplayChange (Window: {window})")
|
|
||||||
}
|
|
||||||
Self::ForceUpdate(window) => {
|
Self::ForceUpdate(window) => {
|
||||||
write!(f, "ForceUpdate (Window: {window})")
|
write!(f, "ForceUpdate (Window: {window})")
|
||||||
}
|
}
|
||||||
@@ -101,7 +97,6 @@ impl WindowManagerEvent {
|
|||||||
| Self::MouseCapture(_, window)
|
| Self::MouseCapture(_, window)
|
||||||
| Self::Raise(window)
|
| Self::Raise(window)
|
||||||
| Self::Manage(window)
|
| Self::Manage(window)
|
||||||
| Self::DisplayChange(window)
|
|
||||||
| Self::Unmanage(window)
|
| Self::Unmanage(window)
|
||||||
| Self::ForceUpdate(window) => window,
|
| Self::ForceUpdate(window) => window,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use std::ffi::c_void;
|
|||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
use color_eyre::eyre::anyhow;
|
use color_eyre::eyre::anyhow;
|
||||||
|
use color_eyre::eyre::bail;
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use widestring::U16CStr;
|
|
||||||
use windows::core::Result as WindowsCrateResult;
|
use windows::core::Result as WindowsCrateResult;
|
||||||
use windows::core::PCWSTR;
|
use windows::core::PCWSTR;
|
||||||
use windows::core::PWSTR;
|
use windows::core::PWSTR;
|
||||||
@@ -30,14 +30,12 @@ use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
|||||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||||
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
||||||
use windows::Win32::Graphics::Gdi::EnumDisplayDevicesW;
|
|
||||||
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
||||||
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
||||||
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
||||||
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||||
use windows::Win32::Graphics::Gdi::RoundRect;
|
use windows::Win32::Graphics::Gdi::RoundRect;
|
||||||
use windows::Win32::Graphics::Gdi::DISPLAY_DEVICEW;
|
|
||||||
use windows::Win32::Graphics::Gdi::HBRUSH;
|
use windows::Win32::Graphics::Gdi::HBRUSH;
|
||||||
use windows::Win32::Graphics::Gdi::HDC;
|
use windows::Win32::Graphics::Gdi::HDC;
|
||||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||||
@@ -46,6 +44,7 @@ use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
|||||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||||
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
|
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
|
||||||
|
use windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification;
|
||||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||||
use windows::Win32::System::Threading::OpenProcess;
|
use windows::Win32::System::Threading::OpenProcess;
|
||||||
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||||
@@ -95,7 +94,6 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
|||||||
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||||
@@ -140,6 +138,8 @@ use crate::ring::Ring;
|
|||||||
use crate::set_window_position::SetWindowPosition;
|
use crate::set_window_position::SetWindowPosition;
|
||||||
use crate::windows_callbacks;
|
use crate::windows_callbacks;
|
||||||
use crate::Window;
|
use crate::Window;
|
||||||
|
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||||
|
use crate::MONITOR_INDEX_PREFERENCES;
|
||||||
|
|
||||||
pub enum WindowsResult<T, E> {
|
pub enum WindowsResult<T, E> {
|
||||||
Err(E),
|
Err(E),
|
||||||
@@ -220,58 +220,74 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
|
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
|
||||||
let mut monitors: Vec<(String, isize)> = vec![];
|
Ok(win32_display_data::connected_displays()
|
||||||
let monitors_ref: &mut Vec<(String, isize)> = monitors.as_mut();
|
.flatten()
|
||||||
Self::enum_display_monitors(
|
.map(|d| {
|
||||||
Some(windows_callbacks::valid_display_monitors),
|
let name = d.device_name.trim_start_matches(r"\\.\").to_string();
|
||||||
monitors_ref as *mut Vec<(String, isize)> as isize,
|
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(monitors)
|
(name, d.hmonitor)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
|
||||||
Self::enum_display_monitors(
|
'read: for display in win32_display_data::connected_displays().flatten() {
|
||||||
Some(windows_callbacks::enum_display_monitor),
|
let path = display.device_path.clone();
|
||||||
monitors as *mut Ring<Monitor> as isize,
|
let mut split: Vec<_> = path.split('#').collect();
|
||||||
)?;
|
split.remove(0);
|
||||||
|
split.remove(split.len() - 1);
|
||||||
|
let device = split[0].to_string();
|
||||||
|
let device_id = split.join("-");
|
||||||
|
|
||||||
Ok(())
|
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
||||||
}
|
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
||||||
|
|
||||||
pub fn enum_display_devices(
|
for monitor in monitors.elements() {
|
||||||
index: u32,
|
if device_id.eq(monitor.device_id()) {
|
||||||
lp_device: Option<*const u16>,
|
continue 'read;
|
||||||
) -> Result<DISPLAY_DEVICEW> {
|
}
|
||||||
#[allow(clippy::option_if_let_else)]
|
}
|
||||||
let lp_device = match lp_device {
|
|
||||||
None => PCWSTR::null(),
|
|
||||||
Some(lp_device) => PCWSTR(lp_device),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut display_device = DISPLAY_DEVICEW {
|
let m = monitor::new(
|
||||||
cb: u32::try_from(std::mem::size_of::<DISPLAY_DEVICEW>())?,
|
display.hmonitor,
|
||||||
..Default::default()
|
display.size.into(),
|
||||||
};
|
display.work_area_size.into(),
|
||||||
|
name,
|
||||||
|
device,
|
||||||
|
device_id,
|
||||||
|
);
|
||||||
|
|
||||||
match unsafe {
|
let mut index_preference = None;
|
||||||
EnumDisplayDevicesW(
|
let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
|
||||||
lp_device,
|
for (index, monitor_size) in &*monitor_index_preferences {
|
||||||
index,
|
if m.size() == monitor_size {
|
||||||
std::ptr::addr_of_mut!(display_device),
|
index_preference = Option::from(index);
|
||||||
EDD_GET_DEVICE_INTERFACE_NAME,
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
.ok()
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||||
{
|
for (index, id) in &*display_index_preferences {
|
||||||
Ok(()) => {}
|
if id.eq(m.device_id()) {
|
||||||
Err(error) => {
|
index_preference = Option::from(index);
|
||||||
tracing::error!("enum_display_devices: {}", error);
|
}
|
||||||
return Err(error.into());
|
}
|
||||||
|
|
||||||
|
if monitors.elements().is_empty() {
|
||||||
|
monitors.elements_mut().push_back(m);
|
||||||
|
} else if let Some(preference) = index_preference {
|
||||||
|
let current_len = monitors.elements().len();
|
||||||
|
if *preference > current_len {
|
||||||
|
monitors.elements_mut().reserve(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
monitors.elements_mut().insert(*preference, m);
|
||||||
|
} else {
|
||||||
|
monitors.elements_mut().push_back(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(display_device)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
|
pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> {
|
||||||
@@ -775,20 +791,32 @@ impl WindowsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
|
pub fn monitor(hmonitor: isize) -> Result<Monitor> {
|
||||||
let ex_info = Self::monitor_info_w(HMONITOR(hmonitor))?;
|
for display in win32_display_data::connected_displays().flatten() {
|
||||||
let name = U16CStr::from_slice_truncate(&ex_info.szDevice)
|
if display.hmonitor == hmonitor {
|
||||||
.expect("monitor name was not a valid u16 c string")
|
let path = display.device_path;
|
||||||
.to_ustring()
|
let mut split: Vec<_> = path.split('#').collect();
|
||||||
.to_string_lossy()
|
split.remove(0);
|
||||||
.trim_start_matches(r"\\.\")
|
split.remove(split.len() - 1);
|
||||||
.to_string();
|
let device = split[0].to_string();
|
||||||
|
let device_id = split.join("-");
|
||||||
|
|
||||||
Ok(monitor::new(
|
let name = display.device_name.trim_start_matches(r"\\.\").to_string();
|
||||||
hmonitor,
|
let name = name.split('\\').collect::<Vec<_>>()[0].to_string();
|
||||||
ex_info.monitorInfo.rcMonitor.into(),
|
|
||||||
ex_info.monitorInfo.rcWork.into(),
|
let monitor = monitor::new(
|
||||||
name,
|
hmonitor,
|
||||||
))
|
display.size.into(),
|
||||||
|
display.work_area_size.into(),
|
||||||
|
name,
|
||||||
|
device,
|
||||||
|
device_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("could not find device_id for hmonitor: {hmonitor}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_process_dpi_awareness_context() -> Result<()> {
|
pub fn set_process_dpi_awareness_context() -> Result<()> {
|
||||||
@@ -1031,4 +1059,8 @@ impl WindowsApi {
|
|||||||
SendInput(&inputs, std::mem::size_of::<INPUT>() as i32)
|
SendInput(&inputs, std::mem::size_of::<INPUT>() as i32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn wts_register_session_notification(hwnd: isize) -> Result<()> {
|
||||||
|
unsafe { WTSRegisterSessionNotification(HWND(hwnd), 1) }.process()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,129 +1,17 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use widestring::U16CStr;
|
|
||||||
|
|
||||||
use windows::Win32::Foundation::BOOL;
|
use windows::Win32::Foundation::BOOL;
|
||||||
use windows::Win32::Foundation::HWND;
|
use windows::Win32::Foundation::HWND;
|
||||||
use windows::Win32::Foundation::LPARAM;
|
use windows::Win32::Foundation::LPARAM;
|
||||||
use windows::Win32::Foundation::LRESULT;
|
|
||||||
use windows::Win32::Foundation::RECT;
|
|
||||||
use windows::Win32::Foundation::WPARAM;
|
|
||||||
use windows::Win32::Graphics::Gdi::HDC;
|
|
||||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
|
||||||
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SPI_ICONVERTICALSPACING;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE;
|
|
||||||
|
|
||||||
use crate::container::Container;
|
use crate::container::Container;
|
||||||
use crate::monitor::Monitor;
|
|
||||||
use crate::ring::Ring;
|
|
||||||
use crate::window::RuleDebug;
|
use crate::window::RuleDebug;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::winevent::WinEvent;
|
use crate::winevent::WinEvent;
|
||||||
use crate::winevent_listener;
|
use crate::winevent_listener;
|
||||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
|
||||||
use crate::MONITOR_INDEX_PREFERENCES;
|
|
||||||
|
|
||||||
pub extern "system" fn valid_display_monitors(
|
|
||||||
hmonitor: HMONITOR,
|
|
||||||
_: HDC,
|
|
||||||
_: *mut RECT,
|
|
||||||
lparam: LPARAM,
|
|
||||||
) -> BOOL {
|
|
||||||
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<(String, isize)>) };
|
|
||||||
if let Ok(m) = WindowsApi::monitor(hmonitor.0) {
|
|
||||||
monitors.push((m.name().to_string(), hmonitor.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "system" fn enum_display_monitor(
|
|
||||||
hmonitor: HMONITOR,
|
|
||||||
_: HDC,
|
|
||||||
_: *mut RECT,
|
|
||||||
lparam: LPARAM,
|
|
||||||
) -> BOOL {
|
|
||||||
let monitors = unsafe { &mut *(lparam.0 as *mut Ring<Monitor>) };
|
|
||||||
|
|
||||||
// Don't duplicate a monitor that is already being managed
|
|
||||||
for monitor in monitors.elements() {
|
|
||||||
if monitor.id() == hmonitor.0 {
|
|
||||||
return true.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_index = monitors.elements().len();
|
|
||||||
|
|
||||||
if let Ok(mut m) = WindowsApi::monitor(hmonitor.0) {
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
if let Ok(d) = WindowsApi::enum_display_devices(current_index as u32, None) {
|
|
||||||
let name = U16CStr::from_slice_truncate(d.DeviceName.as_ref())
|
|
||||||
.expect("display device name was not a valid u16 c string")
|
|
||||||
.to_ustring()
|
|
||||||
.to_string_lossy()
|
|
||||||
.trim_start_matches(r"\\.\")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
if name.eq(m.name()) {
|
|
||||||
if let Ok(device) = WindowsApi::enum_display_devices(0, Some(d.DeviceName.as_ptr()))
|
|
||||||
{
|
|
||||||
let id = U16CStr::from_slice_truncate(device.DeviceID.as_ref())
|
|
||||||
.expect("display device id was not a valid u16 c string")
|
|
||||||
.to_ustring()
|
|
||||||
.to_string_lossy()
|
|
||||||
.trim_start_matches(r"\\?\")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let mut split: Vec<_> = id.split('#').collect();
|
|
||||||
split.remove(0);
|
|
||||||
split.remove(split.len() - 1);
|
|
||||||
|
|
||||||
m.set_device(Option::from(split[0].to_string()));
|
|
||||||
m.set_device_id(Option::from(split.join("-")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
|
|
||||||
let mut index_preference = None;
|
|
||||||
for (index, monitor_size) in &*monitor_index_preferences {
|
|
||||||
if m.size() == monitor_size {
|
|
||||||
index_preference = Option::from(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
|
||||||
for (index, device) in &*display_index_preferences {
|
|
||||||
if let Some(known_device) = m.device_id() {
|
|
||||||
if device == known_device {
|
|
||||||
index_preference = Option::from(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if monitors.elements().is_empty() {
|
|
||||||
monitors.elements_mut().push_back(m);
|
|
||||||
} else if let Some(preference) = index_preference {
|
|
||||||
let current_len = monitors.elements().len();
|
|
||||||
if *preference > current_len {
|
|
||||||
monitors.elements_mut().reserve(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
monitors.elements_mut().insert(*preference, m);
|
|
||||||
} else {
|
|
||||||
monitors.elements_mut().push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||||
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
||||||
@@ -201,48 +89,3 @@ pub extern "system" fn win_event_hook(
|
|||||||
.send(event_type)
|
.send(event_type)
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
.expect("could not send message on winevent_listener::event_tx");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "system" fn hidden_window(
|
|
||||||
window: HWND,
|
|
||||||
message: u32,
|
|
||||||
wparam: WPARAM,
|
|
||||||
lparam: LPARAM,
|
|
||||||
) -> LRESULT {
|
|
||||||
unsafe {
|
|
||||||
match message {
|
|
||||||
WM_DISPLAYCHANGE => {
|
|
||||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
|
||||||
winevent_listener::event_tx()
|
|
||||||
.send(event_type)
|
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
|
||||||
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
// Added based on this https://stackoverflow.com/a/33762334
|
|
||||||
WM_SETTINGCHANGE => {
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
if wparam.0 as u32 == SPI_SETWORKAREA.0
|
|
||||||
|| wparam.0 as u32 == SPI_ICONVERTICALSPACING.0
|
|
||||||
{
|
|
||||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
|
||||||
winevent_listener::event_tx()
|
|
||||||
.send(event_type)
|
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
|
||||||
}
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
// Added based on this https://stackoverflow.com/a/33762334
|
|
||||||
WM_DEVICECHANGE => {
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
|
|
||||||
let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 });
|
|
||||||
winevent_listener::event_tx()
|
|
||||||
.send(event_type)
|
|
||||||
.expect("could not send message on winevent_listener::event_tx");
|
|
||||||
}
|
|
||||||
LRESULT(0)
|
|
||||||
}
|
|
||||||
_ => DefWindowProcW(window, message, wparam, lparam),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ use crate::border_manager::BORDER_OFFSET;
|
|||||||
use crate::border_manager::BORDER_WIDTH;
|
use crate::border_manager::BORDER_WIDTH;
|
||||||
use crate::container::Container;
|
use crate::container::Container;
|
||||||
use crate::ring::Ring;
|
use crate::ring::Ring;
|
||||||
|
use crate::stackbar_manager;
|
||||||
|
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
||||||
use crate::static_config::WorkspaceConfig;
|
use crate::static_config::WorkspaceConfig;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::window::WindowDetails;
|
use crate::window::WindowDetails;
|
||||||
@@ -33,7 +35,6 @@ use crate::DEFAULT_WORKSPACE_PADDING;
|
|||||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||||
use crate::NO_TITLEBAR;
|
use crate::NO_TITLEBAR;
|
||||||
use crate::REMOVE_TITLEBARS;
|
use crate::REMOVE_TITLEBARS;
|
||||||
use crate::STACKBAR_TAB_HEIGHT;
|
|
||||||
|
|
||||||
#[allow(clippy::struct_field_names)]
|
#[allow(clippy::struct_field_names)]
|
||||||
#[derive(
|
#[derive(
|
||||||
@@ -179,6 +180,14 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||||
|
if let Some(container) = self.monocle_container_mut() {
|
||||||
|
if let Some(window) = container.focused_window() {
|
||||||
|
container.restore();
|
||||||
|
window.focus(mouse_follows_focus)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let idx = self.focused_container_idx();
|
let idx = self.focused_container_idx();
|
||||||
let mut to_focus = None;
|
let mut to_focus = None;
|
||||||
|
|
||||||
@@ -196,10 +205,6 @@ impl Workspace {
|
|||||||
container.restore();
|
container.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(container) = self.monocle_container_mut() {
|
|
||||||
container.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
for window in self.floating_windows() {
|
for window in self.floating_windows() {
|
||||||
window.restore();
|
window.restore();
|
||||||
}
|
}
|
||||||
@@ -300,7 +305,7 @@ impl Workspace {
|
|||||||
} else if let Some(window) = self.maximized_window_mut() {
|
} else if let Some(window) = self.maximized_window_mut() {
|
||||||
window.maximize();
|
window.maximize();
|
||||||
} else if !self.containers().is_empty() {
|
} else if !self.containers().is_empty() {
|
||||||
let layouts = self.layout().as_boxed_arrangement().calculate(
|
let mut layouts = self.layout().as_boxed_arrangement().calculate(
|
||||||
&adjusted_work_area,
|
&adjusted_work_area,
|
||||||
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
|
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
@@ -315,24 +320,14 @@ impl Workspace {
|
|||||||
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
|
||||||
let no_titlebar = NO_TITLEBAR.lock().clone();
|
let no_titlebar = NO_TITLEBAR.lock().clone();
|
||||||
|
|
||||||
let focused_hwnd = self
|
|
||||||
.focused_container()
|
|
||||||
.ok_or_else(|| anyhow!("couldn't find a focused container"))?
|
|
||||||
.focused_window()
|
|
||||||
.ok_or_else(|| anyhow!("couldn't find a focused window"))?
|
|
||||||
.hwnd;
|
|
||||||
|
|
||||||
let container_padding = self.container_padding().unwrap_or(0);
|
let container_padding = self.container_padding().unwrap_or(0);
|
||||||
let containers = self.containers_mut();
|
let containers = self.containers_mut();
|
||||||
|
|
||||||
for (i, container) in containers.iter_mut().enumerate() {
|
for (i, container) in containers.iter_mut().enumerate() {
|
||||||
container.renew_stackbar();
|
let window_count = container.windows().len();
|
||||||
|
|
||||||
let container_windows = container.windows().clone();
|
|
||||||
let container_stackbar = container.stackbar().clone();
|
|
||||||
|
|
||||||
if let (Some(window), Some(layout)) =
|
if let (Some(window), Some(layout)) =
|
||||||
(container.focused_window_mut(), layouts.get(i))
|
(container.focused_window_mut(), layouts.get_mut(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()?;
|
||||||
@@ -346,33 +341,23 @@ impl Workspace {
|
|||||||
WindowsApi::restore_window(window.hwnd());
|
WindowsApi::restore_window(window.hwnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rect = *layout;
|
|
||||||
{
|
{
|
||||||
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
||||||
rect.add_padding(border_offset);
|
layout.add_padding(border_offset);
|
||||||
|
|
||||||
let width = BORDER_WIDTH.load(Ordering::SeqCst);
|
let width = BORDER_WIDTH.load(Ordering::SeqCst);
|
||||||
rect.add_padding(width);
|
layout.add_padding(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(stackbar) = container_stackbar {
|
if stackbar_manager::should_have_stackbar(window_count) {
|
||||||
if stackbar
|
let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
|
||||||
.set_position(
|
let total_height = tab_height + container_padding;
|
||||||
&stackbar.get_position_from_container_layout(layout),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
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;
|
layout.top += total_height;
|
||||||
rect.bottom -= total_height;
|
layout.bottom -= total_height;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.set_position(&rect, false)?;
|
window.set_position(&layout, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,17 +386,10 @@ impl Workspace {
|
|||||||
let containers = self.containers_mut();
|
let containers = self.containers_mut();
|
||||||
|
|
||||||
for container in containers.iter_mut() {
|
for container in containers.iter_mut() {
|
||||||
let container_windows = container.windows().clone();
|
|
||||||
let container_topbar = container.stackbar().clone();
|
|
||||||
|
|
||||||
if let Some(idx) = container.idx_for_window(hwnd) {
|
if let Some(idx) = container.idx_for_window(hwnd) {
|
||||||
container.focus_window(idx);
|
container.focus_window(idx);
|
||||||
container.restore();
|
container.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(stackbar) = container_topbar {
|
|
||||||
stackbar.update(&container_windows, hwnd)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ komorebi-core = { path = "../komorebi-core" }
|
|||||||
komorebi-client = { path = "../komorebi-client" }
|
komorebi-client = { path = "../komorebi-client" }
|
||||||
|
|
||||||
clap = { version = "4", features = ["derive", "wrap_help"] }
|
clap = { version = "4", features = ["derive", "wrap_help"] }
|
||||||
|
chrono = "0.4"
|
||||||
color-eyre = { workspace = true }
|
color-eyre = { workspace = true }
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
|
use chrono::Local;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
@@ -160,6 +161,7 @@ gen_enum_subcommand_args! {
|
|||||||
CycleMoveToMonitor: CycleDirection,
|
CycleMoveToMonitor: CycleDirection,
|
||||||
CycleMonitor: CycleDirection,
|
CycleMonitor: CycleDirection,
|
||||||
CycleWorkspace: CycleDirection,
|
CycleWorkspace: CycleDirection,
|
||||||
|
CycleMoveWorkspaceToMonitor: CycleDirection,
|
||||||
Stack: OperationDirection,
|
Stack: OperationDirection,
|
||||||
CycleStack: CycleDirection,
|
CycleStack: CycleDirection,
|
||||||
FlipLayout: Axis,
|
FlipLayout: Axis,
|
||||||
@@ -171,6 +173,7 @@ gen_enum_subcommand_args! {
|
|||||||
WindowHidingBehaviour: HidingBehaviour,
|
WindowHidingBehaviour: HidingBehaviour,
|
||||||
CrossMonitorMoveBehaviour: MoveBehaviour,
|
CrossMonitorMoveBehaviour: MoveBehaviour,
|
||||||
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
||||||
|
PromoteWindow: OperationDirection,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! gen_target_subcommand_args {
|
macro_rules! gen_target_subcommand_args {
|
||||||
@@ -651,13 +654,13 @@ struct FocusFollowsMouse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct ActiveWindowBorder {
|
struct Border {
|
||||||
#[clap(value_enum)]
|
#[clap(value_enum)]
|
||||||
boolean_state: BooleanState,
|
boolean_state: BooleanState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct ActiveWindowBorderColour {
|
struct BorderColour {
|
||||||
#[clap(value_enum, short, long, default_value = "single")]
|
#[clap(value_enum, short, long, default_value = "single")]
|
||||||
window_kind: WindowKind,
|
window_kind: WindowKind,
|
||||||
/// Red
|
/// Red
|
||||||
@@ -669,14 +672,14 @@ struct ActiveWindowBorderColour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct ActiveWindowBorderWidth {
|
struct BorderWidth {
|
||||||
/// Desired width of the active window border
|
/// Desired width of the window border
|
||||||
width: i32,
|
width: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, AhkFunction)]
|
#[derive(Parser, AhkFunction)]
|
||||||
struct ActiveWindowBorderOffset {
|
struct BorderOffset {
|
||||||
/// Desired offset of the active window border
|
/// Desired offset of the window border
|
||||||
offset: i32,
|
offset: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,6 +958,9 @@ enum SubCommand {
|
|||||||
/// Move the focused workspace to the specified monitor
|
/// Move the focused workspace to the specified monitor
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
||||||
|
/// Move the focused workspace monitor in the given cycle direction
|
||||||
|
#[clap(arg_required_else_help = true)]
|
||||||
|
CycleMoveWorkspaceToMonitor(CycleMoveWorkspaceToMonitor),
|
||||||
/// Swap focused monitor workspaces with specified monitor
|
/// Swap focused monitor workspaces with specified monitor
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
SwapWorkspacesWithMonitor(SwapWorkspacesWithMonitor),
|
SwapWorkspacesWithMonitor(SwapWorkspacesWithMonitor),
|
||||||
@@ -1000,6 +1006,8 @@ enum SubCommand {
|
|||||||
Promote,
|
Promote,
|
||||||
/// Promote the user focus to the top of the tree
|
/// Promote the user focus to the top of the tree
|
||||||
PromoteFocus,
|
PromoteFocus,
|
||||||
|
/// Promote the window in the specified direction
|
||||||
|
PromoteWindow(PromoteWindow),
|
||||||
/// Force the retiling of all managed windows
|
/// Force the retiling of all managed windows
|
||||||
Retile,
|
Retile,
|
||||||
/// Set the monitor index preference for a monitor identified using its size
|
/// Set the monitor index preference for a monitor identified using its size
|
||||||
@@ -1141,18 +1149,22 @@ enum SubCommand {
|
|||||||
#[clap(hide = true)]
|
#[clap(hide = true)]
|
||||||
#[clap(alias = "identify-border-overflow")]
|
#[clap(alias = "identify-border-overflow")]
|
||||||
IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),
|
IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),
|
||||||
/// Enable or disable the active window border
|
/// Enable or disable borders
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
ActiveWindowBorder(ActiveWindowBorder),
|
#[clap(alias = "active-window-border")]
|
||||||
/// Set the colour for the active window border
|
Border(Border),
|
||||||
|
/// Set the colour for a window border kind
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
ActiveWindowBorderColour(ActiveWindowBorderColour),
|
#[clap(alias = "active-window-border-colour")]
|
||||||
/// Set the width for the active window border
|
BorderColour(BorderColour),
|
||||||
|
/// Set the border width
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
ActiveWindowBorderWidth(ActiveWindowBorderWidth),
|
#[clap(alias = "active-window-border-width")]
|
||||||
/// Set the offset for the active window border
|
BorderWidth(BorderWidth),
|
||||||
|
/// Set the border offset
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
ActiveWindowBorderOffset(ActiveWindowBorderOffset),
|
#[clap(alias = "active-window-border-offset")]
|
||||||
|
BorderOffset(BorderOffset),
|
||||||
/// Enable or disable focus follows mouse for the operating system
|
/// Enable or disable focus follows mouse for the operating system
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
FocusFollowsMouse(FocusFollowsMouse),
|
FocusFollowsMouse(FocusFollowsMouse),
|
||||||
@@ -1479,7 +1491,8 @@ fn main() -> Result<()> {
|
|||||||
println!("\n#Include komorebic.lib.ahk");
|
println!("\n#Include komorebic.lib.ahk");
|
||||||
}
|
}
|
||||||
SubCommand::Log => {
|
SubCommand::Log => {
|
||||||
let color_log = std::env::temp_dir().join("komorebi.log");
|
let timestamp = Local::now().format("%Y-%m-%d").to_string();
|
||||||
|
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
|
||||||
let file = TailedFile::new(File::open(color_log)?);
|
let file = TailedFile::new(File::open(color_log)?);
|
||||||
let locked = file.lock();
|
let locked = file.lock();
|
||||||
#[allow(clippy::significant_drop_in_scrutinee, clippy::lines_filter_map_ok)]
|
#[allow(clippy::significant_drop_in_scrutinee, clippy::lines_filter_map_ok)]
|
||||||
@@ -1505,6 +1518,9 @@ fn main() -> Result<()> {
|
|||||||
SubCommand::PromoteFocus => {
|
SubCommand::PromoteFocus => {
|
||||||
send_message(&SocketMessage::PromoteFocus.as_bytes()?)?;
|
send_message(&SocketMessage::PromoteFocus.as_bytes()?)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::PromoteWindow(arg) => {
|
||||||
|
send_message(&SocketMessage::PromoteWindow(arg.operation_direction).as_bytes()?)?;
|
||||||
|
}
|
||||||
SubCommand::TogglePause => {
|
SubCommand::TogglePause => {
|
||||||
send_message(&SocketMessage::TogglePause.as_bytes()?)?;
|
send_message(&SocketMessage::TogglePause.as_bytes()?)?;
|
||||||
}
|
}
|
||||||
@@ -1579,6 +1595,11 @@ fn main() -> Result<()> {
|
|||||||
SubCommand::MoveWorkspaceToMonitor(arg) => {
|
SubCommand::MoveWorkspaceToMonitor(arg) => {
|
||||||
send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
|
send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(arg.target).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::CycleMoveWorkspaceToMonitor(arg) => {
|
||||||
|
send_message(
|
||||||
|
&SocketMessage::CycleMoveWorkspaceToMonitor(arg.cycle_direction).as_bytes()?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
SubCommand::SwapWorkspacesWithMonitor(arg) => {
|
SubCommand::SwapWorkspacesWithMonitor(arg) => {
|
||||||
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(arg.target).as_bytes()?)?;
|
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(arg.target).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
@@ -1930,6 +1951,7 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
|||||||
"* Subscribe to https://youtube.com/@LGUG2Z - Live dev videos and feature previews"
|
"* Subscribe to https://youtube.com/@LGUG2Z - Live dev videos and feature previews"
|
||||||
);
|
);
|
||||||
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
|
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
|
||||||
|
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
|
||||||
}
|
}
|
||||||
SubCommand::Stop(arg) => {
|
SubCommand::Stop(arg) => {
|
||||||
if arg.whkd {
|
if arg.whkd {
|
||||||
@@ -2212,19 +2234,18 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|||||||
SubCommand::MouseFollowsFocus(arg) => {
|
SubCommand::MouseFollowsFocus(arg) => {
|
||||||
send_message(&SocketMessage::MouseFollowsFocus(arg.boolean_state.into()).as_bytes()?)?;
|
send_message(&SocketMessage::MouseFollowsFocus(arg.boolean_state.into()).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::ActiveWindowBorder(arg) => {
|
SubCommand::Border(arg) => {
|
||||||
send_message(&SocketMessage::ActiveWindowBorder(arg.boolean_state.into()).as_bytes()?)?;
|
send_message(&SocketMessage::Border(arg.boolean_state.into()).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::ActiveWindowBorderColour(arg) => {
|
SubCommand::BorderColour(arg) => {
|
||||||
send_message(
|
send_message(
|
||||||
&SocketMessage::ActiveWindowBorderColour(arg.window_kind, arg.r, arg.g, arg.b)
|
&SocketMessage::BorderColour(arg.window_kind, arg.r, arg.g, arg.b).as_bytes()?,
|
||||||
.as_bytes()?,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
SubCommand::ActiveWindowBorderWidth(arg) => {
|
SubCommand::BorderWidth(arg) => {
|
||||||
send_message(&SocketMessage::BorderWidth(arg.width).as_bytes()?)?;
|
send_message(&SocketMessage::BorderWidth(arg.width).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::ActiveWindowBorderOffset(arg) => {
|
SubCommand::BorderOffset(arg) => {
|
||||||
send_message(&SocketMessage::BorderOffset(arg.offset).as_bytes()?)?;
|
send_message(&SocketMessage::BorderOffset(arg.offset).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
SubCommand::ResizeDelta(arg) => {
|
SubCommand::ResizeDelta(arg) => {
|
||||||
|
|||||||
12
mkdocs.yml
12
mkdocs.yml
@@ -57,7 +57,7 @@ nav:
|
|||||||
- Troubleshooting: troubleshooting.md
|
- Troubleshooting: troubleshooting.md
|
||||||
- Common workflows:
|
- Common workflows:
|
||||||
- common-workflows/komorebi-config-home.md
|
- common-workflows/komorebi-config-home.md
|
||||||
- common-workflows/active-window-border.md
|
- common-workflows/borders.md
|
||||||
- common-workflows/stackbar.md
|
- common-workflows/stackbar.md
|
||||||
- common-workflows/remove-gaps.md
|
- common-workflows/remove-gaps.md
|
||||||
- common-workflows/ignore-windows.md
|
- common-workflows/ignore-windows.md
|
||||||
@@ -67,7 +67,7 @@ nav:
|
|||||||
- common-workflows/mouse-follows-focus.md
|
- common-workflows/mouse-follows-focus.md
|
||||||
- common-workflows/custom-layouts.md
|
- common-workflows/custom-layouts.md
|
||||||
- common-workflows/dynamic-layout-switching.md
|
- common-workflows/dynamic-layout-switching.md
|
||||||
# - common-workflows/autohotkey.md
|
- common-workflows/autohotkey.md
|
||||||
- Release notes:
|
- Release notes:
|
||||||
- release/v0-1-22.md
|
- release/v0-1-22.md
|
||||||
- Configuration reference: https://komorebi.lgug2z.com/schema
|
- Configuration reference: https://komorebi.lgug2z.com/schema
|
||||||
@@ -189,10 +189,10 @@ nav:
|
|||||||
- cli/identify-layered-application.md
|
- cli/identify-layered-application.md
|
||||||
- cli/remove-title-bar.md
|
- cli/remove-title-bar.md
|
||||||
- cli/toggle-title-bars.md
|
- cli/toggle-title-bars.md
|
||||||
- cli/active-window-border.md
|
- cli/border.md
|
||||||
- cli/active-window-border-colour.md
|
- cli/border-colour.md
|
||||||
- cli/active-window-border-width.md
|
- cli/border-width.md
|
||||||
- cli/active-window-border-offset.md
|
- cli/border-offset.md
|
||||||
- cli/focus-follows-mouse.md
|
- cli/focus-follows-mouse.md
|
||||||
- cli/toggle-focus-follows-mouse.md
|
- cli/toggle-focus-follows-mouse.md
|
||||||
- cli/mouse-follows-focus.md
|
- cli/mouse-follows-focus.md
|
||||||
|
|||||||
161
schema.json
161
schema.json
@@ -4,18 +4,17 @@
|
|||||||
"description": "The `komorebi.json` static configuration file reference for `v0.1.25`",
|
"description": "The `komorebi.json` static configuration file reference for `v0.1.25`",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"active_window_border": {
|
"app_specific_configuration_path": {
|
||||||
|
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
"description": "Display an active window border (default: false)",
|
"description": "Display an active window border (default: false)",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"active_window_border_colours": {
|
"border_colours": {
|
||||||
"description": "Active window border colours for different container types",
|
"description": "Active window border colours for different container types",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
|
||||||
"monocle",
|
|
||||||
"single",
|
|
||||||
"stack"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"monocle": {
|
"monocle": {
|
||||||
"description": "Border colour when the container is in monocle mode",
|
"description": "Border colour when the container is in monocle mode",
|
||||||
@@ -130,39 +129,47 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"unfocused": {
|
||||||
|
"description": "Border colour when the container is unfocused",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"description": "Colour represented as RGB",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"b",
|
||||||
|
"g",
|
||||||
|
"r"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"description": "Blue",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"g": {
|
||||||
|
"description": "Green",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"r": {
|
||||||
|
"description": "Red",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Colour represented as Hex",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active_window_border_style": {
|
|
||||||
"description": "Active window border style (default: System)",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "Use the system border style",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"System"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Use the Windows 11-style rounded borders",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Rounded"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Use the Windows 10-style square borders",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Square"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"app_specific_configuration_path": {
|
|
||||||
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"border_offset": {
|
"border_offset": {
|
||||||
"description": "Offset of the window border (default: -1)",
|
"description": "Offset of the window border (default: -1)",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -251,11 +258,47 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"border_style": {
|
||||||
|
"description": "Active window border style (default: System)",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Use the system border style",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"System"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Use the Windows 11-style rounded borders",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Rounded"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Use the Windows 10-style square borders",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Square"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"border_width": {
|
"border_width": {
|
||||||
"description": "Width of the window border (default: 8)",
|
"description": "Width of the window border (default: 8)",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
},
|
},
|
||||||
|
"border_z_order": {
|
||||||
|
"description": "Active window border z-order (default: System)",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Top",
|
||||||
|
"NoTopMost",
|
||||||
|
"Bottom",
|
||||||
|
"TopMost"
|
||||||
|
]
|
||||||
|
},
|
||||||
"cross_monitor_move_behaviour": {
|
"cross_monitor_move_behaviour": {
|
||||||
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
|
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
@@ -272,6 +315,13 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Insert"
|
"Insert"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Do nothing if trying to move a window container in the direction of an adjacent monitor",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"NoOp"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -668,6 +718,43 @@
|
|||||||
"workspaces"
|
"workspaces"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"window_based_work_area_offset": {
|
||||||
|
"description": "Window based work area offset (default: None)",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"bottom",
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
"top"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"bottom": {
|
||||||
|
"description": "The bottom point in a Win32 Rect",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"left": {
|
||||||
|
"description": "The left point in a Win32 Rect",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"description": "The right point in a Win32 Rect",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"top": {
|
||||||
|
"description": "The top point in a Win32 Rect",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"window_based_work_area_offset_limit": {
|
||||||
|
"description": "Open window limit after which the window based work area offset will no longer be applied (default: 1)",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int"
|
||||||
|
},
|
||||||
"work_area_offset": {
|
"work_area_offset": {
|
||||||
"description": "Monitor-specific work area offset (default: None)",
|
"description": "Monitor-specific work area offset (default: None)",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
Reference in New Issue
Block a user