From e116f20396ca380bc364a48ddaae5810f3932358 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 24 Feb 2025 21:43:16 +0100 Subject: [PATCH] stack: Remember previous position during focus navigation --- src/input/mod.rs | 12 ++- src/shell/element/mod.rs | 9 +- src/shell/element/stack.rs | 160 +++++++++++++++++++++------------ src/shell/layout/tiling/mod.rs | 10 --- src/shell/mod.rs | 2 +- src/shell/seats.rs | 17 +++- 6 files changed, 137 insertions(+), 73 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 2ca5dedb..bfac811d 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -22,7 +22,7 @@ use crate::{ tiling::{NodeDesc, SwapWindowGrab, TilingLayout}, }, zoom::ZoomState, - SeatExt, Trigger, + LastModifierChange, SeatExt, Trigger, }, utils::{float::NextDown, prelude::*, quirks::workspace_overview_is_open}, wayland::{ @@ -219,6 +219,7 @@ impl State { let serial = SERIAL_COUNTER.next_serial(); let time = Event::time_msec(&event); let keyboard = seat.get_keyboard().unwrap(); + let previous_modifiers = keyboard.modifier_state(); if let Some((action, pattern)) = keyboard .input( self, @@ -227,6 +228,15 @@ impl State { serial, time, |data, modifiers, handle| { + if previous_modifiers != *modifiers { + *seat + .user_data() + .get::() + .unwrap() + .0 + .lock() + .unwrap() = Some(serial); + } Self::filter_keyboard_input( data, &event, &seat, modifiers, handle, serial, ) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index f4ba939d..383b0b54 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -319,9 +319,14 @@ impl CosmicMapped { } } - pub fn handle_focus(&self, direction: FocusDirection, swap: Option) -> bool { + pub fn handle_focus( + &self, + seat: &Seat, + direction: FocusDirection, + swap: Option, + ) -> bool { if let CosmicMappedInternal::Stack(stack) = &self.element { - stack.handle_focus(direction, swap) + stack.handle_focus(seat, direction, swap) } else { false } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 8d7ce4e7..a18cfe7d 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -96,6 +96,7 @@ pub struct CosmicStackInternal { active: Arc, activated: Arc, group_focused: Arc, + previous_index: Arc>>, scroll_to_focus: Arc, previous_keyboard: Arc, pointer_entered: Arc, @@ -148,6 +149,7 @@ impl CosmicStack { active: Arc::new(AtomicUsize::new(0)), activated: Arc::new(AtomicBool::new(false)), group_focused: Arc::new(AtomicBool::new(false)), + previous_index: Arc::new(Mutex::new(None)), scroll_to_focus: Arc::new(AtomicBool::new(false)), previous_keyboard: Arc::new(AtomicUsize::new(0)), pointer_entered: Arc::new(AtomicU8::new(0)), @@ -255,75 +257,117 @@ impl CosmicStack { self.0.with_program(|p| p.windows.lock().unwrap().len()) } - pub fn handle_focus(&self, direction: FocusDirection, swap: Option) -> bool { - let result = self.0.with_program(|p| match direction { - FocusDirection::Left => { - if !p.group_focused.load(Ordering::SeqCst) { - if let Ok(old) = - p.active - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| { - val.checked_sub(1) - }) - { - p.previous_keyboard.store(old, Ordering::SeqCst); - p.scroll_to_focus.store(true, Ordering::SeqCst); - true + pub fn handle_focus( + &self, + seat: &Seat, + direction: FocusDirection, + swap: Option, + ) -> bool { + let (result, update) = self.0.with_program(|p| { + let last_mod_serial = seat.last_modifier_change(); + let mut prev_idx = p.previous_index.lock().unwrap(); + if !prev_idx.is_some_and(|(serial, _)| Some(serial) == last_mod_serial) { + *prev_idx = last_mod_serial.map(|s| (s, p.active.load(Ordering::SeqCst))); + } + + match direction { + FocusDirection::Left => { + if !p.group_focused.load(Ordering::SeqCst) { + if let Ok(old) = + p.active + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| { + val.checked_sub(1) + }) + { + p.previous_keyboard.store(old, Ordering::SeqCst); + p.scroll_to_focus.store(true, Ordering::SeqCst); + (true, true) + } else { + let new = prev_idx.unwrap().1; + let old = p.active.swap(new, Ordering::SeqCst); + if old != new { + p.previous_keyboard.store(old, Ordering::SeqCst); + p.scroll_to_focus.store(true, Ordering::SeqCst); + (false, true) + } else { + (false, false) + } + } } else { - false + (false, false) } - } else { - false } - } - FocusDirection::Right => { - if !p.group_focused.load(Ordering::SeqCst) { - let max = p.windows.lock().unwrap().len(); - if let Ok(old) = - p.active - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| { - if val < max - 1 { - Some(val + 1) - } else { - None - } - }) - { - p.previous_keyboard.store(old, Ordering::SeqCst); - p.scroll_to_focus.store(true, Ordering::SeqCst); - true + FocusDirection::Right => { + if !p.group_focused.load(Ordering::SeqCst) { + let max = p.windows.lock().unwrap().len(); + if let Ok(old) = + p.active + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| { + if val < max - 1 { + Some(val + 1) + } else { + None + } + }) + { + p.previous_keyboard.store(old, Ordering::SeqCst); + p.scroll_to_focus.store(true, Ordering::SeqCst); + (true, true) + } else { + let new = prev_idx.unwrap().1; + let old = p.active.swap(new, Ordering::SeqCst); + if old != new { + p.previous_keyboard.store(old, Ordering::SeqCst); + p.scroll_to_focus.store(true, Ordering::SeqCst); + (false, true) + } else { + (false, false) + } + } } else { - false + (false, false) } - } else { - false } - } - FocusDirection::Out if swap.is_none() => { - if !p.group_focused.swap(true, Ordering::SeqCst) { - p.windows.lock().unwrap().iter().for_each(|w| { - w.set_activated(false); - w.send_configure(); - }); - true - } else { - false + FocusDirection::Out if swap.is_none() => { + if !p.group_focused.swap(true, Ordering::SeqCst) { + p.windows.lock().unwrap().iter().for_each(|w| { + w.set_activated(false); + w.send_configure(); + }); + (true, true) + } else { + (false, false) + } } - } - FocusDirection::In if swap.is_none() => { - if !p.group_focused.swap(false, Ordering::SeqCst) { - p.windows.lock().unwrap().iter().for_each(|w| { - w.set_activated(true); - w.send_configure(); - }); - true - } else { - false + FocusDirection::In if swap.is_none() => { + if !p.group_focused.swap(false, Ordering::SeqCst) { + p.windows.lock().unwrap().iter().for_each(|w| { + w.set_activated(true); + w.send_configure(); + }); + (true, true) + } else { + (false, false) + } } + FocusDirection::Up | FocusDirection::Down => { + if !p.group_focused.load(Ordering::SeqCst) { + let new = prev_idx.unwrap().1; + let old = p.active.swap(new, Ordering::SeqCst); + if old != new { + p.previous_keyboard.store(old, Ordering::SeqCst); + p.scroll_to_focus.store(true, Ordering::SeqCst); + } + (false, true) + } else { + (false, false) + } + }, + _ => (false, false), } - _ => false, }); - if result { + if update { self.0 .resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT))); self.0.force_update(); diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index da36ab05..5f14f983 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1826,16 +1826,6 @@ impl TilingLayout { let (last_node_id, data) = focused; - // stacks may handle focus internally - if let FocusedNodeData::Window(window) = data.clone() { - if window.handle_focus( - direction, - swap_desc.clone().filter(|desc| desc.node == last_node_id), - ) { - return FocusResult::Handled; - } - } - if direction == FocusDirection::In { if swap_desc .as_ref() diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 443d360d..945b5a67 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -3176,7 +3176,7 @@ impl Shell { return FocusResult::None; }; - if focused.handle_focus(direction, None) { + if focused.handle_focus(seat, direction, None) { return FocusResult::Handled; } diff --git a/src/shell/seats.rs b/src/shell/seats.rs index 0a7428fb..deeca2be 100644 --- a/src/shell/seats.rs +++ b/src/shell/seats.rs @@ -18,7 +18,7 @@ use smithay::{ }, output::Output, reexports::{input::Device as InputDevice, wayland_server::DisplayHandle}, - utils::{Buffer, IsAlive, Monotonic, Point, Rectangle, Time, Transform}, + utils::{Buffer, IsAlive, Monotonic, Point, Rectangle, Serial, Time, Transform}, wayland::compositor::with_states, }; use tracing::warn; @@ -172,6 +172,9 @@ struct ActiveOutput(pub Mutex); /// The output which currently has keyboard focus struct FocusedOutput(pub Mutex>); +#[derive(Default)] +pub struct LastModifierChange(pub Mutex>); + pub fn create_seat( dh: &DisplayHandle, seat_state: &mut SeatState, @@ -186,6 +189,7 @@ pub fn create_seat( userdata.insert_if_missing(SupressedKeys::default); userdata.insert_if_missing(SupressedButtons::default); userdata.insert_if_missing(ModifiersShortcutQueue::default); + userdata.insert_if_missing(LastModifierChange::default); userdata.insert_if_missing_threadsafe(SeatMoveGrabState::default); userdata.insert_if_missing_threadsafe(SeatMenuGrabState::default); userdata.insert_if_missing_threadsafe(CursorState::default); @@ -241,6 +245,7 @@ pub trait SeatExt { fn supressed_keys(&self) -> &SupressedKeys; fn supressed_buttons(&self) -> &SupressedButtons; fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue; + fn last_modifier_change(&self) -> Option; fn cursor_geometry( &self, @@ -315,6 +320,16 @@ impl SeatExt for Seat { self.user_data().get::().unwrap() } + fn last_modifier_change(&self) -> Option { + *self + .user_data() + .get::() + .unwrap() + .0 + .lock() + .unwrap() + } + fn cursor_geometry( &self, loc: impl Into>,