Fix stuck HUD on fast modifier release
This commit is contained in:
+90
@@ -40,6 +40,7 @@ enum DaemonMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Session {
|
struct Session {
|
||||||
|
id: u64,
|
||||||
groups: Vec<AppGroup>,
|
groups: Vec<AppGroup>,
|
||||||
selected: usize,
|
selected: usize,
|
||||||
original: usize,
|
original: usize,
|
||||||
@@ -49,6 +50,7 @@ struct Session {
|
|||||||
struct RuntimeState {
|
struct RuntimeState {
|
||||||
history: FocusHistory,
|
history: FocusHistory,
|
||||||
session: Option<Session>,
|
session: Option<Session>,
|
||||||
|
next_session_id: u64,
|
||||||
window_cycle: Option<WindowCycleSession>,
|
window_cycle: Option<WindowCycleSession>,
|
||||||
ignore_focus_until: Option<Instant>,
|
ignore_focus_until: Option<Instant>,
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,7 @@ impl Switcher {
|
|||||||
state: RefCell::new(RuntimeState {
|
state: RefCell::new(RuntimeState {
|
||||||
history,
|
history,
|
||||||
session: None,
|
session: None,
|
||||||
|
next_session_id: 0,
|
||||||
window_cycle: None,
|
window_cycle: None,
|
||||||
ignore_focus_until: None,
|
ignore_focus_until: None,
|
||||||
}),
|
}),
|
||||||
@@ -149,7 +152,57 @@ impl Switcher {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let switcher = Rc::clone(self);
|
||||||
|
controller.connect_modifiers(move |_controller, modifiers| {
|
||||||
|
if !switch_modifier_is_pressed(modifiers) {
|
||||||
|
switcher.finish(true);
|
||||||
|
}
|
||||||
|
Propagation::Proceed
|
||||||
|
});
|
||||||
|
|
||||||
self.window.add_controller(controller);
|
self.window.add_controller(controller);
|
||||||
|
|
||||||
|
let focus_controller = gtk::EventControllerLegacy::new();
|
||||||
|
let switcher = Rc::clone(self);
|
||||||
|
focus_controller.connect_event(move |_controller, event| {
|
||||||
|
let Some(focus_event) = event.downcast_ref::<gdk::FocusEvent>() else {
|
||||||
|
return Propagation::Proceed;
|
||||||
|
};
|
||||||
|
if !focus_event.is_in() {
|
||||||
|
return Propagation::Proceed;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session_id = switcher
|
||||||
|
.state
|
||||||
|
.borrow()
|
||||||
|
.session
|
||||||
|
.as_ref()
|
||||||
|
.map(|session| session.id);
|
||||||
|
let Some(session_id) = session_id else {
|
||||||
|
return Propagation::Proceed;
|
||||||
|
};
|
||||||
|
|
||||||
|
let switcher = Rc::clone(&switcher);
|
||||||
|
// The Wayland keyboard-enter and modifier updates can arrive in
|
||||||
|
// adjacent dispatches. Let GDK apply both before checking whether
|
||||||
|
// Super was released before the exclusive grab took effect.
|
||||||
|
glib::timeout_add_local_once(Duration::from_millis(10), move || {
|
||||||
|
let is_current_session = switcher
|
||||||
|
.state
|
||||||
|
.borrow()
|
||||||
|
.session
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|session| session.id == session_id);
|
||||||
|
if is_current_session
|
||||||
|
&& current_keyboard_modifiers()
|
||||||
|
.is_some_and(|modifiers| !switch_modifier_is_pressed(modifiers))
|
||||||
|
{
|
||||||
|
switcher.finish(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Propagation::Proceed
|
||||||
|
});
|
||||||
|
self.window.add_controller(focus_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_message(&self, message: DaemonMessage) {
|
fn handle_message(&self, message: DaemonMessage) {
|
||||||
@@ -247,7 +300,10 @@ impl Switcher {
|
|||||||
(false, Direction::Next) => (original + 1) % groups.len(),
|
(false, Direction::Next) => (original + 1) % groups.len(),
|
||||||
(false, Direction::Previous) => (original + groups.len() - 1) % groups.len(),
|
(false, Direction::Previous) => (original + groups.len() - 1) % groups.len(),
|
||||||
};
|
};
|
||||||
|
let session_id = state.next_session_id;
|
||||||
|
state.next_session_id = state.next_session_id.wrapping_add(1);
|
||||||
state.session = Some(Session {
|
state.session = Some(Session {
|
||||||
|
id: session_id,
|
||||||
groups,
|
groups,
|
||||||
selected,
|
selected,
|
||||||
original,
|
original,
|
||||||
@@ -503,6 +559,17 @@ impl Switcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn switch_modifier_is_pressed(modifiers: gdk::ModifierType) -> bool {
|
||||||
|
modifiers.intersects(gdk::ModifierType::SUPER_MASK | gdk::ModifierType::META_MASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_keyboard_modifiers() -> Option<gdk::ModifierType> {
|
||||||
|
gdk::Display::default()?
|
||||||
|
.default_seat()?
|
||||||
|
.keyboard()
|
||||||
|
.map(|keyboard| keyboard.modifier_state())
|
||||||
|
}
|
||||||
|
|
||||||
fn install_css() {
|
fn install_css() {
|
||||||
let provider = gtk::CssProvider::new();
|
let provider = gtk::CssProvider::new();
|
||||||
provider.load_from_data(
|
provider.load_from_data(
|
||||||
@@ -657,3 +724,26 @@ fn main() -> Result<()> {
|
|||||||
Some(command) => anyhow::bail!("unknown command: {command}"),
|
Some(command) => anyhow::bail!("unknown command: {command}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn switch_modifier_accepts_super_and_meta_mappings() {
|
||||||
|
assert!(switch_modifier_is_pressed(gdk::ModifierType::SUPER_MASK));
|
||||||
|
assert!(switch_modifier_is_pressed(gdk::ModifierType::META_MASK));
|
||||||
|
assert!(switch_modifier_is_pressed(
|
||||||
|
gdk::ModifierType::SHIFT_MASK | gdk::ModifierType::SUPER_MASK
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn switch_modifier_rejects_unrelated_modifiers() {
|
||||||
|
assert!(!switch_modifier_is_pressed(gdk::ModifierType::empty()));
|
||||||
|
assert!(!switch_modifier_is_pressed(gdk::ModifierType::SHIFT_MASK));
|
||||||
|
assert!(!switch_modifier_is_pressed(
|
||||||
|
gdk::ModifierType::CONTROL_MASK | gdk::ModifierType::ALT_MASK
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user