don't focus window until super+tab is released
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user