1
0
Fork 0
forked from wry/wry

seat: add focus history

This commit is contained in:
Julian Orth 2025-07-18 20:09:34 +02:00
parent 9941263a82
commit d12234b38b
21 changed files with 546 additions and 22 deletions

View file

@ -54,7 +54,7 @@ use {
Axis, Direction, Workspace,
client::{Client as ConfigClient, ClientMatcher},
input::{
FocusFollowsMouseMode, InputDevice, Seat,
FocusFollowsMouseMode, InputDevice, Seat, Timeline,
acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT, AccelProfile},
capability::{
CAP_GESTURE, CAP_KEYBOARD, CAP_POINTER, CAP_SWITCH, CAP_TABLET_PAD,
@ -2150,11 +2150,41 @@ impl ConfigProxyHandler {
.set(matcher, (m, tile_state));
Ok(())
}
fn handle_set_pointer_revert_key(&self, seat: Seat, key: KeySym) -> Result<(), CphError> {
self.get_seat(seat)?.set_pointer_revert_key(key);
Ok(())
}
fn handle_seat_focus_history(&self, seat: Seat, timeline: Timeline) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
match timeline {
Timeline::Older => seat.focus_prev(),
Timeline::Newer => seat.focus_next(),
}
Ok(())
}
fn handle_seat_focus_history_set_only_visible(
&self,
seat: Seat,
visible: bool,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.focus_history_set_visible(visible);
Ok(())
}
fn handle_seat_focus_history_set_same_workspace(
&self,
seat: Seat,
same_workspace: bool,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.focus_history_set_same_workspace(same_workspace);
Ok(())
}
fn spaces_change(&self) {
struct V;
impl NodeVisitorBase for V {
@ -2997,6 +3027,18 @@ impl ConfigProxyHandler {
.wrn("get_content_type")?,
ClientMessage::SetShowBar { show } => self.handle_set_show_bar(show),
ClientMessage::GetShowBar => self.handle_get_show_bar(),
ClientMessage::SeatFocusHistory { seat, timeline } => self
.handle_seat_focus_history(seat, timeline)
.wrn("seat_focus_history")?,
ClientMessage::SeatFocusHistorySetOnlyVisible { seat, only_visible } => self
.handle_seat_focus_history_set_only_visible(seat, only_visible)
.wrn("seat_focus_history_set_only_visible")?,
ClientMessage::SeatFocusHistorySetSameWorkspace {
seat,
same_workspace,
} => self
.handle_seat_focus_history_set_same_workspace(seat, same_workspace)
.wrn("seat_focus_history_set_same_workspace")?,
}
Ok(())
}

View file

@ -46,6 +46,7 @@ use {
},
wl_output::WlOutputGlobal,
wl_seat::{
event_handling::FocusHistoryData,
gesture_owner::GestureOwnerHolder,
kb_owner::KbOwnerHolder,
pointer_owner::PointerOwnerHolder,
@ -84,8 +85,14 @@ use {
toplevel_parent_container, toplevel_set_floating, toplevel_set_workspace,
},
utils::{
asyncevent::AsyncEvent, bindings::PerClientBindings, clonecell::CloneCell,
copyhashmap::CopyHashMap, linkedlist::LinkedNode, numcell::NumCell, rc_eq::rc_eq,
asyncevent::AsyncEvent,
bindings::PerClientBindings,
clonecell::CloneCell,
copyhashmap::CopyHashMap,
linkedlist::{LinkedList, LinkedNode, NodeRef},
numcell::NumCell,
on_drop::OnDrop,
rc_eq::{rc_eq, rc_weak_eq},
smallmap::SmallMap,
},
wire::{
@ -218,6 +225,11 @@ pub struct WlSeatGlobal {
keyboard_node_serial: Cell<u64>,
tray_popups: CopyHashMap<(TrayItemId, XdgPopupId), Rc<dyn DynTrayItem>>,
revert_key: Cell<KeySym>,
last_focus_location: Cell<Option<NodeLocation>>,
focus_history: LinkedList<FocusHistoryData>,
focus_history_rotate: NumCell<u64>,
focus_history_visible_only: Cell<bool>,
focus_history_same_workspace: Cell<bool>,
}
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
@ -292,6 +304,11 @@ impl WlSeatGlobal {
ui_drag_highlight: Default::default(),
tray_popups: Default::default(),
revert_key: Cell::new(SYM_Escape),
last_focus_location: Default::default(),
focus_history: Default::default(),
focus_history_rotate: Default::default(),
focus_history_visible_only: Cell::new(false),
focus_history_same_workspace: Cell::new(false),
});
slf.pointer_cursor.set_owner(slf.clone());
let seat = slf.clone();
@ -649,6 +666,141 @@ impl WlSeatGlobal {
}
}
pub fn get_last_focus_on_workspace(&self, ws: &WorkspaceNode) -> Option<Rc<dyn Node>> {
let mut node = self.focus_history.last()?;
loop {
if let Some(node) = node.node.upgrade()
&& let Some(NodeLocation::Workspace(_, new)) = node.node_location()
&& new == ws.id
{
return Some(node);
}
node = node.prev()?;
}
}
fn get_focus_history(
&self,
next: impl Fn(&NodeRef<FocusHistoryData>) -> Option<NodeRef<FocusHistoryData>>,
first: impl FnOnce(&LinkedList<FocusHistoryData>) -> Option<NodeRef<FocusHistoryData>>,
) -> Option<(Rc<dyn Node>, bool)> {
let original = self.keyboard_node.get();
let mut output = None;
let mut workspace = None;
if let Some(old) = original.node_location() {
match old {
NodeLocation::Workspace(o, w) => {
workspace = Some(w);
output = Some(o);
}
NodeLocation::Output(o) => {
output = Some(o);
}
}
}
if (output.is_none() || workspace.is_none())
&& let Some(old) = self.last_focus_location.get()
{
match old {
NodeLocation::Workspace(o, w) => {
workspace = workspace.or(Some(w));
output = output.or(Some(o));
}
NodeLocation::Output(o) => {
output = output.or(Some(o));
}
}
}
if workspace.is_none()
&& let Some(output) = original.node_output()
&& let Some(ws) = output.workspace.get()
{
workspace = Some(ws.id);
}
let matches = |node: &FocusHistoryData| {
let visible = node.visible.get();
if self.focus_history_visible_only.get() && !visible {
return None;
}
let node = node.node.upgrade()?;
if self.focus_history_same_workspace.get() {
let new = node.node_location()?;
let o = match new {
NodeLocation::Workspace(o, w) => {
if workspace != Some(w) {
return None;
}
o
}
NodeLocation::Output(o) => o,
};
if output != Some(o) {
return None;
}
}
Some((node, visible))
};
let node = original.node_seat_state().get_focus_history(self);
if let Some(mut node) = node {
loop {
node = match next(&node) {
Some(n) => n,
_ => break,
};
if let Some(matches) = matches(&node) {
return Some(matches);
}
}
}
let mut node = first(&self.focus_history)?;
loop {
if rc_weak_eq(&original, &node.node) {
return None;
}
if let Some(matches) = matches(&node) {
return Some(matches);
}
node = next(&node)?;
}
}
fn focus_history(
self: &Rc<Self>,
next: impl Fn(&NodeRef<FocusHistoryData>) -> Option<NodeRef<FocusHistoryData>>,
first: impl FnOnce(&LinkedList<FocusHistoryData>) -> Option<NodeRef<FocusHistoryData>>,
) {
let Some((node, visible)) = self.get_focus_history(next, first) else {
return;
};
self.focus_history_rotate.fetch_add(1);
let _reset = OnDrop(|| {
self.focus_history_rotate.fetch_sub(1);
});
if !visible {
node.clone().node_make_visible();
if !node.node_visible() {
return;
}
}
self.focus_node(node);
}
pub fn focus_prev(self: &Rc<Self>) {
self.focus_history(|s| s.prev(), |l| l.last());
}
pub fn focus_next(self: &Rc<Self>) {
self.focus_history(|s| s.next(), |l| l.first());
}
pub fn focus_history_set_visible(&self, visible: bool) {
self.focus_history_visible_only.set(visible);
}
pub fn focus_history_set_same_workspace(&self, same_workspace: bool) {
self.focus_history_same_workspace.set(same_workspace);
}
fn set_selection_<T, X, S>(
self: &Rc<Self>,
field: &CloneCell<Option<Rc<dyn DynDataSource>>>,

View file

@ -40,7 +40,10 @@ use {
state::DeviceHandlerData,
tree::{Direction, Node, ToplevelNode},
utils::{
bitflags::BitflagsExt, hash_map_ext::HashMapExt, smallmap::SmallMap,
bitflags::BitflagsExt,
hash_map_ext::HashMapExt,
linkedlist::{LinkedNode, NodeRef},
smallmap::{SmallMap, SmallMapMut},
syncqueue::SyncQueue,
},
wire::WlDataOfferId,
@ -56,13 +59,20 @@ use {
kbvm::{ModifierMask, state_machine::Event},
linearize::LinearizeExt,
smallvec::SmallVec,
std::{cell::RefCell, collections::hash_map::Entry, mem, rc::Rc},
std::{
cell::{Cell, RefCell},
collections::hash_map::Entry,
mem,
rc::{Rc, Weak},
},
};
#[derive(Default)]
pub struct NodeSeatState {
pointer_foci: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
kb_foci: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
no_focus_history: Cell<bool>,
kb_focus_histories: RefCell<SmallMapMut<SeatId, LinkedNode<FocusHistoryData>, 1>>,
gesture_foci: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
touch_foci: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
pointer_grabs: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
@ -72,6 +82,11 @@ pub struct NodeSeatState {
ui_drags: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
}
pub struct FocusHistoryData {
pub visible: Cell<bool>,
pub node: Weak<dyn Node>,
}
impl NodeSeatState {
pub(super) fn enter(&self, seat: &Rc<WlSeatGlobal>) {
self.pointer_foci.insert(seat.id, seat.clone());
@ -81,7 +96,26 @@ impl NodeSeatState {
self.pointer_foci.remove(&seat.id);
}
pub(super) fn focus(&self, seat: &Rc<WlSeatGlobal>) -> bool {
pub fn disable_focus_history(&self) {
self.no_focus_history.set(true);
}
pub(super) fn focus(&self, node: &Rc<dyn Node>, seat: &Rc<WlSeatGlobal>) -> bool {
if !self.no_focus_history.get() {
let hist = &mut *self.kb_focus_histories.borrow_mut();
let hist = hist.get_or_insert_with(seat.id, || {
seat.focus_history.add_last(FocusHistoryData {
visible: Cell::new(node.node_visible()),
node: Rc::downgrade(node),
})
});
if seat.focus_history_rotate.is_zero() {
seat.last_focus_location.set(node.node_location());
seat.focus_history.add_last_existing(hist);
} else {
seat.focus_history.rotate_last(hist);
}
}
self.kb_foci.insert(seat.id, seat.clone());
self.kb_foci.len() == 1
}
@ -179,6 +213,10 @@ impl NodeSeatState {
}
pub fn destroy_node(&self, node: &dyn Node) {
for (_, entry) in self.kb_focus_histories.borrow_mut().iter_mut() {
entry.visible.set(false);
entry.detach();
}
self.destroy_node2(node, true);
}
@ -223,11 +261,22 @@ impl NodeSeatState {
}
pub fn set_visible(&self, node: &dyn Node, visible: bool) {
for (_, entry) in self.kb_focus_histories.borrow_mut().iter_mut() {
entry.visible.set(visible);
}
if !visible {
self.destroy_node2(node, false);
}
}
pub(super) fn get_focus_history(
&self,
seat: &WlSeatGlobal,
) -> Option<NodeRef<FocusHistoryData>> {
let hist = &*self.kb_focus_histories.borrow();
Some(hist.get(&seat.id)?.to_ref())
}
pub fn on_seat_remove(&self, seat: &WlSeatGlobal) {
self.kb_foci.remove(&seat.id);
self.pointer_foci.remove(&seat.id);

View file

@ -87,7 +87,7 @@ impl KbOwner for DefaultKbOwner {
old.node_active_changed(false);
}
if node.node_seat_state().focus(seat) {
if node.node_seat_state().focus(&node, seat) {
node.node_active_changed(true);
}
// log::info!("focus {}", node.node_id());

View file

@ -117,11 +117,8 @@ pub enum FindTreeUsecase {
#[derive(Copy, Clone)]
pub enum NodeLocation {
Workspace(
#[expect(dead_code)] OutputNodeId,
#[expect(dead_code)] WorkspaceNodeId,
),
Output(#[expect(dead_code)] OutputNodeId),
Workspace(OutputNodeId, WorkspaceNodeId),
Output(OutputNodeId),
}
pub trait Node: 'static {

View file

@ -28,14 +28,16 @@ pub struct DisplayNode {
impl DisplayNode {
pub fn new(id: NodeId) -> Self {
Self {
let slf = Self {
id,
extents: Default::default(),
outputs: Default::default(),
stacked: Default::default(),
stacked_above_layers: Default::default(),
seat_state: Default::default(),
}
};
slf.seat_state.disable_focus_history();
slf
}
pub fn clear(&self) {

View file

@ -324,6 +324,10 @@ impl Node for WorkspaceNode {
fn node_do_focus(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, direction: Direction) {
if let Some(fs) = self.fullscreen.get() {
fs.node_do_focus(seat, direction);
} else if self.stacked.is_not_empty()
&& let Some(last) = seat.get_last_focus_on_workspace(&self)
{
seat.focus_node(last);
} else if let Some(container) = self.container.get() {
container.node_do_focus(seat, direction);
} else if let Some(float) = self

View file

@ -97,7 +97,6 @@ impl<T> LinkedList<T> {
self.root.append_existing(t)
}
#[expect(dead_code)]
pub fn rotate_last(&self, t: &NodeRef<T>) {
unsafe {
let root = self.root.data.as_ref();

View file

@ -1,9 +1,16 @@
use std::{ops::Deref, rc::Rc};
use std::{
ops::Deref,
rc::{Rc, Weak},
};
pub fn rc_eq<T: ?Sized>(a: &Rc<T>, b: &Rc<T>) -> bool {
Rc::as_ptr(a) as *const u8 == Rc::as_ptr(b) as *const u8
}
pub fn rc_weak_eq<T: ?Sized>(a: &Rc<T>, b: &Weak<T>) -> bool {
Rc::as_ptr(a) as *const u8 == b.as_ptr() as *const u8
}
#[derive(Default)]
pub struct RcEq<T>(pub Rc<T>);