Files
komorebi/komorebi/src/process_command.rs
LGUG2Z c65060fbd9 feat(wm): add stackbar for multi-window containers
This commit introduces the stackbar feature through careful extracting
and refactoring of code from the Komorebi-UI hard-fork.

Unfortunately on the fork, this feature was not implemented using atomic
commits, which resulted in the implementation here being more of a
"reinterpretation" than a lift-and-shit of the referenced code.

Nevertheless, this commit represents a working version of the stackbar
feature.

resolve #681
2024-03-25 14:30:30 -07:00

1579 lines
66 KiB
Rust

use std::collections::HashMap;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::net::TcpListener;
use std::net::TcpStream;
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use schemars::gen::SchemaSettings;
use schemars::schema_for;
use uds_windows::UnixStream;
use komorebi_core::config_generation::ApplicationConfiguration;
use komorebi_core::config_generation::IdWithIdentifier;
use komorebi_core::config_generation::MatchingRule;
use komorebi_core::config_generation::MatchingStrategy;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::Axis;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::Layout;
use komorebi_core::MoveBehaviour;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use komorebi_core::Sizing;
use komorebi_core::SocketMessage;
use komorebi_core::StateQuery;
use komorebi_core::WindowContainerBehaviour;
use komorebi_core::WindowKind;
use crate::border::Border;
use crate::colour::Rgb;
use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::static_config::StaticConfig;
use crate::window::Window;
use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::Notification;
use crate::NotificationEvent;
use crate::BORDER_COLOUR_CURRENT;
use crate::BORDER_COLOUR_MONOCLE;
use crate::BORDER_COLOUR_SINGLE;
use crate::BORDER_COLOUR_STACK;
use crate::BORDER_ENABLED;
use crate::BORDER_HIDDEN;
use crate::BORDER_HWND;
use crate::BORDER_OFFSET;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::BORDER_WIDTH;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKETS;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WORKSPACE_RULES;
#[tracing::instrument]
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
std::thread::spawn(move || {
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => match read_commands_uds(&wm, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
},
Err(error) => {
tracing::error!("{}", error);
break;
}
}
}
});
}
#[tracing::instrument]
pub fn listen_for_commands_tcp(wm: Arc<Mutex<WindowManager>>, port: usize) {
let listener =
TcpListener::bind(format!("0.0.0.0:{port}")).expect("could not start tcp server");
std::thread::spawn(move || {
tracing::info!("listening on 0.0.0.0:43663");
for client in listener.incoming() {
match client {
Ok(mut stream) => {
stream
.set_keepalive(Some(Duration::from_secs(30)))
.expect("TCP keepalive should be set");
let addr = stream
.peer_addr()
.expect("incoming connection should have an address")
.to_string();
let mut connections = TCP_CONNECTIONS.lock();
connections.insert(
addr.clone(),
stream.try_clone().expect("stream should be cloneable"),
);
tracing::info!("listening for incoming tcp messages from {}", &addr);
match read_commands_tcp(&wm, &mut stream, &addr) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
}
Err(error) => {
tracing::error!("{}", error);
break;
}
}
}
});
}
impl WindowManager {
// TODO(raggi): wrap reply in a newtype that can decorate a human friendly
// name for the peer, such as getting the pid of the komorebic process for
// the UDS or the IP:port for TCP.
#[tracing::instrument(skip(self, reply))]
pub fn process_command(
&mut self,
message: SocketMessage,
mut reply: impl std::io::Write,
) -> Result<()> {
if let Some(virtual_desktop_id) = &self.virtual_desktop_id {
if let Some(id) = current_virtual_desktop() {
if id != *virtual_desktop_id {
tracing::info!(
"ignoring events and commands while not on virtual desktop {:?}",
virtual_desktop_id
);
return Ok(());
}
}
}
match message {
SocketMessage::CycleFocusMonitor(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusMonitorNumber(_)
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
| SocketMessage::FocusWorkspaceNumber(_) => {
if self.focused_workspace()?.visible_windows().is_empty() {
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.hide()?;
BORDER_HIDDEN.store(true, Ordering::SeqCst);
}
}
_ => {}
};
match message {
SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => {
if let Some(monitor) = self.focused_monitor_mut() {
let idx = monitor.focused_workspace_idx();
monitor.set_last_focused_workspace(Option::from(idx));
}
}
SocketMessage::FocusMonitorWorkspaceNumber(target_monitor_idx, _) => {
let idx = self.focused_workspace_idx_for_monitor_idx(target_monitor_idx)?;
if let Some(monitor) = self.monitors_mut().get_mut(target_monitor_idx) {
monitor.set_last_focused_workspace(Option::from(idx));
}
}
_ => {}
};
match message {
SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
SocketMessage::FocusWindow(direction) => {
self.focus_container_in_direction(direction)?;
}
SocketMessage::MoveWindow(direction) => {
self.move_container_in_direction(direction)?;
}
SocketMessage::CycleFocusWindow(direction) => {
self.focus_container_in_cycle_direction(direction)?;
}
SocketMessage::CycleMoveWindow(direction) => {
self.move_container_in_cycle_direction(direction)?;
}
SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?,
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
SocketMessage::CycleStack(direction) => {
self.cycle_container_window_in_direction(direction)?;
self.focused_window()?.focus(self.mouse_follows_focus)?;
}
SocketMessage::ForceFocus => {
let focused_window = self.focused_window()?;
let focused_window_rect = WindowsApi::window_rect(focused_window.hwnd())?;
WindowsApi::center_cursor_in_rect(&focused_window_rect)?;
WindowsApi::left_click();
}
SocketMessage::Close => self.focused_window()?.close()?,
SocketMessage::Minimize => self.focused_window()?.minimize(),
SocketMessage::ToggleFloat => self.toggle_float()?,
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {
self.set_container_padding(monitor_idx, workspace_idx, size)?;
}
SocketMessage::NamedWorkspaceContainerPadding(ref workspace, size) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.set_container_padding(monitor_idx, workspace_idx, size)?;
}
}
SocketMessage::WorkspacePadding(monitor_idx, workspace_idx, size) => {
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
}
SocketMessage::NamedWorkspacePadding(ref workspace, size) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
}
}
SocketMessage::InitialWorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
self.handle_initial_workspace_rules(id, monitor_idx, workspace_idx)?;
}
SocketMessage::InitialNamedWorkspaceRule(_, ref id, ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.handle_initial_workspace_rules(id, monitor_idx, workspace_idx)?;
}
}
SocketMessage::WorkspaceRule(_, ref id, monitor_idx, workspace_idx) => {
self.handle_definitive_workspace_rules(id, monitor_idx, workspace_idx)?;
}
SocketMessage::NamedWorkspaceRule(_, ref id, ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.handle_definitive_workspace_rules(id, monitor_idx, workspace_idx)?;
}
}
SocketMessage::ManageRule(identifier, ref id) => {
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
let mut should_push = true;
for m in &*manage_identifiers {
if let MatchingRule::Simple(m) = m {
if m.id.eq(id) {
should_push = false;
}
}
}
if should_push {
manage_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
}
SocketMessage::FloatRule(identifier, ref id) => {
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
let mut should_push = true;
for f in &*float_identifiers {
if let MatchingRule::Simple(f) = f {
if f.id.eq(id) {
should_push = false;
}
}
}
if should_push {
float_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
let offset = self.work_area_offset;
let mut hwnds_to_purge = vec![];
for (i, monitor) in self.monitors().iter().enumerate() {
for container in monitor
.focused_workspace()
.ok_or_else(|| anyhow!("there is no workspace"))?
.containers()
{
for window in container.windows() {
match identifier {
ApplicationIdentifier::Path => {
if window.path()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Exe => {
if window.exe()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Class => {
if window.class()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
ApplicationIdentifier::Title => {
if window.title()? == *id {
hwnds_to_purge.push((i, window.hwnd));
}
}
}
}
}
}
for (monitor_idx, hwnd) in hwnds_to_purge {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_else(|| anyhow!("there is no monitor"))?;
monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no focused workspace"))?
.remove_window(hwnd)?;
monitor.update_focused_workspace(offset)?;
}
}
SocketMessage::FocusedWorkspaceContainerPadding(adjustment) => {
let focused_monitor_idx = self.focused_monitor_idx();
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
self.set_container_padding(focused_monitor_idx, focused_workspace_idx, adjustment)?;
}
SocketMessage::FocusedWorkspacePadding(adjustment) => {
let focused_monitor_idx = self.focused_monitor_idx();
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
self.set_workspace_padding(focused_monitor_idx, focused_workspace_idx, adjustment)?;
}
SocketMessage::AdjustContainerPadding(sizing, adjustment) => {
self.adjust_container_padding(sizing, adjustment)?;
}
SocketMessage::AdjustWorkspacePadding(sizing, adjustment) => {
self.adjust_workspace_padding(sizing, adjustment)?;
}
SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, true)?;
}
SocketMessage::CycleMoveContainerToWorkspace(direction) => {
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.move_container_to_workspace(workspace_idx, true)?;
}
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SwapWorkspacesToMonitorNumber(monitor_idx) => {
self.swap_focused_monitor(monitor_idx)?;
}
SocketMessage::CycleMoveContainerToMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false)?;
}
SocketMessage::CycleSendContainerToWorkspace(direction) => {
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.move_container_to_workspace(workspace_idx, false)?;
}
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, false)?;
}
SocketMessage::CycleSendContainerToMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.move_container_to_monitor(monitor_idx, None, false)?;
}
SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), false)?;
}
SocketMessage::SendContainerToNamedWorkspace(ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.move_container_to_monitor(
monitor_idx,
Option::from(workspace_idx),
false,
)?;
}
}
SocketMessage::MoveContainerToNamedWorkspace(ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.move_container_to_monitor(monitor_idx, Option::from(workspace_idx), true)?;
}
}
SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {
self.move_workspace_to_monitor(monitor_idx)?;
}
SocketMessage::TogglePause => {
if self.is_paused {
tracing::info!("resuming");
} else {
tracing::info!("pausing");
}
self.is_paused = !self.is_paused;
self.retile_all(true)?;
}
SocketMessage::ToggleTiling => {
self.toggle_tiling()?;
}
SocketMessage::CycleFocusMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(self.mouse_follows_focus)?;
}
SocketMessage::FocusMonitorNumber(monitor_idx) => {
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(self.mouse_follows_focus)?;
}
SocketMessage::Retile => self.retile_all(false)?,
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
SocketMessage::ChangeLayoutCustom(ref path) => {
self.change_workspace_custom_layout(path)?;
}
SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, ref path) => {
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;
}
SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => {
self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;
}
SocketMessage::WorkspaceLayout(monitor_idx, workspace_idx, layout) => {
self.set_workspace_layout_default(monitor_idx, workspace_idx, layout)?;
}
SocketMessage::WorkspaceLayoutRule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
) => {
self.add_workspace_layout_default_rule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
)?;
}
SocketMessage::WorkspaceLayoutCustomRule(
monitor_idx,
workspace_idx,
at_container_count,
ref path,
) => {
self.add_workspace_layout_custom_rule(
monitor_idx,
workspace_idx,
at_container_count,
path,
)?;
}
SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {
self.clear_workspace_layout_rules(monitor_idx, workspace_idx)?;
}
SocketMessage::NamedWorkspaceLayoutCustom(ref workspace, ref path) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;
}
}
SocketMessage::NamedWorkspaceTiling(ref workspace, tile) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;
}
}
SocketMessage::NamedWorkspaceLayout(ref workspace, layout) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.set_workspace_layout_default(monitor_idx, workspace_idx, layout)?;
}
}
SocketMessage::NamedWorkspaceLayoutRule(ref workspace, at_container_count, layout) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.add_workspace_layout_default_rule(
monitor_idx,
workspace_idx,
at_container_count,
layout,
)?;
}
}
SocketMessage::NamedWorkspaceLayoutCustomRule(
ref workspace,
at_container_count,
ref path,
) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.add_workspace_layout_custom_rule(
monitor_idx,
workspace_idx,
at_container_count,
path,
)?;
}
}
SocketMessage::ClearNamedWorkspaceLayoutRules(ref workspace) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(workspace)
{
self.clear_workspace_layout_rules(monitor_idx, workspace_idx)?;
}
}
SocketMessage::CycleFocusWorkspace(direction) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.focus_workspace(workspace_idx)?;
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::FocusLastWorkspace => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
let idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_idx();
if let Some(monitor) = self.focused_monitor_mut() {
if let Some(last_focused_workspace) = monitor.last_focused_workspace() {
self.focus_workspace(last_focused_workspace)?;
}
}
self.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?
.set_last_focused_workspace(Option::from(idx));
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
self.focus_workspace(workspace_idx)?;
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::FocusWorkspaceNumbers(workspace_idx) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
let focused_monitor_idx = self.focused_monitor_idx();
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
if i != focused_monitor_idx {
monitor.focus_workspace(workspace_idx)?;
monitor.load_focused_workspace(false)?;
}
}
self.focus_workspace(workspace_idx)?;
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
}
SocketMessage::FocusNamedWorkspace(ref name) => {
if let Some((monitor_idx, workspace_idx)) =
self.monitor_workspace_index_by_name(name)
{
self.focus_monitor(monitor_idx)?;
self.focus_workspace(workspace_idx)?;
}
if BORDER_ENABLED.load(Ordering::SeqCst) {
self.show_border()?;
};
}
SocketMessage::Stop => {
tracing::info!(
"received stop command, restoring all hidden windows and terminating process"
);
self.restore_all_windows()?;
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
std::process::exit(0)
}
SocketMessage::MonitorIndexPreference(index_preference, left, top, right, bottom) => {
let mut monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();
monitor_index_preferences.insert(
index_preference,
Rect {
left,
top,
right,
bottom,
},
);
}
SocketMessage::DisplayIndexPreference(index_preference, ref display) => {
let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.insert(index_preference, display.clone());
}
SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {
self.ensure_workspaces_for_monitor(monitor_idx, workspace_count)?;
}
SocketMessage::EnsureNamedWorkspaces(monitor_idx, ref names) => {
self.ensure_named_workspaces_for_monitor(monitor_idx, names)?;
}
SocketMessage::NewWorkspace => {
self.new_workspace()?;
}
SocketMessage::WorkspaceName(monitor_idx, workspace_idx, ref name) => {
self.set_workspace_name(monitor_idx, workspace_idx, name.to_string())?;
}
SocketMessage::State => {
let state = match serde_json::to_string_pretty(&window_manager::State::from(&*self))
{
Ok(state) => state,
Err(error) => error.to_string(),
};
tracing::info!("replying to state");
reply.write_all(state.as_bytes())?;
tracing::info!("replying to state done");
}
SocketMessage::VisibleWindows => {
let mut monitor_visible_windows = HashMap::new();
for (index, monitor) in self.monitors().iter().enumerate() {
if let Some(ws) = monitor.focused_workspace() {
monitor_visible_windows.insert(
monitor
.device_id()
.clone()
.unwrap_or_else(|| format!("{index}")),
ws.visible_window_details().clone(),
);
}
}
let visible_windows_state =
match serde_json::to_string_pretty(&monitor_visible_windows) {
Ok(state) => state,
Err(error) => error.to_string(),
};
reply.write_all(visible_windows_state.as_bytes())?;
}
SocketMessage::Query(query) => {
let response = match query {
StateQuery::FocusedMonitorIndex => self.focused_monitor_idx(),
StateQuery::FocusedWorkspaceIndex => self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_idx(),
StateQuery::FocusedContainerIndex => {
self.focused_workspace()?.focused_container_idx()
}
StateQuery::FocusedWindowIndex => {
self.focused_container()?.focused_window_idx()
}
}
.to_string();
reply.write_all(response.as_bytes())?;
}
SocketMessage::ResizeWindowEdge(direction, sizing) => {
self.resize_window(direction, sizing, self.resize_delta, true)?;
}
SocketMessage::ResizeWindowAxis(axis, sizing) => {
// If the user has a custom layout, allow for the resizing of the primary column
// with this signal
let workspace = self.focused_workspace_mut()?;
let container_len = workspace.containers().len();
let no_layout_rules = workspace.layout_rules().is_empty();
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if matches!(axis, Axis::Horizontal) {
#[allow(clippy::cast_precision_loss)]
let percentage = custom
.primary_width_percentage()
.unwrap_or(100.0 / (custom.len() as f32));
if no_layout_rules {
match sizing {
Sizing::Increase => {
custom.set_primary_width_percentage(percentage + 5.0);
}
Sizing::Decrease => {
custom.set_primary_width_percentage(percentage - 5.0);
}
}
} else {
for rule in workspace.layout_rules_mut() {
if container_len >= rule.0 {
if let Layout::Custom(ref mut custom) = rule.1 {
match sizing {
Sizing::Increase => {
custom
.set_primary_width_percentage(percentage + 5.0);
}
Sizing::Decrease => {
custom
.set_primary_width_percentage(percentage - 5.0);
}
}
}
}
}
}
}
// Otherwise proceed with the resizing logic for individual window containers in the
// assumed BSP layout
} else {
match axis {
Axis::Horizontal => {
self.resize_window(
OperationDirection::Left,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Right,
sizing,
self.resize_delta,
false,
)?;
}
Axis::Vertical => {
self.resize_window(
OperationDirection::Up,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Down,
sizing,
self.resize_delta,
false,
)?;
}
Axis::HorizontalAndVertical => {
self.resize_window(
OperationDirection::Left,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Right,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Up,
sizing,
self.resize_delta,
false,
)?;
self.resize_window(
OperationDirection::Down,
sizing,
self.resize_delta,
false,
)?;
}
}
}
self.update_focused_workspace(false)?;
}
SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
if !CUSTOM_FFM.load(Ordering::SeqCst) {
tracing::warn!(
"komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be enabled; defaulting to windows implementation"
);
implementation = FocusFollowsMouseImplementation::Windows;
}
match implementation {
FocusFollowsMouseImplementation::Komorebi => {
if WindowsApi::focus_follows_mouse()? {
tracing::warn!(
"the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled"
);
} else if enable {
self.focus_follows_mouse = Option::from(implementation);
} else {
self.focus_follows_mouse = None;
self.has_pending_raise_op = false;
}
}
FocusFollowsMouseImplementation::Windows => {
if matches!(
self.focus_follows_mouse,
Some(FocusFollowsMouseImplementation::Komorebi)
) {
tracing::warn!(
"the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled"
);
} else if enable {
WindowsApi::enable_focus_follows_mouse()?;
self.focus_follows_mouse =
Option::from(FocusFollowsMouseImplementation::Windows);
} else {
WindowsApi::disable_focus_follows_mouse()?;
self.focus_follows_mouse = None;
}
}
}
}
SocketMessage::ToggleFocusFollowsMouse(mut implementation) => {
if !CUSTOM_FFM.load(Ordering::SeqCst) {
tracing::warn!(
"komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be toggled; defaulting to windows implementation"
);
implementation = FocusFollowsMouseImplementation::Windows;
}
match implementation {
FocusFollowsMouseImplementation::Komorebi => {
if WindowsApi::focus_follows_mouse()? {
tracing::warn!(
"the komorebi implementation of focus follows mouse cannot be toggled while the windows implementation is enabled"
);
} else {
match self.focus_follows_mouse {
None => {
self.focus_follows_mouse = Option::from(implementation);
self.has_pending_raise_op = false;
}
Some(FocusFollowsMouseImplementation::Komorebi) => {
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Windows) => {
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
}
}
}
}
FocusFollowsMouseImplementation::Windows => {
if matches!(
self.focus_follows_mouse,
Some(FocusFollowsMouseImplementation::Komorebi)
) {
tracing::warn!(
"the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled"
);
} else {
match self.focus_follows_mouse {
None => {
WindowsApi::enable_focus_follows_mouse()?;
self.focus_follows_mouse = Option::from(implementation);
}
Some(FocusFollowsMouseImplementation::Windows) => {
WindowsApi::disable_focus_follows_mouse()?;
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Komorebi) => {
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
}
}
}
}
}
}
SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
}
SocketMessage::ReloadStaticConfiguration(ref pathbuf) => {
self.reload_static_configuration(pathbuf)?;
}
SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
self.update_focused_workspace(false)?;
}
}
SocketMessage::WatchConfiguration(enable) => {
self.watch_configuration(enable)?;
}
SocketMessage::IdentifyBorderOverflowApplication(identifier, ref id) => {
let mut identifiers = BORDER_OVERFLOW_IDENTIFIERS.lock();
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
}
SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {
let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
}
SocketMessage::IdentifyTrayApplication(identifier, ref id) => {
let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
}
SocketMessage::IdentifyLayeredApplication(identifier, ref id) => {
let mut identifiers = LAYERED_WHITELIST.lock();
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
}
SocketMessage::ManageFocusedWindow => {
self.manage_focused_window()?;
}
SocketMessage::UnmanageFocusedWindow => {
self.unmanage_focused_window()?;
}
SocketMessage::InvisibleBorders(_rect) => {}
SocketMessage::WorkAreaOffset(rect) => {
self.work_area_offset = Option::from(rect);
self.retile_all(false)?;
}
SocketMessage::MonitorWorkAreaOffset(monitor_idx, rect) => {
if let Some(monitor) = self.monitors_mut().get_mut(monitor_idx) {
monitor.set_work_area_offset(Option::from(rect));
self.retile_all(false)?;
}
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
let resize = workspace.resize_dimensions();
let quicksave_json = std::env::temp_dir().join("komorebi.quicksave.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(quicksave_json)?;
serde_json::to_writer_pretty(&file, &resize)?;
}
SocketMessage::QuickLoad => {
let workspace = self.focused_workspace_mut()?;
let quicksave_json = std::env::temp_dir().join("komorebi.quicksave.json");
let file = File::open(&quicksave_json)
.map_err(|_| anyhow!("no quicksave found at {}", quicksave_json.display()))?;
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::Save(ref path) => {
let workspace = self.focused_workspace_mut()?;
let resize = workspace.resize_dimensions();
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
serde_json::to_writer_pretty(&file, &resize)?;
}
SocketMessage::Load(ref path) => {
let workspace = self.focused_workspace_mut()?;
let file =
File::open(path).map_err(|_| anyhow!("no file found at {}", path.display()))?;
let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;
workspace.set_resize_dimensions(resize);
self.update_focused_workspace(false)?;
}
SocketMessage::AddSubscriberSocket(ref socket) => {
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
let socket_path = DATA_DIR.join(socket);
sockets.insert(socket.clone(), socket_path);
}
SocketMessage::RemoveSubscriberSocket(ref socket) => {
let mut sockets = SUBSCRIPTION_SOCKETS.lock();
sockets.remove(socket);
}
SocketMessage::AddSubscriberPipe(ref subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
let pipe_path = format!(r"\\.\pipe\{subscriber}");
let pipe = connect(&pipe_path).map_err(|_| {
anyhow!("the named pipe '{}' has not yet been created; please create it before running this command", pipe_path)
})?;
pipes.insert(subscriber.clone(), pipe);
}
SocketMessage::RemoveSubscriberPipe(ref subscriber) => {
let mut pipes = SUBSCRIPTION_PIPES.lock();
pipes.remove(subscriber);
}
SocketMessage::MouseFollowsFocus(enable) => {
self.mouse_follows_focus = enable;
}
SocketMessage::ToggleMouseFollowsFocus => {
self.mouse_follows_focus = !self.mouse_follows_focus;
}
SocketMessage::ResizeDelta(delta) => {
self.resize_delta = delta;
}
SocketMessage::ToggleWindowContainerBehaviour => {
match self.window_container_behaviour {
WindowContainerBehaviour::Create => {
self.window_container_behaviour = WindowContainerBehaviour::Append;
}
WindowContainerBehaviour::Append => {
self.window_container_behaviour = WindowContainerBehaviour::Create;
}
}
}
SocketMessage::WindowHidingBehaviour(behaviour) => {
let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();
*hiding_behaviour = behaviour;
}
SocketMessage::ToggleCrossMonitorMoveBehaviour => {
match self.cross_monitor_move_behaviour {
MoveBehaviour::Swap => {
self.cross_monitor_move_behaviour = MoveBehaviour::Insert;
}
MoveBehaviour::Insert => {
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
}
}
}
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
self.cross_monitor_move_behaviour = behaviour;
}
SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {
self.unmanaged_window_operation_behaviour = behaviour;
}
SocketMessage::ActiveWindowBorder(enable) => {
if enable {
if BORDER_HWND.load(Ordering::SeqCst) == 0 {
Border::create("komorebi-border-window")?;
}
BORDER_ENABLED.store(true, Ordering::SeqCst);
self.show_border()?;
} else {
BORDER_ENABLED.store(false, Ordering::SeqCst);
self.hide_border()?;
}
}
SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => {
match kind {
WindowKind::Single => {
BORDER_COLOUR_SINGLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
BORDER_COLOUR_CURRENT.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Stack => {
BORDER_COLOUR_STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Monocle => {
BORDER_COLOUR_MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
}
WindowsApi::invalidate_border_rect()?;
}
SocketMessage::ActiveWindowBorderWidth(width) => {
BORDER_WIDTH.store(width, Ordering::SeqCst);
WindowsApi::invalidate_border_rect()?;
}
SocketMessage::ActiveWindowBorderOffset(offset) => {
BORDER_OFFSET.store(offset, Ordering::SeqCst);
WindowsApi::invalidate_border_rect()?;
}
SocketMessage::AltFocusHack(_) => {
tracing::info!("this action is deprecated");
}
SocketMessage::ApplicationSpecificConfigurationSchema => {
let asc = schema_for!(Vec<ApplicationConfiguration>);
let schema = serde_json::to_string_pretty(&asc)?;
reply.write_all(schema.as_bytes())?;
}
SocketMessage::NotificationSchema => {
let notification = schema_for!(Notification);
let schema = serde_json::to_string_pretty(&notification)?;
reply.write_all(schema.as_bytes())?;
}
SocketMessage::SocketSchema => {
let socket_message = schema_for!(SocketMessage);
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
}
SocketMessage::StaticConfigSchema => {
let settings = SchemaSettings::default().with(|s| {
s.option_nullable = false;
s.option_add_null_type = false;
s.inline_subschemas = true;
});
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
}
SocketMessage::GenerateStaticConfig => {
let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;
reply.write_all(config.as_bytes())?;
}
SocketMessage::RemoveTitleBar(_, ref id) => {
let mut identifiers = NO_TITLEBAR.lock();
if !identifiers.contains(id) {
identifiers.push(id.clone());
}
}
SocketMessage::ToggleTitleBars => {
let current = REMOVE_TITLEBARS.load(Ordering::SeqCst);
REMOVE_TITLEBARS.store(!current, Ordering::SeqCst);
self.update_focused_workspace(false)?;
}
};
match message {
SocketMessage::ToggleMonocle => {
let current = BORDER_COLOUR_CURRENT.load(Ordering::SeqCst);
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
if monocle != 0 {
if current == monocle {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
} else {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
}
}
}
SocketMessage::StackWindow(_) => {
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
if stack != 0 {
BORDER_COLOUR_CURRENT
.store(BORDER_COLOUR_STACK.load(Ordering::SeqCst), Ordering::SeqCst);
}
}
SocketMessage::UnstackWindow => {
BORDER_COLOUR_CURRENT.store(
BORDER_COLOUR_SINGLE.load(Ordering::SeqCst),
Ordering::SeqCst,
);
}
_ => {}
}
match message {
SocketMessage::ChangeLayout(_)
| SocketMessage::CycleLayout(_)
| SocketMessage::ChangeLayoutCustom(_)
| SocketMessage::FlipLayout(_)
| SocketMessage::ManageFocusedWindow
| SocketMessage::MoveWorkspaceToMonitorNumber(_)
| SocketMessage::MoveContainerToMonitorNumber(_)
| SocketMessage::MoveContainerToWorkspaceNumber(_)
| SocketMessage::ResizeWindowEdge(_, _)
| SocketMessage::ResizeWindowAxis(_, _)
| SocketMessage::ToggleFloat
| SocketMessage::ToggleMonocle
| SocketMessage::ToggleMaximize
| SocketMessage::Promote
| SocketMessage::PromoteFocus
| SocketMessage::StackWindow(_)
| SocketMessage::UnstackWindow
| SocketMessage::Retile
// Adding this one so that changes can be seen instantly after
// modifying the active window border offset
| SocketMessage::ActiveWindowBorderOffset(_)
// Adding this one because sometimes EVENT_SYSTEM_FOREGROUND isn't
// getting sent on FocusWindow, meaning the border won't be set
// when processing events
| SocketMessage::FocusWindow(_)
| SocketMessage::InvisibleBorders(_)
| SocketMessage::WorkAreaOffset(_)
| SocketMessage::CycleMoveWindow(_)
| SocketMessage::MoveWindow(_)
| SocketMessage::CycleFocusMonitor(_)
| SocketMessage::CycleFocusWorkspace(_)
| SocketMessage::FocusMonitorNumber(_)
| SocketMessage::FocusMonitorWorkspaceNumber(_, _)
| SocketMessage::FocusWorkspaceNumber(_) => {
let foreground = WindowsApi::foreground_window()?;
let foreground_window = Window { hwnd: foreground };
let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst);
if monocle != 0 && self.focused_workspace()?.monocle_container().is_some() {
BORDER_COLOUR_CURRENT.store(
monocle,
Ordering::SeqCst,
);
}
// it is not acceptable to fail here; we need to be able to send the event to
// subscribers
if self.focused_container().is_ok() {
let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst);
if stack != 0 && self.focused_container()?.windows().len() > 1 {
BORDER_COLOUR_CURRENT
.store(stack, Ordering::SeqCst);
}
}
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
border.set_position(foreground_window, false)?;
}
SocketMessage::TogglePause => {
let is_paused = self.is_paused;
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
if is_paused {
border.hide()?;
} else {
let focused = self.focused_window()?;
border.set_position(*focused, true)?;
focused.focus(false)?;
}
}
SocketMessage::ToggleTiling | SocketMessage::WorkspaceTiling(..) => {
let tiling_enabled = *self.focused_workspace_mut()?.tile();
let border = Border::from(BORDER_HWND.load(Ordering::SeqCst));
if tiling_enabled {
let focused = self.focused_window()?;
border.set_position(*focused, true)?;
focused.focus(false)?;
} else {
border.hide()?;
}
}
_ => {}
};
tracing::info!("processed");
Ok(())
}
#[tracing::instrument(skip(self))]
fn handle_initial_workspace_rules(
&mut self,
id: &String,
monitor_idx: usize,
workspace_idx: usize,
) -> Result<()> {
self.handle_workspace_rules(id, monitor_idx, workspace_idx, true)?;
Ok(())
}
#[tracing::instrument(skip(self))]
fn handle_definitive_workspace_rules(
&mut self,
id: &String,
monitor_idx: usize,
workspace_idx: usize,
) -> Result<()> {
self.handle_workspace_rules(id, monitor_idx, workspace_idx, false)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn handle_workspace_rules(
&mut self,
id: &String,
monitor_idx: usize,
workspace_idx: usize,
initial_workspace_rule: bool,
) -> Result<()> {
{
let mut workspace_rules = WORKSPACE_RULES.lock();
workspace_rules.insert(
id.to_string(),
(monitor_idx, workspace_idx, initial_workspace_rule),
);
}
self.enforce_workspace_rules()?;
Ok(())
}
}
pub fn read_commands_uds(wm: &Arc<Mutex<WindowManager>>, mut stream: UnixStream) -> Result<()> {
let reader = BufReader::new(stream.try_clone()?);
// TODO(raggi): while this processes more than one command, if there are
// replies there is no clearly defined protocol for framing yet - it's
// perhaps whole-json objects for now, but termination is signalled by
// socket shutdown.
for line in reader.lines() {
let message = SocketMessage::from_str(&line?)?;
let mut wm = wm.lock();
if wm.is_paused {
return match message {
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
Ok(wm.process_command(message, &mut stream)?)
}
_ => {
tracing::trace!("ignoring while paused");
Ok(())
}
};
}
wm.process_command(message.clone(), &mut stream)?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: wm.as_ref().into(),
})?)?;
}
Ok(())
}
pub fn read_commands_tcp(
wm: &Arc<Mutex<WindowManager>>,
stream: &mut TcpStream,
addr: &str,
) -> Result<()> {
let mut reader = BufReader::new(stream.try_clone()?);
loop {
let mut buf = vec![0; 1024];
match reader.read(&mut buf) {
Err(..) => {
tracing::warn!("removing disconnected tcp client: {addr}");
let mut connections = TCP_CONNECTIONS.lock();
connections.remove(addr);
break;
}
Ok(size) => {
let Ok(message) = SocketMessage::from_str(&String::from_utf8_lossy(&buf[..size]))
else {
tracing::warn!("client sent an invalid message, disconnecting: {addr}");
let mut connections = TCP_CONNECTIONS.lock();
connections.remove(addr);
break;
};
let mut wm = wm.lock();
if wm.is_paused {
return match message {
SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
Ok(wm.process_command(message, stream)?)
}
_ => {
tracing::trace!("ignoring while paused");
Ok(())
}
};
}
wm.process_command(message.clone(), &mut *stream)?;
notify_subscribers(&serde_json::to_string(&Notification {
event: NotificationEvent::Socket(message.clone()),
state: wm.as_ref().into(),
})?)?;
}
}
}
Ok(())
}