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
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
+12 -31
View File
@@ -43,7 +43,6 @@ struct Session {
id: u64,
groups: Vec<AppGroup>,
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