Compare commits

..

1 Commits

Author SHA1 Message Date
LGUG2Z
a63490e88a feat(client): introduce komorebi-client crate
This commit introduces the komorebi-client library crate for other Rust
applications to interact with a running instance of komorebi over Unix
Domain Sockets.

Currently the crate re-exports everything one might find in the
komorebi::State struct and everything that is publicly exposed in
komorebi-core.

Public types and methods are still lacking documentation, and this crate
should not be published on crates.io until this is no longer the case.
2024-02-26 18:15:40 -08:00
42 changed files with 953 additions and 1209 deletions

View File

@@ -73,11 +73,6 @@ jobs:
- name: Install the target
run: |
rustup target install ${{ matrix.target }}
- name: Run Cargo checks
run: |
cargo fmt --check
cargo check
cargo clippy
- name: Run a full build
run: |
cargo build --locked --release --target ${{ matrix.target }}
@@ -98,11 +93,6 @@ jobs:
target/${{ matrix.target }}/release/komorebic-no-console.pdb
target/wix/komorebi-*.msi
retention-days: 7
- name: Check GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
version: latest
args: build --skip=validate --clean
# Release
- name: Generate changelog
@@ -116,12 +106,12 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
with:
version: latest
args: release --skip=validate --clean --release-notes=CHANGELOG.md
args: release --skip-validate --clean --release-notes=CHANGELOG.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SCOOP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
- name: Add MSI to release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/v')
with:
files: "target/wix/komorebi-*.msi"

616
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,15 +11,14 @@ members = [
]
[workspace.dependencies]
windows-interface = { version = "0.53" }
windows-implement = { version = "0.53" }
windows-interface = { version = "0.52" }
windows-implement = { version = "0.52" }
dunce = "1"
dirs = "5"
color-eyre = "0.6"
serde_json = { package = "serde_json_lenient", version = "0.1" }
[workspace.dependencies.windows]
version = "0.54"
version = "0.52"
features = [
"implement",
"Win32_System_Com",

View File

@@ -1,7 +1,7 @@
# alt-focus-hack
```
DEPRECATED since v0.1.22
Enable or disable a hack simulating ALT key presses to ensure focus changes succeed
Usage: komorebic.exe alt-focus-hack <BOOLEAN_STATE>

View File

@@ -7,7 +7,7 @@ Usage: komorebic.exe change-layout <DEFAULT_LAYOUT>
Arguments:
<DEFAULT_LAYOUT>
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
Options:
-h, --help

View File

@@ -1,7 +1,7 @@
# check
```
Check komorebi configuration and related files for common errors
Output various important komorebi-related environment values
Usage: komorebic.exe check

View File

@@ -1,12 +0,0 @@
# configuration
```
Show the path to komorebi.json
Usage: komorebic.exe configuration
Options:
-h, --help
Print help
```

10
docs/cli/docgen.md Normal file
View File

@@ -0,0 +1,10 @@
# docgen
```
Usage: komorebic.exe docgen
Options:
-h, --help
Print help
```

View File

@@ -13,7 +13,7 @@ Arguments:
The number of window containers on-screen required to trigger this layout rule
<LAYOUT>
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
Options:
-h, --help

View File

@@ -10,7 +10,7 @@ Arguments:
Target workspace name
<VALUE>
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
Options:
-h, --help

View File

@@ -1,16 +0,0 @@
# subscribe-socket
```
Subscribe to komorebi events using a Unix Domain Socket
Usage: komorebic.exe subscribe-socket <SOCKET>
Arguments:
<SOCKET>
Name of the socket to send event notifications to
Options:
-h, --help
Print help
```

View File

@@ -1,9 +1,9 @@
# subscribe-pipe
# subscribe
```
Subscribe to komorebi events using a Named Pipe
Subscribe to komorebi events
Usage: komorebic.exe subscribe-pipe <NAMED_PIPE>
Usage: komorebic.exe subscribe <NAMED_PIPE>
Arguments:
<NAMED_PIPE>

View File

@@ -1,16 +0,0 @@
# unsubscribe-socket
```
Unsubscribe from komorebi events
Usage: komorebic.exe unsubscribe-socket <SOCKET>
Arguments:
<SOCKET>
Name of the socket to stop sending event notifications to
Options:
-h, --help
Print help
```

View File

@@ -1,9 +1,9 @@
# unsubscribe-pipe
# unsubscribe
```
Unsubscribe from komorebi events
Usage: komorebic.exe unsubscribe-pipe <NAMED_PIPE>
Usage: komorebic.exe unsubscribe <NAMED_PIPE>
Arguments:
<NAMED_PIPE>

View File

@@ -1,12 +0,0 @@
# whkdrc
```
Show the path to whkdrc
Usage: komorebic.exe whkdrc
Options:
-h, --help
Print help
```

View File

@@ -16,7 +16,7 @@ Arguments:
The number of window containers on-screen required to trigger this layout rule
<LAYOUT>
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
Options:
-h, --help

View File

@@ -13,7 +13,7 @@ Arguments:
Workspace index on the specified monitor (zero-indexed)
<VALUE>
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid]
[possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack]
Options:
-h, --help

View File

@@ -2,15 +2,13 @@
If you would like to remove all gaps by default, both between windows
themselves, and between the monitor edges and the windows, you can set the
following configuration options to `0` and `-1` in the `komorebi.json`
configuration file.
following two configuration options to `0` in the `komorebi.json` configuration
file.
```json
{
"default_workspace_padding": 0,
"default_container_padding": 0,
"border_width": 0,
"border_offset": -1
"default_container_padding": 0
}
```

View File

