From 2591dc739b7532271ca3c1825b1659edd6dec28e Mon Sep 17 00:00:00 2001 From: kossLAN Date: Mon, 6 Apr 2026 23:42:38 -0400 Subject: [PATCH] ifs: init hyprland_focus_grab_manager_v1 --- src/globals.rs | 2 + src/ifs.rs | 2 + src/ifs/hyprland_focus_grab_manager_v1.rs | 97 ++++++++++++++ src/ifs/hyprland_focus_grab_v1.rs | 154 ++++++++++++++++++++++ src/ifs/wl_seat.rs | 11 +- src/ifs/wl_seat/event_handling.rs | 5 + src/ifs/wl_seat/pointer_owner.rs | 26 +++- wire/hyprland_focus_grab_manager_v1.txt | 6 + wire/hyprland_focus_grab_v1.txt | 16 +++ 9 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 src/ifs/hyprland_focus_grab_manager_v1.rs create mode 100644 src/ifs/hyprland_focus_grab_v1.rs create mode 100644 wire/hyprland_focus_grab_manager_v1.txt create mode 100644 wire/hyprland_focus_grab_v1.txt diff --git a/src/globals.rs b/src/globals.rs index 5e0d6208..af74f100 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -10,6 +10,7 @@ use { ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, head_management::jay_head_manager_v1::JayHeadManagerV1Global, + hyprland_focus_grab_manager_v1::HyprlandFocusGrabManagerV1Global, ipc::{ data_control::{ ext_data_control_manager_v1::ExtDataControlManagerV1Global, @@ -205,6 +206,7 @@ singletons! { ZwlrScreencopyManagerV1, ZwpRelativePointerManagerV1, ExtSessionLockManagerV1, + HyprlandFocusGrabManagerV1, WpViewporter, WpFractionalScaleManagerV1, ZwpPointerConstraintsV1, diff --git a/src/ifs.rs b/src/ifs.rs index 93ac167b..32464c2c 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -10,6 +10,8 @@ pub mod ext_output_image_capture_source_manager_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod head_management; +pub mod hyprland_focus_grab_manager_v1; +pub mod hyprland_focus_grab_v1; pub mod ipc; pub mod jay_acceptor_request; pub mod jay_client_query; diff --git a/src/ifs/hyprland_focus_grab_manager_v1.rs b/src/ifs/hyprland_focus_grab_manager_v1.rs new file mode 100644 index 00000000..7a3bc008 --- /dev/null +++ b/src/ifs/hyprland_focus_grab_manager_v1.rs @@ -0,0 +1,97 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::hyprland_focus_grab_v1::HyprlandFocusGrabV1, + leaks::Tracker, + object::{Object, Version}, + wire::{HyprlandFocusGrabManagerV1Id, hyprland_focus_grab_manager_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct HyprlandFocusGrabManagerV1Global { + pub name: GlobalName, +} + +impl HyprlandFocusGrabManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: HyprlandFocusGrabManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), HyprlandFocusGrabManagerV1Error> { + let obj = Rc::new(HyprlandFocusGrabManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +pub struct HyprlandFocusGrabManagerV1 { + pub id: HyprlandFocusGrabManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl HyprlandFocusGrabManagerV1RequestHandler for HyprlandFocusGrabManagerV1 { + type Error = HyprlandFocusGrabManagerV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn create_grab(&self, req: CreateGrab, _slf: &Rc) -> Result<(), Self::Error> { + let grab = Rc::new(HyprlandFocusGrabV1::new( + req.grab, + &self.client, + self.version, + )); + track!(self.client, grab); + self.client.add_client_obj(&grab)?; + Ok(()) + } +} + +global_base!( + HyprlandFocusGrabManagerV1Global, + HyprlandFocusGrabManagerV1, + HyprlandFocusGrabManagerV1Error +); + +impl Global for HyprlandFocusGrabManagerV1Global { + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(HyprlandFocusGrabManagerV1Global); + +object_base! { + self = HyprlandFocusGrabManagerV1; + version = self.version; +} + +impl Object for HyprlandFocusGrabManagerV1 {} + +simple_add_obj!(HyprlandFocusGrabManagerV1); + +#[derive(Debug, Error)] +pub enum HyprlandFocusGrabManagerV1Error { + #[error(transparent)] + ClientError(Box), +} + +efrom!(HyprlandFocusGrabManagerV1Error, ClientError); diff --git a/src/ifs/hyprland_focus_grab_v1.rs b/src/ifs/hyprland_focus_grab_v1.rs new file mode 100644 index 00000000..597dc7d6 --- /dev/null +++ b/src/ifs/hyprland_focus_grab_v1.rs @@ -0,0 +1,154 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_seat::SeatFocusGrab, + ifs::wl_surface::WlSurface, + leaks::Tracker, + object::{Object, Version}, + tree::NodeId, + wire::{HyprlandFocusGrabV1Id, hyprland_focus_grab_v1::*}, + }, + ahash::AHashMap, + std::{ + cell::Cell, + rc::Rc, + }, + thiserror::Error, +}; + +pub struct HyprlandFocusGrabV1 { + pub id: HyprlandFocusGrabV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pending: std::cell::RefCell>>>, + committed: std::cell::RefCell>>, + active: Cell, +} + +impl HyprlandFocusGrabV1 { + pub fn new(id: HyprlandFocusGrabV1Id, client: &Rc, version: Version) -> Self { + Self { + id, + client: client.clone(), + tracker: Default::default(), + version, + pending: Default::default(), + committed: Default::default(), + active: Cell::new(false), + } + } + + fn send_cleared(&self) { + self.client.event(Cleared { self_id: self.id }); + } + + fn activate(self: &Rc) { + let state = &self.client.state; + let seats = state.globals.seats.lock(); + let focus_node = self + .committed + .borrow() + .values() + .find_map(|s| s.get_focus_node()); + + for seat in seats.values() { + if let Some(old) = seat.focus_grab.get() { + old.clear(); + } + seat.focus_grab.set(Some(self.clone() as Rc)); + if let Some(node) = focus_node.clone() { + seat.grab(node); + } + } + self.active.set(true); + } + + fn deactivate(&self) { + if !self.active.get() { + return; + } + self.active.set(false); + let state = &self.client.state; + for seat in state.globals.seats.lock().values() { + seat.clear_focus_grab(); + } + } +} + +impl SeatFocusGrab for HyprlandFocusGrabV1 { + fn contains_node(&self, node_id: NodeId) -> bool { + self.committed.borrow().contains_key(&node_id) + } + + fn clear(self: Rc) { + if !self.active.get() { + return; + } + self.active.set(false); + self.send_cleared(); + let state = &self.client.state; + for seat in state.globals.seats.lock().values() { + seat.clear_focus_grab(); + } + } +} + +impl HyprlandFocusGrabV1RequestHandler for HyprlandFocusGrabV1 { + type Error = HyprlandFocusGrabV1Error; + + fn add_surface(&self, req: AddSurface, _slf: &Rc) -> Result<(), Self::Error> { + let surface = self.client.lookup(req.surface)?; + let node_id: NodeId = surface.node_id.into(); + let mut pending = self.pending.borrow_mut(); + let pending = pending.get_or_insert_with(|| self.committed.borrow().clone()); + pending.insert(node_id, surface); + Ok(()) + } + + fn remove_surface(&self, req: RemoveSurface, _slf: &Rc) -> Result<(), Self::Error> { + let surface = self.client.lookup(req.surface)?; + let node_id: NodeId = surface.node_id.into(); + let mut pending = self.pending.borrow_mut(); + let pending = pending.get_or_insert_with(|| self.committed.borrow().clone()); + pending.remove(&node_id); + Ok(()) + } + + fn commit(&self, _req: Commit, slf: &Rc) -> Result<(), Self::Error> { + let was_empty = self.committed.borrow().is_empty(); + if let Some(new_committed) = self.pending.borrow_mut().take() { + *self.committed.borrow_mut() = new_committed; + } + let is_empty = self.committed.borrow().is_empty(); + match (was_empty, is_empty) { + (true, false) => slf.activate(), + (false, true) => self.deactivate(), + _ => {} + } + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.deactivate(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = HyprlandFocusGrabV1; + version = self.version; +} + +impl Object for HyprlandFocusGrabV1 {} + +simple_add_obj!(HyprlandFocusGrabV1); + +#[derive(Debug, Error)] +pub enum HyprlandFocusGrabV1Error { + #[error(transparent)] + ClientError(Box), +} + +efrom!(HyprlandFocusGrabV1Error, ClientError); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 99ede1c5..ec52f374 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -83,8 +83,8 @@ use { rect::Rect, state::{DeviceHandlerData, State}, tree::{ - ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeLayer, NodeLayerLink, - NodeLocation, OutputNode, StackedNode, ToplevelNode, WorkspaceNode, + ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeId, NodeLayer, + NodeLayerLink, NodeLocation, OutputNode, StackedNode, ToplevelNode, WorkspaceNode, generic_node_visitor, toplevel_create_split, toplevel_parent_container, toplevel_set_floating, toplevel_set_workspace, }, @@ -164,6 +164,11 @@ impl Drop for DroppedDnd { } } +pub trait SeatFocusGrab { + fn contains_node(&self, node_id: NodeId) -> bool; + fn clear(self: Rc); +} + linear_ids!(PhysicalKeyboardIds, PhysicalKeyboardId, u64); pub struct PhysicalKeyboard { @@ -258,6 +263,7 @@ pub struct WlSeatGlobal { warp_mouse_to_focus_scheduled: Cell, warp_mouse_to_focus_skip_target_check: Cell, mouse_follows_focus: Cell, + pub focus_grab: CloneCell>>, } impl PartialEq for WlSeatGlobal { @@ -403,6 +409,7 @@ impl WlSeatGlobal { warp_mouse_to_focus_scheduled: Cell::new(false), warp_mouse_to_focus_skip_target_check: Cell::new(false), mouse_follows_focus: Cell::new(false), + focus_grab: Default::default(), }); slf.pointer_cursor.set_owner(slf.clone()); slf.modifiers_listener diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 0c8fd488..877151c4 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -1140,6 +1140,11 @@ impl WlSeatGlobal { self.kb_owner.ungrab(self); } + pub fn clear_focus_grab(self: &Rc) { + self.focus_grab.take(); + self.ungrab_kb(); + } + pub fn grab(self: &Rc, node: Rc) { self.kb_owner.grab(self, node); } diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index e9f4ced1..a946a8a5 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -400,6 +400,7 @@ impl PointerOwner for SimplePointerOwner { seat.state .root .node_find_tree_at(x_int, y_int, &mut found_tree, T::FIND_TREE_USECASE); + self.usecase.filter_found_tree(seat, &mut found_tree); let mut divergence = found_tree.len().min(stack.len()); for (i, (found, stack)) in found_tree.iter().zip(stack.iter()).enumerate() { if found.node.node_id() != stack.node_id() { @@ -717,6 +718,11 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static { fn release_grab(&self, seat: &Rc); + fn filter_found_tree(&self, seat: &Rc, found_tree: &mut Vec) { + let _ = seat; + let _ = found_tree; + } + fn node_focus(&self, seat: &Rc, node: &Rc) { let _ = seat; let _ = node; @@ -829,13 +835,29 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase { fn default_button( &self, _spo: &SimplePointerOwner, - _seat: &Rc, + seat: &Rc, _button: u32, - _pn: &Rc, + pn: &Rc, ) -> bool { + if let Some(grab) = seat.focus_grab.get() { + if !grab.contains_node(pn.node_id()) { + grab.clear(); + return true; + } + } false } + fn filter_found_tree(&self, seat: &Rc, found_tree: &mut Vec) { + if let Some(grab) = seat.focus_grab.get() { + if let Some(leaf) = found_tree.last() { + if !grab.contains_node(leaf.node.node_id()) { + found_tree.truncate(1); + } + } + } + } + fn start_drag( &self, grab: &SimpleGrabPointerOwner, diff --git a/wire/hyprland_focus_grab_manager_v1.txt b/wire/hyprland_focus_grab_manager_v1.txt new file mode 100644 index 00000000..d4fa8d39 --- /dev/null +++ b/wire/hyprland_focus_grab_manager_v1.txt @@ -0,0 +1,6 @@ +request create_grab { + grab: id(hyprland_focus_grab_v1) (new), +} + +request destroy (destructor) { +} diff --git a/wire/hyprland_focus_grab_v1.txt b/wire/hyprland_focus_grab_v1.txt new file mode 100644 index 00000000..22f00e42 --- /dev/null +++ b/wire/hyprland_focus_grab_v1.txt @@ -0,0 +1,16 @@ +request add_surface { + surface: id(wl_surface), +} + +request remove_surface { + surface: id(wl_surface), +} + +request commit { +} + +request destroy (destructor) { +} + +event cleared { +}