mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-19 22:13:41 +01:00
Compare commits
21 Commits
feature/ic
...
feature/cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4968b0fe37 | ||
|
|
b4b400b236 | ||
|
|
2ee0bbc0c7 | ||
|
|
d38d3c956d | ||
|
|
052eb1c763 | ||
|
|
58730b81b4 | ||
|
|
274ae43e8f | ||
|
|
2a30f09bbd | ||
|
|
8fd18048a4 | ||
|
|
5809735024 | ||
|
|
96fdbbd1fb | ||
|
|
de131e9ca5 | ||
|
|
07dba03255 | ||
|
|
acd53dec1b | ||
|
|
a98968d179 | ||
|
|
8a32219867 | ||
|
|
ce4b75cc3c | ||
|
|
e4226ce623 | ||
|
|
4bfd7febb4 | ||
|
|
5cc688dc6b | ||
|
|
d897890032 |
40
CODE_OF_CONDUCT.md
Normal file
40
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# The Komorebi Code of Conduct
|
||||
|
||||
This document is based on the [Rust Code of
|
||||
Conduct](https://www.rust-lang.org/policies/code-of-conduct)
|
||||
|
||||
## Conduct
|
||||
|
||||
- We are committed to providing a friendly, safe and welcoming environment for
|
||||
all, regardless of level of experience, gender identity and expression, sexual
|
||||
orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, nationality, or other similar characteristic.
|
||||
|
||||
- Please avoid using overtly sexual aliases or other nicknames that might
|
||||
detract from a friendly, safe and welcoming environment for all.
|
||||
|
||||
- Please be kind and courteous. There’s no need to be mean or rude.
|
||||
|
||||
- Respect that people have differences of opinion and that every design or
|
||||
implementation choice carries a trade-off and numerous costs. There is seldom a
|
||||
right answer.
|
||||
|
||||
- Please keep unstructured critique to a minimum. If you have solid ideas you
|
||||
want to experiment with, make a fork and see how it works.
|
||||
|
||||
- We will exclude you from interaction if you insult, demean or harass anyone.
|
||||
That is not welcome behavior. We interpret the term “harassment” as including
|
||||
the definition in the [Citizen Code of
|
||||
Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md);
|
||||
if you have any lack of clarity about what might be included in that concept,
|
||||
please read their definition. In particular, we don’t tolerate behavior that
|
||||
excludes people in socially marginalized groups.
|
||||
|
||||
- Private harassment is also unacceptable. No matter who you are, if you feel
|
||||
you have been or are being harassed or made uncomfortable by a community member,
|
||||
please contact me immediately. Whether you’re a regular contributor or a
|
||||
newcomer, we care about making this community a safe place for you and we’ve got
|
||||
your back.
|
||||
|
||||
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing
|
||||
behavior is not welcome.
|
||||
815
Cargo.lock
generated
815
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
LICENSE.md
24
LICENSE.md
@@ -1,6 +1,6 @@
|
||||
# Komorebi License
|
||||
|
||||
Version 1.0.0
|
||||
Version 2.0.0
|
||||
|
||||
## Acceptance
|
||||
|
||||
@@ -13,9 +13,20 @@ your licenses.
|
||||
The licensor grants you a copyright license for the software
|
||||
to do everything you might do with the software that would
|
||||
otherwise infringe the licensor's copyright in it for any
|
||||
permitted purpose. However, you may only make changes according
|
||||
permitted purpose. However, you may only distribute the source
|
||||
code of the software according to the [Distribution License](
|
||||
#distribution-license), you may only make changes according
|
||||
to the [Changes License](#changes-license), and you may not
|
||||
distribute the software or new works based on the software.
|
||||
otherwise distribute the software or new works based on the
|
||||
software.
|
||||
|
||||
## Distribution License
|
||||
|
||||
The licensor grants you an additional copyright license to
|
||||
distribute copies of the source code of the software. Your
|
||||
license to distribute covers distributing the source code of
|
||||
the software with changes permitted by the [Changes License](
|
||||
#changes-license).
|
||||
|
||||
## Changes License
|
||||
|
||||
@@ -45,7 +56,7 @@ law. These terms do not limit them.
|
||||
|
||||
These terms do not allow you to sublicense or transfer any of
|
||||
your licenses to anyone else, or prevent the licensor from
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
any other licenses.
|
||||
|
||||
## Patent Defense
|
||||
@@ -63,7 +74,7 @@ violated any of these terms, or done anything with the software
|
||||
not covered by your licenses, your licenses can nonetheless
|
||||
continue if you come into full compliance with these terms,
|
||||
and take practical steps to correct past violations, within
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
end immediately.
|
||||
|
||||
## No Liability
|
||||
@@ -88,11 +99,10 @@ organizations that have control over, are under the control of,
|
||||
or are under common control with that organization. **Control**
|
||||
means ownership of substantially all the assets of an entity,
|
||||
or the power to direct its management and policies by vote,
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
|
||||
**Your licenses** are all the licenses granted to you for the
|
||||
software under these terms.
|
||||
|
||||
**Use** means anything you do with the software requiring one
|
||||
of your licenses.
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ showcases the many awesome projects that exist in the _komorebi_ ecosystem.
|
||||
`komorebi` is [educational source
|
||||
software](https://lgug2z.com/articles/educational-source-software/).
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 1.0.0
|
||||
`komorebi` is licensed under the [Komorebi 2.0.0
|
||||
license](https://github.com/LGUG2Z/komorebi-license), which is a fork of the
|
||||
[PolyForm Strict 1.0.0
|
||||
license](https://polyformproject.org/licenses/strict/1.0.0). On a high level
|
||||
@@ -85,7 +85,7 @@ hard-forks) based on the software.
|
||||
Anyone is free to make their own fork of `komorebi` with changes intended either
|
||||
for personal use or for integration back upstream via pull requests.
|
||||
|
||||
The [Komorebi 1.0.0 License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
The [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does
|
||||
not permit any kind of commercial use (i.e. using `komorebi` at work).
|
||||
|
||||
## Sponsorship for Personal Use
|
||||
@@ -146,7 +146,8 @@ video will answer the majority of your questions.
|
||||
|
||||
[@amnweb](https://github.com/amnweb) showing _komorebi_ `v0.1.28` running on Windows 11 with window borders,
|
||||
unfocused window transparency and animations enabled, using a custom status bar integrated using
|
||||
_komorebi_'s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).
|
||||
_komorebi_'
|
||||
s [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).
|
||||
|
||||
https://github.com/LGUG2Z/komorebi/assets/13164844/21be8dc4-fa76-4f70-9b37-1d316f4b40c2
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ feature-depth = 1
|
||||
[advisories]
|
||||
ignore = [
|
||||
{ id = "RUSTSEC-2020-0016", reason = "local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt" },
|
||||
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" }
|
||||
{ id = "RUSTSEC-2024-0436", reason = "paste being unmaintained is not an issue in our use" },
|
||||
{ id = "RUSTSEC-2024-0320", reason = "not using any yaml features from this library" }
|
||||
]
|
||||
|
||||
[licenses]
|
||||
@@ -93,4 +94,6 @@ allow-git = [
|
||||
"https://github.com/LGUG2Z/catppuccin-egui",
|
||||
"https://github.com/LGUG2Z/windows-icons",
|
||||
"https://github.com/LGUG2Z/win32-display-data",
|
||||
"https://github.com/LGUG2Z/flavours",
|
||||
"https://github.com/LGUG2Z/base16_color_scheme",
|
||||
]
|
||||
|
||||
@@ -34,7 +34,7 @@ showcases the many awesome projects that exist in the `komorebi` ecosystem.
|
||||
|
||||
## Licensing for Personal Use
|
||||
|
||||
`komorebi` is licensed under the [Komorebi 1.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork
|
||||
`komorebi` is licensed under the [Komorebi 2.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork
|
||||
of the [PolyForm Strict 1.0.0 license](https://polyformproject.org/licenses/strict/1.0.0). On a high level this means
|
||||
that you are free to do whatever you want with `komorebi` for personal use other than redistribution, or distribution of
|
||||
new works (i.e. hard-forks) based on the software.
|
||||
@@ -42,7 +42,7 @@ new works (i.e. hard-forks) based on the software.
|
||||
Anyone is free to make their own fork of `komorebi` with changes intended either for personal use or for integration
|
||||
back upstream via pull requests.
|
||||
|
||||
The [Komorebi 1.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (
|
||||
The [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (
|
||||
i.e. using `komorebi` at work).
|
||||
|
||||
## Sponsorship for Personal Use
|
||||
|
||||
@@ -49,6 +49,7 @@ use komorebi_client::NotificationEvent;
|
||||
use komorebi_client::SocketMessage;
|
||||
use komorebi_themes::catppuccin_egui;
|
||||
use komorebi_themes::Base16Value;
|
||||
use komorebi_themes::Base16Wrapper;
|
||||
use komorebi_themes::Catppuccin;
|
||||
use komorebi_themes::CatppuccinValue;
|
||||
use std::cell::RefCell;
|
||||
@@ -155,7 +156,7 @@ pub fn apply_theme(
|
||||
} => {
|
||||
ctx.set_style(base16.style());
|
||||
let base16_value = base16_value.unwrap_or_default();
|
||||
let accent = base16_value.color32(base16);
|
||||
let accent = base16_value.color32(Base16Wrapper::Base16(base16));
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
@@ -165,6 +166,23 @@ pub fn apply_theme(
|
||||
|
||||
bg_color.replace(base16.background());
|
||||
}
|
||||
KomobarTheme::Custom {
|
||||
colours,
|
||||
accent: base16_value,
|
||||
} => {
|
||||
let background = colours.background();
|
||||
ctx.set_style(colours.style());
|
||||
let base16_value = base16_value.unwrap_or_default();
|
||||
let accent = base16_value.color32(Base16Wrapper::Custom(colours));
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
style.visuals.selection.stroke.color = accent;
|
||||
style.visuals.widgets.hovered.fg_stroke.color = accent;
|
||||
style.visuals.widgets.active.fg_stroke.color = accent;
|
||||
});
|
||||
|
||||
bg_color.replace(background);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply transparency_alpha
|
||||
@@ -431,11 +449,11 @@ impl Komobar {
|
||||
}
|
||||
|
||||
fn try_apply_theme(&mut self, ctx: &Context) {
|
||||
match self.config.theme {
|
||||
match &self.config.theme {
|
||||
Some(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
theme,
|
||||
theme.clone(),
|
||||
self.bg_color.clone(),
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
@@ -463,6 +481,26 @@ impl Komobar {
|
||||
match komorebi_client::StaticConfig::read(&config) {
|
||||
Ok(config) => {
|
||||
if let Some(theme) = config.theme {
|
||||
let stack_accent = match theme {
|
||||
KomorebiTheme::Catppuccin {
|
||||
name, stack_border, ..
|
||||
} => stack_border
|
||||
.unwrap_or(CatppuccinValue::Green)
|
||||
.color32(name.as_theme()),
|
||||
KomorebiTheme::Base16 {
|
||||
name, stack_border, ..
|
||||
} => stack_border
|
||||
.unwrap_or(Base16Value::Base0B)
|
||||
.color32(Base16Wrapper::Base16(name)),
|
||||
KomorebiTheme::Custom {
|
||||
ref colours,
|
||||
stack_border,
|
||||
..
|
||||
} => stack_border
|
||||
.unwrap_or(Base16Value::Base0B)
|
||||
.color32(Base16Wrapper::Custom(colours.clone())),
|
||||
};
|
||||
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
@@ -473,17 +511,6 @@ impl Komobar {
|
||||
self.render_config.clone(),
|
||||
);
|
||||
|
||||
let stack_accent = match theme {
|
||||
KomorebiTheme::Catppuccin {
|
||||
name, stack_border, ..
|
||||
} => stack_border
|
||||
.unwrap_or(CatppuccinValue::Green)
|
||||
.color32(name.as_theme()),
|
||||
KomorebiTheme::Base16 {
|
||||
name, stack_border, ..
|
||||
} => stack_border.unwrap_or(Base16Value::Base0B).color32(name),
|
||||
};
|
||||
|
||||
if let Some(state) = &self.komorebi_notification_state {
|
||||
state.borrow_mut().stack_accent = Some(stack_accent);
|
||||
}
|
||||
@@ -782,7 +809,7 @@ impl eframe::App for Komobar {
|
||||
self.bg_color_with_alpha.clone(),
|
||||
self.config.transparency_alpha,
|
||||
self.config.grouping,
|
||||
self.config.theme,
|
||||
self.config.theme.clone(),
|
||||
self.render_config.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ impl From<Position> for Pos2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "palette")]
|
||||
pub enum KomobarTheme {
|
||||
@@ -389,6 +389,12 @@ pub enum KomobarTheme {
|
||||
name: komorebi_themes::Base16,
|
||||
accent: Option<komorebi_themes::Base16Value>,
|
||||
},
|
||||
/// A custom Base16 theme
|
||||
Custom {
|
||||
/// Colours of the custom Base16 theme palette
|
||||
colours: Box<komorebi_themes::Base16ColourPalette>,
|
||||
accent: Option<komorebi_themes::Base16Value>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<KomorebiTheme> for KomobarTheme {
|
||||
@@ -406,6 +412,14 @@ impl From<KomorebiTheme> for KomobarTheme {
|
||||
name,
|
||||
accent: bar_accent,
|
||||
},
|
||||
KomorebiTheme::Custom {
|
||||
colours,
|
||||
bar_accent,
|
||||
..
|
||||
} => Self::Custom {
|
||||
colours,
|
||||
accent: bar_accent,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ use crate::widgets::widget::BarWidget;
|
||||
use crate::ICON_CACHE;
|
||||
use crate::MAX_LABEL_WIDTH;
|
||||
use crate::MONITOR_INDEX;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use eframe::egui::vec2;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui::ColorImage;
|
||||
use eframe::egui::Context;
|
||||
@@ -24,6 +26,7 @@ use eframe::egui::RichText;
|
||||
use eframe::egui::Sense;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::StrokeKind;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::TextureHandle;
|
||||
use eframe::egui::TextureOptions;
|
||||
use eframe::egui::Ui;
|
||||
@@ -55,8 +58,11 @@ pub struct KomorebiConfig {
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
/// Configure the Workspace Layer widget
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
/// Configure the Focused Window widget
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
/// Configure the Focused Container widget
|
||||
#[serde(alias = "focused_window")]
|
||||
pub focused_container: Option<KomorebiFocusedContainerConfig>,
|
||||
/// Configure the Locked Container widget
|
||||
pub locked_container: Option<KomorebiLockedContainerConfig>,
|
||||
/// Configure the Configuration Switcher widget
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
@@ -96,15 +102,26 @@ pub struct KomorebiWorkspaceLayerConfig {
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiFocusedWindowConfig {
|
||||
/// Enable the Komorebi Focused Window widget
|
||||
pub struct KomorebiFocusedContainerConfig {
|
||||
/// Enable the Komorebi Focused Container widget
|
||||
pub enable: bool,
|
||||
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused window)
|
||||
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused container)
|
||||
pub show_icon: Option<bool>,
|
||||
/// Display format of the currently focused window
|
||||
/// Display format of the currently focused container
|
||||
pub display: Option<DisplayFormat>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiLockedContainerConfig {
|
||||
/// Enable the Komorebi Locked Container widget
|
||||
pub enable: bool,
|
||||
/// Display format of the current locked state
|
||||
pub display: Option<DisplayFormat>,
|
||||
/// Show the widget event if the layer is unlocked
|
||||
pub show_when_unlocked: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiConfigurationSwitcherConfig {
|
||||
@@ -140,15 +157,19 @@ impl From<&KomorebiConfig> for Komorebi {
|
||||
.unwrap_or_default(),
|
||||
mouse_follows_focus: true,
|
||||
work_area_offset: None,
|
||||
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
|
||||
focused_container_information: (
|
||||
false,
|
||||
KomorebiNotificationStateContainerInformation::EMPTY,
|
||||
),
|
||||
stack_accent: None,
|
||||
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
|
||||
monitor_usr_idx_map: HashMap::new(),
|
||||
})),
|
||||
workspaces: value.workspaces,
|
||||
layout: value.layout.clone(),
|
||||
focused_window: value.focused_window,
|
||||
focused_container: value.focused_container,
|
||||
workspace_layer: value.workspace_layer,
|
||||
locked_container: value.locked_container,
|
||||
configuration_switcher,
|
||||
}
|
||||
}
|
||||
@@ -159,8 +180,9 @@ pub struct Komorebi {
|
||||
pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>,
|
||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||
pub layout: Option<KomorebiLayoutConfig>,
|
||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||
pub focused_container: Option<KomorebiFocusedContainerConfig>,
|
||||
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||
pub locked_container: Option<KomorebiLockedContainerConfig>,
|
||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||
}
|
||||
|
||||
@@ -178,9 +200,10 @@ impl BarWidget for Komorebi {
|
||||
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
for (i, (ws, containers, _)) in
|
||||
for (i, (ws, containers, _, should_show)) in
|
||||
komorebi_notification_state.workspaces.iter().enumerate()
|
||||
{
|
||||
if *should_show {
|
||||
let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
|
||||
|
||||
if SelectableFrame::new(
|
||||
@@ -302,6 +325,7 @@ impl BarWidget for Komorebi {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -318,7 +342,7 @@ impl BarWidget for Komorebi {
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
|
||||
.map(|(_, _, layer)| layer);
|
||||
.map(|(_, _, layer, _)| layer);
|
||||
|
||||
if let Some(layer) = layer {
|
||||
if (layer_config.show_when_tiling.unwrap_or_default()
|
||||
@@ -335,7 +359,7 @@ impl BarWidget for Komorebi {
|
||||
if matches!(layer, WorkspaceLayer::Tiling) {
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(size, Sense::hover());
|
||||
let color = ui.style().visuals.text_color();
|
||||
let color = ctx.style().visuals.selection.stroke.color;
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
let mut rect = response.rect;
|
||||
let corner =
|
||||
@@ -365,7 +389,7 @@ impl BarWidget for Komorebi {
|
||||
} else {
|
||||
let (response, painter) =
|
||||
ui.allocate_painter(size, Sense::hover());
|
||||
let color = ui.style().visuals.text_color();
|
||||
let color = ctx.style().visuals.selection.stroke.color;
|
||||
let stroke = Stroke::new(1.0, color);
|
||||
let mut rect = response.rect;
|
||||
let corner =
|
||||
@@ -502,18 +526,85 @@ impl BarWidget for Komorebi {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(focused_window) = self.focused_window {
|
||||
if focused_window.enable {
|
||||
if let Some(locked_container_config) = self.locked_container {
|
||||
if locked_container_config.enable {
|
||||
let is_locked = komorebi_notification_state.focused_container_information.0;
|
||||
|
||||
if locked_container_config
|
||||
.show_when_unlocked
|
||||
.unwrap_or_default()
|
||||
|| is_locked
|
||||
{
|
||||
let titles = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.1
|
||||
.titles;
|
||||
|
||||
if !titles.is_empty() {
|
||||
let display_format = locked_container_config
|
||||
.display
|
||||
.unwrap_or(DisplayFormat::Text);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
if display_format != DisplayFormat::Text {
|
||||
if is_locked {
|
||||
egui_phosphor::regular::LOCK_KEY.to_string()
|
||||
} else {
|
||||
egui_phosphor::regular::LOCK_SIMPLE_OPEN.to_string()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
if display_format != DisplayFormat::Icon {
|
||||
layout_job.append(
|
||||
if is_locked { "Locked" } else { "Unlocked" },
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
&& komorebi_client::send_batch([
|
||||
SocketMessage::FocusMonitorAtCursor,
|
||||
SocketMessage::ToggleLock,
|
||||
])
|
||||
.is_err()
|
||||
{
|
||||
tracing::error!("could not send ToggleLock");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(focused_container_config) = self.focused_container {
|
||||
if focused_container_config.enable {
|
||||
let titles = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.1
|
||||
.titles;
|
||||
|
||||
if !titles.is_empty() {
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
let icons = &komorebi_notification_state
|
||||
.focused_container_information
|
||||
.focused_container_information.1
|
||||
.icons;
|
||||
let focused_window_idx = komorebi_notification_state
|
||||
.focused_container_information
|
||||
.focused_container_information.1
|
||||
.focused_window_idx;
|
||||
|
||||
let iter = titles.iter().zip(icons.iter());
|
||||
@@ -521,13 +612,13 @@ impl BarWidget for Komorebi {
|
||||
|
||||
for (i, (title, icon)) in iter.enumerate() {
|
||||
let selected = i == focused_window_idx && len != 1;
|
||||
let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() };
|
||||
let text_color = if selected { ctx.style().visuals.selection.stroke.color } else { ui.style().visuals.text_color() };
|
||||
|
||||
if SelectableFrame::new(selected)
|
||||
.show(ui, |ui| {
|
||||
// handle legacy setting
|
||||
let format = focused_window.display.unwrap_or(
|
||||
if focused_window.show_icon.unwrap_or(false) {
|
||||
let format = focused_container_config.display.unwrap_or(
|
||||
if focused_container_config.show_icon.unwrap_or(false) {
|
||||
DisplayFormat::IconAndText
|
||||
} else {
|
||||
DisplayFormat::Text
|
||||
@@ -627,9 +718,10 @@ pub struct KomorebiNotificationState {
|
||||
String,
|
||||
Vec<(bool, KomorebiNotificationStateContainerInformation)>,
|
||||
WorkspaceLayer,
|
||||
bool,
|
||||
)>,
|
||||
pub selected_workspace: String,
|
||||
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
||||
pub focused_container_information: (bool, KomorebiNotificationStateContainerInformation),
|
||||
pub layout: KomorebiLayout,
|
||||
pub hide_empty_workspaces: bool,
|
||||
pub mouse_follows_focus: bool,
|
||||
@@ -695,7 +787,7 @@ impl KomorebiNotificationState {
|
||||
SocketMessage::Theme(theme) => {
|
||||
apply_theme(
|
||||
ctx,
|
||||
KomobarTheme::from(theme),
|
||||
KomobarTheme::from(*theme),
|
||||
bg_color,
|
||||
bg_color_with_alpha.clone(),
|
||||
transparency_alpha,
|
||||
@@ -742,42 +834,41 @@ impl KomorebiNotificationState {
|
||||
true
|
||||
};
|
||||
|
||||
if should_show {
|
||||
workspaces.push((
|
||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||
if show_all_icons {
|
||||
let mut containers = vec![];
|
||||
let mut has_monocle = false;
|
||||
workspaces.push((
|
||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||
if show_all_icons {
|
||||
let mut containers = vec![];
|
||||
let mut has_monocle = false;
|
||||
|
||||
// add monocle container
|
||||
if let Some(container) = ws.monocle_container() {
|
||||
containers.push((true, container.into()));
|
||||
has_monocle = true;
|
||||
}
|
||||
// add monocle container
|
||||
if let Some(container) = ws.monocle_container() {
|
||||
containers.push((true, container.into()));
|
||||
has_monocle = true;
|
||||
}
|
||||
|
||||
// add all tiled windows
|
||||
for (i, container) in ws.containers().iter().enumerate() {
|
||||
containers.push((
|
||||
!has_monocle && i == ws.focused_container_idx(),
|
||||
container.into(),
|
||||
));
|
||||
}
|
||||
// add all tiled windows
|
||||
for (i, container) in ws.containers().iter().enumerate() {
|
||||
containers.push((
|
||||
!has_monocle && i == ws.focused_container_idx(),
|
||||
container.into(),
|
||||
));
|
||||
}
|
||||
|
||||
// add all floating windows
|
||||
for floating_window in ws.floating_windows() {
|
||||
containers.push((
|
||||
!has_monocle && floating_window.is_focused(),
|
||||
floating_window.into(),
|
||||
));
|
||||
}
|
||||
// add all floating windows
|
||||
for floating_window in ws.floating_windows() {
|
||||
containers.push((
|
||||
!has_monocle && floating_window.is_focused(),
|
||||
floating_window.into(),
|
||||
));
|
||||
}
|
||||
|
||||
containers
|
||||
} else {
|
||||
vec![(true, ws.into())]
|
||||
},
|
||||
ws.layer().to_owned(),
|
||||
));
|
||||
}
|
||||
containers
|
||||
} else {
|
||||
vec![(true, ws.into())]
|
||||
},
|
||||
ws.layer().to_owned(),
|
||||
should_show,
|
||||
));
|
||||
}
|
||||
|
||||
self.workspaces = workspaces;
|
||||
@@ -798,7 +889,12 @@ impl KomorebiNotificationState {
|
||||
};
|
||||
}
|
||||
|
||||
self.focused_container_information = (&monitor.workspaces()[focused_workspace_idx]).into();
|
||||
let focused_workspace = &monitor.workspaces()[focused_workspace_idx];
|
||||
let is_focused = focused_workspace
|
||||
.locked_containers()
|
||||
.contains(&focused_workspace.focused_container_idx());
|
||||
|
||||
self.focused_container_information = (is_focused, focused_workspace.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ impl KomorebiLayout {
|
||||
let layout_frame = SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
|
||||
self.show_icon(false, font_id.clone(), ctx, ui);
|
||||
self.show_icon(true, font_id.clone(), ctx, ui);
|
||||
}
|
||||
|
||||
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
|
||||
|
||||
@@ -72,7 +72,7 @@ impl WidgetConfig {
|
||||
WidgetConfig::Komorebi(config) => {
|
||||
config.workspaces.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.layout.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.focused_window.as_ref().is_some_and(|w| w.enable)
|
||||
|| config.focused_container.as_ref().is_some_and(|w| w.enable)
|
||||
|| config
|
||||
.configuration_switcher
|
||||
.as_ref()
|
||||
|
||||
@@ -5,8 +5,6 @@ pub use komorebi::animation::prefix::AnimationPrefix;
|
||||
pub use komorebi::animation::PerAnimationPrefixConfig;
|
||||
pub use komorebi::asc::ApplicationSpecificConfiguration;
|
||||
pub use komorebi::border_manager::BorderInfo;
|
||||
pub use komorebi::colour::Colour;
|
||||
pub use komorebi::colour::Rgb;
|
||||
pub use komorebi::config_generation::ApplicationConfiguration;
|
||||
pub use komorebi::config_generation::IdWithIdentifier;
|
||||
pub use komorebi::config_generation::IdWithIdentifierAndComment;
|
||||
@@ -29,6 +27,7 @@ pub use komorebi::core::CustomLayout;
|
||||
pub use komorebi::core::CycleDirection;
|
||||
pub use komorebi::core::DefaultLayout;
|
||||
pub use komorebi::core::Direction;
|
||||
pub use komorebi::core::FloatingLayerBehaviour;
|
||||
pub use komorebi::core::FocusFollowsMouseImplementation;
|
||||
pub use komorebi::core::HidingBehaviour;
|
||||
pub use komorebi::core::Layout;
|
||||
@@ -56,6 +55,7 @@ pub use komorebi::AnimationsConfig;
|
||||
pub use komorebi::AppSpecificConfigurationPath;
|
||||
pub use komorebi::AspectRatio;
|
||||
pub use komorebi::BorderColours;
|
||||
pub use komorebi::Colour;
|
||||
pub use komorebi::CrossBoundaryBehaviour;
|
||||
pub use komorebi::GlobalState;
|
||||
pub use komorebi::KomorebiTheme;
|
||||
@@ -63,6 +63,7 @@ pub use komorebi::MonitorConfig;
|
||||
pub use komorebi::Notification;
|
||||
pub use komorebi::NotificationEvent;
|
||||
pub use komorebi::PredefinedAspectRatio;
|
||||
pub use komorebi::Rgb;
|
||||
pub use komorebi::RuleDebug;
|
||||
pub use komorebi::StackbarConfig;
|
||||
pub use komorebi::State;
|
||||
|
||||
@@ -41,7 +41,9 @@ struct BorderColours {
|
||||
single: Color32,
|
||||
stack: Color32,
|
||||
monocle: Color32,
|
||||
floating: Color32,
|
||||
unfocused: Color32,
|
||||
unfocused_locked: Color32,
|
||||
}
|
||||
|
||||
struct BorderConfig {
|
||||
@@ -154,7 +156,9 @@ impl KomorebiGui {
|
||||
single: colour32(global_state.border_colours.single),
|
||||
stack: colour32(global_state.border_colours.stack),
|
||||
monocle: colour32(global_state.border_colours.monocle),
|
||||
floating: colour32(global_state.border_colours.floating),
|
||||
unfocused: colour32(global_state.border_colours.unfocused),
|
||||
unfocused_locked: colour32(global_state.border_colours.unfocused_locked),
|
||||
};
|
||||
|
||||
let border_config = BorderConfig {
|
||||
@@ -377,6 +381,22 @@ impl eframe::App for KomorebiGui {
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Floating", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.border_config.border_colours.floating,
|
||||
Alpha::Opaque,
|
||||
) {
|
||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
||||
WindowKind::Floating,
|
||||
self.border_config.border_colours.floating.r() as u32,
|
||||
self.border_config.border_colours.floating.g() as u32,
|
||||
self.border_config.border_colours.floating.b() as u32,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Unfocused", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
@@ -391,6 +411,22 @@ impl eframe::App for KomorebiGui {
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ui.collapsing("Unfocused Locked", |ui| {
|
||||
if egui::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.border_config.border_colours.unfocused_locked,
|
||||
Alpha::Opaque,
|
||||
) {
|
||||
komorebi_client::send_message(&SocketMessage::BorderColour(
|
||||
WindowKind::UnfocusedLocked,
|
||||
self.border_config.border_colours.unfocused_locked.r() as u32,
|
||||
self.border_config.border_colours.unfocused_locked.g() as u32,
|
||||
self.border_config.border_colours.unfocused_locked.b() as u32,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@ base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev
|
||||
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
|
||||
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
|
||||
eframe = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_variant = "0.1"
|
||||
strum = { workspace = true }
|
||||
hex_color = { version = "3", features = ["serde"] }
|
||||
flavours = { git = "https://github.com/LGUG2Z/flavours", version = "0.7.2" }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use hex_color::HexColor;
|
||||
use komorebi_themes::Color32;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::gen::SchemaGenerator;
|
||||
#[cfg(feature = "schemars")]
|
||||
@@ -9,6 +8,7 @@ use schemars::schema::Schema;
|
||||
#[cfg(feature = "schemars")]
|
||||
use schemars::schema::SchemaObject;
|
||||
|
||||
use crate::Color32;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -57,7 +57,7 @@ impl From<Colour> for Color32 {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Hex(HexColor);
|
||||
pub struct Hex(pub HexColor);
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for Hex {
|
||||
77
komorebi-themes/src/generator.rs
Normal file
77
komorebi-themes/src/generator.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use crate::colour::Colour;
|
||||
use crate::colour::Hex;
|
||||
use crate::Base16ColourPalette;
|
||||
use hex_color::HexColor;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ThemeVariant {
|
||||
#[default]
|
||||
Dark,
|
||||
Light,
|
||||
}
|
||||
|
||||
impl Display for ThemeVariant {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ThemeVariant::Dark => write!(f, "dark"),
|
||||
ThemeVariant::Light => write!(f, "light"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThemeVariant> for flavours::operations::generate::Mode {
|
||||
fn from(value: ThemeVariant) -> Self {
|
||||
match value {
|
||||
ThemeVariant::Dark => Self::Dark,
|
||||
ThemeVariant::Light => Self::Light,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_base16_palette(
|
||||
image_path: &Path,
|
||||
variant: ThemeVariant,
|
||||
) -> Result<Base16ColourPalette, hex_color::ParseHexColorError> {
|
||||
Base16ColourPalette::try_from(
|
||||
&flavours::operations::generate::generate(image_path, variant.into(), false)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
impl TryFrom<&VecDeque<String>> for Base16ColourPalette {
|
||||
type Error = hex_color::ParseHexColorError;
|
||||
|
||||
fn try_from(value: &VecDeque<String>) -> Result<Self, Self::Error> {
|
||||
let fixed = value.iter().map(|s| format!("#{s}")).collect::<Vec<_>>();
|
||||
if fixed.len() != 16 {
|
||||
return Err(hex_color::ParseHexColorError::Empty);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
base_00: Colour::Hex(Hex(HexColor::parse(&fixed[0])?)),
|
||||
base_01: Colour::Hex(Hex(HexColor::parse(&fixed[1])?)),
|
||||
base_02: Colour::Hex(Hex(HexColor::parse(&fixed[2])?)),
|
||||
base_03: Colour::Hex(Hex(HexColor::parse(&fixed[3])?)),
|
||||
base_04: Colour::Hex(Hex(HexColor::parse(&fixed[4])?)),
|
||||
base_05: Colour::Hex(Hex(HexColor::parse(&fixed[5])?)),
|
||||
base_06: Colour::Hex(Hex(HexColor::parse(&fixed[6])?)),
|
||||
base_07: Colour::Hex(Hex(HexColor::parse(&fixed[7])?)),
|
||||
base_08: Colour::Hex(Hex(HexColor::parse(&fixed[8])?)),
|
||||
base_09: Colour::Hex(Hex(HexColor::parse(&fixed[9])?)),
|
||||
base_0a: Colour::Hex(Hex(HexColor::parse(&fixed[10])?)),
|
||||
base_0b: Colour::Hex(Hex(HexColor::parse(&fixed[11])?)),
|
||||
base_0c: Colour::Hex(Hex(HexColor::parse(&fixed[12])?)),
|
||||
base_0d: Colour::Hex(Hex(HexColor::parse(&fixed[13])?)),
|
||||
base_0e: Colour::Hex(Hex(HexColor::parse(&fixed[14])?)),
|
||||
base_0f: Colour::Hex(Hex(HexColor::parse(&fixed[15])?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,32 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
pub mod colour;
|
||||
mod generator;
|
||||
|
||||
pub use generator::generate_base16_palette;
|
||||
pub use generator::ThemeVariant;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::colour::Colour;
|
||||
pub use base16_egui_themes::Base16;
|
||||
pub use catppuccin_egui;
|
||||
use eframe::egui::style::Selection;
|
||||
use eframe::egui::style::WidgetVisuals;
|
||||
use eframe::egui::style::Widgets;
|
||||
pub use eframe::egui::Color32;
|
||||
use eframe::egui::Shadow;
|
||||
use eframe::egui::Stroke;
|
||||
use eframe::egui::Style;
|
||||
use eframe::egui::Visuals;
|
||||
use serde_variant::to_variant_name;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Theme {
|
||||
/// A theme from catppuccin-egui
|
||||
@@ -25,6 +39,140 @@ pub enum Theme {
|
||||
name: Base16,
|
||||
accent: Option<Base16Value>,
|
||||
},
|
||||
/// A custom base16 palette
|
||||
Custom {
|
||||
palette: Box<Base16ColourPalette>,
|
||||
accent: Option<Base16Value>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct Base16ColourPalette {
|
||||
pub base_00: Colour,
|
||||
pub base_01: Colour,
|
||||
pub base_02: Colour,
|
||||
pub base_03: Colour,
|
||||
pub base_04: Colour,
|
||||
pub base_05: Colour,
|
||||
pub base_06: Colour,
|
||||
pub base_07: Colour,
|
||||
pub base_08: Colour,
|
||||
pub base_09: Colour,
|
||||
pub base_0a: Colour,
|
||||
pub base_0b: Colour,
|
||||
pub base_0c: Colour,
|
||||
pub base_0d: Colour,
|
||||
pub base_0e: Colour,
|
||||
pub base_0f: Colour,
|
||||
}
|
||||
|
||||
impl Base16ColourPalette {
|
||||
pub fn background(self) -> Color32 {
|
||||
self.base_01.into()
|
||||
}
|
||||
pub fn style(self) -> Style {
|
||||
let original = Style::default();
|
||||
Style {
|
||||
visuals: Visuals {
|
||||
widgets: Widgets {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: self.base_01.into(),
|
||||
weak_bg_fill: self.base_01.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.widgets.noninteractive.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_05.into(),
|
||||
..original.visuals.widgets.noninteractive.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.noninteractive
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
|
||||
..original.visuals.widgets.inactive.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_05.into(),
|
||||
..original.visuals.widgets.inactive.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.inactive
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_03.into(),
|
||||
..original.visuals.widgets.hovered.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.hovered.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.hovered
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: self.base_02.into(),
|
||||
weak_bg_fill: self.base_02.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_03.into(),
|
||||
..original.visuals.widgets.hovered.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.hovered.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.active
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: self.base_01.into(),
|
||||
weak_bg_fill: self.base_01.into(),
|
||||
bg_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.widgets.open.bg_stroke
|
||||
},
|
||||
fg_stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.widgets.open.fg_stroke
|
||||
},
|
||||
..original.visuals.widgets.open
|
||||
},
|
||||
},
|
||||
selection: Selection {
|
||||
bg_fill: self.base_02.into(),
|
||||
stroke: Stroke {
|
||||
color: self.base_06.into(),
|
||||
..original.visuals.selection.stroke
|
||||
},
|
||||
},
|
||||
hyperlink_color: self.base_08.into(),
|
||||
faint_bg_color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
|
||||
extreme_bg_color: self.base_00.into(),
|
||||
code_bg_color: self.base_02.into(),
|
||||
warn_fg_color: self.base_0c.into(),
|
||||
error_fg_color: self.base_0b.into(),
|
||||
window_shadow: Shadow {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
|
||||
..original.visuals.window_shadow
|
||||
},
|
||||
window_fill: self.base_01.into(),
|
||||
window_stroke: Stroke {
|
||||
color: self.base_02.into(),
|
||||
..original.visuals.window_stroke
|
||||
},
|
||||
panel_fill: self.base_01.into(),
|
||||
popup_shadow: Shadow {
|
||||
color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
|
||||
..original.visuals.popup_shadow
|
||||
},
|
||||
..original.visuals
|
||||
},
|
||||
..original
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
@@ -45,6 +193,7 @@ impl Theme {
|
||||
.to_string()
|
||||
})
|
||||
.collect(),
|
||||
Theme::Custom { .. } => vec!["Custom".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,25 +219,50 @@ pub enum Base16Value {
|
||||
Base0F,
|
||||
}
|
||||
|
||||
pub enum Base16Wrapper {
|
||||
Base16(Base16),
|
||||
Custom(Box<Base16ColourPalette>),
|
||||
}
|
||||
|
||||
impl Base16Value {
|
||||
pub fn color32(&self, theme: Base16) -> Color32 {
|
||||
match self {
|
||||
Base16Value::Base00 => theme.base00(),
|
||||
Base16Value::Base01 => theme.base01(),
|
||||
Base16Value::Base02 => theme.base02(),
|
||||
Base16Value::Base03 => theme.base03(),
|
||||
Base16Value::Base04 => theme.base04(),
|
||||
Base16Value::Base05 => theme.base05(),
|
||||
Base16Value::Base06 => theme.base06(),
|
||||
Base16Value::Base07 => theme.base07(),
|
||||
Base16Value::Base08 => theme.base08(),
|
||||
Base16Value::Base09 => theme.base09(),
|
||||
Base16Value::Base0A => theme.base0a(),
|
||||
Base16Value::Base0B => theme.base0b(),
|
||||
Base16Value::Base0C => theme.base0c(),
|
||||
Base16Value::Base0D => theme.base0d(),
|
||||
Base16Value::Base0E => theme.base0e(),
|
||||
Base16Value::Base0F => theme.base0f(),
|
||||
pub fn color32(&self, theme: Base16Wrapper) -> Color32 {
|
||||
match theme {
|
||||
Base16Wrapper::Base16(theme) => match self {
|
||||
Base16Value::Base00 => theme.base00(),
|
||||
Base16Value::Base01 => theme.base01(),
|
||||
Base16Value::Base02 => theme.base02(),
|
||||
Base16Value::Base03 => theme.base03(),
|
||||
Base16Value::Base04 => theme.base04(),
|
||||
Base16Value::Base05 => theme.base05(),
|
||||
Base16Value::Base06 => theme.base06(),
|
||||
Base16Value::Base07 => theme.base07(),
|
||||
Base16Value::Base08 => theme.base08(),
|
||||
Base16Value::Base09 => theme.base09(),
|
||||
Base16Value::Base0A => theme.base0a(),
|
||||
Base16Value::Base0B => theme.base0b(),
|
||||
Base16Value::Base0C => theme.base0c(),
|
||||
Base16Value::Base0D => theme.base0d(),
|
||||
Base16Value::Base0E => theme.base0e(),
|
||||
Base16Value::Base0F => theme.base0f(),
|
||||
},
|
||||
Base16Wrapper::Custom(colours) => match self {
|
||||
Base16Value::Base00 => colours.base_00.into(),
|
||||
Base16Value::Base01 => colours.base_01.into(),
|
||||
Base16Value::Base02 => colours.base_02.into(),
|
||||
Base16Value::Base03 => colours.base_03.into(),
|
||||
Base16Value::Base04 => colours.base_04.into(),
|
||||
Base16Value::Base05 => colours.base_05.into(),
|
||||
Base16Value::Base06 => colours.base_06.into(),
|
||||
Base16Value::Base07 => colours.base_07.into(),
|
||||
Base16Value::Base08 => colours.base_08.into(),
|
||||
Base16Value::Base09 => colours.base_09.into(),
|
||||
Base16Value::Base0A => colours.base_0a.into(),
|
||||
Base16Value::Base0B => colours.base_0b.into(),
|
||||
Base16Value::Base0C => colours.base_0c.into(),
|
||||
Base16Value::Base0D => colours.base_0d.into(),
|
||||
Base16Value::Base0E => colours.base_0e.into(),
|
||||
Base16Value::Base0F => colours.base_0f.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ ctrlc = { version = "3", features = ["termination"] }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
getset = "0.1"
|
||||
hex_color = { version = "3", features = ["serde"] }
|
||||
hotwatch = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
miow = "0.6"
|
||||
|
||||
@@ -7,9 +7,6 @@ use crate::core::WindowKind;
|
||||
use crate::ring::Ring;
|
||||
use crate::windows_api;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::Colour;
|
||||
use crate::Rgb;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use border::border_hwnds;
|
||||
@@ -18,6 +15,8 @@ use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use komorebi_themes::colour::Colour;
|
||||
use komorebi_themes::colour::Rgb;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
@@ -333,8 +332,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
if !BORDER_ENABLED.load_consume()
|
||||
// Or if the wm is paused
|
||||
|| is_paused
|
||||
// Or if we are handling an alt-tab across workspaces
|
||||
|| ALT_TAB_HWND.load().is_some()
|
||||
{
|
||||
// Destroy the borders we know about
|
||||
for (_, border) in borders.drain() {
|
||||
|
||||
@@ -163,7 +163,7 @@ pub enum SocketMessage {
|
||||
WatchConfiguration(bool),
|
||||
CompleteConfiguration,
|
||||
AltFocusHack(bool),
|
||||
Theme(KomorebiTheme),
|
||||
Theme(Box<KomorebiTheme>),
|
||||
Animation(bool, Option<AnimationPrefix>),
|
||||
AnimationDuration(u64, Option<AnimationPrefix>),
|
||||
AnimationFps(u64),
|
||||
@@ -329,6 +329,7 @@ pub enum StateQuery {
|
||||
FocusedContainerIndex,
|
||||
FocusedWindowIndex,
|
||||
FocusedWorkspaceName,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -378,6 +379,18 @@ pub enum WindowContainerBehaviour {
|
||||
Append,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum FloatingLayerBehaviour {
|
||||
/// Tile new windows (unless they match a float rule)
|
||||
#[default]
|
||||
Tile,
|
||||
/// Float new windows
|
||||
Float,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum MoveBehaviour {
|
||||
|
||||
@@ -5,7 +5,6 @@ pub mod border_manager;
|
||||
pub mod com;
|
||||
#[macro_use]
|
||||
pub mod ring;
|
||||
pub mod colour;
|
||||
pub mod container;
|
||||
pub mod core;
|
||||
pub mod focus_manager;
|
||||
@@ -30,7 +29,6 @@ pub mod windows_callbacks;
|
||||
pub mod winevent;
|
||||
pub mod winevent_listener;
|
||||
pub mod workspace;
|
||||
pub mod workspace_reconciliator;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use monitor_reconciliator::MonitorNotification;
|
||||
@@ -48,8 +46,8 @@ use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use colour::*;
|
||||
pub use core::*;
|
||||
pub use komorebi_themes::colour::*;
|
||||
pub use process_command::*;
|
||||
pub use process_event::*;
|
||||
pub use static_config::*;
|
||||
@@ -240,6 +238,8 @@ pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
|
||||
|
||||
shadow_rs::shadow!(build);
|
||||
|
||||
#[must_use]
|
||||
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
|
||||
@@ -16,6 +16,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
use crossbeam_utils::Backoff;
|
||||
use komorebi::animation::AnimationEngine;
|
||||
@@ -24,6 +25,7 @@ use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
use parking_lot::deadlock;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use sysinfo::Process;
|
||||
use sysinfo::ProcessesToUpdate;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
@@ -48,16 +50,13 @@ use komorebi::window_manager::State;
|
||||
use komorebi::window_manager::WindowManager;
|
||||
use komorebi::windows_api::WindowsApi;
|
||||
use komorebi::winevent_listener;
|
||||
use komorebi::workspace_reconciliator;
|
||||
use komorebi::CUSTOM_FFM;
|
||||
use komorebi::DATA_DIR;
|
||||
use komorebi::HOME_DIR;
|
||||
use komorebi::INITIAL_CONFIGURATION_LOADED;
|
||||
use komorebi::SESSION_ID;
|
||||
|
||||
shadow_rs::shadow!(build);
|
||||
|
||||
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
fn setup(log_level: LogLevel) -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
}
|
||||
@@ -65,7 +64,16 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
std::env::set_var(
|
||||
"RUST_LOG",
|
||||
match log_level {
|
||||
LogLevel::Error => "error",
|
||||
LogLevel::Warn => "warn",
|
||||
LogLevel::Info => "info",
|
||||
LogLevel::Debug => "debug",
|
||||
LogLevel::Trace => "trace",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
|
||||
@@ -143,8 +151,19 @@ fn detect_deadlocks() {
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, ValueEnum, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
#[default]
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author, about, version = build::CLAP_LONG_VERSION)]
|
||||
#[clap(author, about, version = komorebi::build::CLAP_LONG_VERSION)]
|
||||
struct Opts {
|
||||
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
||||
#[clap(short, long = "ffm")]
|
||||
@@ -161,6 +180,9 @@ struct Opts {
|
||||
/// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi
|
||||
#[clap(long)]
|
||||
clean_state: bool,
|
||||
/// Level of log output verbosity
|
||||
#[clap(long, value_enum, default_value_t=LogLevel::Info)]
|
||||
log_level: LogLevel,
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -198,7 +220,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
// File logging worker guard has to have an assignment in the main fn to work
|
||||
let (_guard, _color_guard) = setup()?;
|
||||
let (_guard, _color_guard) = setup(opts.log_level)?;
|
||||
|
||||
WindowsApi::foreground_lock_timeout()?;
|
||||
|
||||
@@ -278,7 +300,6 @@ fn main() -> Result<()> {
|
||||
border_manager::listen_for_notifications(wm.clone());
|
||||
stackbar_manager::listen_for_notifications(wm.clone());
|
||||
transparency_manager::listen_for_notifications(wm.clone());
|
||||
workspace_reconciliator::listen_for_notifications(wm.clone());
|
||||
monitor_reconciliator::listen_for_notifications(wm.clone())?;
|
||||
reaper::listen_for_notifications(wm.clone(), wm.lock().known_hwnds.clone());
|
||||
focus_manager::listen_for_notifications(wm.clone());
|
||||
|
||||
@@ -370,20 +370,20 @@ impl Monitor {
|
||||
.position(|w| w.hwnd == foreground_hwnd);
|
||||
|
||||
if let Some(idx) = floating_window_index {
|
||||
let window = workspace.floating_windows_mut().remove(idx);
|
||||
if let Some(window) = workspace.floating_windows_mut().remove(idx) {
|
||||
let workspaces = self.workspaces_mut();
|
||||
#[allow(clippy::option_if_let_else)]
|
||||
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
||||
None => {
|
||||
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
||||
workspaces.get_mut(target_workspace_idx).unwrap()
|
||||
}
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
let workspaces = self.workspaces_mut();
|
||||
#[allow(clippy::option_if_let_else)]
|
||||
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
||||
None => {
|
||||
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
||||
workspaces.get_mut(target_workspace_idx).unwrap()
|
||||
}
|
||||
Some(workspace) => workspace,
|
||||
};
|
||||
|
||||
target_workspace.floating_windows_mut().push(window);
|
||||
target_workspace.set_layer(WorkspaceLayer::Floating);
|
||||
target_workspace.floating_windows_mut().push_back(window);
|
||||
target_workspace.set_layer(WorkspaceLayer::Floating);
|
||||
}
|
||||
} else {
|
||||
let container = workspace
|
||||
.remove_focused_container()
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::sync::OnceLock;
|
||||
|
||||
pub mod hidden;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum MonitorNotification {
|
||||
@@ -727,3 +727,304 @@ where
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crossbeam_channel::bounded;
|
||||
use crossbeam_channel::Sender;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
use windows::Win32::Devices::Display::DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;
|
||||
// NOTE: Using RECT instead of RECT since I get a mismatched type error. Can be updated if
|
||||
// needed.
|
||||
use windows::Win32::Foundation::RECT;
|
||||
|
||||
// Creating a Mock Display Provider
|
||||
#[derive(Clone)]
|
||||
struct MockDevice {
|
||||
hmonitor: isize,
|
||||
device_path: String,
|
||||
device_name: String,
|
||||
device_description: String,
|
||||
serial_number_id: Option<String>,
|
||||
size: RECT,
|
||||
work_area_size: RECT,
|
||||
device_key: String,
|
||||
output_technology: Option<DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY>,
|
||||
}
|
||||
|
||||
impl From<MockDevice> for win32_display_data::Device {
|
||||
fn from(mock: MockDevice) -> Self {
|
||||
win32_display_data::Device {
|
||||
hmonitor: mock.hmonitor,
|
||||
device_path: mock.device_path,
|
||||
device_name: mock.device_name,
|
||||
device_description: mock.device_description,
|
||||
serial_number_id: mock.serial_number_id,
|
||||
size: mock.size,
|
||||
work_area_size: mock.work_area_size,
|
||||
device_key: mock.device_key,
|
||||
output_technology: mock.output_technology,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a Window Manager Instance
|
||||
struct TestContext {
|
||||
socket_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Drop for TestContext {
|
||||
fn drop(&mut self) {
|
||||
if let Some(socket_path) = &self.socket_path {
|
||||
// Clean up the socket file
|
||||
if let Err(e) = std::fs::remove_file(socket_path) {
|
||||
tracing::warn!("Failed to remove socket file: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_window_manager() -> (WindowManager, TestContext) {
|
||||
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
||||
bounded(1);
|
||||
|
||||
// Temporary socket path for testing
|
||||
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
|
||||
let socket_path = PathBuf::from(socket_name);
|
||||
|
||||
// Create a new WindowManager instance
|
||||
let wm = match WindowManager::new(receiver, Some(socket_path.clone())) {
|
||||
Ok(manager) => manager,
|
||||
Err(e) => {
|
||||
panic!("Failed to create WindowManager: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
wm,
|
||||
TestContext {
|
||||
socket_path: Some(socket_path),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_notification() {
|
||||
// Create a monitor notification
|
||||
let notification = MonitorNotification::ResolutionScalingChanged;
|
||||
|
||||
// Use the send_notification function to send the notification
|
||||
send_notification(notification);
|
||||
|
||||
// Receive the notification from the channel
|
||||
let received = event_rx().try_recv();
|
||||
|
||||
// Check if we received the notification and if it matches what we sent
|
||||
match received {
|
||||
Ok(notification) => {
|
||||
assert_eq!(notification, MonitorNotification::ResolutionScalingChanged);
|
||||
}
|
||||
Err(e) => panic!("Failed to receive MonitorNotification: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_channel_bounded_capacity() {
|
||||
let (_, receiver) = channel();
|
||||
|
||||
// Fill the channel to its capacity (20 messages)
|
||||
for _ in 0..20 {
|
||||
send_notification(MonitorNotification::WorkAreaChanged);
|
||||
}
|
||||
|
||||
// Attempt to send another message (should be dropped)
|
||||
send_notification(MonitorNotification::ResolutionScalingChanged);
|
||||
|
||||
// Verify the channel contains only the first 20 messages
|
||||
for _ in 0..20 {
|
||||
let notification = match receiver.try_recv() {
|
||||
Ok(notification) => notification,
|
||||
Err(e) => panic!("Failed to receive MonitorNotification: {}", e),
|
||||
};
|
||||
assert_eq!(
|
||||
notification,
|
||||
MonitorNotification::WorkAreaChanged,
|
||||
"Unexpected notification in the channel"
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that no additional messages are in the channel
|
||||
assert!(
|
||||
receiver.try_recv().is_err(),
|
||||
"Channel should be empty after consuming all messages"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_in_monitor_cache() {
|
||||
let m = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"Test Monitor".to_string(),
|
||||
"Test Device".to_string(),
|
||||
"Test Device ID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
// Insert the monitor into the cache
|
||||
insert_in_monitor_cache("TestMonitorID", m.clone());
|
||||
|
||||
// Retrieve the monitor from the cache
|
||||
let cache = MONITOR_CACHE
|
||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
.lock();
|
||||
let retrieved_monitor = cache.get("TestMonitorID");
|
||||
|
||||
// Check that the monitor was inserted correctly and matches the expected value
|
||||
assert_eq!(retrieved_monitor, Some(&m));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_two_monitors_cache() {
|
||||
let m1 = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"Test Monitor".to_string(),
|
||||
"Test Device".to_string(),
|
||||
"Test Device ID".to_string(),
|
||||
Some("TestMonitorID".to_string()),
|
||||
);
|
||||
|
||||
let m2 = monitor::new(
|
||||
0,
|
||||
Rect::default(),
|
||||
Rect::default(),
|
||||
"Test Monitor 2".to_string(),
|
||||
"Test Device 2".to_string(),
|
||||
"Test Device ID 2".to_string(),
|
||||
Some("TestMonitorID2".to_string()),
|
||||
);
|
||||
|
||||
// Insert the first monitor into the cache
|
||||
insert_in_monitor_cache("TestMonitorID", m1.clone());
|
||||
|
||||
// Insert the second monitor into the cache
|
||||
insert_in_monitor_cache("TestMonitorID2", m2.clone());
|
||||
|
||||
// Retrieve the cache to check if the first and second monitors are present
|
||||
let cache = MONITOR_CACHE
|
||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
.lock();
|
||||
|
||||
// Check if Monitor 1 was found in the cache
|
||||
assert_eq!(
|
||||
cache.get("TestMonitorID"),
|
||||
Some(&m1),
|
||||
"Monitor cache should contain monitor 1"
|
||||
);
|
||||
|
||||
// Check if Monitor 2 was found in the cache
|
||||
assert_eq!(
|
||||
cache.get("TestMonitorID2"),
|
||||
Some(&m2),
|
||||
"Monitor cache should contain monitor 2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_listen_for_notifications() {
|
||||
// Create a WindowManager instance for testing
|
||||
let (wm, _test_context) = setup_window_manager();
|
||||
|
||||
// Start the notification listener
|
||||
let result = listen_for_notifications(Arc::new(Mutex::new(wm)));
|
||||
|
||||
// Check if the listener started successfully
|
||||
assert!(result.is_ok(), "Failed to start notification listener");
|
||||
|
||||
// Test sending a notification
|
||||
send_notification(MonitorNotification::DisplayConnectionChange);
|
||||
|
||||
// Receive the notification from the channel
|
||||
let received = event_rx().try_recv();
|
||||
|
||||
// Check if we received the notification and if it matches what we sent
|
||||
match received {
|
||||
Ok(notification) => {
|
||||
assert_eq!(notification, MonitorNotification::DisplayConnectionChange);
|
||||
}
|
||||
Err(e) => panic!("Failed to receive MonitorNotification: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attached_display_devices() {
|
||||
// Define mock display data
|
||||
let mock_monitor = MockDevice {
|
||||
hmonitor: 1,
|
||||
device_path: String::from(
|
||||
"\\\\?\\DISPLAY#ABC123#4&123456&0&UID0#{saucepackets-4321-5678-2468-abc123456789}",
|
||||
),
|
||||
device_name: String::from("\\\\.\\DISPLAY1"),
|
||||
device_description: String::from("Display description"),
|
||||
serial_number_id: Some(String::from("SaucePackets123")),
|
||||
device_key: String::from("Mock Key"),
|
||||
size: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 1920,
|
||||
bottom: 1080,
|
||||
},
|
||||
work_area_size: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 1920,
|
||||
bottom: 1080,
|
||||
},
|
||||
output_technology: Some(DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY(0)),
|
||||
};
|
||||
|
||||
// Create a closure to simulate the display provider
|
||||
let display_provider = || {
|
||||
vec![Ok::<win32_display_data::Device, win32_display_data::Error>(
|
||||
win32_display_data::Device::from(mock_monitor.clone()),
|
||||
)]
|
||||
.into_iter()
|
||||
};
|
||||
|
||||
// Should contain the mock monitor
|
||||
let result = attached_display_devices(display_provider).ok();
|
||||
if let Some(monitors) = result {
|
||||
// Check Number of monitors
|
||||
assert_eq!(monitors.len(), 1, "Expected one monitor");
|
||||
|
||||
// hmonitor
|
||||
assert_eq!(monitors[0].id(), 1);
|
||||
|
||||
// device name
|
||||
assert_eq!(monitors[0].name(), &String::from("DISPLAY1"));
|
||||
|
||||
// Device
|
||||
assert_eq!(monitors[0].device(), &String::from("ABC123"));
|
||||
|
||||
// Device ID
|
||||
assert_eq!(
|
||||
monitors[0].device_id(),
|
||||
&String::from("ABC123-4&123456&0&UID0")
|
||||
);
|
||||
|
||||
// Check monitor serial number id
|
||||
assert_eq!(
|
||||
monitors[0].serial_number_id,
|
||||
Some(String::from("SaucePackets123")),
|
||||
);
|
||||
} else {
|
||||
panic!("No monitors found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use color_eyre::Result;
|
||||
use komorebi_themes::colour::Rgb;
|
||||
use miow::pipe::connect;
|
||||
use net2::TcpStreamExt;
|
||||
use parking_lot::Mutex;
|
||||
@@ -19,9 +20,18 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
||||
use crate::animation::ANIMATION_FPS;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::build;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::config_generation::IdWithIdentifier;
|
||||
use crate::core::config_generation::MatchingRule;
|
||||
use crate::core::config_generation::MatchingStrategy;
|
||||
@@ -38,16 +48,6 @@ use crate::core::SocketMessage;
|
||||
use crate::core::StateQuery;
|
||||
use crate::core::WindowContainerBehaviour;
|
||||
use crate::core::WindowKind;
|
||||
|
||||
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
||||
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
||||
use crate::animation::ANIMATION_FPS;
|
||||
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::colour::Rgb;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::current_virtual_desktop;
|
||||
use crate::monitor::MonitorInformation;
|
||||
use crate::notify_subscribers;
|
||||
@@ -1197,11 +1197,33 @@ impl WindowManager {
|
||||
WorkspaceLayer::Tiling => {
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
|
||||
for (i, window) in workspace.floating_windows().iter().enumerate() {
|
||||
if i == 0 {
|
||||
let focused_idx = workspace.focused_floating_window_idx();
|
||||
let mut window_idx_pairs = workspace
|
||||
.floating_windows_mut()
|
||||
.make_contiguous()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by window area
|
||||
window_idx_pairs.sort_by_key(|(_, w)| {
|
||||
let rect = WindowsApi::window_rect(w.hwnd).unwrap_or_default();
|
||||
rect.right * rect.bottom
|
||||
});
|
||||
window_idx_pairs.reverse();
|
||||
|
||||
for (i, window) in window_idx_pairs {
|
||||
if i == focused_idx {
|
||||
to_focus = Some(*window);
|
||||
} else {
|
||||
window.raise()?;
|
||||
}
|
||||
window.raise()?;
|
||||
}
|
||||
|
||||
if let Some(focused_window) = &to_focus {
|
||||
// The focused window should be the last one raised to make sure it is
|
||||
// on top
|
||||
focused_window.raise()?;
|
||||
}
|
||||
|
||||
for container in workspace.containers() {
|
||||
@@ -1223,7 +1245,19 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
for window in workspace.floating_windows() {
|
||||
let mut window_idx_pairs = workspace
|
||||
.floating_windows_mut()
|
||||
.make_contiguous()
|
||||
.iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by window area
|
||||
window_idx_pairs.sort_by_key(|w| {
|
||||
let rect = WindowsApi::window_rect(w.hwnd).unwrap_or_default();
|
||||
rect.right * rect.bottom
|
||||
});
|
||||
|
||||
for window in window_idx_pairs {
|
||||
window.lower()?;
|
||||
}
|
||||
}
|
||||
@@ -1343,6 +1377,7 @@ impl WindowManager {
|
||||
.focused_workspace_name()
|
||||
.unwrap_or_else(|| focused_monitor.focused_workspace_idx().to_string())
|
||||
}
|
||||
StateQuery::Version => build::RUST_VERSION.to_string(),
|
||||
};
|
||||
|
||||
reply.write_all(response.as_bytes())?;
|
||||
@@ -2057,8 +2092,8 @@ impl WindowManager {
|
||||
|
||||
reply.write_all(schema.as_bytes())?;
|
||||
}
|
||||
SocketMessage::Theme(theme) => {
|
||||
theme_manager::send_notification(theme);
|
||||
SocketMessage::Theme(ref theme) => {
|
||||
theme_manager::send_notification(*theme.clone());
|
||||
}
|
||||
// Deprecated commands
|
||||
SocketMessage::AltFocusHack(_)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
@@ -27,12 +25,10 @@ use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
use crate::workspace::WorkspaceLayer;
|
||||
use crate::workspace_reconciliator;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
|
||||
use crate::Notification;
|
||||
use crate::NotificationEvent;
|
||||
use crate::State;
|
||||
use crate::Window;
|
||||
use crate::FLOATING_APPLICATIONS;
|
||||
use crate::HIDDEN_HWNDS;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
@@ -304,34 +300,7 @@ impl WindowManager {
|
||||
let focused_workspace_idx =
|
||||
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
|
||||
let mut needs_reconciliation = false;
|
||||
|
||||
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if focused_pair != (*m_idx, *w_idx) {
|
||||
// At this point we know we are going to send a notification to the workspace reconciliator
|
||||
// So we get the topmost window returned by EnumWindows, which is almost always the window
|
||||
// that has been selected by alt-tab
|
||||
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
|
||||
if let Some(first) =
|
||||
alt_tab_windows.iter().find(|w| w.title().is_ok())
|
||||
{
|
||||
// If our record of this HWND hasn't been updated in over a minute
|
||||
let mut instant = ALT_TAB_HWND_INSTANT.lock();
|
||||
if instant.elapsed().gt(&Duration::from_secs(1)) {
|
||||
// Update our record with the HWND we just found
|
||||
ALT_TAB_HWND.store(Some(first.hwnd));
|
||||
// Update the timestamp of our record
|
||||
*instant = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workspace_reconciliator::send_notification(*m_idx, *w_idx);
|
||||
needs_reconciliation = true;
|
||||
}
|
||||
}
|
||||
let mut needs_reconciliation = None;
|
||||
|
||||
// There are some applications such as Firefox where, if they are focused when a
|
||||
// workspace switch takes place, it will fire an additional Show event, which will
|
||||
@@ -340,6 +309,23 @@ impl WindowManager {
|
||||
// duplicates across multiple workspaces, as it results in ghost layout tiles.
|
||||
let mut proceed = true;
|
||||
|
||||
// Check for potential `alt-tab` event
|
||||
if matches!(
|
||||
event,
|
||||
WindowManagerEvent::Uncloak(_, _) | WindowManagerEvent::Show(_, _)
|
||||
) {
|
||||
needs_reconciliation = self.needs_reconciliation(window)?;
|
||||
|
||||
if let Some((m_idx, ws_idx)) = needs_reconciliation {
|
||||
self.perform_reconciliation(window, (m_idx, ws_idx))?;
|
||||
|
||||
// Since there was a reconciliation after an `alt-tab`, that means this
|
||||
// window is already handled by komorebi so we shouldn't proceed with
|
||||
// adding it as a new window.
|
||||
proceed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if let Some(focused_workspace_idx) = self
|
||||
.monitors()
|
||||
@@ -368,7 +354,7 @@ impl WindowManager {
|
||||
let workspace_contains_window = workspace.contains_window(window.hwnd);
|
||||
let monocle_container = workspace.monocle_container().clone();
|
||||
|
||||
if !workspace_contains_window && !needs_reconciliation {
|
||||
if !workspace_contains_window && needs_reconciliation.is_none() {
|
||||
let floating_applications = FLOATING_APPLICATIONS.lock();
|
||||
let mut should_float = false;
|
||||
|
||||
@@ -402,7 +388,7 @@ impl WindowManager {
|
||||
matches!(workspace.layer, WorkspaceLayer::Floating)
|
||||
&& !should_float
|
||||
&& workspace.tile;
|
||||
workspace.floating_windows_mut().push(window);
|
||||
workspace.floating_windows_mut().push_back(window);
|
||||
workspace.set_layer(WorkspaceLayer::Floating);
|
||||
if center_spawned_floats {
|
||||
let mut floating_window = window;
|
||||
@@ -630,7 +616,7 @@ impl WindowManager {
|
||||
window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
} else if window_management_behaviour.float_override {
|
||||
workspace.floating_windows_mut().push(window);
|
||||
workspace.floating_windows_mut().push_back(window);
|
||||
self.update_focused_workspace(false, false)?;
|
||||
} else {
|
||||
match window_management_behaviour.current_behaviour {
|
||||
@@ -753,4 +739,119 @@ impl WindowManager {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if this window is from another unfocused workspace or is an unfocused window on a
|
||||
/// stack container. If it is it will return the monitor/workspace index pair of this window so
|
||||
/// that a reconciliation of that monitor/workspace can be done.
|
||||
fn needs_reconciliation(&self, window: Window) -> color_eyre::Result<Option<(usize, usize)>> {
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
let focused_workspace_idx =
|
||||
self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
|
||||
let mut needs_reconciliation = None;
|
||||
|
||||
if let Some((m_idx, ws_idx)) = self.known_hwnds.get(&window.hwnd) {
|
||||
if (*m_idx, *ws_idx) == focused_pair {
|
||||
if let Some(target_workspace) = self
|
||||
.monitors()
|
||||
.get(*m_idx)
|
||||
.and_then(|m| m.workspaces().get(*ws_idx))
|
||||
{
|
||||
if let Some(monocle_with_window) = target_workspace
|
||||
.monocle_container()
|
||||
.as_ref()
|
||||
.and_then(|m| m.contains_window(window.hwnd).then_some(m))
|
||||
{
|
||||
if monocle_with_window.focused_window() != Some(&window) {
|
||||
tracing::debug!("Needs reconciliation within a monocled stack");
|
||||
needs_reconciliation = Some((*m_idx, *ws_idx));
|
||||
}
|
||||
} else {
|
||||
let c_idx = target_workspace.container_idx_for_window(window.hwnd);
|
||||
|
||||
if let Some(target_container) =
|
||||
c_idx.and_then(|c_idx| target_workspace.containers().get(c_idx))
|
||||
{
|
||||
if target_container.focused_window() != Some(&window) {
|
||||
tracing::debug!(
|
||||
"Needs reconciliation within a stack on the focused workspace"
|
||||
);
|
||||
needs_reconciliation = Some((*m_idx, *ws_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("Needs reconciliation for a different monitor/workspace pair");
|
||||
needs_reconciliation = Some((*m_idx, *ws_idx));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(needs_reconciliation)
|
||||
}
|
||||
|
||||
/// When there was an `alt-tab` to a hidden window we need to perform a reconciliation, meaning
|
||||
/// we need to update the focused monitor, workspace, container and window indices to the ones
|
||||
/// corresponding to the window the user just alt-tabbed into.
|
||||
fn perform_reconciliation(
|
||||
&mut self,
|
||||
window: Window,
|
||||
reconciliation_pair: (usize, usize),
|
||||
) -> color_eyre::Result<()> {
|
||||
let (m_idx, ws_idx) = reconciliation_pair;
|
||||
|
||||
tracing::debug!("performing reconciliation");
|
||||
self.focus_monitor(m_idx)?;
|
||||
let mouse_follows_focus = self.mouse_follows_focus;
|
||||
let offset = self.work_area_offset;
|
||||
|
||||
if let Some(monitor) = self.focused_monitor_mut() {
|
||||
if ws_idx != monitor.focused_workspace_idx() {
|
||||
let previous_idx = monitor.focused_workspace_idx();
|
||||
monitor.set_last_focused_workspace(Option::from(previous_idx));
|
||||
monitor.focus_workspace(ws_idx)?;
|
||||
}
|
||||
if let Some(workspace) = monitor.focused_workspace_mut() {
|
||||
let mut layer = WorkspaceLayer::Tiling;
|
||||
if let Some((monocle, idx)) = workspace
|
||||
.monocle_container_mut()
|
||||
.as_mut()
|
||||
.and_then(|m| m.idx_for_window(window.hwnd).map(|i| (m, i)))
|
||||
{
|
||||
monocle.focus_window(idx);
|
||||
} else if workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.any(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
layer = WorkspaceLayer::Floating;
|
||||
} else if !workspace
|
||||
.maximized_window()
|
||||
.is_some_and(|w| w.hwnd == window.hwnd)
|
||||
{
|
||||
// If the window is the maximized window do nothing, else we
|
||||
// reintegrate the monocle if it exists and then focus the
|
||||
// container
|
||||
if workspace.monocle_container().is_some() {
|
||||
tracing::info!("disabling monocle");
|
||||
for container in workspace.containers_mut() {
|
||||
container.restore();
|
||||
}
|
||||
for window in workspace.floating_windows_mut() {
|
||||
window.restore();
|
||||
}
|
||||
workspace.reintegrate_monocle_container()?;
|
||||
}
|
||||
workspace.focus_container_by_window(window.hwnd)?;
|
||||
}
|
||||
workspace.set_layer(layer);
|
||||
}
|
||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
monitor.update_focused_workspace(offset)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,4 +76,36 @@ macro_rules! impl_ring_elements {
|
||||
}
|
||||
}
|
||||
};
|
||||
// This allows passing a different name to be used for the functions. For instance, the
|
||||
// `floating_windows` ring calls this as:
|
||||
// ```rust
|
||||
// impl_ring_elements!(Workspace, Window, "floating_window");
|
||||
// ```
|
||||
// Which allows using the `Window` element but name the functions as `floating_window`
|
||||
($name:ty, $element:ident, $el_name:literal) => {
|
||||
paste::paste! {
|
||||
impl $name {
|
||||
pub const fn [<$el_name:lower s>](&self) -> &VecDeque<$element> {
|
||||
self.[<$el_name:lower s>].elements()
|
||||
}
|
||||
|
||||
pub fn [<$el_name:lower s_mut>](&mut self) -> &mut VecDeque<$element> {
|
||||
self.[<$el_name:lower s>].elements_mut()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn [<focused_ $el_name:lower>](&self) -> Option<&$element> {
|
||||
self.[<$el_name:lower s>].focused()
|
||||
}
|
||||
|
||||
pub const fn [<focused_ $el_name:lower _idx>](&self) -> usize {
|
||||
self.[<$el_name:lower s>].focused_idx()
|
||||
}
|
||||
|
||||
pub fn [<focused_ $el_name:lower _mut>](&mut self) -> Option<&mut $element> {
|
||||
self.[<$el_name:lower s>].focused_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::border_manager;
|
||||
use crate::border_manager::ZOrder;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::colour::Colour;
|
||||
use crate::config_generation::WorkspaceMatchingRule;
|
||||
use crate::core::config_generation::ApplicationConfiguration;
|
||||
use crate::core::config_generation::ApplicationConfigurationGenerator;
|
||||
@@ -60,6 +59,7 @@ use crate::workspace::Workspace;
|
||||
use crate::AspectRatio;
|
||||
use crate::Axis;
|
||||
use crate::CrossBoundaryBehaviour;
|
||||
use crate::FloatingLayerBehaviour;
|
||||
use crate::PredefinedAspectRatio;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
@@ -86,6 +86,7 @@ use color_eyre::Result;
|
||||
use crossbeam_channel::Receiver;
|
||||
use hotwatch::EventKind;
|
||||
use hotwatch::Hotwatch;
|
||||
use komorebi_themes::colour::Colour;
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
@@ -118,6 +119,60 @@ pub struct BorderColours {
|
||||
/// Border colour when the container is unfocused
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused: Option<Colour>,
|
||||
/// Border colour when the container is unfocused and locked
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused_locked: Option<Colour>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ThemeOptions {
|
||||
/// Specify Light or Dark variant for theme generation (default: Dark)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub theme_variant: Option<komorebi_themes::ThemeVariant>,
|
||||
/// Border colour when the container contains a single window (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub single_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container contains multiple windows (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stack_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is in monocle mode (default: Base0F)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub monocle_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the window is floating (default: Base09)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub floating_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused and locked (default: Base08)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unfocused_locked_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar focused tab text colour (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stackbar_focused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar unfocused tab text colour (default: Base05)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar tab background colour (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stackbar_background: Option<komorebi_themes::Base16Value>,
|
||||
/// Komorebi status bar accent (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bar_accent: Option<komorebi_themes::Base16Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Wallpaper {
|
||||
/// Path to the wallpaper image file
|
||||
pub path: PathBuf,
|
||||
/// Generate and apply Base16 theme for this wallpaper (default: true)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub generate_theme: Option<bool>,
|
||||
/// Specify Light or Dark variant for theme generation (default: Dark)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub theme_options: Option<ThemeOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
@@ -164,6 +219,12 @@ pub struct WorkspaceConfig {
|
||||
/// Specify an axis on which to flip the selected layout (default: None)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub layout_flip: Option<Axis>,
|
||||
/// Determine what happens to a new window when the Floating workspace layer is active (default: Tile)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
|
||||
/// Specify a wallpaper for this workspace
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub wallpaper: Option<Wallpaper>,
|
||||
}
|
||||
|
||||
impl From<&Workspace> for WorkspaceConfig {
|
||||
@@ -239,6 +300,8 @@ impl From<&Workspace> for WorkspaceConfig {
|
||||
window_container_behaviour_rules: Option::from(window_container_behaviour_rules),
|
||||
float_override: *value.float_override(),
|
||||
layout_flip: value.layout_flip(),
|
||||
floating_layer_behaviour: Option::from(*value.floating_layer_behaviour()),
|
||||
wallpaper: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,7 +540,7 @@ pub struct AnimationsConfig {
|
||||
pub fps: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(tag = "palette")]
|
||||
pub enum KomorebiTheme {
|
||||
@@ -551,6 +614,41 @@ pub enum KomorebiTheme {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
bar_accent: Option<komorebi_themes::Base16Value>,
|
||||
},
|
||||
/// A custom Base16 theme
|
||||
Custom {
|
||||
/// Colours of the custom Base16 theme palette
|
||||
colours: Box<komorebi_themes::Base16ColourPalette>,
|
||||
/// Border colour when the container contains a single window (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
single_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container contains multiple windows (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stack_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is in monocle mode (default: Base0F)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
monocle_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the window is floating (default: Base09)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
floating_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Border colour when the container is unfocused and locked (default: Base08)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unfocused_locked_border: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar focused tab text colour (default: Base0B)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar unfocused tab text colour (default: Base05)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
|
||||
/// Stackbar tab background colour (default: Base01)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
stackbar_background: Option<komorebi_themes::Base16Value>,
|
||||
/// Komorebi status bar accent (default: Base0D)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
bar_accent: Option<komorebi_themes::Base16Value>,
|
||||
},
|
||||
}
|
||||
|
||||
impl StaticConfig {
|
||||
@@ -695,6 +793,9 @@ impl From<&WindowManager> for StaticConfig {
|
||||
unfocused: Option::from(Colour::from(
|
||||
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
||||
)),
|
||||
unfocused_locked: Option::from(Colour::from(
|
||||
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
|
||||
)),
|
||||
})
|
||||
};
|
||||
|
||||
@@ -876,6 +977,11 @@ impl StaticConfig {
|
||||
if let Some(unfocused) = colours.unfocused {
|
||||
border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if let Some(unfocused_locked) = colours.unfocused_locked {
|
||||
border_manager::UNFOCUSED_LOCKED
|
||||
.store(u32::from(unfocused_locked), Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
STYLE.store(self.border_style.unwrap_or_default());
|
||||
@@ -1016,7 +1122,7 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
if let Some(theme) = &self.theme {
|
||||
theme_manager::send_notification(*theme);
|
||||
theme_manager::send_notification(theme.clone());
|
||||
}
|
||||
|
||||
if let Some(path) = &self.app_specific_configuration_path {
|
||||
|
||||
@@ -5,11 +5,12 @@ use crate::stackbar_manager;
|
||||
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
||||
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
||||
use crate::Colour;
|
||||
use crate::KomorebiTheme;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use komorebi_themes::colour::Colour;
|
||||
use komorebi_themes::Base16Wrapper;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::OnceLock;
|
||||
@@ -157,39 +158,100 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
|
||||
} => {
|
||||
let single_border = single_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0D)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stack_border = stack_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let monocle_border = monocle_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0F)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let unfocused_border = unfocused_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let unfocused_locked_border = unfocused_locked_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base08)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let floating_border = floating_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base09)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stackbar_focused_text = stackbar_focused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stackbar_unfocused_text = stackbar_unfocused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base05)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
let stackbar_background = stackbar_background
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(*name);
|
||||
.color32(Base16Wrapper::Base16(*name));
|
||||
|
||||
(
|
||||
single_border,
|
||||
stack_border,
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
)
|
||||
}
|
||||
KomorebiTheme::Custom {
|
||||
colours,
|
||||
single_border,
|
||||
stack_border,
|
||||
monocle_border,
|
||||
floating_border,
|
||||
unfocused_border,
|
||||
unfocused_locked_border,
|
||||
stackbar_focused_text,
|
||||
stackbar_unfocused_text,
|
||||
stackbar_background,
|
||||
..
|
||||
} => {
|
||||
let single_border = single_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0D)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stack_border = stack_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let monocle_border = monocle_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0F)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let unfocused_border = unfocused_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let unfocused_locked_border = unfocused_locked_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base08)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let floating_border = floating_border
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base09)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stackbar_focused_text = stackbar_focused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base0B)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stackbar_unfocused_text = stackbar_unfocused_text
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base05)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
let stackbar_background = stackbar_background
|
||||
.unwrap_or(komorebi_themes::Base16Value::Base01)
|
||||
.color32(Base16Wrapper::Custom(colours.clone()));
|
||||
|
||||
(
|
||||
single_border,
|
||||
|
||||
@@ -80,6 +80,7 @@ use crate::workspace::WorkspaceLayer;
|
||||
use crate::BorderColours;
|
||||
use crate::Colour;
|
||||
use crate::CrossBoundaryBehaviour;
|
||||
use crate::FloatingLayerBehaviour;
|
||||
use crate::Rgb;
|
||||
use crate::CUSTOM_FFM;
|
||||
use crate::DATA_DIR;
|
||||
@@ -247,6 +248,9 @@ impl Default for GlobalState {
|
||||
unfocused: Option::from(Colour::Rgb(Rgb::from(
|
||||
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
||||
))),
|
||||
unfocused_locked: Option::from(Colour::Rgb(Rgb::from(
|
||||
border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),
|
||||
))),
|
||||
},
|
||||
border_style: STYLE.load(),
|
||||
border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),
|
||||
@@ -341,8 +345,10 @@ impl From<&WindowManager> for State {
|
||||
.clone(),
|
||||
float_override: workspace.float_override,
|
||||
layer: workspace.layer,
|
||||
floating_layer_behaviour: workspace.floating_layer_behaviour,
|
||||
globals: workspace.globals,
|
||||
locked_containers: workspace.locked_containers.clone(),
|
||||
wallpaper: workspace.wallpaper.clone(),
|
||||
workspace_config: None,
|
||||
})
|
||||
.collect::<VecDeque<_>>();
|
||||
@@ -644,10 +650,14 @@ impl WindowManager {
|
||||
self.window_management_behaviour.float_override
|
||||
};
|
||||
|
||||
// If the workspace layer is `Floating`, then consider it as if it had float
|
||||
// override so that new windows spawn as floating
|
||||
float_override =
|
||||
float_override || matches!(workspace.layer, WorkspaceLayer::Floating);
|
||||
// If the workspace layer is `Floating` and the floating layer behaviour is `Float`,
|
||||
// then consider it as if it had float override so that new windows spawn as floating
|
||||
float_override = float_override
|
||||
|| (matches!(workspace.layer, WorkspaceLayer::Floating)
|
||||
&& matches!(
|
||||
workspace.floating_layer_behaviour,
|
||||
FloatingLayerBehaviour::Float
|
||||
));
|
||||
|
||||
return WindowManagementBehaviour {
|
||||
current_behaviour,
|
||||
@@ -968,7 +978,7 @@ impl WindowManager {
|
||||
if op.floating {
|
||||
target_workspace
|
||||
.floating_windows_mut()
|
||||
.push(Window::from(op.hwnd));
|
||||
.push_back(Window::from(op.hwnd));
|
||||
} else {
|
||||
//TODO(alex-ds13): should this take into account the target workspace
|
||||
//`window_container_behaviour`?
|
||||
@@ -1145,18 +1155,18 @@ impl WindowManager {
|
||||
// There is no need to physically move the floating window between areas with
|
||||
// `move_to_area` because the user already did that, so we only need to transfer the
|
||||
// window to the target `floating_windows`
|
||||
let floating_window = origin_workspace.floating_windows_mut().remove(idx);
|
||||
if let Some(floating_window) = origin_workspace.floating_windows_mut().remove(idx) {
|
||||
let target_workspace = self
|
||||
.monitors_mut()
|
||||
.get_mut(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
|
||||
.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no focused workspace for this monitor"))?;
|
||||
|
||||
let target_workspace = self
|
||||
.monitors_mut()
|
||||
.get_mut(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
|
||||
.focused_workspace_mut()
|
||||
.ok_or_else(|| anyhow!("there is no focused workspace for this monitor"))?;
|
||||
|
||||
target_workspace
|
||||
.floating_windows_mut()
|
||||
.push(floating_window);
|
||||
target_workspace
|
||||
.floating_windows_mut()
|
||||
.push_back(floating_window);
|
||||
}
|
||||
} else if origin_workspace
|
||||
.monocle_container()
|
||||
.as_ref()
|
||||
@@ -1830,7 +1840,7 @@ impl WindowManager {
|
||||
.position(|w| w.hwnd == foreground_hwnd);
|
||||
|
||||
let floating_window =
|
||||
floating_window_index.map(|idx| workspace.floating_windows_mut().remove(idx));
|
||||
floating_window_index.and_then(|idx| workspace.floating_windows_mut().remove(idx));
|
||||
let container = if floating_window_index.is_none() {
|
||||
Some(
|
||||
workspace
|
||||
@@ -1859,7 +1869,7 @@ impl WindowManager {
|
||||
.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);
|
||||
target_workspace.floating_windows_mut().push_back(window);
|
||||
target_workspace.set_layer(WorkspaceLayer::Floating);
|
||||
Window::from(window.hwnd)
|
||||
.move_to_area(¤t_area, target_monitor.work_area_size())?;
|
||||
@@ -1978,45 +1988,251 @@ impl WindowManager {
|
||||
direction: OperationDirection,
|
||||
) -> Result<()> {
|
||||
let mouse_follows_focus = self.mouse_follows_focus;
|
||||
let focused_workspace = self.focused_workspace()?;
|
||||
let focused_workspace = self.focused_workspace_mut()?;
|
||||
|
||||
let mut target_idx = None;
|
||||
let len = focused_workspace.floating_windows().len();
|
||||
|
||||
if len > 1 {
|
||||
let focused_hwnd = WindowsApi::foreground_window()?;
|
||||
for (idx, window) in focused_workspace.floating_windows().iter().enumerate() {
|
||||
if window.hwnd == focused_hwnd {
|
||||
match direction {
|
||||
OperationDirection::Left => {}
|
||||
OperationDirection::Right => {}
|
||||
OperationDirection::Up => {
|
||||
if idx == len - 1 {
|
||||
target_idx = Some(0)
|
||||
} else {
|
||||
target_idx = Some(idx + 1)
|
||||
}
|
||||
}
|
||||
OperationDirection::Down => {
|
||||
if idx == 0 {
|
||||
target_idx = Some(len - 1)
|
||||
} else {
|
||||
target_idx = Some(idx - 1)
|
||||
}
|
||||
}
|
||||
let focused_rect = WindowsApi::window_rect(focused_hwnd)?;
|
||||
match direction {
|
||||
OperationDirection::Left => {
|
||||
let mut windows_in_direction = focused_workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, w)| {
|
||||
(w.hwnd != focused_hwnd)
|
||||
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
|
||||
})
|
||||
.flatten()
|
||||
.flat_map(|(idx, r)| {
|
||||
(r.left < focused_rect.left)
|
||||
.then_some((idx, i32::abs(r.left - focused_rect.left)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by distance to focused
|
||||
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
|
||||
|
||||
if let Some((idx, _)) = windows_in_direction.first() {
|
||||
target_idx = Some(*idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
OperationDirection::Right => {
|
||||
let mut windows_in_direction = focused_workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, w)| {
|
||||
(w.hwnd != focused_hwnd)
|
||||
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
|
||||
})
|
||||
.flatten()
|
||||
.flat_map(|(idx, r)| {
|
||||
(r.left > focused_rect.left)
|
||||
.then_some((idx, i32::abs(r.left - focused_rect.left)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if target_idx.is_none() {
|
||||
target_idx = Some(0);
|
||||
}
|
||||
// Sort by distance to focused
|
||||
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
|
||||
|
||||
if let Some((idx, _)) = windows_in_direction.first() {
|
||||
target_idx = Some(*idx);
|
||||
}
|
||||
}
|
||||
OperationDirection::Up => {
|
||||
let mut windows_in_direction = focused_workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, w)| {
|
||||
(w.hwnd != focused_hwnd)
|
||||
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
|
||||
})
|
||||
.flatten()
|
||||
.flat_map(|(idx, r)| {
|
||||
(r.top < focused_rect.top)
|
||||
.then_some((idx, i32::abs(r.top - focused_rect.top)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by distance to focused
|
||||
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
|
||||
|
||||
if let Some((idx, _)) = windows_in_direction.first() {
|
||||
target_idx = Some(*idx);
|
||||
}
|
||||
}
|
||||
OperationDirection::Down => {
|
||||
let mut windows_in_direction = focused_workspace
|
||||
.floating_windows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, w)| {
|
||||
(w.hwnd != focused_hwnd)
|
||||
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
|
||||
})
|
||||
.flatten()
|
||||
.flat_map(|(idx, r)| {
|
||||
(r.top > focused_rect.top)
|
||||
.then_some((idx, i32::abs(r.top - focused_rect.top)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by distance to focused
|
||||
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
|
||||
|
||||
if let Some((idx, _)) = windows_in_direction.first() {
|
||||
target_idx = Some(*idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(idx) = target_idx {
|
||||
focused_workspace.floating_windows.focus(idx);
|
||||
if let Some(window) = focused_workspace.floating_windows().get(idx) {
|
||||
window.focus(mouse_follows_focus)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cross_monitor_monocle_or_max = false;
|
||||
|
||||
let workspace_idx = self.focused_workspace_idx()?;
|
||||
|
||||
// this is for when we are scrolling across workspaces like PaperWM
|
||||
if matches!(
|
||||
self.cross_boundary_behaviour,
|
||||
CrossBoundaryBehaviour::Workspace
|
||||
) && matches!(
|
||||
direction,
|
||||
OperationDirection::Left | OperationDirection::Right
|
||||
) {
|
||||
let workspace_count = if let Some(monitor) = self.focused_monitor() {
|
||||
monitor.workspaces().len()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let next_idx = match direction {
|
||||
OperationDirection::Left => match workspace_idx {
|
||||
0 => workspace_count - 1,
|
||||
n => n - 1,
|
||||
},
|
||||
OperationDirection::Right => match workspace_idx {
|
||||
n if n == workspace_count - 1 => 0,
|
||||
n => n + 1,
|
||||
},
|
||||
_ => workspace_idx,
|
||||
};
|
||||
|
||||
self.focus_workspace(next_idx)?;
|
||||
|
||||
if let Ok(focused_workspace) = self.focused_workspace_mut() {
|
||||
if focused_workspace.monocle_container().is_none() {
|
||||
match direction {
|
||||
OperationDirection::Left => match focused_workspace.layout() {
|
||||
Layout::Default(layout) => {
|
||||
let target_index =
|
||||
layout.rightmost_index(focused_workspace.containers().len());
|
||||
focused_workspace.focus_container(target_index);
|
||||
}
|
||||
Layout::Custom(_) => {
|
||||
focused_workspace.focus_container(
|
||||
focused_workspace.containers().len().saturating_sub(1),
|
||||
);
|
||||
}
|
||||
},
|
||||
OperationDirection::Right => match focused_workspace.layout() {
|
||||
Layout::Default(layout) => {
|
||||
let target_index =
|
||||
layout.leftmost_index(focused_workspace.containers().len());
|
||||
focused_workspace.focus_container(target_index);
|
||||
}
|
||||
Layout::Custom(_) => {
|
||||
focused_workspace.focus_container(0);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if there is no floating_window in that direction for this workspace
|
||||
let monitor_idx = self
|
||||
.monitor_idx_in_direction(direction)
|
||||
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
|
||||
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
let mouse_follows_focus = self.mouse_follows_focus;
|
||||
|
||||
if let Ok(focused_workspace) = self.focused_workspace_mut() {
|
||||
if let Some(window) = focused_workspace.maximized_window() {
|
||||
window.focus(mouse_follows_focus)?;
|
||||
cross_monitor_monocle_or_max = true;
|
||||
} else if let Some(monocle) = focused_workspace.monocle_container() {
|
||||
if let Some(window) = monocle.focused_window() {
|
||||
window.focus(mouse_follows_focus)?;
|
||||
cross_monitor_monocle_or_max = true;
|
||||
}
|
||||
} else if focused_workspace.layer() == &WorkspaceLayer::Tiling {
|
||||
match direction {
|
||||
OperationDirection::Left => match focused_workspace.layout() {
|
||||
Layout::Default(layout) => {
|
||||
let target_index =
|
||||
layout.rightmost_index(focused_workspace.containers().len());
|
||||
focused_workspace.focus_container(target_index);
|
||||
}
|
||||
Layout::Custom(_) => {
|
||||
focused_workspace.focus_container(
|
||||
focused_workspace.containers().len().saturating_sub(1),
|
||||
);
|
||||
}
|
||||
},
|
||||
OperationDirection::Right => match focused_workspace.layout() {
|
||||
Layout::Default(layout) => {
|
||||
let target_index =
|
||||
layout.leftmost_index(focused_workspace.containers().len());
|
||||
focused_workspace.focus_container(target_index);
|
||||
}
|
||||
Layout::Custom(_) => {
|
||||
focused_workspace.focus_container(0);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if !cross_monitor_monocle_or_max {
|
||||
let ws = self.focused_workspace_mut()?;
|
||||
if ws.is_empty() {
|
||||
// This is to remove focus from the previous monitor
|
||||
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!());
|
||||
}
|
||||
}
|
||||
} else if ws.layer() == &WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {
|
||||
if let Some(window) = ws.focused_floating_window() {
|
||||
window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
} else {
|
||||
ws.set_layer(WorkspaceLayer::Tiling);
|
||||
if let Ok(focused_window) = self.focused_window() {
|
||||
focused_window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2123,7 +2339,7 @@ impl WindowManager {
|
||||
window.focus(mouse_follows_focus)?;
|
||||
cross_monitor_monocle_or_max = true;
|
||||
}
|
||||
} else {
|
||||
} else if focused_workspace.layer() == &WorkspaceLayer::Tiling {
|
||||
match direction {
|
||||
OperationDirection::Left => match focused_workspace.layout() {
|
||||
Layout::Default(layout) => {
|
||||
@@ -2159,8 +2375,26 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
if !cross_monitor_monocle_or_max {
|
||||
if let Ok(focused_window) = self.focused_window_mut() {
|
||||
focused_window.focus(self.mouse_follows_focus)?;
|
||||
let ws = self.focused_workspace_mut()?;
|
||||
if ws.is_empty() {
|
||||
// This is to remove focus from the previous monitor
|
||||
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!());
|
||||
}
|
||||
}
|
||||
} else if ws.layer() == &WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {
|
||||
if let Some(window) = ws.focused_floating_window() {
|
||||
window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
} else {
|
||||
ws.set_layer(WorkspaceLayer::Tiling);
|
||||
if let Ok(focused_window) = self.focused_window() {
|
||||
focused_window.focus(self.mouse_follows_focus)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2875,7 +3109,7 @@ impl WindowManager {
|
||||
|
||||
let window = workspace
|
||||
.floating_windows_mut()
|
||||
.last_mut()
|
||||
.back_mut()
|
||||
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
||||
|
||||
window.center(&work_area)?;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use core::ffi::c_void;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::mem::size_of;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::eyre::bail;
|
||||
use color_eyre::eyre::Error;
|
||||
use color_eyre::Result;
|
||||
use std::path::Path;
|
||||
use windows::core::Result as WindowsCrateResult;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
@@ -47,6 +47,8 @@ use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
|
||||
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
|
||||
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
|
||||
use windows::Win32::System::Com::CoCreateInstance;
|
||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::Power::RegisterPowerSettingNotification;
|
||||
use windows::Win32::System::Power::HPOWERNOTIFY;
|
||||
@@ -72,6 +74,9 @@ use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
|
||||
use windows::Win32::UI::Shell::DesktopWallpaper;
|
||||
use windows::Win32::UI::Shell::IDesktopWallpaper;
|
||||
use windows::Win32::UI::Shell::DWPOS_FILL;
|
||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
|
||||
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
|
||||
@@ -141,6 +146,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
|
||||
use windows_core::BOOL;
|
||||
use windows_core::HSTRING;
|
||||
|
||||
use crate::core::Rect;
|
||||
|
||||
@@ -1345,4 +1351,22 @@ impl WindowsApi {
|
||||
pub fn wts_register_session_notification(hwnd: isize) -> Result<()> {
|
||||
unsafe { WTSRegisterSessionNotification(HWND(as_ptr!(hwnd)), 1) }.process()
|
||||
}
|
||||
|
||||
pub fn set_wallpaper(path: &Path) -> Result<()> {
|
||||
let path = path.canonicalize()?;
|
||||
|
||||
let wallpaper: IDesktopWallpaper =
|
||||
unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL)? };
|
||||
|
||||
let wallpaper_path = HSTRING::from(path.to_str().unwrap_or_default());
|
||||
unsafe {
|
||||
wallpaper.SetPosition(DWPOS_FILL)?;
|
||||
}
|
||||
|
||||
// Set the wallpaper
|
||||
unsafe {
|
||||
wallpaper.SetWallpaper(PCWSTR::null(), PCWSTR::from_raw(wallpaper_path.as_ptr()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::container::Container;
|
||||
use crate::core::Axis;
|
||||
use crate::core::CustomLayout;
|
||||
use crate::core::CycleDirection;
|
||||
@@ -22,10 +18,6 @@ use crate::core::DefaultLayout;
|
||||
use crate::core::Layout;
|
||||
use crate::core::OperationDirection;
|
||||
use crate::core::Rect;
|
||||
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::container::Container;
|
||||
use crate::locked_deque::LockedDeque;
|
||||
use crate::ring::Ring;
|
||||
use crate::should_act;
|
||||
@@ -35,13 +27,28 @@ use crate::static_config::WorkspaceConfig;
|
||||
use crate::window::Window;
|
||||
use crate::window::WindowDetails;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::FloatingLayerBehaviour;
|
||||
use crate::KomorebiTheme;
|
||||
use crate::SocketMessage;
|
||||
use crate::Wallpaper;
|
||||
use crate::WindowContainerBehaviour;
|
||||
use crate::DATA_DIR;
|
||||
use crate::DEFAULT_CONTAINER_PADDING;
|
||||
use crate::DEFAULT_WORKSPACE_PADDING;
|
||||
use crate::INITIAL_CONFIGURATION_LOADED;
|
||||
use crate::NO_TITLEBAR;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::REMOVE_TITLEBARS;
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
use getset::CopyGetters;
|
||||
use getset::Getters;
|
||||
use getset::MutGetters;
|
||||
use getset::Setters;
|
||||
use komorebi_themes::Base16ColourPalette;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uds_windows::UnixStream;
|
||||
|
||||
#[allow(clippy::struct_field_names)]
|
||||
#[derive(
|
||||
@@ -62,8 +69,7 @@ pub struct Workspace {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
pub maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
pub floating_windows: Vec<Window>,
|
||||
pub floating_windows: Ring<Window>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub layout: Layout,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
@@ -93,7 +99,11 @@ pub struct Workspace {
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub layer: WorkspaceLayer,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub floating_layer_behaviour: FloatingLayerBehaviour,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub locked_containers: BTreeSet<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
pub wallpaper: Option<Wallpaper>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[getset(get = "pub", set = "pub")]
|
||||
pub workspace_config: Option<WorkspaceConfig>,
|
||||
@@ -117,6 +127,7 @@ impl Display for WorkspaceLayer {
|
||||
}
|
||||
|
||||
impl_ring_elements!(Workspace, Container);
|
||||
impl_ring_elements!(Workspace, Window, "floating_window");
|
||||
|
||||
impl Default for Workspace {
|
||||
fn default() -> Self {
|
||||
@@ -127,7 +138,7 @@ impl Default for Workspace {
|
||||
maximized_window: None,
|
||||
maximized_window_restore_idx: None,
|
||||
monocle_container_restore_idx: None,
|
||||
floating_windows: Vec::default(),
|
||||
floating_windows: Ring::default(),
|
||||
layout: Layout::Default(DefaultLayout::BSP),
|
||||
layout_rules: vec![],
|
||||
layout_flip: None,
|
||||
@@ -141,9 +152,11 @@ impl Default for Workspace {
|
||||
window_container_behaviour_rules: None,
|
||||
float_override: None,
|
||||
layer: Default::default(),
|
||||
floating_layer_behaviour: Default::default(),
|
||||
globals: Default::default(),
|
||||
workspace_config: None,
|
||||
locked_containers: Default::default(),
|
||||
wallpaper: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,6 +263,8 @@ impl Workspace {
|
||||
|
||||
self.set_float_override(config.float_override);
|
||||
self.set_layout_flip(config.layout_flip);
|
||||
self.set_floating_layer_behaviour(config.floating_layer_behaviour.unwrap_or_default());
|
||||
self.set_wallpaper(config.wallpaper.clone());
|
||||
|
||||
self.set_workspace_config(Some(config.clone()));
|
||||
|
||||
@@ -286,12 +301,129 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_wallpaper(&self) -> Result<()> {
|
||||
if let Some(wallpaper) = &self.wallpaper {
|
||||
if let Err(error) = WindowsApi::set_wallpaper(&wallpaper.path) {
|
||||
tracing::error!("failed to set wallpaper: {error}");
|
||||
}
|
||||
|
||||
// if !cfg!(debug_assertions) && wallpaper.generate_theme.unwrap_or(true) {
|
||||
if wallpaper.generate_theme.unwrap_or(true) {
|
||||
let variant = wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|t| t.theme_variant)
|
||||
.unwrap_or_default();
|
||||
|
||||
let cached_palette = DATA_DIR.join(format!(
|
||||
"{}.base16.{variant}.json",
|
||||
wallpaper
|
||||
.path
|
||||
.file_name()
|
||||
.unwrap_or(OsStr::new("tmp"))
|
||||
.to_string_lossy()
|
||||
));
|
||||
|
||||
let mut base16_palette = None;
|
||||
|
||||
if cached_palette.is_file() {
|
||||
tracing::info!(
|
||||
"colour palette for wallpaper {} found in cache",
|
||||
cached_palette.display()
|
||||
);
|
||||
|
||||
// this code is VERY slow on debug builds - should only be a one-time issue when loading
|
||||
// an uncached wallpaper
|
||||
if let Ok(palette) = serde_json::from_str::<Base16ColourPalette>(
|
||||
&std::fs::read_to_string(&cached_palette)?,
|
||||
) {
|
||||
base16_palette = Some(palette);
|
||||
}
|
||||
};
|
||||
|
||||
if base16_palette.is_none() {
|
||||
base16_palette =
|
||||
komorebi_themes::generate_base16_palette(&wallpaper.path, variant).ok();
|
||||
|
||||
std::fs::write(
|
||||
&cached_palette,
|
||||
serde_json::to_string_pretty(&base16_palette)?,
|
||||
)?;
|
||||
|
||||
tracing::info!(
|
||||
"colour palette for wallpaper {} cached",
|
||||
cached_palette.display()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(palette) = base16_palette {
|
||||
let komorebi_theme = KomorebiTheme::Custom {
|
||||
colours: Box::new(palette),
|
||||
single_border: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.single_border),
|
||||
stack_border: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.stack_border),
|
||||
monocle_border: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.monocle_border),
|
||||
floating_border: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.floating_border),
|
||||
unfocused_border: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.unfocused_border),
|
||||
unfocused_locked_border: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.unfocused_locked_border),
|
||||
stackbar_focused_text: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.stackbar_focused_text),
|
||||
stackbar_unfocused_text: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.stackbar_unfocused_text),
|
||||
stackbar_background: wallpaper
|
||||
.theme_options
|
||||
.as_ref()
|
||||
.and_then(|o| o.stackbar_background),
|
||||
bar_accent: wallpaper.theme_options.as_ref().and_then(|o| o.bar_accent),
|
||||
};
|
||||
|
||||
let bytes = SocketMessage::Theme(Box::new(komorebi_theme)).as_bytes()?;
|
||||
|
||||
let socket = DATA_DIR.join("komorebi.sock");
|
||||
match UnixStream::connect(socket) {
|
||||
Ok(mut stream) => {
|
||||
if let Err(error) = stream.write_all(&bytes) {
|
||||
tracing::error!("failed to send theme update message: {error}")
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!("{error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
||||
if let Some(container) = self.monocle_container() {
|
||||
if let Some(window) = container.focused_window() {
|
||||
container.restore();
|
||||
window.focus(mouse_follows_focus)?;
|
||||
return Ok(());
|
||||
return self.apply_wallpaper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,17 +457,17 @@ impl Workspace {
|
||||
} else if let Some(maximized_window) = self.maximized_window() {
|
||||
maximized_window.restore();
|
||||
maximized_window.focus(mouse_follows_focus)?;
|
||||
} else if let Some(floating_window) = self.floating_windows().first() {
|
||||
} else if let Some(floating_window) = self.focused_floating_window() {
|
||||
floating_window.focus(mouse_follows_focus)?;
|
||||
}
|
||||
} else if let Some(maximized_window) = self.maximized_window() {
|
||||
maximized_window.restore();
|
||||
maximized_window.focus(mouse_follows_focus)?;
|
||||
} else if let Some(floating_window) = self.floating_windows().first() {
|
||||
} else if let Some(floating_window) = self.focused_floating_window() {
|
||||
floating_window.focus(mouse_follows_focus)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.apply_wallpaper()
|
||||
}
|
||||
|
||||
pub fn update(&mut self) -> Result<()> {
|
||||
@@ -554,13 +686,13 @@ impl Workspace {
|
||||
|
||||
for window in self.visible_windows().into_iter().flatten() {
|
||||
if !window.is_window()
|
||||
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
|
||||
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
|
||||
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
|
||||
// docs is closed
|
||||
//
|
||||
// I hate every single person who worked on Microsoft Office 365, especially Word
|
||||
|| !window.is_visible()
|
||||
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
|
||||
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
|
||||
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
|
||||
// docs is closed
|
||||
//
|
||||
// I hate every single person who worked on Microsoft Office 365, especially Word
|
||||
|| !window.is_visible()
|
||||
{
|
||||
hwnds.push(window.hwnd);
|
||||
}
|
||||
@@ -608,6 +740,9 @@ impl Workspace {
|
||||
self.containers().get(self.container_idx_for_window(hwnd)?)
|
||||
}
|
||||
|
||||
/// If there is a container which holds the window with `hwnd` it will focus that container.
|
||||
/// This function will only emit a focus on the window if it isn't the focused window of that
|
||||
/// container already.
|
||||
pub fn focus_container_by_window(&mut self, hwnd: isize) -> Result<()> {
|
||||
let container_idx = self
|
||||
.container_idx_for_window(hwnd)
|
||||
@@ -862,7 +997,7 @@ impl Workspace {
|
||||
container
|
||||
}
|
||||
|
||||
fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {
|
||||
pub fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {
|
||||
let mut idx = None;
|
||||
for (i, x) in self.containers().iter().enumerate() {
|
||||
if x.contains_window(hwnd) {
|
||||
@@ -1113,7 +1248,7 @@ impl Workspace {
|
||||
window
|
||||
};
|
||||
|
||||
self.floating_windows_mut().push(window);
|
||||
self.floating_windows_mut().push_back(window);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1431,24 +1566,11 @@ impl Workspace {
|
||||
|
||||
pub fn new_maximized_window(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
let foreground_hwnd = WindowsApi::foreground_window()?;
|
||||
let mut floating_window = None;
|
||||
|
||||
if !self.floating_windows().is_empty() {
|
||||
let mut focused_floating_window_idx = None;
|
||||
for (i, w) in self.floating_windows().iter().enumerate() {
|
||||
if w.hwnd == foreground_hwnd {
|
||||
focused_floating_window_idx = Option::from(i);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(idx) = focused_floating_window_idx {
|
||||
floating_window = Option::from(self.floating_windows_mut().remove(idx));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(floating_window) = floating_window {
|
||||
self.set_maximized_window(Option::from(floating_window));
|
||||
if matches!(self.layer, WorkspaceLayer::Floating) {
|
||||
let floating_window_idx = self.focused_floating_window_idx();
|
||||
let floating_window = self.floating_windows_mut().remove(floating_window_idx);
|
||||
self.set_maximized_window(floating_window);
|
||||
self.set_maximized_window_restore_idx(Option::from(focused_idx));
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
@@ -1563,7 +1685,7 @@ impl Workspace {
|
||||
let hwnd = WindowsApi::foreground_window().ok()?;
|
||||
|
||||
let mut idx = None;
|
||||
for (i, window) in self.floating_windows.iter().enumerate() {
|
||||
for (i, window) in self.floating_windows().iter().enumerate() {
|
||||
if hwnd == window.hwnd {
|
||||
idx = Option::from(i);
|
||||
}
|
||||
@@ -1572,8 +1694,8 @@ impl Workspace {
|
||||
match idx {
|
||||
None => None,
|
||||
Some(idx) => {
|
||||
if self.floating_windows.get(idx).is_some() {
|
||||
Option::from(self.floating_windows_mut().remove(idx))
|
||||
if self.floating_windows().get(idx).is_some() {
|
||||
self.floating_windows_mut().remove(idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1760,7 +1882,7 @@ mod tests {
|
||||
|
||||
// unfloat - have to do this semi-manually becuase of calls to WindowsApi in
|
||||
// new_container_for_floating_window which usually handles unfloating
|
||||
let window = ws.floating_windows_mut().pop().unwrap();
|
||||
let window = ws.floating_windows_mut().pop_back().unwrap();
|
||||
let mut container = Container::default();
|
||||
container.add_window(window);
|
||||
ws.insert_container_at_idx(ws.focused_container_idx(), container);
|
||||
@@ -2270,4 +2392,99 @@ mod tests {
|
||||
assert!(workspace.contains_window(0));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_focus_container_by_window() {
|
||||
let mut workspace = Workspace::default();
|
||||
|
||||
{
|
||||
// Container with 3 windows
|
||||
let mut container = Container::default();
|
||||
for i in 0..3 {
|
||||
container.windows_mut().push_back(Window::from(i));
|
||||
}
|
||||
workspace.add_container_to_back(container);
|
||||
}
|
||||
|
||||
{
|
||||
// Container with 1 window
|
||||
let mut container = Container::default();
|
||||
container.windows_mut().push_back(Window::from(4));
|
||||
workspace.add_container_to_back(container);
|
||||
}
|
||||
|
||||
// Focus container by window
|
||||
workspace.focus_container_by_window(1).unwrap();
|
||||
|
||||
// Should be focused on workspace 0
|
||||
assert_eq!(workspace.focused_container_idx(), 0);
|
||||
|
||||
// Should be focused on window 1 and hwnd should be 1
|
||||
let focused_container = workspace.focused_container_mut().unwrap();
|
||||
assert_eq!(
|
||||
focused_container.focused_window(),
|
||||
Some(&Window { hwnd: 1 })
|
||||
);
|
||||
assert_eq!(focused_container.focused_window_idx(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains_managed_window() {
|
||||
let mut workspace = Workspace::default();
|
||||
|
||||
{
|
||||
// Container with 3 windows
|
||||
let mut container = Container::default();
|
||||
for i in 0..3 {
|
||||
container.windows_mut().push_back(Window::from(i));
|
||||
}
|
||||
workspace.add_container_to_back(container);
|
||||
}
|
||||
|
||||
{
|
||||
// Container with 1 window
|
||||
let mut container = Container::default();
|
||||
container.windows_mut().push_back(Window::from(4));
|
||||
workspace.add_container_to_back(container);
|
||||
}
|
||||
|
||||
// Should return true, window is in container 1
|
||||
assert!(workspace.contains_managed_window(4));
|
||||
|
||||
// Should return true, all the windows are in container 0
|
||||
for i in 0..3 {
|
||||
assert!(workspace.contains_managed_window(i));
|
||||
}
|
||||
|
||||
// Should return false since window was never added
|
||||
assert!(!workspace.contains_managed_window(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_floating_window() {
|
||||
let mut workspace = Workspace::default();
|
||||
|
||||
{
|
||||
// Container with 3 windows
|
||||
let mut container = Container::default();
|
||||
for i in 0..3 {
|
||||
container.windows_mut().push_back(Window::from(i));
|
||||
}
|
||||
workspace.add_container_to_back(container);
|
||||
}
|
||||
|
||||
// Add window to floating_windows
|
||||
workspace.new_floating_window().ok();
|
||||
|
||||
// Should have 1 floating window
|
||||
assert_eq!(workspace.floating_windows().len(), 1);
|
||||
|
||||
// Should have only 2 windows now
|
||||
let container = workspace.focused_container_mut().unwrap();
|
||||
assert_eq!(container.windows().len(), 2);
|
||||
|
||||
// Should contain hwnd 0 since this is the first window in the container
|
||||
let floating_windows = workspace.floating_windows_mut();
|
||||
assert!(floating_windows.contains(&Window { hwnd: 0 }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::WindowManager;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Notification {
|
||||
pub monitor_idx: usize,
|
||||
pub workspace_idx: usize,
|
||||
}
|
||||
|
||||
pub static ALT_TAB_HWND: AtomicCell<Option<isize>> = AtomicCell::new(None);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ALT_TAB_HWND_INSTANT: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
||||
}
|
||||
|
||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||
|
||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
|
||||
}
|
||||
|
||||
fn event_tx() -> Sender<Notification> {
|
||||
channel().0.clone()
|
||||
}
|
||||
|
||||
fn event_rx() -> Receiver<Notification> {
|
||||
channel().1.clone()
|
||||
}
|
||||
|
||||
pub fn send_notification(monitor_idx: usize, workspace_idx: usize) {
|
||||
if event_tx()
|
||||
.try_send(Notification {
|
||||
monitor_idx,
|
||||
workspace_idx,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
tracing::warn!("channel is full; dropping notification")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
Ok(()) => {
|
||||
tracing::warn!("restarting finished thread");
|
||||
}
|
||||
Err(error) => {
|
||||
if cfg!(debug_assertions) {
|
||||
tracing::error!("restarting failed thread: {:?}", error)
|
||||
} else {
|
||||
tracing::error!("restarting failed thread: {}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("listening");
|
||||
|
||||
let receiver = event_rx();
|
||||
let arc = wm.clone();
|
||||
|
||||
for notification in receiver {
|
||||
tracing::info!("running reconciliation");
|
||||
|
||||
let mut wm = wm.lock();
|
||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
||||
let focused_workspace_idx =
|
||||
wm.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
let updated_pair = (notification.monitor_idx, notification.workspace_idx);
|
||||
|
||||
if focused_pair != updated_pair {
|
||||
wm.focus_monitor(notification.monitor_idx)?;
|
||||
let mouse_follows_focus = wm.mouse_follows_focus;
|
||||
|
||||
if let Some(monitor) = wm.focused_monitor_mut() {
|
||||
let previous_idx = monitor.focused_workspace_idx();
|
||||
monitor.set_last_focused_workspace(Option::from(previous_idx));
|
||||
monitor.focus_workspace(notification.workspace_idx)?;
|
||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
}
|
||||
|
||||
// Drop our lock on the window manager state here to not slow down updates
|
||||
drop(wm);
|
||||
|
||||
// Check if there was an alt-tab across workspaces in the last second
|
||||
if let Some(hwnd) = ALT_TAB_HWND.load() {
|
||||
if ALT_TAB_HWND_INSTANT
|
||||
.lock()
|
||||
.elapsed()
|
||||
.lt(&Duration::from_secs(1))
|
||||
{
|
||||
// Sleep for 100 millis to let other events pass
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
tracing::info!("focusing alt-tabbed window");
|
||||
|
||||
// Take a new lock on the wm and try to focus the container with
|
||||
// the recorded HWND from the alt-tab
|
||||
let mut wm = arc.lock();
|
||||
if let Ok(workspace) = wm.focused_workspace_mut() {
|
||||
// Regardless of if this fails, we need to get past this part
|
||||
// to unblock the border manager below
|
||||
let _ = workspace.focus_container_by_window(hwnd);
|
||||
}
|
||||
|
||||
// Unblock the border manager
|
||||
ALT_TAB_HWND.store(None);
|
||||
// Send a notification to the border manager to update the borders
|
||||
border_manager::send_notification(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
872
schema.bar.json
872
schema.bar.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "KomobarConfig",
|
||||
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.35`",
|
||||
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.36`",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"left_widgets",
|
||||
@@ -365,15 +365,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused_window": {
|
||||
"description": "Configure the Focused Window widget",
|
||||
"focused_container": {
|
||||
"description": "Configure the Focused Container widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display format of the currently focused window",
|
||||
"description": "Display format of the currently focused container",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
@@ -413,11 +413,11 @@
|
||||
]
|
||||
},
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Focused Window widget",
|
||||
"description": "Enable the Komorebi Focused Container widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_icon": {
|
||||
"description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused window)",
|
||||
"description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused container)",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
@@ -508,6 +508,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"locked_container": {
|
||||
"description": "Configure the Locked Container widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display format of the current locked state",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Locked Container widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_when_unlocked": {
|
||||
"description": "Show the widget event if the layer is unlocked",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_layer": {
|
||||
"description": "Configure the Workspace Layer widget",
|
||||
"type": "object",
|
||||
@@ -1775,15 +1832,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused_window": {
|
||||
"description": "Configure the Focused Window widget",
|
||||
"focused_container": {
|
||||
"description": "Configure the Focused Container widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display format of the currently focused window",
|
||||
"description": "Display format of the currently focused container",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
@@ -1823,11 +1880,11 @@
|
||||
]
|
||||
},
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Focused Window widget",
|
||||
"description": "Enable the Komorebi Focused Container widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_icon": {
|
||||
"description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused window)",
|
||||
"description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused container)",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
@@ -1918,6 +1975,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"locked_container": {
|
||||
"description": "Configure the Locked Container widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display format of the current locked state",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Locked Container widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_when_unlocked": {
|
||||
"description": "Show the widget event if the layer is unlocked",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_layer": {
|
||||
"description": "Configure the Workspace Layer widget",
|
||||
"type": "object",
|
||||
@@ -3118,15 +3232,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused_window": {
|
||||
"description": "Configure the Focused Window widget",
|
||||
"focused_container": {
|
||||
"description": "Configure the Focused Container widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display format of the currently focused window",
|
||||
"description": "Display format of the currently focused container",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
@@ -3166,11 +3280,11 @@
|
||||
]
|
||||
},
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Focused Window widget",
|
||||
"description": "Enable the Komorebi Focused Container widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_icon": {
|
||||
"description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused window)",
|
||||
"description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused container)",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
@@ -3261,6 +3375,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"locked_container": {
|
||||
"description": "Configure the Locked Container widget",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enable"
|
||||
],
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display format of the current locked state",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show only icon",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Icon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show only text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and text on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"TextAndIconOnSelected"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show both icon and text",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndText"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Show an icon and text for the selected element, and icons on the rest",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IconAndTextOnSelected"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"enable": {
|
||||
"description": "Enable the Komorebi Locked Container widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_when_unlocked": {
|
||||
"description": "Show the widget event if the layer is unlocked",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_layer": {
|
||||
"description": "Configure the Workspace Layer widget",
|
||||
"type": "object",
|
||||
@@ -4193,6 +4364,675 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A custom Base16 theme",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"colours",
|
||||
"palette"
|
||||
],
|
||||
"properties": {
|
||||
"accent": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Base00",
|
||||
"Base01",
|
||||
"Base02",
|
||||
"Base03",
|
||||
"Base04",
|
||||
"Base05",
|
||||
"Base06",
|
||||
"Base07",
|
||||
"Base08",
|
||||
"Base09",
|
||||
"Base0A",
|
||||
"Base0B",
|
||||
"Base0C",
|
||||
"Base0D",
|
||||
"Base0E",
|
||||
"Base0F"
|
||||
]
|
||||
},
|
||||
"colours": {
|
||||
"description": "Colours of the custom Base16 theme palette",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"base_00",
|
||||
"base_01",
|
||||
"base_02",
|
||||
"base_03",
|
||||
"base_04",
|
||||
"base_05",
|
||||
"base_06",
|
||||
"base_07",
|
||||
"base_08",
|
||||
"base_09",
|
||||
"base_0a",
|
||||
"base_0b",
|
||||
"base_0c",
|
||||
"base_0d",
|
||||
"base_0e",
|
||||
"base_0f"
|
||||
],
|
||||
"properties": {
|
||||
"base_00": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_01": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_02": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_03": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_04": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_05": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_06": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_07": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_08": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_09": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_0a": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_0b": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_0c": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_0d": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_0e": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base_0f": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"palette": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Custom"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
1179
schema.json
1179
schema.json
File diff suppressed because it is too large
Load Diff
BIN
wix/License.rtf
BIN
wix/License.rtf
Binary file not shown.
Reference in New Issue
Block a user