Compare commits

..

8 Commits

Author SHA1 Message Date
LGUG2Z
261daedcf5 fix(borders): send notif on ignored hwnd events 2024-10-02 08:59:38 -07:00
LGUG2Z
bd24940173 fix(borders): update on floating win drag 2024-10-01 09:21:01 -07:00
LGUG2Z
9034122447 fix(borders): no redraws on floating win title change
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
105957b5fa refactor(wm): float_rules > ignore_rules w/ compat
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
bf5b675498 feat(wm): allow ws moves of floating apps
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
8c8d1175ba feat(wm): separate floating and ignored apps
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
bf2b73837b fix(windows): conditional compilation for 32-bit
This commit ensures that komorebi will compile when targeting 32-bit
architectures, namely `stable-i686-pc-windows-msvc`.

Thanks to @kennykerr for pointing out that Get/SetWindowLongPtrA/W calls
don't actually exist on 32-bit builds of Windows and are aliased instead
to Get/SetWindowLongA/W which take i32 args instead of isize args:
https://github.com/microsoft/windows-rs/issues/3304
2024-10-01 08:54:18 -07:00
LGUG2Z
04cde3f757 feat(wm): add all matching strats for ws rules
This commit ensures that the full range of matching strategies for both
Simple and Composite matching rules will be respected for both initial
and persistent workspace rules.

The generate-static-config command will no longer attempt to populate
workspace rules, and will likely slowly be deprecated as the
overwhelming majority have users have already migrated to the static
configuration file format.
2024-09-29 18:10:50 -07:00
38 changed files with 1327 additions and 3065 deletions

52
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,52 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]: Short descriptive title"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See bug
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots and Videos**
Add screenshots and videos to help explain your problem.
**Operating System**
Provide the output of `systeminfo | grep "^OS Name\|^OS Version"`
For example:
```
OS Name: Microsoft Windows 11 Pro
OS Version: 10.0.22000 N/A Build 22000
```
**`komorebic check` Output**
Provide the output of `komorebic check`
For example:
```
No KOMOREBI_CONFIG_HOME detected, defaulting to C:\Users\LGUG2Z
Looking for configuration files in C:\Users\LGUG2Z
No komorebi configuration found in C:\Users\LGUG2Z
If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration
```
**Additional context**
Add any other context about the problem here.
In particular, if you have any other AHK scripts or software running that handle any aspect of window management or manipulation

View File

@@ -1,55 +0,0 @@
name: Bug report
description: File a bug report
labels: [ bug ]
body:
- type: textarea
validations:
required: true
attributes:
label: Summary
description: >
Please provide a short summary of the bug, along with any information
you feel is relevant to replicating the bug.
You may include screenshots and videos in this section.
- type: textarea
validations:
required: true
attributes:
label: Version Information
description: >
Please provide information about the versions of Windows and komorebi
running on your machine.
Do not submit a bug if you are not using an official version of Windows
such as AtlasOS; only official versions of Windows are supported.
```
systeminfo | findstr /B /C:"OS Name" /B /C:"OS Version"
```
```
komorebic --version
```
- type: textarea
validations:
required: true
attributes:
label: Komorebi Configuration
description: >
Please provide your configuration file (komorebi.json or komorebi.bar.json)
render: json
- type: textarea
validations:
required: true
attributes:
label: Hotkey Configuration
description: >
Please provide your whkdrc or komorebi.ahk hotkey configuration file
- type: textarea
validations:
required: true
attributes:
label: Output of komorebic check
description: >
Please provide the output of `komorebic check`

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Komorebi Documentation
url: https://lgug2z.github.io/komorebi/
about: Please search the documentation website before opening an issue

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEAT]: Short descriptive title"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,18 +0,0 @@
name: Feature request
description: Suggest an improvement
labels: [ enhancement ]
body:
- type: textarea
validations:
required: true
attributes:
label: Suggestion
description: >
Please share your suggestion here. Be sure to include all necessary context.
- type: textarea
validations:
required: true
attributes:
label: Alternatives Considered
description: >
Please share share alternatives you have considered here.

1384
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,8 @@ chrono = "0.4"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
color-eyre = "0.6"
eframe = "0.29"
egui_extras = "0.29"
eframe = "0.28"
egui_extras = "0.28"
dirs = "5"
dunce = "1"
hotwatch = "0.5"
@@ -63,6 +63,3 @@ features = [
"Media",
"Media_Control"
]
[profile.release]
lto = true

View File

