This commit is contained in:
2026-09-09 11:30:58 +02:00
parent 78631065e2
commit 1367b03b97
8 changed files with 315 additions and 35 deletions
+85 -8
View File
@@ -24,6 +24,7 @@ use wayland_protocols_wlr::layer_shell::v1::client::{
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
use crate::app::{App, Ending};
use crate::config::AltTabMode;
use crate::shm;
use crate::theme::{Rect, fit_centred};
@@ -32,23 +33,41 @@ use crate::theme::{Rect, fit_centred};
const KEY_ESC: u32 = 1;
const KEY_TAB: u32 = 15;
const KEY_Q: u32 = 16;
const KEY_ENTER: u32 = 28;
const KEY_LEFTCTRL: u32 = 29;
// hjkl, by physical position: the same keys as vim on a qwerty layout.
const KEY_H: u32 = 35;
const KEY_J: u32 = 36;
const KEY_K: u32 = 37;
const KEY_L: u32 = 38;
const KEY_ENTER: u32 = 28;
const KEY_LEFTSHIFT: u32 = 42;
const KEY_RIGHTSHIFT: u32 = 54;
const KEY_LEFTALT: u32 = 56;
const KEY_KPENTER: u32 = 96;
const KEY_RIGHTCTRL: u32 = 97;
const KEY_RIGHTALT: u32 = 100;
const KEY_HOME: u32 = 102;
const KEY_UP: u32 = 103;
const KEY_PGUP: u32 = 104;
const KEY_LEFT: u32 = 105;
const KEY_RIGHT: u32 = 106;
const KEY_END: u32 = 107;
const KEY_DOWN: u32 = 108;
const KEY_PGUP: u32 = 104;
const KEY_PGDN: u32 = 109;
const KEY_LEFTMETA: u32 = 125;
const KEY_RIGHTMETA: u32 = 126;
fn is_trigger_modifier(code: u32) -> bool {
matches!(
code,
KEY_LEFTALT
| KEY_RIGHTALT
| KEY_LEFTMETA
| KEY_RIGHTMETA
| KEY_LEFTCTRL
| KEY_RIGHTCTRL
)
}
/// evdev button code, as wl_pointer reports it.
const BTN_LEFT: u32 = 0x110;
@@ -105,6 +124,9 @@ impl App {
}
pool.destroy();
self.chrome = Some(shm::Chrome::new(&file, pw, ph)?);
if let (Some(mgr), Some(seat)) = (&self.inhibit_mgr, &self.seat) {
self.inhibitor = Some(mgr.inhibit_shortcuts(&surface, seat, qh, ()));
}
self.surface = Some(surface);
Ok(())
}
@@ -296,6 +318,9 @@ impl App {
}
fn key(&mut self, code: u32, qh: &QueueHandle<Self>) {
if self.is_alt_tab && is_trigger_modifier(code) {
self.latched_modifiers.insert(code);
}
match code {
KEY_LEFTSHIFT | KEY_RIGHTSHIFT => self.shift = true,
KEY_ESC | KEY_Q => self.ending = Ending::Cancelled,
@@ -315,6 +340,62 @@ impl App {
_ => {}
}
}
fn key_up(&mut self, code: u32) {
if code == KEY_LEFTSHIFT || code == KEY_RIGHTSHIFT {
self.shift = false;
}
if self.is_alt_tab
&& self.latched_modifiers.remove(&code)
&& self.latched_modifiers.is_empty()
{
if self.ending == Ending::Running {
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
self.ending = Ending::Picked;
}
}
}
fn keyboard_enter(&mut self, keys: Vec<u8>, qh: &QueueHandle<Self>) {
self.focused = true;
let held_keys: Vec<u32> = keys
.chunks_exact(4)
.map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap()))
.collect();
if held_keys.iter().any(|&k| k == KEY_LEFTSHIFT || k == KEY_RIGHTSHIFT) {
self.shift = true;
}
let held_modifiers: Vec<u32> = held_keys
.iter()
.copied()
.filter(|&k| is_trigger_modifier(k))
.collect();
if !held_modifiers.is_empty() && self.alt_tab != AltTabMode::No {
self.is_alt_tab = true;
for &m in &held_modifiers {
self.latched_modifiers.insert(m);
}
}
if self.is_alt_tab && !self.initial_stepped {
self.initial_stepped = true;
if self.alt_tab == AltTabMode::Yes && self.latched_modifiers.is_empty() {
// In explicit alt-tab mode, if no modifier was held on enter,
// the modifier was released before focus was acquired: commit immediately!
if self.ending == Ending::Running {
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
self.ending = Ending::Picked;
}
} else if self.alt_tab == AltTabMode::Auto {
// In auto mode, step selection now that we know a modifier was held:
let step = if self.shift { -1 } else { 1 };
self.move_sel(step, qh);
}
}
}
}
// --- event plumbing -------------------------------------------------------
@@ -379,18 +460,14 @@ impl Dispatch<WlKeyboard, ()> for App {
match event {
wl_keyboard::Event::Key { key, state, .. } => match state {
WEnum::Value(wl_keyboard::KeyState::Pressed) => app.key(key, qh),
WEnum::Value(wl_keyboard::KeyState::Released)
if key == KEY_LEFTSHIFT || key == KEY_RIGHTSHIFT =>
{
app.shift = false
}
WEnum::Value(wl_keyboard::KeyState::Released) => app.key_up(key),
_ => {}
},
// Focus is only tracked here. sway sends leave immediately
// followed by enter on the same surface when the pointer crosses
// it, so whether the grab is really gone is decided by the main
// loop, once the event batch has been dispatched.
wl_keyboard::Event::Enter { .. } => app.focused = true,
wl_keyboard::Event::Enter { keys, .. } => app.keyboard_enter(keys, qh),
wl_keyboard::Event::Leave { .. } => app.focused = false,
_ => {}
}