mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-02-24 06:04:50 +01:00
Compare commits
25 Commits
feature/wi
...
feature/ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c07689de90 | ||
|
|
ca893140f5 | ||
|
|
b26910aa58 | ||
|
|
487c217497 | ||
|
|
4031fbf033 | ||
|
|
dadc40777f | ||
|
|
59544edb74 | ||
|
|
5e2c18cad3 | ||
|
|
d69dfeb715 | ||
|
|
3a208b577c | ||
|
|
20817b094d | ||
|
|
394709e356 | ||
|
|
990a339d4e | ||
|
|
f0222dd4ab | ||
|
|
974e5a2b20 | ||
|
|
2bbc269b9f | ||
|
|
13ee42276d | ||
|
|
3641ce6b42 | ||
|
|
9d41a293f6 | ||
|
|
1756983978 | ||
|
|
3d327c407c | ||
|
|
e5fb5390a8 | ||
|
|
6a8e362c21 | ||
|
|
1edeb44203 | ||
|
|
8bc04f0610 |
490
Cargo.lock
generated
490
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
33
Cargo.toml
33
Cargo.toml
@@ -17,8 +17,8 @@ chrono = "0.4"
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-utils = "0.8"
|
||||
color-eyre = "0.6"
|
||||
eframe = "0.30"
|
||||
egui_extras = "0.30"
|
||||
eframe = "0.31"
|
||||
egui_extras = "0.31"
|
||||
dirs = "6"
|
||||
dunce = "1"
|
||||
hotwatch = "0.5"
|
||||
@@ -27,25 +27,27 @@ lazy_static = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { package = "serde_json_lenient", version = "0.2" }
|
||||
serde_yaml = "0.9"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
paste = "1"
|
||||
sysinfo = "0.33"
|
||||
uds_windows = "1"
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "3ff53fb6f53ec3ec4f9941a0409fba5e36decc46" }
|
||||
windows-implement = { version = "0.58" }
|
||||
windows-interface = { version = "0.58" }
|
||||
windows-core = { version = "0.58" }
|
||||
shadow-rs = "0.38"
|
||||
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
|
||||
windows-numerics = { version = "0.1" }
|
||||
windows-implement = { version = "0.59" }
|
||||
windows-interface = { version = "0.59" }
|
||||
windows-core = { version = "0.60" }
|
||||
shadow-rs = "1"
|
||||
which = "7"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.58"
|
||||
version = "0.60"
|
||||
features = [
|
||||
"implement",
|
||||
"Foundation_Numerics",
|
||||
"Win32_Devices",
|
||||
"Win32_Devices_Display",
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_Shell_Common", # for IObjectArray
|
||||
"Win32_Foundation",
|
||||
@@ -70,3 +72,16 @@ features = [
|
||||
"Media",
|
||||
"Media_Control"
|
||||
]
|
||||
|
||||
[profile.dev-jeezy]
|
||||
inherits = "dev"
|
||||
debug = false
|
||||
opt-level = 1
|
||||
|
||||
[profile.dev-jeezy.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release-jeezy]
|
||||
inherits = "release"
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
12
docs/cli/toggle-workspace-layer.md
Normal file
12
docs/cli/toggle-workspace-layer.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# toggle-workspace-layer
|
||||
|
||||
```
|
||||
Toggle between the Tiling and Floating layers on the focused workspace
|
||||
|
||||
Usage: komorebic.exe toggle-workspace-layer
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
14
justfile
14
justfile
@@ -28,11 +28,23 @@ build-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
|
||||
|
||||
build-target target:
|
||||
cargo +stable build --release --package {{ target }} --locked
|
||||
cargo +stable build --package {{ target }} --locked --profile release-jeezy
|
||||
|
||||
build:
|
||||
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
|
||||
copy-target target:
|
||||
cp .\target\release-jeezy\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
|
||||
|
||||
copy-targets *targets:
|
||||
"{{ targets }}" -split ' ' | ForEach-Object { just copy-target $_ }
|
||||
|
||||
wpm target:
|
||||
just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}
|
||||
|
||||
copy:
|
||||
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
|
||||
|
||||
run target:
|
||||
cargo +stable run --bin {{ target }} --locked
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "komorebi-bar"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -16,7 +16,7 @@ crossbeam-channel = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
eframe = { workspace = true }
|
||||
egui-phosphor = "0.8"
|
||||
egui-phosphor = "0.9"
|
||||
font-loader = "0.11"
|
||||
hotwatch = { workspace = true }
|
||||
image = "0.25"
|
||||
@@ -34,4 +34,5 @@ sysinfo = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
|
||||
@@ -1,9 +1,16 @@
|
||||
use crate::config::get_individual_spacing;
|
||||
use crate::BAR_HEIGHT;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use crate::KomorebiEvent;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_LEFT;
|
||||
use crate::MONITOR_RIGHT;
|
||||
use crate::MONITOR_TOP;
|
||||
use crate::config::KomobarConfig;
|
||||
use crate::config::KomobarTheme;
|
||||
use crate::config::MonitorConfigOrIndex;
|
||||
use crate::config::Position;
|
||||
use crate::config::PositionConfig;
|
||||
use crate::config::get_individual_spacing;
|
||||
use crate::komorebi::Komorebi;
|
||||
use crate::komorebi::KomorebiNotificationState;
|
||||
use crate::process_hwnd;
|
||||
@@ -13,13 +20,6 @@ use crate::render::RenderConfig;
|
||||
use crate::render::RenderExt;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::widget::WidgetConfig;
|
||||
use crate::KomorebiEvent;
|
||||
use crate::BAR_HEIGHT;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_LEFT;
|
||||
use crate::MONITOR_RIGHT;
|
||||
use crate::MONITOR_TOP;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::TryRecvError;
|
||||
use eframe::egui::Align;
|
||||
@@ -47,16 +47,16 @@ use komorebi_client::KomorebiTheme;
|
||||
use komorebi_client::MonitorNotification;
|
||||
use komorebi_client::NotificationEvent;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_themes::catppuccin_egui;
|
||||
use komorebi_themes::Base16Value;
|
||||
use komorebi_themes::Catppuccin;
|
||||
use komorebi_themes::CatppuccinValue;
|
||||
use komorebi_themes::catppuccin_egui;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub struct Komobar {
|
||||
pub hwnd: Option<isize>,
|
||||
@@ -178,11 +178,11 @@ pub fn apply_theme(
|
||||
{
|
||||
if let Some(rounding) = config.rounding {
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.widgets.noninteractive.rounding = rounding.into();
|
||||
style.visuals.widgets.inactive.rounding = rounding.into();
|
||||
style.visuals.widgets.hovered.rounding = rounding.into();
|
||||
style.visuals.widgets.active.rounding = rounding.into();
|
||||
style.visuals.widgets.open.rounding = rounding.into();
|
||||
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -365,24 +365,22 @@ impl Komobar {
|
||||
&SocketMessage::MonitorWorkAreaOffset(monitor_index, new_rect),
|
||||
) {
|
||||
tracing::error!(
|
||||
"error applying work area offset to monitor '{}': {}",
|
||||
monitor_index,
|
||||
error,
|
||||
"error applying work area offset to monitor '{monitor_index}': {error}"
|
||||
);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"work area offset applied to monitor: {}\n, {:#?}",
|
||||
monitor_index,
|
||||
new_rect
|
||||
);
|
||||
tracing::info!("work area offset applied to monitor: {monitor_index}",);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.komorebi_notification_state.is_some() && !self.disabled {
|
||||
tracing::warn!("couldn't find the monitor index of this bar! Disabling the bar until the monitor connects...");
|
||||
tracing::warn!(
|
||||
"couldn't find the monitor index of this bar! Disabling the bar until the monitor connects..."
|
||||
);
|
||||
self.disabled = true;
|
||||
} else {
|
||||
tracing::warn!("couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi.");
|
||||
tracing::warn!(
|
||||
"couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi."
|
||||
);
|
||||
self.disabled = true;
|
||||
}
|
||||
|
||||
@@ -425,7 +423,9 @@ impl Komobar {
|
||||
end.x -= margin.left + margin.right;
|
||||
|
||||
if end.y == 0.0 {
|
||||
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
|
||||
tracing::warn!(
|
||||
"position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default"
|
||||
)
|
||||
}
|
||||
|
||||
self.size_rect = komorebi_client::Rect {
|
||||
@@ -508,11 +508,12 @@ impl Komobar {
|
||||
{
|
||||
if let Some(rounding) = config.rounding {
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.widgets.noninteractive.rounding = rounding.into();
|
||||
style.visuals.widgets.inactive.rounding = rounding.into();
|
||||
style.visuals.widgets.hovered.rounding = rounding.into();
|
||||
style.visuals.widgets.active.rounding = rounding.into();
|
||||
style.visuals.widgets.open.rounding = rounding.into();
|
||||
style.visuals.widgets.noninteractive.corner_radius =
|
||||
rounding.into();
|
||||
style.visuals.widgets.inactive.corner_radius = rounding.into();
|
||||
style.visuals.widgets.hovered.corner_radius = rounding.into();
|
||||
style.visuals.widgets.active.corner_radius = rounding.into();
|
||||
style.visuals.widgets.open.corner_radius = rounding.into();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -631,10 +632,10 @@ impl Komobar {
|
||||
let window = komorebi_client::Window::from(hwnd);
|
||||
match window.set_position(&self.size_rect, false) {
|
||||
Ok(_) => {
|
||||
tracing::info!("updated bar position: {:#?}", &self.size_rect);
|
||||
tracing::info!("updated bar position");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!("{}", error.to_string())
|
||||
tracing::error!("{error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -724,6 +725,12 @@ impl eframe::App for Komobar {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the current `work_area_offset` so that it gets recalculated and
|
||||
// properly applied again, since if the monitor has connected for the first
|
||||
// time it won't have the work_area_offset applied but the bar thinks it
|
||||
// does.
|
||||
self.work_area_offset = komorebi_client::Rect::default();
|
||||
|
||||
should_apply_config = true;
|
||||
}
|
||||
self.disabled = false;
|
||||
@@ -839,26 +846,26 @@ impl eframe::App for Komobar {
|
||||
let frame = match &self.config.padding {
|
||||
None => {
|
||||
if let Some(frame) = &self.config.frame {
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::symmetric(
|
||||
frame.inner_margin.x,
|
||||
frame.inner_margin.y,
|
||||
frame.inner_margin.x as i8,
|
||||
frame.inner_margin.y as i8,
|
||||
))
|
||||
.fill(*self.bg_color_with_alpha.borrow())
|
||||
} else {
|
||||
Frame::none()
|
||||
.inner_margin(Margin::same(0.0))
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(0))
|
||||
.fill(*self.bg_color_with_alpha.borrow())
|
||||
}
|
||||
}
|
||||
Some(padding) => {
|
||||
let padding = padding.to_individual(DEFAULT_PADDING);
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.inner_margin(Margin {
|
||||
top: padding.top,
|
||||
bottom: padding.bottom,
|
||||
left: padding.left,
|
||||
right: padding.right,
|
||||
top: padding.top as i8,
|
||||
bottom: padding.bottom as i8,
|
||||
left: padding.left as i8,
|
||||
right: padding.right as i8,
|
||||
})
|
||||
.fill(*self.bg_color_with_alpha.borrow())
|
||||
}
|
||||
@@ -871,13 +878,13 @@ impl eframe::App for Komobar {
|
||||
CentralPanel::default().frame(frame).show(ctx, |ui| {
|
||||
// Apply grouping logic for the bar as a whole
|
||||
let area_frame = if let Some(frame) = &self.config.frame {
|
||||
Frame::none()
|
||||
.inner_margin(Margin::symmetric(0.0, frame.inner_margin.y))
|
||||
.outer_margin(Margin::same(0.0))
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::symmetric(0, frame.inner_margin.y as i8))
|
||||
.outer_margin(Margin::same(0))
|
||||
} else {
|
||||
Frame::none()
|
||||
.inner_margin(Margin::same(0.0))
|
||||
.outer_margin(Margin::same(0.0))
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(0))
|
||||
.outer_margin(Margin::same(0))
|
||||
};
|
||||
|
||||
let available_height = ui.max_rect().max.y;
|
||||
@@ -897,13 +904,13 @@ impl eframe::App for Komobar {
|
||||
.as_ref()
|
||||
.map(|s| s.to_individual(DEFAULT_PADDING))
|
||||
{
|
||||
left_area_frame.inner_margin.left = padding.left;
|
||||
left_area_frame.inner_margin.top = padding.top;
|
||||
left_area_frame.inner_margin.bottom = padding.bottom;
|
||||
left_area_frame.inner_margin.left = padding.left as i8;
|
||||
left_area_frame.inner_margin.top = padding.top as i8;
|
||||
left_area_frame.inner_margin.bottom = padding.bottom as i8;
|
||||
} else if let Some(frame) = &self.config.frame {
|
||||
left_area_frame.inner_margin.left = frame.inner_margin.x;
|
||||
left_area_frame.inner_margin.top = frame.inner_margin.y;
|
||||
left_area_frame.inner_margin.bottom = frame.inner_margin.y;
|
||||
left_area_frame.inner_margin.left = frame.inner_margin.x as i8;
|
||||
left_area_frame.inner_margin.top = frame.inner_margin.y as i8;
|
||||
left_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
|
||||
}
|
||||
|
||||
left_area_frame.show(ui, |ui| {
|
||||
@@ -933,13 +940,13 @@ impl eframe::App for Komobar {
|
||||
.as_ref()
|
||||
.map(|s| s.to_individual(DEFAULT_PADDING))
|
||||
{
|
||||
right_area_frame.inner_margin.right = padding.right;
|
||||
right_area_frame.inner_margin.top = padding.top;
|
||||
right_area_frame.inner_margin.bottom = padding.bottom;
|
||||
right_area_frame.inner_margin.right = padding.right as i8;
|
||||
right_area_frame.inner_margin.top = padding.top as i8;
|
||||
right_area_frame.inner_margin.bottom = padding.bottom as i8;
|
||||
} else if let Some(frame) = &self.config.frame {
|
||||
right_area_frame.inner_margin.right = frame.inner_margin.x;
|
||||
right_area_frame.inner_margin.top = frame.inner_margin.y;
|
||||
right_area_frame.inner_margin.bottom = frame.inner_margin.y;
|
||||
right_area_frame.inner_margin.right = frame.inner_margin.x as i8;
|
||||
right_area_frame.inner_margin.top = frame.inner_margin.y as i8;
|
||||
right_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
|
||||
}
|
||||
|
||||
right_area_frame.show(ui, |ui| {
|
||||
@@ -977,11 +984,11 @@ impl eframe::App for Komobar {
|
||||
.as_ref()
|
||||
.map(|s| s.to_individual(DEFAULT_PADDING))
|
||||
{
|
||||
center_area_frame.inner_margin.top = padding.top;
|
||||
center_area_frame.inner_margin.bottom = padding.bottom;
|
||||
center_area_frame.inner_margin.top = padding.top as i8;
|
||||
center_area_frame.inner_margin.bottom = padding.bottom as i8;
|
||||
} else if let Some(frame) = &self.config.frame {
|
||||
center_area_frame.inner_margin.top = frame.inner_margin.y;
|
||||
center_area_frame.inner_margin.bottom = frame.inner_margin.y;
|
||||
center_area_frame.inner_margin.top = frame.inner_margin.y as i8;
|
||||
center_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
|
||||
}
|
||||
|
||||
center_area_frame.show(ui, |ui| {
|
||||
|
||||
@@ -2,18 +2,18 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use starship_battery::units::ratio::percent;
|
||||
use starship_battery::Manager;
|
||||
use starship_battery::State;
|
||||
use starship_battery::units::ratio::percent;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::DEFAULT_PADDING;
|
||||
use crate::render::Grouping;
|
||||
use crate::widget::WidgetConfig;
|
||||
use crate::DEFAULT_PADDING;
|
||||
use eframe::egui::Pos2;
|
||||
use eframe::egui::TextBuffer;
|
||||
use eframe::egui::Vec2;
|
||||
@@ -115,7 +115,9 @@ impl KomobarConfig {
|
||||
}
|
||||
|
||||
if display {
|
||||
println!("\nYour bar configuration file contains some options that have been renamed or deprecated:\n");
|
||||
println!(
|
||||
"\nYour bar configuration file contains some options that have been renamed or deprecated:\n"
|
||||
);
|
||||
for (canonical, aliases) in map {
|
||||
for alias in aliases {
|
||||
if raw.contains(alias) {
|
||||
@@ -125,6 +127,15 @@ impl KomobarConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_all_icons_on_komorebi_workspace(widgets: &[WidgetConfig]) -> bool {
|
||||
widgets
|
||||
.iter()
|
||||
.any(|w| matches!(w, WidgetConfig::Komorebi(config) if config.workspaces.is_some_and(|w| w.enable && w.display.is_some_and(|s| matches!(s,
|
||||
WorkspacesDisplayFormat::AllIcons
|
||||
| WorkspacesDisplayFormat::AllIconsAndText
|
||||
| WorkspacesDisplayFormat::AllIconsAndTextOnSelected)))))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -416,3 +427,90 @@ pub enum DisplayFormat {
|
||||
/// Show an icon and text for the selected element, and icons on the rest
|
||||
IconAndTextOnSelected,
|
||||
}
|
||||
|
||||
macro_rules! extend_enum {
|
||||
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq)]
|
||||
pub enum $new_enum {
|
||||
// Add new variants
|
||||
$(
|
||||
$(#[$meta])*
|
||||
$variant,
|
||||
)*
|
||||
// Include a variant that wraps the existing enum and flatten it when deserializing
|
||||
#[serde(untagged)]
|
||||
Existing($existing_enum),
|
||||
}
|
||||
|
||||
// Implement From for the existing enum
|
||||
impl From<$existing_enum> for $new_enum {
|
||||
fn from(value: $existing_enum) -> Self {
|
||||
$new_enum::Existing(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
|
||||
/// Show all icons only
|
||||
AllIcons,
|
||||
/// Show both all icons and text
|
||||
AllIconsAndText,
|
||||
/// Show all icons and text for the selected element, and all icons on the rest
|
||||
AllIconsAndTextOnSelected,
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub enum OriginalDisplayFormat {
|
||||
/// Show None Of The Things
|
||||
NoneOfTheThings,
|
||||
}
|
||||
|
||||
extend_enum!(OriginalDisplayFormat, ExtendedDisplayFormat, {
|
||||
/// Show Some Of The Things
|
||||
SomeOfTheThings,
|
||||
});
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct ExampleConfig {
|
||||
#[allow(unused)]
|
||||
format: ExtendedDisplayFormat,
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn extend_new_variant() {
|
||||
let raw = json!({
|
||||
"format": "SomeOfTheThings",
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn extend_existing_variant() {
|
||||
let raw = json!({
|
||||
"format": "NoneOfTheThings",
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn extend_invalid_variant() {
|
||||
let raw = json!({
|
||||
"format": "ALLOFTHETHINGS",
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_err())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -2,13 +2,13 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
use crate::ICON_CACHE;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_INDEX;
|
||||
use crate::bar::apply_theme;
|
||||
use crate::config::DisplayFormat;
|
||||
use crate::config::KomobarTheme;
|
||||
use crate::config::WorkspacesDisplayFormat;
|
||||
use crate::komorebi_layout::KomorebiLayout;
|
||||
use crate::render::Grouping;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::ui::CustomUi;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::ICON_CACHE;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_INDEX;
|
||||
use eframe::egui::vec2;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::ColorImage;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::Frame;
|
||||
use eframe::egui::Image;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::Margin;
|
||||
use eframe::egui::Rounding;
|
||||
use eframe::egui::RichText;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::TextureHandle;
|
||||
use eframe::egui::TextureOptions;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::vec2;
|
||||
use image::RgbaImage;
|
||||
use komorebi_client::Container;
|
||||
use komorebi_client::NotificationEvent;
|
||||
@@ -33,6 +36,7 @@ use komorebi_client::Rect;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_client::Window;
|
||||
use komorebi_client::Workspace;
|
||||
use komorebi_client::WorkspaceLayer;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -49,6 +53,8 @@ pub struct KomorebiConfig {
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
/// Configure the Layout widget
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
/// Configure the Workspace Layer widget
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
/// Configure the Focused Window widget
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
/// Configure the Configuration Switcher widget
|
||||
@@ -62,7 +68,7 @@ pub struct KomorebiWorkspacesConfig {
|
||||
/// Hide workspaces without any windows
|
||||
pub hide_empty_workspaces: bool,
|
||||
/// Display format of the workspace
|
||||
pub display: Option<DisplayFormat>,
|
||||
pub display: Option<WorkspacesDisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -75,6 +81,12 @@ pub struct KomorebiLayoutConfig {
|
||||
pub display: Option<DisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiWorkspaceLayerConfig {
|
||||
/// Enable the Komorebi Workspace Layer widget
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct KomorebiFocusedWindowConfig {
|
||||
/// Enable the Komorebi Focused Window widget
|
||||
@@ -127,6 +139,7 @@ impl From<&KomorebiConfig> for Komorebi {
|
||||
workspaces: value.workspaces,
|
||||
layout: value.layout.clone(),
|
||||
focused_window: value.focused_window,
|
||||
workspace_layer: value.workspace_layer,
|
||||
configuration_switcher,
|
||||
}
|
||||
}
|
||||
@@ -138,6 +151,7 @@ pub struct Komorebi {
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
|
||||
@@ -145,84 +159,92 @@ impl BarWidget for Komorebi {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
|
||||
let icon_size = Vec2::splat(config.icon_font_id.size);
|
||||
let text_size = Vec2::splat(config.text_font_id.size);
|
||||
|
||||
if let Some(workspaces) = self.workspaces {
|
||||
if workspaces.enable {
|
||||
let mut update = None;
|
||||
|
||||
if !komorebi_notification_state.workspaces.is_empty() {
|
||||
let format = workspaces.display.unwrap_or(DisplayFormat::Text);
|
||||
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
for (i, (ws, container_information)) in
|
||||
for (i, (ws, containers, _)) in
|
||||
komorebi_notification_state.workspaces.iter().enumerate()
|
||||
{
|
||||
let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
|
||||
|
||||
if SelectableFrame::new(
|
||||
komorebi_notification_state.selected_workspace.eq(ws),
|
||||
is_selected,
|
||||
)
|
||||
.show(ui, |ui| {
|
||||
let mut has_icon = false;
|
||||
|
||||
if format == DisplayFormat::Icon
|
||||
|| format == DisplayFormat::IconAndText
|
||||
|| format == DisplayFormat::IconAndTextOnSelected
|
||||
|| (format == DisplayFormat::TextAndIconOnSelected
|
||||
&& komorebi_notification_state.selected_workspace.eq(ws))
|
||||
if format == WorkspacesDisplayFormat::AllIcons
|
||||
|| format == WorkspacesDisplayFormat::AllIconsAndText
|
||||
|| format == WorkspacesDisplayFormat::AllIconsAndTextOnSelected
|
||||
|| format == DisplayFormat::Icon.into()
|
||||
|| format == DisplayFormat::IconAndText.into()
|
||||
|| format == DisplayFormat::IconAndTextOnSelected.into()
|
||||
|| (format == DisplayFormat::TextAndIconOnSelected.into() && is_selected)
|
||||
{
|
||||
let icons: Vec<_> =
|
||||
container_information.icons.iter().flatten().collect();
|
||||
has_icon = containers.iter().any(|(_, container_info)| {
|
||||
container_info.icons.iter().any(|icon| icon.is_some())
|
||||
});
|
||||
|
||||
if !icons.is_empty() {
|
||||
Frame::none()
|
||||
if has_icon {
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(
|
||||
ui.style().spacing.button_padding.y,
|
||||
ui.style().spacing.button_padding.y as i8,
|
||||
))
|
||||
.show(ui, |ui| {
|
||||
for icon in icons {
|
||||
ui.add(
|
||||
Image::from(&img_to_texture(ctx, icon))
|
||||
.maintain_aspect_ratio(true)
|
||||
.fit_to_exact_size(icon_size),
|
||||
);
|
||||
|
||||
if !has_icon {
|
||||
has_icon = true;
|
||||
for (is_focused, container) in containers {
|
||||
for icon in container.icons.iter().flatten().collect::<Vec<_>>() {
|
||||
ui.add(
|
||||
Image::from(&img_to_texture(ctx, icon))
|
||||
.maintain_aspect_ratio(true)
|
||||
.fit_to_exact_size(if *is_focused { icon_size } else { text_size }),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// draw a custom icon when there is no app icon
|
||||
if match format {
|
||||
DisplayFormat::Icon => !has_icon,
|
||||
_ => false,
|
||||
} {
|
||||
// draw a custom icon when there is no app icon or text
|
||||
if !has_icon && (matches!(format, WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon))
|
||||
|| (!is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))) {
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(icon_size, Sense::hover());
|
||||
let stroke = Stroke::new(
|
||||
1.0,
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
if is_selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() },
|
||||
);
|
||||
let mut rect = response.rect;
|
||||
let rounding = Rounding::same(rect.width() * 0.1);
|
||||
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
|
||||
rect = rect.shrink(stroke.width);
|
||||
let c = rect.center();
|
||||
let r = rect.width() / 2.0;
|
||||
painter.rect_stroke(rect, rounding, stroke);
|
||||
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
|
||||
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
|
||||
|
||||
response.on_hover_text(ws.to_string())
|
||||
// add hover text when there are only icons
|
||||
} else if match format {
|
||||
DisplayFormat::Icon => has_icon,
|
||||
WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon) => has_icon,
|
||||
_ => false,
|
||||
} {
|
||||
ui.response().on_hover_text(ws.to_string())
|
||||
} else if format != DisplayFormat::IconAndTextOnSelected
|
||||
|| (format == DisplayFormat::IconAndTextOnSelected
|
||||
&& komorebi_notification_state.selected_workspace.eq(ws))
|
||||
// add label only
|
||||
} else if (format != WorkspacesDisplayFormat::AllIconsAndTextOnSelected && format != DisplayFormat::IconAndTextOnSelected.into())
|
||||
|| (is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))
|
||||
{
|
||||
ui.add(Label::new(ws.to_string()).selectable(false))
|
||||
if is_selected {
|
||||
ui.add(Label::new(RichText::new(ws.to_string()).color(ctx.style().visuals.selection.stroke.color)).selectable(false))
|
||||
}
|
||||
else {
|
||||
ui.add(Label::new(ws.to_string()).selectable(false))
|
||||
}
|
||||
} else {
|
||||
ui.response()
|
||||
}
|
||||
@@ -281,6 +303,42 @@ impl BarWidget for Komorebi {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(layer_config) = &self.workspace_layer {
|
||||
if layer_config.enable {
|
||||
let layer = komorebi_notification_state
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
|
||||
.map(|(_, _, layer)| layer);
|
||||
|
||||
if let Some(layer) = layer {
|
||||
let name = layer.to_string();
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(name).selectable(false)))
|
||||
.clicked()
|
||||
&& komorebi_client::send_batch([
|
||||
SocketMessage::MouseFollowsFocus(false),
|
||||
SocketMessage::ToggleWorkspaceLayer,
|
||||
SocketMessage::MouseFollowsFocus(
|
||||
komorebi_notification_state.mouse_follows_focus,
|
||||
),
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!(
|
||||
"could not send the following batch of messages to komorebi:\n\
|
||||
MouseFollowsFocus(false),
|
||||
ToggleWorkspaceLayer,
|
||||
MouseFollowsFocus({})",
|
||||
komorebi_notification_state.mouse_follows_focus,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(layout_config) = &self.layout {
|
||||
if layout_config.enable {
|
||||
let workspace_idx: Option<usize> = komorebi_notification_state
|
||||
@@ -376,6 +434,7 @@ impl BarWidget for Komorebi {
|
||||
|
||||
for (i, (title, icon)) in iter.enumerate() {
|
||||
let selected = i == focused_window_idx && len != 1;
|
||||
let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() };
|
||||
|
||||
if SelectableFrame::new(selected)
|
||||
.show(ui, |ui| {
|
||||
@@ -395,9 +454,9 @@ impl BarWidget for Komorebi {
|
||||
&& i == focused_window_idx)
|
||||
{
|
||||
if let Some(img) = icon {
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.inner_margin(Margin::same(
|
||||
ui.style().spacing.button_padding.y,
|
||||
ui.style().spacing.button_padding.y as i8,
|
||||
))
|
||||
.show(ui, |ui| {
|
||||
let response = ui.add(
|
||||
@@ -427,7 +486,7 @@ impl BarWidget for Komorebi {
|
||||
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
|
||||
available_height,
|
||||
),
|
||||
Label::new(title).selectable(false).truncate(),
|
||||
Label::new(RichText::new( title).color(text_color)).selectable(false).truncate(),
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -474,9 +533,14 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
|
||||
ctx.load_texture("icon", color_image, TextureOptions::default())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KomorebiNotificationState {
|
||||
pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>,
|
||||
pub workspaces: Vec<(
|
||||
String,
|
||||
Vec<(bool, KomorebiNotificationStateContainerInformation)>,
|
||||
WorkspaceLayer,
|
||||
)>,
|
||||
pub selected_workspace: String,
|
||||
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
||||
pub layout: KomorebiLayout,
|
||||
@@ -506,6 +570,8 @@ impl KomorebiNotificationState {
|
||||
default_theme: Option<KomobarTheme>,
|
||||
render_config: Rc<RefCell<RenderConfig>>,
|
||||
) {
|
||||
let show_all_icons = render_config.borrow().show_all_icons;
|
||||
|
||||
match notification.event {
|
||||
NotificationEvent::WindowManager(_) => {}
|
||||
NotificationEvent::Monitor(_) => {}
|
||||
@@ -533,9 +599,13 @@ impl KomorebiNotificationState {
|
||||
grouping,
|
||||
render_config,
|
||||
);
|
||||
tracing::info!("removed theme from updated komorebi.json and applied default theme");
|
||||
tracing::info!(
|
||||
"removed theme from updated komorebi.json and applied default theme"
|
||||
);
|
||||
} else {
|
||||
tracing::warn!("theme was removed from updated komorebi.json but there was no default theme to apply");
|
||||
tracing::warn!(
|
||||
"theme was removed from updated komorebi.json but there was no default theme to apply"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -576,6 +646,7 @@ impl KomorebiNotificationState {
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let mut workspaces = vec![];
|
||||
|
||||
self.selected_workspace = monitor.workspaces()[focused_workspace_idx]
|
||||
.name()
|
||||
.to_owned()
|
||||
@@ -591,7 +662,37 @@ impl KomorebiNotificationState {
|
||||
if should_show {
|
||||
workspaces.push((
|
||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||
ws.into(),
|
||||
if show_all_icons {
|
||||
let mut containers = vec![];
|
||||
let mut has_monocle = false;
|
||||
|
||||
// add monocle container
|
||||
if let Some(container) = ws.monocle_container() {
|
||||
containers.push((true, container.into()));
|
||||
has_monocle = true;
|
||||
}
|
||||
|
||||
// add all tiled windows
|
||||
for (i, container) in ws.containers().iter().enumerate() {
|
||||
containers.push((
|
||||
!has_monocle && i == ws.focused_container_idx(),
|
||||
container.into(),
|
||||
));
|
||||
}
|
||||
|
||||
// add all floating windows
|
||||
for floating_window in ws.floating_windows() {
|
||||
containers.push((
|
||||
!has_monocle && floating_window.is_focused(),
|
||||
floating_window.into(),
|
||||
));
|
||||
}
|
||||
|
||||
containers
|
||||
} else {
|
||||
vec![(true, ws.into())]
|
||||
},
|
||||
ws.layer().to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@ use crate::config::DisplayFormat;
|
||||
use crate::komorebi::KomorebiLayoutConfig;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use eframe::egui::vec2;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::FontId;
|
||||
use eframe::egui::Frame;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::Rounding;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::vec2;
|
||||
use komorebi_client::SocketMessage;
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use serde::de::Error;
|
||||
use serde_json::from_str;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
@@ -122,18 +123,22 @@ impl KomorebiLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_icon(&mut self, font_id: FontId, ctx: &Context, ui: &mut Ui) {
|
||||
fn show_icon(&mut self, is_selected: bool, font_id: FontId, ctx: &Context, ui: &mut Ui) {
|
||||
// paint custom icons for the layout
|
||||
let size = Vec2::splat(font_id.size);
|
||||
let (response, painter) = ui.allocate_painter(size, Sense::hover());
|
||||
let color = ctx.style().visuals.selection.stroke.color;
|
||||
let color = if is_selected {
|
||||
ctx.style().visuals.selection.stroke.color
|
||||
} else {
|
||||
ui.style().visuals.text_color()
|
||||
};
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
let mut rect = response.rect;
|
||||
let rounding = Rounding::same(rect.width() * 0.1);
|
||||
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
|
||||
rect = rect.shrink(stroke.width);
|
||||
let c = rect.center();
|
||||
let r = rect.width() / 2.0;
|
||||
painter.rect_stroke(rect, rounding, stroke);
|
||||
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
|
||||
|
||||
match self {
|
||||
KomorebiLayout::Default(layout) => match layout {
|
||||
@@ -189,7 +194,7 @@ impl KomorebiLayout {
|
||||
rect.width() * 0.35 + stroke.width,
|
||||
));
|
||||
painter.rect_filled(rect_left, rounding, color);
|
||||
painter.rect_stroke(rect_right, rounding, stroke);
|
||||
painter.rect_stroke(rect_right, rounding, stroke, StrokeKind::Outside);
|
||||
}
|
||||
KomorebiLayout::Paused => {
|
||||
let mut rect_left = response.rect;
|
||||
@@ -236,7 +241,7 @@ impl KomorebiLayout {
|
||||
let layout_frame = SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
|
||||
self.show_icon(font_id.clone(), ctx, ui);
|
||||
self.show_icon(false, font_id.clone(), ctx, ui);
|
||||
}
|
||||
|
||||
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
|
||||
@@ -251,7 +256,7 @@ impl KomorebiLayout {
|
||||
|
||||
if show_options {
|
||||
if let Some(workspace_idx) = workspace_idx {
|
||||
Frame::none().show(ui, |ui| {
|
||||
Frame::NONE.show(ui, |ui| {
|
||||
ui.add(
|
||||
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
|
||||
.selectable(false),
|
||||
@@ -279,8 +284,12 @@ impl KomorebiLayout {
|
||||
]);
|
||||
|
||||
for layout_option in &mut layout_options {
|
||||
if SelectableFrame::new(self == layout_option)
|
||||
.show(ui, |ui| layout_option.show_icon(font_id.clone(), ctx, ui))
|
||||
let is_selected = self == layout_option;
|
||||
|
||||
if SelectableFrame::new(is_selected)
|
||||
.show(ui, |ui| {
|
||||
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
|
||||
})
|
||||
.on_hover_text(match layout_option {
|
||||
KomorebiLayout::Default(layout) => layout.to_string(),
|
||||
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
|
||||
|
||||
@@ -30,27 +30,27 @@ use hotwatch::Hotwatch;
|
||||
use image::RgbaImage;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_client::SubscribeOptions;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use schemars::r#gen::SchemaSettings;
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
use windows_core::BOOL;
|
||||
|
||||
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
|
||||
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
|
||||
@@ -132,8 +132,8 @@ fn main() -> color_eyre::Result<()> {
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<KomobarConfig>();
|
||||
let r#gen = settings.into_generator();
|
||||
let socket_message = r#gen.into_root_schema_for::<KomobarConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
|
||||
println!("{schema}");
|
||||
@@ -149,13 +149,15 @@ fn main() -> color_eyre::Result<()> {
|
||||
}
|
||||
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
// TODO: Audit that the environment access only happens in single-threaded code.
|
||||
unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
// TODO: Audit that the environment access only happens in single-threaded code.
|
||||
unsafe { std::env::set_var("RUST_LOG", "info") };
|
||||
}
|
||||
|
||||
tracing::subscriber::set_global_default(
|
||||
@@ -349,7 +351,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
|
||||
let ctx_komorebi = cc.egui_ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
|
||||
let subscriber_name = format!("komorebi-bar-{}", random_word::r#gen(random_word::Lang::En));
|
||||
|
||||
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
|
||||
filter_state_changes: true,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::ui::CustomUi;
|
||||
use crate::widget::BarWidget;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use num_derive::FromPrimitive;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -3,21 +3,20 @@ use crate::config::KomobarConfig;
|
||||
use crate::config::MonitorConfigOrIndex;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::FontId;
|
||||
use eframe::egui::Frame;
|
||||
use eframe::egui::InnerResponse;
|
||||
use eframe::egui::Margin;
|
||||
use eframe::egui::Rounding;
|
||||
use eframe::egui::Shadow;
|
||||
use eframe::egui::TextStyle;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
@@ -54,6 +53,8 @@ pub struct RenderConfig {
|
||||
pub text_font_id: FontId,
|
||||
/// FontId for icon (based on scaling the text font id)
|
||||
pub icon_font_id: FontId,
|
||||
/// Show all icons on the workspace section of the Komorebi widget
|
||||
pub show_all_icons: bool,
|
||||
}
|
||||
|
||||
pub trait RenderExt {
|
||||
@@ -87,6 +88,15 @@ impl RenderExt for &KomobarConfig {
|
||||
MonitorConfigOrIndex::Index(idx) => *idx,
|
||||
};
|
||||
|
||||
// check if any of the alignments have a komorebi widget with the workspace set to show all icons
|
||||
let show_all_icons =
|
||||
KomobarConfig::show_all_icons_on_komorebi_workspace(&self.left_widgets)
|
||||
|| self
|
||||
.center_widgets
|
||||
.as_ref()
|
||||
.is_some_and(|list| KomobarConfig::show_all_icons_on_komorebi_workspace(list))
|
||||
|| KomobarConfig::show_all_icons_on_komorebi_workspace(&self.right_widgets);
|
||||
|
||||
RenderConfig {
|
||||
monitor_idx,
|
||||
spacing: self.widget_spacing.unwrap_or(10.0),
|
||||
@@ -97,6 +107,7 @@ impl RenderExt for &KomobarConfig {
|
||||
applied_on_widget: false,
|
||||
text_font_id,
|
||||
icon_font_id,
|
||||
show_all_icons,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +132,7 @@ impl RenderConfig {
|
||||
applied_on_widget: false,
|
||||
text_font_id: FontId::default(),
|
||||
icon_font_id: FontId::default(),
|
||||
show_all_icons: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,10 +147,10 @@ impl RenderConfig {
|
||||
return self.define_group_frame(
|
||||
//TODO: this outer margin can be a config
|
||||
Some(Margin {
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
top: 6.0,
|
||||
bottom: 6.0,
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
}),
|
||||
config,
|
||||
ui_style,
|
||||
@@ -191,11 +203,11 @@ impl RenderConfig {
|
||||
ui: &mut Ui,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
|
||||
.inner_margin(match self.more_inner_margin {
|
||||
true => Margin::symmetric(5.0, 0.0),
|
||||
false => Margin::same(0.0),
|
||||
true => Margin::symmetric(5, 0),
|
||||
false => Margin::same(0),
|
||||
})
|
||||
.show(ui, add_contents)
|
||||
}
|
||||
@@ -220,13 +232,13 @@ impl RenderConfig {
|
||||
Frame::group(ui_style)
|
||||
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
|
||||
.inner_margin(match self.more_inner_margin {
|
||||
true => Margin::symmetric(6.0, 1.0),
|
||||
false => Margin::symmetric(1.0, 1.0),
|
||||
true => Margin::symmetric(6, 1),
|
||||
false => Margin::symmetric(1, 1),
|
||||
})
|
||||
.stroke(ui_style.visuals.widgets.noninteractive.bg_stroke)
|
||||
.rounding(match config.rounding {
|
||||
.corner_radius(match config.rounding {
|
||||
Some(rounding) => rounding.into(),
|
||||
None => ui_style.visuals.widgets.noninteractive.rounding,
|
||||
None => ui_style.visuals.widgets.noninteractive.corner_radius,
|
||||
})
|
||||
.fill(
|
||||
self.background_color
|
||||
@@ -237,27 +249,27 @@ impl RenderConfig {
|
||||
// new styles can be added if needed here
|
||||
GroupingStyle::Default => Shadow::NONE,
|
||||
GroupingStyle::DefaultWithShadowB4O1S3 => Shadow {
|
||||
blur: 4.0,
|
||||
offset: Vec2::new(1.0, 1.0),
|
||||
spread: 3.0,
|
||||
blur: 4,
|
||||
offset: [1, 1],
|
||||
spread: 3,
|
||||
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
|
||||
},
|
||||
GroupingStyle::DefaultWithShadowB4O0S3 => Shadow {
|
||||
blur: 4.0,
|
||||
offset: Vec2::new(0.0, 0.0),
|
||||
spread: 3.0,
|
||||
blur: 4,
|
||||
offset: [0, 0],
|
||||
spread: 3,
|
||||
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
|
||||
},
|
||||
GroupingStyle::DefaultWithShadowB0O1S3 => Shadow {
|
||||
blur: 0.0,
|
||||
offset: Vec2::new(1.0, 1.0),
|
||||
spread: 3.0,
|
||||
blur: 0,
|
||||
offset: [1, 1],
|
||||
spread: 3,
|
||||
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
|
||||
},
|
||||
GroupingStyle::DefaultWithGlowB3O1S2 => Shadow {
|
||||
blur: 3.0,
|
||||
offset: Vec2::new(1.0, 1.0),
|
||||
spread: 2.0,
|
||||
blur: 3,
|
||||
offset: [1, 1],
|
||||
spread: 2,
|
||||
color: ui_style
|
||||
.visuals
|
||||
.selection
|
||||
@@ -266,9 +278,9 @@ impl RenderConfig {
|
||||
.try_apply_alpha(config.transparency_alpha),
|
||||
},
|
||||
GroupingStyle::DefaultWithGlowB3O0S2 => Shadow {
|
||||
blur: 3.0,
|
||||
offset: Vec2::new(0.0, 0.0),
|
||||
spread: 2.0,
|
||||
blur: 3,
|
||||
offset: [0, 0],
|
||||
spread: 2,
|
||||
color: ui_style
|
||||
.visuals
|
||||
.selection
|
||||
@@ -277,9 +289,9 @@ impl RenderConfig {
|
||||
.try_apply_alpha(config.transparency_alpha),
|
||||
},
|
||||
GroupingStyle::DefaultWithGlowB0O1S2 => Shadow {
|
||||
blur: 0.0,
|
||||
offset: Vec2::new(1.0, 1.0),
|
||||
spread: 2.0,
|
||||
blur: 0,
|
||||
offset: [1, 1],
|
||||
spread: 2,
|
||||
color: ui_style
|
||||
.visuals
|
||||
.selection
|
||||
@@ -295,9 +307,9 @@ impl RenderConfig {
|
||||
fn widget_outer_margin(&mut self, ui: &mut Ui) -> Margin {
|
||||
let spacing = if self.applied_on_widget {
|
||||
// Remove the default item spacing from the margin
|
||||
self.spacing - ui.spacing().item_spacing.x
|
||||
(self.spacing - ui.spacing().item_spacing.x) as i8
|
||||
} else {
|
||||
0.0
|
||||
0
|
||||
};
|
||||
|
||||
if !self.applied_on_widget {
|
||||
@@ -309,20 +321,20 @@ impl RenderConfig {
|
||||
Some(align) => match align {
|
||||
Alignment::Left => spacing,
|
||||
Alignment::Center => spacing,
|
||||
Alignment::Right => 0.0,
|
||||
Alignment::Right => 0,
|
||||
},
|
||||
None => 0.0,
|
||||
None => 0,
|
||||
},
|
||||
right: match self.alignment {
|
||||
Some(align) => match align {
|
||||
Alignment::Left => 0.0,
|
||||
Alignment::Center => 0.0,
|
||||
Alignment::Left => 0,
|
||||
Alignment::Center => 0,
|
||||
Alignment::Right => spacing,
|
||||
},
|
||||
None => 0.0,
|
||||
None => 0,
|
||||
},
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,16 +378,19 @@ pub enum RoundingConfig {
|
||||
Individual([f32; 4]),
|
||||
}
|
||||
|
||||
impl From<RoundingConfig> for Rounding {
|
||||
impl From<RoundingConfig> for CornerRadius {
|
||||
fn from(value: RoundingConfig) -> Self {
|
||||
match value {
|
||||
RoundingConfig::Same(value) => Rounding::same(value),
|
||||
RoundingConfig::Individual(values) => Self {
|
||||
nw: values[0],
|
||||
ne: values[1],
|
||||
sw: values[2],
|
||||
se: values[3],
|
||||
},
|
||||
RoundingConfig::Same(value) => Self::same(value as u8),
|
||||
RoundingConfig::Individual(values) => {
|
||||
let values = values.map(|f| f as u8);
|
||||
Self {
|
||||
nw: values[0],
|
||||
ne: values[1],
|
||||
sw: values[2],
|
||||
se: values[3],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ impl SelectableFrame {
|
||||
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
|
||||
let Self { selected } = self;
|
||||
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.show(ui, |ui| {
|
||||
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
|
||||
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
let inner_margin = Margin::symmetric(
|
||||
ui.style().spacing.button_padding.x,
|
||||
ui.style().spacing.button_padding.y,
|
||||
ui.style().spacing.button_padding.x as i8,
|
||||
ui.style().spacing.button_padding.y as i8,
|
||||
);
|
||||
|
||||
if selected
|
||||
@@ -35,14 +35,14 @@ impl SelectableFrame {
|
||||
{
|
||||
let visuals = ui.style().interact_selectable(&response, selected);
|
||||
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.stroke(visuals.bg_stroke)
|
||||
.rounding(visuals.rounding)
|
||||
.corner_radius(visuals.corner_radius)
|
||||
.fill(visuals.bg_fill)
|
||||
.inner_margin(inner_margin)
|
||||
.show(ui, add_contents);
|
||||
} else {
|
||||
Frame::none()
|
||||
Frame::NONE
|
||||
.inner_margin(inner_margin)
|
||||
.show(ui, add_contents);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -3,16 +3,17 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::CornerRadius;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::Rounding;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::Vec2;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -158,14 +159,14 @@ impl Time {
|
||||
let color = ctx.style().visuals.text_color();
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
|
||||
let round_all = Rounding::same(response.rect.width() * 0.1);
|
||||
let round_top = Rounding {
|
||||
let round_all = CornerRadius::same((response.rect.width() * 0.1) as u8);
|
||||
let round_top = CornerRadius {
|
||||
nw: round_all.nw,
|
||||
ne: round_all.ne,
|
||||
..Default::default()
|
||||
};
|
||||
let round_none = Rounding::ZERO;
|
||||
let round_bottom = Rounding {
|
||||
let round_none = CornerRadius::ZERO;
|
||||
let round_bottom = CornerRadius {
|
||||
sw: round_all.nw,
|
||||
se: round_all.ne,
|
||||
..Default::default()
|
||||
@@ -175,14 +176,19 @@ impl Time {
|
||||
let mut rect = response.rect.shrink(stroke.width);
|
||||
rect.set_height(rect.height() - height * 2.0);
|
||||
rect = rect.translate(Vec2::new(0.0, height * 2.0));
|
||||
painter.rect_stroke(rect, round_all, stroke);
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
} else if max_power == 3 {
|
||||
let mut rect = response.rect.shrink(stroke.width);
|
||||
rect.set_height(rect.height() - height);
|
||||
rect = rect.translate(Vec2::new(0.0, height));
|
||||
painter.rect_stroke(rect, round_all, stroke);
|
||||
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
|
||||
} else {
|
||||
painter.rect_stroke(response.rect.shrink(stroke.width), round_all, stroke);
|
||||
painter.rect_stroke(
|
||||
response.rect.shrink(stroke.width),
|
||||
round_all,
|
||||
stroke,
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
}
|
||||
|
||||
let mut rect_bin = response.rect;
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widget::BarWidget;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "komorebi-client"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
pub use komorebi::animation::prefix::AnimationPrefix;
|
||||
pub use komorebi::AnimationsConfig;
|
||||
pub use komorebi::AspectRatio;
|
||||
pub use komorebi::BorderColours;
|
||||
pub use komorebi::CrossBoundaryBehaviour;
|
||||
pub use komorebi::GlobalState;
|
||||
pub use komorebi::KomorebiTheme;
|
||||
pub use komorebi::MonitorConfig;
|
||||
pub use komorebi::Notification;
|
||||
pub use komorebi::NotificationEvent;
|
||||
pub use komorebi::PredefinedAspectRatio;
|
||||
pub use komorebi::RuleDebug;
|
||||
pub use komorebi::StackbarConfig;
|
||||
pub use komorebi::State;
|
||||
pub use komorebi::StaticConfig;
|
||||
pub use komorebi::SubscribeOptions;
|
||||
pub use komorebi::TabsConfig;
|
||||
pub use komorebi::WindowContainerBehaviour;
|
||||
pub use komorebi::WindowsApi;
|
||||
pub use komorebi::WorkspaceConfig;
|
||||
pub use komorebi::animation::PerAnimationPrefixConfig;
|
||||
pub use komorebi::animation::prefix::AnimationPrefix;
|
||||
pub use komorebi::asc::ApplicationSpecificConfiguration;
|
||||
pub use komorebi::colour::Colour;
|
||||
pub use komorebi::colour::Rgb;
|
||||
@@ -12,8 +31,6 @@ pub use komorebi::config_generation::IdWithIdentifierAndComment;
|
||||
pub use komorebi::config_generation::MatchingRule;
|
||||
pub use komorebi::config_generation::MatchingStrategy;
|
||||
pub use komorebi::container::Container;
|
||||
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
|
||||
pub use komorebi::core::resolve_home_path;
|
||||
pub use komorebi::core::AnimationStyle;
|
||||
pub use komorebi::core::ApplicationIdentifier;
|
||||
pub use komorebi::core::Arrangement;
|
||||
@@ -42,31 +59,16 @@ pub use komorebi::core::StackbarLabel;
|
||||
pub use komorebi::core::StackbarMode;
|
||||
pub use komorebi::core::StateQuery;
|
||||
pub use komorebi::core::WindowKind;
|
||||
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
|
||||
pub use komorebi::core::resolve_home_path;
|
||||
pub use komorebi::monitor::Monitor;
|
||||
pub use komorebi::monitor_reconciliator::MonitorNotification;
|
||||
pub use komorebi::ring::Ring;
|
||||
pub use komorebi::window::Window;
|
||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||
pub use komorebi::workspace::Workspace;
|
||||
pub use komorebi::AnimationsConfig;
|
||||
pub use komorebi::AspectRatio;
|
||||
pub use komorebi::BorderColours;
|
||||
pub use komorebi::CrossBoundaryBehaviour;
|
||||
pub use komorebi::GlobalState;
|
||||
pub use komorebi::KomorebiTheme;
|
||||
pub use komorebi::MonitorConfig;
|
||||
pub use komorebi::Notification;
|
||||
pub use komorebi::NotificationEvent;
|
||||
pub use komorebi::PredefinedAspectRatio;
|
||||
pub use komorebi::RuleDebug;
|
||||
pub use komorebi::StackbarConfig;
|
||||
pub use komorebi::State;
|
||||
pub use komorebi::StaticConfig;
|
||||
pub use komorebi::SubscribeOptions;
|
||||
pub use komorebi::TabsConfig;
|
||||
pub use komorebi::WindowContainerBehaviour;
|
||||
pub use komorebi::WindowsApi;
|
||||
pub use komorebi::WorkspaceConfig;
|
||||
pub use komorebi::workspace::WorkspaceGlobals;
|
||||
pub use komorebi::workspace::WorkspaceLayer;
|
||||
|
||||
use komorebi::DATA_DIR;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "komorebi-gui"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -12,4 +12,5 @@ eframe = { workspace = true }
|
||||
egui_extras = { workspace = true }
|
||||
random_word = { version = "0.4", features = ["en"] }
|
||||
serde_json = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
@@ -1,9 +1,9 @@
|
||||
#![warn(clippy::all)]
|
||||
|
||||
use eframe::egui;
|
||||
use eframe::egui::color_picker::Alpha;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::ViewportBuilder;
|
||||
use eframe::egui::color_picker::Alpha;
|
||||
use komorebi_client::BorderStyle;
|
||||
use komorebi_client::Colour;
|
||||
use komorebi_client::DefaultLayout;
|
||||
@@ -101,7 +101,7 @@ impl From<&komorebi_client::Workspace> for WorkspaceConfig {
|
||||
let name = value
|
||||
.name()
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
|
||||
.unwrap_or_else(|| random_word::r#gen(random_word::Lang::En).to_string());
|
||||
|
||||
Self {
|
||||
layout,
|
||||
@@ -215,7 +215,7 @@ impl KomorebiGui {
|
||||
extern "system" fn enum_window(
|
||||
hwnd: windows::Win32::Foundation::HWND,
|
||||
lparam: windows::Win32::Foundation::LPARAM,
|
||||
) -> windows::Win32::Foundation::BOOL {
|
||||
) -> windows_core::BOOL {
|
||||
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
|
||||
let window = Window::from(hwnd.0 as isize);
|
||||
|
||||
@@ -248,10 +248,11 @@ impl eframe::App for KomorebiGui {
|
||||
ui.collapsing("Window Rules", |ui| {
|
||||
let window = Window::from(self.debug_hwnd);
|
||||
|
||||
let label = if let (Ok(title), Ok(exe)) = (window.title(), window.exe()) {
|
||||
format!("{title} ({exe})")
|
||||
} else {
|
||||
String::from("Select a Window")
|
||||
let label = match (window.title(), window.exe()) {
|
||||
(Ok(title), Ok(exe)) => {
|
||||
format!("{title} ({exe})")
|
||||
}
|
||||
_ => String::from("Select a Window"),
|
||||
};
|
||||
|
||||
if ui.button("Refresh Windows").clicked() {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[package]
|
||||
name = "komorebi-themes"
|
||||
version = "0.1.35"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "911079d" }
|
||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "f85cc3c", default-features = false, features = ["egui30"] }
|
||||
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "96f26c88d83781f234d42222293ec73d23a39ad8" }
|
||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
|
||||
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
|
||||
eframe = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_variant = "0.1"
|
||||
strum = "0.26"
|
||||
strum = { workspace = true }
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "komorebi"
|
||||
version = "0.1.35"
|
||||
description = "A tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -34,7 +34,7 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
shadow-rs = { workspace = true }
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
strum = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-appender = { workspace = true }
|
||||
@@ -44,6 +44,7 @@ which = { workspace = true }
|
||||
win32-display-data = { workspace = true }
|
||||
windows = { workspace = true }
|
||||
windows-core = { workspace = true }
|
||||
windows-numerics = { workspace = true }
|
||||
windows-implement = { workspace = true }
|
||||
windows-interface = { workspace = true }
|
||||
winput = "0.2"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use super::prefix::AnimationPrefix;
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use super::RenderDispatcher;
|
||||
use super::ANIMATION_DURATION_GLOBAL;
|
||||
use super::ANIMATION_FPS;
|
||||
use super::ANIMATION_MANAGER;
|
||||
use super::RenderDispatcher;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct AnimationEngine;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::core::Rect;
|
||||
use crate::AnimationStyle;
|
||||
use crate::core::Rect;
|
||||
|
||||
use super::style::apply_ease_func;
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::core::animation::AnimationStyle;
|
||||
use lazy_static::lazy_static;
|
||||
use prefix::AnimationPrefix;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
use crate::border_manager::window_kind_colour;
|
||||
use crate::border_manager::WindowKind;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WindowsApi;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::border_manager::FOCUS_STATE;
|
||||
use crate::border_manager::RENDER_TARGETS;
|
||||
use crate::border_manager::RenderTarget;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::border_manager::WindowKind;
|
||||
use crate::border_manager::window_kind_colour;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::Rect;
|
||||
use crate::windows_api;
|
||||
use crate::WindowsApi;
|
||||
use crate::WINDOWS_11;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::OnceLock;
|
||||
use windows::Foundation::Numerics::Matrix3x2;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use windows::Win32::Foundation::FALSE;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Foundation::LRESULT;
|
||||
use windows::Win32::Foundation::TRUE;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
|
||||
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
|
||||
@@ -42,39 +37,58 @@ use windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
|
||||
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
|
||||
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
|
||||
use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;
|
||||
use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;
|
||||
use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;
|
||||
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
|
||||
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
|
||||
use windows::Win32::Graphics::Gdi::CreateRectRgn;
|
||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
||||
use windows::Win32::Graphics::Gdi::ValidateRect;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
use windows_core::BOOL;
|
||||
use windows_core::PCWSTR;
|
||||
use windows_numerics::Matrix3x2;
|
||||
|
||||
pub struct RenderFactory(ID2D1Factory);
|
||||
unsafe impl Sync for RenderFactory {}
|
||||
unsafe impl Send for RenderFactory {}
|
||||
|
||||
impl Deref for RenderFactory {
|
||||
type Target = ID2D1Factory;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
static RENDER_FACTORY: LazyLock<ID2D1Factory> = unsafe {
|
||||
static RENDER_FACTORY: LazyLock<RenderFactory> = unsafe {
|
||||
LazyLock::new(|| {
|
||||
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)
|
||||
.expect("creating RENDER_FACTORY failed")
|
||||
RenderFactory(
|
||||
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)
|
||||
.expect("creating RENDER_FACTORY failed"),
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -100,7 +114,7 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Border {
|
||||
pub hwnd: isize,
|
||||
pub render_target: OnceLock<ID2D1HwndRenderTarget>,
|
||||
pub render_target: OnceLock<RenderTarget>,
|
||||
pub tracking_hwnd: isize,
|
||||
pub window_rect: Rect,
|
||||
pub window_kind: WindowKind,
|
||||
@@ -180,7 +194,7 @@ impl Border {
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
|
||||
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
|
||||
tracing::debug!("border window event processing thread shutdown");
|
||||
break;
|
||||
};
|
||||
@@ -261,7 +275,11 @@ impl Border {
|
||||
|
||||
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
|
||||
if border.render_target.set(render_target.clone()).is_err() {
|
||||
if border
|
||||
.render_target
|
||||
.set(RenderTarget(render_target.clone()))
|
||||
.is_err()
|
||||
{
|
||||
return Err(anyhow!("could not store border render target"));
|
||||
}
|
||||
|
||||
@@ -275,7 +293,7 @@ impl Border {
|
||||
};
|
||||
|
||||
let mut render_targets = RENDER_TARGETS.lock();
|
||||
render_targets.insert(border.hwnd, render_target);
|
||||
render_targets.insert(border.hwnd, RenderTarget(render_target));
|
||||
Ok(border.clone())
|
||||
},
|
||||
Err(error) => Err(error.into()),
|
||||
@@ -300,7 +318,7 @@ impl Border {
|
||||
|
||||
// this triggers WM_PAINT in the callback below
|
||||
pub fn invalidate(&self) {
|
||||
let _ = unsafe { InvalidateRect(self.hwnd(), None, false) };
|
||||
let _ = unsafe { InvalidateRect(Option::from(self.hwnd()), None, false) };
|
||||
}
|
||||
|
||||
pub extern "system" fn callback(
|
||||
@@ -457,13 +475,11 @@ impl Border {
|
||||
});
|
||||
|
||||
// Get window kind and color
|
||||
|
||||
(*border_pointer).window_kind = FOCUS_STATE
|
||||
.lock()
|
||||
.get(&(window.0 as isize))
|
||||
.copied()
|
||||
.unwrap_or(WindowKind::Unfocused);
|
||||
|
||||
let window_kind = (*border_pointer).window_kind;
|
||||
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
|
||||
render_target.BeginDraw();
|
||||
@@ -508,7 +524,7 @@ impl Border {
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = ValidateRect(window, None);
|
||||
let _ = ValidateRect(Option::from(window), None);
|
||||
LRESULT(0)
|
||||
}
|
||||
WM_DESTROY => {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
mod border;
|
||||
use crate::core::BorderImplementation;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::WindowKind;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::Colour;
|
||||
use crate::Rgb;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use border::border_hwnds;
|
||||
use crate::core::BorderImplementation;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::WindowKind;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
pub use border::Border;
|
||||
use border::border_hwnds;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
@@ -21,14 +22,15 @@ use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use strum::Display;
|
||||
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
|
||||
|
||||
@@ -57,8 +59,19 @@ lazy_static! {
|
||||
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
|
||||
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, Border>> = Mutex::new(HashMap::new());
|
||||
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
|
||||
static ref RENDER_TARGETS: Mutex<HashMap<isize, ID2D1HwndRenderTarget>> =
|
||||
Mutex::new(HashMap::new());
|
||||
static ref RENDER_TARGETS: Mutex<HashMap<isize, RenderTarget>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RenderTarget(pub ID2D1HwndRenderTarget);
|
||||
unsafe impl Send for RenderTarget {}
|
||||
|
||||
impl Deref for RenderTarget {
|
||||
type Target = ID2D1HwndRenderTarget;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Notification(pub Option<isize>);
|
||||
@@ -100,6 +113,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
|
||||
borders.clear();
|
||||
BORDERS_MONITORS.lock().clear();
|
||||
WINDOWS_BORDERS.lock().clear();
|
||||
FOCUS_STATE.lock().clear();
|
||||
RENDER_TARGETS.lock().clear();
|
||||
|
||||
@@ -132,13 +146,15 @@ fn window_kind_colour(focus_kind: WindowKind) -> u32 {
|
||||
}
|
||||
|
||||
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);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("restarting failed thread: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -154,6 +170,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let mut previous_pending_move_op = None;
|
||||
let mut previous_is_paused = false;
|
||||
let mut previous_notification: Option<Notification> = None;
|
||||
let mut previous_layer = WorkspaceLayer::default();
|
||||
|
||||
'receiver: for notification in receiver {
|
||||
// Check the wm state every time we receive a notification
|
||||
@@ -170,6 +187,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
.iter()
|
||||
.map(|w| w.hwnd)
|
||||
.collect::<Vec<_>>();
|
||||
let workspace_layer = *state.monitors.elements()[focused_monitor_idx].workspaces()
|
||||
[focused_workspace_idx]
|
||||
.layer();
|
||||
let foreground_window = WindowsApi::foreground_window().unwrap_or_default();
|
||||
|
||||
drop(state);
|
||||
@@ -283,6 +303,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||
let mut windows_borders = WINDOWS_BORDERS.lock();
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
|
||||
// If borders are disabled
|
||||
if !BORDER_ENABLED.load_consume()
|
||||
@@ -297,7 +318,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
borders_monitors.clear();
|
||||
windows_borders.clear();
|
||||
focus_state.clear();
|
||||
|
||||
previous_is_paused = is_paused;
|
||||
continue 'receiver;
|
||||
@@ -308,19 +331,15 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
// Workspaces with tiling disabled don't have borders
|
||||
if !ws.tile() {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default()
|
||||
== monitor_idx
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
// Remove all borders on this monitor
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, _| true,
|
||||
)?;
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
@@ -331,14 +350,17 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let border = match borders.entry(monocle.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(
|
||||
match Border::create(
|
||||
monocle.id(),
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd,
|
||||
) {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
Ok(border) => {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
}
|
||||
_ => {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -349,10 +371,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
WindowKind::Monocle
|
||||
};
|
||||
border.window_kind = new_focus_state;
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
|
||||
let reference_hwnd =
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd;
|
||||
@@ -372,20 +391,15 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
// Remove all borders on this monitor except monocle
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, b| border_hwnd != b.hwnd,
|
||||
)?;
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
@@ -397,24 +411,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
&& WindowsApi::is_zoomed(foreground_hwnd);
|
||||
|
||||
if is_maximized {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default()
|
||||
== monitor_idx
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
// Remove all borders on this monitor
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, _| true,
|
||||
)?;
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
// Destroy any borders not associated with the focused workspace
|
||||
// Collect focused workspace container and floating windows ID's
|
||||
let mut container_and_floating_window_ids = ws
|
||||
.containers()
|
||||
.iter()
|
||||
@@ -425,34 +435,67 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
container_and_floating_window_ids.push(w.hwnd.to_string());
|
||||
}
|
||||
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||
&& !container_and_floating_window_ids.contains(id)
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
// Remove any borders not associated with the focused workspace
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|id, _| !container_and_floating_window_ids.contains(id),
|
||||
)?;
|
||||
|
||||
'containers: for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// In case this container is a stack we need to check it's
|
||||
// unfocused windows to remove any attached border
|
||||
let is_stack = c.windows().len() > 1;
|
||||
if is_stack {
|
||||
let focused_window_idx = c.focused_window_idx();
|
||||
let potential_stacked_border_handles = c
|
||||
.windows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(i, w)| {
|
||||
if i != focused_window_idx {
|
||||
windows_borders.get(&w.hwnd).map(|b| b.hwnd)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !potential_stacked_border_handles.is_empty() {
|
||||
tracing::debug!(
|
||||
"purging stacked borders: {:?}",
|
||||
potential_stacked_border_handles
|
||||
);
|
||||
remove_borders(
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
monitor_idx,
|
||||
|_, b| potential_stacked_border_handles.contains(&b.hwnd),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let focused_window_hwnd =
|
||||
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
|
||||
|
||||
// Get the border entry for this container from the map or create one
|
||||
let mut new_border = false;
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(
|
||||
c.id(),
|
||||
c.focused_window().copied().unwrap_or_default().hwnd,
|
||||
) {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
match Border::create(c.id(), focused_window_hwnd) {
|
||||
Ok(border) => {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
}
|
||||
_ => {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -462,9 +505,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
|
||||
let new_focus_state = if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
|| c.focused_window()
|
||||
.map(|w| w.hwnd != foreground_window)
|
||||
.unwrap_or_default()
|
||||
|| focused_window_hwnd != foreground_window
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
} else if c.windows().len() > 1 {
|
||||
@@ -474,34 +515,72 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
};
|
||||
border.window_kind = new_focus_state;
|
||||
|
||||
// Update the focused state for all containers on this workspace
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
last_focus_state = focus_state.get(&border.hwnd).copied();
|
||||
|
||||
let reference_hwnd =
|
||||
c.focused_window().copied().unwrap_or_default().hwnd;
|
||||
// If this container's border was previously tracking a different
|
||||
// window, then we need to destroy that border and create a new one
|
||||
// tracking the correct window.
|
||||
if border.tracking_hwnd != focused_window_hwnd {
|
||||
// Create new border
|
||||
match Border::create(
|
||||
c.id(),
|
||||
c.focused_window().copied().unwrap_or_default().hwnd,
|
||||
) {
|
||||
Ok(b) => {
|
||||
// Destroy previously stacked border window and remove its hwnd
|
||||
// and tracking_hwnd.
|
||||
border.destroy()?;
|
||||
focus_state.remove(&border.hwnd);
|
||||
if let Some(previous) =
|
||||
windows_borders.get(&border.tracking_hwnd)
|
||||
{
|
||||
// Only remove the border from `windows_borders` if it
|
||||
// still is the same border, if it isn't then it means it
|
||||
// was already updated by another border for that window
|
||||
// and in that case we don't want to remove it.
|
||||
if previous.hwnd == border.hwnd {
|
||||
windows_borders.remove(&border.tracking_hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace with new border
|
||||
new_border = true;
|
||||
*border = b;
|
||||
}
|
||||
_ => {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// avoid getting into a thread restart loop if we try to look up
|
||||
// rect info for a window that has been destroyed by the time
|
||||
// we get here
|
||||
let rect = match WindowsApi::window_rect(reference_hwnd) {
|
||||
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
|
||||
Ok(rect) => rect,
|
||||
Err(_) => {
|
||||
let _ = border.destroy();
|
||||
borders.remove(c.id());
|
||||
remove_border(
|
||||
c.id(),
|
||||
&mut borders,
|
||||
&mut windows_borders,
|
||||
&mut focus_state,
|
||||
&mut borders_monitors,
|
||||
)?;
|
||||
continue 'containers;
|
||||
}
|
||||
};
|
||||
|
||||
let layer_changed = previous_layer != workspace_layer;
|
||||
|
||||
let should_invalidate = match last_focus_state {
|
||||
None => true,
|
||||
Some(last_focus_state) => last_focus_state != new_focus_state,
|
||||
Some(last_focus_state) => {
|
||||
(last_focus_state != new_focus_state) || layer_changed
|
||||
}
|
||||
};
|
||||
|
||||
if new_border || should_invalidate {
|
||||
border.set_position(&rect, reference_hwnd)?;
|
||||
border.set_position(&rect, focused_window_hwnd)?;
|
||||
}
|
||||
|
||||
if should_invalidate {
|
||||
@@ -513,6 +592,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
c.focused_window().cloned().unwrap_or_default().hwnd,
|
||||
border.clone(),
|
||||
);
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -521,13 +601,15 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let border = match borders.entry(window.hwnd.to_string()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) =
|
||||
Border::create(&window.hwnd.to_string(), window.hwnd)
|
||||
match Border::create(&window.hwnd.to_string(), window.hwnd)
|
||||
{
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
Ok(border) => {
|
||||
new_border = true;
|
||||
entry.insert(border)
|
||||
}
|
||||
_ => {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -541,17 +623,17 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
|
||||
border.window_kind = new_focus_state;
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
last_focus_state =
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
last_focus_state = focus_state.get(&border.hwnd).copied();
|
||||
|
||||
let rect = WindowsApi::window_rect(window.hwnd)?;
|
||||
|
||||
let layer_changed = previous_layer != workspace_layer;
|
||||
|
||||
let should_invalidate = match last_focus_state {
|
||||
None => true,
|
||||
Some(last_focus_state) => last_focus_state != new_focus_state,
|
||||
Some(last_focus_state) => {
|
||||
last_focus_state != new_focus_state || layer_changed
|
||||
}
|
||||
};
|
||||
|
||||
if new_border {
|
||||
@@ -564,6 +646,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
|
||||
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
|
||||
windows_borders.insert(window.hwnd, border.clone());
|
||||
focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -575,11 +658,58 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
previous_pending_move_op = pending_move_op;
|
||||
previous_is_paused = is_paused;
|
||||
previous_notification = Some(notification);
|
||||
previous_layer = workspace_layer;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes all borders from monitor with index `monitor_idx` filtered by
|
||||
/// `condition`. This condition is a function that will take a reference to
|
||||
/// the container id and the border and returns a bool, if true that border
|
||||
/// will be removed.
|
||||
fn remove_borders(
|
||||
borders: &mut HashMap<String, Border>,
|
||||
windows_borders: &mut HashMap<isize, Border>,
|
||||
focus_state: &mut HashMap<isize, WindowKind>,
|
||||
borders_monitors: &mut HashMap<String, usize>,
|
||||
monitor_idx: usize,
|
||||
condition: impl Fn(&String, &Border) -> bool,
|
||||
) -> color_eyre::Result<()> {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||
&& condition(id, border)
|
||||
{
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
remove_border(id, borders, windows_borders, focus_state, borders_monitors)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the border with `id` and all its related info from all maps
|
||||
fn remove_border(
|
||||
id: &str,
|
||||
borders: &mut HashMap<String, Border>,
|
||||
windows_borders: &mut HashMap<isize, Border>,
|
||||
focus_state: &mut HashMap<isize, WindowKind>,
|
||||
borders_monitors: &mut HashMap<String, usize>,
|
||||
) -> color_eyre::Result<()> {
|
||||
if let Some(removed_border) = borders.remove(id) {
|
||||
removed_border.destroy()?;
|
||||
windows_borders.remove(&removed_border.tracking_hwnd);
|
||||
focus_state.remove(&removed_border.hwnd);
|
||||
}
|
||||
borders_monitors.remove(id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub enum ZOrder {
|
||||
Top,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use hex_color::HexColor;
|
||||
use komorebi_themes::Color32;
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::InstanceType;
|
||||
use schemars::schema::Schema;
|
||||
use schemars::schema::SchemaObject;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ops::Deref;
|
||||
use windows::core::IUnknown;
|
||||
use windows::core::IUnknown_Vtbl;
|
||||
use windows::core::GUID;
|
||||
use windows::core::HRESULT;
|
||||
use windows::core::HSTRING;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
use windows::Win32::Foundation::SIZE;
|
||||
use windows::Win32::UI::Shell::Common::IObjectArray;
|
||||
use windows::core::GUID;
|
||||
use windows::core::HRESULT;
|
||||
use windows::core::HSTRING;
|
||||
use windows::core::IUnknown;
|
||||
use windows::core::IUnknown_Vtbl;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
use windows_core::BOOL;
|
||||
|
||||
type DesktopID = GUID;
|
||||
|
||||
@@ -129,7 +129,7 @@ pub unsafe trait IApplicationView: IUnknown {
|
||||
pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17
|
||||
pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;
|
||||
pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)
|
||||
-> HRESULT;
|
||||
-> HRESULT;
|
||||
|
||||
/*** IApplicationView methods ***/
|
||||
pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20
|
||||
|
||||
@@ -11,11 +11,11 @@ use interfaces::IServiceProvider;
|
||||
use std::ffi::c_void;
|
||||
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||
use windows::Win32::System::Com::COINIT_MULTITHREADED;
|
||||
use windows::Win32::System::Com::CoCreateInstance;
|
||||
use windows::Win32::System::Com::CoInitializeEx;
|
||||
use windows::Win32::System::Com::CoUninitialize;
|
||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||
use windows::Win32::System::Com::COINIT_MULTITHREADED;
|
||||
use windows_core::Interface;
|
||||
|
||||
struct ComInit();
|
||||
@@ -64,7 +64,7 @@ fn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplication
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {
|
||||
COM_INIT.with(|_| {
|
||||
let provider = get_iservice_provider();
|
||||
|
||||
@@ -7,12 +7,12 @@ use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use super::custom_layout::Column;
|
||||
use super::custom_layout::ColumnSplit;
|
||||
use super::custom_layout::ColumnSplitWithCapacity;
|
||||
use super::CustomLayout;
|
||||
use super::DefaultLayout;
|
||||
use super::Rect;
|
||||
use super::custom_layout::Column;
|
||||
use super::custom_layout::ColumnSplit;
|
||||
use super::custom_layout::ColumnSplitWithCapacity;
|
||||
|
||||
pub trait Arrangement {
|
||||
fn calculate(
|
||||
|
||||
@@ -5,9 +5,9 @@ use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::DefaultLayout;
|
||||
use super::OperationDirection;
|
||||
use super::custom_layout::Column;
|
||||
use super::custom_layout::ColumnSplit;
|
||||
use super::custom_layout::ColumnSplitWithCapacity;
|
||||
use super::custom_layout::CustomLayout;
|
||||
use super::DefaultLayout;
|
||||
use super::OperationDirection;
|
||||
|
||||
pub trait Direction {
|
||||
fn index_in_direction(
|
||||
|
||||
@@ -6,16 +6,16 @@ use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::animation::prefix::AnimationPrefix;
|
||||
use crate::KomorebiTheme;
|
||||
use crate::animation::prefix::AnimationPrefix;
|
||||
pub use animation::AnimationStyle;
|
||||
pub use arrangement::Arrangement;
|
||||
pub use arrangement::Axis;
|
||||
@@ -149,6 +149,7 @@ pub enum SocketMessage {
|
||||
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
|
||||
ClearWorkspaceLayoutRules(usize, usize),
|
||||
ClearNamedWorkspaceLayoutRules(String),
|
||||
ToggleWorkspaceLayer,
|
||||
// Configuration
|
||||
ReloadConfiguration,
|
||||
ReplaceConfiguration(PathBuf),
|
||||
|
||||
@@ -7,8 +7,8 @@ use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use super::direction::Direction;
|
||||
use super::Axis;
|
||||
use super::direction::Direction;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
|
||||
|
||||
@@ -44,13 +44,15 @@ pub fn send_notification(hwnd: isize) {
|
||||
}
|
||||
|
||||
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);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("restarting failed thread: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -40,12 +40,12 @@ use std::io::Write;
|
||||
use std::net::TcpStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use colour::*;
|
||||
pub use core::*;
|
||||
@@ -71,8 +71,8 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixStream;
|
||||
use which::which;
|
||||
use winreg::enums::HKEY_CURRENT_USER;
|
||||
use winreg::RegKey;
|
||||
use winreg::enums::HKEY_CURRENT_USER;
|
||||
|
||||
lazy_static! {
|
||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
@@ -10,27 +10,32 @@
|
||||
use std::env::temp_dir;
|
||||
use std::net::Shutdown;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_utils::Backoff;
|
||||
use komorebi::animation::AnimationEngine;
|
||||
use komorebi::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
use komorebi::animation::AnimationEngine;
|
||||
use parking_lot::Mutex;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use parking_lot::deadlock;
|
||||
use parking_lot::Mutex;
|
||||
use sysinfo::Process;
|
||||
use sysinfo::ProcessesToUpdate;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use komorebi::CUSTOM_FFM;
|
||||
use komorebi::DATA_DIR;
|
||||
use komorebi::HOME_DIR;
|
||||
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
||||
use komorebi::SESSION_ID;
|
||||
use komorebi::border_manager;
|
||||
use komorebi::focus_manager;
|
||||
use komorebi::load_configuration;
|
||||
@@ -49,23 +54,20 @@ use komorebi::window_manager::WindowManager;
|
||||
use komorebi::windows_api::WindowsApi;
|
||||
use komorebi::winevent_listener;
|
||||
use komorebi::workspace_reconciliator;
|
||||
use komorebi::CUSTOM_FFM;
|
||||
use komorebi::DATA_DIR;
|
||||
use komorebi::HOME_DIR;
|
||||
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
||||
use komorebi::SESSION_ID;
|
||||
|
||||
shadow_rs::shadow!(build);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
// TODO: Audit that the environment access only happens in single-threaded code.
|
||||
unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
// TODO: Audit that the environment access only happens in single-threaded code.
|
||||
unsafe { std::env::set_var("RUST_LOG", "info") };
|
||||
}
|
||||
|
||||
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
|
||||
@@ -124,20 +126,22 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
#[tracing::instrument]
|
||||
fn detect_deadlocks() {
|
||||
// Create a background thread which checks for deadlocks every 10s
|
||||
std::thread::spawn(move || loop {
|
||||
tracing::info!("running deadlock detector");
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
let deadlocks = deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
}
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
tracing::info!("running deadlock detector");
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
let deadlocks = deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::error!("{} deadlocks detected", deadlocks.len());
|
||||
for (i, threads) in deadlocks.iter().enumerate() {
|
||||
tracing::error!("deadlock #{}", i);
|
||||
for t in threads {
|
||||
tracing::error!("thread id: {:#?}", t.thread_id());
|
||||
tracing::error!("{:#?}", t.backtrace());
|
||||
tracing::error!("{} deadlocks detected", deadlocks.len());
|
||||
for (i, threads) in deadlocks.iter().enumerate() {
|
||||
tracing::error!("deadlock #{}", i);
|
||||
for t in threads {
|
||||
tracing::error!("thread id: {:#?}", t.thread_id());
|
||||
tracing::error!("{:#?}", t.backtrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -192,7 +196,9 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if len > 1 {
|
||||
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
|
||||
tracing::error!(
|
||||
"komorebi.exe is already running, please exit the existing process before starting a new one"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -244,16 +250,10 @@ fn main() -> Result<()> {
|
||||
StaticConfig::postload(config, &wm)?;
|
||||
}
|
||||
|
||||
listen_for_commands(wm.clone());
|
||||
|
||||
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
|
||||
};
|
||||
|
||||
if let Some(port) = opts.tcp_port {
|
||||
listen_for_commands_tcp(wm.clone(), port);
|
||||
}
|
||||
|
||||
if static_config.is_none() {
|
||||
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
|
||||
|
||||
@@ -280,21 +280,27 @@ fn main() -> Result<()> {
|
||||
|
||||
wm.lock().retile_all(false)?;
|
||||
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
if CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||
listen_for_movements(wm.clone());
|
||||
}
|
||||
|
||||
border_manager::listen_for_notifications(wm.clone());
|
||||
stackbar_manager::listen_for_notifications(wm.clone());
|
||||
transparency_manager::listen_for_notifications(wm.clone());
|
||||
workspace_reconciliator::listen_for_notifications(wm.clone());
|
||||
monitor_reconciliator::listen_for_notifications(wm.clone())?;
|
||||
reaper::watch_for_orphans(wm.clone());
|
||||
reaper::listen_for_notifications(wm.clone(), wm.lock().known_hwnds.clone());
|
||||
focus_manager::listen_for_notifications(wm.clone());
|
||||
theme_manager::listen_for_notifications();
|
||||
|
||||
listen_for_commands(wm.clone());
|
||||
|
||||
if let Some(port) = opts.tcp_port {
|
||||
listen_for_commands_tcp(wm.clone(), port);
|
||||
}
|
||||
|
||||
listen_for_events(wm.clone());
|
||||
|
||||
if CUSTOM_FFM.load(Ordering::SeqCst) {
|
||||
listen_for_movements(wm.clone());
|
||||
}
|
||||
|
||||
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
|
||||
ctrlc::set_handler(move || {
|
||||
ctrlc_sender
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
@@ -14,13 +15,15 @@ use serde::Serialize;
|
||||
|
||||
use crate::core::Rect;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::DefaultLayout;
|
||||
use crate::Layout;
|
||||
use crate::OperationDirection;
|
||||
use crate::WindowsApi;
|
||||
use crate::container::Container;
|
||||
use crate::ring::Ring;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -61,6 +64,10 @@ pub struct Monitor {
|
||||
pub last_focused_workspace: Option<usize>,
|
||||
#[getset(get_mut = "pub")]
|
||||
pub workspace_names: HashMap<usize, String>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
pub container_padding: Option<i32>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
pub workspace_padding: Option<i32>,
|
||||
}
|
||||
|
||||
impl_ring_elements!(Monitor, Workspace);
|
||||
@@ -114,6 +121,8 @@ pub fn new(
|
||||
workspaces,
|
||||
last_focused_workspace: None,
|
||||
workspace_names: HashMap::default(),
|
||||
container_padding: None,
|
||||
workspace_padding: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +162,8 @@ impl Monitor {
|
||||
workspaces: Default::default(),
|
||||
last_focused_workspace: None,
|
||||
workspace_names: Default::default(),
|
||||
container_padding: None,
|
||||
workspace_padding: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +186,52 @@ impl Monitor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the `globals` field of all workspaces
|
||||
pub fn update_workspaces_globals(&mut self, offset: Option<Rect>) {
|
||||
let container_padding = self
|
||||
.container_padding()
|
||||
.or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
|
||||
let workspace_padding = self
|
||||
.workspace_padding()
|
||||
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
|
||||
let work_area = *self.work_area_size();
|
||||
let offset = self.work_area_offset.or(offset);
|
||||
let window_based_work_area_offset = self.window_based_work_area_offset();
|
||||
let limit = self.window_based_work_area_offset_limit();
|
||||
|
||||
for workspace in self.workspaces_mut() {
|
||||
workspace.globals_mut().container_padding = container_padding;
|
||||
workspace.globals_mut().workspace_padding = workspace_padding;
|
||||
workspace.globals_mut().work_area = work_area;
|
||||
workspace.globals_mut().work_area_offset = offset;
|
||||
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
|
||||
workspace.globals_mut().window_based_work_area_offset_limit = limit;
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the `globals` field of workspace with index `workspace_idx`
|
||||
pub fn update_workspace_globals(&mut self, workspace_idx: usize, offset: Option<Rect>) {
|
||||
let container_padding = self
|
||||
.container_padding()
|
||||
.or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
|
||||
let workspace_padding = self
|
||||
.workspace_padding()
|
||||
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
|
||||
let work_area = *self.work_area_size();
|
||||
let offset = self.work_area_offset.or(offset);
|
||||
let window_based_work_area_offset = self.window_based_work_area_offset();
|
||||
let limit = self.window_based_work_area_offset_limit();
|
||||
|
||||
if let Some(workspace) = self.workspaces_mut().get_mut(workspace_idx) {
|
||||
workspace.globals_mut().container_padding = container_padding;
|
||||
workspace.globals_mut().workspace_padding = workspace_padding;
|
||||
workspace.globals_mut().work_area = work_area;
|
||||
workspace.globals_mut().work_area_offset = offset;
|
||||
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
|
||||
workspace.globals_mut().window_based_work_area_offset_limit = limit;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_container(
|
||||
&mut self,
|
||||
container: Container,
|
||||
@@ -401,21 +458,17 @@ impl Monitor {
|
||||
}
|
||||
|
||||
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
|
||||
let work_area = *self.work_area_size();
|
||||
let window_based_work_area_offset = (
|
||||
self.window_based_work_area_offset_limit(),
|
||||
self.window_based_work_area_offset(),
|
||||
);
|
||||
|
||||
let offset = if self.work_area_offset().is_some() {
|
||||
self.work_area_offset()
|
||||
} else {
|
||||
offset
|
||||
};
|
||||
|
||||
let focused_workspace_idx = self.focused_workspace_idx();
|
||||
self.update_workspace_globals(focused_workspace_idx, offset);
|
||||
self.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no workspace"))?
|
||||
.update(&work_area, offset, window_based_work_area_offset)?;
|
||||
.update()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_DISPLAY_ADAPTER;
|
||||
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_MONITOR;
|
||||
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL;
|
||||
@@ -11,10 +10,6 @@ use windows::Win32::Foundation::LRESULT;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::System::Power::POWERBROADCAST_SETTING;
|
||||
use windows::Win32::System::SystemServices::GUID_LIDSWITCH_STATE_CHANGE;
|
||||
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_CONFIGCHANGED;
|
||||
@@ -23,6 +18,9 @@ use windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEREMOVECOMPLETE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVTYP_DEVICEINTERFACE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND;
|
||||
@@ -30,6 +28,7 @@ use windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PBT_POWERSETTINGCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_POWERBROADCAST;
|
||||
@@ -38,10 +37,11 @@ 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 windows::core::PCWSTR;
|
||||
|
||||
use crate::WindowsApi;
|
||||
use crate::monitor_reconciliator;
|
||||
use crate::windows_api;
|
||||
use crate::WindowsApi;
|
||||
|
||||
// This is a hidden window specifically spawned to listen to system-wide events related to monitors
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -87,7 +87,7 @@ impl Hidden {
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
|
||||
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
|
||||
tracing::debug!("hidden window event processing thread shutdown");
|
||||
break;
|
||||
};
|
||||
@@ -224,14 +224,18 @@ impl Hidden {
|
||||
WM_WTSSESSION_CHANGE => {
|
||||
match wparam.0 as u32 {
|
||||
WTS_SESSION_LOCK => {
|
||||
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked");
|
||||
tracing::debug!(
|
||||
"WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked"
|
||||
);
|
||||
|
||||
monitor_reconciliator::send_notification(
|
||||
monitor_reconciliator::MonitorNotification::SessionLocked,
|
||||
);
|
||||
}
|
||||
WTS_SESSION_UNLOCK => {
|
||||
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked");
|
||||
tracing::debug!(
|
||||
"WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked"
|
||||
);
|
||||
|
||||
monitor_reconciliator::send_notification(
|
||||
monitor_reconciliator::MonitorNotification::SessionUnlocked,
|
||||
@@ -251,7 +255,8 @@ impl Hidden {
|
||||
// and resolution changes here
|
||||
WM_DISPLAYCHANGE => {
|
||||
tracing::debug!(
|
||||
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed", wparam.0
|
||||
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed",
|
||||
wparam.0
|
||||
);
|
||||
|
||||
monitor_reconciliator::send_notification(
|
||||
@@ -265,8 +270,8 @@ impl Hidden {
|
||||
#[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)"
|
||||
);
|
||||
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
|
||||
);
|
||||
|
||||
monitor_reconciliator::send_notification(
|
||||
monitor_reconciliator::MonitorNotification::WorkAreaChanged,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use crate::border_manager;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::Rect;
|
||||
@@ -7,12 +13,6 @@ use crate::monitor;
|
||||
use crate::monitor::Monitor;
|
||||
use crate::monitor_reconciliator::hidden::Hidden;
|
||||
use crate::notify_subscribers;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
@@ -21,10 +21,10 @@ use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub mod hidden;
|
||||
|
||||
@@ -112,16 +112,18 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,6 +266,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
|| attached.device_id().eq(monitor.device_id())
|
||||
{
|
||||
monitor.set_id(attached.id());
|
||||
monitor.set_device(attached.device().clone());
|
||||
monitor.set_device_id(attached.device_id().clone());
|
||||
monitor.set_serial_number_id(attached.serial_number_id().clone());
|
||||
monitor.set_name(attached.name().clone());
|
||||
@@ -385,7 +388,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
|
||||
// Update known_hwnds
|
||||
wm.known_hwnds.retain(|i| !windows_to_remove.contains(i));
|
||||
wm.known_hwnds.retain(|i, _| !windows_to_remove.contains(i));
|
||||
|
||||
if !newly_removed_displays.is_empty() {
|
||||
// After we have cached them, remove them from our state
|
||||
@@ -460,11 +463,35 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
cache_hit = true;
|
||||
cached_id = id.clone();
|
||||
|
||||
tracing::info!("found monitor and workspace configuration for {id} in the monitor cache, applying");
|
||||
tracing::info!(
|
||||
"found monitor and workspace configuration for {id} in the monitor cache, applying"
|
||||
);
|
||||
|
||||
// If it does, load the monitor removing any window that has since
|
||||
// been closed or moved to another workspace
|
||||
*m = cached.clone();
|
||||
// If it does, update the cached monitor info with the new one and
|
||||
// load the cached monitor removing any window that has since been
|
||||
// closed or moved to another workspace
|
||||
*m = Monitor {
|
||||
// Data that should be the one just read from `win32-display-data`
|
||||
id: m.id,
|
||||
name: m.name.clone(),
|
||||
device: m.device.clone(),
|
||||
device_id: m.device_id.clone(),
|
||||
serial_number_id: m.serial_number_id.clone(),
|
||||
size: m.size,
|
||||
work_area_size: m.work_area_size,
|
||||
|
||||
// The rest should come from the cached monitor
|
||||
work_area_offset: cached.work_area_offset,
|
||||
window_based_work_area_offset: cached
|
||||
.window_based_work_area_offset,
|
||||
window_based_work_area_offset_limit: cached
|
||||
.window_based_work_area_offset_limit,
|
||||
workspaces: cached.workspaces.clone(),
|
||||
last_focused_workspace: cached.last_focused_workspace,
|
||||
workspace_names: cached.workspace_names.clone(),
|
||||
container_padding: cached.container_padding,
|
||||
workspace_padding: cached.workspace_padding,
|
||||
};
|
||||
|
||||
let focused_workspace_idx = m.focused_workspace_idx();
|
||||
|
||||
@@ -481,7 +508,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
{
|
||||
container.windows_mut().retain(|window| {
|
||||
window.exe().is_ok()
|
||||
&& !known_hwnds.contains(&window.hwnd)
|
||||
&& !known_hwnds.contains_key(&window.hwnd)
|
||||
});
|
||||
|
||||
if container.windows().is_empty() {
|
||||
@@ -519,7 +546,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
|
||||
if let Some(window) = workspace.maximized_window() {
|
||||
if window.exe().is_err()
|
||||
|| known_hwnds.contains(&window.hwnd)
|
||||
|| known_hwnds.contains_key(&window.hwnd)
|
||||
{
|
||||
workspace.set_maximized_window(None);
|
||||
} else if is_focused_workspace {
|
||||
@@ -530,7 +557,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
if let Some(container) = workspace.monocle_container_mut() {
|
||||
container.windows_mut().retain(|window| {
|
||||
window.exe().is_ok()
|
||||
&& !known_hwnds.contains(&window.hwnd)
|
||||
&& !known_hwnds.contains_key(&window.hwnd)
|
||||
});
|
||||
|
||||
if container.windows().is_empty() {
|
||||
@@ -552,7 +579,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
|
||||
workspace.floating_windows_mut().retain(|window| {
|
||||
window.exe().is_ok() && !known_hwnds.contains(&window.hwnd)
|
||||
window.exe().is_ok()
|
||||
&& !known_hwnds.contains_key(&window.hwnd)
|
||||
});
|
||||
|
||||
if is_focused_workspace {
|
||||
|
||||
@@ -8,26 +8,22 @@ use std::net::TcpListener;
|
||||
use std::net::TcpStream;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use miow::pipe::connect;
|
||||
use net2::TcpStreamExt;
|
||||
use parking_lot::Mutex;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use schemars::r#gen::SchemaSettings;
|
||||
use schemars::schema_for;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use crate::core::ApplicationIdentifier;
|
||||
use crate::core::Axis;
|
||||
use crate::core::BorderImplementation;
|
||||
@@ -41,7 +37,34 @@ use crate::core::SocketMessage;
|
||||
use crate::core::StateQuery;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
use crate::core::WindowKind;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
|
||||
use crate::CUSTOM_FFM;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::GlobalState;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::IGNORE_IDENTIFIERS;
|
||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::REMOVE_TITLEBARS;
|
||||
use crate::SUBSCRIPTION_PIPES;
|
||||
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
|
||||
use crate::SUBSCRIPTION_SOCKETS;
|
||||
use crate::State;
|
||||
use crate::TCP_CONNECTIONS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
use crate::animation::ANIMATION_FPS;
|
||||
@@ -66,30 +89,8 @@ use crate::window_manager;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent_listener;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace::WorkspaceWindowLocation;
|
||||
use crate::GlobalState;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::CUSTOM_FFM;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::IGNORE_IDENTIFIERS;
|
||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::REMOVE_TITLEBARS;
|
||||
use crate::SUBSCRIPTION_PIPES;
|
||||
use crate::SUBSCRIPTION_SOCKETS;
|
||||
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
|
||||
use crate::TCP_CONNECTIONS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
use stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||
use stackbar_manager::STACKBAR_LABEL;
|
||||
use stackbar_manager::STACKBAR_MODE;
|
||||
@@ -100,42 +101,44 @@ use stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || loop {
|
||||
let wm = wm.clone();
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
let wm = wm.clone();
|
||||
|
||||
let _ = std::thread::spawn(move || {
|
||||
let listener = wm
|
||||
.lock()
|
||||
.command_listener
|
||||
.try_clone()
|
||||
.expect("could not clone unix listener");
|
||||
let _ = std::thread::spawn(move || {
|
||||
let listener = wm
|
||||
.lock()
|
||||
.command_listener
|
||||
.try_clone()
|
||||
.expect("could not clone unix listener");
|
||||
|
||||
tracing::info!("listening on komorebi.sock");
|
||||
for client in listener.incoming() {
|
||||
match client {
|
||||
Ok(stream) => {
|
||||
let wm_clone = wm.clone();
|
||||
std::thread::spawn(move || {
|
||||
match stream.set_read_timeout(Some(Duration::from_secs(1))) {
|
||||
Ok(()) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
}
|
||||
match read_commands_uds(&wm_clone, stream) {
|
||||
Ok(()) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!("{}", error);
|
||||
break;
|
||||
tracing::info!("listening on komorebi.sock");
|
||||
for client in listener.incoming() {
|
||||
match client {
|
||||
Ok(stream) => {
|
||||
let wm_clone = wm.clone();
|
||||
std::thread::spawn(move || {
|
||||
match stream.set_read_timeout(Some(Duration::from_secs(1))) {
|
||||
Ok(()) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
}
|
||||
match read_commands_uds(&wm_clone, stream) {
|
||||
Ok(()) => {}
|
||||
Err(error) => tracing::error!("{}", error),
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!("{}", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.join();
|
||||
})
|
||||
.join();
|
||||
|
||||
tracing::error!("restarting failed thread");
|
||||
tracing::error!("restarting failed thread");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -291,13 +294,37 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
SocketMessage::FocusWindow(direction) => {
|
||||
self.focus_container_in_direction(direction)?;
|
||||
let focused_workspace = self.focused_workspace()?;
|
||||
match focused_workspace.layer() {
|
||||
WorkspaceLayer::Tiling => {
|
||||
self.focus_container_in_direction(direction)?;
|
||||
}
|
||||
WorkspaceLayer::Floating => {
|
||||
self.focus_floating_window_in_direction(direction)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::MoveWindow(direction) => {
|
||||
self.move_container_in_direction(direction)?;
|
||||
let focused_workspace = self.focused_workspace()?;
|
||||
match focused_workspace.layer() {
|
||||
WorkspaceLayer::Tiling => {
|
||||
self.move_container_in_direction(direction)?;
|
||||
}
|
||||
WorkspaceLayer::Floating => {
|
||||
self.move_floating_window_in_direction(direction)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::CycleFocusWindow(direction) => {
|
||||
self.focus_container_in_cycle_direction(direction)?;
|
||||
let focused_workspace = self.focused_workspace()?;
|
||||
match focused_workspace.layer() {
|
||||
WorkspaceLayer::Tiling => {
|
||||
self.focus_container_in_cycle_direction(direction)?;
|
||||
}
|
||||
WorkspaceLayer::Floating => {
|
||||
self.focus_floating_window_in_cycle_direction(direction)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
SocketMessage::CycleMoveWindow(direction) => {
|
||||
self.move_container_in_cycle_direction(direction)?;
|
||||
@@ -1020,6 +1047,50 @@ impl WindowManager {
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::ToggleWorkspaceLayer => {
|
||||
let mouse_follows_focus = self.mouse_follows_focus;
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let mut to_focus = None;
|
||||
match workspace.layer() {
|
||||
WorkspaceLayer::Tiling => {
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
|
||||
for (i, window) in workspace.floating_windows().iter().enumerate() {
|
||||
if i == 0 {
|
||||
to_focus = Some(*window);
|
||||
}
|
||||
window.raise()?;
|
||||
}
|
||||
|
||||
for container in workspace.containers() {
|
||||
if let Some(window) = container.focused_window() {
|
||||
window.lower()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkspaceLayer::Floating => {
|
||||
workspace.set_layer(WorkspaceLayer::Tiling);
|
||||
|
||||
let focused_container_idx = workspace.focused_container_idx();
|
||||
for (i, container) in workspace.containers_mut().iter_mut().enumerate() {
|
||||
if let Some(window) = container.focused_window() {
|
||||
if i == focused_container_idx {
|
||||
to_focus = Some(*window);
|
||||
}
|
||||
window.raise()?;
|
||||
}
|
||||
}
|
||||
|
||||
for window in workspace.floating_windows() {
|
||||
window.lower()?;
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(window) = to_focus {
|
||||
window.focus(mouse_follows_focus)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::Stop => {
|
||||
self.stop(false)?;
|
||||
}
|
||||
@@ -1145,7 +1216,7 @@ impl WindowManager {
|
||||
let container_len = workspace.containers().len();
|
||||
let no_layout_rules = workspace.layout_rules().is_empty();
|
||||
|
||||
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
|
||||
if let Layout::Custom(custom) = workspace.layout_mut() {
|
||||
if matches!(axis, Axis::Horizontal) {
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let percentage = custom
|
||||
@@ -1307,7 +1378,9 @@ impl WindowManager {
|
||||
self.focus_follows_mouse = None;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Windows) => {
|
||||
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
|
||||
tracing::warn!(
|
||||
"ignoring command that could mix different focus follows mouse implementations"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1331,7 +1404,9 @@ impl WindowManager {
|
||||
self.focus_follows_mouse = None;
|
||||
}
|
||||
Some(FocusFollowsMouseImplementation::Komorebi) => {
|
||||
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
|
||||
tracing::warn!(
|
||||
"ignoring command that could mix different focus follows mouse implementations"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1573,33 +1648,41 @@ impl WindowManager {
|
||||
}
|
||||
SocketMessage::ToggleWorkspaceWindowContainerBehaviour => {
|
||||
let current_global_behaviour = self.window_management_behaviour.current_behaviour;
|
||||
if let Some(behaviour) = self
|
||||
match self
|
||||
.focused_workspace_mut()?
|
||||
.window_container_behaviour_mut()
|
||||
{
|
||||
match behaviour {
|
||||
Some(behaviour) => match behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
*behaviour = WindowContainerBehaviour::Append
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
*behaviour = WindowContainerBehaviour::Create
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
self.focused_workspace_mut()?
|
||||
.set_window_container_behaviour(Some(match current_global_behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
WindowContainerBehaviour::Append
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
WindowContainerBehaviour::Create
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
self.focused_workspace_mut()?
|
||||
.set_window_container_behaviour(Some(match current_global_behaviour {
|
||||
WindowContainerBehaviour::Create => WindowContainerBehaviour::Append,
|
||||
WindowContainerBehaviour::Append => WindowContainerBehaviour::Create,
|
||||
}));
|
||||
};
|
||||
}
|
||||
SocketMessage::ToggleWorkspaceFloatOverride => {
|
||||
let current_global_override = self.window_management_behaviour.float_override;
|
||||
if let Some(float_override) = self.focused_workspace_mut()?.float_override_mut() {
|
||||
*float_override = !*float_override;
|
||||
} else {
|
||||
self.focused_workspace_mut()?
|
||||
.set_float_override(Some(!current_global_override));
|
||||
match self.focused_workspace_mut()?.float_override_mut() {
|
||||
Some(float_override) => {
|
||||
*float_override = !*float_override;
|
||||
}
|
||||
_ => {
|
||||
self.focused_workspace_mut()?
|
||||
.set_float_override(Some(!current_global_override));
|
||||
}
|
||||
};
|
||||
}
|
||||
SocketMessage::WindowHidingBehaviour(behaviour) => {
|
||||
@@ -1783,8 +1866,8 @@ impl WindowManager {
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||
let r#gen = settings.into_generator();
|
||||
let socket_message = r#gen.into_root_schema_for::<StaticConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
@@ -1835,6 +1918,9 @@ impl WindowManager {
|
||||
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
|
||||
};
|
||||
|
||||
// Update list of known_hwnds and their monitor/workspace index pair
|
||||
self.update_known_hwnds();
|
||||
|
||||
notify_subscribers(
|
||||
Notification {
|
||||
event: NotificationEvent::Socket(message.clone()),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
@@ -14,6 +13,13 @@ use crate::core::Rect;
|
||||
use crate::core::Sizing;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::State;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
@@ -21,23 +27,16 @@ use crate::current_virtual_desktop;
|
||||
use crate::notify_subscribers;
|
||||
use crate::stackbar_manager;
|
||||
use crate::transparency_manager;
|
||||
use crate::window::should_act;
|
||||
use crate::window::RuleDebug;
|
||||
use crate::window::should_act;
|
||||
use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace_reconciliator;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::DATA_DIR;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||
@@ -282,10 +281,12 @@ impl WindowManager {
|
||||
} else {
|
||||
workspace.focus_container_by_window(window.hwnd)?;
|
||||
}
|
||||
|
||||
workspace.set_layer(WorkspaceLayer::Tiling);
|
||||
}
|
||||
Some(idx) => {
|
||||
if let Some(window) = workspace.floating_windows().get(idx) {
|
||||
window.focus(false)?;
|
||||
if let Some(_window) = workspace.floating_windows().get(idx) {
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,30 +308,28 @@ impl WindowManager {
|
||||
|
||||
let mut needs_reconciliation = false;
|
||||
|
||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
||||
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
|
||||
// At this point we know we are going to send a notification to the workspace reconciliator
|
||||
// So we get the topmost window returned by EnumWindows, which is almost always the window
|
||||
// that has been selected by alt-tab
|
||||
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
|
||||
if let Some(first) =
|
||||
alt_tab_windows.iter().find(|w| w.title().is_ok())
|
||||
{
|
||||
// If our record of this HWND hasn't been updated in over a minute
|
||||
let mut instant = ALT_TAB_HWND_INSTANT.lock();
|
||||
if instant.elapsed().gt(&Duration::from_secs(1)) {
|
||||
// Update our record with the HWND we just found
|
||||
ALT_TAB_HWND.store(Some(first.hwnd));
|
||||
// Update the timestamp of our record
|
||||
*instant = Instant::now();
|
||||
}
|
||||
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if focused_pair != (*m_idx, *w_idx) {
|
||||
// At this point we know we are going to send a notification to the workspace reconciliator
|
||||
// So we get the topmost window returned by EnumWindows, which is almost always the window
|
||||
// that has been selected by alt-tab
|
||||
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
|
||||
if let Some(first) =
|
||||
alt_tab_windows.iter().find(|w| w.title().is_ok())
|
||||
{
|
||||
// If our record of this HWND hasn't been updated in over a minute
|
||||
let mut instant = ALT_TAB_HWND_INSTANT.lock();
|
||||
if instant.elapsed().gt(&Duration::from_secs(1)) {
|
||||
// Update our record with the HWND we just found
|
||||
ALT_TAB_HWND.store(Some(first.hwnd));
|
||||
// Update the timestamp of our record
|
||||
*instant = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
workspace_reconciliator::send_notification(i, j);
|
||||
needs_reconciliation = true;
|
||||
}
|
||||
|
||||
workspace_reconciliator::send_notification(*m_idx, *w_idx);
|
||||
needs_reconciliation = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,11 +340,14 @@ impl WindowManager {
|
||||
// duplicates across multiple workspaces, as it results in ghost layout tiles.
|
||||
let mut proceed = true;
|
||||
|
||||
for (i, monitor) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
||||
if workspace.contains_window(window.hwnd)
|
||||
&& i != self.focused_monitor_idx()
|
||||
&& j != monitor.focused_workspace_idx()
|
||||
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if let Some(focused_workspace_idx) = self
|
||||
.monitors()
|
||||
.get(*m_idx)
|
||||
.map(|m| m.focused_workspace_idx())
|
||||
{
|
||||
if *m_idx != self.focused_monitor_idx()
|
||||
&& *w_idx != focused_workspace_idx
|
||||
{
|
||||
tracing::debug!(
|
||||
"ignoring show event for window already associated with another workspace"
|
||||
@@ -394,11 +396,13 @@ impl WindowManager {
|
||||
|
||||
if behaviour.float_override {
|
||||
workspace.floating_windows_mut().push(window);
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
self.update_focused_workspace(false, false)?;
|
||||
} else {
|
||||
match behaviour.current_behaviour {
|
||||
WindowContainerBehaviour::Create => {
|
||||
workspace.new_container_for_window(window);
|
||||
workspace.set_layer(WorkspaceLayer::Tiling);
|
||||
self.update_focused_workspace(false, false)?;
|
||||
}
|
||||
WindowContainerBehaviour::Append => {
|
||||
@@ -408,6 +412,7 @@ impl WindowManager {
|
||||
anyhow!("there is no focused container")
|
||||
})?
|
||||
.add_window(window);
|
||||
workspace.set_layer(WorkspaceLayer::Tiling);
|
||||
self.update_focused_workspace(true, false)?;
|
||||
stackbar_manager::send_notification();
|
||||
}
|
||||
@@ -504,15 +509,9 @@ impl WindowManager {
|
||||
// This will be true if we have moved to another monitor
|
||||
let mut moved_across_monitors = false;
|
||||
|
||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||
for workspace in monitors.workspaces() {
|
||||
if workspace.contains_window(window.hwnd) && i != target_monitor_idx {
|
||||
moved_across_monitors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if moved_across_monitors {
|
||||
break;
|
||||
if let Some((m_idx, _)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if *m_idx != target_monitor_idx {
|
||||
moved_across_monitors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +661,7 @@ impl WindowManager {
|
||||
let mut ops = vec![];
|
||||
|
||||
macro_rules! resize_op {
|
||||
($coordinate:expr, $comparator:tt, $direction:expr) => {{
|
||||
($coordinate:expr_2021, $comparator:tt, $direction:expr_2021) => {{
|
||||
let adjusted = $coordinate * 2;
|
||||
let sizing = if adjusted $comparator 0 {
|
||||
Sizing::Decrease
|
||||
@@ -716,42 +715,8 @@ impl WindowManager {
|
||||
window.center(&self.focused_monitor_work_area()?)?;
|
||||
}
|
||||
|
||||
tracing::trace!("updating list of known hwnds");
|
||||
let mut known_hwnds = vec![];
|
||||
for monitor in self.monitors() {
|
||||
for workspace in monitor.workspaces() {
|
||||
for container in workspace.containers() {
|
||||
for window in container.windows() {
|
||||
known_hwnds.push(window.hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
for window in workspace.floating_windows() {
|
||||
known_hwnds.push(window.hwnd);
|
||||
}
|
||||
|
||||
if let Some(window) = workspace.maximized_window() {
|
||||
known_hwnds.push(window.hwnd);
|
||||
}
|
||||
|
||||
if let Some(container) = workspace.monocle_container() {
|
||||
for window in container.windows() {
|
||||
known_hwnds.push(window.hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(hwnd_json)?;
|
||||
|
||||
serde_json::to_writer_pretty(&file, &known_hwnds)?;
|
||||
|
||||
self.known_hwnds = known_hwnds;
|
||||
// Update list of known_hwnds and their monitor/workspace index pair
|
||||
self.update_known_hwnds();
|
||||
|
||||
notify_subscribers(
|
||||
Notification {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use winput::Action;
|
||||
use winput::message_loop;
|
||||
use winput::message_loop::Event;
|
||||
use winput::Action;
|
||||
|
||||
use crate::core::FocusFollowsMouseImplementation;
|
||||
|
||||
|
||||
@@ -1,72 +1,204 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::DATA_DIR;
|
||||
use crate::NotificationEvent;
|
||||
use crate::Window;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowManagerEvent;
|
||||
use crate::border_manager;
|
||||
use crate::notify_subscribers;
|
||||
use crate::winevent::WinEvent;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || loop {
|
||||
match find_orphans(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
if cfg!(debug_assertions) {
|
||||
tracing::error!("restarting failed thread: {:?}", error)
|
||||
} else {
|
||||
tracing::error!("restarting failed thread: {}", error)
|
||||
lazy_static! {
|
||||
pub static ref HWNDS_CACHE: Arc<Mutex<HashMap<isize, (usize, usize)>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
}
|
||||
|
||||
pub struct ReaperNotification(pub HashMap<isize, (usize, usize)>);
|
||||
|
||||
static CHANNEL: OnceLock<(Sender<ReaperNotification>, Receiver<ReaperNotification>)> =
|
||||
OnceLock::new();
|
||||
|
||||
pub fn channel() -> &'static (Sender<ReaperNotification>, Receiver<ReaperNotification>) {
|
||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))
|
||||
}
|
||||
|
||||
fn event_tx() -> Sender<ReaperNotification> {
|
||||
channel().0.clone()
|
||||
}
|
||||
|
||||
fn event_rx() -> Receiver<ReaperNotification> {
|
||||
channel().1.clone()
|
||||
}
|
||||
|
||||
pub fn send_notification(hwnds: HashMap<isize, (usize, usize)>) {
|
||||
if event_tx().try_send(ReaperNotification(hwnds)).is_err() {
|
||||
tracing::warn!("channel is full; dropping notification")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications(
|
||||
wm: Arc<Mutex<WindowManager>>,
|
||||
known_hwnds: HashMap<isize, (usize, usize)>,
|
||||
) {
|
||||
watch_for_orphans(known_hwnds);
|
||||
|
||||
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 find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("watching");
|
||||
fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("listening");
|
||||
|
||||
let arc = wm.clone();
|
||||
let receiver = event_rx();
|
||||
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
|
||||
let mut wm = arc.lock();
|
||||
let offset = wm.work_area_offset;
|
||||
for notification in receiver {
|
||||
let orphan_hwnds = notification.0;
|
||||
let mut wm = wm.lock();
|
||||
|
||||
let mut update_borders = false;
|
||||
|
||||
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
||||
let work_area = *monitor.work_area_size();
|
||||
let window_based_work_area_offset = (
|
||||
monitor.window_based_work_area_offset_limit(),
|
||||
monitor.window_based_work_area_offset(),
|
||||
);
|
||||
for (hwnd, (m_idx, w_idx)) in orphan_hwnds.iter() {
|
||||
if let Some(monitor) = wm.monitors_mut().get_mut(*m_idx) {
|
||||
let focused_workspace_idx = monitor.focused_workspace_idx();
|
||||
|
||||
let offset = if monitor.work_area_offset().is_some() {
|
||||
monitor.work_area_offset()
|
||||
} else {
|
||||
offset
|
||||
};
|
||||
if let Some(workspace) = monitor.workspaces_mut().get_mut(*w_idx) {
|
||||
// Remove orphan window
|
||||
if let Err(error) = workspace.remove_window(*hwnd) {
|
||||
tracing::warn!(
|
||||
"error reaping orphan window ({}) on monitor: {}, workspace: {}. Error: {}",
|
||||
hwnd,
|
||||
m_idx,
|
||||
w_idx,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||
let reaped_orphans = workspace.reap_orphans()?;
|
||||
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
|
||||
workspace.update(&work_area, offset, window_based_work_area_offset)?;
|
||||
update_borders = true;
|
||||
if focused_workspace_idx == *w_idx {
|
||||
// If this is not a focused workspace there is no need to update the
|
||||
// workspace or the borders. That will already be done when the user
|
||||
// changes to this workspace.
|
||||
workspace.update()?;
|
||||
update_borders = true;
|
||||
}
|
||||
tracing::info!(
|
||||
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
|
||||
reaped_orphans.0,
|
||||
reaped_orphans.1,
|
||||
i,
|
||||
j
|
||||
"reaped orphan window ({}) on monitor: {}, workspace: {}",
|
||||
hwnd,
|
||||
m_idx,
|
||||
w_idx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
wm.known_hwnds.remove(hwnd);
|
||||
|
||||
let window = Window::from(*hwnd);
|
||||
notify_subscribers(
|
||||
crate::Notification {
|
||||
event: NotificationEvent::WindowManager(WindowManagerEvent::Destroy(
|
||||
WinEvent::ObjectDestroy,
|
||||
window,
|
||||
)),
|
||||
state: wm.as_ref().into(),
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
if update_borders {
|
||||
border_manager::send_notification(None);
|
||||
}
|
||||
|
||||
// Save to file
|
||||
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(hwnd_json)?;
|
||||
|
||||
serde_json::to_writer_pretty(&file, &wm.known_hwnds.keys().collect::<Vec<_>>())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn watch_for_orphans(known_hwnds: HashMap<isize, (usize, usize)>) {
|
||||
// Cache current hwnds
|
||||
{
|
||||
let mut cache = HWNDS_CACHE.lock();
|
||||
*cache = known_hwnds;
|
||||
}
|
||||
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match find_orphans() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn find_orphans() -> color_eyre::Result<()> {
|
||||
tracing::info!("watching");
|
||||
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
|
||||
let mut cache = HWNDS_CACHE.lock();
|
||||
let mut orphan_hwnds = HashMap::new();
|
||||
|
||||
for (hwnd, (m_idx, w_idx)) in cache.iter() {
|
||||
let window = Window::from(*hwnd);
|
||||
|
||||
if !window.is_window()
|
||||
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
|
||||
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
|
||||
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
|
||||
// docs is closed
|
||||
//
|
||||
// I hate every single person who worked on Microsoft Office 365, especially Word
|
||||
|| !window.is_visible()
|
||||
{
|
||||
orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx));
|
||||
}
|
||||
}
|
||||
|
||||
if !orphan_hwnds.is_empty() {
|
||||
// Update reaper cache
|
||||
cache.retain(|h, _| !orphan_hwnds.contains_key(h));
|
||||
|
||||
// Send handles to remove
|
||||
event_tx().send(ReaperNotification(orphan_hwnds))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
mod stackbar;
|
||||
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use crate::container::Container;
|
||||
use crate::core::StackbarLabel;
|
||||
use crate::core::StackbarMode;
|
||||
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 lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub static STACKBAR_FONT_SIZE: AtomicI32 = AtomicI32::new(0); // 0 will produce the system default
|
||||
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
|
||||
@@ -71,13 +71,15 @@ pub fn should_have_stackbar(window_count: usize) -> bool {
|
||||
}
|
||||
|
||||
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);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("restarting failed thread: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -193,13 +195,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
// 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 {
|
||||
Entry::Vacant(entry) => match Stackbar::create(container.id()) {
|
||||
Ok(stackbar) => entry.insert(stackbar),
|
||||
_ => {
|
||||
continue 'receiver;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
stackbars_monitors.insert(container.id().clone(), monitor_idx);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WindowsApi;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::border_manager::STYLE;
|
||||
@@ -5,7 +8,6 @@ use crate::container::Container;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::Rect;
|
||||
use crate::core::StackbarLabel;
|
||||
use crate::stackbar_manager::STACKBARS_CONTAINERS;
|
||||
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
|
||||
use crate::stackbar_manager::STACKBAR_FONT_SIZE;
|
||||
@@ -14,16 +16,13 @@ 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::stackbar_manager::STACKBARS_CONTAINERS;
|
||||
use crate::windows_api;
|
||||
use crate::WindowsApi;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::WINDOWS_11;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::Foundation::COLORREF;
|
||||
use windows::Win32::Foundation::HINSTANCE;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
@@ -33,38 +32,38 @@ 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::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::DeleteObject;
|
||||
use windows::Win32::Graphics::Gdi::DrawTextW;
|
||||
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
|
||||
use windows::Win32::Graphics::Gdi::FW_BOLD;
|
||||
use windows::Win32::Graphics::Gdi::GetDC;
|
||||
use windows::Win32::Graphics::Gdi::GetDeviceCaps;
|
||||
use windows::Win32::Graphics::Gdi::LOGFONTW;
|
||||
use windows::Win32::Graphics::Gdi::LOGPIXELSY;
|
||||
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
|
||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||
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::LOGPIXELSY;
|
||||
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
|
||||
use windows::Win32::Graphics::Gdi::PS_SOLID;
|
||||
use windows::Win32::System::WindowsProgramming::MulDiv;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
|
||||
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::LWA_COLORKEY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
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;
|
||||
@@ -72,6 +71,7 @@ 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 windows::core::PCWSTR;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stackbar {
|
||||
@@ -123,7 +123,7 @@ impl Stackbar {
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
HINSTANCE(windows_api::as_ptr!(instance)),
|
||||
Option::from(HINSTANCE(windows_api::as_ptr!(instance))),
|
||||
None,
|
||||
)?;
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Stackbar {
|
||||
let mut msg: MSG = MSG::default();
|
||||
|
||||
loop {
|
||||
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
|
||||
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
|
||||
tracing::debug!("stackbar window event processing thread shutdown");
|
||||
break;
|
||||
};
|
||||
@@ -183,13 +183,13 @@ impl Stackbar {
|
||||
WindowsApi::position_window(self.hwnd, &layout, false)?;
|
||||
|
||||
unsafe {
|
||||
let hdc = GetDC(self.hwnd());
|
||||
let hdc = GetDC(Option::from(self.hwnd()));
|
||||
|
||||
let hpen = CreatePen(PS_SOLID, 0, COLORREF(background));
|
||||
let hbrush = CreateSolidBrush(COLORREF(background));
|
||||
|
||||
SelectObject(hdc, hpen);
|
||||
SelectObject(hdc, hbrush);
|
||||
SelectObject(hdc, hpen.into());
|
||||
SelectObject(hdc, hbrush.into());
|
||||
SetBkColor(hdc, COLORREF(background));
|
||||
|
||||
let mut logfont = LOGFONTW {
|
||||
@@ -209,14 +209,14 @@ impl Stackbar {
|
||||
let logical_height = -MulDiv(
|
||||
STACKBAR_FONT_SIZE.load(Ordering::SeqCst),
|
||||
72,
|
||||
GetDeviceCaps(hdc, LOGPIXELSY),
|
||||
GetDeviceCaps(Option::from(hdc), LOGPIXELSY),
|
||||
);
|
||||
|
||||
logfont.lfHeight = logical_height;
|
||||
|
||||
let hfont = CreateFontIndirectW(&logfont);
|
||||
|
||||
SelectObject(hdc, hfont);
|
||||
SelectObject(hdc, hfont.into());
|
||||
|
||||
for (i, window) in container.windows().iter().enumerate() {
|
||||
if window.hwnd == container.focused_window().copied().unwrap_or_default().hwnd {
|
||||
@@ -283,13 +283,13 @@ impl Stackbar {
|
||||
);
|
||||
}
|
||||
|
||||
ReleaseDC(self.hwnd(), hdc);
|
||||
ReleaseDC(Option::from(self.hwnd()), hdc);
|
||||
// TODO: error handling
|
||||
let _ = DeleteObject(hpen);
|
||||
let _ = DeleteObject(hpen.into());
|
||||
// TODO: error handling
|
||||
let _ = DeleteObject(hbrush);
|
||||
let _ = DeleteObject(hbrush.into());
|
||||
// TODO: error handling
|
||||
let _ = DeleteObject(hfont);
|
||||
let _ = DeleteObject(hfont.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -342,10 +342,10 @@ impl Stackbar {
|
||||
window.set_position(&focused_window_rect, false)
|
||||
{
|
||||
tracing::error!(
|
||||
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
|
||||
*window,
|
||||
err
|
||||
);
|
||||
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
|
||||
*window,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,28 @@
|
||||
use crate::animation::PerAnimationPrefixConfig;
|
||||
use crate::AspectRatio;
|
||||
use crate::Axis;
|
||||
use crate::CrossBoundaryBehaviour;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::IGNORE_IDENTIFIERS;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
|
||||
use crate::PredefinedAspectRatio;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
||||
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
||||
use crate::TRANSPARENCY_BLACKLIST;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
@@ -7,10 +31,11 @@ use crate::animation::ANIMATION_FPS;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::animation::DEFAULT_ANIMATION_FPS;
|
||||
use crate::animation::PerAnimationPrefixConfig;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::ZOrder;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::border_manager::ZOrder;
|
||||
use crate::colour::Colour;
|
||||
use crate::core::BorderImplementation;
|
||||
use crate::core::StackbarLabel;
|
||||
@@ -36,41 +61,10 @@ use crate::window_manager::WindowManager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::workspace::Workspace;
|
||||
use crate::AspectRatio;
|
||||
use crate::Axis;
|
||||
use crate::CrossBoundaryBehaviour;
|
||||
use crate::PredefinedAspectRatio;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
use crate::IGNORE_IDENTIFIERS;
|
||||
use crate::LAYERED_WHITELIST;
|
||||
use crate::MANAGE_IDENTIFIERS;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
||||
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
||||
use crate::TRANSPARENCY_BLACKLIST;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WINDOWS_11;
|
||||
use crate::WORKSPACE_MATCHING_RULES;
|
||||
|
||||
use crate::asc::ApplicationSpecificConfiguration;
|
||||
use crate::asc::AscApplicationRulesOrSchema;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::ApplicationConfigurationGenerator;
|
||||
use crate::core::config_generation::ApplicationOptions;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use crate::core::resolve_home_path;
|
||||
use crate::core::AnimationStyle;
|
||||
use crate::core::BorderStyle;
|
||||
use crate::core::DefaultLayout;
|
||||
@@ -83,6 +77,12 @@ use crate::core::Rect;
|
||||
use crate::core::SocketMessage;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
use crate::core::WindowManagementBehaviour;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::ApplicationConfigurationGenerator;
|
||||
use crate::core::config_generation::ApplicationOptions;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use crate::core::resolve_home_path;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use hotwatch::EventKind;
|
||||
@@ -97,8 +97,8 @@ use std::collections::HashSet;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use uds_windows::UnixListener;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
@@ -140,7 +140,7 @@ pub struct WorkspaceConfig {
|
||||
/// Container padding (default: global)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub container_padding: Option<i32>,
|
||||
/// Container padding (default: global)
|
||||
/// Workspace padding (default: global)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workspace_padding: Option<i32>,
|
||||
/// Initial workspace application rules
|
||||
@@ -256,6 +256,12 @@ pub struct MonitorConfig {
|
||||
/// Open window limit after which the window based work area offset will no longer be applied (default: 1)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub window_based_work_area_offset_limit: Option<isize>,
|
||||
/// Container padding (default: global)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub container_padding: Option<i32>,
|
||||
/// Workspace padding (default: global)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workspace_padding: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<&Monitor> for MonitorConfig {
|
||||
@@ -265,11 +271,32 @@ impl From<&Monitor> for MonitorConfig {
|
||||
workspaces.push(WorkspaceConfig::from(w));
|
||||
}
|
||||
|
||||
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
|
||||
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
|
||||
|
||||
let container_padding = value.container_padding().and_then(|container_padding| {
|
||||
if container_padding == default_container_padding {
|
||||
None
|
||||
} else {
|
||||
Option::from(container_padding)
|
||||
}
|
||||
});
|
||||
|
||||
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
|
||||
if workspace_padding == default_workspace_padding {
|
||||
None
|
||||
} else {
|
||||
Option::from(workspace_padding)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
workspaces,
|
||||
work_area_offset: value.work_area_offset(),
|
||||
window_based_work_area_offset: value.window_based_work_area_offset(),
|
||||
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
|
||||
container_padding,
|
||||
workspace_padding,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,7 +555,9 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
if display {
|
||||
println!("\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n")
|
||||
println!(
|
||||
"\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +582,9 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
if display {
|
||||
println!("\nYour configuration file contains some options that have been renamed or deprecated:\n");
|
||||
println!(
|
||||
"\nYour configuration file contains some options that have been renamed or deprecated:\n"
|
||||
);
|
||||
for (canonical, aliases) in map {
|
||||
for alias in aliases {
|
||||
if raw.contains(alias) {
|
||||
@@ -1208,7 +1239,7 @@ impl StaticConfig {
|
||||
pending_move_op: Arc::new(None),
|
||||
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
|
||||
uncloack_to_ignore: 0,
|
||||
known_hwnds: Vec::new(),
|
||||
known_hwnds: HashMap::new(),
|
||||
};
|
||||
|
||||
match value.focus_follows_mouse {
|
||||
@@ -1250,6 +1281,7 @@ impl StaticConfig {
|
||||
workspace_matching_rules.clear();
|
||||
drop(workspace_matching_rules);
|
||||
|
||||
let offset = wm.work_area_offset;
|
||||
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
||||
let preferred_config_idx = {
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
@@ -1295,7 +1327,10 @@ impl StaticConfig {
|
||||
.window_based_work_area_offset_limit
|
||||
.unwrap_or(1),
|
||||
);
|
||||
monitor.set_container_padding(monitor_config.container_padding);
|
||||
monitor.set_workspace_padding(monitor_config.workspace_padding);
|
||||
|
||||
monitor.update_workspaces_globals(offset);
|
||||
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
||||
ws.load_static_config(workspace_config)?;
|
||||
@@ -1377,6 +1412,10 @@ impl StaticConfig {
|
||||
.window_based_work_area_offset_limit
|
||||
.unwrap_or(1),
|
||||
);
|
||||
m.set_container_padding(monitor_config.container_padding);
|
||||
m.set_workspace_padding(monitor_config.workspace_padding);
|
||||
|
||||
m.update_workspaces_globals(offset);
|
||||
|
||||
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
||||
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
||||
@@ -1411,6 +1450,7 @@ impl StaticConfig {
|
||||
workspace_matching_rules.clear();
|
||||
drop(workspace_matching_rules);
|
||||
|
||||
let offset = wm.work_area_offset;
|
||||
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
||||
let preferred_config_idx = {
|
||||
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
|
||||
@@ -1458,6 +1498,10 @@ impl StaticConfig {
|
||||
.window_based_work_area_offset_limit
|
||||
.unwrap_or(1),
|
||||
);
|
||||
monitor.set_container_padding(monitor_config.container_padding);
|
||||
monitor.set_workspace_padding(monitor_config.workspace_padding);
|
||||
|
||||
monitor.update_workspaces_globals(offset);
|
||||
|
||||
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
|
||||
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
||||
@@ -1540,6 +1584,10 @@ impl StaticConfig {
|
||||
.window_based_work_area_offset_limit
|
||||
.unwrap_or(1),
|
||||
);
|
||||
m.set_container_padding(monitor_config.container_padding);
|
||||
m.set_workspace_padding(monitor_config.workspace_padding);
|
||||
|
||||
m.update_workspaces_globals(offset);
|
||||
|
||||
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
||||
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::Colour;
|
||||
use crate::KomorebiTheme;
|
||||
use crate::border_manager;
|
||||
use crate::stackbar_manager;
|
||||
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||
use crate::Colour;
|
||||
use crate::KomorebiTheme;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub struct Notification(KomorebiTheme);
|
||||
|
||||
@@ -50,13 +50,15 @@ pub fn send_notification(theme: KomorebiTheme) {
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications() {
|
||||
std::thread::spawn(move || loop {
|
||||
match handle_notifications() {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("restarting failed thread: {}", error);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match handle_notifications() {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("restarting failed thread: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,17 +4,17 @@ use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU8;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU8;
|
||||
|
||||
use crate::should_act;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::TRANSPARENCY_BLACKLIST;
|
||||
use crate::Window;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::TRANSPARENCY_BLACKLIST;
|
||||
use crate::should_act;
|
||||
|
||||
pub static TRANSPARENCY_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
pub static TRANSPARENCY_ALPHA: AtomicU8 = AtomicU8::new(200);
|
||||
@@ -49,13 +49,15 @@ pub fn send_notification() {
|
||||
}
|
||||
|
||||
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);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("restarting failed thread: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -175,7 +177,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
match window.transparent() {
|
||||
Err(error) => {
|
||||
let hwnd = foreground_hwnd;
|
||||
tracing::error!("failed to make unfocused window {hwnd} transparent: {error}" )
|
||||
tracing::error!(
|
||||
"failed to make unfocused window {hwnd} transparent: {error}"
|
||||
)
|
||||
}
|
||||
Ok(..) => {
|
||||
known_hwnds.lock().push(window.hwnd);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::animation::lerp::Lerp;
|
||||
use crate::animation::prefix::new_animation_key;
|
||||
use crate::animation::prefix::AnimationPrefix;
|
||||
use crate::animation::AnimationEngine;
|
||||
use crate::animation::RenderDispatcher;
|
||||
use crate::AnimationStyle;
|
||||
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
|
||||
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
||||
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
@@ -10,14 +9,15 @@ use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_MANAGER;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::animation::AnimationEngine;
|
||||
use crate::animation::RenderDispatcher;
|
||||
use crate::animation::lerp::Lerp;
|
||||
use crate::animation::prefix::AnimationPrefix;
|
||||
use crate::animation::prefix::new_animation_key;
|
||||
use crate::com::SetCloak;
|
||||
use crate::focus_manager;
|
||||
use crate::stackbar_manager;
|
||||
use crate::windows_api;
|
||||
use crate::AnimationStyle;
|
||||
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
|
||||
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
||||
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
@@ -31,15 +31,15 @@ use std::time::Duration;
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use serde::ser::SerializeStruct;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
@@ -48,11 +48,6 @@ use crate::core::ApplicationIdentifier;
|
||||
use crate::core::HidingBehaviour;
|
||||
use crate::core::Rect;
|
||||
|
||||
use crate::styles::ExtendedWindowStyle;
|
||||
use crate::styles::WindowStyle;
|
||||
use crate::transparency_manager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::HIDING_BEHAVIOUR;
|
||||
@@ -63,6 +58,11 @@ use crate::NO_TITLEBAR;
|
||||
use crate::PERMAIGNORE_CLASSES;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::WSL2_UI_PROCESSES;
|
||||
use crate::styles::ExtendedWindowStyle;
|
||||
use crate::styles::WindowStyle;
|
||||
use crate::transparency_manager;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
|
||||
pub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);
|
||||
pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
|
||||
@@ -736,6 +736,30 @@ impl Window {
|
||||
self.update_style(&style)
|
||||
}
|
||||
|
||||
/// Raise the window to the top of the Z order, but do not activate or focus
|
||||
/// it. Use raise_and_focus_window to activate and focus a window.
|
||||
/// It also checks if there is a border attached to this window and if it is
|
||||
/// it raises it as well.
|
||||
pub fn raise(self) -> Result<()> {
|
||||
WindowsApi::raise_window(self.hwnd)?;
|
||||
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
|
||||
WindowsApi::raise_window(border.hwnd)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lower the window to the bottom of the Z order, but do not activate or focus
|
||||
/// it.
|
||||
/// It also checks if there is a border attached to this window and if it is
|
||||
/// it lowers it as well.
|
||||
pub fn lower(self) -> Result<()> {
|
||||
WindowsApi::lower_window(self.hwnd)?;
|
||||
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
|
||||
WindowsApi::lower_window(border.hwnd)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(exe, title), skip(debug))]
|
||||
pub fn should_manage(
|
||||
self,
|
||||
@@ -862,7 +886,7 @@ fn window_is_eligible(
|
||||
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
||||
|
||||
let ignore_identifiers = IGNORE_IDENTIFIERS.lock();
|
||||
let should_ignore = if let Some(rule) = should_act(
|
||||
let should_ignore = match should_act(
|
||||
title,
|
||||
exe_name,
|
||||
class,
|
||||
@@ -870,14 +894,15 @@ fn window_is_eligible(
|
||||
&ignore_identifiers,
|
||||
®ex_identifiers,
|
||||
) {
|
||||
debug.matches_ignore_identifier = Some(rule);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
Some(rule) => {
|
||||
debug.matches_ignore_identifier = Some(rule);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
||||
let managed_override = if let Some(rule) = should_act(
|
||||
let managed_override = match should_act(
|
||||
title,
|
||||
exe_name,
|
||||
class,
|
||||
@@ -885,10 +910,11 @@ fn window_is_eligible(
|
||||
&manage_identifiers,
|
||||
®ex_identifiers,
|
||||
) {
|
||||
debug.matches_managed_override = Some(rule);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
Some(rule) => {
|
||||
debug.matches_managed_override = Some(rule);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let floating_identifiers = FLOATING_APPLICATIONS.lock();
|
||||
@@ -908,7 +934,7 @@ fn window_is_eligible(
|
||||
}
|
||||
|
||||
let layered_whitelist = LAYERED_WHITELIST.lock();
|
||||
let mut allow_layered = if let Some(rule) = should_act(
|
||||
let mut allow_layered = match should_act(
|
||||
title,
|
||||
exe_name,
|
||||
class,
|
||||
@@ -916,10 +942,11 @@ fn window_is_eligible(
|
||||
&layered_whitelist,
|
||||
®ex_identifiers,
|
||||
) {
|
||||
debug.matches_layered_whitelist = Some(rule);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
Some(rule) => {
|
||||
debug.matches_layered_whitelist = Some(rule);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let known_layered_hwnds = transparency_manager::known_hwnds();
|
||||
@@ -946,7 +973,7 @@ fn window_is_eligible(
|
||||
};
|
||||
|
||||
let titlebars_removed = NO_TITLEBAR.lock();
|
||||
let allow_titlebar_removed = if let Some(rule) = should_act(
|
||||
let allow_titlebar_removed = match should_act(
|
||||
title,
|
||||
exe_name,
|
||||
class,
|
||||
@@ -954,10 +981,11 @@ fn window_is_eligible(
|
||||
&titlebars_removed,
|
||||
®ex_identifiers,
|
||||
) {
|
||||
debug.matches_no_titlebar = Some(rule);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
Some(rule) => {
|
||||
debug.matches_no_titlebar = Some(rule);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,12 @@ use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::window::should_act;
|
||||
use crate::window::Window;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::window::Window;
|
||||
use crate::window::should_act;
|
||||
use crate::winevent::WinEvent;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
use core::ffi::c_void;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use windows::core::Result as WindowsCrateResult;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
use windows::Win32::Foundation::CloseHandle;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::COLORREF;
|
||||
use windows::Win32::Foundation::CloseHandle;
|
||||
use windows::Win32::Foundation::HANDLE;
|
||||
use windows::Win32::Foundation::HINSTANCE;
|
||||
use windows::Win32::Foundation::HMODULE;
|
||||
@@ -22,8 +18,9 @@ use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Foundation::POINT;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_BORDER_COLOR;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_COLOR_NONE;
|
||||
@@ -31,52 +28,56 @@ use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
|
||||
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
|
||||
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
|
||||
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
|
||||
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
||||
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
||||
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
|
||||
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
|
||||
use windows::Win32::Graphics::Gdi::HBRUSH;
|
||||
use windows::Win32::Graphics::Gdi::HDC;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
||||
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
|
||||
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
|
||||
use windows::Win32::Graphics::Gdi::Rectangle;
|
||||
use windows::Win32::Graphics::Gdi::RoundRect;
|
||||
use windows::Win32::Graphics::Gdi::HBRUSH;
|
||||
use windows::Win32::Graphics::Gdi::HDC;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::Power::RegisterPowerSettingNotification;
|
||||
use windows::Win32::System::Power::HPOWERNOTIFY;
|
||||
use windows::Win32::System::Power::RegisterPowerSettingNotification;
|
||||
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
|
||||
use windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification;
|
||||
use windows::Win32::System::Threading::GetCurrentProcessId;
|
||||
use windows::Win32::System::Threading::OpenProcess;
|
||||
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
|
||||
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
|
||||
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
|
||||
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
|
||||
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
|
||||
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
|
||||
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
|
||||
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
|
||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
|
||||
@@ -87,15 +88,37 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
|
||||
use windows::Win32::UI::WindowsAndMessaging::IsZoomed;
|
||||
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MoveWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::RegisterDeviceNotificationW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
|
||||
@@ -103,33 +126,6 @@ use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
|
||||
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
|
||||
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
@@ -140,22 +136,27 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
use windows::core::Result as WindowsCrateResult;
|
||||
use windows_core::BOOL;
|
||||
|
||||
use crate::core::Rect;
|
||||
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
use crate::Window;
|
||||
use crate::WindowManager;
|
||||
use crate::container::Container;
|
||||
use crate::monitor;
|
||||
use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::set_window_position::SetWindowPosition;
|
||||
use crate::windows_callbacks;
|
||||
use crate::Window;
|
||||
use crate::WindowManager;
|
||||
use crate::DISPLAY_INDEX_PREFERENCES;
|
||||
use crate::MONITOR_INDEX_PREFERENCES;
|
||||
|
||||
macro_rules! as_ptr {
|
||||
($value:expr) => {
|
||||
($value:expr_2021) => {
|
||||
$value as *mut core::ffi::c_void
|
||||
};
|
||||
}
|
||||
@@ -236,16 +237,9 @@ impl WindowsApi {
|
||||
callback: MONITORENUMPROC,
|
||||
callback_data_address: isize,
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
EnumDisplayMonitors(
|
||||
HDC(std::ptr::null_mut()),
|
||||
None,
|
||||
callback,
|
||||
LPARAM(callback_data_address),
|
||||
)
|
||||
}
|
||||
.ok()
|
||||
.process()
|
||||
unsafe { EnumDisplayMonitors(None, None, callback, LPARAM(callback_data_address)) }
|
||||
.ok()
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
|
||||
@@ -484,10 +478,13 @@ impl WindowsApi {
|
||||
unsafe { BringWindowToTop(HWND(as_ptr!(hwnd))) }.process()
|
||||
}
|
||||
|
||||
// Raise the window to the top of the Z order, but do not activate or focus
|
||||
// it. Use raise_and_focus_window to activate and focus a window.
|
||||
/// Raise the window to the top of the Z order, but do not activate or focus
|
||||
/// it. Use raise_and_focus_window to activate and focus a window.
|
||||
pub fn raise_window(hwnd: isize) -> Result<()> {
|
||||
let flags = SetWindowPosition::NO_MOVE | SetWindowPosition::NO_ACTIVATE;
|
||||
let flags = SetWindowPosition::NO_MOVE
|
||||
| SetWindowPosition::NO_SIZE
|
||||
| SetWindowPosition::NO_ACTIVATE
|
||||
| SetWindowPosition::SHOW_WINDOW;
|
||||
|
||||
let position = HWND_TOP;
|
||||
Self::set_window_pos(
|
||||
@@ -498,6 +495,23 @@ impl WindowsApi {
|
||||
)
|
||||
}
|
||||
|
||||
/// Lower the window to the bottom of the Z order, but do not activate or focus
|
||||
/// it.
|
||||
pub fn lower_window(hwnd: isize) -> Result<()> {
|
||||
let flags = SetWindowPosition::NO_MOVE
|
||||
| SetWindowPosition::NO_SIZE
|
||||
| SetWindowPosition::NO_ACTIVATE
|
||||
| SetWindowPosition::SHOW_WINDOW;
|
||||
|
||||
let position = HWND_BOTTOM;
|
||||
Self::set_window_pos(
|
||||
HWND(as_ptr!(hwnd)),
|
||||
&Rect::default(),
|
||||
position,
|
||||
flags.bits(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> {
|
||||
let flags = {
|
||||
SetWindowPosition::NO_SEND_CHANGING
|
||||
@@ -519,7 +533,7 @@ impl WindowsApi {
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
position,
|
||||
Option::from(position),
|
||||
layout.left,
|
||||
layout.top,
|
||||
layout.right,
|
||||
@@ -557,7 +571,7 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
fn post_message(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> Result<()> {
|
||||
unsafe { PostMessageW(hwnd, message, wparam, lparam) }.process()
|
||||
unsafe { PostMessageW(Option::from(hwnd), message, wparam, lparam) }.process()
|
||||
}
|
||||
|
||||
pub fn close_window(hwnd: isize) -> Result<()> {
|
||||
@@ -600,7 +614,7 @@ impl WindowsApi {
|
||||
// Error ignored, as the operation is not always necessary.
|
||||
let _ = SetWindowPos(
|
||||
HWND(as_ptr!(hwnd)),
|
||||
HWND_TOP,
|
||||
Option::from(HWND_TOP),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -616,7 +630,7 @@ impl WindowsApi {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn top_window() -> Result<isize> {
|
||||
unsafe { GetTopWindow(HWND::default())? }.process()
|
||||
unsafe { GetTopWindow(None)? }.process()
|
||||
}
|
||||
|
||||
pub fn desktop_window() -> Result<isize> {
|
||||
@@ -932,7 +946,7 @@ impl WindowsApi {
|
||||
}
|
||||
|
||||
pub fn is_window(hwnd: isize) -> bool {
|
||||
unsafe { IsWindow(HWND(as_ptr!(hwnd))) }.into()
|
||||
unsafe { IsWindow(Option::from(HWND(as_ptr!(hwnd)))) }.into()
|
||||
}
|
||||
|
||||
pub fn is_window_visible(hwnd: isize) -> bool {
|
||||
@@ -1023,7 +1037,9 @@ impl WindowsApi {
|
||||
tracing::info!("current value of ForegroundLockTimeout is {value}");
|
||||
|
||||
if value != 0 {
|
||||
tracing::info!("updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating");
|
||||
tracing::info!(
|
||||
"updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating"
|
||||
);
|
||||
|
||||
Self::system_parameters_info_w(
|
||||
SPI_SETFOREGROUNDLOCKTIMEOUT,
|
||||
@@ -1160,7 +1176,7 @@ impl WindowsApi {
|
||||
CW_USEDEFAULT,
|
||||
None,
|
||||
None,
|
||||
HINSTANCE(as_ptr!(instance)),
|
||||
Option::from(HINSTANCE(as_ptr!(instance))),
|
||||
Some(border as _),
|
||||
)?
|
||||
}
|
||||
@@ -1209,7 +1225,7 @@ impl WindowsApi {
|
||||
CW_USEDEFAULT,
|
||||
None,
|
||||
None,
|
||||
HINSTANCE(as_ptr!(instance)),
|
||||
Option::from(HINSTANCE(as_ptr!(instance))),
|
||||
None,
|
||||
)?
|
||||
}
|
||||
@@ -1221,7 +1237,7 @@ impl WindowsApi {
|
||||
guid: &windows_core::GUID,
|
||||
flags: REGISTER_NOTIFICATION_FLAGS,
|
||||
) -> WindowsCrateResult<HPOWERNOTIFY> {
|
||||
unsafe { RegisterPowerSettingNotification(HWND(as_ptr!(hwnd)), guid, flags) }
|
||||
unsafe { RegisterPowerSettingNotification(HANDLE::from(HWND(as_ptr!(hwnd))), guid, flags) }
|
||||
}
|
||||
|
||||
pub fn register_device_notification(
|
||||
@@ -1230,15 +1246,14 @@ impl WindowsApi {
|
||||
flags: REGISTER_NOTIFICATION_FLAGS,
|
||||
) -> WindowsCrateResult<HDEVNOTIFY> {
|
||||
unsafe {
|
||||
let state_ptr: *const core::ffi::c_void =
|
||||
&mut filter as *mut _ as *const core::ffi::c_void;
|
||||
RegisterDeviceNotificationW(HWND(as_ptr!(hwnd)), state_ptr, flags)
|
||||
let state_ptr: *const c_void = &mut filter as *mut _ as *const c_void;
|
||||
RegisterDeviceNotificationW(HANDLE::from(HWND(as_ptr!(hwnd))), state_ptr, flags)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate_rect(hwnd: isize, rect: Option<&Rect>, erase: bool) -> bool {
|
||||
let rect = rect.map(|rect| &rect.rect() as *const RECT);
|
||||
unsafe { InvalidateRect(HWND(as_ptr!(hwnd)), rect, erase) }.as_bool()
|
||||
unsafe { InvalidateRect(Option::from(HWND(as_ptr!(hwnd))), rect, erase) }.as_bool()
|
||||
}
|
||||
|
||||
pub fn alt_is_pressed() -> bool {
|
||||
|
||||
@@ -8,19 +8,19 @@ use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::winevent_listener;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
|
||||
use windows_core::BOOL;
|
||||
|
||||
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
|
||||
|
||||
@@ -3,14 +3,13 @@ use std::time::Duration;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::Accessibility::SetWinEventHook;
|
||||
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
|
||||
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
|
||||
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::MSG;
|
||||
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_OUTOFCONTEXT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_SKIPOWNPROCESS;
|
||||
|
||||
@@ -41,7 +40,7 @@ pub fn start() {
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
if !GetMessageW(&mut msg, HWND(std::ptr::null_mut()), 0, 0).as_bool() {
|
||||
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
|
||||
tracing::debug!("windows event processing thread shutdown");
|
||||
break;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
@@ -20,6 +22,13 @@ use crate::core::Layout;
|
||||
use crate::core::OperationDirection;
|
||||
use crate::core::Rect;
|
||||
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::REMOVE_TITLEBARS;
|
||||
use crate::WindowContainerBehaviour;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::container::Container;
|
||||
@@ -31,13 +40,6 @@ use crate::static_config::WorkspaceConfig;
|
||||
use crate::window::Window;
|
||||
use crate::window::WindowDetails;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::WindowContainerBehaviour;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::REMOVE_TITLEBARS;
|
||||
|
||||
#[allow(clippy::struct_field_names)]
|
||||
#[derive(
|
||||
@@ -54,47 +56,67 @@ use crate::REMOVE_TITLEBARS;
|
||||
)]
|
||||
pub struct Workspace {
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
name: Option<String>,
|
||||
containers: Ring<Container>,
|
||||
pub name: Option<String>,
|
||||
pub containers: Ring<Container>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
monocle_container: Option<Container>,
|
||||
pub monocle_container: Option<Container>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
monocle_container_restore_idx: Option<usize>,
|
||||
pub monocle_container_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
maximized_window: Option<Window>,
|
||||
pub maximized_window: Option<Window>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
maximized_window_restore_idx: Option<usize>,
|
||||
pub maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
floating_windows: Vec<Window>,
|
||||
pub floating_windows: Vec<Window>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
layout: Layout,
|
||||
pub layout: Layout,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
layout_rules: Vec<(usize, Layout)>,
|
||||
pub layout_rules: Vec<(usize, Layout)>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
layout_flip: Option<Axis>,
|
||||
pub layout_flip: Option<Axis>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
workspace_padding: Option<i32>,
|
||||
pub workspace_padding: Option<i32>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
container_padding: Option<i32>,
|
||||
pub container_padding: Option<i32>,
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
latest_layout: Vec<Rect>,
|
||||
pub latest_layout: Vec<Rect>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
resize_dimensions: Vec<Option<Rect>>,
|
||||
pub resize_dimensions: Vec<Option<Rect>>,
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
tile: bool,
|
||||
pub tile: bool,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
apply_window_based_work_area_offset: bool,
|
||||
pub apply_window_based_work_area_offset: bool,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
window_container_behaviour: Option<WindowContainerBehaviour>,
|
||||
pub window_container_behaviour: Option<WindowContainerBehaviour>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
|
||||
pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
float_override: Option<bool>,
|
||||
#[serde(skip)]
|
||||
pub float_override: Option<bool>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub globals: WorkspaceGlobals,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub layer: WorkspaceLayer,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
workspace_config: Option<WorkspaceConfig>,
|
||||
pub workspace_config: Option<WorkspaceConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub enum WorkspaceLayer {
|
||||
#[default]
|
||||
Tiling,
|
||||
Floating,
|
||||
}
|
||||
|
||||
impl Display for WorkspaceLayer {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorkspaceLayer::Tiling => write!(f, "Tiling"),
|
||||
WorkspaceLayer::Floating => write!(f, "Floating"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_ring_elements!(Workspace, Container);
|
||||
@@ -121,6 +143,8 @@ impl Default for Workspace {
|
||||
window_container_behaviour: None,
|
||||
window_container_behaviour_rules: None,
|
||||
float_override: None,
|
||||
layer: Default::default(),
|
||||
globals: Default::default(),
|
||||
workspace_config: None,
|
||||
}
|
||||
}
|
||||
@@ -134,21 +158,37 @@ pub enum WorkspaceWindowLocation {
|
||||
Floating(usize), // idx in floating_windows
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Getters,
|
||||
CopyGetters,
|
||||
MutGetters,
|
||||
Setters,
|
||||
JsonSchema,
|
||||
PartialEq,
|
||||
)]
|
||||
/// Settings setup either by the parent monitor or by the `WindowManager`
|
||||
pub struct WorkspaceGlobals {
|
||||
pub container_padding: Option<i32>,
|
||||
pub workspace_padding: Option<i32>,
|
||||
pub work_area: Rect,
|
||||
pub work_area_offset: Option<Rect>,
|
||||
pub window_based_work_area_offset: Option<Rect>,
|
||||
pub window_based_work_area_offset_limit: isize,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> {
|
||||
self.name = Option::from(config.name.clone());
|
||||
|
||||
if config.container_padding.is_some() {
|
||||
self.set_container_padding(config.container_padding);
|
||||
} else {
|
||||
self.set_container_padding(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
|
||||
}
|
||||
self.set_container_padding(config.container_padding);
|
||||
|
||||
if config.workspace_padding.is_some() {
|
||||
self.set_workspace_padding(config.workspace_padding);
|
||||
} else {
|
||||
self.set_container_padding(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
|
||||
}
|
||||
self.set_workspace_padding(config.workspace_padding);
|
||||
|
||||
if let Some(layout) = &config.layout {
|
||||
self.layout = Layout::Default(*layout);
|
||||
@@ -282,7 +322,7 @@ impl Workspace {
|
||||
// Maximised windows and floating windows should always be drawn at the top of the Z order
|
||||
// when switching to a workspace
|
||||
if let Some(window) = to_focus {
|
||||
if self.maximized_window().is_none() && self.floating_windows().is_empty() {
|
||||
if self.maximized_window().is_none() && matches!(self.layer, WorkspaceLayer::Tiling) {
|
||||
window.focus(mouse_follows_focus)?;
|
||||
} else if let Some(maximized_window) = self.maximized_window() {
|
||||
maximized_window.focus(mouse_follows_focus)?;
|
||||
@@ -294,24 +334,29 @@ impl Workspace {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
work_area: &Rect,
|
||||
work_area_offset: Option<Rect>,
|
||||
window_based_work_area_offset: (isize, Option<Rect>),
|
||||
) -> Result<()> {
|
||||
pub fn update(&mut self) -> Result<()> {
|
||||
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (window_based_work_area_offset_limit, window_based_work_area_offset) =
|
||||
window_based_work_area_offset;
|
||||
let container_padding = self
|
||||
.container_padding()
|
||||
.or(self.globals().container_padding)
|
||||
.unwrap_or_default();
|
||||
let workspace_padding = self
|
||||
.workspace_padding()
|
||||
.or(self.globals().workspace_padding)
|
||||
.unwrap_or_default();
|
||||
let work_area = self.globals().work_area;
|
||||
let work_area_offset = self.globals().work_area_offset;
|
||||
let window_based_work_area_offset = self.globals().window_based_work_area_offset;
|
||||
let window_based_work_area_offset_limit =
|
||||
self.globals().window_based_work_area_offset_limit;
|
||||
|
||||
let container_padding = self.container_padding();
|
||||
let mut adjusted_work_area = work_area_offset.map_or_else(
|
||||
|| *work_area,
|
||||
|| work_area,
|
||||
|offset| {
|
||||
let mut with_offset = *work_area;
|
||||
let mut with_offset = work_area;
|
||||
with_offset.left += offset.left;
|
||||
with_offset.top += offset.top;
|
||||
with_offset.right -= offset.right;
|
||||
@@ -339,7 +384,7 @@ impl Workspace {
|
||||
);
|
||||
}
|
||||
|
||||
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
|
||||
adjusted_work_area.add_padding(workspace_padding);
|
||||
|
||||
self.enforce_resize_constraints();
|
||||
|
||||
@@ -373,7 +418,7 @@ impl Workspace {
|
||||
if *self.tile() {
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window) = container.focused_window_mut() {
|
||||
adjusted_work_area.add_padding(container_padding.unwrap_or_default());
|
||||
adjusted_work_area.add_padding(container_padding);
|
||||
{
|
||||
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
||||
adjusted_work_area.add_padding(border_offset);
|
||||
@@ -392,7 +437,7 @@ impl Workspace {
|
||||
"there must be at least one container to calculate a workspace layout"
|
||||
)
|
||||
})?,
|
||||
self.container_padding(),
|
||||
Some(container_padding),
|
||||
self.layout_flip(),
|
||||
self.resize_dimensions(),
|
||||
);
|
||||
@@ -401,7 +446,6 @@ impl Workspace {
|
||||
let no_titlebar = NO_TITLEBAR.lock().clone();
|
||||
let regex_identifiers = REGEX_IDENTIFIERS.lock().clone();
|
||||
|
||||
let container_padding = self.container_padding().unwrap_or(0);
|
||||
let containers = self.containers_mut();
|
||||
|
||||
for (i, container) in containers.iter_mut().enumerate() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::WindowManager;
|
||||
use crate::border_manager;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
@@ -51,16 +51,18 @@ pub fn send_notification(monitor_idx: usize, workspace_idx: usize) {
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
if cfg!(debug_assertions) {
|
||||
tracing::error!("restarting failed thread: {:?}", error)
|
||||
} else {
|
||||
tracing::error!("restarting failed thread: {}", error)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "komorebic-no-console"
|
||||
version = "0.1.35"
|
||||
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "komorebic"
|
||||
version = "0.1.35"
|
||||
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
|
||||
repository = "https://github.com/LGUG2Z/komorebi"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -17,31 +17,31 @@ use std::time::Duration;
|
||||
use clap::CommandFactory;
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::Result;
|
||||
use dirs::data_local_dir;
|
||||
use fs_tail::TailedFile;
|
||||
use komorebi_client::ApplicationSpecificConfiguration;
|
||||
use komorebi_client::Notification;
|
||||
use komorebi_client::resolve_home_path;
|
||||
use komorebi_client::send_message;
|
||||
use komorebi_client::send_query;
|
||||
use komorebi_client::ApplicationSpecificConfiguration;
|
||||
use komorebi_client::Notification;
|
||||
use lazy_static::lazy_static;
|
||||
use miette::NamedSource;
|
||||
use miette::Report;
|
||||
use miette::SourceOffset;
|
||||
use miette::SourceSpan;
|
||||
use paste::paste;
|
||||
use schemars::gen::SchemaSettings;
|
||||
use schemars::r#gen::SchemaSettings;
|
||||
use schemars::schema_for;
|
||||
use serde::Deserialize;
|
||||
use sysinfo::ProcessesToUpdate;
|
||||
use which::which;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||
|
||||
use komorebi_client::ApplicationConfigurationGenerator;
|
||||
use komorebi_client::ApplicationIdentifier;
|
||||
@@ -1269,6 +1269,8 @@ enum SubCommand {
|
||||
/// mode, for the currently focused workspace. If there was no override value set for the
|
||||
/// workspace previously it takes the opposite of the global value.
|
||||
ToggleWorkspaceFloatOverride,
|
||||
/// Toggle between the Tiling and Floating layers on the focused workspace
|
||||
ToggleWorkspaceLayer,
|
||||
/// Toggle window tiling on the focused workspace
|
||||
TogglePause,
|
||||
/// Toggle window tiling on the focused workspace
|
||||
@@ -1539,7 +1541,9 @@ fn main() -> Result<()> {
|
||||
let whkdrc = include_str!("../../docs/whkdrc.sample");
|
||||
std::fs::write(WHKD_CONFIG_DIR.join("whkdrc"), whkdrc)?;
|
||||
|
||||
println!("Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created");
|
||||
println!(
|
||||
"Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created"
|
||||
);
|
||||
println!("You can now run komorebic start --whkd --bar");
|
||||
}
|
||||
SubCommand::EnableAutostart(args) => {
|
||||
@@ -1585,8 +1589,12 @@ fn main() -> Result<()> {
|
||||
.env("TARGET_ARGS", arguments)
|
||||
.output()?;
|
||||
|
||||
println!("NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,");
|
||||
println!("you need to add this to System Properties > Environment Variables > User Variables");
|
||||
println!(
|
||||
"NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,"
|
||||
);
|
||||
println!(
|
||||
"you need to add this to System Properties > Environment Variables > User Variables"
|
||||
);
|
||||
println!("in order for the autostart command to work properly");
|
||||
}
|
||||
SubCommand::DisableAutostart => {
|
||||
@@ -1650,16 +1658,23 @@ fn main() -> Result<()> {
|
||||
println!("{:?}", Report::new(diagnostic));
|
||||
}
|
||||
|
||||
println!("Found komorebi.json; this file can be passed to the start command with the --config flag\n");
|
||||
println!(
|
||||
"Found komorebi.json; this file can be passed to the start command with the --config flag\n"
|
||||
);
|
||||
|
||||
if let Ok(config) = StaticConfig::read(&static_config) {
|
||||
match config.app_specific_configuration_path {
|
||||
None => {
|
||||
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
|
||||
println!(
|
||||
"Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"
|
||||
);
|
||||
}
|
||||
Some(path) => {
|
||||
if !Path::exists(Path::new(&path)) {
|
||||
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
|
||||
println!(
|
||||
"Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1677,9 +1692,14 @@ fn main() -> Result<()> {
|
||||
StaticConfig::end_of_life(&raw);
|
||||
|
||||
if config_whkd.exists() {
|
||||
println!("Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n", config_whkd.to_string_lossy());
|
||||
println!(
|
||||
"Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n",
|
||||
config_whkd.to_string_lossy()
|
||||
);
|
||||
} else {
|
||||
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
|
||||
println!(
|
||||
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
|
||||
);
|
||||
}
|
||||
} else if config_pwsh.exists() {
|
||||
println!("Found komorebi.ps1; this file will be autoloaded by komorebi\n");
|
||||
@@ -1689,13 +1709,17 @@ fn main() -> Result<()> {
|
||||
config_whkd.to_string_lossy()
|
||||
);
|
||||
} else {
|
||||
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
|
||||
println!(
|
||||
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
|
||||
);
|
||||
}
|
||||
} else if config_ahk.exists() {
|
||||
println!("Found komorebi.ahk; this file will be autoloaded by komorebi\n");
|
||||
} else {
|
||||
println!("No komorebi configuration found in {home_display}\n");
|
||||
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
|
||||
println!(
|
||||
"If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n"
|
||||
);
|
||||
}
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
@@ -1717,7 +1741,9 @@ fn main() -> Result<()> {
|
||||
{
|
||||
let trimmed = release.tag_name.trim_start_matches("v");
|
||||
if trimmed > version {
|
||||
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
|
||||
println!(
|
||||
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2032,38 +2058,45 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if arg.whkd && which("whkd").is_err() {
|
||||
bail!("could not find whkd, please make sure it is installed before using the --whkd flag");
|
||||
bail!(
|
||||
"could not find whkd, please make sure it is installed before using the --whkd flag"
|
||||
);
|
||||
}
|
||||
|
||||
if arg.masir && which("masir").is_err() {
|
||||
bail!("could not find masir, please make sure it is installed before using the --masir flag");
|
||||
bail!(
|
||||
"could not find masir, please make sure it is installed before using the --masir flag"
|
||||
);
|
||||
}
|
||||
|
||||
if arg.ahk && which(&ahk).is_err() {
|
||||
bail!("could not find autohotkey, please make sure it is installed before using the --ahk flag");
|
||||
bail!(
|
||||
"could not find autohotkey, please make sure it is installed before using the --ahk flag"
|
||||
);
|
||||
}
|
||||
|
||||
let mut buf: PathBuf;
|
||||
|
||||
// The komorebi.ps1 shim will only exist in the Path if installed by Scoop
|
||||
let exec = if let Ok(output) = Command::new("where.exe").arg("komorebi.ps1").output() {
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
match stdout.trim() {
|
||||
"" => None,
|
||||
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
|
||||
stdout if !stdout.contains("scoop") => None,
|
||||
stdout => {
|
||||
buf = PathBuf::from(stdout);
|
||||
buf.pop(); // %USERPROFILE%\scoop\shims
|
||||
buf.pop(); // %USERPROFILE%\scoop
|
||||
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
|
||||
Some(buf.to_str().ok_or_else(|| {
|
||||
anyhow!("cannot create a string from the scoop komorebi path")
|
||||
})?)
|
||||
let exec = match Command::new("where.exe").arg("komorebi.ps1").output() {
|
||||
Ok(output) => {
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
match stdout.trim() {
|
||||
"" => None,
|
||||
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
|
||||
stdout if !stdout.contains("scoop") => None,
|
||||
stdout => {
|
||||
buf = PathBuf::from(stdout);
|
||||
buf.pop(); // %USERPROFILE%\scoop\shims
|
||||
buf.pop(); // %USERPROFILE%\scoop
|
||||
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
|
||||
Some(buf.to_str().ok_or_else(|| {
|
||||
anyhow!("cannot create a string from the scoop komorebi path")
|
||||
})?)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut flags = vec![];
|
||||
@@ -2265,18 +2298,28 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
|
||||
|
||||
println!("\nThank you for using komorebi!\n");
|
||||
println!("# Commercial Use License");
|
||||
println!("* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work");
|
||||
println!(
|
||||
"* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work"
|
||||
);
|
||||
println!("\n# Personal Use Sponsorship");
|
||||
println!("* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference");
|
||||
println!(
|
||||
"* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference"
|
||||
);
|
||||
println!("* Leave a tip https://ko-fi.com/lgug2z - An alternative to GitHub Sponsors");
|
||||
println!("\n# Community");
|
||||
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!(
|
||||
"* Subscribe to https://youtube.com/@LGUG2Z - Development videos, feature previews and release overviews"
|
||||
);
|
||||
println!("* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem");
|
||||
println!(
|
||||
"* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem"
|
||||
);
|
||||
println!("\n# Documentation");
|
||||
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
|
||||
println!(
|
||||
"* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands"
|
||||
);
|
||||
|
||||
let bar_config = arg.config.map_or_else(
|
||||
|| {
|
||||
@@ -2323,7 +2366,9 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
|
||||
{
|
||||
let trimmed = release.tag_name.trim_start_matches("v");
|
||||
if trimmed > version {
|
||||
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
|
||||
println!(
|
||||
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2854,6 +2899,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
||||
SubCommand::ToggleWorkspaceFloatOverride => {
|
||||
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
|
||||
}
|
||||
SubCommand::ToggleWorkspaceLayer => {
|
||||
send_message(&SocketMessage::ToggleWorkspaceLayer)?;
|
||||
}
|
||||
SubCommand::WindowHidingBehaviour(arg) => {
|
||||
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?;
|
||||
}
|
||||
@@ -2945,7 +2993,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
||||
|
||||
file.write_all(formatted_content.as_bytes())?;
|
||||
|
||||
println!("File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration");
|
||||
println!(
|
||||
"File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration"
|
||||
);
|
||||
}
|
||||
SubCommand::FetchAppSpecificConfiguration => {
|
||||
let content = reqwest::blocking::get("https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json")?
|
||||
@@ -2961,10 +3011,12 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
||||
|
||||
file.write_all(content.as_bytes())?;
|
||||
|
||||
println!("Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n");
|
||||
println!(
|
||||
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
|
||||
output_file.display().to_string().replace("\\", "/")
|
||||
"Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n"
|
||||
);
|
||||
println!(
|
||||
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
|
||||
output_file.display().to_string().replace("\\", "/")
|
||||
);
|
||||
}
|
||||
SubCommand::ApplicationSpecificConfigurationSchema => {
|
||||
@@ -2989,8 +3041,8 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
||||
s.inline_subschemas = true;
|
||||
});
|
||||
|
||||
let gen = settings.into_generator();
|
||||
let socket_message = gen.into_root_schema_for::<StaticConfig>();
|
||||
let r#gen = settings.into_generator();
|
||||
let socket_message = r#gen.into_root_schema_for::<StaticConfig>();
|
||||
let schema = serde_json::to_string_pretty(&socket_message)?;
|
||||
println!("{schema}");
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@ nav:
|
||||
- cli/toggle-float-override.md
|
||||
- cli/toggle-workspace-window-container-behaviour.md
|
||||
- cli/toggle-workspace-float-override.md
|
||||
- cli/toggle-workspace-layer.md
|
||||
- cli/toggle-pause.md
|
||||
- cli/toggle-tiling.md
|
||||
- cli/toggle-float.md
|
||||
|
||||
249
schema.bar.json
249
schema.bar.json
@@ -504,6 +504,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_layer": {
|
||||
"description": "Configure the Workspace Layer widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Workspace Layer widget",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaces": {
|
||||
"description": "Configure the Workspaces widget",
|
||||
"type": "object",
|
||||
@@ -516,39 +529,73 @@
|
||||
"description": "Display format of the workspace",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"description": "Show all icons only",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
"AllIcons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"description": "Show both all icons and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
"AllIconsAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"description": "Show all icons and text for the selected element, and all icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
"AllIconsAndTextOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Existing"
|
||||
],
|
||||
"properties": {
|
||||
"Existing": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1811,6 +1858,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_layer": {
|
||||
"description": "Configure the Workspace Layer widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Workspace Layer widget",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaces": {
|
||||
"description": "Configure the Workspaces widget",
|
||||
"type": "object",
|
||||
@@ -1823,39 +1883,73 @@
|
||||
"description": "Display format of the workspace",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"description": "Show all icons only",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
"AllIcons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"description": "Show both all icons and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
"AllIconsAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"description": "Show all icons and text for the selected element, and all icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
"AllIconsAndTextOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Existing"
|
||||
],
|
||||
"properties": {
|
||||
"Existing": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3051,6 +3145,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_layer": {
|
||||
"description": "Configure the Workspace Layer widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Workspace Layer widget",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaces": {
|
||||
"description": "Configure the Workspaces widget",
|
||||
"type": "object",
|
||||
@@ -3063,39 +3170,73 @@
|
||||
"description": "Display format of the workspace",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"description": "Show all icons only",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
"AllIcons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"description": "Show both all icons and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
"AllIconsAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"description": "Show all icons and text for the selected element, and all icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
"AllIconsAndTextOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Existing"
|
||||
],
|
||||
"properties": {
|
||||
"Existing": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
12
schema.json
12
schema.json
@@ -1075,6 +1075,11 @@
|
||||
"workspaces"
|
||||
],
|
||||
"properties": {
|
||||
"container_padding": {
|
||||
"description": "Container padding (default: global)",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"window_based_work_area_offset": {
|
||||
"description": "Window based work area offset (default: None)",
|
||||
"type": "object",
|
||||
@@ -1144,6 +1149,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_padding": {
|
||||
"description": "Workspace padding (default: global)",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"workspaces": {
|
||||
"description": "Workspace configurations",
|
||||
"type": "array",
|
||||
@@ -1346,7 +1356,7 @@
|
||||
}
|
||||
},
|
||||
"workspace_padding": {
|
||||
"description": "Container padding (default: global)",
|
||||
"description": "Workspace padding (default: global)",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user