don't focus window until super+tab is released

This commit is contained in:
2026-07-26 18:33:15 -07:00
parent ab47c01cf7
commit 67562f4e92
2 changed files with 20 additions and 38 deletions
+8 -7
View File
@@ -6,8 +6,8 @@ commands for application and same-application window switching.
The application HUD groups windows by Hyprland `class`, orders applications and The application HUD groups windows by Hyprland `class`, orders applications and
their windows by most-recent focus, and resolves names and icons from installed 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 desktop files. Cycling changes only the HUD selection; accepting that selection
its most-recent window. 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 This implementation uses only standard Hyprland IPC. It does not depend on the
custom `cycleapp` or `cycleappwindow` dispatchers. 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. - `Super+Tab` / `Super+Shift+Tab`: show the application HUD and move selection.
- Release `Super` or press `Enter`: accept the 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+grave` / `Super+Shift+grave`: cycle windows in the current application.
- `Super+H`: hide every window 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 - `src/main.rs` owns the daemon command socket, switch sessions, GTK HUD, and
keyboard handling. keyboard handling.
The daemon freezes MRU updates while the HUD is previewing applications so its The daemon freezes MRU updates while the HUD is active, keeping the application
own focus requests do not reorder the active switch session. Final focus is order stable throughout the switch session. It does not raise or focus the
sent shortly after the layer-shell HUD closes, allowing its exclusive keyboard highlighted application while cycling. Final focus is sent shortly after the
grab to be released first. layer-shell HUD closes, allowing its exclusive keyboard grab to be released
first.
## Troubleshooting ## Troubleshooting
+10 -29
View File
@@ -43,7 +43,6 @@ struct Session {
id: u64, id: u64,
groups: Vec<AppGroup>, groups: Vec<AppGroup>,
selected: usize, selected: usize,
original: usize,
destination_workspace: String, destination_workspace: String,
} }
@@ -306,7 +305,6 @@ impl Switcher {
id: session_id, id: session_id,
groups, groups,
selected, selected,
original,
destination_workspace: workspace.name, destination_workspace: workspace.name,
}); });
drop(state); drop(state);
@@ -315,7 +313,6 @@ impl Switcher {
self.window self.window
.set_keyboard_mode(layer::KeyboardMode::Exclusive); .set_keyboard_mode(layer::KeyboardMode::Exclusive);
self.window.present(); self.window.present();
self.activate_selected();
} }
fn cycle(&self, direction: Direction) { fn cycle(&self, direction: Direction) {
@@ -330,24 +327,6 @@ impl Switcher {
}; };
drop(state); drop(state);
self.render(); 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) { fn hide_current_app(&self) {
@@ -480,26 +459,28 @@ impl Switcher {
} }
fn finish(&self, commit: bool) { fn finish(&self, commit: bool) {
let (target, destination_workspace) = { let selection = {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
let Some(session) = state.session.take() else { let Some(session) = state.session.take() else {
return; return;
}; };
let target_index = if commit { if commit {
session.selected let target = session.groups[session.selected].clone();
} else {
session.original
};
let target = session.groups[target_index].clone();
if let Some(window) = target.representative() { if let Some(window) = target.representative() {
state.history.touch(&window.address); state.history.touch(&window.address);
} }
state.ignore_focus_until = Some(Instant::now() + Duration::from_millis(200)); state.ignore_focus_until = Some(Instant::now() + Duration::from_millis(200));
(target, session.destination_workspace) Some((target, session.destination_workspace))
} else {
None
}
}; };
self.window.set_keyboard_mode(layer::KeyboardMode::None); self.window.set_keyboard_mode(layer::KeyboardMode::None);
self.window.hide(); self.window.hide();
let Some((target, destination_workspace)) = selection else {
return;
};
// The layer surface releases its exclusive keyboard focus // The layer surface releases its exclusive keyboard focus
// asynchronously. Wait for that round-trip before focusing the // asynchronously. Wait for that round-trip before focusing the
// selected toplevel, otherwise Hyprland raises it without granting // selected toplevel, otherwise Hyprland raises it without granting