@@ -16,12 +16,6 @@ the example files have been downloaded. For most new users this will be in the
komorebic quickstart
```
With the example configurations downloaded, you can now start `komorebi` and `whkd.
```powershell
komorebic start --whkd
```
## komorebi.json
The example window manager configuration sets some sane defaults and provides
@@ -145,19 +139,6 @@ If you have an ultrawide monitor, I recommend using this layout.
+-----+-----------+-----+
```
### Grid
If you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layouts) this is almost exactly the same!
```
+-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
| | | | | | | | | | | | | | |
| | | | | | | | | | | | | +---+
+-----+-----+ | +---+---+ +---+---+---+ +---+---| |
| | | | | | | | | | | | | +---+
| | | | | | | | | | | | | | |
+-----+-----+ +---+---+---+ +---+---+---+ +---+---+---+
4 windows 5 windows 6 windows 7 windows
```
## whkdrc

View File

@@ -1,17 +1,15 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.22/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",
"default_workspace_padding": 20,
"default_container_padding": 20,
"border_padding": 8,
"border_offset": -1,
"active_window_border": false,
"active_window_border_colours": {
"single": "#42a5f5",
"stack": "#00a542",
"monocle": "#ff3399"
"single": { "r": 66, "g": 165, "b": 245 },
"stack": { "r": 256, "g": 165, "b": 66 },
"monocle": { "r": 255, "g": 51, "b": 153 }
},
"monitors": [
{

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-client"
version = "0.1.23-dev.0"
version = "0.1.22-dev.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -9,4 +9,4 @@ edition = "2021"
komorebi = { path = "../komorebi" }
komorebi-core = { path = "../komorebi-core" }
uds_windows = "1"
serde_json = { workspace = true }
serde_json = "1"

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-core"
version = "0.1.23-dev.0"
version = "0.1.22-dev.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = { workspace = true }
serde_json = "1"
serde_yaml = "0.9"
strum = { version = "0.26", features = ["derive"] }
schemars = "0.8"

View File

@@ -8,9 +8,7 @@ use strum::EnumString;
use crate::ApplicationIdentifier;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationOptions {
@@ -52,13 +50,6 @@ impl ApplicationOptions {
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum MatchingRule {
Simple(IdWithIdentifier),
Composite(Vec<IdWithIdentifier>),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct IdWithIdentifier {
pub kind: ApplicationIdentifier,
@@ -104,14 +95,14 @@ pub struct ApplicationConfiguration {
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<ApplicationOptions>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub float_identifiers: Option<Vec<MatchingRule>>,
pub float_identifiers: Option<Vec<IdWithIdentifierAndComment>>,
}
impl ApplicationConfiguration {
pub fn populate_default_matching_strategies(&mut self) {
if self.identifier.matching_strategy.is_none() {
match self.identifier.kind {
ApplicationIdentifier::Exe | ApplicationIdentifier::Path => {
ApplicationIdentifier::Exe => {
self.identifier.matching_strategy = Option::from(MatchingStrategy::Equals);
}
ApplicationIdentifier::Class | ApplicationIdentifier::Title => {}
@@ -190,21 +181,19 @@ impl ApplicationConfigurationGenerator {
}
if let Some(float_identifiers) = app.float_identifiers {
for matching_rule in float_identifiers {
if let MatchingRule::Simple(float) = matching_rule {
let float_rule =
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
for float in float_identifiers {
let float_rule =
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
// if let Some(comment) = float.comment {
// lines.push(format!("# {comment}"));
// };
if let Some(comment) = float.comment {
lines.push(format!("# {comment}"));
};
lines.push(float_rule);
}
lines.push(float_rule);
}
}
}
@@ -241,23 +230,21 @@ impl ApplicationConfigurationGenerator {
}
if let Some(float_identifiers) = app.float_identifiers {
for matching_rule in float_identifiers {
if let MatchingRule::Simple(float) = matching_rule {
let float_rule = format!(
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
float.kind, float.id
);
for float in float_identifiers {
let float_rule = format!(
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
float.kind, float.id
);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
// if let Some(comment) = float.comment {
// lines.push(format!("; {comment}"));
// };
if let Some(comment) = float.comment {
lines.push(format!("; {comment}"));
};
lines.push(float_rule);
}
lines.push(float_rule);
}
}
}

View File

@@ -223,8 +223,6 @@ pub enum ApplicationIdentifier {
Class,
#[serde(alias = "title")]
Title,
#[serde(alias = "path")]
Path,
}
#[derive(

View File

@@ -1,17 +1,15 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.22/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.20/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",
"default_workspace_padding": 20,
"default_container_padding": 20,
"border_padding": 8,
"border_offset": -1,
"active_window_border": false,
"active_window_border_colours": {
"single": "#42a5f5",
"stack": "#00a542",
"monocle": "#ff3399"
"single": { "r": 66, "g": 165, "b": 245 },
"stack": { "r": 256, "g": 165, "b": 66 },
"monocle": { "r": 255, "g": 51, "b": 153 }
},
"monitors": [
{

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.23-dev.0"
version = "0.1.22-dev.0"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -22,18 +22,18 @@ ctrlc = "3"
dirs = { workspace = true }
getset = "0.1"
hex_color = { version = "3", features = ["serde"] }
hotwatch = "0.5"
hotwatch = "0.4"
lazy_static = "1"
miow = "0.5"
nanoid = "0.4"
net2 = "0.2"
os_info = "3.8"
os_info = "3.7"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
paste = "1"
regex = "1"
schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = { workspace = true }
serde_json = "1"
strum = { version = "0.26", features = ["derive"] }
sysinfo = "0.30"
tracing = "0.1"

View File

@@ -10,6 +10,7 @@ use interfaces::IServiceProvider;
use std::ffi::c_void;
use windows::core::ComInterface;
use windows::core::Interface;
use windows::Win32::Foundation::HWND;
use windows::Win32::System::Com::CoCreateInstance;

View File

@@ -46,7 +46,6 @@ pub use windows_api::*;
use color_eyre::Result;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::HidingBehaviour;
@@ -67,57 +66,57 @@ type WorkspaceRule = (usize, usize, bool);
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
MatchingRule::Simple(IdWithIdentifier {
static ref LAYERED_WHITELIST: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("steam.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
},
]));
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> =
static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> =
Arc::new(Mutex::new(vec![
MatchingRule::Simple(IdWithIdentifier {
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("explorer.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("firefox.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("chrome.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("idea64.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("ApplicationFrameHost.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("steam.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
})
}
]));
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
MatchingRule::Simple(IdWithIdentifier {
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("firefox.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("idea64.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
},
]));
static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =
Arc::new(Mutex::new(HashMap::new()));
@@ -127,26 +126,25 @@ lazy_static! {
Arc::new(Mutex::new(HashMap::new()));
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![
// mstsc.exe creates these on Windows 11 when a WSL process is launched
// https://github.com/LGUG2Z/komorebi/issues/74
MatchingRule::Simple(IdWithIdentifier {
IdWithIdentifier {
kind: ApplicationIdentifier::Class,
id: String::from("OPContainerClass"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
MatchingRule::Simple(IdWithIdentifier {
},
IdWithIdentifier {
kind: ApplicationIdentifier::Class,
id: String::from("IHWindowClass"),
matching_strategy: Option::from(MatchingStrategy::Equals),
})
}
]));
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"Chrome_RenderWidgetHostHWND".to_string(),
]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<IdWithIdentifier>>> = Arc::new(Mutex::new(vec![]));
static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"X410.exe".to_string(),
"vcxsrv.exe".to_string(),
@@ -194,6 +192,7 @@ lazy_static! {
static ref BORDER_RECT: Arc<Mutex<Rect>> =
Arc::new(Mutex::new(Rect::default()));
static ref BORDER_OFFSET: AtomicI32 = Default::default();
// Use app-specific titlebar removal options where possible
// eg. Windows Terminal, IntelliJ IDEA, Firefox
@@ -213,9 +212,7 @@ pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_MONOCLE: AtomicU32 = AtomicU32::new(0);
pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0);
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(20);
// 0 0 0 aka pure black, I doubt anyone will want this as a border colour
pub const TRANSPARENCY_COLOUR: u32 = 0;
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);

View File

@@ -23,7 +23,6 @@ use uds_windows::UnixStream;
use komorebi_core::config_generation::ApplicationConfiguration;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
@@ -274,19 +273,17 @@ impl WindowManager {
let mut should_push = true;
for m in &*manage_identifiers {
if let MatchingRule::Simple(m) = m {
if m.id.eq(id) {
should_push = false;
}
if m.id.eq(id) {
should_push = false;
}
}
if should_push {
manage_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
manage_identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
});
}
}
SocketMessage::FloatRule(identifier, ref id) => {
@@ -294,19 +291,17 @@ impl WindowManager {
let mut should_push = true;
for f in &*float_identifiers {
if let MatchingRule::Simple(f) = f {
if f.id.eq(id) {
should_push = false;
}
if f.id.eq(id) {
should_push = false;
}
}
if should_push {
float_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
float_identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
});
}
let offset = self.work_area_offset;
@@ -320,11 +315,6 @@ impl WindowManager {
{
for window in container.windows() {
match identifier {
ApplicationIdentifier::Path => {
if window.path()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Exe => {
if window.exe()? == *id {
hwnds_to_purge.push((i, window.hwnd));
@@ -1030,19 +1020,17 @@ impl WindowManager {
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
});
}
}
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
@@ -1050,38 +1038,34 @@ impl WindowManager {
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
});
}
}
SocketMessage::IdentifyTrayApplication(identifier, ref id) => {
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
});
}
}
SocketMessage::IdentifyLayeredApplication(identifier, ref id) => {
@@ -1089,19 +1073,17 @@ impl WindowManager {
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
if i.id.eq(id) {
should_push = false;
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
identifiers.push(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
});
}
}
SocketMessage::ManageFocusedWindow => {
@@ -1402,14 +1384,10 @@ impl WindowManager {
);
}
// it is not acceptable to fail here; we need to be able to send the event to
// subscribers
if self.focused_container().is_ok() {
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
if stack != 0 && self.focused_container()?.windows().len() > 1 {
BORDER_COLOUR_CURRENT
.store(stack, Ordering::SeqCst);
}
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
if stack != 0 && self.focused_container()?.windows().len() > 1 {
BORDER_COLOUR_CURRENT
.store(stack, Ordering::SeqCst);
}
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));

View File

@@ -28,8 +28,6 @@ use crate::BORDER_COLOUR_STACK;
use crate::BORDER_ENABLED;
use crate::BORDER_HIDDEN;
use crate::BORDER_HWND;
use crate::BORDER_OFFSET;
use crate::BORDER_WIDTH;
use crate::DATA_DIR;
use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS;
@@ -147,7 +145,7 @@ impl WindowManager {
match event {
WindowManagerEvent::Raise(window) => {
window.focus(false)?;
window.raise();
self.has_pending_raise_op = false;
}
WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {
@@ -187,7 +185,6 @@ impl WindowManager {
let title = &window.title()?;
let exe_name = &window.exe()?;
let class = &window.class()?;
let path = &window.path()?;
// We don't want to purge windows that have been deliberately hidden by us, eg. when
// they are not on the top of a container stack.
@@ -196,7 +193,6 @@ impl WindowManager {
title,
exe_name,
class,
path,
&tray_and_multi_window_identifiers,
&regex_identifiers,
);
@@ -380,14 +376,7 @@ impl WindowManager {
// If we have moved across the monitors, use that override, otherwise determine
// if a move has taken place by ruling out a resize
let right_bottom_constant = ((BORDER_WIDTH.load(Ordering::SeqCst)
+ BORDER_OFFSET.load(Ordering::SeqCst))
* 2)
.abs();
let is_move = moved_across_monitors
|| resize.right.abs() == right_bottom_constant
&& resize.bottom.abs() == right_bottom_constant;
let is_move = moved_across_monitors || resize.right == 0 && resize.bottom == 0;
if is_move {
tracing::info!("moving with mouse");
@@ -479,17 +468,17 @@ impl WindowManager {
let mut ops = vec![];
macro_rules! resize_op {
($coordinate:expr, $comparator:tt, $direction:expr) => {{
let adjusted = $coordinate * 2;
let sizing = if adjusted $comparator 0 {
Sizing::Decrease
} else {
Sizing::Increase
};
($coordinate:expr, $comparator:tt, $direction:expr) => {{
let adjusted = $coordinate * 2;
let sizing = if adjusted $comparator 0 {
Sizing::Decrease
} else {
Sizing::Increase
};
($direction, sizing, adjusted.abs())
}};
}
($direction, sizing, adjusted.abs())
}};
}
if resize.left != 0 {
ops.push(resize_op!(resize.left, >, OperationDirection::Left));
@@ -499,14 +488,11 @@ impl WindowManager {
ops.push(resize_op!(resize.top, >, OperationDirection::Up));
}
let top_left_constant = BORDER_WIDTH.load(Ordering::SeqCst)
+ BORDER_OFFSET.load(Ordering::SeqCst);
if resize.right != 0 && resize.left == top_left_constant {
if resize.right != 0 && resize.left == 0 {
ops.push(resize_op!(resize.right, <, OperationDirection::Right));
}
if resize.bottom != 0 && resize.top == top_left_constant {
if resize.bottom != 0 && resize.top == 0 {
ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));
}

View File

@@ -32,13 +32,11 @@ use crate::WORKSPACE_RULES;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
use hotwatch::notify::DebouncedEvent;
use hotwatch::Hotwatch;
use komorebi_core::config_generation::ApplicationConfiguration;
use komorebi_core::config_generation::ApplicationConfigurationGenerator;
use komorebi_core::config_generation::ApplicationOptions;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::resolve_home_path;
use komorebi_core::ApplicationIdentifier;
@@ -271,22 +269,22 @@ pub struct StaticConfig {
pub global_work_area_offset: Option<Rect>,
/// Individual window floating rules
#[serde(skip_serializing_if = "Option::is_none")]
pub float_rules: Option<Vec<MatchingRule>>,
pub float_rules: Option<Vec<IdWithIdentifier>>,
/// Individual window force-manage rules
#[serde(skip_serializing_if = "Option::is_none")]
pub manage_rules: Option<Vec<MatchingRule>>,
pub manage_rules: Option<Vec<IdWithIdentifier>>,
/// Identify border overflow applications
#[serde(skip_serializing_if = "Option::is_none")]
pub border_overflow_applications: Option<Vec<MatchingRule>>,
pub border_overflow_applications: Option<Vec<IdWithIdentifier>>,
/// Identify tray and multi-window applications
#[serde(skip_serializing_if = "Option::is_none")]
pub tray_and_multi_window_applications: Option<Vec<MatchingRule>>,
pub tray_and_multi_window_applications: Option<Vec<IdWithIdentifier>>,
/// Identify applications that have the WS_EX_LAYERED extended window style
#[serde(skip_serializing_if = "Option::is_none")]
pub layered_applications: Option<Vec<MatchingRule>>,
pub layered_applications: Option<Vec<IdWithIdentifier>>,
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
#[serde(skip_serializing_if = "Option::is_none")]
pub object_name_change_applications: Option<Vec<MatchingRule>>,
pub object_name_change_applications: Option<Vec<IdWithIdentifier>>,
/// Set monitor index preferences
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor_index_preferences: Option<HashMap<usize, Rect>>,
@@ -454,40 +452,106 @@ impl StaticConfig {
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
let mut layered_identifiers = LAYERED_WHITELIST.lock();
if let Some(rules) = &mut self.float_rules {
populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?;
if let Some(float) = &mut self.float_rules {
for identifier in float {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !float_identifiers.contains(identifier) {
float_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(rules) = &mut self.manage_rules {
populate_rules(rules, &mut manage_identifiers, &mut regex_identifiers)?;
if let Some(manage) = &mut self.manage_rules {
for identifier in manage {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !manage_identifiers.contains(identifier) {
manage_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(rules) = &mut self.object_name_change_applications {
populate_rules(
rules,
&mut object_name_change_identifiers,
&mut regex_identifiers,
)?;
if let Some(identifiers) = &mut self.object_name_change_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !object_name_change_identifiers.contains(identifier) {
object_name_change_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(rules) = &mut self.layered_applications {
populate_rules(rules, &mut layered_identifiers, &mut regex_identifiers)?;
if let Some(identifiers) = &mut self.layered_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !layered_identifiers.contains(identifier) {
layered_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(rules) = &mut self.border_overflow_applications {
populate_rules(
rules,
&mut border_overflow_identifiers,
&mut regex_identifiers,
)?;
if let Some(identifiers) = &mut self.border_overflow_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !border_overflow_identifiers.contains(identifier) {
border_overflow_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(rules) = &mut self.tray_and_multi_window_applications {
populate_rules(
rules,
&mut tray_and_multi_window_identifiers,
&mut regex_identifiers,
)?;
if let Some(identifiers) = &mut self.tray_and_multi_window_applications {
for identifier in identifiers {
if identifier.matching_strategy.is_none() {
identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if !tray_and_multi_window_identifiers.contains(identifier) {
tray_and_multi_window_identifiers.push(identifier.clone());
if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&identifier.id)?;
regex_identifiers.insert(identifier.id.clone(), re);
}
}
}
}
if let Some(path) = &self.app_specific_configuration_path {
@@ -496,48 +560,120 @@ impl StaticConfig {
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(rules) = &mut entry.float_identifiers {
populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?;
}
if let Some(float) = entry.float_identifiers {
for f in float {
let mut without_comment: IdWithIdentifier = f.into();
if without_comment.matching_strategy.is_none() {
without_comment.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if let Some(ref options) = entry.options {
let options = options.clone();
if !float_identifiers.contains(&without_comment) {
float_identifiers.push(without_comment.clone());
if matches!(
without_comment.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&without_comment.id)?;
regex_identifiers.insert(without_comment.id.clone(), re);
}
}
}
}
if let Some(options) = entry.options {
for o in options {
match o {
ApplicationOptions::ObjectNameChange => {
populate_option(
&mut entry,
&mut object_name_change_identifiers,
&mut regex_identifiers,
)?;
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !object_name_change_identifiers.contains(&entry.identifier) {
object_name_change_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::Layered => {
populate_option(
&mut entry,
&mut layered_identifiers,
&mut regex_identifiers,
)?;
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !layered_identifiers.contains(&entry.identifier) {
layered_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::BorderOverflow => {
populate_option(
&mut entry,
&mut border_overflow_identifiers,
&mut regex_identifiers,
)?;
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !border_overflow_identifiers.contains(&entry.identifier) {
border_overflow_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::TrayAndMultiWindow => {
populate_option(
&mut entry,
&mut tray_and_multi_window_identifiers,
&mut regex_identifiers,
)?;
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !tray_and_multi_window_identifiers.contains(&entry.identifier) {
tray_and_multi_window_identifiers
.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
ApplicationOptions::Force => {
populate_option(
&mut entry,
&mut manage_identifiers,
&mut regex_identifiers,
)?;
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy =
Option::from(MatchingStrategy::Legacy);
}
if !manage_identifiers.contains(&entry.identifier) {
manage_identifiers.push(entry.identifier.clone());
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
}
}
}
@@ -608,10 +744,10 @@ impl StaticConfig {
let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;
wm.hotwatch.watch(path, move |event| match event.kind {
wm.hotwatch.watch(path, move |event| match event {
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
// a NoticeRemove, presumably because of the use of swap files?
EventKind::Modify(_) | EventKind::Remove(_) => {
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
let socket = DATA_DIR.join("komorebi.sock");
let mut stream =
UnixStream::connect(socket).expect("could not connect to komorebi.sock");
@@ -765,67 +901,3 @@ impl StaticConfig {
Ok(())
}
}
fn populate_option(
entry: &mut ApplicationConfiguration,
identifiers: &mut Vec<MatchingRule>,
regex_identifiers: &mut HashMap<String, Regex>,
) -> Result<()> {
if entry.identifier.matching_strategy.is_none() {
entry.identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
let rule = MatchingRule::Simple(entry.identifier.clone());
if !identifiers.contains(&rule) {
identifiers.push(rule);
if matches!(
entry.identifier.matching_strategy,
Some(MatchingStrategy::Regex)
) {
let re = Regex::new(&entry.identifier.id)?;
regex_identifiers.insert(entry.identifier.id.clone(), re);
}
}
Ok(())
}
fn populate_rules(
matching_rules: &mut Vec<MatchingRule>,
identifiers: &mut Vec<MatchingRule>,
regex_identifiers: &mut HashMap<String, Regex>,
) -> Result<()> {
for matching_rule in matching_rules {
if !identifiers.contains(matching_rule) {
match matching_rule {
MatchingRule::Simple(simple) => {
if simple.matching_strategy.is_none() {
simple.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if matches!(simple.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&simple.id)?;
regex_identifiers.insert(simple.id.clone(), re);
}
}
MatchingRule::Composite(composite) => {
for rule in composite {
if rule.matching_strategy.is_none() {
rule.matching_strategy = Option::from(MatchingStrategy::Legacy);
}
if matches!(rule.matching_strategy, Some(MatchingStrategy::Regex)) {
let re = Regex::new(&rule.id)?;
regex_identifiers.insert(rule.id.clone(), re);
}
}
}
}
identifiers.push(matching_rule.clone());
}
}
Ok(())
}

View File

@@ -4,13 +4,11 @@ use std::convert::TryFrom;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use std::time::Duration;
use color_eyre::eyre;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use regex::Regex;
use schemars::JsonSchema;
@@ -217,6 +215,57 @@ impl Window {
WindowsApi::unmaximize_window(self.hwnd());
}
pub fn raise(self) {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
let current_thread_id = WindowsApi::current_thread_id();
// This can be allowed to fail if a window doesn't have a message queue or if a journal record
// hook has been installed
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput#remarks
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not attach to window thread input processing mechanism, but continuing execution of raise(): {}",
error
);
}
};
// Raise Window to foreground
match WindowsApi::set_foreground_window(self.hwnd()) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not set as foreground window, but continuing execution of raise(): {}",
error
);
}
};
// This isn't really needed when the above command works as expected via AHK
match WindowsApi::set_focus(self.hwnd()) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not set focus, but continuing execution of raise(): {}",
error
);
}
};
match WindowsApi::attach_thread_input(current_thread_id, window_thread_id, false) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"could not detach from window thread input processing mechanism, but continuing execution of raise(): {}",
error
);
}
};
}
pub fn focus(self, mouse_follows_focus: bool) -> Result<()> {
// Attach komorebi thread to Window thread
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
@@ -328,14 +377,6 @@ impl Window {
WindowsApi::window_text_w(self.hwnd())
}
pub fn path(self) -> Result<String> {
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
let handle = WindowsApi::process_handle(process_id)?;
let path = WindowsApi::exe_path(handle);
WindowsApi::close_process(handle)?;
path
}
pub fn exe(self) -> Result<String> {
let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd());
let handle = WindowsApi::process_handle(process_id)?;
@@ -399,8 +440,8 @@ impl Window {
(true, _) |
// If not allowing cloaked windows, we need to ensure the window is not cloaked
(false, false) => {
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = (self.title(), self.exe(), self.class(), self.path()) {
return Ok(window_is_eligible(&title, &exe_name, &class, &path, &self.style()?, &self.ex_style()?, event));
if let (Ok(title), Ok(exe_name), Ok(class)) = (self.title(), self.exe(), self.class()) {
return Ok(window_is_eligible(&title, &exe_name, &class, &self.style()?, &self.ex_style()?, event));
}
}
_ => {}
@@ -414,7 +455,6 @@ fn window_is_eligible(
title: &String,
exe_name: &String,
class: &String,
path: &str,
style: &WindowStyle,
ex_style: &ExtendedWindowStyle,
event: Option<WindowManagerEvent>,
@@ -433,7 +473,6 @@ fn window_is_eligible(
title,
exe_name,
class,
path,
&float_identifiers,
&regex_identifiers,
);
@@ -443,7 +482,6 @@ fn window_is_eligible(
title,
exe_name,
class,
path,
&manage_identifiers,
&regex_identifiers,
);
@@ -457,7 +495,6 @@ fn window_is_eligible(
title,
exe_name,
class,
path,
&layered_whitelist,
&regex_identifiers,
);
@@ -475,10 +512,6 @@ fn window_is_eligible(
titlebars_removed.contains(exe_name)
};
if exe_name.contains("firefox") {
std::thread::sleep(Duration::from_millis(10));
}
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))
&& !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)
// Get a lot of dupe events coming through that make the redrawing go crazy
@@ -489,13 +522,8 @@ fn window_is_eligible(
|| managed_override
{
return true;
} else if let Some(event) = event {
tracing::debug!(
"ignoring (exe: {}, title: {}, event: {})",
exe_name,
title,
event
);
} else if event.is_some() {
tracing::debug!("ignoring (exe: {}, title: {})", exe_name, title);
}
false
@@ -506,204 +534,125 @@ pub fn should_act(
title: &str,
exe_name: &str,
class: &str,
path: &str,
identifiers: &[MatchingRule],
identifiers: &[IdWithIdentifier],
regex_identifiers: &HashMap<String, Regex>,
) -> bool {
let mut should_act = false;
for identifier in identifiers {
match identifier {
MatchingRule::Simple(identifier) => {
if should_act_individual(
title,
exe_name,
class,
path,
identifier,
regex_identifiers,
) {
should_act = true
};
match identifier.matching_strategy {
None => {
panic!("there is no matching strategy identified for this rule");
}
MatchingRule::Composite(identifiers) => {
let mut composite_results = vec![];
for identifier in identifiers {
composite_results.push(should_act_individual(
title,
exe_name,
class,
path,
identifier,
regex_identifiers,
));
Some(MatchingStrategy::Legacy) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
should_act = true;
}
}
if composite_results.iter().all(|&x| x) {
should_act = true;
ApplicationIdentifier::Class => {
if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {
should_act = true;
}
}
}
ApplicationIdentifier::Exe => {
if exe_name.eq(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Equals) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.eq(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::StartsWith) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.starts_with(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::EndsWith) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.ends_with(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Contains) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.contains(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Regex) => match identifier.kind {
ApplicationIdentifier::Title => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(title) {
should_act = true;
}
}
}
ApplicationIdentifier::Class => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(class) {
should_act = true;
}
}
}
ApplicationIdentifier::Exe => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(exe_name) {
should_act = true;
}
}
}
},
}
}
should_act
}
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
pub fn should_act_individual(
title: &str,
exe_name: &str,
class: &str,
path: &str,
identifier: &IdWithIdentifier,
regex_identifiers: &HashMap<String, Regex>,
) -> bool {
let mut should_act = false;
match identifier.matching_strategy {
None => {
panic!("there is no matching strategy identified for this rule");
}
Some(MatchingStrategy::Legacy) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Path => {
if path.eq(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Equals) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.eq(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Path => {
if path.eq(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::StartsWith) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.starts_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Path => {
if path.starts_with(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::EndsWith) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.ends_with(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Path => {
if path.ends_with(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Contains) => match identifier.kind {
ApplicationIdentifier::Title => {
if title.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Class => {
if class.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Exe => {
if exe_name.contains(&identifier.id) {
should_act = true;
}
}
ApplicationIdentifier::Path => {
if path.contains(&identifier.id) {
should_act = true;
}
}
},
Some(MatchingStrategy::Regex) => match identifier.kind {
ApplicationIdentifier::Title => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(title) {
should_act = true;
}
}
}
ApplicationIdentifier::Class => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(class) {
should_act = true;
}
}
}
ApplicationIdentifier::Exe => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(exe_name) {
should_act = true;
}
}
}
ApplicationIdentifier::Path => {
if let Some(re) = regex_identifiers.get(&identifier.id) {
if re.is_match(path) {
should_act = true;
}
}
}
},
}
should_act
}

View File

@@ -12,8 +12,7 @@ use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::notify::ErrorKind as NotifyErrorKind;
use hotwatch::EventKind;
use hotwatch::notify::DebouncedEvent;
use hotwatch::Hotwatch;
use parking_lot::Mutex;
use schemars::JsonSchema;
@@ -21,7 +20,7 @@ use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixListener;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::custom_layout::CustomLayout;
use komorebi_core::Arrangement;
use komorebi_core::Axis;
@@ -97,12 +96,12 @@ pub struct State {
pub mouse_follows_focus: bool,
pub has_pending_raise_op: bool,
pub remove_titlebars: bool,
pub float_identifiers: Vec<MatchingRule>,
pub manage_identifiers: Vec<MatchingRule>,
pub layered_whitelist: Vec<MatchingRule>,
pub tray_and_multi_window_identifiers: Vec<MatchingRule>,
pub border_overflow_identifiers: Vec<MatchingRule>,
pub name_change_on_launch_identifiers: Vec<MatchingRule>,
pub float_identifiers: Vec<IdWithIdentifier>,
pub manage_identifiers: Vec<IdWithIdentifier>,
pub layered_whitelist: Vec<IdWithIdentifier>,
pub tray_and_multi_window_identifiers: Vec<IdWithIdentifier>,
pub border_overflow_identifiers: Vec<IdWithIdentifier>,
pub name_change_on_launch_identifiers: Vec<IdWithIdentifier>,
pub monitor_index_preferences: HashMap<usize, Rect>,
pub display_index_preferences: HashMap<usize, String>,
}
@@ -212,16 +211,12 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn show_border(&self) -> Result<()> {
if self.focused_container().is_ok() {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(foreground_window, true)?;
WindowsApi::invalidate_border_rect()?;
}
Ok(())
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(foreground_window, true)?;
WindowsApi::invalidate_border_rect()
}
#[tracing::instrument(skip(self))]
@@ -266,18 +261,18 @@ impl WindowManager {
match self.hotwatch.unwatch(&config) {
Ok(()) => {}
Err(error) => match error {
hotwatch::Error::Notify(ref notify_error) => match notify_error.kind {
NotifyErrorKind::WatchNotFound => {}
_ => return Err(error.into()),
hotwatch::Error::Notify(error) => match error {
hotwatch::notify::Error::WatchNotFound => {}
error => return Err(error.into()),
},
error @ hotwatch::Error::Io(_) => return Err(error.into()),
},
}
self.hotwatch.watch(config, |event| match event.kind {
self.hotwatch.watch(config, |event| match event {
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
// a NoticeRemove, presumably because of the use of swap files?
EventKind::Modify(_) | EventKind::Remove(_) => {
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
std::thread::spawn(|| {
load_configuration().expect("could not load configuration");
});
@@ -671,44 +666,63 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn raise_window_at_cursor_pos(&mut self) -> Result<()> {
let mut hwnd = None;
let mut hwnd = WindowsApi::window_at_cursor_pos()?;
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
if let Some(container_idx) = workspace.container_idx_from_current_point() {
if let Some(container) = workspace.containers().get(container_idx) {
if let Some(window) = container.focused_window() {
hwnd = Some(window.hwnd);
if self.has_pending_raise_op
|| self.focused_window()?.hwnd == hwnd
// Sometimes we need this check, because the focus may have been given by a click
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
// the focused window of the workspace is not actually focused by the OS at that point
|| WindowsApi::foreground_window()? == hwnd
{
Ok(())
} else {
let mut known_hwnd = false;
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
if workspace.contains_window(hwnd) {
known_hwnd = true;
}
}
}
// TODO: Not sure if this needs to be made configurable just yet...
let overlay_classes = [
// Chromium/Electron
"Chrome_RenderWidgetHostHWND".to_string(),
// Explorer
"DirectUIHWND".to_string(),
"SysTreeView32".to_string(),
"ToolbarWindow32".to_string(),
"NetUIHWND".to_string(),
];
if !known_hwnd {
let class = Window { hwnd }.class()?;
// Some applications (Electron/Chromium-based, explorer) have (invisible?) overlays
// windows that we need to look beyond to find the actual window to raise
if overlay_classes.contains(&class) {
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?)
{
hwnd = exe_hwnd;
known_hwnd = true;
}
}
}
}
}
}
if let Some(hwnd) = hwnd {
if self.has_pending_raise_op
|| self.focused_window()?.hwnd == hwnd
// Sometimes we need this check, because the focus may have been given by a click
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
// the focused window of the workspace is not actually focused by the OS at that point
|| WindowsApi::foreground_window()? == hwnd
{
return Ok(());
if known_hwnd {
let event = WindowManagerEvent::Raise(Window { hwnd });
self.has_pending_raise_op = true;
Ok(winevent_listener::event_tx().send(event)?)
} else {
tracing::debug!("not raising unknown window: {}", Window { hwnd });
Ok(())
}
let event = WindowManagerEvent::Raise(Window { hwnd });
self.has_pending_raise_op = true;
winevent_listener::event_tx().send(event)?;
} else {
tracing::debug!(
"not raising unknown window: {}",
Window {
hwnd: WindowsApi::window_at_cursor_pos()?
}
);
}
Ok(())
}
#[tracing::instrument(skip(self))]
@@ -837,18 +851,17 @@ impl WindowManager {
}
}
// if we passed false for follow_focus and there is a container on the workspace
if !follow_focus && self.focused_container_mut().is_ok() {
// if we passed false for follow_focus
if !follow_focus
// and we have a stack with >1 windows
if self.focused_container_mut()?.windows().len() > 1
&& self.focused_container_mut()?.windows().len() > 1
// and we don't have a maxed window
&& self.focused_workspace()?.maximized_window().is_none()
// and we don't have a monocle container
&& self.focused_workspace()?.monocle_container().is_none()
{
if let Ok(window) = self.focused_window_mut() {
window.focus(self.mouse_follows_focus)?;
}
{
if let Ok(window) = self.focused_window_mut() {
window.focus(self.mouse_follows_focus)?;
}
};
@@ -1436,9 +1449,7 @@ impl WindowManager {
anyhow!("this is not a valid direction from the current position")
})?;
let adjusted_new_index = if new_idx > current_container_idx
&& !matches!(workspace.layout(), Layout::Default(DefaultLayout::Grid))
{
let adjusted_new_index = if new_idx > current_container_idx {
new_idx - 1
} else {
new_idx

View File

@@ -139,13 +139,11 @@ impl WindowManagerEvent {
let title = &window.title().ok()?;
let exe_name = &window.exe().ok()?;
let class = &window.class().ok()?;
let path = &window.path().ok()?;
let should_trigger = should_act(
title,
exe_name,
class,
path,
&object_name_change_on_launch,
&regex_identifiers,
);

View File

@@ -357,7 +357,7 @@ impl WindowsApi {
bottom: layout.bottom + shadow_rect.bottom,
};
let position = if top { HWND_TOP } else { HWND_NOTOPMOST };
let position = if top { HWND_TOP } else { HWND_BOTTOM };
Self::set_window_pos(hwnd, &rect, position, flags.bits())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic-no-console"
version = "0.1.23-dev.0"
version = "0.1.22-dev.0"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.23-dev.0"
version = "0.1.22-dev.0"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
@@ -21,12 +21,12 @@ dunce = { workspace = true }
fs-tail = "0.1"
heck = "0.4"
lazy_static = "1"
miette = { version = "7", features = ["fancy"] }
miette = { version = "5", features = ["fancy"] }
paste = "1"
powershell_script = "1.0"
reqwest = { version = "0.11", features = ["blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = { workspace = true }
serde_json = "1"
serde_yaml = "0.9"
sysinfo = "0.30"
thiserror = "1"

View File

@@ -114,7 +114,7 @@ trait AhkFunction {
struct ConfigurationError {
message: String,
#[source_code]
src: NamedSource<String>,
src: NamedSource,
#[label("This bit here")]
bad_bit: SourceSpan,
}
@@ -1367,7 +1367,7 @@ fn main() -> Result<()> {
let diagnostic = ConfigurationError {
message: msgs[0].to_string(),
src: NamedSource::new("komorebi.json", config_source.clone()),
bad_bit: SourceSpan::new(offset, 2),
bad_bit: SourceSpan::new(offset, 2.into()),
};
println!("{:?}", Report::new(diagnostic));

View File

@@ -62,23 +62,17 @@ nav:
- common-workflows/custom-layouts.md
- common-workflows/dynamic-layout-switching.md
# - common-workflows/autohotkey.md
- Release notes:
- release/v0-1-22.md
- Configuration reference: https://komorebi.lgug2z.com/schema
- CLI reference:
- cli/quickstart.md
- cli/start.md
- cli/stop.md
- cli/check.md
- cli/configuration.md
- cli/whkdrc.md
- cli/state.md
- cli/visible-windows.md
- cli/query.md
- cli/subscribe-socket.md
- cli/unsubscribe-socket.md
- cli/subscribe-pipe.md
- cli/unsubscribe-pipe.md
- cli/subscribe.md
- cli/unsubscribe.md
- cli/log.md
- cli/quick-save-resize.md
- cli/quick-load-resize.md
@@ -166,6 +160,7 @@ nav:
- cli/reload-configuration.md
- cli/watch-configuration.md
- cli/complete-configuration.md
- cli/alt-focus-hack.md
- cli/window-hiding-behaviour.md
- cli/cross-monitor-move-behaviour.md
- cli/toggle-cross-monitor-move-behaviour.md

View File

@@ -44,8 +44,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"ApplicationOptions": {

View File

@@ -133,15 +133,20 @@
}
}
},
"active_window_border_offset": {
"description": "Offset of the active window border (default: -1)",
"type": "integer",
"format": "int32"
},
"active_window_border_width": {
"description": "Width of the active window border (default: 8)",
"type": "integer",
"format": "int32"
},
"app_specific_configuration_path": {
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
"type": "string"
},
"border_offset": {
"description": "Offset of the window border (default: -1)",
"type": "integer",
"format": "int32"
},
"border_overflow_applications": {
"description": "Identify border overflow applications",
"type": "array",
@@ -160,8 +165,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -178,11 +182,6 @@
}
}
},
"border_width": {
"description": "Width of the window border (default: 8)",
"type": "integer",
"format": "int32"
},
"cross_monitor_move_behaviour": {
"description": "Determine what happens when a window is moved across a monitor boundary (default: Swap)",
"oneOf": [
@@ -237,8 +236,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -307,7 +305,7 @@
}
},
"invisible_borders": {
"description": "DEPRECATED from v0.1.22: no longer required",
"description": "Dimensions of Windows' own invisible borders; don't set these yourself unless you are told to",
"type": "object",
"required": [
"bottom",
@@ -356,8 +354,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -392,8 +389,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -529,8 +525,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -556,8 +551,7 @@
"Rows",
"VerticalStack",
"HorizontalStack",
"UltrawideVerticalStack",
"Grid"
"UltrawideVerticalStack"
]
},
"layout_rules": {
@@ -571,8 +565,7 @@
"Rows",
"VerticalStack",
"HorizontalStack",
"UltrawideVerticalStack",
"Grid"
"UltrawideVerticalStack"
]
}
},
@@ -603,8 +596,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -649,8 +641,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {
@@ -672,143 +663,6 @@
"type": "integer",
"format": "int32"
},
"stackbar": {
"type": "object",
"properties": {
"height": {
"type": "integer",
"format": "int32"
},
"mode": {
"type": "string",
"enum": [
"Always",
"Never",
"OnStack"
]
},
"tabs": {
"type": "object",
"properties": {
"background": {
"anyOf": [
{
"description": "Colour represented as RGB",
"type": "object",
"required": [
"b",
"g",
"r"
],
"properties": {
"b": {
"description": "Blue",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"g": {
"description": "Green",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"r": {
"description": "Red",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
{
"description": "Colour represented as Hex",
"type": "string"
}
]
},
"focused_text": {
"anyOf": [
{
"description": "Colour represented as RGB",
"type": "object",
"required": [
"b",
"g",
"r"
],
"properties": {
"b": {
"description": "Blue",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"g": {
"description": "Green",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"r": {
"description": "Red",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
{
"description": "Colour represented as Hex",
"type": "string"
}
]
},
"unfocused_text": {
"anyOf": [
{
"description": "Colour represented as RGB",
"type": "object",
"required": [
"b",
"g",
"r"
],
"properties": {
"b": {
"description": "Blue",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"g": {
"description": "Green",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"r": {
"description": "Red",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
{
"description": "Colour represented as Hex",
"type": "string"
}
]
},
"width": {
"type": "integer",
"format": "int32"
}
}
}
}
},
"tray_and_multi_window_applications": {
"description": "Identify tray and multi-window applications",
"type": "array",
@@ -827,8 +681,7 @@
"enum": [
"Exe",
"Class",
"Title",
"Path"
"Title"
]
},
"matching_strategy": {