From 67562f4e92144936f2a0faf796f182ed01d98d23 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sun, 26 Jul 2026 18:33:15 -0700 Subject: [PATCH] don't focus window until super+tab is released --- README.md | 15 ++++++++------- src/main.rs | 43 ++++++++++++------------------------------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index bb326af..3d19de6 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ commands for application and same-application window switching. The application HUD groups windows by Hyprland `class`, orders applications and their windows by most-recent focus, and resolves names and icons from installed -desktop files. Selecting an application raises all of its windows and focuses -its most-recent window. +desktop files. Cycling changes only the HUD selection; accepting that selection +raises all of the application's windows and focuses its most-recent window. This implementation uses only standard Hyprland IPC. It does not depend on the custom `cycleapp` or `cycleappwindow` dispatchers. @@ -16,7 +16,7 @@ custom `cycleapp` or `cycleappwindow` dispatchers. - `Super+Tab` / `Super+Shift+Tab`: show the application HUD and move selection. - Release `Super` or press `Enter`: accept the selection. -- `Escape`: cancel and restore the original application. +- `Escape`: cancel without changing application focus or window stacking. - `Super+grave` / `Super+Shift+grave`: cycle windows in the current application. - `Super+H`: hide every window in the current application. @@ -91,10 +91,11 @@ Set `RUST_LOG=hypr_switcher=debug` on the daemon command for diagnostics. - `src/main.rs` owns the daemon command socket, switch sessions, GTK HUD, and keyboard handling. -The daemon freezes MRU updates while the HUD is previewing applications so its -own focus requests do not reorder the active switch session. Final focus is -sent shortly after the layer-shell HUD closes, allowing its exclusive keyboard -grab to be released first. +The daemon freezes MRU updates while the HUD is active, keeping the application +order stable throughout the switch session. It does not raise or focus the +highlighted application while cycling. Final focus is sent shortly after the +layer-shell HUD closes, allowing its exclusive keyboard grab to be released +first. ## Troubleshooting diff --git a/src/main.rs b/src/main.rs index 3ae82ae..8368e05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,6 @@ struct Session { id: u64, groups: Vec, selected: usize, - original: usize, destination_workspace: String, } @@ -306,7 +305,6 @@ impl Switcher { id: session_id, groups, selected, - original, destination_workspace: workspace.name, }); drop(state); @@ -315,7 +313,6 @@ impl Switcher { self.window .set_keyboard_mode(layer::KeyboardMode::Exclusive); self.window.present(); - self.activate_selected(); } fn cycle(&self, direction: Direction) { @@ -330,24 +327,6 @@ impl Switcher { }; drop(state); self.render(); - self.activate_selected(); - } - - fn activate_selected(&self) { - let selected = self - .state - .borrow() - .session - .as_ref() - .map(|session| session.groups[session.selected].clone()); - if let Some(group) = selected { - if group.hidden { - return; - } - if let Err(error) = ipc::activate_app(&group) { - log::error!("failed to activate {}: {error:#}", group.key); - } - } } fn hide_current_app(&self) { @@ -480,26 +459,28 @@ impl Switcher { } fn finish(&self, commit: bool) { - let (target, destination_workspace) = { + let selection = { let mut state = self.state.borrow_mut(); let Some(session) = state.session.take() else { return; }; - let target_index = if commit { - session.selected + if commit { + let target = session.groups[session.selected].clone(); + if let Some(window) = target.representative() { + state.history.touch(&window.address); + } + state.ignore_focus_until = Some(Instant::now() + Duration::from_millis(200)); + Some((target, session.destination_workspace)) } else { - session.original - }; - let target = session.groups[target_index].clone(); - if let Some(window) = target.representative() { - state.history.touch(&window.address); + None } - state.ignore_focus_until = Some(Instant::now() + Duration::from_millis(200)); - (target, session.destination_workspace) }; self.window.set_keyboard_mode(layer::KeyboardMode::None); self.window.hide(); + let Some((target, destination_workspace)) = selection else { + return; + }; // The layer surface releases its exclusive keyboard focus // asynchronously. Wait for that round-trip before focusing the // selected toplevel, otherwise Hyprland raises it without granting