feat(wm): add replace configuration socket message

This commit introduces a new SocketMessage, ReplaceConfiguration, which
attempts to replace a running instance of WindowManager with another
created from a (presumably) different komorebi.json file.

This will likely be useful for people who have multiple different
monitor setups that they connect and disconnect from throughout the day,
but definitely needs more testing.

An experimental sub-widget which calls this SocketMessage has been added
to komorebi-bar to aid with initial testing.
This commit is contained in:
LGUG2Z
2024-09-17 21:09:11 -07:00
parent 21a2138330
commit 2916256e79
12 changed files with 292 additions and 69 deletions
+2 -2
View File
@@ -183,7 +183,7 @@ impl Komobar {
for (idx, widget_config) in config.left_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(*config));
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Side::Left);
}
@@ -191,7 +191,7 @@ impl Komobar {
for (idx, widget_config) in config.right_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(*config));
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Side::Right);
}
+113 -32
View File
@@ -16,21 +16,26 @@ use eframe::egui::Ui;
use image::RgbaImage;
use komorebi_client::CycleDirection;
use komorebi_client::NotificationEvent;
use komorebi_client::Rect;
use komorebi_client::SocketMessage;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::rc::Rc;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiConfig {
/// Configure the Workspaces widget
pub workspaces: KomorebiWorkspacesConfig,
/// Configure the Layout widget
pub layout: KomorebiLayoutConfig,
pub layout: Option<KomorebiLayoutConfig>,
/// Configure the Focused Window widget
pub focused_window: KomorebiFocusedWindowConfig,
pub focused_window: Option<KomorebiFocusedWindowConfig>,
/// Configure the Configuration Switcher widget
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -55,8 +60,37 @@ pub struct KomorebiFocusedWindowConfig {
pub show_icon: bool,
}
impl From<KomorebiConfig> for Komorebi {
fn from(value: KomorebiConfig) -> Self {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiConfigurationSwitcherConfig {
/// Enable the Komorebi Configurations widget
pub enable: bool,
/// A map of display friendly name => path to configuration.json
pub configurations: BTreeMap<String, String>,
}
impl From<&KomorebiConfig> for Komorebi {
fn from(value: &KomorebiConfig) -> Self {
let configuration_switcher =
if let Some(configuration_switcher) = &value.configuration_switcher {
let mut configuration_switcher = configuration_switcher.clone();
for (_, location) in configuration_switcher.configurations.iter_mut() {
if let Ok(expanded) = std::env::var("KOMOREBI_CONFIG_HOME") {
*location = location.replace("$Env:KOMOREBI_CONFIG_HOME", &expanded);
}
if let Ok(expanded) = std::env::var("USERPROFILE") {
*location = location.replace("$Env:USERPROFILE", &expanded);
}
*location = dunce::simplified(&PathBuf::from(location.clone()))
.to_string_lossy()
.to_string();
}
Some(configuration_switcher)
} else {
None
};
Self {
komorebi_notification_state: Rc::new(RefCell::new(KomorebiNotificationState {
selected_workspace: String::new(),
@@ -67,10 +101,12 @@ impl From<KomorebiConfig> for Komorebi {
workspaces: vec![],
hide_empty_workspaces: value.workspaces.hide_empty_workspaces,
mouse_follows_focus: true,
work_area_offset: None,
})),
workspaces: value.workspaces,
layout: value.layout,
focused_window: value.focused_window,
configuration_switcher,
}
}
}
@@ -79,8 +115,9 @@ impl From<KomorebiConfig> for Komorebi {
pub struct Komorebi {
pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>,
pub workspaces: KomorebiWorkspacesConfig,
pub layout: KomorebiLayoutConfig,
pub focused_window: KomorebiFocusedWindowConfig,
pub layout: Option<KomorebiLayoutConfig>,
pub focused_window: Option<KomorebiFocusedWindowConfig>,
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
impl BarWidget for Komorebi {
@@ -103,9 +140,7 @@ impl BarWidget for Komorebi {
.unwrap();
komorebi_client::send_message(&SocketMessage::FocusWorkspaceNumber(i)).unwrap();
komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
self.komorebi_notification_state
.borrow()
.mouse_follows_focus,
komorebi_notification_state.mouse_follows_focus,
))
.unwrap();
komorebi_client::send_message(&SocketMessage::Retile).unwrap();
@@ -119,36 +154,78 @@ impl BarWidget for Komorebi {
ui.add_space(WIDGET_SPACING);
}
if self.layout.enable {
if ui
.add(
Label::new(&komorebi_notification_state.layout)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
komorebi_client::send_message(&SocketMessage::CycleLayout(CycleDirection::Next))
if let Some(layout) = self.layout {
if layout.enable {
if ui
.add(
Label::new(&komorebi_notification_state.layout)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
komorebi_client::send_message(&SocketMessage::CycleLayout(
CycleDirection::Next,
))
.unwrap();
}
}
ui.add_space(WIDGET_SPACING);
ui.add_space(WIDGET_SPACING);
}
}
if self.focused_window.enable {
if self.focused_window.show_icon {
if let Some(img) = &komorebi_notification_state.focused_window_icon {
ui.add(
Image::from(&img_to_texture(ctx, img))
.maintain_aspect_ratio(true)
.max_height(15.0),
);
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);
komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized,
))
.unwrap();
if let Some(rect) = komorebi_notification_state.work_area_offset {
let monitor_index = komorebi_client::send_query(&SocketMessage::Query(
komorebi_client::StateQuery::FocusedMonitorIndex,
))
.unwrap();
komorebi_client::send_message(&SocketMessage::MonitorWorkAreaOffset(
monitor_index.parse::<usize>().unwrap(),
rect,
))
.unwrap();
}
}
}
ui.add_space(WIDGET_SPACING);
}
}
ui.add(Label::new(&komorebi_notification_state.focused_window_title).selectable(false));
if let Some(focused_window) = self.focused_window {
if focused_window.enable {
if focused_window.show_icon {
if let Some(img) = &komorebi_notification_state.focused_window_icon {
ui.add(
Image::from(&img_to_texture(ctx, img))
.maintain_aspect_ratio(true)
.max_height(15.0),
);
}
}
ui.add_space(WIDGET_SPACING);
ui.add(
Label::new(&komorebi_notification_state.focused_window_title).selectable(false),
);
ui.add_space(WIDGET_SPACING);
}
}
}
}
@@ -170,6 +247,7 @@ pub struct KomorebiNotificationState {
pub layout: String,
pub hide_empty_workspaces: bool,
pub mouse_follows_focus: bool,
pub work_area_offset: Option<Rect>,
}
impl KomorebiNotificationState {
@@ -199,6 +277,9 @@ impl KomorebiNotificationState {
self.mouse_follows_focus = notification.state.mouse_follows_focus;
let monitor = &notification.state.monitors.elements()[monitor_index];
self.work_area_offset =
notification.state.monitors.elements()[monitor_index].work_area_offset();
let focused_workspace_idx = monitor.focused_workspace_idx();
let mut workspaces = vec![];
+1 -1
View File
@@ -41,7 +41,7 @@ impl WidgetConfig {
match self {
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(*config)),
WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)),
WidgetConfig::Media(config) => Box::new(Media::from(*config)),
WidgetConfig::Memory(config) => Box::new(Memory::from(*config)),
WidgetConfig::Network(config) => Box::new(Network::from(*config)),