diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index 6e9f07ef..fd49d763 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -75,6 +75,18 @@ impl Container { None } + pub fn idx_from_exe(&self, exe: &str) -> Option { + for (idx, window) in self.windows().iter().enumerate() { + if let Ok(window_exe) = window.exe() { + if exe == window_exe { + return Option::from(idx); + } + } + } + + None + } + pub fn contains_window(&self, hwnd: isize) -> bool { for window in self.windows() { if window.hwnd == hwnd { diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 131c55ea..68025d51 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -77,6 +77,7 @@ pub enum SocketMessage { Promote, PromoteFocus, PromoteWindow(OperationDirection), + EagerFocus(String), ToggleFloat, ToggleMonocle, ToggleMaximize, diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index dea13813..7053127a 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -65,6 +65,7 @@ use crate::window_manager; use crate::window_manager::WindowManager; use crate::windows_api::WindowsApi; use crate::winevent_listener; +use crate::workspace::WorkspaceWindowLocation; use crate::GlobalState; use crate::Notification; use crate::NotificationEvent; @@ -229,6 +230,65 @@ impl WindowManager { self.focus_container_in_direction(direction)?; self.promote_container_to_front()? } + SocketMessage::EagerFocus(ref exe) => { + let focused_monitor_idx = self.focused_monitor_idx(); + let focused_workspace_idx = self.focused_workspace_idx()?; + + let mut window_location = None; + let mut monitor_workspace_indices = None; + + 'search: for (monitor_idx, monitor) in self.monitors().iter().enumerate() { + for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() { + if let Some(location) = workspace.location_from_exe(exe) { + window_location = Some(location); + monitor_workspace_indices = Some((monitor_idx, workspace_idx)); + break 'search; + } + } + } + + if let Some((monitor_idx, workspace_idx)) = monitor_workspace_indices { + if monitor_idx != focused_monitor_idx { + self.focus_monitor(monitor_idx)?; + } + + if workspace_idx != focused_workspace_idx { + self.focus_workspace(workspace_idx)?; + } + } + + if let Some(location) = window_location { + match location { + WorkspaceWindowLocation::Monocle(window_idx) => { + self.focus_container_window(window_idx)?; + } + WorkspaceWindowLocation::Maximized => { + if let Some(window) = + self.focused_workspace_mut()?.maximized_window_mut() + { + window.focus(self.mouse_follows_focus)?; + } + } + WorkspaceWindowLocation::Container(container_idx, window_idx) => { + let focused_container_idx = self.focused_container_idx()?; + if container_idx != focused_container_idx { + self.focused_workspace_mut()?.focus_container(container_idx); + } + + self.focus_container_window(window_idx)?; + } + WorkspaceWindowLocation::Floating(window_idx) => { + if let Some(window) = self + .focused_workspace_mut()? + .floating_windows_mut() + .get_mut(window_idx) + { + window.focus(self.mouse_follows_focus)?; + } + } + } + } + } SocketMessage::FocusWindow(direction) => { self.focus_container_in_direction(direction)?; } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 2c80cdad..512026d5 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -2245,7 +2245,7 @@ impl WindowManager { let len = NonZeroUsize::new(container.windows().len()) .ok_or_else(|| anyhow!("there must be at least one window in a container"))?; - if len.get() == 1 { + if len.get() == 1 && idx != 0 { bail!("there is only one window in this container"); } @@ -3270,6 +3270,10 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no container")) } + pub fn focused_container_idx(&self) -> Result { + Ok(self.focused_workspace()?.focused_container_idx()) + } + pub fn focused_container_mut(&mut self) -> Result<&mut Container> { self.focused_workspace_mut()? .focused_container_mut() diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index b0dc72d6..c3180a3e 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -119,6 +119,14 @@ impl Default for Workspace { } } +#[derive(Debug)] +pub enum WorkspaceWindowLocation { + Monocle(usize), // window_idx + Maximized, + Container(usize, usize), // container_idx, window_idx + Floating(usize), // idx in floating_windows +} + impl Workspace { pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> { self.name = Option::from(config.name.clone()); @@ -579,6 +587,41 @@ impl Workspace { None } + pub fn location_from_exe(&self, exe: &str) -> Option { + for (container_idx, container) in self.containers().iter().enumerate() { + if let Some(window_idx) = container.idx_from_exe(exe) { + return Some(WorkspaceWindowLocation::Container( + container_idx, + window_idx, + )); + } + } + + if let Some(window) = self.maximized_window() { + if let Ok(window_exe) = window.exe() { + if exe == window_exe { + return Some(WorkspaceWindowLocation::Maximized); + } + } + } + + if let Some(container) = self.monocle_container() { + if let Some(window_idx) = container.idx_from_exe(exe) { + return Some(WorkspaceWindowLocation::Monocle(window_idx)); + } + } + + for (window_idx, window) in self.floating_windows().iter().enumerate() { + if let Ok(window_exe) = window.exe() { + if exe == window_exe { + return Some(WorkspaceWindowLocation::Floating(window_idx)); + } + } + } + + None + } + pub fn contains_managed_window(&self, hwnd: isize) -> bool { for container in self.containers() { if container.contains_window(hwnd) { diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 6b32c60b..fd6fb145 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -927,6 +927,12 @@ struct ReplaceConfiguration { path: PathBuf, } +#[derive(Parser)] +struct EagerFocus { + /// Case-sensitive exe identifier + exe: String, +} + #[derive(Parser)] #[clap(author, about, version = build::CLAP_LONG_VERSION)] struct Opts { @@ -1020,6 +1026,9 @@ enum SubCommand { /// Move the focused window in the specified cycle direction #[clap(arg_required_else_help = true)] CycleMove(CycleMove), + /// Focus the first managed window matching the given exe + #[clap(arg_required_else_help = true)] + EagerFocus(EagerFocus), /// Stack the focused window in the specified direction #[clap(arg_required_else_help = true)] Stack(Stack), @@ -1726,6 +1735,9 @@ fn main() -> Result<()> { SubCommand::CycleMove(arg) => { send_message(&SocketMessage::CycleMoveWindow(arg.cycle_direction))?; } + SubCommand::EagerFocus(arg) => { + send_message(&SocketMessage::EagerFocus(arg.exe))?; + } SubCommand::MoveToMonitor(arg) => { send_message(&SocketMessage::MoveContainerToMonitorNumber(arg.target))?; }