Compare commits

..

5 Commits

Author SHA1 Message Date
LGUG2Z
3857d1a46c feat(borders): clean up render targets on destroy 2024-11-17 10:53:54 -08:00
LGUG2Z
47cb19e54a feat(borders): remove black pixels around direct2d corners 2024-11-17 10:53:54 -08:00
LGUG2Z
431970d7b6 feat(borders): reduce redraws to improve perf 2024-11-17 10:53:54 -08:00
LGUG2Z
5525a382b9 feat(borders): avoid multiple render target creation calls 2024-11-17 10:53:54 -08:00
LGUG2Z
238271a71e feat(borders): initial impl of direct2d border drawing
Not sure what I'm doing wrong but this is super slow to update on window dragging and an absolute
resource hog.
2024-11-17 10:53:54 -08:00
50 changed files with 1755 additions and 4586 deletions

420
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ serde_json = { workspace = true }
starship-battery = "0.10"
sysinfo = { workspace = true }
tracing = { workspace = true }
tracing-appender = { workspace = true }
tracing-subscriber = { workspace = true }
windows = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }

View File

@@ -5,10 +5,6 @@ use crate::config::PositionConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
use crate::render::Color32Ext;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::render::RenderExt;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crate::BAR_HEIGHT;
@@ -18,8 +14,6 @@ use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crossbeam_channel::Receiver;
use eframe::egui::Align;
use eframe::egui::Align2;
use eframe::egui::Area;
use eframe::egui::CentralPanel;
use eframe::egui::Color32;
use eframe::egui::Context;
@@ -28,13 +22,10 @@ use eframe::egui::FontDefinitions;
use eframe::egui::FontFamily;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Id;
use eframe::egui::Layout;
use eframe::egui::Margin;
use eframe::egui::Rgba;
use eframe::egui::Style;
use eframe::egui::TextStyle;
use eframe::egui::Visuals;
use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder;
use komorebi_client::KomorebiTheme;
@@ -50,10 +41,8 @@ use std::sync::Arc;
pub struct Komobar {
pub config: Arc<KomobarConfig>,
pub render_config: Rc<RefCell<RenderConfig>>,
pub komorebi_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
pub left_widgets: Vec<Box<dyn BarWidget>>,
pub center_widgets: Vec<Box<dyn BarWidget>>,
pub right_widgets: Vec<Box<dyn BarWidget>>,
pub rx_gui: Receiver<komorebi_client::Notification>,
pub rx_config: Receiver<KomobarConfig>,
@@ -182,10 +171,6 @@ impl Komobar {
y: BAR_HEIGHT,
});
if end.y == 0.0 {
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
}
let rect = komorebi_client::Rect {
left: start.x as i32,
top: start.y as i32,
@@ -252,30 +237,6 @@ impl Komobar {
}
}
// apply rounding to the widgets
if let Some(
Grouping::Bar(config) | Grouping::Alignment(config) | Grouping::Widget(config),
) = &config.grouping
{
if let Some(rounding) = config.rounding {
ctx.style_mut(|style| {
style.visuals.widgets.noninteractive.rounding = rounding.into();
style.visuals.widgets.inactive.rounding = rounding.into();
style.visuals.widgets.hovered.rounding = rounding.into();
style.visuals.widgets.active.rounding = rounding.into();
style.visuals.widgets.open.rounding = rounding.into();
});
}
}
let theme_color = *self.bg_color.borrow();
self.render_config
.replace(config.new_renderconfig(theme_color));
self.bg_color
.replace(theme_color.try_apply_alpha(config.transparency_alpha));
if let Some(font_size) = &config.font_size {
tracing::info!("attempting to set custom font size: {font_size}");
Self::set_font_size(ctx, *font_size);
@@ -290,17 +251,7 @@ impl Komobar {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Alignment::Left);
}
}
if let Some(center_widgets) = &config.center_widgets {
for (idx, widget_config) in center_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Alignment::Center);
}
side = Some(Side::Left);
}
}
@@ -308,7 +259,7 @@ impl Komobar {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Alignment::Right);
side = Some(Side::Right);
}
}
@@ -318,14 +269,6 @@ impl Komobar {
.map(|config| config.as_boxed_bar_widget())
.collect::<Vec<Box<dyn BarWidget>>>();
let mut center_widgets = match &config.center_widgets {
Some(center_widgets) => center_widgets
.iter()
.map(|config| config.as_boxed_bar_widget())
.collect::<Vec<Box<dyn BarWidget>>>(),
None => vec![],
};
let mut right_widgets = config
.right_widgets
.iter()
@@ -350,23 +293,20 @@ impl Komobar {
let boxed: Box<dyn BarWidget> = Box::new(widget);
match side {
Alignment::Left => left_widgets[idx] = boxed,
Alignment::Center => center_widgets[idx] = boxed,
Alignment::Right => right_widgets[idx] = boxed,
Side::Left => left_widgets[idx] = boxed,
Side::Right => right_widgets[idx] = boxed,
}
}
right_widgets.reverse();
self.left_widgets = left_widgets;
self.center_widgets = center_widgets;
self.right_widgets = right_widgets;
tracing::info!("widget configuration options applied");
self.komorebi_notification_state = komorebi_notification_state;
}
pub fn new(
cc: &eframe::CreationContext<'_>,
rx_gui: Receiver<komorebi_client::Notification>,
@@ -375,10 +315,8 @@ impl Komobar {
) -> Self {
let mut komobar = Self {
config: config.clone(),
render_config: Rc::new(RefCell::new(RenderConfig::new())),
komorebi_notification_state: None,
left_widgets: vec![],
center_widgets: vec![],
right_widgets: vec![],
rx_gui,
rx_config,
@@ -447,10 +385,13 @@ impl Komobar {
}
}
impl eframe::App for Komobar {
// Needed for transparency
fn clear_color(&self, _visuals: &Visuals) -> [f32; 4] {
Rgba::TRANSPARENT.to_array()
}
// TODO: I think this is needed for transparency??
// fn clear_color(&self, _visuals: &Visuals) -> [f32; 4] {
// egui::Rgba::TRANSPARENT.to_array()
// let mut background = Color32::from_gray(18).to_normalized_gamma_f32();
// background[3] = 0.9;
// background
// }
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {
@@ -492,54 +433,17 @@ impl eframe::App for Komobar {
Frame::none().fill(*self.bg_color.borrow())
};
let mut render_config = self.render_config.borrow_mut();
CentralPanel::default().frame(frame).show(ctx, |ui| {
// Apply grouping logic for the bar as a whole
render_config.clone().apply_on_bar(ui, |ui| {
ui.horizontal_centered(|ui| {
// Left-aligned widgets layout
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
let mut render_conf = *render_config;
render_conf.alignment = Some(Alignment::Left);
ui.horizontal_centered(|ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
for w in &mut self.left_widgets {
w.render(ctx, ui);
}
});
render_config.apply_on_alignment(ui, |ui| {
for w in &mut self.left_widgets {
w.render(ctx, ui, &mut render_conf);
}
});
});
// Right-aligned widgets layout
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
let mut render_conf = *render_config;
render_conf.alignment = Some(Alignment::Right);
render_config.apply_on_alignment(ui, |ui| {
for w in &mut self.right_widgets {
w.render(ctx, ui, &mut render_conf);
}
});
});
if !self.center_widgets.is_empty() {
// Floating center widgets
Area::new(Id::new("center_panel"))
.anchor(Align2::CENTER_CENTER, [0.0, 0.0]) // Align in the center of the window
.show(ctx, |ui| {
Frame::none().show(ui, |ui| {
ui.horizontal_centered(|ui| {
let mut render_conf = *render_config;
render_conf.alignment = Some(Alignment::Center);
render_config.apply_on_alignment(ui, |ui| {
for w in &mut self.center_widgets {
w.render(ctx, ui, &mut render_conf);
}
});
});
});
});
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
for w in &mut self.right_widgets {
w.render(ctx, ui);
}
})
})
@@ -548,8 +452,7 @@ impl eframe::App for Komobar {
}
#[derive(Copy, Clone)]
pub enum Alignment {
enum Side {
Left,
Center,
Right,
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -115,7 +115,7 @@ impl Battery {
}
impl BarWidget for Battery {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -147,14 +147,14 @@ impl BarWidget for Battery {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
ui.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
);
});
ui.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
);
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::render::Grouping;
use crate::widget::WidgetConfig;
use eframe::egui::Pos2;
use eframe::egui::TextBuffer;
@@ -29,16 +28,8 @@ pub struct KomobarConfig {
pub max_label_width: Option<f32>,
/// Theme
pub theme: Option<KomobarTheme>,
/// Alpha value for the color transparency [[0-255]] (default: 200)
pub transparency_alpha: Option<u8>,
/// Spacing between widgets (default: 10.0)
pub widget_spacing: Option<f32>,
/// Visual grouping for widgets
pub grouping: Option<Grouping>,
/// Left side widgets (ordered left-to-right)
pub left_widgets: Vec<WidgetConfig>,
/// Center widgets (ordered left-to-right)
pub center_widgets: Option<Vec<WidgetConfig>>,
/// Right side widgets (ordered left-to-right)
pub right_widgets: Vec<WidgetConfig>,
}
@@ -187,13 +178,3 @@ pub enum LabelPrefix {
/// Show an icon and text
IconAndText,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum DisplayFormat {
/// Show only icon
Icon,
/// Show only text
Text,
/// Show both icon and text
IconAndText,
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -70,7 +70,7 @@ impl Cpu {
}
impl BarWidget for Cpu {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -99,23 +99,22 @@ impl BarWidget for Cpu {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
if let Err(error) =
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
eprintln!("{}", error)
}
eprintln!("{}", error)
}
});
}
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -86,7 +86,7 @@ impl Date {
}
impl BarWidget for Date {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let mut output = self.output();
if !output.is_empty() {
@@ -119,19 +119,19 @@ impl BarWidget for Date {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(WidgetText::LayoutJob(layout_job.clone()))
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.next()
}
});
if ui
.add(
Label::new(WidgetText::LayoutJob(layout_job.clone()))
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.next()
}
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,44 +1,37 @@
use crate::bar::apply_theme;
use crate::config::DisplayFormat;
use crate::config::KomobarTheme;
use crate::komorebi_layout::KomorebiLayout;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX;
use crate::WIDGET_SPACING;
use crossbeam_channel::Receiver;
use crossbeam_channel::TryRecvError;
use eframe::egui::vec2;
use eframe::egui::text::LayoutJob;
use eframe::egui::Color32;
use eframe::egui::ColorImage;
use eframe::egui::Context;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Image;
use eframe::egui::Label;
use eframe::egui::Margin;
use eframe::egui::Rounding;
use eframe::egui::SelectableLabel;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::TextStyle;
use eframe::egui::TextureHandle;
use eframe::egui::TextureOptions;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use image::RgbaImage;
use komorebi_client::Container;
use komorebi_client::CycleDirection;
use komorebi_client::NotificationEvent;
use komorebi_client::Rect;
use komorebi_client::SocketMessage;
use komorebi_client::Window;
use komorebi_client::Workspace;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fmt::Display;
use std::fmt::Formatter;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::Ordering;
@@ -61,28 +54,20 @@ pub struct KomorebiWorkspacesConfig {
pub enable: bool,
/// Hide workspaces without any windows
pub hide_empty_workspaces: bool,
/// Display format of the workspace
pub display: Option<DisplayFormat>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiLayoutConfig {
/// Enable the Komorebi Layout widget
pub enable: bool,
/// List of layout options
pub options: Option<Vec<KomorebiLayout>>,
/// Display format of the current layout
pub display: Option<DisplayFormat>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiFocusedWindowConfig {
/// Enable the Komorebi Focused Window widget
pub enable: bool,
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused window)
pub show_icon: Option<bool>,
/// Display format of the currently focused window
pub display: Option<DisplayFormat>,
/// Show the icon of the currently focused window
pub show_icon: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -116,12 +101,11 @@ impl From<&KomorebiConfig> for Komorebi {
hide_empty_workspaces: value.workspaces.hide_empty_workspaces,
mouse_follows_focus: true,
work_area_offset: None,
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
focused_container_information: (vec![], vec![], 0),
stack_accent: None,
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
})),
workspaces: value.workspaces,
layout: value.layout.clone(),
layout: value.layout,
focused_window: value.focused_window,
configuration_switcher,
}
@@ -138,89 +122,238 @@ pub struct Komorebi {
}
impl BarWidget for Komorebi {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
if self.workspaces.enable {
let mut update = None;
if !komorebi_notification_state.workspaces.is_empty() {
let format = self.workspaces.display.unwrap_or(DisplayFormat::Text);
config.apply_on_widget(false, ui, |ui| {
for (i, (ws, container_information)) in
komorebi_notification_state.workspaces.iter().enumerate()
{
if SelectableFrame::new(
for (i, (ws, should_show)) in komorebi_notification_state.workspaces.iter().enumerate()
{
if *should_show
&& ui
.add(SelectableLabel::new(
komorebi_notification_state.selected_workspace.eq(ws),
)
.show(ui, |ui| {
let mut has_icon = false;
ws.to_string(),
))
.clicked()
{
update = Some(ws.to_string());
let mut proceed = true;
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
let icons: Vec<_> =
container_information.icons.iter().flatten().collect();
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(false))
.is_err()
{
tracing::error!("could not send message to komorebi: MouseFollowsFocus");
proceed = false;
}
if !icons.is_empty() {
Frame::none()
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y,
))
.show(ui, |ui| {
for icon in icons {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.shrink_to_fit(),
if proceed
&& komorebi_client::send_message(&SocketMessage::FocusWorkspaceNumber(i))
.is_err()
{
tracing::error!("could not send message to komorebi: FocusWorkspaceNumber");
proceed = false;
}
if proceed
&& komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
))
.is_err()
{
tracing::error!("could not send message to komorebi: MouseFollowsFocus");
proceed = false;
}
if proceed
&& komorebi_client::send_message(&SocketMessage::RetileWithResizeDimensions)
.is_err()
{
tracing::error!("could not send message to komorebi: Retile");
}
}
}
if let Some(update) = update {
komorebi_notification_state.selected_workspace = update;
}
ui.add_space(WIDGET_SPACING);
}
if let Some(layout) = self.layout {
if layout.enable {
if ui
.add(
Label::new(komorebi_notification_state.layout.to_string())
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
match komorebi_notification_state.layout {
KomorebiLayout::Default(_) => {
if komorebi_client::send_message(&SocketMessage::CycleLayout(
CycleDirection::Next,
))
.is_err()
{
tracing::error!("could not send message to komorebi: CycleLayout");
}
}
KomorebiLayout::Floating => {
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err()
{
tracing::error!("could not send message to komorebi: ToggleTiling");
}
}
KomorebiLayout::Paused => {
if komorebi_client::send_message(&SocketMessage::TogglePause).is_err() {
tracing::error!("could not send message to komorebi: TogglePause");
}
}
KomorebiLayout::Custom => {}
}
}
ui.add_space(WIDGET_SPACING);
}
}
if let Some(configuration_switcher) = &self.configuration_switcher {
if configuration_switcher.enable {
for (name, location) in configuration_switcher.configurations.iter() {
let path = PathBuf::from(location);
if path.is_file()
&& ui
.add(Label::new(name).selectable(false).sense(Sense::click()))
.clicked()
{
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: ReplaceConfiguration"
);
proceed = false;
}
if let Some(rect) = komorebi_notification_state.work_area_offset {
if proceed {
match komorebi_client::send_query(&SocketMessage::Query(
komorebi_client::StateQuery::FocusedMonitorIndex,
)) {
Ok(idx) => {
if let Ok(monitor_idx) = idx.parse::<usize>() {
if komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
rect,
),
)
.is_err()
{
tracing::error!(
"could not send message to komorebi: MonitorWorkAreaOffset"
);
if !has_icon {
has_icon = true;
}
}
});
}
}
Err(_) => {
tracing::error!(
"could not send message to komorebi: Query"
);
}
}
}
}
}
}
// draw a custom icon when there is no app icon
if match format {
DisplayFormat::Icon => !has_icon,
_ => false,
} {
let font_id = ctx
.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap_or_else(FontId::default);
ui.add_space(WIDGET_SPACING);
}
}
let (response, painter) =
ui.allocate_painter(Vec2::splat(font_id.size), Sense::hover());
let stroke =
Stroke::new(1.0, ctx.style().visuals.selection.stroke.color);
let mut rect = response.rect;
let rounding = Rounding::same(rect.width() * 0.1);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke);
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
if let Some(focused_window) = self.focused_window {
if focused_window.enable {
let titles = &komorebi_notification_state.focused_container_information.0;
let icons = &komorebi_notification_state.focused_container_information.1;
let focused_window_idx =
komorebi_notification_state.focused_container_information.2;
response.on_hover_text(ws.to_string())
} else if match format {
DisplayFormat::Icon => has_icon,
_ => false,
} {
ui.response().on_hover_text(ws.to_string())
} else {
ui.add(Label::new(ws.to_string()).selectable(false))
}
})
.clicked()
let iter = titles.iter().zip(icons.iter());
for (i, (title, icon)) in iter.enumerate() {
if focused_window.show_icon {
if let Some(img) = icon {
ui.add(
Image::from(&img_to_texture(ctx, img))
.maintain_aspect_ratio(true)
.max_height(15.0),
);
}
}
if i == focused_window_idx {
let font_id = ctx
.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap_or_else(FontId::default);
let layout_job = LayoutJob::simple(
title.to_string(),
font_id.clone(),
komorebi_notification_state
.stack_accent
.unwrap_or(ctx.style().visuals.selection.stroke.color),
100.0,
);
if titles.len() > 1 {
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
custom_ui.add_sized_left_to_right(
Vec2::new(
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(layout_job).selectable(false).truncate(),
);
} else {
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
custom_ui.add_sized_left_to_right(
Vec2::new(
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(title).selectable(false).truncate(),
);
}
} else {
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
if custom_ui
.add_sized_left_to_right(
Vec2::new(
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(title)
.selectable(false)
.sense(Sense::click())
.truncate(),
)
.clicked()
{
update = Some(ws.to_string());
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
false,
))
@@ -229,235 +362,33 @@ impl BarWidget for Komorebi {
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
);
proceed = false;
}
if proceed
&& komorebi_client::send_message(
&SocketMessage::FocusMonitorWorkspaceNumber(
komorebi_notification_state.monitor_index,
i,
),
)
if komorebi_client::send_message(&SocketMessage::FocusStackWindow(i))
.is_err()
{
tracing::error!(
"could not send message to komorebi: FocusWorkspaceNumber"
"could not send message to komorebi: FocusStackWindow"
);
proceed = false;
}
if proceed
&& komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
);
proceed = false;
}
if proceed
&& komorebi_client::send_message(
&SocketMessage::RetileWithResizeDimensions,
)
.is_err()
{
tracing::error!("could not send message to komorebi: Retile");
}
}
}
});
}
if let Some(update) = update {
komorebi_notification_state.selected_workspace = update;
}
}
if let Some(layout_config) = &self.layout {
if layout_config.enable {
let workspace_idx: Option<usize> = komorebi_notification_state
.workspaces
.iter()
.position(|o| komorebi_notification_state.selected_workspace.eq(&o.0));
komorebi_notification_state.layout.show(
ctx,
ui,
config,
layout_config,
workspace_idx,
);
}
}
if let Some(configuration_switcher) = &self.configuration_switcher {
if configuration_switcher.enable {
for (name, location) in configuration_switcher.configurations.iter() {
let path = PathBuf::from(location);
if path.is_file() {
config.apply_on_widget(false, ui,|ui|{
if SelectableFrame::new(false).show(ui, |ui|{
ui.add(Label::new(name).selectable(false))
})
.clicked()
{
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized,
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: ReplaceConfiguration"
"could not send message to komorebi: MouseFollowsFocus"
);
proceed = false;
}
if let Some(rect) = komorebi_notification_state.work_area_offset {
if proceed {
match komorebi_client::send_query(&SocketMessage::Query(
komorebi_client::StateQuery::FocusedMonitorIndex,
)) {
Ok(idx) => {
if let Ok(monitor_idx) = idx.parse::<usize>() {
if komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
rect,
),
)
.is_err()
{
tracing::error!(
"could not send message to komorebi: MonitorWorkAreaOffset"
);
}
}
}
Err(_) => {
tracing::error!(
"could not send message to komorebi: Query"
);
}
}
}
}
}});
}
}
}
}
if let Some(focused_window) = self.focused_window {
if focused_window.enable {
let titles = &komorebi_notification_state
.focused_container_information
.titles;
if !titles.is_empty() {
config.apply_on_widget(false, ui, |ui| {
let icons = &komorebi_notification_state
.focused_container_information
.icons;
let focused_window_idx = komorebi_notification_state
.focused_container_information
.focused_window_idx;
let iter = titles.iter().zip(icons.iter());
for (i, (title, icon)) in iter.enumerate() {
let selected = i == focused_window_idx;
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) {
DisplayFormat::IconAndText
} else {
DisplayFormat::Text
},
);
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format
{
if let Some(img) = icon {
Frame::none()
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y,
))
.show(ui, |ui| {
let response = ui.add(
Image::from(&img_to_texture(ctx, img))
.maintain_aspect_ratio(true)
.shrink_to_fit(),
);
if let DisplayFormat::Icon = format {
response.on_hover_text(title);
}
});
}
}
if let DisplayFormat::Text | DisplayFormat::IconAndText = format
{
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
custom_ui.add_sized_left_to_right(
Vec2::new(
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(title).selectable(false).truncate(),
);
}
})
.clicked()
{
if selected {
return;
}
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
false,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
);
}
if komorebi_client::send_message(&SocketMessage::FocusStackWindow(
i,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: FocusStackWindow"
);
}
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
);
}
}
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
ui.add_space(WIDGET_SPACING);
}
}
}
@@ -471,15 +402,33 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
#[derive(Clone, Debug)]
pub struct KomorebiNotificationState {
pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>,
pub workspaces: Vec<(String, bool)>,
pub selected_workspace: String,
pub focused_container_information: KomorebiNotificationStateContainerInformation,
pub focused_container_information: (Vec<String>, Vec<Option<RgbaImage>>, usize),
pub layout: KomorebiLayout,
pub hide_empty_workspaces: bool,
pub mouse_follows_focus: bool,
pub work_area_offset: Option<Rect>,
pub stack_accent: Option<Color32>,
pub monitor_index: usize,
}
#[derive(Copy, Clone, Debug)]
pub enum KomorebiLayout {
Default(komorebi_client::DefaultLayout),
Floating,
Paused,
Custom,
}
impl Display for KomorebiLayout {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
KomorebiLayout::Default(layout) => write!(f, "{layout}"),
KomorebiLayout::Floating => write!(f, "Floating"),
KomorebiLayout::Paused => write!(f, "Paused"),
KomorebiLayout::Custom => write!(f, "Custom"),
}
}
}
impl KomorebiNotificationState {
@@ -523,8 +472,6 @@ impl KomorebiNotificationState {
},
}
self.monitor_index = monitor_index;
self.mouse_follows_focus = notification.state.mouse_follows_focus;
let monitor = &notification.state.monitors.elements()[monitor_index];
@@ -546,100 +493,64 @@ impl KomorebiNotificationState {
true
};
if should_show {
workspaces.push((
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
ws.into(),
));
}
workspaces.push((
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
should_show,
));
}
self.workspaces = workspaces;
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
komorebi_client::Layout::Default(layout) => KomorebiLayout::Default(*layout),
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
};
if monitor.workspaces()[focused_workspace_idx]
.monocle_container()
.is_some()
{
self.layout = KomorebiLayout::Monocle;
} else if !*monitor.workspaces()[focused_workspace_idx].tile() {
if !*monitor.workspaces()[focused_workspace_idx].tile() {
self.layout = KomorebiLayout::Floating;
} else if notification.state.is_paused {
self.layout = KomorebiLayout::Paused;
} else {
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
komorebi_client::Layout::Default(layout) => {
KomorebiLayout::Default(*layout)
}
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
};
}
self.focused_container_information =
(&monitor.workspaces()[focused_workspace_idx]).into();
if notification.state.is_paused {
self.layout = KomorebiLayout::Paused;
}
if let Some(container) =
monitor.workspaces()[focused_workspace_idx].monocle_container()
{
self.focused_container_information = (
container
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
container
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
container.focused_window_idx(),
);
} else if let Some(container) =
monitor.workspaces()[focused_workspace_idx].focused_container()
{
self.focused_container_information = (
container
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
container
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
container.focused_window_idx(),
);
} else {
self.focused_container_information.0.clear();
self.focused_container_information.1.clear();
self.focused_container_information.2 = 0;
}
}
}
}
}
#[derive(Clone, Debug)]
pub struct KomorebiNotificationStateContainerInformation {
pub titles: Vec<String>,
pub icons: Vec<Option<RgbaImage>>,
pub focused_window_idx: usize,
}
impl From<&Workspace> for KomorebiNotificationStateContainerInformation {
fn from(value: &Workspace) -> Self {
let mut container_info = Self::EMPTY;
if let Some(container) = value.monocle_container() {
container_info = container.into();
} else if let Some(container) = value.focused_container() {
container_info = container.into();
}
for floating_window in value.floating_windows() {
if floating_window.is_focused() {
container_info = floating_window.into();
}
}
container_info
}
}
impl From<&Container> for KomorebiNotificationStateContainerInformation {
fn from(value: &Container) -> Self {
Self {
titles: value
.windows()
.iter()
.map(|w| w.title().unwrap_or_default())
.collect::<Vec<_>>(),
icons: value
.windows()
.iter()
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
.collect::<Vec<_>>(),
focused_window_idx: value.focused_window_idx(),
}
}
}
impl From<&Window> for KomorebiNotificationStateContainerInformation {
fn from(value: &Window) -> Self {
Self {
titles: vec![value.title().unwrap_or_default()],
icons: vec![windows_icons::get_icon_by_process_id(value.process_id())],
focused_window_idx: 0,
}
}
}
impl KomorebiNotificationStateContainerInformation {
pub const EMPTY: Self = Self {
titles: vec![],
icons: vec![],
focused_window_idx: 0,
};
}

