diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 35d3bc68..9fbd7072 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -60,6 +60,8 @@ pub enum SocketMessage { FocusStackWindow(usize), StackAll, UnstackAll, + FocusExe(Option, Option), + DisplayMonitorWorkspaceNumber(usize, usize), ResizeWindowEdge(OperationDirection, Sizing), ResizeWindowAxis(Axis, Sizing), MoveContainerToLastWorkspace, @@ -92,6 +94,7 @@ pub enum SocketMessage { ToggleFloat, ToggleMonocle, ToggleMaximize, + ToggleAlwaysOnTop, ToggleWindowContainerBehaviour, ToggleFloatOverride, WindowHidingBehaviour(HidingBehaviour), diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 311b8475..dd9ae4d4 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -232,6 +232,14 @@ impl WindowManager { self.focus_container_in_direction(direction)?; self.promote_container_to_front()? } + SocketMessage::DisplayMonitorWorkspaceNumber(monitor_idx, workspace_idx) => { + self.send_always_on_top( + Option::from(monitor_idx), + Option::from(workspace_idx), + None, + )?; + self.display_monitor_workspace(monitor_idx, workspace_idx)?; + } SocketMessage::EagerFocus(ref exe) => { let focused_monitor_idx = self.focused_monitor_idx(); let focused_workspace_idx = self.focused_workspace_idx()?; @@ -304,6 +312,9 @@ impl WindowManager { } } } + SocketMessage::FocusExe(ref exe, hwnd) => { + self.focus_window_from_exe(exe, hwnd)?; + } SocketMessage::MoveWindow(direction) => { let focused_workspace = self.focused_workspace()?; match focused_workspace.layer() { @@ -399,6 +410,7 @@ impl WindowManager { SocketMessage::ToggleFloat => self.toggle_float()?, SocketMessage::ToggleMonocle => self.toggle_monocle()?, SocketMessage::ToggleMaximize => self.toggle_maximize()?, + SocketMessage::ToggleAlwaysOnTop => self.toggle_always_on_top()?, SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => { self.set_container_padding(monitor_idx, workspace_idx, size)?; } @@ -704,6 +716,7 @@ impl WindowManager { } SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => { self.move_container_to_workspace(workspace_idx, true, None)?; + self.send_always_on_top(None, Some(workspace_idx), Some(true))?; } SocketMessage::CycleMoveContainerToWorkspace(direction) => { let focused_monitor = self @@ -720,8 +733,10 @@ impl WindowManager { ); self.move_container_to_workspace(workspace_idx, true, None)?; + self.send_always_on_top(None, Some(workspace_idx), Some(true))?; } SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => { + self.send_always_on_top(Some(monitor_idx), None, Some(true))?; let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor(monitor_idx, None, true, direction)?; } @@ -735,11 +750,14 @@ impl WindowManager { .ok_or_else(|| anyhow!("there must be at least one monitor"))?, ); + self.send_always_on_top(Some(monitor_idx), None, Some(true))?; + let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor(monitor_idx, None, true, direction)?; } SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => { self.move_container_to_workspace(workspace_idx, false, None)?; + self.send_always_on_top(None, Some(workspace_idx), Some(false))?; } SocketMessage::CycleSendContainerToWorkspace(direction) => { let focused_monitor = self @@ -756,8 +774,10 @@ impl WindowManager { ); self.move_container_to_workspace(workspace_idx, false, None)?; + self.send_always_on_top(None, Some(workspace_idx), Some(false))?; } SocketMessage::SendContainerToMonitorNumber(monitor_idx) => { + self.send_always_on_top(Some(monitor_idx), None, Some(false))?; let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor(monitor_idx, None, false, direction)?; } @@ -768,10 +788,13 @@ impl WindowManager { .ok_or_else(|| anyhow!("there must be at least one monitor"))?, ); + self.send_always_on_top(Some(monitor_idx), None, Some(false))?; + let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor(monitor_idx, None, false, direction)?; } SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => { + self.send_always_on_top(Some(monitor_idx), Some(workspace_idx), Some(false))?; let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor( monitor_idx, @@ -781,6 +804,7 @@ impl WindowManager { )?; } SocketMessage::MoveContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => { + self.send_always_on_top(Some(monitor_idx), Some(workspace_idx), Some(true))?; let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor( monitor_idx, @@ -793,6 +817,7 @@ impl WindowManager { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(workspace) { + self.send_always_on_top(Some(monitor_idx), Some(workspace_idx), Some(false))?; let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor( monitor_idx, @@ -806,6 +831,7 @@ impl WindowManager { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(workspace) { + self.send_always_on_top(Some(monitor_idx), Some(workspace_idx), Some(true))?; let direction = self.direction_from_monitor_idx(monitor_idx); self.move_container_to_monitor( monitor_idx, @@ -997,6 +1023,8 @@ impl WindowManager { .ok_or_else(|| anyhow!("there must be at least one workspace"))?, ); + self.send_always_on_top(None, Option::from(workspace_idx), None)?; + self.focus_workspace(workspace_idx)?; } SocketMessage::CycleFocusEmptyWorkspace(direction) => { @@ -1115,6 +1143,7 @@ impl WindowManager { if let Some(monitor) = self.focused_monitor_mut() { if let Some(last_focused_workspace) = monitor.last_focused_workspace() { + self.send_always_on_top(None, Option::from(last_focused_workspace), None)?; self.focus_workspace(last_focused_workspace)?; } } @@ -1140,6 +1169,7 @@ impl WindowManager { } if self.focused_workspace_idx().unwrap_or_default() != workspace_idx { + self.send_always_on_top(None, Option::from(workspace_idx), None)?; self.focus_workspace(workspace_idx)?; } } @@ -1161,6 +1191,16 @@ impl WindowManager { let focused_monitor_idx = self.focused_monitor_idx(); + for i in 0..self.monitors.elements().len() { + if i != focused_monitor_idx { + self.send_always_on_top( + Option::from(i), + Option::from(workspace_idx), + None, + )?; + } + } + for (i, monitor) in self.monitors_mut().iter_mut().enumerate() { if i != focused_monitor_idx { monitor.focus_workspace(workspace_idx)?; @@ -1168,6 +1208,11 @@ impl WindowManager { } } + self.send_always_on_top( + Option::from(focused_monitor_idx), + Some(workspace_idx), + None, + )?; self.focus_workspace(workspace_idx)?; } SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => { @@ -1177,6 +1222,7 @@ impl WindowManager { let focused_pair = (focused_monitor_idx, focused_workspace_idx); if focused_pair != (monitor_idx, workspace_idx) { + self.send_always_on_top(Option::from(monitor_idx), Some(workspace_idx), None)?; self.focus_monitor(monitor_idx)?; self.focus_workspace(workspace_idx)?; } @@ -1185,6 +1231,7 @@ impl WindowManager { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(name) { + self.send_always_on_top(Option::from(monitor_idx), Some(workspace_idx), None)?; self.focus_monitor(monitor_idx)?; self.focus_workspace(workspace_idx)?; } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index aa90d692..996f68a6 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -187,6 +187,13 @@ impl WindowManager { already_moved_window_handles.remove(&window.hwnd); } + + if let Some(aot) = self.always_on_top.as_mut() { + if aot.contains(&window.hwnd) { + let idx = aot.iter().position(|x| *x == window.hwnd).unwrap(); + aot.remove(idx); + } + } } WindowManagerEvent::Minimize(_, window) => { let mut hide = false; diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index d2331739..0df8dca3 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -1167,6 +1167,7 @@ impl StaticConfig { already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())), uncloack_to_ignore: 0, known_hwnds: HashMap::new(), + always_on_top: None, }; match value.focus_follows_mouse { diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 678fa62e..cc025e4e 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -124,6 +124,7 @@ pub struct WindowManager { pub uncloack_to_ignore: usize, /// Maps each known window hwnd to the (monitor, workspace) index pair managing it pub known_hwnds: HashMap, + pub always_on_top: Option>, } #[allow(clippy::struct_excessive_bools)] @@ -444,6 +445,7 @@ impl WindowManager { already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())), uncloack_to_ignore: 0, known_hwnds: HashMap::new(), + always_on_top: None, }) } @@ -1792,6 +1794,302 @@ impl WindowManager { self.update_focused_workspace(mouse_follows_focus, true) } + #[tracing::instrument(skip(self))] + pub fn focus_window_from_exe( + &mut self, + exe: &Option, + hwnd: Option, + ) -> Result<()> { + let mut monitor_idx = 0; + let mut workspace_idx = 0; + let mut hwnd_from_exe: isize = 0; + let mut bool = false; + let mouse_follows_focus = self.mouse_follows_focus; + let offset = self.work_area_offset; + + 'outer: for (i, m) in self.monitors.elements().iter().enumerate() { + for (j, w) in m.workspaces().iter().enumerate() { + if let Some(hwnd) = hwnd { + if w.contains_managed_window(hwnd) { + monitor_idx = i; + workspace_idx = j; + hwnd_from_exe = hwnd; + bool = true; + break 'outer; + } + } + if let Some(exe) = exe { + if let Some(hwndexe) = w.hwnd_from_exe(&exe) { + monitor_idx = i; + workspace_idx = j; + hwnd_from_exe = hwndexe; + bool = true; + if !hwnd.is_some() { + break 'outer; + } + } + } + } + } + + if bool { + if self.focused_monitor_idx() != monitor_idx { + self.focus_monitor(monitor_idx)?; + } + if self.focused_workspace_idx()? != workspace_idx { + self.focused_monitor_mut() + .ok_or_else(|| anyhow!("there is no monitor"))? + .focus_workspace(workspace_idx)?; + } + let target_monitor = self + .focused_monitor_mut() + .ok_or_else(|| anyhow!("there is no monitor"))?; + target_monitor + .workspaces_mut() + .get_mut(workspace_idx) + .ok_or_else(|| anyhow!("there is no workspace"))? + .focus_container_by_window(hwnd_from_exe)?; + target_monitor.load_focused_workspace(mouse_follows_focus)?; + target_monitor.update_focused_workspace(offset)?; + self.update_focused_workspace(self.mouse_follows_focus, true)?; + } else { + Err(anyhow!("there is no window with that exe"))? + } + Ok(()) + } + + #[tracing::instrument(skip(self))] + pub fn display_monitor_workspace( + &mut self, + monitor_idx: usize, + workspace_idx: usize, + ) -> Result<()> { + let monitor = self + .monitors_mut() + .get_mut(monitor_idx) + .ok_or_else(|| anyhow!("There is no monitor"))?; + + monitor.focus_workspace(workspace_idx)?; + monitor.load_focused_workspace(false)?; + + let focused_workspace_idx = self.focused_workspace_idx()?; + self.focus_workspace(focused_workspace_idx)?; + + Ok(()) + } + + #[tracing::instrument(skip(self))] + pub fn send_always_on_top( + &mut self, + monitor_idx: Option, + workspace_idx: Option, + follows: Option, + ) -> Result<()> { + let mut contains_always_on_top = false; + let last_window = if let Ok(window) = self.focused_window() { + window.hwnd + } else { + if self.focused_workspace()?.floating_windows().len() > 0 { + self.focused_workspace()?.floating_windows()[0].hwnd + } else { + return Ok(()); + } + }; + let aot = self.always_on_top.clone(); + let mut windows_vec = vec![]; + + let flw_contains = if let Some(aot) = self.always_on_top.as_ref() { + if aot.len() == 0 { + return Ok(()); + } + if let Some(flw) = follows { + if let Ok(fc) = self.focused_container() { + let contains = fc.windows().iter().any(|w| aot.contains(&w.hwnd)); + if flw && contains { + windows_vec = fc.windows().into_iter().map(|w| w.hwnd).collect::>(); + true + } else if !flw && !contains { + return Ok(()); + } else if !flw && contains { + Err(anyhow!("cannot send an always on top window"))? + } else { + false + } + } else { + false + } + } else { + false + } + } else { + return Ok(()); + }; + self.check_aot_windows()?; + + aot.ok_or_else(|| anyhow!("there is no always on Top windows"))? + .iter() + .filter(|&&window| { + let mut is_window = false; + if flw_contains { + if windows_vec.contains(&&window) { + is_window = true; + } + } + !is_window + }) + .try_for_each(|&window| { + if let Some(monitor_idx) = monitor_idx { + if self + .monitors() + .get(monitor_idx) + .ok_or_else(|| anyhow!("there is no monitor at this index"))? + .focused_workspace() + .unwrap() + .contains_managed_window(window) + { + let idx = self + .monitors() + .get(monitor_idx) + .ok_or_else(|| anyhow!("there is no monitor at this index"))? + .focused_workspace() + .unwrap() + .container_idx_for_window(window) + .ok_or_else(|| anyhow!("there is no container at this index"))?; + + let con = self + .monitors_mut() + .get_mut(monitor_idx) + .ok_or_else(|| anyhow!("there is no monitor at this index"))? + .focused_workspace_mut() + .unwrap() + .remove_container_by_idx(idx) + .unwrap(); + + self.monitors_mut() + .get_mut(monitor_idx) + .ok_or_else(|| anyhow!("there is no monitor at this index"))? + .add_container(con, workspace_idx)?; + } + } else { + if self + .focused_workspace() + .unwrap() + .contains_managed_window(window) + { + let idx = self + .focused_workspace() + .unwrap() + .container_idx_for_window(window); + let con = self + .focused_workspace_mut() + .unwrap() + .remove_container_by_idx(idx.unwrap()) + .ok_or_else(|| anyhow!("there is no container at this index"))?; + + self.focused_monitor_mut() + .ok_or_else(|| anyhow!("there is no focused monitor"))? + .add_container(con, workspace_idx)?; + + contains_always_on_top = true; + + //self.update_focused_workspace(mff, false).unwrap() + } else if self + .focused_workspace()? + .floating_windows() + .iter() + .any(|w| w.hwnd == window) + { + let idx = self + .focused_workspace_mut()? + .floating_windows() + .iter() + .position(|x| x.hwnd == window) + .unwrap(); + let float_window = self + .focused_workspace_mut()? + .floating_windows_mut() + .remove(idx); + self.focused_monitor_mut() + .ok_or_else(|| anyhow!("there is no focused workspace"))? + .workspaces_mut() + .get_mut(workspace_idx.unwrap()) + .ok_or_else(|| anyhow!("there is no workspace at this index"))? + .floating_windows_mut() + .push_back(float_window.unwrap()); + } + } + + Ok::<(), color_eyre::eyre::Error>(()) + })?; + + if contains_always_on_top + && self.focused_workspace()?.containers().len() != 0 + && self + .focused_workspace()? + .contains_managed_window(last_window) + { + self.focused_workspace_mut()? + .focus_container_by_window(last_window)?; + } + Ok(()) + } + + pub fn check_aot_windows(&mut self) -> Result<()> { + let mut not_contains = vec![]; + if self.always_on_top.is_none() { + return Ok(()); + } + for (i, hwnd) in self + .always_on_top + .as_ref() + .ok_or_else(|| anyhow!("there is no always on top windows"))? + .iter() + .enumerate() + { + let mut not_contains_bool = true; + 'monitor: for monitor in self.monitors.elements().iter() { + for workspace in monitor.workspaces().iter() { + if workspace.contains_managed_window(*hwnd) { + not_contains_bool = false; + break 'monitor; + } + } + } + + if not_contains_bool { + not_contains.push(i); + } + } + not_contains.iter().for_each(|&i| { + self.always_on_top.as_mut().unwrap().remove(i); + }); + Ok(()) + } + + pub fn toggle_always_on_top(&mut self) -> Result<()> { + self.check_aot_windows()?; + + let focused_con = self.focused_container().unwrap().clone(); + + focused_con.windows().iter().try_for_each(|window| { + if let Some(always_on_top) = self.always_on_top.as_mut() { + if always_on_top.contains(&window.hwnd) { + let idx = always_on_top + .iter() + .position(|x| *x == window.hwnd) + .unwrap(); + always_on_top.remove(idx); + } else { + always_on_top.push(window.hwnd.clone()) + } + } else { + self.always_on_top = Some(vec![window.hwnd.clone()]) + } + Ok::<(), color_eyre::eyre::Error>(()) + })?; + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn move_container_to_monitor( &mut self, diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 772e272c..b07609cc 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -829,6 +829,23 @@ struct Kill { masir: bool, } +#[derive(Parser)] +struct Exe { + /// executable name + #[clap(short, long)] + exe: Option, + /// hwnd handle of the window + #[clap(long)] + hwnd: Option, +} + +#[derive(Parser)] +struct DisplayMonitorWorkspace { + /// Monitor index (zero-indexed) + monitor: usize, + /// Workspace index on the specified monitor (zero-indexed) + workspace: usize, +} #[derive(Parser)] struct SaveResize { /// File to which the resize layout dimensions should be saved @@ -1022,9 +1039,15 @@ enum SubCommand { #[clap(arg_required_else_help = true)] #[clap(alias = "load")] LoadResize(LoadResize), + /// Display the workspace index at monitor index + #[clap(arg_required_else_help = true)] + DisplayMonitorWorkspace(DisplayMonitorWorkspace), /// Change focus to the window in the specified direction #[clap(arg_required_else_help = true)] Focus(Focus), + /// Change focus to the window with the specified executable + #[clap(arg_required_else_help = true)] + FocusExe(Exe), /// Move the focused window in the specified direction #[clap(arg_required_else_help = true)] Move(Move), @@ -1289,6 +1312,8 @@ enum SubCommand { ToggleMaximize, /// Toggle a lock for the focused container, ensuring it will not be displaced by any new windows ToggleLock, + /// Toggle Always on top mode for the focused window + ToggleAlwaysOnTop, /// Restore all hidden windows (debugging command) RestoreWindows, /// Force komorebi to manage the focused window @@ -1764,9 +1789,18 @@ fn main() -> Result<()> { println!("{line}"); } } + SubCommand::DisplayMonitorWorkspace(arg) => { + send_message(&SocketMessage::DisplayMonitorWorkspaceNumber( + arg.monitor, + arg.workspace, + ))?; + } SubCommand::Focus(arg) => { send_message(&SocketMessage::FocusWindow(arg.operation_direction))?; } + SubCommand::FocusExe(arg) => { + send_message(&SocketMessage::FocusExe(arg.exe, arg.hwnd))?; + } SubCommand::ForceFocus => { send_message(&SocketMessage::ForceFocus)?; } @@ -1962,6 +1996,9 @@ fn main() -> Result<()> { SubCommand::ToggleLock => { send_message(&SocketMessage::ToggleLock)?; } + SubCommand::ToggleAlwaysOnTop => { + send_message(&SocketMessage::ToggleAlwaysOnTop)?; + } SubCommand::WorkspaceLayout(arg) => { send_message(&SocketMessage::WorkspaceLayout( arg.monitor,