@@ -82,7 +82,7 @@ A [detailed installation and quickstart
guide](https://lgug2z.github.io/komorebi/installation.html) is available which shows how to get started
using `scoop`, `winget` or building from source.
[![Watch the quickstart walkthrough video](https://img.youtube.com/vi/MMZUAtHbTYY/hqdefault.jpg)](https://www.youtube.com/watch?v=MMZUAtHbTYY)
[![Watch the quickstart walkthrough video](https://img.youtube.com/vi/H9-_c1egQ4g/hqdefault.jpg)](https://www.youtube.com/watch?v=H9-_c1egQ4g)
# Comparison With Fancy Zones

View File

@@ -16,15 +16,14 @@ crossbeam-channel = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
eframe = { workspace = true }
egui-phosphor = "0.7"
egui-phosphor = "0.6.0"
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
netdev = "0.31"
num = "0.4"
num-derive = "0.4"
num-traits = "0.2"
random_word = { version = "0.4", features = ["en"] }
num = "0.4.3"
num-derive = "0.4.2"
num-traits = "0.2.19"
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View File

@@ -1,17 +1,10 @@
use crate::config::KomobarConfig;
use crate::config::KomobarTheme;
use crate::config::Position;
use crate::config::PositionConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crate::BAR_HEIGHT;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_LEFT;
use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crossbeam_channel::Receiver;
use eframe::egui::Align;
use eframe::egui::CentralPanel;
@@ -26,6 +19,8 @@ use eframe::egui::Layout;
use eframe::egui::Margin;
use eframe::egui::Style;
use eframe::egui::TextStyle;
use eframe::egui::Vec2;
use eframe::egui::ViewportCommand;
use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder;
use komorebi_client::KomorebiTheme;
@@ -149,43 +144,14 @@ impl Komobar {
Self::add_custom_font(ctx, font_family);
}
let position = config.position.clone().unwrap_or(PositionConfig {
start: Some(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
}),
end: Some(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
}),
});
if let Some(hwnd) = process_hwnd() {
let start = position.start.unwrap_or(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
});
let end = position.end.unwrap_or(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
});
let rect = komorebi_client::Rect {
left: start.x as i32,
top: start.y as i32,
right: end.x as i32,
bottom: end.y as i32,
};
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&rect, false) {
Ok(_) => {
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{}", error.to_string())
if let Some(viewport) = &config.viewport {
if let Some(inner_size) = viewport.inner_size {
let mut vec2 = Vec2::new(inner_size.x, inner_size.y * 2.0);
if self.scale_factor != 1.0 {
vec2 = Vec2::new(inner_size.x / self.scale_factor, inner_size.y * 2.0);
}
ctx.send_viewport_cmd(ViewportCommand::InnerSize(vec2));
}
}
@@ -324,8 +290,6 @@ impl Komobar {
scale_factor: cc.egui_ctx.native_pixels_per_point().unwrap_or(1.0),
};
komobar.apply_config(&cc.egui_ctx, &config, None);
// needs a double apply the first time for some reason
komobar.apply_config(&cc.egui_ctx, &config, None);
komobar

View File

@@ -1,4 +1,3 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
@@ -24,8 +23,6 @@ pub struct BatteryConfig {
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<BatteryConfig> for Battery {
@@ -33,7 +30,6 @@ impl From<BatteryConfig> for Battery {
let manager = Manager::new().unwrap();
let mut last_state = String::new();
let mut state = None;
let prefix = value.label_prefix.unwrap_or(LabelPrefix::Icon);
if let Ok(mut batteries) = manager.batteries() {
if let Some(Ok(first)) = batteries.nth(0) {
@@ -44,12 +40,7 @@ impl From<BatteryConfig> for Battery {
_ => {}
}
last_state = match prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage:.0}%")
}
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"),
}
last_state = format!("{percentage}%");
}
}
@@ -58,7 +49,6 @@ impl From<BatteryConfig> for Battery {
manager,
last_state,
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: prefix,
state: state.unwrap_or(BatteryState::Discharging),
last_updated: Instant::now(),
}
@@ -75,7 +65,6 @@ pub struct Battery {
manager: Manager,
pub state: BatteryState,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
last_state: String,
last_updated: Instant,
}
@@ -97,12 +86,7 @@ impl Battery {
_ => {}
}
output = match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("BAT: {percentage:.0}%")
}
LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"),
}
output = format!("{percentage:.0}%");
}
}
@@ -132,10 +116,7 @@ impl BarWidget for Battery {
.unwrap_or_else(FontId::default);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(),
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
emoji.to_string(),
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,

View File

@@ -7,15 +7,13 @@ use komorebi_client::Rect;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.bar.json` configuration file reference for `v0.1.30`
pub struct KomobarConfig {
/// Bar positioning options
#[serde(alias = "viewport")]
pub position: Option<PositionConfig>,
/// Viewport options (see: https://docs.rs/egui/latest/egui/viewport/struct.ViewportBuilder.html)
pub viewport: Option<ViewportConfig>,
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/struct.Frame.html)
pub frame: Option<FrameConfig>,
/// Monitor options
@@ -34,43 +32,12 @@ pub struct KomobarConfig {
pub right_widgets: Vec<WidgetConfig>,
}
impl KomobarConfig {
pub fn aliases(raw: &str) {
let mut map = HashMap::new();
map.insert("position", ["viewport"]);
map.insert("end", ["inner_frame"]);
let mut display = false;
for aliases in map.values() {
for a in aliases {
if raw.contains(a) {
display = true;
}
}
}
if display {
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) {
println!(r#""{alias}" is now "{canonical}""#);
}
}
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct PositionConfig {
pub struct ViewportConfig {
/// The desired starting position of the bar (0,0 = top left of the screen)
#[serde(alias = "position")]
pub start: Option<Position>,
pub position: Option<Position>,
/// The desired size of the bar from the starting position (usually monitor width x desired height)
#[serde(alias = "inner_size")]
pub end: Option<Position>,
pub inner_size: Option<Position>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -164,15 +131,3 @@ impl From<KomorebiTheme> for KomobarTheme {
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum LabelPrefix {
/// Show no prefix
None,
/// Show an icon
Icon,
/// Show text
Text,
/// Show an icon and text
IconAndText,
}

View File

@@ -1,120 +0,0 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
use eframe::egui::Label;
use eframe::egui::Sense;
use eframe::egui::TextFormat;
use eframe::egui::TextStyle;
use eframe::egui::Ui;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
use sysinfo::RefreshKind;
use sysinfo::System;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct CpuConfig {
/// Enable the Cpu widget
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<CpuConfig> for Cpu {
fn from(value: CpuConfig) -> Self {
let mut system =
System::new_with_specifics(RefreshKind::default().without_memory().without_processes());
system.refresh_cpu_usage();
Self {
enable: value.enable,
system,
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
last_updated: Instant::now(),
}
}
}
pub struct Cpu {
pub enable: bool,
system: System,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
last_updated: Instant,
}
impl Cpu {
fn output(&mut self) -> String {
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.system.refresh_cpu_usage();
self.last_updated = now;
}
let used = self.system.global_cpu_usage();
match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {:.0}%", used),
LabelPrefix::None | LabelPrefix::Icon => format!("{:.0}%", used),
}
}
}
impl BarWidget for Cpu {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let output = self.output();
if !output.is_empty() {
let font_id = ctx
.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap_or_else(FontId::default);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::CPU.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,
);
layout_job.append(
&output,
10.0,
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
eprintln!("{}", error)
}
}
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
@@ -20,8 +19,6 @@ pub struct DateConfig {
pub enable: bool,
/// Set the Date format
pub format: DateFormat,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<DateConfig> for Date {
@@ -29,7 +26,6 @@ impl From<DateConfig> for Date {
Self {
enable: value.enable,
format: value.format,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
}
}
}
@@ -74,7 +70,6 @@ impl DateFormat {
pub struct Date {
pub enable: bool,
pub format: DateFormat,
label_prefix: LabelPrefix,
}
impl Date {
@@ -88,7 +83,7 @@ impl Date {
impl BarWidget for Date {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let mut output = self.output();
let output = self.output();
if !output.is_empty() {
let font_id = ctx
.style()
@@ -98,21 +93,12 @@ impl BarWidget for Date {
.unwrap_or_else(FontId::default);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::CALENDAR_DOTS.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
egui_phosphor::regular::CALENDAR_DOTS.to_string(),
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,
);
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
output.insert_str(0, "DATE: ");
}
layout_job.append(
&output,
10.0,

View File

@@ -5,7 +5,6 @@ use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use crate::WIDGET_SPACING;
use crossbeam_channel::Receiver;
use crossbeam_channel::TryRecvError;
use eframe::egui::text::LayoutJob;
use eframe::egui::Color32;
use eframe::egui::ColorImage;
@@ -84,6 +83,14 @@ impl From<&KomorebiConfig> for Komorebi {
if let Some(configuration_switcher) = &value.configuration_switcher {
let mut configuration_switcher = configuration_switcher.clone();
for (_, location) in configuration_switcher.configurations.iter_mut() {
if let Ok(expanded) = std::env::var("KOMOREBI_CONFIG_HOME") {
*location = location.replace("$Env:KOMOREBI_CONFIG_HOME", &expanded);
}
if let Ok(expanded) = std::env::var("USERPROFILE") {
*location = location.replace("$Env:USERPROFILE", &expanded);
}
*location = dunce::simplified(&PathBuf::from(location.clone()))
.to_string_lossy()
.to_string();
@@ -438,105 +445,93 @@ impl KomorebiNotificationState {
rx_gui: Receiver<komorebi_client::Notification>,
bg_color: Rc<RefCell<Color32>>,
) {
match rx_gui.try_recv() {
Err(error) => match error {
TryRecvError::Empty => {}
TryRecvError::Disconnected => {
tracing::error!(
"failed to receive komorebi notification on gui thread: {error}"
);
}
},
Ok(notification) => {
if let NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(path)) =
notification.event
{
if let Ok(config) = komorebi_client::StaticConfig::read(&path) {
if let Some(theme) = config.theme {
apply_theme(ctx, KomobarTheme::from(theme), bg_color);
tracing::info!("applied theme from updated komorebi.json");
}
if let Ok(notification) = rx_gui.try_recv() {
if let NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(path)) =
notification.event
{
if let Ok(config) = komorebi_client::StaticConfig::read(&path) {
if let Some(theme) = config.theme {
apply_theme(ctx, KomobarTheme::from(theme), bg_color);
tracing::info!("applied theme from updated komorebi.json");
}
}
}
self.mouse_follows_focus = notification.state.mouse_follows_focus;
self.mouse_follows_focus = notification.state.mouse_follows_focus;
let monitor = &notification.state.monitors.elements()[monitor_index];
self.work_area_offset =
notification.state.monitors.elements()[monitor_index].work_area_offset();
let monitor = &notification.state.monitors.elements()[monitor_index];
self.work_area_offset =
notification.state.monitors.elements()[monitor_index].work_area_offset();
let focused_workspace_idx = monitor.focused_workspace_idx();
let focused_workspace_idx = monitor.focused_workspace_idx();
let mut workspaces = vec![];
self.selected_workspace = monitor.workspaces()[focused_workspace_idx]
.name()
.to_owned()
.unwrap_or_else(|| format!("{}", focused_workspace_idx + 1));
let mut workspaces = vec![];
self.selected_workspace = monitor.workspaces()[focused_workspace_idx]
.name()
.to_owned()
.unwrap_or_else(|| format!("{}", focused_workspace_idx + 1));
for (i, ws) in monitor.workspaces().iter().enumerate() {
let should_add = if self.hide_empty_workspaces {
focused_workspace_idx == i || !ws.containers().is_empty()
} else {
true
};
if should_add {
workspaces
.push(ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)));
}
}
self.workspaces = workspaces;
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
komorebi_client::Layout::Default(layout) => KomorebiLayout::Default(*layout),
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
for (i, ws) in monitor.workspaces().iter().enumerate() {
let should_add = if self.hide_empty_workspaces {
focused_workspace_idx == i || !ws.containers().is_empty()
} else {
true
};
if !*monitor.workspaces()[focused_workspace_idx].tile() {
self.layout = KomorebiLayout::Floating;
if should_add {
workspaces.push(ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)));
}
}
if notification.state.is_paused {
self.layout = KomorebiLayout::Paused;
}
self.workspaces = workspaces;
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
komorebi_client::Layout::Default(layout) => KomorebiLayout::Default(*layout),
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
};
if let Some(container) =
monitor.workspaces()[focused_workspace_idx].monocle_container()
{
self.focused_container_information = (
container
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
container
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
container.focused_window_idx(),
);
} else if let Some(container) =
monitor.workspaces()[focused_workspace_idx].focused_container()
{
self.focused_container_information = (
container
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
container
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
container.focused_window_idx(),
);
} else {
self.focused_container_information.0.clear();
self.focused_container_information.1.clear();
self.focused_container_information.2 = 0;
}
if !*monitor.workspaces()[focused_workspace_idx].tile() {
self.layout = KomorebiLayout::Floating;
}
if notification.state.is_paused {
self.layout = KomorebiLayout::Paused;
}
if let Some(container) = monitor.workspaces()[focused_workspace_idx].monocle_container()
{
self.focused_container_information = (
container
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
container
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
container.focused_window_idx(),
);
} else if let Some(container) =
monitor.workspaces()[focused_workspace_idx].focused_container()
{
self.focused_container_information = (
container
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
container
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
container.focused_window_idx(),
);
} else {
self.focused_container_information.0.clear();
self.focused_container_information.1.clear();
self.focused_container_information.2 = 0;
}
}
}

View File

@@ -1,7 +1,6 @@
mod bar;
mod battery;
mod config;
mod cpu;
mod date;
mod komorebi;
mod media;
@@ -15,40 +14,23 @@ mod widget;
use crate::bar::Komobar;
use crate::config::KomobarConfig;
use crate::config::Position;
use crate::config::PositionConfig;
use clap::Parser;
use eframe::egui::ViewportBuilder;
use font_loader::system_fonts;
use hotwatch::EventKind;
use hotwatch::Hotwatch;
use komorebi_client::SocketMessage;
use komorebi_client::SubscribeOptions;
use schemars::gen::SchemaSettings;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
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::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
pub static WIDGET_SPACING: f32 = 10.0;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_RIGHT: AtomicI32 = AtomicI32::new(0);
pub static BAR_HEIGHT: f32 = 50.0;
#[derive(Parser)]
#[clap(author, about, version)]
@@ -65,46 +47,9 @@ struct Opts {
/// Write an example komorebi.bar.json to disk
#[clap(long)]
quickstart: bool,
/// Print a list of aliases that can be renamed to canonical variants
#[clap(long)]
#[clap(hide = true)]
aliases: bool,
}
extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
unsafe {
let mut process_id = 0;
GetWindowThreadProcessId(hwnd, Some(&mut process_id));
if process_id == GetCurrentProcessId() {
*(lparam.0 as *mut HWND) = hwnd;
BOOL::from(false) // Stop enumeration
} else {
BOOL::from(true) // Continue enumeration
}
}
}
fn process_hwnd() -> Option<isize> {
unsafe {
let mut hwnd = HWND::default();
let _ = EnumThreadWindows(
GetCurrentThreadId(),
Some(enum_window),
LPARAM(&mut hwnd as *mut HWND as isize),
);
if hwnd.0 as isize == 0 {
None
} else {
Some(hwnd.0 as isize)
}
}
}
fn main() -> color_eyre::Result<()> {
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }?;
let opts: Opts = Opts::parse();
if opts.schema {
@@ -183,7 +128,7 @@ fn main() -> color_eyre::Result<()> {
Option::from,
);
let mut config = match config_path {
let config = match config_path {
None => {
let komorebi_bar_json =
include_str!("../../docs/komorebi.bar.example.json").to_string();
@@ -197,12 +142,10 @@ fn main() -> color_eyre::Result<()> {
KomobarConfig::read(&default_config_path)?
}
Some(ref config) => {
if !opts.aliases {
tracing::info!(
"found configuration file: {}",
config.as_path().to_string_lossy()
);
}
tracing::info!(
"found configuration file: {}",
config.as_path().to_string_lossy()
);
KomobarConfig::read(config)?
}
@@ -210,64 +153,33 @@ fn main() -> color_eyre::Result<()> {
let config_path = config_path.unwrap_or(default_config_path);
if opts.aliases {
KomobarConfig::aliases(&std::fs::read_to_string(&config_path)?);
std::process::exit(0);
}
let state = serde_json::from_str::<komorebi_client::State>(&komorebi_client::send_query(
&SocketMessage::State,
)?)?;
MONITOR_RIGHT.store(
state.monitors.elements()[config.monitor.index].size().right,
Ordering::SeqCst,
);
MONITOR_TOP.store(
state.monitors.elements()[config.monitor.index].size().top,
Ordering::SeqCst,
);
MONITOR_TOP.store(
state.monitors.elements()[config.monitor.index].size().left,
Ordering::SeqCst,
);
match config.position {
None => {
config.position = Some(PositionConfig {
start: Some(Position {
x: state.monitors.elements()[config.monitor.index].size().left as f32,
y: state.monitors.elements()[config.monitor.index].size().top as f32,
}),
end: Some(Position {
x: state.monitors.elements()[config.monitor.index].size().right as f32,
y: 50.0,
}),
})
}
Some(ref mut position) => {
if position.start.is_none() {
position.start = Some(Position {
x: state.monitors.elements()[config.monitor.index].size().left as f32,
y: state.monitors.elements()[config.monitor.index].size().top as f32,
});
}
if position.end.is_none() {
position.end = Some(Position {
x: state.monitors.elements()[config.monitor.index].size().right as f32,
y: 50.0,
})
}
}
}
let viewport_builder = ViewportBuilder::default()
let mut viewport_builder = ViewportBuilder::default()
.with_decorations(false)
// .with_transparent(config.transparent)
.with_taskbar(false);
.with_taskbar(false)
.with_position(Position { x: 0.0, y: 0.0 })
.with_inner_size({
Position {
x: state.monitors.elements()[config.monitor.index].size().right as f32,
y: 20.0,
}
});
if let Some(viewport) = &config.viewport {
if let Some(position) = &viewport.position {
let b = viewport_builder.clone();
viewport_builder = b.with_position(*position);
}
if let Some(inner_size) = &viewport.inner_size {
let b = viewport_builder.clone();
viewport_builder = b.with_inner_size(*inner_size);
}
}
let native_options = eframe::NativeOptions {
viewport: viewport_builder,
@@ -327,14 +239,10 @@ 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 listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
filter_state_changes: true,
})
let listener = komorebi_client::subscribe("komorebi-bar")
.expect("could not subscribe to komorebi notifications");
tracing::info!("subscribed to komorebi notifications: \"{}\"", subscriber_name);
tracing::info!("subscribed to komorebi notifications: \"komorebi-bar\"");
for client in listener.incoming() {
match client {
@@ -348,7 +256,9 @@ fn main() -> color_eyre::Result<()> {
// keep trying to reconnect to komorebi
while komorebi_client::send_message(
&SocketMessage::AddSubscriberSocket(subscriber_name.clone()),
&SocketMessage::AddSubscriberSocket(String::from(
"komorebi-bar",
)),
)
.is_err()
{
@@ -373,21 +283,18 @@ fn main() -> color_eyre::Result<()> {
match String::from_utf8(buffer) {
Ok(notification_string) => {
match serde_json::from_str::<komorebi_client::Notification>(
&notification_string,
) {
Ok(notification) => {
tracing::debug!("received notification from komorebi");
if let Ok(notification) =
serde_json::from_str::<komorebi_client::Notification>(
&notification_string,
)
{
tracing::debug!("received notification from komorebi");
if let Err(error) = tx_gui.send(notification) {
tracing::error!("could not send komorebi notification update to gui thread: {error}")
}
if let Err(error) = tx_gui.send(notification) {
tracing::error!("could not send komorebi notification update to gui: {error}")
}
ctx_komorebi.request_repaint();
}
Err(error) => {
tracing::error!("could not deserialize komorebi notification: {error}");
}
ctx_komorebi.request_repaint();
}
}
Err(error) => {

View File

@@ -1,4 +1,3 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
@@ -24,8 +23,6 @@ pub struct MemoryConfig {
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<MemoryConfig> for Memory {
@@ -39,7 +36,6 @@ impl From<MemoryConfig> for Memory {
enable: value.enable,
system,
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
last_updated: Instant::now(),
}
}
@@ -49,7 +45,6 @@ pub struct Memory {
pub enable: bool,
system: System,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
last_updated: Instant,
}
@@ -63,12 +58,7 @@ impl Memory {
let used = self.system.used_memory();
let total = self.system.total_memory();
match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("RAM: {}%", (used * 100) / total)
}
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total),
}
format!("RAM: {}%", (used * 100) / total)
}
}
@@ -85,12 +75,7 @@ impl BarWidget for Memory {
.unwrap_or_else(FontId::default);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::MEMORY.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
egui_phosphor::regular::MEMORY.to_string(),
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,

View File

@@ -1,4 +1,3 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
@@ -31,8 +30,6 @@ pub struct NetworkConfig {
pub network_activity_fill_characters: Option<usize>,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<NetworkConfig> for Network {
@@ -45,8 +42,6 @@ impl From<NetworkConfig> for Network {
let mut default_interface = String::new();
let prefix = value.label_prefix.unwrap_or(LabelPrefix::Icon);
if let Ok(interface) = netdev::get_default_interface() {
if let Some(friendly_name) = interface.friendly_name {
default_interface.clone_from(&friendly_name);
@@ -55,32 +50,13 @@ impl From<NetworkConfig> for Network {
networks_total_data_transmitted.refresh();
for (interface_name, data) in &networks_total_data_transmitted {
if friendly_name.eq(interface_name) {
last_state_data.push(match prefix {
LabelPrefix::None => format!(
"{} | {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Icon => format!(
"{} {} | {} {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Text => format!(
"\u{2211}DOWN: {} | \u{2211}UP: {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::IconAndText => format!(
"{} \u{2211}DOWN: {} | {} \u{2211}UP: {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
})
last_state_data.push(format!(
"{} {} / {} {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
))
}
}
}
@@ -89,40 +65,14 @@ impl From<NetworkConfig> for Network {
networks_network_activity.refresh();
for (interface_name, data) in &networks_network_activity {
if friendly_name.eq(interface_name) {
last_state_transmitted.push(match prefix {
LabelPrefix::None => format!(
"{: >width$}/s | {: >width$}/s",
to_pretty_bytes(data.received(), 1),
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
LabelPrefix::Icon => format!(
"{} {: >width$}/s | {} {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
LabelPrefix::Text => format!(
"DOWN: {: >width$}/s | UP: {: >width$}/s",
to_pretty_bytes(data.received(), 1),
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
LabelPrefix::IconAndText => format!(
"{} DOWN: {: >width$}/s | {} UP: {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
})
last_state_transmitted.push(format!(
"{} {: >width$}/s {} {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.transmitted(), 1),
width = value.network_activity_fill_characters.unwrap_or_default(),
))
}
}
}
@@ -135,7 +85,6 @@ impl From<NetworkConfig> for Network {
networks_network_activity,
default_interface,
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: prefix,
show_total_data_transmitted: value.show_total_data_transmitted,
show_network_activity: value.show_network_activity,
network_activity_fill_characters: value
@@ -156,7 +105,6 @@ pub struct Network {
networks_total_data_transmitted: Networks,
networks_network_activity: Networks,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
default_interface: String,
last_state_total_data_transmitted: Vec<String>,
last_state_network_activity: Vec<String>,
@@ -190,62 +138,14 @@ impl Network {
self.networks_network_activity.refresh();
for (interface_name, data) in &self.networks_network_activity {
if friendly_name.eq(interface_name) {
outputs.push(match self.label_prefix {
LabelPrefix::None => format!(
"{: >width$}/s | {: >width$}/s",
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
),
LabelPrefix::Icon => format!(
"{} {: >width$}/s | {} {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
),
LabelPrefix::Text => format!(
"DOWN: {: >width$}/s | UP: {: >width$}/s",
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
),
LabelPrefix::IconAndText => {
format!(
"{} DOWN: {: >width$}/s | {} UP: {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
)
}
})
outputs.push(format!(
"{} {: >width$}/s {} {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.received(), self.data_refresh_interval),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.transmitted(), self.data_refresh_interval),
width = self.network_activity_fill_characters,
))
}
}
}
@@ -276,32 +176,13 @@ impl Network {
for (interface_name, data) in &self.networks_total_data_transmitted {
if friendly_name.eq(interface_name) {
outputs.push(match self.label_prefix {
LabelPrefix::None => format!(
"{} | {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Icon => format!(
"{} {} | {} {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Text => format!(
"\u{2211}DOWN: {} | \u{2211}UP: {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::IconAndText => format!(
"{} \u{2211}DOWN: {} | {} \u{2211}UP: {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
})
outputs.push(format!(
"{} {} / {} {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
))
}
}
}
@@ -346,21 +227,12 @@ impl BarWidget for Network {
.unwrap_or_else(FontId::default);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::WIFI_HIGH.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
egui_phosphor::regular::WIFI_HIGH.to_string(),
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,
);
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
self.default_interface.insert_str(0, "NET: ");
}
layout_job.append(
&self.default_interface,
10.0,

View File

@@ -1,4 +1,3 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
@@ -23,8 +22,6 @@ pub struct StorageConfig {
pub enable: bool,
/// Data refresh interval (default: 10 seconds)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<StorageConfig> for Storage {
@@ -33,7 +30,6 @@ impl From<StorageConfig> for Storage {
enable: value.enable,
disks: Disks::new_with_refreshed_list(),
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
last_updated: Instant::now(),
}
}
@@ -43,7 +39,6 @@ pub struct Storage {
pub enable: bool,
disks: Disks,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
last_updated: Instant,
}
@@ -63,12 +58,11 @@ impl Storage {
let available = disk.available_space();
let used = total - available;
disks.push(match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => {
format!("{} {}%", mount.to_string_lossy(), (used * 100) / total)
}
LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total),
})
disks.push(format!(
"{} {}%",
mount.to_string_lossy(),
(used * 100) / total
))
}
disks.sort();
@@ -90,12 +84,7 @@ impl BarWidget for Storage {
for output in self.output() {
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::HARD_DRIVES.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
egui_phosphor::regular::HARD_DRIVES.to_string(),
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,

View File

@@ -1,4 +1,3 @@
use crate::config::LabelPrefix;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
@@ -19,8 +18,6 @@ pub struct TimeConfig {
pub enable: bool,
/// Set the Time format
pub format: TimeFormat,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<TimeConfig> for Time {
@@ -28,7 +25,6 @@ impl From<TimeConfig> for Time {
Self {
enable: value.enable,
format: value.format,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
}
}
}
@@ -65,7 +61,6 @@ impl TimeFormat {
pub struct Time {
pub enable: bool,
pub format: TimeFormat,
label_prefix: LabelPrefix,
}
impl Time {
@@ -79,7 +74,7 @@ impl Time {
impl BarWidget for Time {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let mut output = self.output();
let output = self.output();
if !output.is_empty() {
let font_id = ctx
.style()
@@ -89,21 +84,12 @@ impl BarWidget for Time {
.unwrap_or_else(FontId::default);
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::CLOCK.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
egui_phosphor::regular::CLOCK.to_string(),
font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,
);
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
output.insert_str(0, "TIME: ");
}
layout_job.append(
&output,
10.0,

View File

@@ -1,7 +1,5 @@
use crate::battery::Battery;
use crate::battery::BatteryConfig;
use crate::cpu::Cpu;
use crate::cpu::CpuConfig;
use crate::date::Date;
use crate::date::DateConfig;
use crate::komorebi::Komorebi;
@@ -29,7 +27,6 @@ pub trait BarWidget {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum WidgetConfig {
Battery(BatteryConfig),
Cpu(CpuConfig),
Date(DateConfig),
Komorebi(KomorebiConfig),
Media(MediaConfig),
@@ -43,7 +40,6 @@ impl WidgetConfig {
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
match self {
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)),
WidgetConfig::Media(config) => Box::new(Media::from(*config)),

View File

@@ -44,7 +44,6 @@ pub use komorebi::RuleDebug;
pub use komorebi::StackbarConfig;
pub use komorebi::State;
pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig;
use komorebi::DATA_DIR;
@@ -97,29 +96,3 @@ pub fn subscribe(name: &str) -> std::io::Result<UnixListener> {
Ok(listener)
}
pub fn subscribe_with_options(
name: &str,
options: SubscribeOptions,
) -> std::io::Result<UnixListener> {
let socket = DATA_DIR.join(name);
match std::fs::remove_file(&socket) {
Ok(()) => {}
Err(error) => match error.kind() {
std::io::ErrorKind::NotFound => {}
_ => {
return Err(error);
}
},
};
let listener = UnixListener::bind(&socket)?;
send_message(&SocketMessage::AddSubscriberSocketWithOptions(
name.to_string(),
options,
))?;
Ok(listener)
}

View File

@@ -27,6 +27,7 @@ fn main() {
viewport: ViewportBuilder::default()
.with_always_on_top()
.with_inner_size([320.0, 500.0]),
follow_system_theme: true,
..Default::default()
};
@@ -233,8 +234,7 @@ extern "system" fn enum_window(
fn json_view_ui(ui: &mut egui::Ui, code: &str) {
let language = "json";
let theme =
egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), &ui.ctx().style());
let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);
}

View File

@@ -4,9 +4,8 @@ version = "0.1.30"
edition = "2021"
[dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "57c38257cb0c6434321320d3746049bd58c34674" }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "f579847bf2f552b144361d5a78ed8cf360b55cbb" }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui28"] }
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "a2c48f45782c5604bf5482d3873021a9fe45ea1a" }
catppuccin-egui = { version = "5.1", default-features = false, features = ["egui28"] }
eframe = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }

View File

@@ -124,39 +124,35 @@ pub enum CatppuccinValue {
Crust,
}
pub fn color32_compat(rgba: [u8; 4]) -> Color32 {
Color32::from_rgba_unmultiplied(rgba[0], rgba[1], rgba[2], rgba[3])
}
impl CatppuccinValue {
pub fn color32(&self, theme: catppuccin_egui::Theme) -> Color32 {
match self {
CatppuccinValue::Rosewater => color32_compat(theme.rosewater.to_srgba_unmultiplied()),
CatppuccinValue::Flamingo => color32_compat(theme.flamingo.to_srgba_unmultiplied()),
CatppuccinValue::Pink => color32_compat(theme.pink.to_srgba_unmultiplied()),
CatppuccinValue::Mauve => color32_compat(theme.mauve.to_srgba_unmultiplied()),
CatppuccinValue::Red => color32_compat(theme.red.to_srgba_unmultiplied()),
CatppuccinValue::Maroon => color32_compat(theme.maroon.to_srgba_unmultiplied()),
CatppuccinValue::Peach => color32_compat(theme.peach.to_srgba_unmultiplied()),
CatppuccinValue::Yellow => color32_compat(theme.yellow.to_srgba_unmultiplied()),
CatppuccinValue::Green => color32_compat(theme.green.to_srgba_unmultiplied()),
CatppuccinValue::Teal => color32_compat(theme.teal.to_srgba_unmultiplied()),
CatppuccinValue::Sky => color32_compat(theme.sky.to_srgba_unmultiplied()),
CatppuccinValue::Sapphire => color32_compat(theme.sapphire.to_srgba_unmultiplied()),
CatppuccinValue::Blue => color32_compat(theme.blue.to_srgba_unmultiplied()),
CatppuccinValue::Lavender => color32_compat(theme.lavender.to_srgba_unmultiplied()),
CatppuccinValue::Text => color32_compat(theme.text.to_srgba_unmultiplied()),
CatppuccinValue::Subtext1 => color32_compat(theme.subtext1.to_srgba_unmultiplied()),
CatppuccinValue::Subtext0 => color32_compat(theme.subtext0.to_srgba_unmultiplied()),
CatppuccinValue::Overlay2 => color32_compat(theme.overlay2.to_srgba_unmultiplied()),
CatppuccinValue::Overlay1 => color32_compat(theme.overlay1.to_srgba_unmultiplied()),
CatppuccinValue::Overlay0 => color32_compat(theme.overlay0.to_srgba_unmultiplied()),
CatppuccinValue::Surface2 => color32_compat(theme.surface2.to_srgba_unmultiplied()),
CatppuccinValue::Surface1 => color32_compat(theme.surface1.to_srgba_unmultiplied()),
CatppuccinValue::Surface0 => color32_compat(theme.surface0.to_srgba_unmultiplied()),
CatppuccinValue::Base => color32_compat(theme.base.to_srgba_unmultiplied()),
CatppuccinValue::Mantle => color32_compat(theme.mantle.to_srgba_unmultiplied()),
CatppuccinValue::Crust => color32_compat(theme.crust.to_srgba_unmultiplied()),
CatppuccinValue::Rosewater => theme.rosewater,
CatppuccinValue::Flamingo => theme.flamingo,
CatppuccinValue::Pink => theme.pink,
CatppuccinValue::Mauve => theme.mauve,
CatppuccinValue::Red => theme.red,
CatppuccinValue::Maroon => theme.maroon,
CatppuccinValue::Peach => theme.peach,
CatppuccinValue::Yellow => theme.yellow,
CatppuccinValue::Green => theme.green,
CatppuccinValue::Teal => theme.teal,
CatppuccinValue::Sky => theme.sky,
CatppuccinValue::Sapphire => theme.sapphire,
CatppuccinValue::Blue => theme.blue,
CatppuccinValue::Lavender => theme.lavender,
CatppuccinValue::Text => theme.text,
CatppuccinValue::Subtext1 => theme.subtext1,
CatppuccinValue::Subtext0 => theme.subtext0,
CatppuccinValue::Overlay2 => theme.overlay2,
CatppuccinValue::Overlay1 => theme.overlay1,
CatppuccinValue::Overlay0 => theme.overlay0,
CatppuccinValue::Surface2 => theme.surface2,
CatppuccinValue::Surface1 => theme.surface1,
CatppuccinValue::Surface0 => theme.surface0,
CatppuccinValue::Base => theme.base,
CatppuccinValue::Mantle => theme.mantle,
CatppuccinValue::Crust => theme.crust,
}
}
}

View File

@@ -9,7 +9,7 @@ use serde::Serialize;
use crate::ring::Ring;
use crate::window::Window;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, JsonSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, Getters, JsonSchema)]
pub struct Container {
#[getset(get = "pub")]
id: String,
@@ -27,6 +27,12 @@ impl Default for Container {
}
}
impl PartialEq for Container {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Container {
pub fn hide(&self, omit: Option<isize>) {
for window in self.windows().iter().rev() {

View File

@@ -77,7 +77,6 @@ pub enum SocketMessage {
ToggleMonocle,
ToggleMaximize,
ToggleWindowContainerBehaviour,
ToggleFloatOverride,
WindowHidingBehaviour(HidingBehaviour),
ToggleCrossMonitorMoveBehaviour,
CrossMonitorMoveBehaviour(MoveBehaviour),
@@ -91,8 +90,6 @@ pub enum SocketMessage {
CycleLayout(CycleDirection),
ChangeLayoutCustom(PathBuf),
FlipLayout(Axis),
ToggleWorkspaceWindowContainerBehaviour,
ToggleWorkspaceFloatOverride,
// Monitor and Workspace Commands
MonitorIndexPreference(usize, i32, i32, i32, i32),
DisplayIndexPreference(usize, String),
@@ -196,7 +193,6 @@ pub enum SocketMessage {
RemoveTitleBar(ApplicationIdentifier, String),
ToggleTitleBars,
AddSubscriberSocket(String),
AddSubscriberSocketWithOptions(String, SubscribeOptions),
RemoveSubscriberSocket(String),
AddSubscriberPipe(String),
RemoveSubscriberPipe(String),
@@ -222,12 +218,6 @@ impl FromStr for SocketMessage {
}
}
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct SubscribeOptions {
/// Only emit notifications when the window manager state has changed
pub filter_state_changes: bool,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema)]
pub enum StackbarMode {
Always,
@@ -343,16 +333,7 @@ pub enum ApplicationIdentifier {
}
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum FocusFollowsMouseImplementation {
/// A custom FFM implementation (slightly more CPU-intensive)
@@ -361,48 +342,18 @@ pub enum FocusFollowsMouseImplementation {
Windows,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct WindowManagementBehaviour {
/// The current WindowContainerBehaviour to be used
pub current_behaviour: WindowContainerBehaviour,
/// Override of `current_behaviour` to open new windows as floating windows
/// that can be later toggled to tiled, when false it will default to
/// `current_behaviour` again.
pub float_override: bool,
}
#[derive(
Clone,
Copy,
Debug,
Default,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum WindowContainerBehaviour {
/// Create a new container for each new window
#[default]
Create,
/// Append new windows to the focused window container
Append,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum MoveBehaviour {
/// Swap the window container with the window container at the edge of the adjacent monitor
@@ -436,16 +387,7 @@ pub enum HidingBehaviour {
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum OperationBehaviour {
/// Process komorebic commands on temporarily unmanaged/floated windows

View File

@@ -166,19 +166,10 @@ lazy_static! {
"X410.exe".to_string(),
"vcxsrv.exe".to_string(),
]));
static ref SLOW_APPLICATION_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
MatchingRule::Simple(IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("firefox.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
]));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref SUBSCRIPTION_SOCKET_OPTIONS: Arc<Mutex<HashMap<String, SubscribeOptions>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
@@ -240,8 +231,6 @@ pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_ENABLED: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_DURATION: AtomicU64 = AtomicU64::new(250);
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
#[must_use]
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
@@ -299,34 +288,18 @@ pub struct Notification {
pub state: State,
}
pub fn notify_subscribers(notification: Notification, state_has_been_modified: bool) -> Result<()> {
let is_subscription_event = matches!(
notification.event,
NotificationEvent::Socket(SocketMessage::AddSubscriberSocket(_))
| NotificationEvent::Socket(SocketMessage::AddSubscriberSocketWithOptions(_, _))
);
let notification = &serde_json::to_string(&notification)?;
pub fn notify_subscribers(notification: &str) -> Result<()> {
let mut stale_sockets = vec![];
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
let options = SUBSCRIPTION_SOCKET_OPTIONS.lock();
for (socket, path) in &mut *sockets {
let apply_state_filter = (*options)
.get(socket)
.copied()
.unwrap_or_default()
.filter_state_changes;
if !apply_state_filter || state_has_been_modified || is_subscription_event {
match UnixStream::connect(path) {
Ok(mut stream) => {
tracing::debug!("pushed notification to subscriber: {socket}");
stream.write_all(notification.as_bytes())?;
}
Err(_) => {
stale_sockets.push(socket.clone());
}
match UnixStream::connect(path) {
Ok(mut stream) => {
tracing::debug!("pushed notification to subscriber: {socket}");
stream.write_all(notification.as_bytes())?;
}
Err(_) => {
stale_sockets.push(socket.clone());
}
}
}
@@ -334,13 +307,6 @@ pub fn notify_subscribers(notification: Notification, state_has_been_modified: b
for socket in stale_sockets {
tracing::warn!("removing stale subscription: {socket}");
sockets.remove(&socket);
let socket_path = DATA_DIR.join(socket);
if let Err(error) = std::fs::remove_file(&socket_path) {
tracing::error!(
"could not remove stale subscriber socket file at {}: {error}",
socket_path.display()
)
}
}
let mut stale_pipes = vec![];

View File

@@ -61,7 +61,6 @@ use crate::winevent_listener;
use crate::GlobalState;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::ANIMATION_FPS;
@@ -80,7 +79,6 @@ 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;
@@ -189,10 +187,6 @@ impl WindowManager {
}
}
#[allow(clippy::useless_asref)]
// We don't have From implemented for &mut WindowManager
let initial_state = State::from(self.as_ref());
match message {
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
if let Some(monitor) = self.focused_monitor_mut() {
@@ -238,13 +232,6 @@ impl WindowManager {
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::FocusStackWindow(idx) => {
// In case you are using this command on a bar on a monitor
// different from the currently focused one, you'd want that
// monitor to be focused so that the FocusStackWindow happens
// on the monitor with the bar you just pressed.
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
self.focus_container_window(idx)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
@@ -1325,14 +1312,6 @@ impl WindowManager {
let socket_path = DATA_DIR.join(socket);
sockets.insert(socket.clone(), socket_path);
}
SocketMessage::AddSubscriberSocketWithOptions(ref socket, options) => {
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
let socket_path = DATA_DIR.join(socket);
sockets.insert(socket.clone(), socket_path);
let mut socket_options = SUBSCRIPTION_SOCKET_OPTIONS.lock();
socket_options.insert(socket.clone(), options);
}
SocketMessage::RemoveSubscriberSocket(ref socket) => {
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
sockets.remove(socket);
@@ -1360,52 +1339,15 @@ impl WindowManager {
self.resize_delta = delta;
}
SocketMessage::ToggleWindowContainerBehaviour => {
match self.window_management_behaviour.current_behaviour {
match self.window_container_behaviour {
WindowContainerBehaviour::Create => {
self.window_management_behaviour.current_behaviour =
WindowContainerBehaviour::Append;
self.window_container_behaviour = WindowContainerBehaviour::Append;
}
WindowContainerBehaviour::Append => {
self.window_management_behaviour.current_behaviour =
WindowContainerBehaviour::Create;
self.window_container_behaviour = WindowContainerBehaviour::Create;
}
}
}
SocketMessage::ToggleFloatOverride => {
self.window_management_behaviour.float_override =
!self.window_management_behaviour.float_override;
}
SocketMessage::ToggleWorkspaceWindowContainerBehaviour => {
let current_global_behaviour = self.window_management_behaviour.current_behaviour;
if let Some(behaviour) = self
.focused_workspace_mut()?
.window_container_behaviour_mut()
{
match behaviour {
WindowContainerBehaviour::Create => {
*behaviour = WindowContainerBehaviour::Append
}
WindowContainerBehaviour::Append => {
*behaviour = 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));
};
}
SocketMessage::WindowHidingBehaviour(behaviour) => {
let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();
*hiding_behaviour = behaviour;
@@ -1588,14 +1530,12 @@ impl WindowManager {
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
};
notify_subscribers(
Notification {
event: NotificationEvent::Socket(message.clone()),
state: self.as_ref().into(),
},
initial_state.has_been_modified(self.as_ref()),
)?;
let notification = Notification {
event: NotificationEvent::Socket(message.clone()),
state: self.as_ref().into(),
};
notify_subscribers(&serde_json::to_string(&notification)?)?;
border_manager::send_notification(None);
transparency_manager::send_notification();
stackbar_manager::send_notification();

View File

@@ -32,7 +32,6 @@ 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;
@@ -123,10 +122,6 @@ impl WindowManager {
}
}
#[allow(clippy::useless_asref)]
// We don't have From implemented for &mut WindowManager
let initial_state = State::from(self.as_ref());
// Make sure we have the most recently focused monitor from any event
match event {
WindowManagerEvent::FocusChange(_, window)
@@ -338,8 +333,8 @@ impl WindowManager {
}
if proceed {
let mut behaviour = self
.window_management_behaviour(focused_monitor_idx, focused_workspace_idx);
let behaviour =
self.window_container_behaviour(focused_monitor_idx, focused_workspace_idx);
let workspace = self.focused_workspace_mut()?;
let workspace_contains_window = workspace.contains_window(window.hwnd);
let monocle_container = workspace.monocle_container().clone();
@@ -365,14 +360,11 @@ impl WindowManager {
}
}
behaviour.float_override = behaviour.float_override
|| (should_float && !matches!(event, WindowManagerEvent::Manage(_)));
if behaviour.float_override {
if should_float && !matches!(event, WindowManagerEvent::Manage(_)) {
workspace.floating_windows_mut().push(window);
self.update_focused_workspace(false, false)?;
self.update_focused_workspace(false, true)?;
} else {
match behaviour.current_behaviour {
match behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
self.update_focused_workspace(false, false)?;
@@ -383,6 +375,7 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no focused container"))?
.add_window(window);
self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification();
}
}
@@ -438,8 +431,8 @@ impl WindowManager {
let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx = self.focused_workspace_idx().unwrap_or_default();
let window_management_behaviour =
self.window_management_behaviour(focused_monitor_idx, focused_workspace_idx);
let window_container_behaviour =
self.window_container_behaviour(focused_monitor_idx, focused_workspace_idx);
let workspace = self.focused_workspace_mut()?;
let focused_container_idx = workspace.focused_container_idx();
@@ -557,11 +550,8 @@ impl WindowManager {
}
// Here we handle a simple move on the same monitor which is treated as
// a container swap
} else if window_management_behaviour.float_override {
workspace.floating_windows_mut().push(window);
self.update_focused_workspace(false, false)?;
} else {
match window_management_behaviour.current_behaviour {
match window_container_behaviour {
WindowContainerBehaviour::Create => {
match workspace.container_idx_from_current_point() {
Some(target_idx) => {
@@ -675,14 +665,12 @@ impl WindowManager {
serde_json::to_writer_pretty(&file, &known_hwnds)?;
notify_subscribers(
Notification {
event: NotificationEvent::WindowManager(event),
state: self.as_ref().into(),
},
initial_state.has_been_modified(self.as_ref()),
)?;
let notification = Notification {
event: NotificationEvent::WindowManager(event),
state: self.as_ref().into(),
};
notify_subscribers(&serde_json::to_string(&notification)?)?;
border_manager::send_notification(Some(event.hwnd()));
transparency_manager::send_notification();
stackbar_manager::send_notification();

View File

@@ -43,8 +43,6 @@ use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
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;
@@ -68,7 +66,6 @@ use crate::core::OperationBehaviour;
use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
@@ -96,8 +93,6 @@ pub struct BorderColours {
pub stack: Option<Colour>,
/// Border colour when the container is in monocle mode
pub monocle: Option<Colour>,
/// Border colour when the container is in floating mode
pub floating: Option<Colour>,
/// Border colour when the container is unfocused
pub unfocused: Option<Colour>,
}
@@ -133,13 +128,6 @@ pub struct WorkspaceConfig {
/// Apply this monitor's window-based work area offset (default: true)
#[serde(skip_serializing_if = "Option::is_none")]
pub apply_window_based_work_area_offset: Option<bool>,
/// Determine what happens when a new window is opened (default: Create)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
/// Enable or disable float override, which makes it so every new window opens in floating mode
/// (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override: Option<bool>,
}
impl From<&Workspace> for WorkspaceConfig {
@@ -192,8 +180,6 @@ impl From<&Workspace> for WorkspaceConfig {
initial_workspace_rules: None,
workspace_rules: None,
apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset()),
window_container_behaviour: *value.window_container_behaviour(),
float_override: *value.float_override(),
}
}
}
@@ -247,10 +233,6 @@ pub struct StaticConfig {
/// Determine what happens when a new window is opened (default: Create)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
/// Enable or disable float override, which makes it so every new window opens in floating mode
/// (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override: Option<bool>,
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
#[serde(skip_serializing_if = "Option::is_none")]
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
@@ -356,16 +338,6 @@ pub struct StaticConfig {
/// Theme configuration options
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<KomorebiTheme>,
/// Identify applications which are slow to send initial event notifications
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application_identifiers: Option<Vec<MatchingRule>>,
/// How long to wait when compensating for slow applications, in milliseconds (default: 20)
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application_compensation_time: Option<u64>,
/// Komorebi status bar configuration files for multiple instances on different monitors
#[serde(skip_serializing_if = "Option::is_none")]
// this option is a little special because it is only consumed by komorebic
pub bar_configurations: Option<Vec<PathBuf>>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -527,9 +499,6 @@ impl From<&WindowManager> for StaticConfig {
single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))),
stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))),
monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))),
floating: Option::from(Colour::from(
border_manager::FLOATING.load(Ordering::SeqCst),
)),
unfocused: Option::from(Colour::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst),
)),
@@ -539,10 +508,7 @@ impl From<&WindowManager> for StaticConfig {
Self {
invisible_borders: None,
resize_delta: Option::from(value.resize_delta),
window_container_behaviour: Option::from(
value.window_management_behaviour.current_behaviour,
),
float_override: Option::from(value.window_management_behaviour.float_override),
window_container_behaviour: Option::from(value.window_container_behaviour),
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour),
unmanaged_window_operation_behaviour: Option::from(
@@ -588,11 +554,6 @@ impl From<&WindowManager> for StaticConfig {
stackbar: None,
animation: None,
theme: None,
slow_application_compensation_time: Option::from(
SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),
),
slow_application_identifiers: Option::from(SLOW_APPLICATION_IDENTIFIERS.lock().clone()),
bar_configurations: None,
}
}
}
@@ -659,10 +620,6 @@ impl StaticConfig {
border_manager::MONOCLE.store(u32::from(monocle), Ordering::SeqCst);
}
if let Some(floating) = colours.floating {
border_manager::FLOATING.store(u32::from(floating), Ordering::SeqCst);
}
if let Some(unfocused) = colours.unfocused {
border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst);
}
@@ -705,7 +662,6 @@ impl StaticConfig {
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
let mut layered_identifiers = LAYERED_WHITELIST.lock();
let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock();
let mut slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
let mut floating_applications = FLOATING_APPLICATIONS.lock();
if let Some(rules) = &mut self.ignore_rules {
@@ -744,14 +700,6 @@ impl StaticConfig {
populate_rules(rules, &mut transparency_blacklist, &mut regex_identifiers)?;
}
if let Some(rules) = &mut self.slow_application_identifiers {
populate_rules(
rules,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(stackbar) = &self.stackbar {
if let Some(height) = &stackbar.height {
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
@@ -990,34 +938,7 @@ impl StaticConfig {
pub fn read(path: &PathBuf) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?;
if let Some(path) = &mut value.app_specific_configuration_path {
*path = resolve_home_path(&*path)?;
}
if let Some(monitors) = &mut value.monitors {
for m in monitors {
for w in &mut m.workspaces {
if let Some(path) = &mut w.custom_layout {
*path = resolve_home_path(&*path)?;
}
if let Some(map) = &mut w.custom_layout_rules {
for path in map.values_mut() {
*path = resolve_home_path(&*path)?;
}
}
}
}
}
if let Some(bar_configurations) = &mut value.bar_configurations {
for path in bar_configurations {
*path = resolve_home_path(&*path)?;
}
}
let value: Self = serde_json::from_str(&content)?;
Ok(value)
}
@@ -1027,7 +948,8 @@ impl StaticConfig {
incoming: Receiver<WindowManagerEvent>,
unix_listener: Option<UnixListener>,
) -> Result<WindowManager> {
let mut value = Self::read(path)?;
let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?;
value.apply_globals()?;
let listener = match unix_listener {
@@ -1057,12 +979,9 @@ impl StaticConfig {
is_paused: false,
virtual_desktop_id: current_virtual_desktop(),
work_area_offset: value.global_work_area_offset,
window_management_behaviour: WindowManagementBehaviour {
current_behaviour: value
.window_container_behaviour
.unwrap_or(WindowContainerBehaviour::Create),
float_override: value.float_override.unwrap_or_default(),
},
window_container_behaviour: value
.window_container_behaviour
.unwrap_or(WindowContainerBehaviour::Create),
cross_monitor_move_behaviour: value
.cross_monitor_move_behaviour
.unwrap_or(MoveBehaviour::Swap),
@@ -1109,7 +1028,8 @@ impl StaticConfig {
}
pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> Result<()> {
let value = Self::read(path)?;
let content = std::fs::read_to_string(path)?;
let value: Self = serde_json::from_str(&content)?;
let mut wm = wm.lock();
if let Some(monitors) = value.monitors {
@@ -1176,7 +1096,8 @@ impl StaticConfig {
}
pub fn reload(path: &PathBuf, wm: &mut WindowManager) -> Result<()> {
let mut value = Self::read(path)?;
let content = std::fs::read_to_string(path)?;
let mut value: Self = serde_json::from_str(&content)?;
value.apply_globals()?;
@@ -1237,11 +1158,7 @@ impl StaticConfig {
}
if let Some(val) = value.window_container_behaviour {
wm.window_management_behaviour.current_behaviour = val;
}
if let Some(val) = value.float_override {
wm.window_management_behaviour.float_override = val;
wm.window_container_behaviour = val;
}
if let Some(val) = value.cross_monitor_move_behaviour {

View File

@@ -6,8 +6,6 @@ use crate::windows_api;
use crate::ANIMATIONS_IN_PROGRESS;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
@@ -159,31 +157,6 @@ impl Window {
HWND(windows_api::as_ptr!(self.hwnd))
}
pub fn move_to_area(&mut self, current_area: &Rect, target_area: &Rect) -> Result<()> {
let current_rect = WindowsApi::window_rect(self.hwnd)?;
let x_diff = target_area.left - current_area.left;
let y_diff = target_area.top - current_area.top;
let x_ratio = f32::abs((target_area.right as f32) / (current_area.right as f32));
let y_ratio = f32::abs((target_area.bottom as f32) / (current_area.bottom as f32));
let window_relative_x = current_rect.left - current_area.left;
let window_relative_y = current_rect.top - current_area.top;
let corrected_relative_x = (window_relative_x as f32 * x_ratio) as i32;
let corrected_relative_y = (window_relative_y as f32 * y_ratio) as i32;
let window_x = current_area.left + corrected_relative_x;
let window_y = current_area.top + corrected_relative_y;
let new_rect = Rect {
left: x_diff + window_x,
top: y_diff + window_y,
right: current_rect.right,
bottom: current_rect.bottom,
};
//TODO: We might need to take into account the differences in DPI for the new_rect, unless
//we can use the xy ratios above to the right/bottom (width/height of window) as well?
self.set_position(&new_rect, true)
}
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
let half_width = work_area.right / 2;
let half_weight = work_area.bottom / 2;
@@ -305,10 +278,7 @@ impl Window {
}
pub fn minimize(self) {
let exe = self.exe().unwrap_or_default();
if !exe.contains("komorebi-bar") {
WindowsApi::minimize_window(self.hwnd);
}
WindowsApi::minimize_window(self.hwnd);
}
pub fn close(self) -> Result<()> {
@@ -664,23 +634,8 @@ fn window_is_eligible(
titlebars_removed.contains(exe_name)
};
{
let slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
let should_sleep = should_act(
title,
exe_name,
class,
path,
&slow_application_identifiers,
&regex_identifiers,
)
.is_some();
if should_sleep {
std::thread::sleep(Duration::from_millis(
SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),
));
}
if exe_name.contains("firefox") {
std::thread::sleep(Duration::from_millis(10));
}
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))

View File

@@ -39,7 +39,6 @@ use crate::core::Rect;
use crate::core::Sizing;
use crate::core::StackbarLabel;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::border_manager;
use crate::border_manager::STYLE;
@@ -93,7 +92,7 @@ pub struct WindowManager {
pub is_paused: bool,
pub work_area_offset: Option<Rect>,
pub resize_delta: i32,
pub window_management_behaviour: WindowManagementBehaviour,
pub window_container_behaviour: WindowContainerBehaviour,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub cross_boundary_behaviour: CrossBoundaryBehaviour,
pub unmanaged_window_operation_behaviour: OperationBehaviour,
@@ -113,7 +112,6 @@ pub struct State {
pub is_paused: bool,
pub resize_delta: i32,
pub new_window_behaviour: WindowContainerBehaviour,
pub float_override: bool,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub unmanaged_window_operation_behaviour: OperationBehaviour,
pub work_area_offset: Option<Rect>,
@@ -122,54 +120,6 @@ pub struct State {
pub has_pending_raise_op: bool,
}
impl State {
pub fn has_been_modified(&self, wm: &WindowManager) -> bool {
let new = Self::from(wm);
if self.monitors != new.monitors {
return true;
}
if self.is_paused != new.is_paused {
return true;
}
if self.new_window_behaviour != new.new_window_behaviour {
return true;
}
if self.float_override != new.float_override {
return true;
}
if self.cross_monitor_move_behaviour != new.cross_monitor_move_behaviour {
return true;
}
if self.unmanaged_window_operation_behaviour != new.unmanaged_window_operation_behaviour {
return true;
}
if self.work_area_offset != new.work_area_offset {
return true;
}
if self.focus_follows_mouse != new.focus_follows_mouse {
return true;
}
if self.mouse_follows_focus != new.mouse_follows_focus {
return true;
}
if self.has_pending_raise_op != new.has_pending_raise_op {
return true;
}
false
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct GlobalState {
@@ -215,9 +165,6 @@ impl Default for GlobalState {
monocle: Option::from(Colour::Rgb(Rgb::from(
border_manager::MONOCLE.load(Ordering::SeqCst),
))),
floating: Option::from(Colour::Rgb(Rgb::from(
border_manager::FLOATING.load(Ordering::SeqCst),
))),
unfocused: Option::from(Colour::Rgb(Rgb::from(
border_manager::UNFOCUSED.load(Ordering::SeqCst),
))),
@@ -268,8 +215,7 @@ impl From<&WindowManager> for State {
is_paused: wm.is_paused,
work_area_offset: wm.work_area_offset,
resize_delta: wm.resize_delta,
new_window_behaviour: wm.window_management_behaviour.current_behaviour,
float_override: wm.window_management_behaviour.float_override,
new_window_behaviour: wm.window_container_behaviour,
cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour,
focus_follows_mouse: wm.focus_follows_mouse,
mouse_follows_focus: wm.mouse_follows_focus,
@@ -329,7 +275,7 @@ impl WindowManager {
is_paused: false,
virtual_desktop_id: current_virtual_desktop(),
work_area_offset: None,
window_management_behaviour: WindowManagementBehaviour::default(),
window_container_behaviour: WindowContainerBehaviour::Create,
cross_monitor_move_behaviour: MoveBehaviour::Swap,
cross_boundary_behaviour: CrossBoundaryBehaviour::Workspace,
unmanaged_window_operation_behaviour: OperationBehaviour::Op,
@@ -362,52 +308,22 @@ impl WindowManager {
StaticConfig::reload(pathbuf, self)
}
pub fn window_management_behaviour(
pub fn window_container_behaviour(
&self,
monitor_idx: usize,
workspace_idx: usize,
) -> WindowManagementBehaviour {
) -> WindowContainerBehaviour {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.workspaces().get(workspace_idx) {
let current_behaviour =
if let Some(behaviour) = workspace.window_container_behaviour() {
if workspace.containers().is_empty()
&& matches!(behaviour, WindowContainerBehaviour::Append)
{
// You can't append to an empty workspace
WindowContainerBehaviour::Create
} else {
*behaviour
}
} else if workspace.containers().is_empty()
&& matches!(
self.window_management_behaviour.current_behaviour,
WindowContainerBehaviour::Append
)
{
// You can't append to an empty workspace
WindowContainerBehaviour::Create
} else {
self.window_management_behaviour.current_behaviour
};
let float_override = if let Some(float_override) = workspace.float_override() {
*float_override
return if workspace.containers().is_empty() {
WindowContainerBehaviour::Create
} else {
self.window_management_behaviour.float_override
};
return WindowManagementBehaviour {
current_behaviour,
float_override,
self.window_container_behaviour
};
}
}
WindowManagementBehaviour {
current_behaviour: WindowContainerBehaviour::Create,
float_override: self.window_management_behaviour.float_override,
}
WindowContainerBehaviour::Create
}
#[tracing::instrument(skip(self))]
@@ -910,39 +826,28 @@ impl WindowManager {
}
}
}
} else {
if self.focused_workspace()?.is_empty() {
let desktop_window = Window::from(WindowsApi::desktop_window()?);
}
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
// if we passed false for follow_focus and there is a container on the workspace
if !follow_focus && self.focused_container_mut().is_ok() {
// and we have a stack with >1 windows
if self.focused_container_mut()?.windows().len() > 1
// and we don't have a maxed window
&& self.focused_workspace()?.maximized_window().is_none()
// and we don't have a monocle container
&& self.focused_workspace()?.monocle_container().is_none()
{
if let Ok(window) = self.focused_window_mut() {
if trigger_focus {
window.focus(self.mouse_follows_focus)?;
}
}
}
}
// if we passed false for follow_focus and there is a container on the workspace
if self.focused_container_mut().is_ok() {
// and we have a stack with >1 windows
if self.focused_container_mut()?.windows().len() > 1
// and we don't have a maxed window
&& self.focused_workspace()?.maximized_window().is_none()
// and we don't have a monocle container
&& self.focused_workspace()?.monocle_container().is_none()
// and we don't have any floating windows that should show on top
&& self.focused_workspace()?.floating_windows().is_empty()
{
if let Ok(window) = self.focused_window_mut() {
if trigger_focus {
window.focus(self.mouse_follows_focus)?;
}
}
}
}
// This is to correctly restore and focus when switching to a workspace which
// contains a managed maximized window
// This is to correctly restore and focus when switching to a workspace which
// contains a managed maximized window
if !follow_focus {
if let Some(window) = self.focused_workspace()?.maximized_window() {
window.restore();
if trigger_focus {
@@ -1055,14 +960,6 @@ impl WindowManager {
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
if let Some(monocle) = workspace.monocle_container() {
for window in monocle.windows() {
if matches!(border_implementation, BorderImplementation::Windows) {
window.remove_accent()?;
}
}
}
for containers in workspace.containers_mut() {
for window in containers.windows_mut() {
if no_titlebar.contains(&window.exe()?) {
@@ -1236,8 +1133,6 @@ impl WindowManager {
.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let current_area = *monitor.work_area_size();
let workspace = monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?;
@@ -1246,23 +1141,16 @@ impl WindowManager {
bail!("cannot move native maximized window to another monitor or workspace");
}
let foreground_hwnd = WindowsApi::foreground_window()?;
let floating_window_index = workspace
.floating_windows()
.iter()
.position(|w| w.hwnd == foreground_hwnd);
let container = workspace
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?;
let container_hwnds = container
.windows()
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
let floating_window =
floating_window_index.map(|idx| workspace.floating_windows_mut().remove(idx));
let container = if floating_window_index.is_none() {
Some(
workspace
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?,
)
} else {
None
};
monitor.update_focused_workspace(offset)?;
let target_monitor = self
@@ -1270,36 +1158,18 @@ impl WindowManager {
.get_mut(monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor"))?;
target_monitor.add_container(container, workspace_idx)?;
if let Some(workspace_idx) = workspace_idx {
target_monitor.focus_workspace(workspace_idx)?;
}
let target_workspace = target_monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no focused workspace on target monitor"))?;
if let Some(window) = floating_window {
target_workspace.floating_windows_mut().push(window);
Window::from(window.hwnd)
.move_to_area(&current_area, target_monitor.work_area_size())?;
} else if let Some(container) = container {
let container_hwnds = container
.windows()
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
target_monitor.add_container(container, workspace_idx)?;
if let Some(workspace) = target_monitor.focused_workspace() {
if !*workspace.tile() {
for hwnd in container_hwnds {
Window::from(hwnd)
.move_to_area(&current_area, target_monitor.work_area_size())?;
}
if let Some(workspace) = target_monitor.focused_workspace() {
if !*workspace.tile() {
for hwnd in container_hwnds {
Window::from(hwnd).center(target_monitor.work_area_size())?;
}
}
} else {
bail!("failed to find a window to move");
}
target_monitor.load_focused_workspace(mouse_follows_focus)?;

View File

@@ -30,7 +30,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;
@@ -84,10 +83,6 @@ pub struct Workspace {
tile: bool,
#[getset(get_copy = "pub", set = "pub")]
apply_window_based_work_area_offset: bool,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
window_container_behaviour: Option<WindowContainerBehaviour>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
float_override: Option<bool>,
}
impl_ring_elements!(Workspace, Container);
@@ -111,8 +106,6 @@ impl Default for Workspace {
resize_dimensions: vec![],
tile: true,
apply_window_based_work_area_offset: true,
window_container_behaviour: None,
float_override: None,
}
}
}
@@ -169,14 +162,6 @@ impl Workspace {
config.apply_window_based_work_area_offset.unwrap_or(true),
);
if config.window_container_behaviour.is_some() {
self.set_window_container_behaviour(config.window_container_behaviour);
}
if config.float_override.is_some() {
self.set_float_override(config.float_override);
}
Ok(())
}
@@ -232,6 +217,10 @@ impl Workspace {
container.restore();
}
for container in self.containers_mut() {
container.restore();
}
if let Some(container) = self.focused_container_mut() {
container.focus_window(container.focused_window_idx());
}
@@ -597,13 +586,6 @@ impl Workspace {
Ok(false)
}
pub fn is_empty(&self) -> bool {
self.containers().is_empty()
&& self.maximized_window().is_none()
&& self.monocle_container().is_none()
&& self.floating_windows().is_empty()
}
pub fn contains_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {

View File

@@ -1167,16 +1167,6 @@ enum SubCommand {
WorkspaceName(WorkspaceName),
/// Toggle the behaviour for new windows (stacking or dynamic tiling)
ToggleWindowContainerBehaviour,
/// Enable or disable float override, which makes it so every new window opens in floating mode
ToggleFloatOverride,
/// Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused
/// workspace. If there was no behaviour set for the workspace previously it takes the opposite
/// of the global value.
ToggleWorkspaceWindowContainerBehaviour,
/// Enable or disable float override, which makes it so every new window opens in floating
/// 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 window tiling on the focused workspace
TogglePause,
/// Toggle window tiling on the focused workspace
@@ -1528,15 +1518,26 @@ fn main() -> Result<()> {
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");
if let Ok(config) = &parsed_config {
if let Some(asc_path) = config.get("app_specific_configuration_path") {
let mut normalized_asc_path = asc_path
.to_string()
.replace(
"$Env:USERPROFILE",
&dirs::home_dir().unwrap().to_string_lossy(),
)
.replace('"', "")
.replace('\\', "/");
if let Ok(komorebi_config_home) = std::env::var("KOMOREBI_CONFIG_HOME") {
normalized_asc_path = normalized_asc_path
.replace("$Env:KOMOREBI_CONFIG_HOME", &komorebi_config_home)
.replace('"', "")
.replace('\\', "/");
}
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());
}
if !Path::exists(Path::new(&normalized_asc_path)) {
println!("Application specific configuration file path '{normalized_asc_path}' does not exist. Try running 'komorebic fetch-asc'\n");
}
}
}
@@ -2025,50 +2026,19 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
}
}
let static_config = arg.config.clone().map_or_else(
|| {
let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() {
Option::from(komorebi_json)
} else {
None
}
},
Option::from,
);
if arg.bar {
if let Some(config) = &static_config {
let mut config = StaticConfig::read(config)?;
if let Some(display_bar_configurations) = &mut config.bar_configurations {
for config_file_path in &mut *display_bar_configurations {
let script = r"Start-Process 'komorebi-bar' '--config CONFIGFILE' -WindowStyle hidden"
.replace("CONFIGFILE", &config_file_path.to_string_lossy());
match powershell_script::run(&script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
} else {
let script = r"
let script = r"
if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
{
Start-Process komorebi-bar -WindowStyle hidden
}
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
@@ -2081,11 +2051,11 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
let bar_config = arg.config.map_or_else(
let static_config = arg.config.map_or_else(
|| {
let bar_json = HOME_DIR.join("komorebi.bar.json");
if bar_json.is_file() {
Option::from(bar_json)
let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() {
Option::from(komorebi_json)
} else {
None
}
@@ -2093,18 +2063,12 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
Option::from,
);
if let Some(config) = &static_config {
if let Some(config) = static_config {
let path = resolve_home_path(config)?;
let raw = std::fs::read_to_string(path)?;
StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw);
}
if bar_config.is_some() {
let output = Command::new("komorebi-bar.exe").arg("--aliases").output()?;
let stdout = String::from_utf8(output.stdout)?;
println!("{stdout}");
}
}
SubCommand::Stop(arg) => {
if arg.whkd {
@@ -2480,15 +2444,6 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
SubCommand::ToggleWindowContainerBehaviour => {
send_message(&SocketMessage::ToggleWindowContainerBehaviour)?;
}
SubCommand::ToggleFloatOverride => {
send_message(&SocketMessage::ToggleFloatOverride)?;
}
SubCommand::ToggleWorkspaceWindowContainerBehaviour => {
send_message(&SocketMessage::ToggleWorkspaceWindowContainerBehaviour)?;
}
SubCommand::ToggleWorkspaceFloatOverride => {
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
}
SubCommand::WindowHidingBehaviour(arg) => {
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?;
}

View File

@@ -13,10 +13,7 @@
"name"
],
"properties": {
"identifier": {
"$ref": "#/definitions/IdWithIdentifier"
},
"ignore_identifiers": {
"float_identifiers": {
"type": [
"array",
"null"
@@ -25,6 +22,9 @@
"$ref": "#/definitions/MatchingRule"
}
},
"identifier": {
"$ref": "#/definitions/IdWithIdentifier"
},
"name": {
"type": "string"
},

View File

@@ -73,99 +73,6 @@
"enable": {
"description": "Enable the Battery widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Cpu"
],
"properties": {
"Cpu": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 10 seconds)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Cpu widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -234,39 +141,6 @@
"additionalProperties": false
}
]
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -402,39 +276,6 @@
"enable": {
"description": "Enable the Memory widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -465,39 +306,6 @@
"description": "Enable the Network widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
},
"network_activity_fill_characters": {
"description": "Characters to reserve for network activity data",
"type": "integer",
@@ -538,39 +346,6 @@
"enable": {
"description": "Enable the Storage widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -625,39 +400,6 @@
"additionalProperties": false
}
]
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -719,52 +461,6 @@
}
}
},
"position": {
"description": "Bar positioning options",
"type": "object",
"properties": {
"end": {
"description": "The desired size of the bar from the starting position (usually monitor width x desired height)",
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"description": "X coordinate",
"type": "number",
"format": "float"
},
"y": {
"description": "Y coordinate",
"type": "number",
"format": "float"
}
}
},
"start": {
"description": "The desired starting position of the bar (0,0 = top left of the screen)",
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"description": "X coordinate",
"type": "number",
"format": "float"
},
"y": {
"description": "Y coordinate",
"type": "number",
"format": "float"
}
}
}
}
},
"right_widgets": {
"description": "Right side widgets (ordered left-to-right)",
"type": "array",
@@ -791,99 +487,6 @@
"enable": {
"description": "Enable the Battery widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Cpu"
],
"properties": {
"Cpu": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 10 seconds)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Cpu widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -952,39 +555,6 @@
"additionalProperties": false
}
]
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -1120,39 +690,6 @@
"enable": {
"description": "Enable the Memory widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -1183,39 +720,6 @@
"description": "Enable the Network widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
},
"network_activity_fill_characters": {
"description": "Characters to reserve for network activity data",
"type": "integer",
@@ -1256,39 +760,6 @@
"enable": {
"description": "Enable the Storage widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -1343,39 +814,6 @@
"additionalProperties": false
}
]
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
@@ -1756,6 +1194,52 @@
}
}
]
},
"viewport": {
"description": "Viewport options (see: https://docs.rs/egui/latest/egui/viewport/struct.ViewportBuilder.html)",
"type": "object",
"properties": {
"inner_size": {
"description": "The desired size of the bar from the starting position (usually monitor width x desired height)",
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"description": "X coordinate",
"type": "number",
"format": "float"
},
"y": {
"description": "Y coordinate",
"type": "number",
"format": "float"
}
}
},
"position": {
"description": "The desired starting position of the bar (0,0 = top left of the screen)",
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"description": "X coordinate",
"type": "number",
"format": "float"
},
"y": {
"description": "Y coordinate",
"type": "number",
"format": "float"
}
}
}
}
}
}
}

View File

@@ -69,13 +69,6 @@
"description": "Path to applications.yaml from komorebi-application-specific-configurations (default: None)",
"type": "string"
},
"bar_configurations": {
"description": "Komorebi status bar configuration files for multiple instances on different monitors",
"type": "array",
"items": {
"type": "string"
}
},
"border": {
"description": "Display an active window border (default: false)",
"type": "boolean"
@@ -84,45 +77,6 @@
"description": "Active window border colours for different container types",
"type": "object",
"properties": {
"floating": {
"description": "Border colour when the container is in floating mode",
"anyOf": [
{
"description": "Colour represented as RGB",
"type": "object",
"required": [
"b",
"g",
"r"
],
"properties": {
"b": {
"description": "Blue",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"g": {
"description": "Green",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"r": {
"description": "Red",
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
{
"description": "Colour represented as Hex",
"type": "string",
"format": "color-hex"
}
]
},
"monocle": {
"description": "Border colour when the container is in monocle mode",
"anyOf": [
@@ -491,12 +445,8 @@
"type": "string"
}
},
"float_override": {
"description": "Enable or disable float override, which makes it so every new window opens in floating mode (default: false)",
"type": "boolean"
},
"floating_applications": {
"description": "Identify applications which should be managed as floating windows",
"float_rules": {
"description": "Individual window floating rules",
"type": "array",
"items": {
"anyOf": [
@@ -629,89 +579,6 @@
}
}
},
"ignore_rules": {
"description": "Individual window floating rules",
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
},
{
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
}
}
]
}
},
"invisible_borders": {
"description": "DEPRECATED from v0.1.22: no longer required",
"type": "object",
@@ -1062,91 +929,44 @@
"type": "string"
}
},
"float_override": {
"description": "Enable or disable float override, which makes it so every new window opens in floating mode (default: false)",
"type": "boolean"
},
"initial_workspace_rules": {
"description": "Initial workspace application rules",
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
{
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
}
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
]
}
}
},
"layout": {
@@ -1184,25 +1004,6 @@
"description": "Name",
"type": "string"
},
"window_container_behaviour": {
"description": "Determine what happens when a new window is opened (default: Create)",
"oneOf": [
{
"description": "Create a new container for each new window",
"type": "string",
"enum": [
"Create"
]
},
{
"description": "Append new windows to the focused window container",
"type": "string",
"enum": [
"Append"
]
}
]
},
"workspace_padding": {
"description": "Container padding (default: global)",
"type": "integer",
@@ -1212,83 +1013,40 @@
"description": "Permanent workspace application rules",
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
{
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
}
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
]
}
}
}
}
@@ -1389,95 +1147,6 @@
"type": "integer",
"format": "int32"
},
"slow_application_compensation_time": {
"description": "How long to wait when compensating for slow applications, in milliseconds (default: 20)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"slow_application_identifiers": {
"description": "Identify applications which are slow to send initial event notifications",
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
},
{
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
}
}
]
}
},
"stackbar": {
"description": "Stackbar configuration options",
"type": "object",
@@ -1686,38 +1355,6 @@
"Crust"
]
},
"floating_border": {
"description": "Border colour when the window is floating (default: Yellow)",
"type": "string",
"enum": [
"Rosewater",
"Flamingo",
"Pink",
"Mauve",
"Red",
"Maroon",
"Peach",
"Yellow",
"Green",
"Teal",
"Sky",
"Sapphire",
"Blue",
"Lavender",
"Text",
"Subtext1",
"Subtext0",
"Overlay2",
"Overlay1",
"Overlay0",
"Surface2",
"Surface1",
"Surface0",
"Base",
"Mantle",
"Crust"
]
},
"monocle_border": {
"description": "Border colour when the container is in monocle mode (default: Pink)",
"type": "string",
@@ -1990,28 +1627,6 @@
"Base0F"
]
},
"floating_border": {
"description": "Border colour when the window is floating (default: Base09)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"monocle_border": {
"description": "Border colour when the container is in monocle mode (default: Base0F)",
"type": "string",