View File

@@ -1,311 +0,0 @@
use crate::config::DisplayFormat;
use crate::komorebi::KomorebiLayoutConfig;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use eframe::egui::vec2;
use eframe::egui::Context;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Label;
use eframe::egui::Rounding;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::TextStyle;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use komorebi_client::SocketMessage;
use schemars::JsonSchema;
use serde::de::Error;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde_json::from_str;
use std::fmt::Display;
use std::fmt::Formatter;
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq)]
#[serde(untagged)]
pub enum KomorebiLayout {
Default(komorebi_client::DefaultLayout),
Monocle,
Floating,
Paused,
Custom,
}
impl<'de> Deserialize<'de> for KomorebiLayout {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = String::deserialize(deserializer)?;
// Attempt to deserialize the string as a DefaultLayout
if let Ok(default_layout) =
from_str::<komorebi_client::DefaultLayout>(&format!("\"{}\"", s))
{
return Ok(KomorebiLayout::Default(default_layout));
}
// Handle other cases
match s.as_str() {
"Monocle" => Ok(KomorebiLayout::Monocle),
"Floating" => Ok(KomorebiLayout::Floating),
"Paused" => Ok(KomorebiLayout::Paused),
"Custom" => Ok(KomorebiLayout::Custom),
_ => Err(Error::custom(format!("Invalid layout: {}", s))),
}
}
}
impl Display for KomorebiLayout {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
KomorebiLayout::Default(layout) => write!(f, "{layout}"),
KomorebiLayout::Monocle => write!(f, "Monocle"),
KomorebiLayout::Floating => write!(f, "Floating"),
KomorebiLayout::Paused => write!(f, "Paused"),
KomorebiLayout::Custom => write!(f, "Custom"),
}
}
}
impl KomorebiLayout {
fn is_default(&mut self) -> bool {
matches!(self, KomorebiLayout::Default(_))
}
fn on_click(
&mut self,
show_options: &bool,
monitor_idx: usize,
workspace_idx: Option<usize>,
) -> bool {
if self.is_default() {
!show_options
} else {
self.on_click_option(monitor_idx, workspace_idx);
false
}
}
fn on_click_option(&mut self, monitor_idx: usize, workspace_idx: Option<usize>) {
match self {
KomorebiLayout::Default(option) => {
if let Some(ws_idx) = workspace_idx {
if komorebi_client::send_message(&SocketMessage::WorkspaceLayout(
monitor_idx,
ws_idx,
*option,
))
.is_err()
{
tracing::error!("could not send message to komorebi: WorkspaceLayout");
}
}
}
KomorebiLayout::Monocle => {
if komorebi_client::send_message(&SocketMessage::ToggleMonocle).is_err() {
tracing::error!("could not send message to komorebi: ToggleMonocle");
}
}
KomorebiLayout::Floating => {
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err() {
tracing::error!("could not send message to komorebi: ToggleTiling");
}
}
KomorebiLayout::Paused => {
if komorebi_client::send_message(&SocketMessage::TogglePause).is_err() {
tracing::error!("could not send message to komorebi: TogglePause");
}
}
KomorebiLayout::Custom => {}
}
}
fn show_icon(&mut self, font_id: FontId, ctx: &Context, ui: &mut Ui) {
// paint custom icons for the layout
let size = Vec2::splat(font_id.size);
let (response, painter) = ui.allocate_painter(size, Sense::hover());
let color = ctx.style().visuals.selection.stroke.color;
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let rounding = Rounding::same(rect.width() * 0.1);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke);
match self {
KomorebiLayout::Default(layout) => match layout {
komorebi_client::DefaultLayout::BSP => {
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
painter.line_segment([c, c + vec2(r, 0.0)], stroke);
painter.line_segment([c + vec2(r / 2.0, 0.0), c + vec2(r / 2.0, r)], stroke);
}
komorebi_client::DefaultLayout::Columns => {
painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);
}
komorebi_client::DefaultLayout::Rows => {
painter.line_segment([c - vec2(r, r / 2.0), c + vec2(r, -r / 2.0)], stroke);
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
painter.line_segment([c - vec2(r, -r / 2.0), c + vec2(r, r / 2.0)], stroke);
}
komorebi_client::DefaultLayout::VerticalStack => {
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
painter.line_segment([c, c + vec2(r, 0.0)], stroke);
}
komorebi_client::DefaultLayout::RightMainVerticalStack => {
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
painter.line_segment([c - vec2(r, 0.0), c], stroke);
}
komorebi_client::DefaultLayout::HorizontalStack => {
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
painter.line_segment([c, c + vec2(0.0, r)], stroke);
}
komorebi_client::DefaultLayout::UltrawideVerticalStack => {
painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);
painter.line_segment([c + vec2(r / 2.0, 0.0), c + vec2(r, 0.0)], stroke);
painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);
}
komorebi_client::DefaultLayout::Grid => {
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
}
},
KomorebiLayout::Monocle => {}
KomorebiLayout::Floating => {
let mut rect_left = response.rect;
rect_left.set_width(rect.width() * 0.5);
rect_left.set_height(rect.height() * 0.5);
let mut rect_right = rect_left;
rect_left = rect_left.translate(Vec2::new(
rect.width() * 0.1 + stroke.width,
rect.width() * 0.1 + stroke.width,
));
rect_right = rect_right.translate(Vec2::new(
rect.width() * 0.35 + stroke.width,
rect.width() * 0.35 + stroke.width,
));
painter.rect_filled(rect_left, rounding, color);
painter.rect_stroke(rect_right, rounding, stroke);
}
KomorebiLayout::Paused => {
let mut rect_left = response.rect;
rect_left.set_width(rect.width() * 0.25);
rect_left.set_height(rect.height() * 0.8);
let mut rect_right = rect_left;
rect_left = rect_left.translate(Vec2::new(
rect.width() * 0.2 + stroke.width,
rect.width() * 0.1 + stroke.width,
));
rect_right = rect_right.translate(Vec2::new(
rect.width() * 0.55 + stroke.width,
rect.width() * 0.1 + stroke.width,
));
painter.rect_filled(rect_left, rounding, color);
painter.rect_filled(rect_right, rounding, color);
}
KomorebiLayout::Custom => {
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
painter.line_segment([c + vec2(0.0, r / 2.0), c + vec2(r, r / 2.0)], stroke);
painter.line_segment([c - vec2(0.0, r / 3.0), c - vec2(r, r / 3.0)], stroke);
}
}
}
pub fn show(
&mut self,
ctx: &Context,
ui: &mut Ui,
render_config: &mut RenderConfig,
layout_config: &KomorebiLayoutConfig,
workspace_idx: Option<usize>,
) {
let monitor_idx = render_config.monitor_idx;
let font_id = ctx
.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap_or_else(FontId::default);
let mut show_options = RenderConfig::load_show_komorebi_layout_options();
let format = layout_config.display.unwrap_or(DisplayFormat::IconAndText);
if !self.is_default() {
show_options = false;
}
render_config.apply_on_widget(false, ui, |ui| {
let layout_frame = SelectableFrame::new(false)
.show(ui, |ui| {
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
self.show_icon(font_id.clone(), ctx, ui);
}
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
ui.add(Label::new(self.to_string()).selectable(false));
}
})
.on_hover_text(self.to_string());
if layout_frame.clicked() {
show_options = self.on_click(&show_options, monitor_idx, workspace_idx);
}
if show_options {
if let Some(workspace_idx) = workspace_idx {
Frame::none().show(ui, |ui| {
ui.add(
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
.selectable(false),
);
let mut layout_options = layout_config.options.clone().unwrap_or(vec![
KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),
KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),
KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),
KomorebiLayout::Default(
komorebi_client::DefaultLayout::RightMainVerticalStack,
),
KomorebiLayout::Default(
komorebi_client::DefaultLayout::HorizontalStack,
),
KomorebiLayout::Default(
komorebi_client::DefaultLayout::UltrawideVerticalStack,
),
KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),
//KomorebiLayout::Custom,
KomorebiLayout::Monocle,
KomorebiLayout::Floating,
KomorebiLayout::Paused,
]);
for layout_option in &mut layout_options {
if SelectableFrame::new(self == layout_option)
.show(ui, |ui| layout_option.show_icon(font_id.clone(), ctx, ui))
.on_hover_text(match layout_option {
KomorebiLayout::Default(layout) => layout.to_string(),
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
KomorebiLayout::Floating => "Toggle tiling".to_string(),
KomorebiLayout::Paused => "Toggle pause".to_string(),
KomorebiLayout::Custom => "Custom".to_string(),
})
.clicked()
{
layout_option.on_click_option(monitor_idx, Some(workspace_idx));
show_options = false;
};
}
});
}
}
});
RenderConfig::store_show_komorebi_layout_options(show_options);
}
}

View File

@@ -4,12 +4,9 @@ mod config;
mod cpu;
mod date;
mod komorebi;
mod komorebi_layout;
mod media;
mod memory;
mod network;
mod render;
mod selected_frame;
mod storage;
mod time;
mod ui;
@@ -31,7 +28,6 @@ use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
@@ -46,11 +42,12 @@ use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
pub static WIDGET_SPACING: f32 = 10.0;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_RIGHT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
pub static BAR_HEIGHT: f32 = 50.0;
#[derive(Parser)]
@@ -237,8 +234,6 @@ fn main() -> color_eyre::Result<()> {
Ordering::SeqCst,
);
MONITOR_INDEX.store(config.monitor.index, Ordering::SeqCst);
match config.position {
None => {
config.position = Some(PositionConfig {
@@ -271,7 +266,7 @@ fn main() -> color_eyre::Result<()> {
let viewport_builder = ViewportBuilder::default()
.with_decorations(false)
.with_transparent(true)
// .with_transparent(config.transparent)
.with_taskbar(false);
let native_options = eframe::NativeOptions {

View File

@@ -1,7 +1,7 @@
use crate::render::RenderConfig;
use crate::ui::CustomUi;
use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -78,7 +78,7 @@ impl Media {
}
impl BarWidget for Media {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -102,26 +102,26 @@ impl BarWidget for Media {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
if custom_ui
.add_sized_left_to_right(
Vec2::new(
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(layout_job)
.selectable(false)
.sense(Sense::click())
.truncate(),
)
.clicked()
{
self.toggle();
}
});
if custom_ui
.add_sized_left_to_right(
Vec2::new(
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(layout_job)
.selectable(false)
.sense(Sense::click())
.truncate(),
)
.clicked()
{
self.toggle();
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -73,7 +73,7 @@ impl Memory {
}
impl BarWidget for Memory {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -102,23 +102,22 @@ impl BarWidget for Memory {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
if let Err(error) =
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
eprintln!("{}", error)
}
eprintln!("{}", error)
}
});
}
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -37,38 +37,130 @@ pub struct NetworkConfig {
impl From<NetworkConfig> for Network {
fn from(value: NetworkConfig) -> Self {
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
let mut last_state_data = vec![];
let mut last_state_transmitted = vec![];
let mut networks_total_data_transmitted = Networks::new_with_refreshed_list();
let mut networks_network_activity = Networks::new_with_refreshed_list();
let mut default_interface = String::new();
let prefix = value.label_prefix.unwrap_or(LabelPrefix::Icon);
if let Ok(interface) = netdev::get_default_interface() {
if let Some(friendly_name) = interface.friendly_name {
default_interface.clone_from(&friendly_name);
if value.show_total_data_transmitted {
networks_total_data_transmitted.refresh();
for (interface_name, data) in &networks_total_data_transmitted {
if friendly_name.eq(interface_name) {
last_state_data.push(match prefix {
LabelPrefix::None => format!(
"{} | {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Icon => format!(
"{} {} | {} {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Text => format!(
"\u{2211}DOWN: {} | \u{2211}UP: {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::IconAndText => format!(
"{} \u{2211}DOWN: {} | {} \u{2211}UP: {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
})
}
}
}
if value.show_network_activity {
networks_network_activity.refresh();
for (interface_name, data) in &networks_network_activity {
if friendly_name.eq(interface_name) {
last_state_transmitted.push(match prefix {
LabelPrefix::None => format!(
"{: >width$}/s | {: >width$}/s",
to_pretty_bytes(data.received(), 1),
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
LabelPrefix::Icon => format!(
"{} {: >width$}/s | {} {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
LabelPrefix::Text => format!(
"DOWN: {: >width$}/s | UP: {: >width$}/s",
to_pretty_bytes(data.received(), 1),
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
LabelPrefix::IconAndText => format!(
"{} DOWN: {: >width$}/s | {} UP: {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.transmitted(), 1),
width =
value.network_activity_fill_characters.unwrap_or_default(),
),
})
}
}
}
}
}
Self {
enable: value.enable,
networks_network_activity: Networks::new_with_refreshed_list(),
default_interface: String::new(),
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
show_total_activity: value.show_total_data_transmitted,
show_activity: value.show_network_activity,
networks_total_data_transmitted,
networks_network_activity,
default_interface,
data_refresh_interval: value.data_refresh_interval.unwrap_or(10),
label_prefix: prefix,
show_total_data_transmitted: value.show_total_data_transmitted,
show_network_activity: value.show_network_activity,
network_activity_fill_characters: value
.network_activity_fill_characters
.unwrap_or_default(),
last_state_total_activity: vec![],
last_state_activity: vec![],
last_updated_network_activity: Instant::now()
.checked_sub(Duration::from_secs(data_refresh_interval))
.unwrap(),
last_state_total_data_transmitted: last_state_data,
last_state_network_activity: last_state_transmitted,
last_updated_total_data_transmitted: Instant::now(),
last_updated_network_activity: Instant::now(),
}
}
}
pub struct Network {
pub enable: bool,
pub show_total_activity: bool,
pub show_activity: bool,
pub show_total_data_transmitted: bool,
pub show_network_activity: bool,
networks_total_data_transmitted: Networks,
networks_network_activity: Networks,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
default_interface: String,
last_state_total_activity: Vec<NetworkReading>,
last_state_activity: Vec<NetworkReading>,
last_state_total_data_transmitted: Vec<String>,
last_state_network_activity: Vec<String>,
last_updated_total_data_transmitted: Instant,
last_updated_network_activity: Instant,
network_activity_fill_characters: usize,
}
@@ -82,192 +174,164 @@ impl Network {
}
}
fn network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {
let mut activity = self.last_state_activity.clone();
let mut total_activity = self.last_state_total_activity.clone();
fn network_activity(&mut self) -> Vec<String> {
let mut outputs = self.last_state_network_activity.clone();
let now = Instant::now();
if now.duration_since(self.last_updated_network_activity)
> Duration::from_secs(self.data_refresh_interval)
if self.show_network_activity
&& now.duration_since(self.last_updated_network_activity)
> Duration::from_secs(self.data_refresh_interval)
{
activity.clear();
total_activity.clear();
outputs.clear();
if let Ok(interface) = netdev::get_default_interface() {
if let Some(friendly_name) = &interface.friendly_name {
self.default_interface.clone_from(friendly_name);
self.networks_network_activity.refresh();
for (interface_name, data) in &self.networks_network_activity {
if friendly_name.eq(interface_name) {
if self.show_activity {
activity.push(NetworkReading::new(
NetworkReadingFormat::Speed,
Self::to_pretty_bytes(
data.received(),
self.data_refresh_interval,
if self.show_network_activity {
self.networks_network_activity.refresh();
for (interface_name, data) in &self.networks_network_activity {
if friendly_name.eq(interface_name) {
outputs.push(match self.label_prefix {
LabelPrefix::None => format!(
"{: >width$}/s | {: >width$}/s",
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
),
Self::to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval,
LabelPrefix::Icon => format!(
"{} {: >width$}/s | {} {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
),
));
}
if self.show_total_activity {
total_activity.push(NetworkReading::new(
NetworkReadingFormat::Total,
Self::to_pretty_bytes(data.total_received(), 1),
Self::to_pretty_bytes(data.total_transmitted(), 1),
))
LabelPrefix::Text => format!(
"DOWN: {: >width$}/s | UP: {: >width$}/s",
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
),
LabelPrefix::IconAndText => {
format!(
"{} DOWN: {: >width$}/s | {} UP: {: >width$}/s",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(
data.received(),
self.data_refresh_interval
),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(
data.transmitted(),
self.data_refresh_interval
),
width = self.network_activity_fill_characters,
)
}
})
}
}
}
}
}
self.last_state_activity.clone_from(&activity);
self.last_state_total_activity.clone_from(&total_activity);
self.last_state_network_activity.clone_from(&outputs);
self.last_updated_network_activity = now;
}
(activity, total_activity)
outputs
}
fn reading_to_label(&self, ctx: &Context, reading: NetworkReading) -> Label {
let (text_down, text_up) = match self.label_prefix {
LabelPrefix::None | LabelPrefix::Icon => match reading.format {
NetworkReadingFormat::Speed => (
format!(
"{: >width$}/s | ",
reading.received_text,
width = self.network_activity_fill_characters
),
format!(
"{: >width$}/s",
reading.transmitted_text,
width = self.network_activity_fill_characters
),
),
NetworkReadingFormat::Total => (
format!("{} | ", reading.received_text),
reading.transmitted_text,
),
},
LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {
NetworkReadingFormat::Speed => (
format!(
"DOWN: {: >width$}/s | ",
reading.received_text,
width = self.network_activity_fill_characters
),
format!(
"UP: {: >width$}/s",
reading.transmitted_text,
width = self.network_activity_fill_characters
),
),
NetworkReadingFormat::Total => (
format!("\u{2211}DOWN: {}/s | ", reading.received_text),
format!("\u{2211}UP: {}/s", reading.transmitted_text),
),
},
};
fn total_data_transmitted(&mut self) -> Vec<String> {
let mut outputs = self.last_state_total_data_transmitted.clone();
let now = Instant::now();
let font_id = ctx
.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap_or_else(FontId::default);
if self.show_total_data_transmitted
&& now.duration_since(self.last_updated_total_data_transmitted)
> Duration::from_secs(self.data_refresh_interval)
{
outputs.clear();
let icon_format =
TextFormat::simple(font_id.clone(), ctx.style().visuals.selection.stroke.color);
let text_format = TextFormat::simple(font_id.clone(), ctx.style().visuals.text_color());
if let Ok(interface) = netdev::get_default_interface() {
if let Some(friendly_name) = &interface.friendly_name {
if self.show_total_data_transmitted {
self.networks_total_data_transmitted.refresh();
// icon
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::ARROW_FAT_DOWN.to_string()
for (interface_name, data) in &self.networks_total_data_transmitted {
if friendly_name.eq(interface_name) {
outputs.push(match self.label_prefix {
LabelPrefix::None => format!(
"{} | {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Icon => format!(
"{} {} | {} {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::Text => format!(
"\u{2211}DOWN: {} | \u{2211}UP: {}",
to_pretty_bytes(data.total_received(), 1),
to_pretty_bytes(data.total_transmitted(), 1),
),
LabelPrefix::IconAndText => format!(
"{} \u{2211}DOWN: {} | {} \u{2211}UP: {}",
egui_phosphor::regular::ARROW_FAT_DOWN,
to_pretty_bytes(data.total_received(), 1),
egui_phosphor::regular::ARROW_FAT_UP,
to_pretty_bytes(data.total_transmitted(), 1),
),
})
}
}
}
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
icon_format.font_id.clone(),
icon_format.color,
100.0,
);
}
// text
layout_job.append(
&text_down,
ctx.style().spacing.item_spacing.x,
text_format.clone(),
);
// icon
layout_job.append(
&match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::ARROW_FAT_UP.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
0.0,
icon_format.clone(),
);
// text
layout_job.append(
&text_up,
ctx.style().spacing.item_spacing.x,
text_format.clone(),
);
Label::new(layout_job).selectable(false)
}
fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> String {
let input = input_in_bytes as f32 / timespan_in_s as f32;
let mut magnitude = input.log(1024f32) as u32;
// let the base unit be KiB
if magnitude < 1 {
magnitude = 1;
self.last_state_total_data_transmitted.clone_from(&outputs);
self.last_updated_total_data_transmitted = now;
}
let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);
let result = input / ((1u64) << (magnitude * 10)) as f32;
match base {
Some(DataUnit::B) => format!("{result:.1} B"),
Some(unit) => format!("{result:.1} {unit}iB"),
None => String::from("Unknown data unit"),
}
outputs
}
}
impl BarWidget for Network {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.show_total_activity || self.show_activity {
let (activity, total_activity) = self.network_activity();
if self.show_total_activity {
for reading in total_activity {
config.apply_on_widget(true, ui, |ui| {
ui.add(self.reading_to_label(ctx, reading));
});
}
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.show_total_data_transmitted {
for output in self.total_data_transmitted() {
ui.add(Label::new(output).selectable(false));
}
if self.show_activity {
for reading in activity {
config.apply_on_widget(true, ui, |ui| {
ui.add(self.reading_to_label(ctx, reading));
});
}
ui.add_space(WIDGET_SPACING);
}
if self.show_network_activity {
for output in self.network_activity() {
ui.add(Label::new(output).selectable(false));
}
ui.add_space(WIDGET_SPACING);
}
if self.enable {
@@ -303,44 +367,21 @@ impl BarWidget for Network {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() {
eprintln!("{}", error)
}
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() {
eprintln!("{}", error)
}
});
}
}
}
}
}
#[derive(Clone)]
enum NetworkReadingFormat {
Speed = 0,
Total = 1,
}
#[derive(Clone)]
struct NetworkReading {
pub format: NetworkReadingFormat,
pub received_text: String,
pub transmitted_text: String,
}
impl NetworkReading {
pub fn new(format: NetworkReadingFormat, received: String, transmitted: String) -> Self {
NetworkReading {
format,
received_text: received,
transmitted_text: transmitted,
ui.add_space(WIDGET_SPACING);
}
}
}
@@ -363,3 +404,22 @@ impl fmt::Display for DataUnit {
write!(f, "{:?}", self)
}
}
fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> String {
let input = input_in_bytes as f32 / timespan_in_s as f32;
let mut magnitude = input.log(1024f32) as u32;
// let the base unit be KiB
if magnitude < 1 {
magnitude = 1;
}
let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);
let result = input / ((1u64) << (magnitude * 10)) as f32;
match base {
Some(DataUnit::B) => format!("{result:.1} B"),
Some(unit) => format!("{result:.1} {unit}iB"),
None => String::from("Unknown data unit"),
}
}

View File

@@ -1,283 +0,0 @@
use crate::bar::Alignment;
use crate::config::KomobarConfig;
use eframe::egui::Color32;
use eframe::egui::Frame;
use eframe::egui::InnerResponse;
use eframe::egui::Margin;
use eframe::egui::Rounding;
use eframe::egui::Shadow;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind")]
pub enum Grouping {
/// No grouping is applied
None,
/// Widgets are grouped as a whole
Bar(GroupingConfig),
/// Widgets are grouped by alignment
Alignment(GroupingConfig),
/// Widgets are grouped individually
Widget(GroupingConfig),
}
#[derive(Copy, Clone)]
pub struct RenderConfig {
/// Komorebi monitor index of the monitor on which to render the bar
pub monitor_idx: usize,
/// Spacing between widgets
pub spacing: f32,
/// Sets how widgets are grouped
pub grouping: Grouping,
/// Background color
pub background_color: Color32,
/// Alignment of the widgets
pub alignment: Option<Alignment>,
/// Add more inner margin when adding a widget group
pub more_inner_margin: bool,
/// Set to true after the first time the apply_on_widget was called on an alignment
pub applied_on_widget: bool,
}
pub trait RenderExt {
fn new_renderconfig(&self, background_color: Color32) -> RenderConfig;
}
impl RenderExt for &KomobarConfig {
fn new_renderconfig(&self, background_color: Color32) -> RenderConfig {
RenderConfig {
monitor_idx: self.monitor.index,
spacing: self.widget_spacing.unwrap_or(10.0),
grouping: self.grouping.unwrap_or(Grouping::None),
background_color,
alignment: None,
more_inner_margin: false,
applied_on_widget: false,
}
}
}
impl RenderConfig {
pub fn load_show_komorebi_layout_options() -> bool {
SHOW_KOMOREBI_LAYOUT_OPTIONS.load(Ordering::SeqCst) != 0
}
pub fn store_show_komorebi_layout_options(show: bool) {
SHOW_KOMOREBI_LAYOUT_OPTIONS.store(show as usize, Ordering::SeqCst);
}
pub fn new() -> Self {
Self {
monitor_idx: 0,
spacing: 0.0,
grouping: Grouping::None,
background_color: Color32::BLACK,
alignment: None,
more_inner_margin: false,
applied_on_widget: false,
}
}
pub fn apply_on_bar<R>(
&mut self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.alignment = None;
if let Grouping::Bar(config) = self.grouping {
return self.define_group(None, config, ui, add_contents);
}
Self::fallback_group(ui, add_contents)
}
pub fn apply_on_alignment<R>(
&mut self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.alignment = None;
if let Grouping::Alignment(config) = self.grouping {
return self.define_group(None, config, ui, add_contents);
}
Self::fallback_group(ui, add_contents)
}
pub fn apply_on_widget<R>(
&mut self,
more_inner_margin: bool,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.more_inner_margin = more_inner_margin;
let outer_margin = self.widget_outer_margin(ui);
if let Grouping::Widget(config) = self.grouping {
return self.define_group(Some(outer_margin), config, ui, add_contents);
}
self.fallback_widget_group(Some(outer_margin), ui, add_contents)
}
fn fallback_group<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
InnerResponse {
inner: add_contents(ui),
response: ui.response().clone(),
}
}
fn fallback_widget_group<R>(
&mut self,
outer_margin: Option<Margin>,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Frame::none()
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(5.0, 0.0),
false => Margin::same(0.0),
})
.show(ui, add_contents)
}
fn define_group<R>(
&mut self,
outer_margin: Option<Margin>,
config: GroupingConfig,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Frame::group(ui.style_mut())
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(8.0, 3.0),
false => Margin::symmetric(3.0, 3.0),
})
.stroke(ui.style().visuals.widgets.noninteractive.bg_stroke)
.rounding(match config.rounding {
Some(rounding) => rounding.into(),
None => ui.style().visuals.widgets.noninteractive.rounding,
})
.fill(
self.background_color
.try_apply_alpha(config.transparency_alpha),
)
.shadow(match config.style {
Some(style) => match style {
// new styles can be added if needed here
GroupingStyle::Default => Shadow::NONE,
GroupingStyle::DefaultWithShadow => Shadow {
blur: 4.0,
offset: Vec2::new(1.0, 1.0),
spread: 3.0,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
},
None => Shadow::NONE,
})
.show(ui, add_contents)
}
fn widget_outer_margin(&mut self, ui: &mut Ui) -> Margin {
let spacing = if self.applied_on_widget {
// Remove the default item spacing from the margin
self.spacing - ui.spacing().item_spacing.x
} else {
0.0
};
if !self.applied_on_widget {
self.applied_on_widget = true;
}
Margin {
left: match self.alignment {
Some(align) => match align {
Alignment::Left => spacing,
Alignment::Center => spacing,
Alignment::Right => 0.0,
},
None => 0.0,
},
right: match self.alignment {
Some(align) => match align {
Alignment::Left => 0.0,
Alignment::Center => 0.0,
Alignment::Right => spacing,
},
None => 0.0,
},
top: 0.0,
bottom: 0.0,
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GroupingConfig {
/// Styles for the grouping
pub style: Option<GroupingStyle>,
/// Alpha value for the color transparency [[0-255]] (default: 200)
pub transparency_alpha: Option<u8>,
/// Rounding values for the 4 corners. Can be a single or 4 values.
pub rounding: Option<RoundingConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum GroupingStyle {
#[serde(alias = "CtByte")]
Default,
/// A black shadow is added under the default group
#[serde(alias = "CtByteWithShadow")]
DefaultWithShadow,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum RoundingConfig {
/// All 4 corners are the same
Same(f32),
/// All 4 corners are custom. Order: NW, NE, SW, SE
Individual([f32; 4]),
}
impl From<RoundingConfig> for Rounding {
fn from(value: RoundingConfig) -> Self {
match value {
RoundingConfig::Same(value) => Rounding::same(value),
RoundingConfig::Individual(values) => Self {
nw: values[0],
ne: values[1],
sw: values[2],
se: values[3],
},
}
}
}
pub trait Color32Ext {
fn try_apply_alpha(self, transparency_alpha: Option<u8>) -> Self;
}
impl Color32Ext for Color32 {
/// Tries to apply the alpha value to the Color32
fn try_apply_alpha(self, transparency_alpha: Option<u8>) -> Self {
if let Some(alpha) = transparency_alpha {
return Color32::from_rgba_unmultiplied(self.r(), self.g(), self.b(), alpha);
}
self
}
}

View File

@@ -1,55 +0,0 @@
use eframe::egui::Frame;
use eframe::egui::Margin;
use eframe::egui::Response;
use eframe::egui::Sense;
use eframe::egui::Ui;
/// Same as SelectableLabel, but supports all content
pub struct SelectableFrame {
selected: bool,
}
impl SelectableFrame {
pub fn new(selected: bool) -> Self {
Self { selected }
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
let Self { selected } = self;
Frame::none()
.show(ui, |ui| {
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
if ui.is_rect_visible(response.rect) {
let inner_margin = Margin::symmetric(
ui.style().spacing.button_padding.x,
ui.style().spacing.button_padding.y,
);
if selected
|| response.hovered()
|| response.highlighted()
|| response.has_focus()
{
let visuals = ui.style().interact_selectable(&response, selected);
Frame::none()
.stroke(visuals.bg_stroke)
.rounding(visuals.rounding)
.fill(visuals.bg_fill)
.inner_margin(inner_margin)
.show(ui, add_contents);
} else {
Frame::none()
.inner_margin(inner_margin)
.show(ui, add_contents);
}
}
response
})
.inner
.on_hover_cursor(eframe::egui::CursorIcon::PointingHand)
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -79,7 +79,7 @@ impl Storage {
}
impl BarWidget for Storage {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let font_id = ctx
.style()
@@ -107,27 +107,27 @@ impl BarWidget for Storage {
TextFormat::simple(font_id.clone(), ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe")
.args([
"/C",
"explorer.exe",
output.split(' ').collect::<Vec<&str>>()[0],
])
.spawn()
{
if let Err(error) = Command::new("cmd.exe")
.args([
"/C",
"explorer.exe",
output.split(' ').collect::<Vec<&str>>()[0],
])
.spawn()
{
eprintln!("{}", error)
}
eprintln!("{}", error)
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -77,7 +77,7 @@ impl Time {
}
impl BarWidget for Time {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
if self.enable {
let mut output = self.output();
if !output.is_empty() {
@@ -110,19 +110,19 @@ impl BarWidget for Time {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.toggle()
}
});
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.toggle()
}
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -12,7 +12,6 @@ use crate::memory::Memory;
use crate::memory::MemoryConfig;
use crate::network::Network;
use crate::network::NetworkConfig;
use crate::render::RenderConfig;
use crate::storage::Storage;
use crate::storage::StorageConfig;
use crate::time::Time;
@@ -24,7 +23,7 @@ use serde::Deserialize;
use serde::Serialize;
pub trait BarWidget {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
fn render(&mut self, ctx: &Context, ui: &mut Ui);
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]

View File

@@ -1,7 +1,6 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc)]
pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
@@ -55,7 +54,6 @@ use std::io::BufReader;
use std::io::Read;
use std::io::Write;
use std::net::Shutdown;
use std::time::Duration;
pub use uds_windows::UnixListener;
use uds_windows::UnixStream;
@@ -64,7 +62,6 @@ const KOMOREBI: &str = "komorebi.sock";
pub fn send_message(message: &SocketMessage) -> std::io::Result<()> {
let socket = DATA_DIR.join(KOMOREBI);
let mut stream = UnixStream::connect(socket)?;
stream.set_write_timeout(Some(Duration::from_secs(1)))?;
stream.write_all(serde_json::to_string(message)?.as_bytes())
}
@@ -72,8 +69,6 @@ pub fn send_query(message: &SocketMessage) -> std::io::Result<String> {
let socket = DATA_DIR.join(KOMOREBI);
let mut stream = UnixStream::connect(socket)?;
stream.set_read_timeout(Some(Duration::from_secs(1)))?;
stream.set_write_timeout(Some(Duration::from_secs(1)))?;
stream.write_all(serde_json::to_string(message)?.as_bytes())?;
stream.shutdown(Shutdown::Write)?;

View File

@@ -42,6 +42,7 @@ tracing-appender = { workspace = true }
tracing-subscriber = { workspace = true }
uds_windows = { workspace = true }
which = { workspace = true }
widestring = "1"
win32-display-data = { workspace = true }
windows = { workspace = true }
windows-core = { workspace = true }

View File

@@ -1,6 +1,22 @@
use crate::core::AnimationStyle;
use crate::core::Rect;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::f64::consts::PI;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_MANAGER;
use crate::ANIMATION_STYLE;
pub static ANIMATION_FPS: AtomicU64 = AtomicU64::new(60);
pub trait Ease {
fn evaluate(t: f64) -> f64;
@@ -354,8 +370,9 @@ impl Ease for EaseInOutBounce {
}
}
}
fn apply_ease_func(t: f64) -> f64 {
let style = *ANIMATION_STYLE.lock();
pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
match style {
AnimationStyle::Linear => Linear::evaluate(t),
AnimationStyle::EaseInSine => EaseInSine::evaluate(t),
@@ -389,3 +406,112 @@ pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t),
}
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Animation {
pub hwnd: isize,
}
impl Animation {
pub fn new(hwnd: isize) -> Self {
Self { hwnd }
}
/// Returns true if the animation needs to continue
pub fn cancel(&mut self) -> bool {
if !ANIMATION_MANAGER.lock().in_progress(self.hwnd) {
return true;
}
// should be more than 0
let cancel_idx = ANIMATION_MANAGER.lock().init_cancel(self.hwnd);
let max_duration = Duration::from_secs(1);
let spent_duration = Instant::now();
while ANIMATION_MANAGER.lock().in_progress(self.hwnd) {
if spent_duration.elapsed() >= max_duration {
ANIMATION_MANAGER.lock().end(self.hwnd);
}
std::thread::sleep(Duration::from_millis(
ANIMATION_DURATION.load(Ordering::SeqCst) / 2,
));
}
let latest_cancel_idx = ANIMATION_MANAGER.lock().latest_cancel_idx(self.hwnd);
ANIMATION_MANAGER.lock().end_cancel(self.hwnd);
latest_cancel_idx == cancel_idx
}
#[allow(clippy::cast_possible_truncation)]
pub fn lerp(start: i32, end: i32, t: f64) -> i32 {
let time = apply_ease_func(t);
f64::from(end - start)
.mul_add(time, f64::from(start))
.round() as i32
}
pub fn lerp_rect(start_rect: &Rect, end_rect: &Rect, t: f64) -> Rect {
Rect {
left: Self::lerp(start_rect.left, end_rect.left, t),
top: Self::lerp(start_rect.top, end_rect.top, t),
right: Self::lerp(start_rect.right, end_rect.right, t),
bottom: Self::lerp(start_rect.bottom, end_rect.bottom, t),
}
}
#[allow(clippy::cast_precision_loss)]
pub fn animate(
&mut self,
duration: Duration,
mut render_callback: impl FnMut(f64) -> Result<()>,
) -> Result<()> {
if ANIMATION_MANAGER.lock().in_progress(self.hwnd) {
let should_animate = self.cancel();
if !should_animate {
return Ok(());
}
}
ANIMATION_MANAGER.lock().start(self.hwnd);
let target_frame_time = Duration::from_millis(1000 / ANIMATION_FPS.load(Ordering::Relaxed));
let mut progress = 0.0;
let animation_start = Instant::now();
// start animation
while progress < 1.0 {
// check if animation is cancelled
if ANIMATION_MANAGER.lock().is_cancelled(self.hwnd) {
// cancel animation
ANIMATION_MANAGER.lock().cancel(self.hwnd);
return Ok(());
}
let frame_start = Instant::now();
// calculate progress
progress = animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
render_callback(progress).ok();
// sleep until next frame
let frame_time_elapsed = frame_start.elapsed();
if frame_time_elapsed < target_frame_time {
std::thread::sleep(target_frame_time - frame_time_elapsed);
}
}
ANIMATION_MANAGER.lock().end(self.hwnd);
// limit progress to 1.0 if animation took longer
if progress > 1.0 {
progress = 1.0;
}
// process animation for 1.0 to set target position
render_callback(progress)
}
}

View File

@@ -1,115 +0,0 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use super::prefix::AnimationPrefix;
#[derive(Debug, Clone, Copy)]
struct AnimationState {
pub in_progress: bool,
pub cancel_idx_counter: usize,
pub pending_cancel_count: usize,
}
#[derive(Debug)]
pub struct AnimationManager {
animations: HashMap<String, AnimationState>,
}
impl Default for AnimationManager {
fn default() -> Self {
Self::new()
}
}
impl AnimationManager {
pub fn new() -> Self {
Self {
animations: HashMap::new(),
}
}
pub fn is_cancelled(&self, animation_key: &str) -> bool {
if let Some(animation_state) = self.animations.get(animation_key) {
animation_state.pending_cancel_count > 0
} else {
false
}
}
pub fn in_progress(&self, animation_key: &str) -> bool {
if let Some(animation_state) = self.animations.get(animation_key) {
animation_state.in_progress
} else {
false
}
}
pub fn init_cancel(&mut self, animation_key: &str) -> usize {
if let Some(animation_state) = self.animations.get_mut(animation_key) {
animation_state.pending_cancel_count += 1;
animation_state.cancel_idx_counter += 1;
// return cancel idx
animation_state.cancel_idx_counter
} else {
0
}
}
pub fn latest_cancel_idx(&mut self, animation_key: &str) -> usize {
if let Some(animation_state) = self.animations.get_mut(animation_key) {
animation_state.cancel_idx_counter
} else {
0
}
}
pub fn end_cancel(&mut self, animation_key: &str) {
if let Some(animation_state) = self.animations.get_mut(animation_key) {
animation_state.pending_cancel_count -= 1;
}
}
pub fn cancel(&mut self, animation_key: &str) {
if let Some(animation_state) = self.animations.get_mut(animation_key) {
animation_state.in_progress = false;
}
}
pub fn start(&mut self, animation_key: &str) {
if let Entry::Vacant(e) = self.animations.entry(animation_key.to_string()) {
e.insert(AnimationState {
in_progress: true,
cancel_idx_counter: 0,
pending_cancel_count: 0,
});
return;
}
if let Some(animation_state) = self.animations.get_mut(animation_key) {
animation_state.in_progress = true;
}
}
pub fn end(&mut self, animation_key: &str) {
if let Some(animation_state) = self.animations.get_mut(animation_key) {
animation_state.in_progress = false;
if animation_state.pending_cancel_count == 0 {
self.animations.remove(animation_key);
}
}
}
pub fn count_in_progress(&self, animation_key_prefix: AnimationPrefix) -> usize {
self.animations
.keys()
.filter(|key| key.starts_with(animation_key_prefix.to_string().as_str()))
.count()
}
pub fn count(&self) -> usize {
self.animations.len()
}
}

View File

@@ -1,122 +0,0 @@
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use super::RenderDispatcher;
use super::ANIMATION_DURATION_GLOBAL;
use super::ANIMATION_FPS;
use super::ANIMATION_MANAGER;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct AnimationEngine;
impl AnimationEngine {
pub fn wait_for_all_animations() {
let max_duration = Duration::from_secs(20);
let spent_duration = Instant::now();
while ANIMATION_MANAGER.lock().count() > 0 {
if spent_duration.elapsed() >= max_duration {
break;
}
std::thread::sleep(Duration::from_millis(
ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst),
));
}
}
/// Returns true if the animation needs to continue
pub fn cancel(animation_key: &str) -> bool {
// should be more than 0
let cancel_idx = ANIMATION_MANAGER.lock().init_cancel(animation_key);
let max_duration = Duration::from_secs(5);
let spent_duration = Instant::now();
while ANIMATION_MANAGER.lock().in_progress(animation_key) {
if spent_duration.elapsed() >= max_duration {
ANIMATION_MANAGER.lock().end(animation_key);
}
std::thread::sleep(Duration::from_millis(250 / 2));
}
let latest_cancel_idx = ANIMATION_MANAGER.lock().latest_cancel_idx(animation_key);
ANIMATION_MANAGER.lock().end_cancel(animation_key);
latest_cancel_idx == cancel_idx
}
#[allow(clippy::cast_precision_loss)]
pub fn animate(
render_dispatcher: (impl RenderDispatcher + Send + 'static),
duration: Duration,
) -> Result<()> {
std::thread::spawn(move || {
let animation_key = render_dispatcher.get_animation_key();
if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) {
let should_animate = Self::cancel(animation_key.as_str());
if !should_animate {
return Ok(());
}
}
render_dispatcher.pre_render()?;
ANIMATION_MANAGER.lock().start(animation_key.as_str());
let target_frame_time =
Duration::from_millis(1000 / ANIMATION_FPS.load(Ordering::Relaxed));
let mut progress = 0.0;
let animation_start = Instant::now();
// start animation
while progress < 1.0 {
// check if animation is cancelled
if ANIMATION_MANAGER
.lock()
.is_cancelled(animation_key.as_str())
{
// cancel animation
ANIMATION_MANAGER.lock().cancel(animation_key.as_str());
return Ok(());
}
let frame_start = Instant::now();
// calculate progress
progress =
animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;
render_dispatcher.render(progress).ok();
// sleep until next frame
let frame_time_elapsed = frame_start.elapsed();
if frame_time_elapsed < target_frame_time {
std::thread::sleep(target_frame_time - frame_time_elapsed);
}
}
ANIMATION_MANAGER.lock().end(animation_key.as_str());
// limit progress to 1.0 if animation took longer
if progress != 1.0 {
progress = 1.0;
// process animation for 1.0 to set target position
render_dispatcher.render(progress).ok();
}
render_dispatcher.post_render()
});
Ok(())
}
}

View File

@@ -1,42 +0,0 @@
use crate::core::Rect;
use crate::AnimationStyle;
use super::style::apply_ease_func;
pub trait Lerp<T = Self> {
fn lerp(self, end: T, time: f64, style: AnimationStyle) -> T;
}
impl Lerp for i32 {
#[allow(clippy::cast_possible_truncation)]
fn lerp(self, end: i32, time: f64, style: AnimationStyle) -> i32 {
let time = apply_ease_func(time, style);
f64::from(end - self).mul_add(time, f64::from(self)).round() as i32
}
}
impl Lerp for f64 {
fn lerp(self, end: f64, time: f64, style: AnimationStyle) -> f64 {
let time = apply_ease_func(time, style);
(end - self).mul_add(time, self)
}
}
impl Lerp for u8 {
fn lerp(self, end: u8, time: f64, style: AnimationStyle) -> u8 {
(self as f64).lerp(end as f64, time, style) as u8
}
}
impl Lerp for Rect {
fn lerp(self, end: Rect, time: f64, style: AnimationStyle) -> Rect {
Rect {
left: self.left.lerp(end.left, time, style),
top: self.top.lerp(end.top, time, style),
right: self.right.lerp(end.right, time, style),
bottom: self.bottom.lerp(end.bottom, time, style),
}
}
}

View File

@@ -1,54 +0,0 @@
use crate::animation::animation_manager::AnimationManager;
use crate::core::animation::AnimationStyle;
use lazy_static::lazy_static;
use prefix::AnimationPrefix;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use parking_lot::Mutex;
pub use engine::AnimationEngine;
pub mod animation_manager;
pub mod engine;
pub mod lerp;
pub mod prefix;
pub mod render_dispatcher;
pub use render_dispatcher::RenderDispatcher;
pub mod style;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum PerAnimationPrefixConfig<T> {
Prefix(HashMap<AnimationPrefix, T>),
Global(T),
}
pub const DEFAULT_ANIMATION_ENABLED: bool = false;
pub const DEFAULT_ANIMATION_STYLE: AnimationStyle = AnimationStyle::Linear;
pub const DEFAULT_ANIMATION_DURATION: u64 = 250;
pub const DEFAULT_ANIMATION_FPS: u64 = 60;
lazy_static! {
pub static ref ANIMATION_MANAGER: Arc<Mutex<AnimationManager>> =
Arc::new(Mutex::new(AnimationManager::new()));
pub static ref ANIMATION_STYLE_GLOBAL: Arc<Mutex<AnimationStyle>> =
Arc::new(Mutex::new(DEFAULT_ANIMATION_STYLE));
pub static ref ANIMATION_ENABLED_GLOBAL: Arc<AtomicBool> =
Arc::new(AtomicBool::new(DEFAULT_ANIMATION_ENABLED));
pub static ref ANIMATION_DURATION_GLOBAL: Arc<AtomicU64> =
Arc::new(AtomicU64::new(DEFAULT_ANIMATION_DURATION));
pub static ref ANIMATION_STYLE_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, AnimationStyle>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref ANIMATION_ENABLED_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, bool>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref ANIMATION_DURATION_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, u64>>> =
Arc::new(Mutex::new(HashMap::new()));
}
pub static ANIMATION_FPS: AtomicU64 = AtomicU64::new(DEFAULT_ANIMATION_FPS);

View File

@@ -1,31 +0,0 @@
use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum AnimationPrefix {
Movement,
Transparency,
}
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {
format!("{}:{}", prefix, key)
}

View File

@@ -1,8 +0,0 @@
use color_eyre::Result;
pub trait RenderDispatcher {
fn get_animation_key(&self) -> String;
fn pre_render(&self) -> Result<()>;
fn render(&self, delta: f64) -> Result<()>;
fn post_render(&self) -> Result<()>;
}

View File

@@ -0,0 +1,108 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
pub static ANIMATIONS_IN_PROGRESS: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, Copy)]
struct AnimationState {
pub in_progress: bool,
pub cancel_idx_counter: usize,
pub pending_cancel_count: usize,
}
#[derive(Debug)]
pub struct AnimationManager {
animations: HashMap<isize, AnimationState>,
}
impl Default for AnimationManager {
fn default() -> Self {
Self::new()
}
}
impl AnimationManager {
pub fn new() -> Self {
Self {
animations: HashMap::new(),
}
}
pub fn is_cancelled(&self, hwnd: isize) -> bool {
if let Some(animation_state) = self.animations.get(&hwnd) {
animation_state.pending_cancel_count > 0
} else {
false
}
}
pub fn in_progress(&self, hwnd: isize) -> bool {
if let Some(animation_state) = self.animations.get(&hwnd) {
animation_state.in_progress
} else {
false
}
}
pub fn init_cancel(&mut self, hwnd: isize) -> usize {
if let Some(animation_state) = self.animations.get_mut(&hwnd) {
animation_state.pending_cancel_count += 1;
animation_state.cancel_idx_counter += 1;
// return cancel idx
animation_state.cancel_idx_counter
} else {
0
}
}
pub fn latest_cancel_idx(&mut self, hwnd: isize) -> usize {
if let Some(animation_state) = self.animations.get_mut(&hwnd) {
animation_state.cancel_idx_counter
} else {
0
}
}
pub fn end_cancel(&mut self, hwnd: isize) {
if let Some(animation_state) = self.animations.get_mut(&hwnd) {
animation_state.pending_cancel_count -= 1;
}
}
pub fn cancel(&mut self, hwnd: isize) {
if let Some(animation_state) = self.animations.get_mut(&hwnd) {
animation_state.in_progress = false;
}
}
pub fn start(&mut self, hwnd: isize) {
if let Entry::Vacant(e) = self.animations.entry(hwnd) {
e.insert(AnimationState {
in_progress: true,
cancel_idx_counter: 0,
pending_cancel_count: 0,
});
ANIMATIONS_IN_PROGRESS.store(self.animations.len(), Ordering::Release);
return;
}
if let Some(animation_state) = self.animations.get_mut(&hwnd) {
animation_state.in_progress = true;
}
}
pub fn end(&mut self, hwnd: isize) {
if let Some(animation_state) = self.animations.get_mut(&hwnd) {
animation_state.in_progress = false;
if animation_state.pending_cancel_count == 0 {
self.animations.remove(&hwnd);
ANIMATIONS_IN_PROGRESS.store(self.animations.len(), Ordering::Release);
}
}
}
}

View File

@@ -5,18 +5,16 @@ use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::FOCUS_STATE;
use crate::border_manager::RENDER_TARGETS;
use crate::border_manager::STYLE;
use crate::border_manager::Z_ORDER;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::windows_api;
use crate::WindowsApi;
use crate::WINDOWS_11;
use color_eyre::eyre::anyhow;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::LazyLock;
use std::sync::OnceLock;
use windows::Foundation::Numerics::Matrix3x2;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::FALSE;
@@ -32,8 +30,6 @@ use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
@@ -47,26 +43,22 @@ use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;
use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;
use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
use windows::Win32::Graphics::Gdi::BeginPaint;
use windows::Win32::Graphics::Gdi::CreateRectRgn;
use windows::Win32::Graphics::Gdi::EndPaint;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::ValidateRect;
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WM_SIZE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::PCWSTR;
@@ -97,36 +89,14 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
true.into()
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Border {
pub hwnd: isize,
pub render_target: OnceLock<ID2D1HwndRenderTarget>,
pub tracking_hwnd: isize,
pub window_rect: Rect,
pub window_kind: WindowKind,
pub style: BorderStyle,
pub width: i32,
pub offset: i32,
pub brush_properties: D2D1_BRUSH_PROPERTIES,
pub rounded_rect: D2D1_ROUNDED_RECT,
pub brushes: HashMap<WindowKind, ID2D1SolidColorBrush>,
}
impl From<isize> for Border {
fn from(value: isize) -> Self {
Self {
hwnd: value,
render_target: OnceLock::new(),
tracking_hwnd: 0,
window_rect: Rect::default(),
window_kind: WindowKind::Unfocused,
style: STYLE.load(),
width: BORDER_WIDTH.load(Ordering::Relaxed),
offset: BORDER_OFFSET.load(Ordering::Relaxed),
brush_properties: D2D1_BRUSH_PROPERTIES::default(),
rounded_rect: D2D1_ROUNDED_RECT::default(),
brushes: HashMap::new(),
}
Self { hwnd: value }
}
}
@@ -135,7 +105,7 @@ impl Border {
HWND(windows_api::as_ptr!(self.hwnd))
}
pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result<Self> {
pub fn create(id: &str) -> color_eyre::Result<Self> {
let name: Vec<u16> = format!("komoborder-{id}\0").encode_utf16().collect();
let class_name = PCWSTR(name.as_ptr());
@@ -151,30 +121,12 @@ impl Border {
let _ = WindowsApi::register_class_w(&window_class);
let (border_sender, border_receiver) = mpsc::channel();
let (hwnd_sender, hwnd_receiver) = mpsc::channel();
let instance = h_module.0 as isize;
std::thread::spawn(move || -> color_eyre::Result<()> {
let mut border = Self {
hwnd: 0,
render_target: OnceLock::new(),
tracking_hwnd,
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
window_kind: WindowKind::Unfocused,
style: STYLE.load(),
width: BORDER_WIDTH.load(Ordering::Relaxed),
offset: BORDER_OFFSET.load(Ordering::Relaxed),
brush_properties: Default::default(),
rounded_rect: Default::default(),
brushes: HashMap::new(),
};
let border_pointer = std::ptr::addr_of!(border);
let hwnd =
WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance, border_pointer)?;
border.hwnd = hwnd;
border_sender.send(border_pointer as isize)?;
let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance)?;
hwnd_sender.send(hwnd)?;
let mut msg: MSG = MSG::default();
@@ -193,8 +145,8 @@ impl Border {
Ok(())
});
let border_ref = border_receiver.recv()?;
let border = unsafe { &mut *(border_ref as *mut Border) };
let hwnd = hwnd_receiver.recv()?;
let border = Self { hwnd };
// I have literally no idea, apparently this is to get rid of the black pixels
// around the edges of rounded corners? @lukeyou05 borrowed this from PowerToys
@@ -215,7 +167,7 @@ impl Border {
}
let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES {
hwnd: HWND(windows_api::as_ptr!(border.hwnd)),
hwnd: HWND(windows_api::as_ptr!(hwnd)),
pixelSize: Default::default(),
presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,
};
@@ -236,47 +188,10 @@ impl Border {
.CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties)
} {
Ok(render_target) => unsafe {
border.brush_properties = *BRUSH_PROPERTIES.deref();
for window_kind in [
WindowKind::Single,
WindowKind::Stack,
WindowKind::Monocle,
WindowKind::Unfocused,
WindowKind::Floating,
] {
let color = window_kind_colour(window_kind);
let color = D2D1_COLOR_F {
r: ((color & 0xFF) as f32) / 255.0,
g: (((color >> 8) & 0xFF) as f32) / 255.0,
b: (((color >> 16) & 0xFF) as f32) / 255.0,
a: 1.0,
};
if let Ok(brush) =
render_target.CreateSolidColorBrush(&color, Some(&border.brush_properties))
{
border.brushes.insert(window_kind, brush);
}
}
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
if border.render_target.set(render_target.clone()).is_err() {
return Err(anyhow!("could not store border render target"));
}
border.rounded_rect = {
let radius = 8.0 + border.width as f32 / 2.0;
D2D1_ROUNDED_RECT {
rect: Default::default(),
radiusX: radius,
radiusY: radius,
}
};
let mut render_targets = RENDER_TARGETS.lock();
render_targets.insert(border.hwnd, render_target);
Ok(border.clone())
render_targets.insert(hwnd, render_target);
Ok(border)
},
Err(error) => Err(error.into()),
}
@@ -288,17 +203,27 @@ impl Border {
WindowsApi::close_window(self.hwnd)
}
pub fn set_position(&self, rect: &Rect, reference_hwnd: isize) -> color_eyre::Result<()> {
pub fn update(&self, rect: &Rect, should_invalidate: bool) -> color_eyre::Result<()> {
// Make adjustments to the border
let mut rect = *rect;
rect.add_margin(self.width);
rect.add_padding(-self.offset);
rect.add_margin(BORDER_WIDTH.load(Ordering::Relaxed));
rect.add_padding(-BORDER_OFFSET.load(Ordering::Relaxed));
WindowsApi::set_border_pos(self.hwnd, &rect, reference_hwnd)?;
// Update the position of the border if required
// This effectively handles WM_MOVE
// Also if I remove this no borders render at all lol
if !WindowsApi::window_rect(self.hwnd)?.eq(&rect) {
WindowsApi::set_border_pos(self.hwnd, &rect, Z_ORDER.load().into())?;
}
// Invalidate the rect to trigger the callback to update colours etc.
if should_invalidate {
self.invalidate();
}
Ok(())
}
// this triggers WM_PAINT in the callback below
pub fn invalidate(&self) {
let _ = unsafe { InvalidateRect(self.hwnd(), None, false) };
}
@@ -311,156 +236,50 @@ impl Border {
) -> LRESULT {
unsafe {
match message {
WM_CREATE => {
let mut border_pointer: *mut Border =
GetWindowLongPtrW(window, GWLP_USERDATA) as _;
if border_pointer.is_null() {
let create_struct: *mut CREATESTRUCTW = lparam.0 as *mut _;
border_pointer = (*create_struct).lpCreateParams as *mut _;
SetWindowLongPtrW(window, GWLP_USERDATA, border_pointer as _);
}
LRESULT(0)
}
EVENT_OBJECT_DESTROY => {
let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;
if border_pointer.is_null() {
return LRESULT(0);
}
// we don't actually want to destroy the window here, just hide it for quicker
// visual feedback to the user; the actual destruction will be handled by the
// core border manager loop
WindowsApi::hide_window(window.0 as isize);
LRESULT(0)
}
EVENT_OBJECT_LOCATIONCHANGE => {
let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;
if border_pointer.is_null() {
return LRESULT(0);
}
let reference_hwnd = lparam.0;
let old_rect = (*border_pointer).window_rect;
let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default();
(*border_pointer).window_rect = rect;
if let Err(error) = (*border_pointer).set_position(&rect, reference_hwnd) {
tracing::error!("failed to update border position {error}");
}
if !rect.is_same_size_as(&old_rect) {
if let Some(render_target) = (*border_pointer).render_target.get() {
let border_width = (*border_pointer).width;
let border_offset = (*border_pointer).offset;
(*border_pointer).rounded_rect.rect = D2D_RECT_F {
left: (border_width / 2 - border_offset) as f32,
top: (border_width / 2 - border_offset) as f32,
right: (rect.right - border_width / 2 + border_offset) as f32,
bottom: (rect.bottom - border_width / 2 + border_offset) as f32,
};
let _ = render_target.Resize(&D2D_SIZE_U {
width: rect.right as u32,
height: rect.bottom as u32,
});
let window_kind = (*border_pointer).window_kind;
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
render_target.BeginDraw();
render_target.Clear(None);
// Calculate border radius based on style
let style = match (*border_pointer).style {
BorderStyle::System => {
if *WINDOWS_11 {
BorderStyle::Rounded
} else {
BorderStyle::Square
}
}
BorderStyle::Rounded => BorderStyle::Rounded,
BorderStyle::Square => BorderStyle::Square,
};
match style {
BorderStyle::Rounded => {
render_target.DrawRoundedRectangle(
&(*border_pointer).rounded_rect,
brush,
border_width as f32,
None,
);
}
BorderStyle::Square => {
render_target.DrawRectangle(
&(*border_pointer).rounded_rect.rect,
brush,
border_width as f32,
None,
);
}
_ => {}
}
let _ = render_target.EndDraw(None, None);
}
}
}
LRESULT(0)
}
WM_PAINT => {
WM_SIZE | WM_PAINT => {
if let Ok(rect) = WindowsApi::window_rect(window.0 as isize) {
let border_pointer: *mut Border =
GetWindowLongPtrW(window, GWLP_USERDATA) as _;
let render_targets = RENDER_TARGETS.lock();
if let Some(render_target) = render_targets.get(&(window.0 as isize)) {
let pixel_size = D2D_SIZE_U {
width: rect.right as u32,
height: rect.bottom as u32,
};
if border_pointer.is_null() {
return LRESULT(0);
}
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
if let Some(render_target) = (*border_pointer).render_target.get() {
(*border_pointer).width = BORDER_WIDTH.load(Ordering::Relaxed);
(*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed);
let border_width = (*border_pointer).width;
let border_offset = (*border_pointer).offset;
(*border_pointer).rounded_rect.rect = D2D_RECT_F {
let rect = D2D_RECT_F {
left: (border_width / 2 - border_offset) as f32,
top: (border_width / 2 - border_offset) as f32,
right: (rect.right - border_width / 2 + border_offset) as f32,
bottom: (rect.bottom - border_width / 2 + border_offset) as f32,
};
let _ = render_target.Resize(&D2D_SIZE_U {
width: rect.right as u32,
height: rect.bottom as u32,
});
let _ = render_target.Resize(&pixel_size);
// Get window kind and color
(*border_pointer).window_kind = FOCUS_STATE
let window_kind = FOCUS_STATE
.lock()
.get(&(window.0 as isize))
.copied()
.unwrap_or(WindowKind::Unfocused);
let window_kind = (*border_pointer).window_kind;
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
let color = window_kind_colour(window_kind);
let color = D2D1_COLOR_F {
r: ((color & 0xFF) as f32) / 255.0,
g: (((color >> 8) & 0xFF) as f32) / 255.0,
b: (((color >> 16) & 0xFF) as f32) / 255.0,
a: 1.0,
};
if let Ok(brush) = render_target
.CreateSolidColorBrush(&color, Some(BRUSH_PROPERTIES.deref()))
{
render_target.BeginDraw();
render_target.Clear(None);
(*border_pointer).style = STYLE.load();
// Calculate border radius based on style
let style = match (*border_pointer).style {
let style = match STYLE.load() {
BorderStyle::System => {
if *WINDOWS_11 {
BorderStyle::Rounded
@@ -474,17 +293,31 @@ impl Border {
match style {
BorderStyle::Rounded => {
let radius = 8.0 + border_width as f32 / 2.0;
let rounded_rect = D2D1_ROUNDED_RECT {
rect,
radiusX: radius,
radiusY: radius,
};
render_target.DrawRoundedRectangle(
&(*border_pointer).rounded_rect,
brush,
&rounded_rect,
&brush,
border_width as f32,
None,
);
}
BorderStyle::Square => {
let rect = D2D_RECT_F {
left: rect.left,
top: rect.top,
right: rect.right,
bottom: rect.bottom,
};
render_target.DrawRectangle(
&(*border_pointer).rounded_rect.rect,
brush,
&rect,
&brush,
border_width as f32,
None,
);
@@ -493,14 +326,17 @@ impl Border {
}
let _ = render_target.EndDraw(None, None);
// If we don't do this we'll get spammed with WM_PAINT according to Raymond Chen
// https://stackoverflow.com/questions/41783234/why-does-my-call-to-d2d1rendertargetdrawtext-result-in-a-wm-paint-being-se#comment70756781_41783234
let _ = BeginPaint(window, &mut PAINTSTRUCT::default());
let _ = EndPaint(window, &PAINTSTRUCT::default());
}
}
}
let _ = ValidateRect(window, None);
LRESULT(0)
}
WM_DESTROY => {
SetWindowLongPtrW(window, GWLP_USERDATA, 0);
PostQuitMessage(0);
LRESULT(0)
}

View File

@@ -11,7 +11,7 @@ use crate::Rgb;
use crate::WindowManager;
use crate::WindowsApi;
use border::border_hwnds;
pub use border::Border;
use border::Border;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
@@ -30,13 +30,18 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
use windows::Win32::System::Threading::GetCurrentThread;
use windows::Win32::System::Threading::SetThreadPriority;
use windows::Win32::System::Threading::THREAD_PRIORITY_TIME_CRITICAL;
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true);
pub static BORDER_TEMPORARILY_DISABLED: AtomicBool = AtomicBool::new(false);
lazy_static! {
pub static ref Z_ORDER: AtomicCell<ZOrder> = AtomicCell::new(ZOrder::Bottom);
pub static ref STYLE: AtomicCell<BorderStyle> = AtomicCell::new(BorderStyle::System);
pub static ref IMPLEMENTATION: AtomicCell<BorderImplementation> =
AtomicCell::new(BorderImplementation::Komorebi);
@@ -54,7 +59,6 @@ lazy_static! {
lazy_static! {
static ref BORDERS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, Border>> = Mutex::new(HashMap::new());
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
static ref RENDER_TARGETS: Mutex<HashMap<isize, ID2D1HwndRenderTarget>> =
Mutex::new(HashMap::new());
@@ -76,10 +80,6 @@ fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn window_border(hwnd: isize) -> Option<Border> {
WINDOWS_BORDERS.lock().get(&hwnd).cloned()
}
pub fn send_notification(hwnd: Option<isize>) {
if event_tx().try_send(Notification(hwnd)).is_err() {
tracing::warn!("channel is full; dropping notification")
@@ -122,22 +122,31 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
fn window_kind_colour(focus_kind: WindowKind) -> u32 {
match focus_kind {
WindowKind::Unfocused => UNFOCUSED.load(Ordering::Relaxed),
WindowKind::Single => FOCUSED.load(Ordering::Relaxed),
WindowKind::Stack => STACK.load(Ordering::Relaxed),
WindowKind::Monocle => MONOCLE.load(Ordering::Relaxed),
WindowKind::Floating => FLOATING.load(Ordering::Relaxed),
WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
WindowKind::Stack => STACK.load(Ordering::SeqCst),
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
WindowKind::Floating => FLOATING.load(Ordering::SeqCst),
}
}
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");
std::thread::spawn(move || {
unsafe {
if let Err(error) = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)
{
tracing::error!("{error}");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
}
});
@@ -146,6 +155,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("listening");
BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
let receiver = event_rx();
event_tx().send(Notification(None))?;
@@ -162,6 +172,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let focused_workspace_idx =
state.monitors.elements()[focused_monitor_idx].focused_workspace_idx();
let monitors = state.monitors.clone();
let weak_pending_move_op = Arc::downgrade(&state.pending_move_op);
let pending_move_op = *state.pending_move_op;
let floating_window_hwnds = state.monitors.elements()[focused_monitor_idx].workspaces()
[focused_workspace_idx]
@@ -260,10 +271,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let mut borders = BORDER_STATE.lock();
let mut borders_monitors = BORDERS_MONITORS.lock();
let mut windows_borders = WINDOWS_BORDERS.lock();
// If borders are disabled
if !BORDER_ENABLED.load_consume()
// Or if they are temporarily disabled
|| BORDER_TEMPORARILY_DISABLED.load(Ordering::SeqCst)
// Or if the wm is paused
|| is_paused
// Or if we are handling an alt-tab across workspaces
@@ -275,7 +287,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
borders.clear();
windows_borders.clear();
previous_is_paused = is_paused;
continue 'receiver;
@@ -308,10 +319,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let border = match borders.entry(monocle.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(
monocle.id(),
monocle.focused_window().copied().unwrap_or_default().hwnd,
) {
if let Ok(border) = Border::create(monocle.id()) {
entry.insert(border)
} else {
continue 'monitors;
@@ -320,10 +328,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
};
borders_monitors.insert(monocle.id().clone(), monitor_idx);
windows_borders.insert(
monocle.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
{
let mut focus_state = FOCUS_STATE.lock();
@@ -337,14 +341,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
);
}
let reference_hwnd =
monocle.focused_window().copied().unwrap_or_default().hwnd;
let rect = WindowsApi::window_rect(
monocle.focused_window().copied().unwrap_or_default().hwnd,
)?;
let rect = WindowsApi::window_rect(reference_hwnd)?;
border.set_position(&rect, reference_hwnd)?;
border.invalidate();
border.update(&rect, true)?;
let border_hwnd = border.hwnd;
let mut to_remove = vec![];
@@ -365,11 +366,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
continue 'monitors;
}
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();
let foreground_monitor_id =
WindowsApi::monitor_from_window(foreground_hwnd);
let is_maximized = foreground_monitor_id == m.id()
&& WindowsApi::is_zoomed(foreground_hwnd);
let is_maximized = WindowsApi::is_zoomed(
WindowsApi::foreground_window().unwrap_or_default(),
);
if is_maximized {
let mut to_remove = vec![];
@@ -415,14 +414,65 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
for (idx, c) in ws.containers().iter().enumerate() {
let hwnd = c.focused_window().copied().unwrap_or_default().hwnd;
let notification_hwnd = notification.0.unwrap_or_default();
// Update border when moving or resizing with mouse
if pending_move_op.is_some()
&& idx == ws.focused_container_idx()
&& hwnd == notification_hwnd
{
let restore_z_order = Z_ORDER.load();
Z_ORDER.store(ZOrder::TopMost);
let mut rect = WindowsApi::window_rect(
c.focused_window().copied().unwrap_or_default().hwnd,
)?;
// We create a new variable to track the actual pending move op so
// that the other variable `pending_move_op` still holds the
// pending move info so that when the move ends we know on the next
// notification that the previous pending move and pending move are
// different (because a move just finished) and still handle the
// notification. If otherwise we updated the pending_move_op here
// directly then the next pending move after finish would be the
// same because we had already updated it here.
let mut sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
while sync_pending_move_op.is_some() {
sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(c.id()) {
entry.insert(border)
} else {
continue 'monitors;
}
}
};
let new_rect = WindowsApi::window_rect(
c.focused_window().copied().unwrap_or_default().hwnd,
)?;
if rect != new_rect {
rect = new_rect;
border.update(&rect, false)?;
}
}
Z_ORDER.store(restore_z_order);
continue 'monitors;
}
// Get the border entry for this container from the map or create one
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(
c.id(),
c.focused_window().copied().unwrap_or_default().hwnd,
) {
if let Ok(border) = Border::create(c.id()) {
entry.insert(border)
} else {
continue 'monitors;
@@ -431,10 +481,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
};
borders_monitors.insert(c.id().clone(), monitor_idx);
windows_borders.insert(
c.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
#[allow(unused_assignments)]
let mut last_focus_state = None;
@@ -455,30 +501,65 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
}
let reference_hwnd =
c.focused_window().copied().unwrap_or_default().hwnd;
let rect = WindowsApi::window_rect(reference_hwnd)?;
let rect = WindowsApi::window_rect(
c.focused_window().copied().unwrap_or_default().hwnd,
)?;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => last_focus_state != new_focus_state,
};
border.set_position(&rect, reference_hwnd)?;
if should_invalidate {
border.invalidate();
}
border.update(&rect, should_invalidate)?;
}
{
for window in ws.floating_windows() {
let restore_z_order = Z_ORDER.load();
Z_ORDER.store(ZOrder::TopMost);
'windows: for window in ws.floating_windows() {
let hwnd = window.hwnd;
let notification_hwnd = notification.0.unwrap_or_default();
if pending_move_op.is_some() && hwnd == notification_hwnd {
let mut rect = WindowsApi::window_rect(hwnd)?;
// Check comment above for containers move
let mut sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
while sync_pending_move_op.is_some() {
sync_pending_move_op =
weak_pending_move_op.upgrade().and_then(|p| *p);
let border = match borders.entry(hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) =
Border::create(&hwnd.to_string())
{
entry.insert(border)
} else {
continue 'monitors;
}
}
};
let new_rect = WindowsApi::window_rect(hwnd)?;
if rect != new_rect {
rect = new_rect;
border.update(&rect, true)?;
}
}
Z_ORDER.store(restore_z_order);
continue 'monitors;
}
let border = match borders.entry(window.hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) =
Border::create(&window.hwnd.to_string(), window.hwnd)
if let Ok(border) = Border::create(&window.hwnd.to_string())
{
entry.insert(border)
} else {
@@ -488,25 +569,31 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
};
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
windows_borders.insert(window.hwnd, border.clone());
let mut is_focused = false;
let mut should_destroy = false;
if let Some(notification_hwnd) = notification.0 {
if notification_hwnd != window.hwnd {
should_destroy = true;
}
}
if WindowsApi::foreground_window().unwrap_or_default()
== window.hwnd
!= window.hwnd
{
is_focused = true;
should_destroy = true;
}
if should_destroy {
border.destroy()?;
borders.remove(&window.hwnd.to_string());
borders_monitors.remove(&window.hwnd.to_string());
continue 'windows;
}
#[allow(unused_assignments)]
let mut last_focus_state = None;
let new_focus_state = if is_focused {
WindowKind::Floating
} else {
WindowKind::Unfocused
};
// Update the focused state for all containers on this workspace
let new_focus_state = WindowKind::Floating;
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state =
@@ -520,14 +607,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
Some(last_focus_state) => last_focus_state != new_focus_state,
};
// this has to be sent to match the z-order when clicking on
// a floating window to focus it
border.set_position(&rect, window.hwnd)?;
if should_invalidate {
border.invalidate();
}
border.update(&rect, should_invalidate)?;
}
Z_ORDER.store(restore_z_order);
}
}
}

View File

@@ -39,18 +39,6 @@ impl From<Color32> for Colour {
}
}
impl From<Colour> for Color32 {
fn from(value: Colour) -> Self {
match value {
Colour::Rgb(rgb) => Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8),
Colour::Hex(hex) => {
let rgb = Rgb::from(hex);
Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8)
}
}
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct Hex(HexColor);

View File

@@ -44,7 +44,7 @@ impl<'a, T: Clone> ComIn<'a, T> {
}
}
impl<T> Deref for ComIn<'_, T> {
impl<'a, T> Deref for ComIn<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data

View File

@@ -14,7 +14,6 @@ use serde::Serialize;
use strum::Display;
use strum::EnumString;
use crate::animation::prefix::AnimationPrefix;
use crate::KomorebiTheme;
pub use animation::AnimationStyle;
pub use arrangement::Arrangement;
@@ -148,10 +147,10 @@ pub enum SocketMessage {
CompleteConfiguration,
AltFocusHack(bool),
Theme(KomorebiTheme),
Animation(bool, Option<AnimationPrefix>),
AnimationDuration(u64, Option<AnimationPrefix>),
Animation(bool),
AnimationDuration(u64),
AnimationFps(u64),
AnimationStyle(AnimationStyle, Option<AnimationPrefix>),
AnimationStyle(AnimationStyle),
#[serde(alias = "ActiveWindowBorder")]
Border(bool),
#[serde(alias = "ActiveWindowBorderColour")]
@@ -306,8 +305,6 @@ pub enum BorderImplementation {
ValueEnum,
JsonSchema,
PartialEq,
Eq,
Hash,
)]
pub enum WindowKind {
Single,

View File

@@ -37,12 +37,6 @@ impl From<Rect> for RECT {
}
}
impl Rect {
pub fn is_same_size_as(&self, rhs: &Self) -> bool {
self.right == rhs.right && self.bottom == rhs.bottom
}
}
impl Rect {
/// decrease the size of self by the padding amount.
pub fn add_padding<T>(&mut self, padding: T)

View File

@@ -1,6 +1,7 @@
#![warn(clippy::all)]
pub mod animation;
pub mod animation_manager;
pub mod border_manager;
pub mod com;
#[macro_use]
@@ -46,6 +47,8 @@ use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub use animation::*;
pub use animation_manager::*;
pub use colour::*;
pub use core::*;
pub use process_command::*;
@@ -213,6 +216,12 @@ lazy_static! {
)
};
static ref ANIMATION_STYLE: Arc<Mutex<AnimationStyle >> =
Arc::new(Mutex::new(AnimationStyle::Linear));
static ref ANIMATION_MANAGER: Arc<Mutex<AnimationManager>> =
Arc::new(Mutex::new(AnimationManager::new()));
// Use app-specific titlebar removal options where possible
// eg. Windows Terminal, IntelliJ IDEA, Firefox
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
@@ -229,6 +238,8 @@ pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_ENABLED: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_DURATION: AtomicU64 = AtomicU64::new(250);
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
@@ -296,8 +307,6 @@ pub fn notify_subscribers(notification: Notification, state_has_been_modified: b
| NotificationEvent::Socket(SocketMessage::AddSubscriberSocketWithOptions(_, _))
| NotificationEvent::Socket(SocketMessage::Theme(_))
| NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(_))
| NotificationEvent::WindowManager(WindowManagerEvent::TitleUpdate(_, _))
| NotificationEvent::WindowManager(WindowManagerEvent::Show(_, _))
);
let notification = &serde_json::to_string(&notification)?;

View File

@@ -17,9 +17,6 @@ use std::time::Duration;
use clap::Parser;
use color_eyre::Result;
use crossbeam_utils::Backoff;
use komorebi::animation::AnimationEngine;
use komorebi::animation::ANIMATION_ENABLED_GLOBAL;
use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
@@ -290,10 +287,7 @@ fn main() -> Result<()> {
tracing::error!("received ctrl-c, restoring all hidden windows and terminating process");
ANIMATION_ENABLED_PER_ANIMATION.lock().clear();
ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst);
wm.lock().restore_all_windows()?;
AnimationEngine::wait_for_all_animations();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;

View File

@@ -139,86 +139,6 @@ impl Monitor {
Ok(())
}
/// Adds a container to this `Monitor` using the move direction to calculate if the container
/// should be added in front of all containers, in the back or in place of the focused
/// container, moving the rest along. The move direction should be from the origin monitor
/// towards the target monitor or from the origin workspace towards the target workspace.
pub fn add_container_with_direction(
&mut self,
container: Container,
workspace_idx: Option<usize>,
direction: OperationDirection,
) -> Result<()> {
let workspace = if let Some(idx) = workspace_idx {
self.workspaces_mut()
.get_mut(idx)
.ok_or_else(|| anyhow!("there is no workspace at index {}", idx))?
} else {
self.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?
};
match direction {
OperationDirection::Left => {
// insert the container into the workspace on the monitor at the back (or rightmost position)
// if we are moving across a boundary to the left (back = right side of the target)
match workspace.layout() {
Layout::Default(layout) => match layout {
DefaultLayout::RightMainVerticalStack => {
workspace.add_container_to_front(container);
}
DefaultLayout::UltrawideVerticalStack => {
if workspace.containers().len() == 1 {
workspace.insert_container_at_idx(0, container);
} else {
workspace.add_container_to_back(container);
}
}
_ => {
workspace.add_container_to_back(container);
}
},
Layout::Custom(_) => {
workspace.add_container_to_back(container);
}
}
}
OperationDirection::Right => {
// insert the container into the workspace on the monitor at the front (or leftmost position)
// if we are moving across a boundary to the right (front = left side of the target)
match workspace.layout() {
Layout::Default(layout) => {
let target_index = layout.leftmost_index(workspace.containers().len());
match layout {
DefaultLayout::RightMainVerticalStack
| DefaultLayout::UltrawideVerticalStack => {
if workspace.containers().len() == 1 {
workspace.add_container_to_back(container);
} else {
workspace.insert_container_at_idx(target_index, container);
}
}
_ => {
workspace.insert_container_at_idx(target_index, container);
}
}
}
Layout::Custom(_) => {
workspace.add_container_to_front(container);
}
}
}
OperationDirection::Up | OperationDirection::Down => {
// insert the container into the workspace on the monitor at the position
// where the currently focused container on that workspace is
workspace.insert_container_at_idx(workspace.focused_container_idx(), container);
}
};
Ok(())
}
pub fn remove_workspace_by_idx(&mut self, idx: usize) -> Option<Workspace> {
if idx < self.workspaces().len() {
return self.workspaces_mut().remove(idx);
@@ -295,14 +215,54 @@ impl Monitor {
Some(workspace) => workspace,
};
if let Some(direction) = direction {
self.add_container_with_direction(
container,
Some(target_workspace_idx),
direction,
)?;
} else {
target_workspace.add_container_to_back(container);
match direction {
Some(OperationDirection::Left) => match target_workspace.layout() {
Layout::Default(layout) => match layout {
DefaultLayout::RightMainVerticalStack => {
target_workspace.add_container_to_front(container);
}
DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace.insert_container_at_idx(0, container);
} else {
target_workspace.add_container_to_back(container);
}
}
_ => {
target_workspace.add_container_to_back(container);
}
},
Layout::Custom(_) => {
target_workspace.add_container_to_back(container);
}
},
Some(OperationDirection::Right) => match target_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(target_workspace.containers().len());
match layout {
DefaultLayout::RightMainVerticalStack
| DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace.add_container_to_back(container);
} else {
target_workspace
.insert_container_at_idx(target_index, container);
}
}
_ => {
target_workspace.insert_container_at_idx(target_index, container);
}
}
}
Layout::Custom(_) => {
target_workspace.add_container_to_front(container);
}
},
_ => {
target_workspace.add_container_to_back(container);
}
}
}

View File

@@ -132,7 +132,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
);
ACTIVE.store(true, Ordering::SeqCst);
border_manager::send_notification(None);
}
continue 'receiver;

View File

@@ -22,10 +22,6 @@ use schemars::gen::SchemaSettings;
use schemars::schema_for;
use uds_windows::UnixStream;
use crate::animation::AnimationEngine;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
@@ -44,10 +40,6 @@ 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;
@@ -71,6 +63,10 @@ use crate::GlobalState;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::ANIMATION_FPS;
use crate::ANIMATION_STYLE;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
@@ -536,8 +532,7 @@ impl WindowManager {
self.move_container_to_workspace(workspace_idx, true, None)?;
}
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(monitor_idx, None, true, direction)?;
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SwapWorkspacesToMonitorNumber(monitor_idx) => {
self.swap_focused_monitor(monitor_idx)?;
@@ -549,8 +544,7 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(monitor_idx, None, true, direction)?;
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false, None)?;
@@ -572,8 +566,7 @@ impl WindowManager {
self.move_container_to_workspace(workspace_idx, false, None)?;
}
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(monitor_idx, None, false, direction)?;
self.move_container_to_monitor(monitor_idx, None, false)?;
}
SocketMessage::CycleSendContainerToMonitor(direction) => {
let monitor_idx = direction.next_idx(
@@ -582,37 +575,22 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(monitor_idx, None, false, direction)?;
self.move_container_to_monitor(monitor_idx, None, false)?;
}
SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(
monitor_idx,
Option::from(workspace_idx),
false,
direction,
)?;
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), false)?;
}
SocketMessage::MoveContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(
monitor_idx,
Option::from(workspace_idx),
true,
direction,
)?;
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), true)?;
}
SocketMessage::SendContainerToNamedWorkspace(ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(
monitor_idx,
Option::from(workspace_idx),
false,
direction,
)?;
}
}
@@ -620,13 +598,7 @@ impl WindowManager {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
let direction = self.direction_from_monitor_idx(monitor_idx);
self.move_container_to_monitor(
monitor_idx,
Option::from(workspace_idx),
true,
direction,
)?;
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), true)?;
}
}
@@ -670,10 +642,12 @@ impl WindowManager {
self.update_focused_workspace(self.mouse_follows_focus, true)?;
}
SocketMessage::Retile => {
border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
border_manager::destroy_all_borders()?;
self.retile_all(false)?
}
SocketMessage::RetileWithResizeDimensions => {
border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
border_manager::destroy_all_borders()?;
self.retile_all(true)?
}
@@ -915,11 +889,7 @@ impl WindowManager {
tracing::info!(
"received stop command, restoring all hidden windows and terminating process"
);
ANIMATION_ENABLED_PER_ANIMATION.lock().clear();
ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst);
self.restore_all_windows()?;
AnimationEngine::wait_for_all_animations();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
@@ -1516,16 +1486,6 @@ impl WindowManager {
}
SocketMessage::Border(enable) => {
border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst);
if !enable {
match IMPLEMENTATION.load() {
BorderImplementation::Komorebi => {
border_manager::destroy_all_borders()?;
}
BorderImplementation::Windows => {
self.remove_all_accents()?;
}
}
}
}
SocketMessage::BorderImplementation(implementation) => {
if !*WINDOWS_11 && matches!(implementation, BorderImplementation::Windows) {
@@ -1572,41 +1532,18 @@ impl WindowManager {
SocketMessage::BorderOffset(offset) => {
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
}
SocketMessage::Animation(enable, prefix) => match prefix {
Some(prefix) => {
ANIMATION_ENABLED_PER_ANIMATION
.lock()
.insert(prefix, enable);
}
None => {
ANIMATION_ENABLED_GLOBAL.store(enable, Ordering::SeqCst);
ANIMATION_ENABLED_PER_ANIMATION.lock().clear();
}
},
SocketMessage::AnimationDuration(duration, prefix) => match prefix {
Some(prefix) => {
ANIMATION_DURATION_PER_ANIMATION
.lock()
.insert(prefix, duration);
}
None => {
ANIMATION_DURATION_GLOBAL.store(duration, Ordering::SeqCst);
ANIMATION_DURATION_PER_ANIMATION.lock().clear();
}
},
SocketMessage::Animation(enable) => {
ANIMATION_ENABLED.store(enable, Ordering::SeqCst);
}
SocketMessage::AnimationDuration(duration) => {
ANIMATION_DURATION.store(duration, Ordering::SeqCst);
}
SocketMessage::AnimationFps(fps) => {
ANIMATION_FPS.store(fps, Ordering::SeqCst);
}
SocketMessage::AnimationStyle(style, prefix) => match prefix {
Some(prefix) => {
ANIMATION_STYLE_PER_ANIMATION.lock().insert(prefix, style);
}
None => {
let mut animation_style = ANIMATION_STYLE_GLOBAL.lock();
*animation_style = style;
ANIMATION_STYLE_PER_ANIMATION.lock().clear();
}
},
SocketMessage::AnimationStyle(style) => {
*ANIMATION_STYLE.lock() = style;
}
SocketMessage::ToggleTransparency => {
let current = transparency_manager::TRANSPARENCY_ENABLED.load(Ordering::SeqCst);
transparency_manager::TRANSPARENCY_ENABLED.store(!current, Ordering::SeqCst);

View File

@@ -485,21 +485,8 @@ impl WindowManager {
// place across a monitor boundary to an empty workspace
.unwrap_or(&Rect::default());
// This will be true if we have moved to another monitor
let mut moved_across_monitors = false;
for (i, monitors) in self.monitors().iter().enumerate() {
for workspace in monitors.workspaces() {
if workspace.contains_window(window.hwnd) && i != target_monitor_idx {
moved_across_monitors = true;
break;
}
}
if moved_across_monitors {
break;
}
}
// This will be true if we have moved to an empty workspace on another monitor
let mut moved_across_monitors = old_position == Rect::default();
if let Some((origin_monitor_idx, origin_workspace_idx, _)) = pending {
// If we didn't move to another monitor with an empty workspace, it is
// still possible that we moved to another monitor with a populated workspace
@@ -530,9 +517,7 @@ impl WindowManager {
}
let workspace = self.focused_workspace_mut()?;
if (*workspace.tile() && workspace.contains_managed_window(window.hwnd))
|| moved_across_monitors
{
if workspace.contains_managed_window(window.hwnd) || moved_across_monitors {
let resize = Rect {
left: new_position.left - old_position.left,
top: new_position.top - old_position.top,
@@ -585,19 +570,11 @@ impl WindowManager {
// so that we don't have ghost tiles until we force an interaction on
// the origin monitor's focused workspace
self.focus_monitor(origin_monitor_idx)?;
let origin_monitor = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?;
origin_monitor.focus_workspace(origin_workspace_idx)?;
self.focus_workspace(origin_workspace_idx)?;
self.update_focused_workspace(false, false)?;
self.focus_monitor(target_monitor_idx)?;
let target_monitor = self
.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?;
target_monitor.focus_workspace(target_workspace_idx)?;
self.focus_workspace(target_workspace_idx)?;
self.update_focused_workspace(false, false)?;
// Make sure to give focus to the moved window again
@@ -709,20 +686,6 @@ impl WindowManager {
known_hwnds.push(window.hwnd);
}
}
for window in workspace.floating_windows() {
known_hwnds.push(window.hwnd);
}
if let Some(window) = workspace.maximized_window() {
known_hwnds.push(window.hwnd);
}
if let Some(container) = workspace.monocle_container() {
for window in container.windows() {
known_hwnds.push(window.hwnd);
}
}
}
}

View File

@@ -1,16 +1,8 @@
use crate::animation::PerAnimationPrefixConfig;
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::animation::DEFAULT_ANIMATION_FPS;
use crate::border_manager;
use crate::border_manager::ZOrder;
use crate::border_manager::IMPLEMENTATION;
use crate::border_manager::STYLE;
use crate::border_manager::Z_ORDER;
use crate::colour::Colour;
use crate::core::BorderImplementation;
use crate::core::StackbarLabel;
@@ -36,6 +28,10 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::workspace::Workspace;
use crate::CrossBoundaryBehaviour;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::ANIMATION_FPS;
use crate::ANIMATION_STYLE;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
@@ -267,7 +263,7 @@ pub struct StaticConfig {
/// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)
#[serde(skip_serializing_if = "Option::is_none")]
pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,
/// END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead
/// END OF LIFE FEATURE: Determine focus follows mouse implementation (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
/// Enable or disable mouse follows focus (default: true)
@@ -296,7 +292,7 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_style")]
pub border_style: Option<BorderStyle>,
/// DEPRECATED from v0.1.31: no longer required
/// Active window border z-order (default: System)
#[serde(skip_serializing_if = "Option::is_none")]
pub border_z_order: Option<ZOrder>,
/// Active window border implementation (default: Komorebi)
@@ -378,11 +374,11 @@ pub struct StaticConfig {
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct AnimationsConfig {
/// Enable or disable animations (default: false)
enabled: PerAnimationPrefixConfig<bool>,
enabled: bool,
/// Set the animation duration in ms (default: 250)
duration: Option<PerAnimationPrefixConfig<u64>>,
duration: Option<u64>,
/// Set the animation style (default: Linear)
style: Option<PerAnimationPrefixConfig<AnimationStyle>>,
style: Option<AnimationStyle>,
/// Set the animation FPS (default: 60)
fps: Option<u64>,
}
@@ -496,7 +492,7 @@ impl StaticConfig {
}
pub fn deprecated(raw: &str) {
let deprecated_options = ["invisible_borders", "border_z_order"];
let deprecated_options = ["invisible_borders"];
let deprecated_variants = vec![
("Hide", "window_hiding_behaviour", "Cloak"),
("Minimize", "window_hiding_behaviour", "Cloak"),
@@ -599,7 +595,7 @@ impl From<&WindowManager> for StaticConfig {
),
transparency_ignore_rules: None,
border_style: Option::from(STYLE.load()),
border_z_order: None,
border_z_order: Option::from(Z_ORDER.load()),
border_implementation: Option::from(IMPLEMENTATION.load()),
default_workspace_padding: Option::from(
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
@@ -658,43 +654,11 @@ impl StaticConfig {
}
if let Some(animations) = &self.animation {
match &animations.enabled {
PerAnimationPrefixConfig::Prefix(enabled) => {
ANIMATION_ENABLED_PER_ANIMATION.lock().clone_from(enabled);
}
PerAnimationPrefixConfig::Global(enabled) => {
ANIMATION_ENABLED_GLOBAL.store(*enabled, Ordering::SeqCst);
ANIMATION_ENABLED_PER_ANIMATION.lock().clear();
}
}
match &animations.style {
Some(PerAnimationPrefixConfig::Prefix(style)) => {
ANIMATION_STYLE_PER_ANIMATION.lock().clone_from(style);
}
Some(PerAnimationPrefixConfig::Global(style)) => {
let mut animation_style = ANIMATION_STYLE_GLOBAL.lock();
*animation_style = *style;
ANIMATION_STYLE_PER_ANIMATION.lock().clear();
}
None => {}
}
match &animations.duration {
Some(PerAnimationPrefixConfig::Prefix(duration)) => {
ANIMATION_DURATION_PER_ANIMATION.lock().clone_from(duration);
}
Some(PerAnimationPrefixConfig::Global(duration)) => {
ANIMATION_DURATION_GLOBAL.store(*duration, Ordering::SeqCst);
ANIMATION_DURATION_PER_ANIMATION.lock().clear();
}
None => {}
}
ANIMATION_FPS.store(
animations.fps.unwrap_or(DEFAULT_ANIMATION_FPS),
Ordering::SeqCst,
);
ANIMATION_ENABLED.store(animations.enabled, Ordering::SeqCst);
ANIMATION_DURATION.store(animations.duration.unwrap_or(250), Ordering::SeqCst);
ANIMATION_FPS.store(animations.fps.unwrap_or(60), Ordering::SeqCst);
let mut animation_style = ANIMATION_STYLE.lock();
*animation_style = animations.style.unwrap_or(AnimationStyle::Linear);
}
if let Some(container) = self.default_container_padding {

View File

@@ -1,20 +1,11 @@
use crate::animation::lerp::Lerp;
use crate::animation::prefix::new_animation_key;
use crate::animation::prefix::AnimationPrefix;
use crate::animation::AnimationEngine;
use crate::animation::RenderDispatcher;
use crate::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_MANAGER;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::border_manager;
use crate::com::SetCloak;
use crate::focus_manager;
use crate::stackbar_manager;
use crate::windows_api;
use crate::AnimationStyle;
use crate::ANIMATIONS_IN_PROGRESS;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use std::collections::HashMap;
@@ -24,7 +15,6 @@ use std::fmt::Formatter;
use std::fmt::Write as _;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use crate::core::config_generation::IdWithIdentifier;
@@ -45,6 +35,7 @@ use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::animation::Animation;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
@@ -67,11 +58,16 @@ pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema, PartialEq)]
pub struct Window {
pub hwnd: isize,
#[serde(skip)]
animation: Animation,
}
impl From<isize> for Window {
fn from(value: isize) -> Self {
Self { hwnd: value }
Self {
hwnd: value,
animation: Animation::new(value),
}
}
}
@@ -79,6 +75,7 @@ impl From<HWND> for Window {
fn from(value: HWND) -> Self {
Self {
hwnd: value.0 as isize,
animation: Animation::new(value.0 as isize),
}
}
}
@@ -158,144 +155,6 @@ impl Serialize for Window {
}
}
struct MovementRenderDispatcher {
hwnd: isize,
start_rect: Rect,
target_rect: Rect,
top: bool,
style: AnimationStyle,
}
impl MovementRenderDispatcher {
const PREFIX: AnimationPrefix = AnimationPrefix::Movement;
pub fn new(
hwnd: isize,
start_rect: Rect,
target_rect: Rect,
top: bool,
style: AnimationStyle,
) -> Self {
Self {
hwnd,
start_rect,
target_rect,
top,
style,
}
}
}
impl RenderDispatcher for MovementRenderDispatcher {
fn get_animation_key(&self) -> String {
new_animation_key(MovementRenderDispatcher::PREFIX, self.hwnd.to_string())
}
fn pre_render(&self) -> Result<()> {
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
stackbar_manager::send_notification();
Ok(())
}
fn render(&self, progress: f64) -> Result<()> {
let new_rect = self.start_rect.lerp(self.target_rect, progress, self.style);
// using MoveWindow because it runs faster than SetWindowPos
// so animation have more fps and feel smoother
WindowsApi::move_window(self.hwnd, &new_rect, false)?;
WindowsApi::invalidate_rect(self.hwnd, None, false);
Ok(())
}
fn post_render(&self) -> Result<()> {
WindowsApi::position_window(self.hwnd, &self.target_rect, self.top)?;
if ANIMATION_MANAGER
.lock()
.count_in_progress(MovementRenderDispatcher::PREFIX)
== 0
{
if WindowsApi::foreground_window().unwrap_or_default() == self.hwnd {
focus_manager::send_notification(self.hwnd)
}
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
stackbar_manager::send_notification();
transparency_manager::send_notification();
}
Ok(())
}
}
struct TransparencyRenderDispatcher {
hwnd: isize,
start_opacity: u8,
target_opacity: u8,
style: AnimationStyle,
is_opaque: bool,
}
impl TransparencyRenderDispatcher {
const PREFIX: AnimationPrefix = AnimationPrefix::Transparency;
pub fn new(
hwnd: isize,
is_opaque: bool,
start_opacity: u8,
target_opacity: u8,
style: AnimationStyle,
) -> Self {
Self {
hwnd,
start_opacity,
target_opacity,
style,
is_opaque,
}
}
}
impl RenderDispatcher for TransparencyRenderDispatcher {
fn get_animation_key(&self) -> String {
new_animation_key(TransparencyRenderDispatcher::PREFIX, self.hwnd.to_string())
}
fn pre_render(&self) -> Result<()> {
//transparent
if !self.is_opaque {
let window = Window::from(self.hwnd);
let mut ex_style = window.ex_style()?;
ex_style.insert(ExtendedWindowStyle::LAYERED);
window.update_ex_style(&ex_style)?;
}
Ok(())
}
fn render(&self, progress: f64) -> Result<()> {
WindowsApi::set_transparent(
self.hwnd,
self.start_opacity
.lerp(self.target_opacity, progress, self.style),
)
}
fn post_render(&self) -> Result<()> {
//opaque
if self.is_opaque {
let window = Window::from(self.hwnd);
let mut ex_style = window.ex_style()?;
ex_style.remove(ExtendedWindowStyle::LAYERED);
window.update_ex_style(&ex_style)?;
}
Ok(())
}
}
impl Window {
pub const fn hwnd(self) -> HWND {
HWND(windows_api::as_ptr!(self.hwnd))
@@ -313,59 +172,17 @@ impl Window {
let corrected_relative_y = (window_relative_y as f32 * y_ratio) as i32;
let window_x = current_area.left + corrected_relative_x;
let window_y = current_area.top + corrected_relative_y;
let left = x_diff + window_x;
let top = y_diff + window_y;
let corrected_width = (current_rect.right as f32 * x_ratio) as i32;
let corrected_height = (current_rect.bottom as f32 * y_ratio) as i32;
let new_rect = Rect {
left,
top,
right: corrected_width,
bottom: corrected_height,
left: x_diff + window_x,
top: y_diff + window_y,
right: current_rect.right,
bottom: current_rect.bottom,
};
//TODO: We might need to take into account the differences in DPI for the new_rect, unless
//we can use the xy ratios above to the right/bottom (width/height of window) as well?
let is_maximized = &new_rect == target_area;
if is_maximized {
windows_api::WindowsApi::unmaximize_window(self.hwnd);
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
let move_enabled = animation_enabled
.get(&MovementRenderDispatcher::PREFIX)
.is_some_and(|v| *v);
drop(animation_enabled);
if move_enabled || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst) {
let anim_count = ANIMATION_MANAGER
.lock()
.count_in_progress(MovementRenderDispatcher::PREFIX);
self.set_position(&new_rect, true)?;
let hwnd = self.hwnd;
// Wait for the animation to finish before maximizing the window again, otherwise
// we would be maximizing the window on the current monitor anyway
thread::spawn(move || {
let mut new_anim_count = ANIMATION_MANAGER
.lock()
.count_in_progress(MovementRenderDispatcher::PREFIX);
let mut max_wait = 2000; // Max waiting time. No one will be using an animation longer than 2s, right? RIGHT??? WHY?
while new_anim_count > anim_count && max_wait > 0 {
thread::sleep(Duration::from_millis(10));
new_anim_count = ANIMATION_MANAGER
.lock()
.count_in_progress(MovementRenderDispatcher::PREFIX);
max_wait -= 1;
}
windows_api::WindowsApi::maximize_window(hwnd);
});
} else {
self.set_position(&new_rect, true)?;
windows_api::WindowsApi::maximize_window(self.hwnd);
}
} else {
self.set_position(&new_rect, true)?;
}
Ok(())
self.set_position(&new_rect, true)
}
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
@@ -383,6 +200,53 @@ impl Window {
)
}
pub fn animate_position(&self, start_rect: &Rect, target_rect: &Rect, top: bool) -> Result<()> {
let start_rect = *start_rect;
let target_rect = *target_rect;
let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst));
let mut animation = self.animation;
border_manager::BORDER_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
border_manager::send_notification(Some(self.hwnd));
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
stackbar_manager::send_notification();
let hwnd = self.hwnd;
std::thread::spawn(move || {
animation.animate(duration, |progress: f64| {
let new_rect = Animation::lerp_rect(&start_rect, &target_rect, progress);
if progress == 1.0 {
WindowsApi::position_window(hwnd, &new_rect, top)?;
if WindowsApi::foreground_window().unwrap_or_default() == hwnd {
focus_manager::send_notification(hwnd)
}
if ANIMATIONS_IN_PROGRESS.load(Ordering::Acquire) == 0 {
border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED
.store(false, Ordering::SeqCst);
border_manager::send_notification(Some(hwnd));
stackbar_manager::send_notification();
transparency_manager::send_notification();
}
} else {
// using MoveWindow because it runs faster than SetWindowPos
// so animation have more fps and feel smoother
WindowsApi::move_window(hwnd, &new_rect, false)?;
WindowsApi::invalidate_rect(hwnd, None, false);
}
Ok(())
})
});
Ok(())
}
pub fn set_position(&self, layout: &Rect, top: bool) -> Result<()> {
let window_rect = WindowsApi::window_rect(self.hwnd)?;
@@ -390,27 +254,8 @@ impl Window {
return Ok(());
}
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
let move_enabled = animation_enabled.get(&MovementRenderDispatcher::PREFIX);
if move_enabled.is_some_and(|enabled| *enabled)
|| ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)
{
let duration = Duration::from_millis(
*ANIMATION_DURATION_PER_ANIMATION
.lock()
.get(&MovementRenderDispatcher::PREFIX)
.unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),
);
let style = *ANIMATION_STYLE_PER_ANIMATION
.lock()
.get(&MovementRenderDispatcher::PREFIX)
.unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());
let render_dispatcher =
MovementRenderDispatcher::new(self.hwnd, window_rect, *layout, top, style);
AnimationEngine::animate(render_dispatcher, duration)
if ANIMATION_ENABLED.load(Ordering::SeqCst) {
self.animate_position(&window_rect, layout, top)
} else {
WindowsApi::position_window(self.hwnd, layout, top)
}
@@ -518,81 +363,20 @@ impl Window {
Ok(())
}
pub fn is_focused(self) -> bool {
WindowsApi::foreground_window().unwrap_or_default() == self.hwnd
}
pub fn transparent(self) -> Result<()> {
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
let transparent_enabled = animation_enabled.get(&TransparencyRenderDispatcher::PREFIX);
if transparent_enabled.is_some_and(|enabled| *enabled)
|| ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)
{
let duration = Duration::from_millis(
*ANIMATION_DURATION_PER_ANIMATION
.lock()
.get(&TransparencyRenderDispatcher::PREFIX)
.unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),
);
let style = *ANIMATION_STYLE_PER_ANIMATION
.lock()
.get(&TransparencyRenderDispatcher::PREFIX)
.unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());
let render_dispatcher = TransparencyRenderDispatcher::new(
self.hwnd,
false,
WindowsApi::get_transparent(self.hwnd).unwrap_or(255),
transparency_manager::TRANSPARENCY_ALPHA.load_consume(),
style,
);
AnimationEngine::animate(render_dispatcher, duration)
} else {
let mut ex_style = self.ex_style()?;
ex_style.insert(ExtendedWindowStyle::LAYERED);
self.update_ex_style(&ex_style)?;
WindowsApi::set_transparent(
self.hwnd,
transparency_manager::TRANSPARENCY_ALPHA.load_consume(),
)
}
let mut ex_style = self.ex_style()?;
ex_style.insert(ExtendedWindowStyle::LAYERED);
self.update_ex_style(&ex_style)?;
WindowsApi::set_transparent(
self.hwnd,
transparency_manager::TRANSPARENCY_ALPHA.load_consume(),
)
}
pub fn opaque(self) -> Result<()> {
let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();
let transparent_enabled = animation_enabled.get(&TransparencyRenderDispatcher::PREFIX);
if transparent_enabled.is_some_and(|enabled| *enabled)
|| ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)
{
let duration = Duration::from_millis(
*ANIMATION_DURATION_PER_ANIMATION
.lock()
.get(&TransparencyRenderDispatcher::PREFIX)
.unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),
);
let style = *ANIMATION_STYLE_PER_ANIMATION
.lock()
.get(&TransparencyRenderDispatcher::PREFIX)
.unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());
let render_dispatcher = TransparencyRenderDispatcher::new(
self.hwnd,
true,
WindowsApi::get_transparent(self.hwnd)
.unwrap_or(transparency_manager::TRANSPARENCY_ALPHA.load_consume()),
255,
style,
);
AnimationEngine::animate(render_dispatcher, duration)
} else {
let mut ex_style = self.ex_style()?;
ex_style.remove(ExtendedWindowStyle::LAYERED);
self.update_ex_style(&ex_style)
}
let mut ex_style = self.ex_style()?;
ex_style.remove(ExtendedWindowStyle::LAYERED);
self.update_ex_style(&ex_style)
}
pub fn set_accent(self, colour: u32) -> Result<()> {

View File

@@ -498,35 +498,6 @@ impl WindowManager {
None
}
/// Calculates the direction of a move across monitors given a specific monitor index
pub fn direction_from_monitor_idx(
&self,
target_monitor_idx: usize,
) -> Option<OperationDirection> {
let current_monitor_idx = self.focused_monitor_idx();
if current_monitor_idx == target_monitor_idx {
return None;
}
let current_monitor_size = self.focused_monitor_size().ok()?;
let target_monitor_size = *self.monitors().get(target_monitor_idx)?.size();
if target_monitor_size.left + target_monitor_size.right == current_monitor_size.left {
return Some(OperationDirection::Left);
}
if current_monitor_size.right + current_monitor_size.left == target_monitor_size.left {
return Some(OperationDirection::Right);
}
if target_monitor_size.top + target_monitor_size.bottom == current_monitor_size.top {
return Some(OperationDirection::Up);
}
if current_monitor_size.top + current_monitor_size.bottom == target_monitor_size.top {
return Some(OperationDirection::Down);
}
None
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip(self), level = "debug")]
fn add_window_handle_to_move_based_on_workspace_rule(
@@ -1412,7 +1383,6 @@ impl WindowManager {
monitor_idx: usize,
workspace_idx: Option<usize>,
follow: bool,
move_direction: Option<OperationDirection>,
) -> Result<()> {
self.handle_unmanaged_window_behaviour()?;
@@ -1467,12 +1437,8 @@ impl WindowManager {
.get_mut(monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor"))?;
let mut should_load_workspace = false;
if let Some(workspace_idx) = workspace_idx {
if workspace_idx != target_monitor.focused_workspace_idx() {
target_monitor.focus_workspace(workspace_idx)?;
should_load_workspace = true;
}
target_monitor.focus_workspace(workspace_idx)?;
}
let target_workspace = target_monitor
.focused_workspace_mut()
@@ -1489,11 +1455,7 @@ impl WindowManager {
.map(|w| w.hwnd)
.collect::<Vec<_>>();
if let Some(direction) = move_direction {
target_monitor.add_container_with_direction(container, workspace_idx, direction)?;
} else {
target_monitor.add_container(container, workspace_idx)?;
}
target_monitor.add_container(container, workspace_idx)?;
if let Some(workspace) = target_monitor.focused_workspace() {
if !*workspace.tile() {
@@ -1507,9 +1469,7 @@ impl WindowManager {
bail!("failed to find a window to move");
}
if should_load_workspace {
target_monitor.load_focused_workspace(mouse_follows_focus)?;
}
target_monitor.load_focused_workspace(mouse_follows_focus)?;
target_monitor.update_focused_workspace(offset)?;
// this second one is for DPI changes when the target is another monitor
@@ -1588,14 +1548,13 @@ impl WindowManager {
tracing::info!("focusing container");
let new_idx =
if workspace.maximized_window().is_some() || workspace.monocle_container().is_some() {
None
} else {
workspace.new_idx_for_direction(direction)
};
let new_idx = if workspace.monocle_container().is_some() {
None
} else {
workspace.new_idx_for_direction(direction)
};
let mut cross_monitor_monocle_or_max = false;
let mut cross_monitor_monocle = false;
// this is for when we are scrolling across workspaces like PaperWM
if new_idx.is_none()
@@ -1672,24 +1631,14 @@ impl WindowManager {
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)?;
// (alex-ds13): @LGUG2Z Why was this being done below on the monocle?
// Should it really be done?
//
// WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(
// window.hwnd,
// )?)?;
cross_monitor_monocle_or_max = true;
} else if let Some(monocle) = focused_workspace.monocle_container() {
if let Some(monocle) = focused_workspace.monocle_container() {
if let Some(window) = monocle.focused_window() {
window.focus(mouse_follows_focus)?;
WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(
window.hwnd,
)?)?;
cross_monitor_monocle_or_max = true;
cross_monitor_monocle = true;
}
} else {
match direction {
@@ -1726,7 +1675,7 @@ impl WindowManager {
}
}
if !cross_monitor_monocle_or_max {
if !cross_monitor_monocle {
if let Ok(focused_window) = self.focused_window_mut() {
focused_window.focus(self.mouse_follows_focus)?;
}
@@ -1805,13 +1754,15 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
{
// actually move the container to target monitor using the direction
self.move_container_to_monitor(
target_monitor_idx,
None,
true,
Some(direction),
)?;
// remove the container from the origin monitor workspace
let origin_container = self
.focused_workspace_mut()?
.remove_container_by_idx(origin_container_idx)
.ok_or_else(|| {
anyhow!("could not remove container at given origin index")
})?;
self.focused_workspace_mut()?.focus_previous_container();
// focus the target monitor
self.focus_monitor(target_monitor_idx)?;
@@ -1831,6 +1782,79 @@ impl WindowManager {
// get a mutable ref to the focused workspace on the target monitor
let target_workspace = self.focused_workspace_mut()?;
match direction {
OperationDirection::Left => {
// insert the origin container into the focused workspace on the target monitor
// at the back (or rightmost position) if we are moving across a boundary to
// the left (back = right side of the target)
match target_workspace.layout() {
Layout::Default(layout) => match layout {
DefaultLayout::RightMainVerticalStack => {
target_workspace.add_container_to_front(origin_container);
}
DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace
.insert_container_at_idx(0, origin_container);
} else {
target_workspace
.add_container_to_back(origin_container);
}
}
_ => {
target_workspace.add_container_to_back(origin_container);
}
},
Layout::Custom(_) => {
target_workspace.add_container_to_back(origin_container);
}
}
}
OperationDirection::Right => {
// insert the origin container into the focused workspace on the target monitor
// at the front (or leftmost position) if we are moving across a boundary to the
// right (front = left side of the target)
match target_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(target_workspace.containers().len());
match layout {
DefaultLayout::RightMainVerticalStack
| DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace
.add_container_to_back(origin_container);
} else {
target_workspace.insert_container_at_idx(
target_index,
origin_container,
);
}
}
_ => {
target_workspace.insert_container_at_idx(
target_index,
origin_container,
);
}
}
}
Layout::Custom(_) => {
target_workspace.add_container_to_front(origin_container);
}
}
}
OperationDirection::Up | OperationDirection::Down => {
// insert the origin container into the focused workspace on the target monitor
// at the position where the currently focused container on that workspace is
target_workspace.insert_container_at_idx(
target_workspace.focused_container_idx(),
origin_container,
);
}
};
// if there is only one container on the target workspace after the insertion
// it means that there won't be one swapped back, so we have to decrement the
// focused position

View File

@@ -77,7 +77,6 @@ use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::GetLayeredWindowAttributes;
use windows::Win32::UI::WindowsAndMessaging::GetTopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
@@ -106,6 +105,7 @@ use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
@@ -128,6 +128,7 @@ use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
@@ -152,7 +153,6 @@ macro_rules! as_ptr {
};
}
use crate::border_manager::Border;
pub(crate) use as_ptr;
pub enum WindowsResult<T, E> {
@@ -452,13 +452,7 @@ impl WindowsApi {
}
pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> {
let flags = {
SetWindowPosition::NO_SEND_CHANGING
| SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::NO_REDRAW
| SetWindowPosition::SHOW_WINDOW
};
let flags = { SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE };
Self::set_window_pos(
HWND(as_ptr!(hwnd)),
layout,
@@ -1095,14 +1089,10 @@ impl WindowsApi {
.process()
}
pub fn create_border_window(
name: PCWSTR,
instance: isize,
border: *const Border,
) -> Result<isize> {
pub fn create_border_window(name: PCWSTR, instance: isize) -> Result<isize> {
unsafe {
CreateWindowExW(
WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
let hwnd = CreateWindowExW(
WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
name,
name,
WS_POPUP | WS_SYSMENU,
@@ -1113,8 +1103,12 @@ impl WindowsApi {
None,
None,
HINSTANCE(as_ptr!(instance)),
Some(border as _),
)?
None,
)?;
SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?;
hwnd
}
.process()
}
@@ -1133,21 +1127,6 @@ impl WindowsApi {
Ok(())
}
pub fn get_transparent(hwnd: isize) -> Result<u8> {
unsafe {
let mut alpha: u8 = u8::default();
let mut color_ref = COLORREF(-1i32 as u32);
let mut flags = LWA_ALPHA;
GetLayeredWindowAttributes(
HWND(as_ptr!(hwnd)),
Some(std::ptr::addr_of_mut!(color_ref)),
Some(std::ptr::addr_of_mut!(alpha)),
Some(std::ptr::addr_of_mut!(flags)),
)?;
Ok(alpha)
}
}
pub fn create_hidden_window(name: PCWSTR, instance: isize) -> Result<isize> {
unsafe {
CreateWindowExW(

View File

@@ -1,6 +1,10 @@
use std::collections::VecDeque;
use crate::border_manager;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use crate::container::Container;
use crate::window::RuleDebug;
use crate::window::Window;
@@ -8,19 +12,6 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent;
use crate::winevent_listener;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
@@ -69,15 +60,6 @@ pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL {
true.into()
}
fn has_filtered_style(hwnd: HWND) -> bool {
let style = unsafe { GetWindowLongW(hwnd, GWL_STYLE) as u32 };
let ex_style = unsafe { GetWindowLongW(hwnd, GWL_EXSTYLE) as u32 };
style & WS_CHILD.0 != 0
|| ex_style & WS_EX_TOOLWINDOW.0 != 0
|| ex_style & WS_EX_NOACTIVATE.0 != 0
}
pub extern "system" fn win_event_hook(
_h_win_event_hook: HWINEVENTHOOK,
event: u32,
@@ -87,7 +69,8 @@ pub extern "system" fn win_event_hook(
_id_event_thread: u32,
_dwms_event_time: u32,
) {
if id_object != OBJID_WINDOW.0 {
// OBJID_WINDOW
if id_object != 0 {
return;
}
@@ -98,23 +81,6 @@ pub extern "system" fn win_event_hook(
Err(_) => return,
};
// this forwards the message to the window's border when it moves or is destroyed
// see border_manager/border.rs
if matches!(
winevent,
WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy
) && !has_filtered_style(hwnd)
{
let border_window = border_manager::window_border(hwnd.0 as isize);
if let Some(border) = border_window {
unsafe {
let _ =
SendNotifyMessageW(border.hwnd(), event, WPARAM(0), LPARAM(hwnd.0 as isize));
}
}
}
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
None => {
tracing::trace!(

View File

@@ -11,8 +11,6 @@ use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_OUTOFCONTEXT;
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_SKIPOWNPROCESS;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_callbacks;
@@ -33,7 +31,7 @@ pub fn start() {
Some(windows_callbacks::win_event_hook),
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
0,
)
};

View File

@@ -246,10 +246,6 @@ impl Workspace {
if let Some(window) = to_focus {
if self.maximized_window().is_none() && self.floating_windows().is_empty() {
window.focus(mouse_follows_focus)?;
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.focus(mouse_follows_focus)?;
} else if let Some(floating_window) = self.floating_windows().first() {
floating_window.focus(mouse_follows_focus)?;
}
}

View File

@@ -724,18 +724,12 @@ struct BorderImplementation {
struct Animation {
#[clap(value_enum)]
boolean_state: BooleanState,
/// Animation type to apply the state to. If not specified, sets global state
#[clap(value_enum, short, long)]
animation_type: Option<komorebi_client::AnimationPrefix>,
}
#[derive(Parser)]
struct AnimationDuration {
/// Desired animation durations in ms
duration: u64,
/// Animation type to apply the duration to. If not specified, sets global duration
#[clap(value_enum, short, long)]
animation_type: Option<komorebi_client::AnimationPrefix>,
}
#[derive(Parser)]
@@ -749,9 +743,6 @@ struct AnimationStyle {
/// Desired ease function for animation
#[clap(value_enum, short, long, default_value = "linear")]
style: komorebi_client::AnimationStyle,
/// Animation type to apply the style to. If not specified, sets global style
#[clap(value_enum, short, long)]
animation_type: Option<komorebi_client::AnimationPrefix>,
}
#[derive(Parser)]
@@ -779,9 +770,6 @@ struct Start {
/// Start komorebi-bar in a background process
#[clap(long)]
bar: bool,
/// Start masir in a background process for focus-follows-mouse
#[clap(long)]
masir: bool,
}
#[derive(Parser)]
@@ -795,25 +783,6 @@ struct Stop {
/// Stop komorebi-bar if it is running as a background process
#[clap(long)]
bar: bool,
/// Stop masir if it is running as a background process
#[clap(long)]
masir: bool,
}
#[derive(Parser)]
struct Kill {
/// Kill whkd if it is running as a background process
#[clap(long)]
whkd: bool,
/// Kill ahk if it is running as a background process
#[clap(long)]
ahk: bool,
/// Kill komorebi-bar if it is running as a background process
#[clap(long)]
bar: bool,
/// Kill masir if it is running as a background process
#[clap(long)]
masir: bool,
}
#[derive(Parser)]
@@ -910,9 +879,6 @@ struct EnableAutostart {
/// Enable autostart of komorebi-bar
#[clap(long)]
bar: bool,
/// Enable autostart of masir
#[clap(long)]
masir: bool,
}
#[derive(Parser)]
@@ -938,8 +904,6 @@ enum SubCommand {
Start(Start),
/// Stop the komorebi.exe process and restore all hidden windows
Stop(Stop),
/// Kill background processes started by komorebic
Kill(Kill),
/// Check komorebi configuration and related files for common errors
Check,
/// Show the path to komorebi.json
@@ -1534,10 +1498,6 @@ fn main() -> Result<()> {
arguments.push_str(" --ahk");
}
if args.bar {
arguments.push_str(" --masir");
}
Command::new("powershell")
.arg("-c")
.arg("$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()")
@@ -1958,10 +1918,6 @@ fn main() -> Result<()> {
bail!("could not find whkd, please make sure it is installed before using the --whkd flag");
}
if arg.masir && which("masir").is_err() {
bail!("could not find masir, please make sure it is installed before using the --masir flag");
}
if arg.ahk && which(&ahk).is_err() {
bail!("could not find autohotkey, please make sure it is installed before using the --ahk flag");
}
@@ -2025,14 +1981,8 @@ fn main() -> Result<()> {
)
};
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All);
let mut attempts = 0;
let mut running = system
.processes_by_name("komorebi.exe".as_ref())
.next()
.is_some();
let mut running = false;
while !running && attempts <= 2 {
match powershell_script::run(&script) {
@@ -2047,6 +1997,7 @@ fn main() -> Result<()> {
print!("Waiting for komorebi.exe to start...");
std::thread::sleep(Duration::from_secs(3));
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All);
if system
@@ -2134,7 +2085,7 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue))
let mut config = StaticConfig::read(config)?;
if let Some(display_bar_configurations) = &mut config.bar_configurations {
for config_file_path in &mut *display_bar_configurations {
let script = r#"Start-Process "komorebi-bar" '"--config" "CONFIGFILE"' -WindowStyle hidden"#
let script = r#"Start-Process 'komorebi-bar' '--config "CONFIGFILE"' -WindowStyle hidden"#
.replace("CONFIGFILE", &config_file_path.to_string_lossy());
match powershell_script::run(&script) {
@@ -2165,23 +2116,6 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
}
}
if arg.masir {
let script = r"
if (!(Get-Process masir -ErrorAction SilentlyContinue))
{
Start-Process masir -WindowStyle hidden
}
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
println!("\nThank you for using komorebi!\n");
println!("# Sponsorship");
println!("* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference");
@@ -2250,20 +2184,6 @@ Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
}
}
if arg.masir {
let script = r"
Stop-Process -Name:masir -ErrorAction SilentlyContinue
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
if arg.ahk {
let script = r#"
if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
@@ -2323,78 +2243,6 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
}
}
}
SubCommand::Kill(arg) => {
if arg.whkd {
let script = r"
Stop-Process -Name:whkd -ErrorAction SilentlyContinue
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
if arg.bar {
let script = r"
Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
if arg.masir {
let script = r"
Stop-Process -Name:masir -ErrorAction SilentlyContinue
";
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
if arg.ahk {
let script = r#"
if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
(Get-CimInstance Win32_Process | Where-Object {
($_.CommandLine -like '*komorebi.ahk"') -and
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))
} | Select-Object -First 1) | ForEach-Object {
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
}
} else {
(Get-WmiObject Win32_Process | Where-Object {
($_.CommandLine -like '*komorebi.ahk"') -and
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))
} | Select-Object -First 1) | ForEach-Object {
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
}
}
"#;
match powershell_script::run(script) {
Ok(_) => {
println!("{script}");
}
Err(error) => {
println!("Error: {error}");
}
}
}
}
SubCommand::IgnoreRule(arg) => {
send_message(&SocketMessage::IgnoreRule(arg.identifier, arg.id))?;
}
@@ -2698,25 +2546,16 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
send_message(&SocketMessage::ToggleTransparency)?;
}
SubCommand::Animation(arg) => {
send_message(&SocketMessage::Animation(
arg.boolean_state.into(),
arg.animation_type,
))?;
send_message(&SocketMessage::Animation(arg.boolean_state.into()))?;
}
SubCommand::AnimationDuration(arg) => {
send_message(&SocketMessage::AnimationDuration(
arg.duration,
arg.animation_type,
))?;
send_message(&SocketMessage::AnimationDuration(arg.duration))?;
}
SubCommand::AnimationFps(arg) => {
send_message(&SocketMessage::AnimationFps(arg.fps))?;
}
SubCommand::AnimationStyle(arg) => {
send_message(&SocketMessage::AnimationStyle(
arg.style,
arg.animation_type,
))?;
send_message(&SocketMessage::AnimationStyle(arg.style))?;
}
SubCommand::ResizeDelta(arg) => {

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.31`",
"description": "The `komorebi.json` static configuration file reference for `v0.1.30`",
"type": "object",
"properties": {
"animation": {
@@ -13,35 +13,13 @@
"properties": {
"duration": {
"description": "Set the animation duration in ms (default: 250)",
"anyOf": [
{
"type": "object",
"additionalProperties": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
{
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
]
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enabled": {
"description": "Enable or disable animations (default: false)",
"anyOf": [
{
"type": "object",
"additionalProperties": {
"type": "boolean"
}
},
{
"type": "boolean"
}
]
"type": "boolean"
},
"fps": {
"description": "Set the animation FPS (default: 60)",
@@ -51,80 +29,38 @@
},
"style": {
"description": "Set the animation style (default: Linear)",
"anyOf": [
{
"type": "object",
"additionalProperties": {
"type": "string",
"enum": [
"Linear",
"EaseInSine",
"EaseOutSine",
"EaseInOutSine",
"EaseInQuad",
"EaseOutQuad",
"EaseInOutQuad",
"EaseInCubic",
"EaseInOutCubic",
"EaseInQuart",
"EaseOutQuart",
"EaseInOutQuart",
"EaseInQuint",
"EaseOutQuint",
"EaseInOutQuint",
"EaseInExpo",
"EaseOutExpo",
"EaseInOutExpo",
"EaseInCirc",
"EaseOutCirc",
"EaseInOutCirc",
"EaseInBack",
"EaseOutBack",
"EaseInOutBack",
"EaseInElastic",
"EaseOutElastic",
"EaseInOutElastic",
"EaseInBounce",
"EaseOutBounce",
"EaseInOutBounce"
]
}
},
{
"type": "string",
"enum": [
"Linear",
"EaseInSine",
"EaseOutSine",
"EaseInOutSine",
"EaseInQuad",
"EaseOutQuad",
"EaseInOutQuad",
"EaseInCubic",
"EaseInOutCubic",
"EaseInQuart",
"EaseOutQuart",
"EaseInOutQuart",
"EaseInQuint",
"EaseOutQuint",
"EaseInOutQuint",
"EaseInExpo",
"EaseOutExpo",
"EaseInOutExpo",
"EaseInCirc",
"EaseOutCirc",
"EaseInOutCirc",
"EaseInBack",
"EaseOutBack",
"EaseInOutBack",
"EaseInElastic",
"EaseOutElastic",
"EaseInOutElastic",
"EaseInBounce",
"EaseOutBounce",
"EaseInOutBounce"
]
}
"type": "string",
"enum": [
"Linear",
"EaseInSine",
"EaseOutSine",
"EaseInOutSine",
"EaseInQuad",
"EaseOutQuad",
"EaseInOutQuad",
"EaseInCubic",
"EaseInOutCubic",
"EaseInQuart",
"EaseOutQuart",
"EaseInOutQuart",
"EaseInQuint",
"EaseOutQuint",
"EaseInOutQuint",
"EaseInExpo",
"EaseOutExpo",
"EaseInOutExpo",
"EaseInCirc",
"EaseOutCirc",
"EaseInOutCirc",
"EaseInBack",
"EaseOutBack",
"EaseInOutBack",
"EaseInElastic",
"EaseOutElastic",
"EaseInOutElastic",
"EaseInBounce",
"EaseOutBounce",
"EaseInOutBounce"
]
}
}
@@ -484,7 +420,7 @@
"format": "int32"
},
"border_z_order": {
"description": "DEPRECATED from v0.1.31: no longer required",
"description": "Active window border z-order (default: System)",
"type": "string",
"enum": [
"Top",
@@ -643,7 +579,7 @@
}
},
"focus_follows_mouse": {
"description": "END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead",
"description": "END OF LIFE FEATURE: Determine focus follows mouse implementation (default: None)",
"oneOf": [
{
"description": "A custom FFM implementation (slightly more CPU-intensive)",