From fb8d4c2700376814ce30a40da9a16a9074602eea Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 2 May 2024 15:33:45 +0200 Subject: [PATCH 1/5] seat: remove dead self-move code --- src/ifs/wl_seat.rs | 14 ++------------ src/ifs/wl_seat/event_handling.rs | 9 +-------- src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs | 8 +------- src/tree.rs | 1 + 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 995cf94d..57a57305 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -67,8 +67,8 @@ use { state::{DeviceHandlerData, State}, time::now_usec, tree::{ - generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode, - Node, OutputNode, ToplevelNode, WorkspaceNode, + generic_node_visitor, ContainerNode, ContainerSplit, Direction, FoundNode, Node, + OutputNode, ToplevelNode, WorkspaceNode, }, utils::{ asyncevent::AsyncEvent, bindings::PerClientBindings, clonecell::CloneCell, @@ -140,9 +140,6 @@ pub struct WlSeatGlobal { name: GlobalName, state: Rc, seat_name: String, - move_: Cell, - move_start_pos: Cell<(Fixed, Fixed)>, - extents_start_pos: Cell<(i32, i32)>, pos_time_usec: Cell, pos: Cell<(Fixed, Fixed)>, pointer_stack: RefCell>>, @@ -220,9 +217,6 @@ impl WlSeatGlobal { name, state: state.clone(), seat_name: seat_name.to_string(), - move_: Cell::new(false), - move_start_pos: Cell::new((Fixed(0), Fixed(0))), - extents_start_pos: Cell::new((0, 0)), pos_time_usec: Cell::new(0), pos: Cell::new((Fixed(0), Fixed(0))), pointer_stack: RefCell::new(vec![]), @@ -1235,10 +1229,6 @@ impl WlSeat { }) } - pub fn move_(&self, node: &Rc) { - self.global.move_(node); - } - pub fn keymap_fd(&self, state: &KeyboardState) -> Result, WlKeyboardError> { if self.version >= READ_ONLY_KEYMAP_SINCE { return Ok(state.map.clone()); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 0244838a..a314af1f 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -31,7 +31,7 @@ use { }, object::Version, state::DeviceHandlerData, - tree::{Direction, FloatNode, Node, ToplevelNode}, + tree::{Direction, Node, ToplevelNode}, utils::{bitflags::BitflagsExt, smallmap::SmallMap}, wire::WlDataOfferId, xkbcommon::{KeyboardState, XkbState, XKB_KEY_DOWN, XKB_KEY_UP}, @@ -631,13 +631,6 @@ impl WlSeatGlobal { self.pointer_stack.borrow().last().cloned() } - pub fn move_(&self, node: &Rc) { - self.move_.set(true); - self.move_start_pos.set(self.pos.get()); - let ex = node.position.get(); - self.extents_start_pos.set((ex.x1(), ex.y1())); - } - pub fn focus_toplevel(self: &Rc, n: Rc) { let node = match n.tl_focus_child(self.id) { Some(n) => n, diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 31ab0432..185060dc 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -260,13 +260,7 @@ impl XdgToplevelRequestHandler for XdgToplevel { Ok(()) } - fn move_(&self, req: Move, _slf: &Rc) -> Result<(), Self::Error> { - let seat = self.xdg.surface.client.lookup(req.seat)?; - if let Some(parent) = self.toplevel_data.parent.get() { - if let Some(float) = parent.node_into_float() { - seat.move_(&float); - } - } + fn move_(&self, _req: Move, _slf: &Rc) -> Result<(), Self::Error> { Ok(()) } diff --git a/src/tree.rs b/src/tree.rs index 394cb636..89b6160c 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -346,6 +346,7 @@ pub trait Node: 'static { // TYPE CONVERTERS + #[cfg_attr(not(feature = "it"), allow(dead_code))] fn node_into_float(self: Rc) -> Option> { None } From dc97827f7a62d4907eb144ed25f5a68c2cb86a9f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 2 May 2024 13:10:20 +0200 Subject: [PATCH 2/5] seat: optimize seat-queue handling --- src/ifs/wl_seat.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 57a57305..6f073be1 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -173,7 +173,7 @@ pub struct WlSeatGlobal { gesture_owner: GestureOwnerHolder, dropped_dnd: RefCell>, shortcuts: RefCell>>, - queue_link: Cell>>>, + queue_link: RefCell>>>, tree_changed_handler: Cell>>, output: CloneCell>, desired_known_cursor: Cell>, @@ -243,7 +243,7 @@ impl WlSeatGlobal { gesture_owner: Default::default(), dropped_dnd: RefCell::new(None), shortcuts: Default::default(), - queue_link: Cell::new(None), + queue_link: Default::default(), tree_changed_handler: Cell::new(None), output: CloneCell::new(state.dummy_output.get().unwrap()), desired_known_cursor: Cell::new(None), @@ -495,8 +495,12 @@ impl WlSeatGlobal { } pub fn mark_last_active(self: &Rc) { - self.queue_link - .set(Some(self.state.seat_queue.add_last(self.clone()))); + let link = &mut *self.queue_link.borrow_mut(); + if let Some(link) = link { + self.state.seat_queue.add_last_existing(link) + } else { + *link = Some(self.state.seat_queue.add_last(self.clone())) + } } pub fn disable_pointer_constraint(&self) { @@ -1084,7 +1088,7 @@ impl WlSeatGlobal { self.pointer_owner.clear(); self.kb_owner.clear(); *self.dropped_dnd.borrow_mut() = None; - self.queue_link.set(None); + self.queue_link.take(); self.tree_changed_handler.set(None); self.output.set(self.state.dummy_output.get().unwrap()); self.constraint.take(); From efdca4de49e61438bdf7d592219974e73de48a6e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 30 Apr 2024 22:45:07 +0200 Subject: [PATCH 3/5] seat: add cursor groups --- src/compositor.rs | 5 + src/config/handler.rs | 22 +- src/cursor_user.rs | 462 ++++++++++++++++++ src/gfx_api.rs | 18 +- src/ifs/jay_input.rs | 7 +- src/ifs/jay_pointer.rs | 2 +- src/ifs/wl_seat.rs | 294 ++--------- src/ifs/wl_seat/event_handling.rs | 43 +- src/ifs/wl_seat/pointer_owner.rs | 10 +- src/ifs/wl_seat/wl_pointer.rs | 4 +- src/ifs/wl_surface.rs | 9 +- src/ifs/wl_surface/cursor.rs | 19 +- src/ifs/wl_surface/x_surface/xwindow.rs | 2 +- src/ifs/wl_surface/xdg_surface/xdg_popup.rs | 2 +- .../wl_surface/xdg_surface/xdg_toplevel.rs | 2 +- src/ifs/wp_cursor_shape_device_v1.rs | 2 +- src/ifs/xdg_toplevel_drag_v1.rs | 2 +- src/it/testrun.rs | 2 +- src/it/tests/t0030_cursor_shape.rs | 2 +- src/main.rs | 1 + src/state.rs | 84 +++- src/tasks/connector.rs | 16 +- src/tree/container.rs | 4 +- src/tree/display.rs | 2 +- src/tree/float.rs | 4 +- src/tree/output.rs | 6 +- src/tree/placeholder.rs | 2 +- src/tree/workspace.rs | 2 +- 28 files changed, 629 insertions(+), 401 deletions(-) create mode 100644 src/cursor_user.rs diff --git a/src/compositor.rs b/src/compositor.rs index eeebec41..ae663ce0 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -236,6 +236,11 @@ fn start_compositor2( explicit_sync_enabled: Cell::new(true), keyboard_state_ids: Default::default(), security_context_acceptors: Default::default(), + cursor_user_group_ids: Default::default(), + cursor_user_ids: Default::default(), + cursor_users: Default::default(), + cursor_user_groups: Default::default(), + cursor_user_group_hardware_cursor: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index a5e74c19..2b97c782 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -956,7 +956,7 @@ impl ConfigProxyHandler { if size < 0 { return Err(CphError::NegativeCursorSize); } - seat.set_cursor_size(size as _); + seat.cursor_group().set_cursor_size(size as _); Ok(()) } @@ -972,14 +972,7 @@ impl ConfigProxyHandler { use_hardware_cursor: bool, ) -> Result<(), CphError> { let seat = self.get_seat(seat)?; - if use_hardware_cursor { - for other in self.state.globals.seats.lock().values() { - if other.id() != seat.id() { - other.set_hardware_cursor(false); - } - } - } - seat.set_hardware_cursor(use_hardware_cursor); + seat.cursor_group().set_hardware_cursor(use_hardware_cursor); self.state.refresh_hardware_cursors(); Ok(()) } @@ -1037,18 +1030,7 @@ impl ConfigProxyHandler { if x < 0 || y < 0 || x > MAX_EXTENTS || y > MAX_EXTENTS { return Err(CphError::InvalidConnectorPosition(x, y)); } - let old_pos = connector.global.pos.get(); connector.set_position(x, y); - let seats = self.state.globals.seats.lock(); - for seat in seats.values() { - if seat.get_output().id == connector.id { - let seat_pos = seat.position(); - seat.set_position( - seat_pos.0.round_down() + x - old_pos.x1(), - seat_pos.1.round_down() + y - old_pos.y1(), - ); - } - } Ok(()) } diff --git a/src/cursor_user.rs b/src/cursor_user.rs new file mode 100644 index 00000000..f16abf3c --- /dev/null +++ b/src/cursor_user.rs @@ -0,0 +1,462 @@ +use { + crate::{ + cursor::{Cursor, KnownCursor, DEFAULT_CURSOR_SIZE}, + fixed::Fixed, + rect::Rect, + state::State, + tree::OutputNode, + utils::{ + clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, rc_eq::rc_eq, + transform_ext::TransformExt, + }, + }, + std::{cell::Cell, ops::Deref, rc::Rc}, +}; + +linear_ids!(CursorUserGroupIds, CursorUserGroupId, u64); +linear_ids!(CursorUserIds, CursorUserId, u64); + +pub trait CursorUserOwner { + fn output_changed(&self, output: &Rc); +} + +pub struct CursorUserGroup { + pub id: CursorUserGroupId, + state: Rc, + active_id: Cell>, + active: CloneCell>>, + users: CopyHashMap>, + hardware_cursor: Cell, + size: Cell, + latest_output: CloneCell>, +} + +pub struct CursorUser { + pub id: CursorUserId, + group: Rc, + desired_known_cursor: Cell>, + cursor: CloneCell>>, + output: CloneCell>, + output_pos: Cell, + pos: Cell<(Fixed, Fixed)>, + owner: CloneCell>>, +} + +impl CursorUserGroup { + pub fn create(state: &Rc) -> Rc { + let output = state + .root + .outputs + .lock() + .values() + .next() + .cloned() + .or_else(|| state.dummy_output.get()) + .unwrap(); + let hardware_cursor = state.cursor_user_group_hardware_cursor.is_none(); + let group = Rc::new(Self { + id: state.cursor_user_group_ids.next(), + state: state.clone(), + active_id: Default::default(), + active: Default::default(), + users: Default::default(), + hardware_cursor: Cell::new(hardware_cursor), + size: Cell::new(*DEFAULT_CURSOR_SIZE), + latest_output: CloneCell::new(output), + }); + state.add_cursor_size(*DEFAULT_CURSOR_SIZE); + state.cursor_user_groups.set(group.id, group.clone()); + if hardware_cursor { + state + .cursor_user_group_hardware_cursor + .set(Some(group.clone())); + } + group + } + + pub fn deactivate(&self) { + if self.hardware_cursor.get() { + self.remove_hardware_cursor(); + } + self.active_id.take(); + self.active.take(); + self.state.damage(); + } + + pub fn latest_output(&self) -> Rc { + self.latest_output.get() + } + + fn remove_hardware_cursor(&self) { + self.state.hardware_tick_cursor.push(None); + self.state.disable_hardware_cursors(); + self.state.cursor_user_group_hardware_cursor.take(); + } + + pub fn detach(&self) { + self.deactivate(); + self.latest_output + .set(self.state.dummy_output.get().unwrap()); + self.state.remove_cursor_size(self.size.get()); + self.state.cursor_user_groups.remove(&self.id); + for (_, user) in self.users.lock().drain() { + user.detach(); + } + } + + pub fn create_user(self: &Rc) -> Rc { + let output = self.latest_output.get(); + let user = Rc::new(CursorUser { + id: self.state.cursor_user_ids.next(), + group: self.clone(), + desired_known_cursor: Cell::new(None), + cursor: Default::default(), + pos: Cell::new(self.output_center(&output)), + output_pos: Cell::new(output.global.pos.get()), + output: CloneCell::new(output), + owner: Default::default(), + }); + self.users.set(user.id, user.clone()); + user + } + + pub fn set_visible(&self, visible: bool) { + if let Some(user) = self.active.get() { + if let Some(cursor) = user.cursor.get() { + cursor.set_visible(visible); + } + } + } + + pub fn active(&self) -> Option> { + self.active.get() + } + + pub fn render_ctx_changed(&self) { + for user in self.users.lock().values() { + if let Some(cursor) = user.desired_known_cursor.get() { + user.set_known(cursor); + } + } + } + + pub fn reload_known_cursor(&self) { + for user in self.users.lock().values() { + user.reload_known_cursor(); + } + } + + pub fn set_hardware_cursor(self: &Rc, hardware_cursor: bool) { + if self.hardware_cursor.replace(hardware_cursor) == hardware_cursor { + return; + } + if hardware_cursor { + let prev = self + .state + .cursor_user_group_hardware_cursor + .set(Some(self.clone())); + if let Some(prev) = prev { + prev.hardware_cursor.set(false); + } + match self.active.get() { + None => self.remove_hardware_cursor(), + Some(a) => a.update_hardware_cursor(), + } + } else { + self.remove_hardware_cursor(); + } + } + + pub fn hardware_cursor(&self) -> bool { + self.hardware_cursor.get() + } + + pub fn set_cursor_size(&self, size: u32) { + let old = self.size.replace(size); + if size != old { + self.state.remove_cursor_size(old); + self.state.add_cursor_size(size); + self.reload_known_cursor(); + } + } + + fn output_center(&self, output: &Rc) -> (Fixed, Fixed) { + let pos = output.global.pos.get(); + let x = Fixed::from_int((pos.x1() + pos.x2()) / 2); + let y = Fixed::from_int((pos.y1() + pos.y2()) / 2); + (x, y) + } + + pub fn first_output_connected(&self, output: &Rc) { + self.latest_output.set(output.clone()); + let (x, y) = self.output_center(output); + for user in self.users.lock().values() { + user.set_output(output); + user.set_position(x, y); + } + } + + pub fn output_disconnected(&self, output: &Rc, next: &Rc) { + if self.latest_output.get().id == output.id { + self.latest_output.set(next.clone()); + } + let (x, y) = self.output_center(next); + for user in self.users.lock().values() { + if user.output.get().id == output.id { + user.set_output(next); + user.set_position(x, y); + } + } + } + + pub fn output_pos_changed(&self, output: &Rc) { + let (x, y) = self.output_center(output); + for user in self.users.lock().values() { + if user.output.get().id == output.id { + user.output_pos.set(output.global.pos.get()); + user.set_position(x, y); + } + } + } +} + +impl CursorUser { + pub fn set_owner(&self, owner: Rc) { + self.owner.set(Some(owner)); + } + + pub fn detach(&self) { + self.set(None); + self.owner.take(); + self.group.users.remove(&self.id); + if self.group.active_id.get() == Some(self.id) { + self.group.active_id.take(); + self.group.active.take(); + self.group.state.damage(); + } + } + + pub fn activate(self: &Rc) { + if self.group.active_id.replace(Some(self.id)) == Some(self.id) { + return; + } + self.group.latest_output.set(self.output.get()); + self.group.active.set(Some(self.clone())); + self.update_hardware_cursor(); + self.group.state.damage(); + } + + #[cfg_attr(not(feature = "it"), allow(dead_code))] + pub fn desired_known_cursor(&self) -> Option { + self.desired_known_cursor.get() + } + + pub fn set_known(&self, cursor: KnownCursor) { + self.desired_known_cursor.set(Some(cursor)); + let cursors = match self.group.state.cursors.get() { + Some(c) => c, + None => { + self.set_cursor2(None); + return; + } + }; + let tpl = match cursor { + KnownCursor::Default => &cursors.default, + KnownCursor::ContextMenu => &cursors.context_menu, + KnownCursor::Help => &cursors.help, + KnownCursor::Pointer => &cursors.pointer, + KnownCursor::Progress => &cursors.progress, + KnownCursor::Wait => &cursors.wait, + KnownCursor::Cell => &cursors.cell, + KnownCursor::Crosshair => &cursors.crosshair, + KnownCursor::Text => &cursors.text, + KnownCursor::VerticalText => &cursors.vertical_text, + KnownCursor::Alias => &cursors.alias, + KnownCursor::Copy => &cursors.copy, + KnownCursor::Move => &cursors.r#move, + KnownCursor::NoDrop => &cursors.no_drop, + KnownCursor::NotAllowed => &cursors.not_allowed, + KnownCursor::Grab => &cursors.grab, + KnownCursor::Grabbing => &cursors.grabbing, + KnownCursor::EResize => &cursors.e_resize, + KnownCursor::NResize => &cursors.n_resize, + KnownCursor::NeResize => &cursors.ne_resize, + KnownCursor::NwResize => &cursors.nw_resize, + KnownCursor::SResize => &cursors.s_resize, + KnownCursor::SeResize => &cursors.se_resize, + KnownCursor::SwResize => &cursors.sw_resize, + KnownCursor::WResize => &cursors.w_resize, + KnownCursor::EwResize => &cursors.ew_resize, + KnownCursor::NsResize => &cursors.ns_resize, + KnownCursor::NeswResize => &cursors.nesw_resize, + KnownCursor::NwseResize => &cursors.nwse_resize, + KnownCursor::ColResize => &cursors.col_resize, + KnownCursor::RowResize => &cursors.row_resize, + KnownCursor::AllScroll => &cursors.all_scroll, + KnownCursor::ZoomIn => &cursors.zoom_in, + KnownCursor::ZoomOut => &cursors.zoom_out, + }; + self.set_cursor2(Some(tpl.instantiate(self.group.size.get()))); + } + + fn set_output(&self, output: &Rc) { + self.output.set(output.clone()); + self.output_pos.set(output.global.pos.get()); + if self.is_active() { + self.group.latest_output.set(output.clone()); + } + if let Some(cursor) = self.cursor.get() { + cursor.set_output(output); + } + if let Some(owner) = self.owner.get() { + owner.output_changed(output); + } + } + + pub fn output(&self) -> Rc { + self.output.get() + } + + pub fn get(&self) -> Option> { + self.cursor.get() + } + + pub fn set(&self, cursor: Option>) { + self.set_cursor2(cursor); + self.desired_known_cursor.set(None); + } + + fn is_active(&self) -> bool { + self.group.active_id.get() == Some(self.id) + } + + fn set_cursor2(&self, cursor: Option>) { + if let Some(old) = self.cursor.get() { + if let Some(new) = cursor.as_ref() { + if rc_eq(&old, new) { + self.update_hardware_cursor(); + return; + } + } + old.handle_unset(); + } + if let Some(cursor) = cursor.as_ref() { + cursor.clone().handle_set(); + cursor.set_output(&self.output.get()); + } + self.cursor.set(cursor.clone()); + self.update_hardware_cursor(); + } + + pub fn position(&self) -> (Fixed, Fixed) { + self.pos.get() + } + + pub fn set_position(&self, mut x: Fixed, mut y: Fixed) -> (Fixed, Fixed) { + let x_int = x.round_down(); + let y_int = y.round_down(); + if !self.output_pos.get().contains(x_int, y_int) { + let (output, x_tmp, y_tmp) = self.group.state.find_closest_output(x_int, y_int); + self.set_output(&output); + x = x.apply_fract(x_tmp); + y = y.apply_fract(y_tmp); + } + self.pos.set((x, y)); + self.update_hardware_cursor_(false); + (x, y) + } + + pub fn update_hardware_cursor(&self) { + self.update_hardware_cursor_(true); + } + + fn hardware_cursor(&self) -> bool { + self.is_active() && self.group.hardware_cursor.get() + } + + fn update_hardware_cursor_(&self, render: bool) { + if !self.hardware_cursor() { + return; + } + let cursor = self.cursor.get(); + self.group.state.hardware_tick_cursor.push(cursor.clone()); + let cursor = match cursor { + Some(c) => c, + _ => { + self.group.state.disable_hardware_cursors(); + return; + } + }; + if render { + cursor.tick(); + } + let (x, y) = self.pos.get(); + for output in self.group.state.root.outputs.lock().values() { + if let Some(hc) = output.hardware_cursor.get() { + let transform = output.global.persistent.transform.get(); + let render = render | output.hardware_cursor_needs_render.take(); + let scale = output.global.persistent.scale.get(); + let extents = cursor.extents_at_scale(scale); + let (hc_width, hc_height) = hc.size(); + if render { + let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); + if extents.width() > max_width || extents.height() > max_height { + hc.set_enabled(false); + hc.commit(); + continue; + } + } + let opos = output.global.pos.get(); + let (x_rel, y_rel); + if scale == 1 { + x_rel = x.round_down() - opos.x1(); + y_rel = y.round_down() - opos.y1(); + } else { + let scalef = scale.to_f64(); + x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32; + y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32; + } + let (width, height) = output.global.pixel_size(); + if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { + if render { + let buffer = hc.get_buffer(); + let res = buffer.render_hardware_cursor( + cursor.deref(), + &self.group.state, + scale, + transform, + ); + match res { + Ok(sync_file) => { + hc.set_sync_file(sync_file); + hc.swap_buffer(); + } + Err(e) => { + log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); + } + } + } + hc.set_enabled(true); + let mode = output.global.mode.get(); + let (x_rel, y_rel) = + transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); + let (hot_x, hot_y) = + transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); + hc.set_position(x_rel - hot_x, y_rel - hot_y); + } else { + if render { + output.hardware_cursor_needs_render.set(true); + } + hc.set_enabled(false); + } + hc.commit(); + } + } + } + + fn reload_known_cursor(&self) { + if let Some(kc) = self.desired_known_cursor.get() { + self.set_known(kc); + } + } +} diff --git a/src/gfx_api.rs b/src/gfx_api.rs index a8f79551..51b45d8c 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -345,7 +345,7 @@ impl dyn GfxFramebuffer { if let Some(rect) = cursor_rect { let seats = state.globals.lock_seats(); for seat in seats.values() { - let (mut x, mut y) = seat.get_position(); + let (x, y) = seat.pointer_cursor().position(); if let Some(im) = seat.input_method() { for (_, popup) in &im.popups { if popup.surface.node_visible() { @@ -380,12 +380,16 @@ impl dyn GfxFramebuffer { } } if render_cursor { - if let Some(cursor) = seat.get_cursor() { - if render_hardware_cursor || !seat.hardware_cursor() { - cursor.tick(); - x -= Fixed::from_int(rect.x1()); - y -= Fixed::from_int(rect.y1()); - cursor.render(&mut renderer, x, y); + let cursor_user_group = seat.cursor_group(); + if render_hardware_cursor || !cursor_user_group.hardware_cursor() { + if let Some(cursor_user) = cursor_user_group.active() { + if let Some(cursor) = cursor_user.get() { + cursor.tick(); + let (mut x, mut y) = cursor_user.position(); + x -= Fixed::from_int(rect.x1()); + y -= Fixed::from_int(rect.y1()); + cursor.render(&mut renderer, x, y); + } } } } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index f9368f6e..13284139 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -57,7 +57,7 @@ impl JayInput { name: data.seat_name(), repeat_rate: data.get_rate().0, repeat_delay: data.get_rate().1, - hardware_cursor: data.hardware_cursor() as _, + hardware_cursor: data.cursor_group().hardware_cursor() as _, }); } @@ -202,7 +202,8 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let seat = self.seat(req.seat)?; - seat.set_hardware_cursor(req.use_hardware_cursor != 0); + seat.cursor_group() + .set_hardware_cursor(req.use_hardware_cursor != 0); Ok(()) }) } @@ -316,7 +317,7 @@ impl JayInputRequestHandler for JayInput { fn set_cursor_size(&self, req: SetCursorSize, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let seat = self.seat(req.seat)?; - seat.set_cursor_size(req.size); + seat.cursor_group().set_cursor_size(req.size); Ok(()) }) } diff --git a/src/ifs/jay_pointer.rs b/src/ifs/jay_pointer.rs index 35170189..1e8fdb86 100644 --- a/src/ifs/jay_pointer.rs +++ b/src/ifs/jay_pointer.rs @@ -42,7 +42,7 @@ impl JayPointerRequestHandler for JayPointer { if pointer_node.node_client_id() != Some(self.client.id) { return Ok(()); } - self.seat.set_known_cursor(cursor); + self.seat.pointer_cursor().set_known(cursor); Ok(()) } } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 6f073be1..440d6ae0 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -22,7 +22,7 @@ use { crate::{ async_engine::SpawnedFuture, client::{Client, ClientError, ClientId}, - cursor::{Cursor, KnownCursor, DEFAULT_CURSOR_SIZE}, + cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, fixed::Fixed, globals::{Global, GlobalName}, ifs::{ @@ -63,7 +63,6 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, state::{DeviceHandlerData, State}, time::now_usec, tree::{ @@ -73,7 +72,7 @@ use { utils::{ asyncevent::AsyncEvent, bindings::PerClientBindings, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, linkedlist::LinkedNode, numcell::NumCell, - rc_eq::rc_eq, smallmap::SmallMap, transform_ext::TransformExt, + rc_eq::rc_eq, smallmap::SmallMap, }, wire::{ wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId, @@ -141,7 +140,6 @@ pub struct WlSeatGlobal { state: Rc, seat_name: String, pos_time_usec: Cell, - pos: Cell<(Fixed, Fixed)>, pointer_stack: RefCell>>, pointer_stack_modified: Cell, found_tree: RefCell>, @@ -162,7 +160,8 @@ pub struct WlSeatGlobal { seat_xkb_state: CloneCell>>, latest_kb_state: CloneCell>, xkb_states: CopyHashMap>>, - cursor: CloneCell>>, + cursor_user_group: Rc, + pointer_cursor: Rc, tree_changed: Rc, selection: CloneCell>>, selection_serial: Cell, @@ -175,11 +174,7 @@ pub struct WlSeatGlobal { shortcuts: RefCell>>, queue_link: RefCell>>>, tree_changed_handler: Cell>>, - output: CloneCell>, - desired_known_cursor: Cell>, changes: NumCell, - cursor_size: Cell, - hardware_cursor: Cell, constraint: CloneCell>>, idle_notifications: CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc>, last_input_usec: Cell, @@ -197,12 +192,6 @@ pub struct WlSeatGlobal { const CHANGE_CURSOR_MOVED: u32 = 1 << 0; const CHANGE_TREE: u32 = 1 << 1; -impl Drop for WlSeatGlobal { - fn drop(&mut self) { - self.state.remove_cursor_size(self.cursor_size.get()); - } -} - impl WlSeatGlobal { pub fn new(name: GlobalName, seat_name: &str, state: &Rc) -> Rc { let seat_xkb_state = state @@ -212,13 +201,15 @@ impl WlSeatGlobal { .unwrap(); let xkb_states = CopyHashMap::new(); xkb_states.set(state.default_keymap.id, Rc::downgrade(&seat_xkb_state)); + let cursor_user_group = CursorUserGroup::create(state); + let cursor_user = cursor_user_group.create_user(); + cursor_user.activate(); let slf = Rc::new(Self { id: state.seat_ids.next(), name, state: state.clone(), seat_name: seat_name.to_string(), pos_time_usec: Cell::new(0), - pos: Cell::new((Fixed(0), Fixed(0))), pointer_stack: RefCell::new(vec![]), pointer_stack_modified: Cell::new(false), found_tree: RefCell::new(vec![]), @@ -232,7 +223,8 @@ impl WlSeatGlobal { seat_xkb_state: CloneCell::new(seat_xkb_state.clone()), latest_kb_state: CloneCell::new(seat_xkb_state.clone()), xkb_states, - cursor: Default::default(), + cursor_user_group, + pointer_cursor: cursor_user, tree_changed: Default::default(), selection: Default::default(), selection_serial: Cell::new(0), @@ -245,11 +237,7 @@ impl WlSeatGlobal { shortcuts: Default::default(), queue_link: Default::default(), tree_changed_handler: Cell::new(None), - output: CloneCell::new(state.dummy_output.get().unwrap()), - desired_known_cursor: Cell::new(None), changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE), - cursor_size: Cell::new(*DEFAULT_CURSOR_SIZE), - hardware_cursor: Cell::new(state.globals.seats.len() == 0), constraint: Default::default(), idle_notifications: Default::default(), last_input_usec: Cell::new(now_usec()), @@ -264,7 +252,7 @@ impl WlSeatGlobal { pinch_bindings: Default::default(), hold_bindings: Default::default(), }); - state.add_cursor_size(*DEFAULT_CURSOR_SIZE); + slf.pointer_cursor.set_owner(slf.clone()); let seat = slf.clone(); let future = state.eng.spawn(async move { loop { @@ -291,109 +279,6 @@ impl WlSeatGlobal { self.pointer_owner.toplevel_drag() } - pub fn set_hardware_cursor(&self, hardware_cursor: bool) { - self.hardware_cursor.set(hardware_cursor); - } - - pub fn hardware_cursor(&self) -> bool { - self.hardware_cursor.get() - } - - fn update_hardware_cursor_position(&self) { - self.update_hardware_cursor_(false); - } - - pub fn update_hardware_cursor(&self) { - self.update_hardware_cursor_(true); - } - - fn update_hardware_cursor_(&self, render: bool) { - if !self.hardware_cursor.get() { - return; - } - let cursor = match self.get_cursor() { - Some(c) => c, - _ => { - self.state.disable_hardware_cursors(); - return; - } - }; - if render { - cursor.tick(); - } - let (x, y) = self.get_position(); - for output in self.state.root.outputs.lock().values() { - if let Some(hc) = output.hardware_cursor.get() { - let transform = output.global.persistent.transform.get(); - let render = render | output.hardware_cursor_needs_render.take(); - let scale = output.global.persistent.scale.get(); - let extents = cursor.extents_at_scale(scale); - let (hc_width, hc_height) = hc.size(); - if render { - let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); - if extents.width() > max_width || extents.height() > max_height { - hc.set_enabled(false); - hc.commit(); - continue; - } - } - let opos = output.global.pos.get(); - let (x_rel, y_rel); - if scale == 1 { - x_rel = x.round_down() - opos.x1(); - y_rel = y.round_down() - opos.y1(); - } else { - let scalef = scale.to_f64(); - x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32; - y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32; - } - let (width, height) = output.global.pixel_size(); - if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { - if render { - let buffer = hc.get_buffer(); - let res = buffer.render_hardware_cursor( - cursor.deref(), - &self.state, - scale, - transform, - ); - match res { - Ok(sync_file) => { - hc.set_sync_file(sync_file); - hc.swap_buffer(); - } - Err(e) => { - log::error!("Could not render hardware cursor: {}", ErrorFmt(e)); - } - } - } - hc.set_enabled(true); - let mode = output.global.mode.get(); - let (x_rel, y_rel) = - transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); - let (hot_x, hot_y) = - transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); - hc.set_position(x_rel - hot_x, y_rel - hot_y); - } else { - if render { - output.hardware_cursor_needs_render.set(true); - } - hc.set_enabled(false); - } - hc.commit(); - } - } - } - - pub fn set_cursor_size(&self, size: u32) { - let old = self.cursor_size.replace(size); - if size != old { - self.state.remove_cursor_size(old); - self.state.add_cursor_size(size); - self.reload_known_cursor(); - } - } - pub fn add_data_device(&self, device: &Rc) { let mut dd = self.data_devices.borrow_mut(); dd.entry(device.client.id) @@ -452,7 +337,7 @@ impl WlSeatGlobal { } pub fn get_output(&self) -> Rc { - self.output.get() + self.cursor_user_group.latest_output() } pub fn set_workspace(&self, ws: &Rc) { @@ -517,7 +402,7 @@ impl WlSeatGlobal { fn maybe_constrain_pointer_node(&self) { if let Some(pn) = self.pointer_node() { if let Some(surface) = pn.node_into_surface() { - let (mut x, mut y) = self.pos.get(); + let (mut x, mut y) = self.pointer_cursor.position(); let (sx, sy) = surface.buffer_abs_pos.get().position(); x -= Fixed::from_int(sx); y -= Fixed::from_int(sy); @@ -608,47 +493,6 @@ impl WlSeatGlobal { self.kb_owner.ungrab(self); } - pub fn set_position(&self, x: i32, y: i32) { - self.pos.set((Fixed::from_int(x), Fixed::from_int(y))); - self.update_hardware_cursor_position(); - self.trigger_tree_changed(); - let output = 'set_output: { - let outputs = self.state.root.outputs.lock(); - for output in outputs.values() { - if output.global.pos.get().contains(x, y) { - break 'set_output output.clone(); - } - } - self.state.dummy_output.get().unwrap() - }; - self.set_output(&output); - } - - fn set_output(&self, output: &Rc) { - self.output.set(output.clone()); - if let Some(cursor) = self.cursor.get() { - cursor.set_output(output); - } - if let Some(dnd) = self.pointer_owner.dnd_icon() { - dnd.set_output(output); - } - if let Some(drag) = self.pointer_owner.toplevel_drag() { - if let Some(tl) = drag.toplevel.get() { - tl.xdg.set_output(output); - } - } - } - - pub fn position(&self) -> (Fixed, Fixed) { - self.pos.get() - } - - pub fn render_ctx_changed(&self) { - if let Some(cursor) = self.desired_known_cursor.get() { - self.set_known_cursor(cursor); - } - } - pub fn kb_parent_container(&self) -> Option> { if let Some(tl) = self.keyboard_node.get().node_toplevel() { if let Some(parent) = tl.tl_data().parent.get() { @@ -877,7 +721,7 @@ impl WlSeatGlobal { serial: u32, ) -> Result<(), WlSeatError> { if let Some(icon) = &icon { - icon.set_output(&self.output.get()); + icon.set_output(&self.pointer_cursor.output()); } self.pointer_owner .start_drag(self, origin, source, icon, serial) @@ -969,89 +813,6 @@ impl WlSeatGlobal { self.primary_selection.get() } - pub fn reload_known_cursor(&self) { - if let Some(kc) = self.desired_known_cursor.get() { - self.set_known_cursor(kc); - } - } - - #[cfg_attr(not(feature = "it"), allow(dead_code))] - pub fn get_desired_known_cursor(&self) -> Option { - self.desired_known_cursor.get() - } - - pub fn set_known_cursor(&self, cursor: KnownCursor) { - self.desired_known_cursor.set(Some(cursor)); - let cursors = match self.state.cursors.get() { - Some(c) => c, - None => { - self.set_cursor2(None); - return; - } - }; - let tpl = match cursor { - KnownCursor::Default => &cursors.default, - KnownCursor::ContextMenu => &cursors.context_menu, - KnownCursor::Help => &cursors.help, - KnownCursor::Pointer => &cursors.pointer, - KnownCursor::Progress => &cursors.progress, - KnownCursor::Wait => &cursors.wait, - KnownCursor::Cell => &cursors.cell, - KnownCursor::Crosshair => &cursors.crosshair, - KnownCursor::Text => &cursors.text, - KnownCursor::VerticalText => &cursors.vertical_text, - KnownCursor::Alias => &cursors.alias, - KnownCursor::Copy => &cursors.copy, - KnownCursor::Move => &cursors.r#move, - KnownCursor::NoDrop => &cursors.no_drop, - KnownCursor::NotAllowed => &cursors.not_allowed, - KnownCursor::Grab => &cursors.grab, - KnownCursor::Grabbing => &cursors.grabbing, - KnownCursor::EResize => &cursors.e_resize, - KnownCursor::NResize => &cursors.n_resize, - KnownCursor::NeResize => &cursors.ne_resize, - KnownCursor::NwResize => &cursors.nw_resize, - KnownCursor::SResize => &cursors.s_resize, - KnownCursor::SeResize => &cursors.se_resize, - KnownCursor::SwResize => &cursors.sw_resize, - KnownCursor::WResize => &cursors.w_resize, - KnownCursor::EwResize => &cursors.ew_resize, - KnownCursor::NsResize => &cursors.ns_resize, - KnownCursor::NeswResize => &cursors.nesw_resize, - KnownCursor::NwseResize => &cursors.nwse_resize, - KnownCursor::ColResize => &cursors.col_resize, - KnownCursor::RowResize => &cursors.row_resize, - KnownCursor::AllScroll => &cursors.all_scroll, - KnownCursor::ZoomIn => &cursors.zoom_in, - KnownCursor::ZoomOut => &cursors.zoom_out, - }; - self.set_cursor2(Some(tpl.instantiate(self.cursor_size.get()))); - } - - pub fn set_app_cursor(&self, cursor: Option>) { - self.set_cursor2(cursor); - self.desired_known_cursor.set(None); - } - - fn set_cursor2(&self, cursor: Option>) { - if let Some(old) = self.cursor.get() { - if let Some(new) = cursor.as_ref() { - if rc_eq(&old, new) { - self.update_hardware_cursor(); - return; - } - } - old.handle_unset(); - } - if let Some(cursor) = cursor.as_ref() { - cursor.clone().handle_set(); - cursor.set_output(&self.output.get()); - } - self.cursor.set(cursor.clone()); - self.state.hardware_tick_cursor.push(cursor); - self.update_hardware_cursor(); - } - pub fn dnd_icon(&self) -> Option> { self.pointer_owner.dnd_icon() } @@ -1060,12 +821,12 @@ impl WlSeatGlobal { self.pointer_owner.remove_dnd_icon(); } - pub fn get_position(&self) -> (Fixed, Fixed) { - self.pos.get() + pub fn pointer_cursor(&self) -> &Rc { + &self.pointer_cursor } - pub fn get_cursor(&self) -> Option> { - self.cursor.get() + pub fn cursor_group(&self) -> &Rc { + &self.cursor_user_group } pub fn clear(self: &Rc) { @@ -1082,7 +843,7 @@ impl WlSeatGlobal { self.data_devices.borrow_mut().clear(); self.primary_selection_devices.borrow_mut().clear(); self.wlr_data_devices.clear(); - self.cursor.set(None); + self.cursor_user_group.detach(); self.selection.set(None); self.primary_selection.set(None); self.pointer_owner.clear(); @@ -1090,7 +851,6 @@ impl WlSeatGlobal { *self.dropped_dnd.borrow_mut() = None; self.queue_link.take(); self.tree_changed_handler.set(None); - self.output.set(self.state.dummy_output.get().unwrap()); self.constraint.take(); self.text_inputs.borrow_mut().clear(); self.text_input.take(); @@ -1099,6 +859,7 @@ impl WlSeatGlobal { self.swipe_bindings.clear(); self.pinch_bindings.clear(); self.hold_bindings.clear(); + self.cursor_user_group.detach(); } pub fn id(&self) -> SeatId { @@ -1156,9 +917,7 @@ impl WlSeatGlobal { } pub fn set_visible(&self, visible: bool) { - if let Some(cursor) = self.cursor.get() { - cursor.set_visible(visible); - } + self.cursor_user_group.set_visible(visible); if let Some(icon) = self.dnd_icon() { icon.set_visible(visible); } @@ -1191,6 +950,19 @@ impl WlSeatGlobal { } } +impl CursorUserOwner for WlSeatGlobal { + fn output_changed(&self, output: &Rc) { + if let Some(dnd) = self.pointer_owner.dnd_icon() { + dnd.set_output(output); + } + if let Some(drag) = self.pointer_owner.toplevel_drag() { + if let Some(tl) = drag.toplevel.get() { + tl.xdg.set_output(output); + } + } + } +} + global_base!(WlSeatGlobal, WlSeat, WlSeatError); impl Global for WlSeatGlobal { diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index a314af1f..7bb9cd18 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -120,8 +120,7 @@ impl NodeSeatState { seat.kb_owner.set_kb_node(&seat, seat.state.root.clone()); // log::info!("keyboard_node = root"); if focus_last { - seat.output - .get() + seat.get_output() .node_do_focus(&seat, Direction::Unspecified); } } @@ -320,10 +319,10 @@ impl WlSeatGlobal { Some(o) => o, _ => return, }; - self.set_output(&output); let pos = output.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); + (x, y) = self.pointer_cursor.set_position(x, y); if let Some(c) = self.constraint.get() { if c.ty == ConstraintType::Lock || !c.contains(x.round_down(), y.round_down()) { c.deactivate(); @@ -332,7 +331,7 @@ impl WlSeatGlobal { self.state.for_each_seat_tester(|t| { t.send_pointer_abs(self.id, time_usec, x, y); }); - self.set_new_position(time_usec, x, y); + self.cursor_moved(time_usec); } fn motion_event( @@ -356,7 +355,7 @@ impl WlSeatGlobal { Some(c) if c.ty == ConstraintType::Lock => true, _ => false, }; - let (mut x, mut y) = self.pos.get(); + let (mut x, mut y) = self.pointer_cursor.position(); if !locked { x += dx; y += dy; @@ -383,34 +382,8 @@ impl WlSeatGlobal { dy_unaccelerated, ); }); - let output = self.output.get(); - let pos = output.global.pos.get(); - let mut x_int = x.round_down(); - let mut y_int = y.round_down(); - if !pos.contains(x_int, y_int) { - 'warp: { - let outputs = self.state.root.outputs.lock(); - for output in outputs.values() { - if output.global.pos.get().contains(x_int, y_int) { - self.set_output(output); - break 'warp; - } - } - if x_int < pos.x1() { - x_int = pos.x1(); - } else if x_int >= pos.x2() { - x_int = pos.x2() - 1; - } - if y_int < pos.y1() { - y_int = pos.y1(); - } else if y_int >= pos.y2() { - y_int = pos.y2() - 1; - } - x = Fixed::from_int(x_int); - y = Fixed::from_int(y_int); - } - } - self.set_new_position(time_usec, x, y); + self.pointer_cursor.set_position(x, y); + self.cursor_moved(time_usec); } fn button_event(self: &Rc, time_usec: u64, button: u32, state: KeyState) { @@ -776,10 +749,8 @@ impl WlSeatGlobal { // client.flush(); } - fn set_new_position(self: &Rc, time_usec: u64, x: Fixed, y: Fixed) { + fn cursor_moved(self: &Rc, time_usec: u64) { self.pos_time_usec.set(time_usec); - self.pos.set((x, y)); - self.update_hardware_cursor_position(); self.changes.or_assign(CHANGE_CURSOR_MOVED); self.apply_changes(); } diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index f02e1605..86cd5229 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -274,7 +274,7 @@ impl PointerOwner for SimplePointerOwner { } fn apply_changes(&self, seat: &Rc) { - let (x, y) = seat.pos.get(); + let (x, y) = seat.pointer_cursor.position(); let mut found_tree = seat.found_tree.borrow_mut(); let mut stack = seat.pointer_stack.borrow_mut(); let x_int = x.round_down(); @@ -408,7 +408,7 @@ impl PointerOwner for SimpleGrabPointerOwner { } fn apply_changes(&self, seat: &Rc) { - let (x, y) = seat.pos.get(); + let (x, y) = seat.pointer_cursor.position(); let pos = self.node.node_absolute_position(); let (x_int, y_int) = pos.translate(x.round_down(), y.round_down()); // log::info!("apply_changes"); @@ -493,7 +493,7 @@ impl PointerOwner for DndPointerOwner { } fn apply_changes(&self, seat: &Rc) { - let (x, y) = seat.pos.get(); + let (x, y) = seat.pointer_cursor.position(); let (x_int, y_int) = (x.round_down(), y.round_down()); let (node, x_int, y_int) = { let mut found_tree = seat.found_tree.borrow_mut(); @@ -781,7 +781,7 @@ impl NodeSelectorUsecase for SelectToplevelUsecase { if let Some(tl) = &tl { tl.tl_data().render_highlight.fetch_add(1); if !tl.tl_admits_children() { - seat.set_known_cursor(KnownCursor::Pointer); + seat.pointer_cursor().set_known(KnownCursor::Pointer); } damage = true; } @@ -829,7 +829,7 @@ impl NodeSelectorUsecase for SelectWorkspaceUsecase { let ws = node.clone().node_into_workspace(); if let Some(ws) = &ws { ws.render_highlight.fetch_add(1); - seat.set_known_cursor(KnownCursor::Pointer); + seat.pointer_cursor().set_known(KnownCursor::Pointer); damage = true; } if let Some(prev) = self.latest.set(ws) { diff --git a/src/ifs/wl_seat/wl_pointer.rs b/src/ifs/wl_seat/wl_pointer.rs index cfd16383..839828cc 100644 --- a/src/ifs/wl_seat/wl_pointer.rs +++ b/src/ifs/wl_seat/wl_pointer.rs @@ -186,7 +186,7 @@ impl WlPointerRequestHandler for WlPointer { let mut cursor_opt = None; if req.surface.is_some() { let surface = self.seat.client.lookup(req.surface)?; - let cursor = surface.get_cursor(&self.seat.global)?; + let cursor = surface.get_cursor(&self.seat.global.pointer_cursor)?; cursor.set_hotspot(req.hotspot_x, req.hotspot_y); cursor_opt = Some(cursor as Rc); } @@ -211,7 +211,7 @@ impl WlPointerRequestHandler for WlPointer { // ); // return Ok(()); // } - self.seat.global.set_app_cursor(cursor_opt); + self.seat.global.pointer_cursor().set(cursor_opt); Ok(()) } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index be783528..d6c22126 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -18,6 +18,7 @@ use { crate::{ backend::KeyState, client::{Client, ClientError}, + cursor_user::{CursorUser, CursorUserId}, drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{AcquireSync, BufferResv, BufferResvUser, ReleaseSync, SampleRect, SyncFile}, @@ -262,7 +263,7 @@ pub struct WlSurface { pub presentation_feedback: RefCell>>, seat_state: NodeSeatState, toplevel: CloneCell>>, - cursors: SmallMap, 1>, + cursors: SmallMap, 1>, dnd_icons: SmallMap, 1>, pub tracker: Tracker, idle_inhibitors: SmallMap, 1>, @@ -655,13 +656,13 @@ impl WlSurface { pub fn get_cursor( self: &Rc, - seat: &Rc, + user: &Rc, ) -> Result, WlSurfaceError> { - if let Some(cursor) = self.cursors.get(&seat.id()) { + if let Some(cursor) = self.cursors.get(&user.id) { return Ok(cursor); } self.set_role(SurfaceRole::Cursor)?; - let cursor = Rc::new(CursorSurface::new(seat, self)); + let cursor = Rc::new(CursorSurface::new(user, self)); track!(self.client, cursor); cursor.handle_buffer_change(); Ok(cursor) diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index 9de08603..2d6208c5 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -1,8 +1,9 @@ use { crate::{ cursor::Cursor, + cursor_user::CursorUser, fixed::Fixed, - ifs::{wl_seat::WlSeatGlobal, wl_surface::WlSurface}, + ifs::wl_surface::WlSurface, leaks::Tracker, rect::Rect, renderer::Renderer, @@ -13,7 +14,7 @@ use { }; pub struct CursorSurface { - seat: Rc, + user: Rc, surface: Rc, hotspot: Cell<(i32, i32)>, extents: Cell, @@ -21,9 +22,9 @@ pub struct CursorSurface { } impl CursorSurface { - pub fn new(seat: &Rc, surface: &Rc) -> Self { + pub fn new(user: &Rc, surface: &Rc) -> Self { Self { - seat: seat.clone(), + user: user.clone(), surface: surface.clone(), hotspot: Cell::new((0, 0)), extents: Cell::new(Default::default()), @@ -38,7 +39,7 @@ impl CursorSurface { } pub fn handle_surface_destroy(&self) { - self.seat.set_app_cursor(None); + self.user.set(None); } pub fn handle_buffer_change(&self) { @@ -57,9 +58,7 @@ impl CursorSurface { } pub fn update_hardware_cursor(&self) { - if self.seat.hardware_cursor() { - self.seat.update_hardware_cursor(); - } + self.user.update_hardware_cursor(); } } @@ -124,7 +123,7 @@ impl Cursor for CursorSurface { } fn handle_set(self: Rc) { - self.surface.cursors.insert(self.seat.id(), self.clone()); + self.surface.cursors.insert(self.user.id, self.clone()); if self.surface.cursors.is_not_empty() { self.surface .set_visible(self.surface.client.state.root_visible()); @@ -132,7 +131,7 @@ impl Cursor for CursorSurface { } fn handle_unset(&self) { - self.surface.cursors.remove(&self.seat.id()); + self.surface.cursors.remove(&self.user.id); if self.surface.cursors.is_empty() { self.surface.set_visible(false); } diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 28b8c391..850f3625 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -366,7 +366,7 @@ impl Node for Xwindow { fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("wl-surface focus"); - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); } fn node_into_toplevel(self: Rc) -> Option> { diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 96bb64a1..33c9b690 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -344,7 +344,7 @@ impl Node for XdgPopup { fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("xdg-popup focus"); - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); } } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 185060dc..50f4d41f 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -526,7 +526,7 @@ impl Node for XdgToplevel { fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("xdg-toplevel focus"); - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); } fn node_into_toplevel(self: Rc) -> Option> { diff --git a/src/ifs/wp_cursor_shape_device_v1.rs b/src/ifs/wp_cursor_shape_device_v1.rs index ede3c710..8524d2ae 100644 --- a/src/ifs/wp_cursor_shape_device_v1.rs +++ b/src/ifs/wp_cursor_shape_device_v1.rs @@ -107,7 +107,7 @@ impl WpCursorShapeDeviceV1RequestHandler for WpCursorShapeDeviceV1 { if pointer_node.node_client_id() != Some(self.client.id) { return Ok(()); } - self.seat.set_known_cursor(cursor); + self.seat.pointer_cursor().set_known(cursor); Ok(()) } } diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index 3a8e0eb7..d8ce9513 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -102,7 +102,7 @@ impl XdgToplevelDragV1 { if self.source.data.was_used() { if let Some(tl) = self.toplevel.get() { let output = seat.get_output(); - let (x, y) = seat.position(); + let (x, y) = seat.pointer_cursor().position(); tl.drag.take(); tl.after_toplevel_drag( &output, diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 46f6953d..ad3a5b6f 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -156,7 +156,7 @@ pub struct DefaultSetup { impl DefaultSetup { pub fn move_to(&self, x: i32, y: i32) { - let (ox, oy) = self.seat.position(); + let (ox, oy) = self.seat.pointer_cursor().position(); let (nx, ny) = (Fixed::from_int(x), Fixed::from_int(y)); let (dx, dy) = (nx - ox, ny - oy); self.mouse.rel(dx.to_f64(), dy.to_f64()) diff --git a/src/it/tests/t0030_cursor_shape.rs b/src/it/tests/t0030_cursor_shape.rs index 0067cbcd..dbf9fe7c 100644 --- a/src/it/tests/t0030_cursor_shape.rs +++ b/src/it/tests/t0030_cursor_shape.rs @@ -24,7 +24,7 @@ async fn test(run: Rc) -> TestResult { client.sync().await; tassert_eq!( - ds.seat.get_desired_known_cursor(), + ds.seat.pointer_cursor().desired_known_cursor(), Some(KnownCursor::ContextMenu) ); diff --git a/src/main.rs b/src/main.rs index d3b54551..d7cc40b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,7 @@ mod clientmem; mod compositor; mod config; mod cursor; +mod cursor_user; mod dbus; mod drm_feedback; mod edid; diff --git a/src/state.rs b/src/state.rs index 628440cb..77d6cab2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,6 +12,7 @@ use { clientmem::ClientMemOffset, config::ConfigProxy, cursor::{Cursor, ServerCursors}, + cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds}, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, fixed::Fixed, @@ -185,6 +186,11 @@ pub struct State { pub explicit_sync_enabled: Cell, pub keyboard_state_ids: KeyboardStateIds, pub security_context_acceptors: SecurityContextAcceptors, + pub cursor_user_group_ids: CursorUserGroupIds, + pub cursor_user_ids: CursorUserIds, + pub cursor_users: CopyHashMap>, + pub cursor_user_groups: CopyHashMap>, + pub cursor_user_group_hardware_cursor: CloneCell>>, } // impl Drop for State { @@ -460,9 +466,8 @@ impl State { UpdateTextTexturesVisitor.visit_display(&self.root); } - let seats = self.globals.seats.lock(); - for seat in seats.values() { - seat.render_ctx_changed(); + for cursor_user_groups in self.cursor_user_groups.lock().values() { + cursor_user_groups.render_ctx_changed(); } if let Some(ctx) = &ctx { @@ -505,8 +510,8 @@ impl State { } }; self.cursors.set(cursors); - for seat in self.globals.seats.lock().values() { - seat.reload_known_cursor(); + for cursor_user_group in self.cursor_user_groups.lock().values() { + cursor_user_group.reload_known_cursor(); } } } @@ -785,21 +790,13 @@ impl State { } pub fn refresh_hardware_cursors(&self) { - let seat = self - .globals - .seats - .lock() - .values() - .find(|s| s.hardware_cursor()) - .cloned(); - let seat = match seat { - Some(s) => s, - _ => { - self.disable_hardware_cursors(); + if let Some(g) = self.cursor_user_group_hardware_cursor.get() { + if let Some(u) = g.active() { + u.update_hardware_cursor(); return; } - }; - seat.update_hardware_cursor(); + } + self.disable_hardware_cursors() } pub fn for_each_seat_tester(&self, f: F) { @@ -868,10 +865,10 @@ impl State { ReleaseSync::Implicit, ); if render_hardware_cursors { - for seat in self.globals.lock_seats().values() { - if let Some(cursor) = seat.get_cursor() { - let (mut x, mut y) = seat.get_position(); - if seat.hardware_cursor() { + if let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() { + if let Some(cursor_user) = cursor_user_group.active() { + if let Some(cursor) = cursor_user.get() { + let (mut x, mut y) = cursor_user.position(); x = x + x_off - Fixed::from_int(position.x1()); y = y + y_off - Fixed::from_int(position.y1()); cursor.render(&mut renderer, x, y); @@ -883,9 +880,9 @@ impl State { } fn have_hardware_cursor(&self) -> bool { - for seat in self.globals.lock_seats().values() { - if seat.get_cursor().is_some() { - if seat.hardware_cursor() { + if let Some(group) = self.cursor_user_group_hardware_cursor.get() { + if let Some(user) = group.active() { + if user.get().is_some() { return true; } } @@ -995,6 +992,43 @@ impl State { pub fn root_visible(&self) -> bool { !self.idle.backend_idle.get() } + + pub fn find_closest_output(&self, mut x: i32, mut y: i32) -> (Rc, i32, i32) { + let mut optimal_dist = i32::MAX; + let mut optimal_output = None; + let outputs = self.root.outputs.lock(); + for output in outputs.values() { + let pos = output.global.pos.get(); + let dist = pos.dist_squared(x, y); + if dist == 0 { + if pos.contains(x, y) { + return (output.clone(), x, y); + } + } + if dist < optimal_dist { + optimal_dist = dist; + optimal_output = Some(output.clone()); + } + } + if let Some(output) = optimal_output { + let pos = output.global.pos.get(); + if pos.is_empty() { + return (output, pos.x1(), pos.y1()); + } + if x < pos.x1() { + x = pos.x1(); + } else if x >= pos.x2() { + x = pos.x2() - 1; + } + if y < pos.y1() { + y = pos.y1(); + } else if y >= pos.y2() { + y = pos.y2() - 1; + } + return (output, x, y); + } + (self.dummy_output.get().unwrap(), 0, 0) + } } #[derive(Debug, Error)] diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index bd61ea59..2824010c 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -185,12 +185,8 @@ impl ConnectorHandler { global.opt.global.set(Some(global.clone())); let mut ws_to_move = VecDeque::new(); if self.state.root.outputs.len() == 1 { - let seats = self.state.globals.seats.lock(); - let pos = global.pos.get(); - let x = (pos.x1() + pos.x2()) / 2; - let y = (pos.y1() + pos.y2()) / 2; - for seat in seats.values() { - seat.set_position(x, y); + for seat in self.state.globals.seats.lock().values() { + seat.cursor_group().first_output_connected(&on); } let dummy = self.state.dummy_output.get().unwrap(); for ws in dummy.workspaces.iter() { @@ -287,12 +283,8 @@ impl ConnectorHandler { }; move_ws_to_output(&ws, &target, config); } - let seats = self.state.globals.seats.lock(); - for seat in seats.values() { - if seat.get_output().id == on.id { - let tpos = target.global.pos.get(); - seat.set_position((tpos.x1() + tpos.x2()) / 2, (tpos.y1() + tpos.y2()) / 2); - } + for seat in self.state.globals.seats.lock().values() { + seat.cursor_group().output_disconnected(&on, &target); } self.state .remove_output_scale(on.global.persistent.scale.get()); diff --git a/src/tree/container.rs b/src/tree/container.rs index 650d5b0b..ba41875b 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -601,7 +601,7 @@ impl ContainerNode { }; if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) { if seat_state.target { - seat.set_known_cursor(new_cursor); + seat.pointer_cursor().set_known(new_cursor); } } } @@ -1315,7 +1315,7 @@ impl Node for ContainerNode { let mut seats = self.seats.borrow_mut(); if let Some(seat_state) = seats.get_mut(&seat.id()) { seat_state.target = true; - seat.set_known_cursor(seat_state.cursor); + seat.pointer_cursor().set_known(seat_state.cursor); } } diff --git a/src/tree/display.rs b/src/tree/display.rs index 0288bc4a..610d6480 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -140,6 +140,6 @@ impl Node for DisplayNode { fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("display focus"); - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); } } diff --git a/src/tree/float.rs b/src/tree/float.rs index 949ac892..fdf8516b 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -334,7 +334,7 @@ impl FloatNode { seat_state.op_type = op_type; if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) { if seat_state.target { - seat.set_known_cursor(new_cursor); + seat.pointer_cursor().set_known(new_cursor); } } } @@ -564,7 +564,7 @@ impl Node for FloatNode { let mut seats = self.seats.borrow_mut(); if let Some(seat_state) = seats.get_mut(&seat.id()) { seat_state.target = true; - seat.set_known_cursor(seat_state.cursor); + seat.pointer_cursor().set_known(seat_state.cursor); } } diff --git a/src/tree/output.rs b/src/tree/output.rs index 2b71957d..dfeac4ef 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -553,6 +553,10 @@ impl OutputNode { } } self.global.send_mode(); + for seat in self.state.globals.seats.lock().values() { + seat.cursor_group().output_pos_changed(self) + } + self.state.tree_changed(); } pub fn find_layer_surface_at( @@ -906,7 +910,7 @@ impl Node for OutputNode { fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("output focus"); - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); } fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index fc8b0d8c..549a301d 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -139,7 +139,7 @@ impl Node for PlaceholderNode { } fn node_on_pointer_enter(self: Rc, seat: &Rc, _x: Fixed, _y: Fixed) { - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); seat.enter_toplevel(self.clone()); } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index b986c44c..9eac5444 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -274,7 +274,7 @@ impl Node for WorkspaceNode { fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("workspace focus"); - seat.set_known_cursor(KnownCursor::Default); + seat.pointer_cursor().set_known(KnownCursor::Default); } fn node_into_workspace(self: Rc) -> Option> { From 86e283d255c16a52ad3fa7fafe6ea746b1cc9c2b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 1 May 2024 15:29:52 +0200 Subject: [PATCH 4/5] config: allow mapping input devices to outputs --- jay-config/src/_private/client.rs | 34 +++++++- jay-config/src/_private/ipc.rs | 7 ++ jay-config/src/input.rs | 19 ++++ jay-config/src/video.rs | 5 ++ src/cli/input.rs | 41 +++++++++ src/config/handler.rs | 26 ++++++ src/ifs/jay_input.rs | 37 ++++++++ src/ifs/wl_seat.rs | 14 +++ src/state.rs | 3 +- src/tasks/input_device.rs | 1 + toml-config/src/config.rs | 1 + toml-config/src/config/parsers/action.rs | 2 +- toml-config/src/config/parsers/input.rs | 49 +++++++++-- toml-config/src/lib.rs | 106 +++++++++++++++++++++-- toml-spec/spec/spec.generated.json | 8 ++ toml-spec/spec/spec.generated.md | 36 ++++++++ toml-spec/spec/spec.yaml | 34 ++++++++ wire/jay_input.txt | 10 +++ 18 files changed, 420 insertions(+), 13 deletions(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index d00a274c..b34dc2c7 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -88,7 +88,9 @@ pub(crate) struct Client { response: RefCell>, on_new_seat: RefCell>>, on_new_input_device: RefCell>>, + on_input_device_removed: RefCell>>, on_connector_connected: RefCell>>, + on_connector_disconnected: RefCell>>, on_graphics_initialized: Cell>>, on_devices_enumerated: Cell>>, on_new_connector: RefCell>>, @@ -216,7 +218,9 @@ pub unsafe extern "C" fn init( response: Default::default(), on_new_seat: Default::default(), on_new_input_device: Default::default(), + on_input_device_removed: Default::default(), on_connector_connected: Default::default(), + on_connector_disconnected: Default::default(), on_graphics_initialized: Default::default(), on_devices_enumerated: Default::default(), on_new_connector: Default::default(), @@ -605,6 +609,10 @@ impl Client { *self.on_new_input_device.borrow_mut() = Some(cb(f)); } + pub fn on_input_device_removed(&self, f: F) { + *self.on_input_device_removed.borrow_mut() = Some(cb(f)); + } + pub fn on_switch_event( &self, input_device: InputDevice, @@ -818,6 +826,10 @@ impl Client { *self.on_connector_connected.borrow_mut() = Some(cb(f)); } + pub fn on_connector_disconnected(&self, f: F) { + *self.on_connector_disconnected.borrow_mut() = Some(cb(f)); + } + pub fn on_graphics_initialized(&self, f: F) { self.on_graphics_initialized.set(Some(Box::new(f))); } @@ -943,6 +955,17 @@ impl Client { self.send(&ClientMessage::SetFocusFollowsMouseMode { seat, mode }) } + pub fn set_input_device_connector(&self, input_device: InputDevice, connector: Connector) { + self.send(&ClientMessage::SetInputDeviceConnector { + input_device, + connector, + }) + } + + pub fn remove_input_mapping(&self, input_device: InputDevice) { + self.send(&ClientMessage::RemoveInputMapping { input_device }) + } + pub fn parse_keymap(&self, keymap: &str) -> Keymap { let res = self.send_with_response(&ClientMessage::ParseKeymap { keymap }); get_response!(res, Keymap(0), ParseKeymap { keymap }); @@ -1272,6 +1295,10 @@ impl Client { } ServerMessage::DelInputDevice { device } => { self.on_switch_event.borrow_mut().remove(&device); + let handler = self.on_input_device_removed.borrow_mut().clone(); + if let Some(handler) = handler { + run_cb("input device removed", &handler, device); + } } ServerMessage::ConnectorConnect { device } => { let handler = self.on_connector_connected.borrow_mut().clone(); @@ -1279,7 +1306,12 @@ impl Client { run_cb("connector connected", &handler, device); } } - ServerMessage::ConnectorDisconnect { .. } => {} + ServerMessage::ConnectorDisconnect { device } => { + let handler = self.on_connector_disconnected.borrow_mut().clone(); + if let Some(handler) = handler { + run_cb("connector disconnected", &handler, device); + } + } ServerMessage::NewConnector { device } => { let handler = self.on_new_connector.borrow_mut().clone(); if let Some(handler) = handler { diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 16e57be1..65fa8db6 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -476,6 +476,13 @@ pub enum ClientMessage<'a> { seat: Seat, mode: FocusFollowsMouseMode, }, + SetInputDeviceConnector { + input_device: InputDevice, + connector: Connector, + }, + RemoveInputMapping { + input_device: InputDevice, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 30edfb73..ae64897f 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -143,6 +143,20 @@ impl InputDevice { pub fn on_switch_event(self, f: F) { get!().on_switch_event(self, f) } + + /// Maps this input device to a connector. + /// + /// The connector should be connected. + /// + /// This should be used for touch screens and graphics tablets. + pub fn set_connector(self, connector: Connector) { + get!().set_input_device_connector(self, connector); + } + + /// Removes the mapping of this device to a connector. + pub fn remove_mapping(self) { + get!().remove_input_mapping(self); + } } /// A seat. @@ -449,6 +463,11 @@ pub fn on_new_input_device(f: F) { get!().on_new_input_device(f) } +/// Sets a closure to run when an input device has been removed. +pub fn on_input_device_removed(f: F) { + get!().on_input_device_removed(f) +} + /// Sets the maximum time between two clicks to be registered as a double click by the /// compositor. /// diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index acbd2601..00fa2b8c 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -275,6 +275,11 @@ pub fn on_connector_connected(f: F) { get!().on_connector_connected(f) } +/// Sets the callback to be called when a connector is disconnected from an output device. +pub fn on_connector_disconnected(f: F) { + get!().on_connector_disconnected(f) +} + /// Sets the callback to be called when the graphics of the compositor have been initialized. /// /// This callback is only invoked once during the lifetime of the compositor. This is a good place diff --git a/src/cli/input.rs b/src/cli/input.rs index a67dde78..280ee485 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -127,6 +127,10 @@ pub enum DeviceCommand { Attach(AttachArgs), /// Detach the device from its seat. Detach, + /// Maps this device to an output. + MapToOutput(MapToOutputArgs), + /// Removes the mapping from this device to an output. + RemoveMapping, } #[derive(ValueEnum, Debug, Clone)] @@ -196,6 +200,12 @@ pub struct SetTransformMatrixArgs { pub m22: f64, } +#[derive(Args, Debug, Clone)] +pub struct MapToOutputArgs { + /// The output to map to. + pub output: String, +} + #[derive(Args, Debug, Clone)] pub struct AttachArgs { /// The seat to attach to. @@ -261,6 +271,7 @@ struct InputDevice { pub natural_scrolling_enabled: Option, pub px_per_wheel_scroll: Option, pub transform_matrix: Option<[[f64; 2]; 2]>, + pub output: Option, } #[derive(Clone, Debug, Default)] @@ -564,6 +575,26 @@ impl Input { let map = self.handle_keymap(input).await; stdout().write_all(&map).unwrap(); } + DeviceCommand::MapToOutput(a) => { + self.handle_error(input, |e| { + eprintln!("Could not map the device to an output: {}", e); + }); + tc.send(jay_input::MapToOutput { + self_id: input, + id: args.device, + output: Some(&a.output), + }); + } + DeviceCommand::RemoveMapping => { + self.handle_error(input, |e| { + eprintln!("Could not remove the output mapping: {}", e); + }); + tc.send(jay_input::MapToOutput { + self_id: input, + id: args.device, + output: None, + }); + } } tc.round_trip().await; } @@ -694,6 +725,9 @@ impl Input { if let Some(v) = &device.transform_matrix { println!("{prefix} transform matrix: {:?}", v); } + if let Some(v) = &device.output { + println!("{prefix} mapped to output: {}", v); + } } async fn get(self: &Rc, input: JayInputId) -> Data { @@ -757,8 +791,15 @@ impl Input { .then_some(msg.natural_scrolling_enabled != 0), px_per_wheel_scroll: is_pointer.then_some(msg.px_per_wheel_scroll), transform_matrix: uapi::pod_read(msg.transform_matrix).ok(), + output: None, }); }); + jay_input::InputDeviceOutput::handle(tc, input, data.clone(), |data, msg| { + let mut data = data.borrow_mut(); + if let Some(last) = data.input_device.last_mut() { + last.output = Some(msg.output.to_string()); + } + }); tc.round_trip().await; let x = data.borrow_mut().clone(); x diff --git a/src/config/handler.rs b/src/config/handler.rs index 2b97c782..f6829226 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -338,6 +338,23 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_input_device_connector( + &self, + input_device: InputDevice, + connector: Connector, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(input_device)?; + let output = self.get_output_node(connector)?; + dev.set_output(Some(&output.global)); + Ok(()) + } + + fn handle_remove_input_mapping(&self, input_device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(input_device)?; + dev.set_output(None); + Ok(()) + } + fn handle_set_status(&self, status: &str) { self.state.set_status(status); } @@ -1790,6 +1807,15 @@ impl ConfigProxyHandler { ClientMessage::SetFocusFollowsMouseMode { seat, mode } => self .handle_set_focus_follows_mouse_mode(seat, mode) .wrn("set_focus_follows_mouse_mode")?, + ClientMessage::SetInputDeviceConnector { + input_device, + connector, + } => self + .handle_set_input_device_connector(input_device, connector) + .wrn("set_input_device_connector")?, + ClientMessage::RemoveInputMapping { input_device } => self + .handle_remove_input_mapping(input_device) + .wrn("remove_input_mapping")?, } Ok(()) } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 13284139..605e26a8 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -129,6 +129,15 @@ impl JayInput { .map(uapi::as_bytes) .unwrap_or_default(), }); + if let Some(output) = data.data.output.get() { + if let Some(output) = output.get() { + self.client.event(InputDeviceOutput { + self_id: self.id, + id: data.id.raw(), + output: &output.connector.name, + }); + } + } } fn device(&self, id: u32) -> Result, JayInputError> { @@ -389,6 +398,32 @@ impl JayInputRequestHandler for JayInput { Ok(()) }) } + + fn map_to_output(&self, req: MapToOutput<'_>, _slf: &Rc) -> Result<(), Self::Error> { + self.or_error(|| { + let dev = self.device(req.id)?; + match req.output { + Some(output) => { + let namelc = output.to_ascii_lowercase(); + let c = self + .client + .state + .root + .outputs + .lock() + .values() + .find(|c| c.global.connector.name.to_ascii_lowercase() == namelc) + .cloned(); + match c { + Some(c) => dev.set_output(Some(&c.global)), + _ => return Err(JayInputError::OutputNotConnected), + } + } + _ => dev.set_output(None), + } + Ok(()) + }) + } } object_base! { @@ -418,5 +453,7 @@ pub enum JayInputError { ClientMemError(#[from] ClientMemError), #[error("Could not parse keymap")] XkbCommonError(#[from] XkbCommonError), + #[error("Output is not connected")] + OutputNotConnected, } efrom!(JayInputError, ClientError); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 440d6ae0..b79b039f 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -41,6 +41,7 @@ use { zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, DynDataSource, IpcError, }, + wl_output::WlOutputGlobal, wl_seat::{ gesture_owner::GestureOwnerHolder, kb_owner::KbOwnerHolder, @@ -1163,4 +1164,17 @@ impl DeviceHandlerData { seat.handle_xkb_state_change(&old.borrow(), &new.borrow()); } } + + pub fn set_output(&self, output: Option<&WlOutputGlobal>) { + match output { + None => { + log::info!("Removing output mapping of {}", self.device.name()); + self.output.take(); + } + Some(o) => { + log::info!("Mapping {} to {}", self.device.name(), o.connector.name); + self.output.set(Some(o.opt.clone())); + } + } + } } diff --git a/src/state.rs b/src/state.rs index 77d6cab2..097e11b2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -33,7 +33,7 @@ use { jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_drm::WlDrmGlobal, - wl_output::{OutputId, PersistentOutputState}, + wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState}, wl_seat::{SeatIds, WlSeatGlobal}, wl_surface::{ wl_subsurface::SubsurfaceIds, @@ -262,6 +262,7 @@ pub struct DeviceHandlerData { pub devnode: Option, pub keymap: CloneCell>>, pub xkb_state: CloneCell>>>, + pub output: CloneCell>>, } pub struct ConnectorData { diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 08857ad7..ee8d7250 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -23,6 +23,7 @@ pub fn handle(state: &Rc, dev: Rc) { devnode: props.devnode, keymap: Default::default(), xkb_state: Default::default(), + output: Default::default(), }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 44e7d9cb..f2e3370c 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -246,6 +246,7 @@ pub struct Input { pub transform_matrix: Option<[[f64; 2]; 2]>, pub keymap: Option, pub switch_actions: AHashMap, + pub output: Option>, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index 42b19df4..a0133a4b 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -162,7 +162,7 @@ impl ActionParser<'_> { .extract(val("input"))? .parse_map(&mut InputParser { cx: self.0, - tag_ok: false, + is_inputs_array: false, }) .map_spanned_err(ActionParserError::ConfigureInput)?; Ok(Action::ConfigureInput { input }) diff --git a/toml-config/src/config/parsers/input.rs b/toml-config/src/config/parsers/input.rs index 83ee3bed..3b5ff6b8 100644 --- a/toml-config/src/config/parsers/input.rs +++ b/toml-config/src/config/parsers/input.rs @@ -8,6 +8,7 @@ use { action::ActionParser, input_match::{InputMatchParser, InputMatchParserError}, keymap::KeymapParser, + output_match::OutputMatchParser, }, Input, }, @@ -43,7 +44,7 @@ pub enum InputParserError { pub struct InputParser<'a> { pub cx: &'a Context<'a>, - pub tag_ok: bool, + pub is_inputs_array: bool, } impl<'a> Parser for InputParser<'a> { @@ -77,6 +78,8 @@ impl<'a> Parser for InputParser<'a> { on_lid_closed_val, on_converted_to_laptop_val, on_converted_to_tablet_val, + output_val, + remove_mapping, ), ) = ext.extract(( ( @@ -98,6 +101,8 @@ impl<'a> Parser for InputParser<'a> { opt(val("on-lid-closed")), opt(val("on-converted-to-laptop")), opt(val("on-converted-to-tablet")), + opt(val("output")), + recover(opt(bol("remove-mapping"))), ), ))?; let accel_profile = match accel_profile { @@ -122,7 +127,7 @@ impl<'a> Parser for InputParser<'a> { }, }; if let Some(tag) = tag { - if self.tag_ok { + if self.is_inputs_array { self.cx.used.borrow_mut().defined_inputs.insert(tag.into()); } else { log::warn!( @@ -147,7 +152,7 @@ impl<'a> Parser for InputParser<'a> { let mut switch_actions = AHashMap::new(); let mut parse_action = |val: Option>, name, event| { if let Some(val) = val { - if !self.tag_ok { + if !self.is_inputs_array { log::warn!( "{name} has no effect in this position: {}", self.cx.error3(val.span) @@ -176,6 +181,39 @@ impl<'a> Parser for InputParser<'a> { "on-converted-to-tablet", SwitchEvent::ConvertedToTablet, ); + let mut output = None; + if let Some(val) = output_val { + match val.parse(&mut OutputMatchParser(self.cx)) { + Ok(v) => output = Some(Some(v)), + Err(e) => { + log::warn!("Could not parse output: {}", self.cx.error(e)); + } + } + } + if let Some(val) = remove_mapping { + if self.is_inputs_array { + log::warn!( + "`remove-mapping` has no effect in this position: {}", + self.cx.error3(val.span) + ); + } else if !val.value { + log::warn!( + "`remove-mapping = false` has no effect: {}", + self.cx.error3(val.span) + ); + } else if let Some(output) = output_val { + log::warn!( + "Ignoring `remove-mapping = true` due to conflicting `output` field: {}", + self.cx.error3(val.span) + ); + log::info!( + "`output` field defined here: {}", + self.cx.error3(output.span) + ); + } else { + output = Some(None); + } + } Ok(Input { tag: tag.despan_into(), match_: match_val.parse_map(&mut InputMatchParser(self.cx))?, @@ -190,6 +228,7 @@ impl<'a> Parser for InputParser<'a> { transform_matrix, keymap, switch_actions, + output, }) } } @@ -206,7 +245,7 @@ impl<'a> Parser for InputsParser<'a> { for el in array { match el.parse(&mut InputParser { cx: self.0, - tag_ok: true, + is_inputs_array: true, }) { Ok(o) => res.push(o), Err(e) => { @@ -228,7 +267,7 @@ impl<'a> Parser for InputsParser<'a> { ); InputParser { cx: self.0, - tag_ok: true, + is_inputs_array: true, } .parse_table(span, table) .map(|v| vec![v]) diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 73ce2329..7e8b8ba9 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -16,8 +16,8 @@ use { exec::{set_env, unset_env, Command}, get_workspace, input::{ - capability::CAP_SWITCH, get_seat, input_devices, on_new_input_device, - FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, + capability::CAP_SWITCH, get_seat, input_devices, on_input_device_removed, + on_new_input_device, FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, }, is_reload, keyboard::{Keymap, ModifiedKeySym}, @@ -28,9 +28,9 @@ use { switch_to_vt, theme::{reset_colors, reset_font, reset_sizes, set_font}, video::{ - connectors, drm_devices, on_connector_connected, on_graphics_initialized, - on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api, - Connector, DrmDevice, + connectors, drm_devices, on_connector_connected, on_connector_disconnected, + on_graphics_initialized, on_new_connector, on_new_drm_device, + set_direct_scanout_enabled, set_gfx_api, Connector, DrmDevice, }, }, std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc}, @@ -422,6 +422,17 @@ impl Input { c.set_keymap(km); } } + if let Some(output) = &self.output { + if let Some(output) = output { + for connector in connectors() { + if output.matches(connector, state) { + c.set_connector(connector); + } + } + } else { + c.remove_mapping(); + } + } } } @@ -550,6 +561,10 @@ struct State { input_devices: AHashMap, persistent: Rc, keymaps: AHashMap, + + io_maps: Vec<(InputMatch, OutputMatch)>, + io_inputs: RefCell>>, + io_outputs: RefCell>>, } impl Drop for State { @@ -701,6 +716,60 @@ impl State { } }); } + + fn add_io_output(&self, c: Connector) { + let mappings: Vec<_> = self + .io_maps + .iter() + .map(|(_, output)| output.matches(c, self)) + .collect(); + if mappings.len() > 0 { + self.io_outputs.borrow_mut().insert(c, mappings); + } + } + + fn add_io_input(&self, d: InputDevice) { + let mappings: Vec<_> = self + .io_maps + .iter() + .map(|(input, _)| input.matches(d, self)) + .collect(); + if mappings.len() > 0 { + self.io_inputs.borrow_mut().insert(d, mappings); + } + } + + fn map_input_to_output(&self, d: InputDevice) { + let input_mappings = &*self.io_inputs.borrow(); + let Some(input_matches) = input_mappings.get(&d) else { + return; + }; + for (idx, &input_is_match) in input_matches.iter().enumerate() { + if input_is_match { + for (&c, output_maps) in &*self.io_outputs.borrow() { + if output_maps.get(idx) == Some(&true) { + d.set_connector(c); + } + } + } + } + } + + fn map_output_to_input(&self, c: Connector) { + let output_mappings = &*self.io_outputs.borrow(); + let Some(output_matches) = output_mappings.get(&c) else { + return; + }; + for (idx, &output_is_match) in output_matches.iter().enumerate() { + if output_is_match { + for (&d, input_matches) in &*self.io_inputs.borrow() { + if input_matches.get(idx) == Some(&true) { + d.set_connector(c); + } + } + } + } + } } #[derive(Eq, PartialEq, Hash)] @@ -763,6 +832,7 @@ fn load_config(initial_load: bool, persistent: &Rc) { } } let mut input_devices = AHashMap::new(); + let mut io_maps = vec![]; for input in &config.inputs { if let Some(tag) = &input.tag { let prev = input_devices.insert(tag.clone(), input.match_.clone()); @@ -770,6 +840,9 @@ fn load_config(initial_load: bool, persistent: &Rc) { log::warn!("Duplicate input tag {tag}"); } } + if let Some(Some(output)) = &input.output { + io_maps.push((input.match_.clone(), output.clone())); + } } let mut named_drm_device = AHashMap::new(); for drm_device in &config.drm_devices { @@ -786,6 +859,9 @@ fn load_config(initial_load: bool, persistent: &Rc) { input_devices, persistent: persistent.clone(), keymaps, + io_maps, + io_inputs: Default::default(), + io_outputs: Default::default(), }); state.set_status(&config.status); let mut switch_actions = vec![]; @@ -827,6 +903,8 @@ fn load_config(initial_load: bool, persistent: &Rc) { on_connector_connected({ let state = state.clone(); move |c| { + state.add_io_output(c); + state.map_output_to_input(c); let id = OutputId { manufacturer: c.manufacturer(), model: c.model(), @@ -841,6 +919,12 @@ fn load_config(initial_load: bool, persistent: &Rc) { } } }); + on_connector_disconnected({ + let state = state.clone(); + move |c| { + state.io_outputs.borrow_mut().remove(&c); + } + }); set_default_workspace_capture(config.workspace_capture); for (k, v) in config.env { set_env(&k, &v); @@ -896,6 +980,7 @@ fn load_config(initial_load: bool, persistent: &Rc) { let state = state.clone(); let switch_actions = switch_actions.clone(); move |c| { + state.add_io_input(c); for input in &config.inputs { if input.match_.matches(c, &state) { input.apply(c, &state); @@ -904,7 +989,18 @@ fn load_config(initial_load: bool, persistent: &Rc) { state.handle_switch_device(c, &switch_actions); } }); + on_input_device_removed({ + let state = state.clone(); + move |c| { + state.io_inputs.borrow_mut().remove(&c); + } + }); + for c in connectors() { + state.add_io_output(c); + } for c in jay_config::input::input_devices() { + state.add_io_input(c); + state.map_input_to_output(c); state.handle_switch_device(c, &switch_actions); } persistent diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 86e68430..56d908c2 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -841,6 +841,14 @@ "on-converted-to-tablet": { "description": "An action to execute when the convertible device is converted to a tablet.\n\nThis should only be used in the top-level inputs array.\n", "$ref": "#/$defs/Action" + }, + "output": { + "description": "Maps this input device to an output.\n\nThis is used to map touch screen and graphics tablets to outputs.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.name = \"Wacom Bamboo Comic 2FG Pen\"\n output.connector = \"DP-1\"\n ```\n", + "$ref": "#/$defs/OutputMatch" + }, + "remove-mapping": { + "type": "boolean", + "description": "Removes the mapping of from this device to an output.\n\nThis should only be used within `configure-input` actions.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-x = { type = \"configure-input\", input = { match.tag = \"wacom\", remove-mapping = true } }\n\n [[inputs]]\n tag = \"wacom\"\n match.name = \"Wacom Bamboo Comic 2FG Pen\"\n output.connector = \"DP-1\"\n ```\n" } }, "required": [ diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index d50e2c1b..0c3c48ce 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1685,6 +1685,42 @@ The table has the following fields: The value of this field should be a [Action](#types-Action). +- `output` (optional): + + Maps this input device to an output. + + This is used to map touch screen and graphics tablets to outputs. + + - Example: + + ```toml + [[inputs]] + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` + + The value of this field should be a [OutputMatch](#types-OutputMatch). + +- `remove-mapping` (optional): + + Removes the mapping of from this device to an output. + + This should only be used within `configure-input` actions. + + - Example: + + ```toml + [shortcuts] + alt-x = { type = "configure-input", input = { match.tag = "wacom", remove-mapping = true } } + + [[inputs]] + tag = "wacom" + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` + + The value of this field should be a boolean. + ### `InputMatch` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 58456a23..06da5295 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1285,6 +1285,40 @@ Input: An action to execute when the convertible device is converted to a tablet. This should only be used in the top-level inputs array. + output: + ref: OutputMatch + required: false + description: | + Maps this input device to an output. + + This is used to map touch screen and graphics tablets to outputs. + + - Example: + + ```toml + [[inputs]] + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` + remove-mapping: + kind: boolean + required: false + description: | + Removes the mapping of from this device to an output. + + This should only be used within `configure-input` actions. + + - Example: + + ```toml + [shortcuts] + alt-x = { type = "configure-input", input = { match.tag = "wacom", remove-mapping = true } } + + [[inputs]] + tag = "wacom" + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` AccelProfile: diff --git a/wire/jay_input.txt b/wire/jay_input.txt index 3539389e..91a7875d 100644 --- a/wire/jay_input.txt +++ b/wire/jay_input.txt @@ -109,6 +109,11 @@ request get_device_keymap { id: u32, } +request map_to_output { + id: u32, + output: optstr, +} + # events event seat { @@ -148,3 +153,8 @@ event keymap { keymap: fd, keymap_len: u32, } + +event input_device_output { + id: u32, + output: str, +} From 7ed499eabd39dfa41c1552946d3a79041861b1a4 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 1 May 2024 00:09:16 +0200 Subject: [PATCH 5/5] wayland: implement tablet-v2 --- build/wire.rs | 4 - docs/config.md | 16 + docs/features.md | 2 +- release-notes.md | 1 + src/backend.rs | 64 ++- src/backends/metal.rs | 92 +++- src/backends/metal/input.rs | 235 ++++++++++- src/backends/metal/monitor.rs | 5 + src/cli/seat_test.rs | 241 ++++++++++- src/client.rs | 5 + src/client/objects.rs | 7 +- src/compositor.rs | 5 +- src/globals.rs | 2 + src/ifs/jay_seat_events.rs | 234 ++++++++++- src/ifs/wl_seat.rs | 31 +- src/ifs/wl_seat/event_handling.rs | 115 ++++- src/ifs/wl_seat/kb_owner.rs | 1 + src/ifs/wl_seat/tablet.rs | 393 ++++++++++++++++++ src/ifs/wl_seat/tablet/pad.rs | 290 +++++++++++++ src/ifs/wl_seat/tablet/pad_owner.rs | 135 ++++++ src/ifs/wl_seat/tablet/tablet_bindings.rs | 43 ++ src/ifs/wl_seat/tablet/tool.rs | 274 ++++++++++++ src/ifs/wl_seat/tablet/tool_owner.rs | 213 ++++++++++ .../wl_seat/tablet/zwp_tablet_manager_v2.rs | 104 +++++ .../wl_seat/tablet/zwp_tablet_pad_group_v2.rs | 101 +++++ .../wl_seat/tablet/zwp_tablet_pad_ring_v2.rs | 90 ++++ .../wl_seat/tablet/zwp_tablet_pad_strip_v2.rs | 89 ++++ src/ifs/wl_seat/tablet/zwp_tablet_pad_v2.rs | 127 ++++++ src/ifs/wl_seat/tablet/zwp_tablet_seat_v2.rs | 221 ++++++++++ src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs | 252 +++++++++++ src/ifs/wl_seat/tablet/zwp_tablet_v2.rs | 86 ++++ src/ifs/wl_surface.rs | 113 ++++- src/ifs/wl_surface/x_surface/xwindow.rs | 12 +- src/ifs/wl_surface/xdg_surface/xdg_popup.rs | 12 +- .../wl_surface/xdg_surface/xdg_toplevel.rs | 12 +- src/ifs/wp_cursor_shape_device_v1.rs | 29 +- src/ifs/wp_cursor_shape_manager_v1.rs | 51 ++- src/libinput/device.rs | 116 +++++- src/libinput/event.rs | 339 ++++++++++++--- src/libinput/sys.rs | 191 +++++++++ src/state.rs | 15 +- src/tasks/input_device.rs | 2 + src/time.rs | 4 + src/tree.rs | 120 +++++- src/tree/container.rs | 255 ++++++++---- src/tree/display.rs | 13 +- src/tree/float.rs | 239 +++++++---- src/tree/output.rs | 100 +++-- src/tree/workspace.rs | 13 +- src/utils.rs | 1 + src/utils/bindings.rs | 10 +- src/utils/copyhashmap.rs | 2 +- src/utils/pending_serial.rs | 19 + wire/jay_seat_events.txt | 107 +++++ wire/zwp_tablet_manager_v2.txt | 7 + wire/zwp_tablet_pad_group_v2.txt | 27 ++ wire/zwp_tablet_pad_ring_v2.txt | 22 + wire/zwp_tablet_pad_strip_v2.txt | 22 + wire/zwp_tablet_pad_v2.txt | 43 ++ wire/zwp_tablet_seat_v2.txt | 14 + wire/zwp_tablet_tool_v2.txt | 83 +++- wire/zwp_tablet_v2.txt | 21 + 62 files changed, 5174 insertions(+), 318 deletions(-) create mode 100644 src/ifs/wl_seat/tablet.rs create mode 100644 src/ifs/wl_seat/tablet/pad.rs create mode 100644 src/ifs/wl_seat/tablet/pad_owner.rs create mode 100644 src/ifs/wl_seat/tablet/tablet_bindings.rs create mode 100644 src/ifs/wl_seat/tablet/tool.rs create mode 100644 src/ifs/wl_seat/tablet/tool_owner.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_manager_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_pad_group_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_pad_strip_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_pad_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_seat_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs create mode 100644 src/ifs/wl_seat/tablet/zwp_tablet_v2.rs create mode 100644 src/utils/pending_serial.rs create mode 100644 wire/zwp_tablet_manager_v2.txt create mode 100644 wire/zwp_tablet_pad_group_v2.txt create mode 100644 wire/zwp_tablet_pad_ring_v2.txt create mode 100644 wire/zwp_tablet_pad_strip_v2.txt create mode 100644 wire/zwp_tablet_pad_v2.txt create mode 100644 wire/zwp_tablet_seat_v2.txt create mode 100644 wire/zwp_tablet_v2.txt diff --git a/build/wire.rs b/build/wire.rs index e6c597f2..f6d1979e 100644 --- a/build/wire.rs +++ b/build/wire.rs @@ -723,10 +723,6 @@ fn write_request_handler( messages: &ParseResult, ) -> Result<()> { writeln!(f)?; - // TODO: remove this after https://github.com/mahkoh/jay/pull/190 - if camel_obj_name == "ZwpTabletToolV2" { - writeln!(f, " #[allow(dead_code)]")?; - } writeln!( f, " pub trait {camel_obj_name}RequestHandler: crate::object::Object + Sized {{" diff --git a/docs/config.md b/docs/config.md index 1173a5f9..f4afe007 100644 --- a/docs/config.md +++ b/docs/config.md @@ -474,6 +474,22 @@ You can use the `configure-input` action to change these settings at runtime. See the specification for more details. +### Mapping Tablets to Outputs + +You can map tablets to outputs using the `output` property: + +```toml +[[outputs]] +name = "left" +match.serial-number = "33K03894SL0" + +[[inputs]] +match.name = "Wacom Bamboo Comic 2FG Pen" +output.name = "left" +``` + +See the specification for more details. + # Theming You can configure the colors, sizes, and fonts used by the compositor with the top-level `theme` table. diff --git a/docs/features.md b/docs/features.md index b49073b5..3d1feb0e 100644 --- a/docs/features.md +++ b/docs/features.md @@ -165,6 +165,7 @@ Jay supports the following wayland protocols: | zwp_pointer_gestures_v1 | 3 | | | zwp_primary_selection_device_manager_v1 | 1 | | | zwp_relative_pointer_manager_v1 | 1 | | +| zwp_tablet_manager_v2 | 1 | | | zwp_text_input_manager_v3 | 1 | | | zwp_virtual_keyboard_manager_v1 | 1 | Yes | | zxdg_decoration_manager_v1 | 1 | | @@ -182,5 +183,4 @@ The following features are currently not supported but might get implemented in - Fine-grained damage tracking. - Touch support. -- Tablet support. - Tearing updates of fullscreen games. diff --git a/release-notes.md b/release-notes.md index 3d42166d..077b98d1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,6 +7,7 @@ - Focus-follows-mouse can now be disabled. - Add support for pointer-gestures-unstable-v1. - Configs can now handle switch events (laptop lid closed/opened). +- Add support for tablet-v2. # 1.1.0 (2024-04-22) diff --git a/src/backend.rs b/src/backend.rs index 0a41c053..421fcffe 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -4,7 +4,14 @@ use { drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{GfxFramebuffer, SyncFile}, - ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, + ifs::wl_seat::{ + tablet::{ + PadButtonState, TabletInit, TabletPadId, TabletPadInit, TabletRingEventSource, + TabletStripEventSource, TabletToolChanges, TabletToolId, TabletToolInit, + ToolButtonState, + }, + wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, + }, libinput::consts::DeviceCapability, video::drm::{ConnectorType, DrmConnector, DrmError, DrmVersion}, }, @@ -126,6 +133,8 @@ pub trait HardwareCursor: Debug { pub type TransformMatrix = [[f64; 2]; 2]; +linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize); + pub trait InputDevice { fn id(&self) -> InputDeviceId; fn removed(&self) -> bool; @@ -169,6 +178,12 @@ pub trait InputDevice { None } fn set_natural_scrolling_enabled(&self, enabled: bool); + fn tablet_info(&self) -> Option> { + None + } + fn tablet_pad_info(&self) -> Option> { + None + } } #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] @@ -321,6 +336,53 @@ pub enum InputEvent { time_usec: u64, event: SwitchEvent, }, + + TabletToolAdded { + time_usec: u64, + init: Box, + }, + TabletToolChanged { + time_usec: u64, + id: TabletToolId, + changes: Box, + }, + TabletToolButton { + time_usec: u64, + id: TabletToolId, + button: u32, + state: ToolButtonState, + }, + TabletToolRemoved { + time_usec: u64, + id: TabletToolId, + }, + + TabletPadButton { + time_usec: u64, + id: TabletPadId, + button: u32, + state: PadButtonState, + }, + TabletPadModeSwitch { + time_usec: u64, + pad: TabletPadId, + group: u32, + mode: u32, + }, + TabletPadRing { + time_usec: u64, + pad: TabletPadId, + ring: u32, + source: Option, + angle: Option, + }, + TabletPadStrip { + time_usec: u64, + pad: TabletPadId, + strip: u32, + source: Option, + position: Option, + }, } pub enum DrmEvent { diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 9d562e35..614f02f6 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -6,8 +6,8 @@ use { crate::{ async_engine::SpawnedFuture, backend::{ - Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, - InputEvent, KeyState, TransformMatrix, + Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, + InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, TransformMatrix, }, backends::metal::video::{ MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, @@ -15,12 +15,16 @@ use { dbus::{DbusError, SignalHandler}, drm_feedback::DrmFeedback, gfx_api::GfxError, + ifs::wl_seat::tablet::{ + TabletId, TabletInit, TabletPadGroupInit, TabletPadId, TabletPadInit, + }, libinput::{ consts::{ AccelProfile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_DEVICE_CAP_TABLET_PAD, + LIBINPUT_DEVICE_CAP_TABLET_TOOL, }, - device::RegisteredDevice, + device::{LibInputDevice, RegisteredDevice}, LibInput, LibInputAdapter, LibInputError, }, logind::{LogindError, Session}, @@ -41,6 +45,7 @@ use { gbm::GbmError, }, }, + bstr::ByteSlice, std::{ any::Any, cell::{Cell, RefCell}, @@ -335,6 +340,7 @@ pub async fn create(state: &Rc) -> Result, MetalError> { } struct MetalInputDevice { + state: Rc, slot: usize, id: InputDeviceId, devnum: c::dev_t, @@ -342,11 +348,14 @@ struct MetalInputDevice { inputdev: CloneCell>>, devnode: CString, _sysname: CString, + syspath: CString, removed: Cell, events: SyncQueue, cb: CloneCell>>, name: CloneCell>, transform_matrix: Cell>, + tablet_id: Cell>, + tablet_pad_id: Cell>, // state pressed_keys: SmallMap, @@ -646,6 +655,71 @@ impl InputDevice for MetalInputDevice { fn natural_scrolling_enabled(&self) -> Option { self.effective.natural_scrolling_enabled.get() } + + fn tablet_info(&self) -> Option> { + let dev = self.inputdev.get()?; + let dev = dev.device(); + if !dev.has_cap(LIBINPUT_DEVICE_CAP_TABLET_TOOL) { + return None; + } + let id = match self.tablet_id.get() { + Some(id) => id, + None => { + let id = self.state.tablet_ids.next(); + self.tablet_id.set(Some(id)); + id + } + }; + Some(Box::new(TabletInit { + id, + group: self.get_device_group(&dev), + name: dev.name(), + pid: dev.product(), + vid: dev.vendor(), + path: self.syspath.as_bytes().as_bstr().to_string(), + })) + } + + fn tablet_pad_info(&self) -> Option> { + let dev = self.inputdev.get()?; + let dev = dev.device(); + if !dev.has_cap(LIBINPUT_DEVICE_CAP_TABLET_PAD) { + return None; + } + let id = match self.tablet_pad_id.get() { + Some(id) => id, + None => { + let id = self.state.tablet_pad_ids.next(); + self.tablet_pad_id.set(Some(id)); + id + } + }; + let buttons = dev.pad_num_buttons(); + let strips = dev.pad_num_strips(); + let rings = dev.pad_num_rings(); + let mut groups = vec![]; + for n in 0..dev.pad_num_mode_groups() { + let Some(group) = dev.pad_mode_group(n) else { + break; + }; + groups.push(TabletPadGroupInit { + buttons: (0..buttons).filter(|b| group.has_button(*b)).collect(), + rings: (0..rings).filter(|b| group.has_ring(*b)).collect(), + strips: (0..strips).filter(|b| group.has_strip(*b)).collect(), + modes: group.num_modes(), + mode: group.mode(), + }); + } + Some(Box::new(TabletPadInit { + id, + group: self.get_device_group(&dev), + path: self.syspath.as_bytes().as_bstr().to_string(), + buttons, + strips, + rings, + groups, + })) + } } impl MetalInputDevice { @@ -655,4 +729,14 @@ impl MetalInputDevice { cb(); } } + + fn get_device_group(&self, dev: &LibInputDevice) -> InputDeviceGroupId { + let group = dev.device_group(); + let mut id = group.user_data(); + if id == 0 { + id = self.state.input_device_group_ids.next().raw(); + group.set_user_data(id); + } + InputDeviceGroupId::from_raw(id) + } } diff --git a/src/backends/metal/input.rs b/src/backends/metal/input.rs index c64f683c..c9788993 100644 --- a/src/backends/metal/input.rs +++ b/src/backends/metal/input.rs @@ -3,14 +3,25 @@ use { backend::{AxisSource, InputEvent, KeyState, ScrollAxis}, backends::metal::MetalBackend, fixed::Fixed, + ifs::wl_seat::tablet::{ + PadButtonState, TabletRingEventSource, TabletStripEventSource, TabletTool2dChange, + TabletToolCapability, TabletToolChanges, TabletToolId, TabletToolInit, + TabletToolPositionChange, TabletToolType, TabletToolWheelChange, ToolButtonState, + }, libinput::{ consts::{ - LIBINPUT_BUTTON_STATE_PRESSED, LIBINPUT_KEY_STATE_PRESSED, - LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, - LIBINPUT_SWITCH_LID, LIBINPUT_SWITCH_STATE_OFF, LIBINPUT_SWITCH_STATE_ON, - LIBINPUT_SWITCH_TABLET_MODE, + LIBINPUT_BUTTON_STATE_PRESSED, LIBINPUT_BUTTON_STATE_RELEASED, + LIBINPUT_KEY_STATE_PRESSED, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, LIBINPUT_SWITCH_LID, + LIBINPUT_SWITCH_STATE_OFF, LIBINPUT_SWITCH_STATE_ON, LIBINPUT_SWITCH_TABLET_MODE, + LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER, LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, LIBINPUT_TABLET_TOOL_TIP_DOWN, + LIBINPUT_TABLET_TOOL_TIP_UP, LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH, + LIBINPUT_TABLET_TOOL_TYPE_BRUSH, LIBINPUT_TABLET_TOOL_TYPE_ERASER, + LIBINPUT_TABLET_TOOL_TYPE_LENS, LIBINPUT_TABLET_TOOL_TYPE_MOUSE, + LIBINPUT_TABLET_TOOL_TYPE_PEN, LIBINPUT_TABLET_TOOL_TYPE_PENCIL, }, - event::LibInputEvent, + event::{LibInputEvent, LibInputEventTabletTool}, }, utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt}, }, @@ -103,6 +114,13 @@ impl MetalBackend { c::LIBINPUT_EVENT_GESTURE_HOLD_BEGIN => self.handle_gesture_hold_begin(event), c::LIBINPUT_EVENT_GESTURE_HOLD_END => self.handle_gesture_hold_end(event), c::LIBINPUT_EVENT_SWITCH_TOGGLE => self.handle_switch_toggle(event), + c::LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY => self.handle_tablet_tool_proximity(event), + c::LIBINPUT_EVENT_TABLET_TOOL_AXIS => self.handle_tablet_tool_axis(event), + c::LIBINPUT_EVENT_TABLET_TOOL_BUTTON => self.handle_tablet_tool_button(event), + c::LIBINPUT_EVENT_TABLET_TOOL_TIP => self.handle_tablet_tool_tip(event), + c::LIBINPUT_EVENT_TABLET_PAD_BUTTON => self.handle_tablet_pad_button(event), + c::LIBINPUT_EVENT_TABLET_PAD_RING => self.handle_tablet_pad_ring(event), + c::LIBINPUT_EVENT_TABLET_PAD_STRIP => self.handle_tablet_pad_strip(event), _ => {} } } @@ -320,4 +338,211 @@ impl MetalBackend { event: switch_event, }); } + + fn get_tool_id(&self, event: &LibInputEventTabletTool) -> TabletToolId { + let tool = event.tool(); + let mut user_data = tool.user_data(); + if user_data == 0 { + user_data = self.state.tablet_tool_ids.next().raw(); + tool.set_user_data(user_data); + } + TabletToolId::from_raw(user_data) + } + + fn build_tablet_tool_changed( + &self, + event: &LibInputEventTabletTool, + down: Option, + ) -> InputEvent { + let mut changes = Box::::default(); + changes.down = down; + if event.x_has_changed() || event.y_has_changed() { + changes.pos = Some(TabletTool2dChange { + x: TabletToolPositionChange { + x: event.x_transformed(1), + dx: event.dx(), + }, + y: TabletToolPositionChange { + x: event.y_transformed(1), + dx: event.dy(), + }, + }) + } + if event.pressure_has_changed() { + changes.pressure = Some(event.pressure()); + } + if event.distance_has_changed() { + changes.distance = Some(event.distance()); + } + if event.tilt_x_has_changed() || event.tilt_y_has_changed() { + changes.tilt = Some(TabletTool2dChange { + x: event.tilt_x(), + y: event.tilt_y(), + }); + } + if event.rotation_has_changed() { + changes.rotation = Some(event.rotation()); + } + if event.slider_has_changed() { + changes.slider = Some(event.slider_position()); + } + if event.wheel_has_changed() { + changes.wheel = Some(TabletToolWheelChange { + degrees: event.wheel_delta(), + clicks: event.wheel_delta_discrete(), + }); + } + InputEvent::TabletToolChanged { + time_usec: event.time_usec(), + id: self.get_tool_id(event), + changes, + } + } + + fn handle_tablet_tool_proximity(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_tool_event); + let id = self.get_tool_id(&event); + if event.proximity_state() == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN { + let Some(tablet_id) = dev.tablet_id.get() else { + return; + }; + let tool = event.tool(); + dev.event(InputEvent::TabletToolAdded { + time_usec: event.time_usec(), + init: Box::new(TabletToolInit { + tablet_id, + id, + type_: match tool.type_() { + LIBINPUT_TABLET_TOOL_TYPE_PEN => TabletToolType::Pen, + LIBINPUT_TABLET_TOOL_TYPE_ERASER => TabletToolType::Eraser, + LIBINPUT_TABLET_TOOL_TYPE_BRUSH => TabletToolType::Brush, + LIBINPUT_TABLET_TOOL_TYPE_PENCIL => TabletToolType::Pencil, + LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH => TabletToolType::Airbrush, + LIBINPUT_TABLET_TOOL_TYPE_MOUSE => TabletToolType::Mouse, + LIBINPUT_TABLET_TOOL_TYPE_LENS => TabletToolType::Lens, + _ => return, + }, + hardware_serial: tool.serial(), + hardware_id_wacom: tool.tool_id(), + capabilities: { + let mut caps = vec![]; + macro_rules! add_cap { + ($f:ident, $cap:ident) => { + if tool.$f() { + caps.push(TabletToolCapability::$cap); + } + }; + } + add_cap!(has_tilt, Tilt); + add_cap!(has_pressure, Pressure); + add_cap!(has_distance, Distance); + add_cap!(has_rotation, Rotation); + add_cap!(has_slider, Slider); + add_cap!(has_wheel, Wheel); + caps + }, + }), + }); + dev.event(self.build_tablet_tool_changed(&event, None)); + } else { + dev.event(InputEvent::TabletToolRemoved { + time_usec: event.time_usec(), + id, + }); + } + } + + fn handle_tablet_tool_tip(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_tool_event); + let down = match event.tip_state() { + LIBINPUT_TABLET_TOOL_TIP_UP => false, + LIBINPUT_TABLET_TOOL_TIP_DOWN => true, + _ => return, + }; + dev.event(self.build_tablet_tool_changed(&event, Some(down))); + } + + fn handle_tablet_tool_axis(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_tool_event); + dev.event(self.build_tablet_tool_changed(&event, None)); + } + + fn handle_tablet_tool_button(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_tool_event); + dev.event(InputEvent::TabletToolButton { + time_usec: event.time_usec(), + id: self.get_tool_id(&event), + button: event.button(), + state: match event.button_state() { + LIBINPUT_BUTTON_STATE_RELEASED => ToolButtonState::Released, + LIBINPUT_BUTTON_STATE_PRESSED => ToolButtonState::Pressed, + _ => return, + }, + }); + } + + fn handle_tablet_pad_button(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_pad_event); + let id = match dev.tablet_pad_id.get() { + None => return, + Some(id) => id, + }; + let state = match event.button_state() { + LIBINPUT_BUTTON_STATE_RELEASED => PadButtonState::Released, + LIBINPUT_BUTTON_STATE_PRESSED => PadButtonState::Pressed, + _ => return, + }; + dev.event(InputEvent::TabletPadModeSwitch { + time_usec: event.time_usec(), + pad: id, + group: event.mode_group().index(), + mode: event.mode(), + }); + dev.event(InputEvent::TabletPadButton { + time_usec: event.time_usec(), + id, + button: event.button_number(), + state, + }); + } + + fn handle_tablet_pad_ring(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_pad_event); + dev.event(InputEvent::TabletPadRing { + time_usec: event.time_usec(), + pad: match dev.tablet_pad_id.get() { + None => return, + Some(id) => id, + }, + ring: event.ring_number(), + source: match event.ring_source() { + LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER => Some(TabletRingEventSource::Finger), + _ => None, + }, + angle: match event.ring_position() { + n if n == -1.0 => None, + n => Some(n), + }, + }); + } + + fn handle_tablet_pad_strip(self: &Rc, event: LibInputEvent) { + let (event, dev) = unpack!(self, event, tablet_pad_event); + dev.event(InputEvent::TabletPadStrip { + time_usec: event.time_usec(), + pad: match dev.tablet_pad_id.get() { + None => return, + Some(id) => id, + }, + strip: event.strip_number(), + source: match event.strip_source() { + LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER => Some(TabletStripEventSource::Finger), + _ => None, + }, + position: match event.strip_position() { + n if n == -1.0 => None, + n => Some(n), + }, + }); + } } diff --git a/src/backends/metal/monitor.rs b/src/backends/metal/monitor.rs index acfa51e8..c9baf699 100644 --- a/src/backends/metal/monitor.rs +++ b/src/backends/metal/monitor.rs @@ -293,6 +293,7 @@ impl MetalBackend { let devnum = dev.devnum(); let devnode = dev.devnode()?; let sysname = dev.sysname()?; + let syspath = dev.syspath()?; log::info!("Device added: {}", devnode.to_bytes().as_bstr()); let mut slots = self.device_holder.input_devices.borrow_mut(); let slot = 'slot: { @@ -305,6 +306,7 @@ impl MetalBackend { slots.len() - 1 }; let dev = Rc::new(MetalInputDevice { + state: self.state.clone(), slot, id: device_id, devnum, @@ -312,6 +314,7 @@ impl MetalBackend { inputdev: Default::default(), devnode: devnode.to_owned(), _sysname: sysname.to_owned(), + syspath: syspath.to_owned(), removed: Cell::new(false), events: Default::default(), cb: Default::default(), @@ -321,6 +324,8 @@ impl MetalBackend { desired: Default::default(), transform_matrix: Default::default(), effective: Default::default(), + tablet_id: Default::default(), + tablet_pad_id: Default::default(), }); slots[slot] = Some(dev.clone()); self.device_holder diff --git a/src/cli/seat_test.rs b/src/cli/seat_test.rs index 7456ab37..cdc8422e 100644 --- a/src/cli/seat_test.rs +++ b/src/cli/seat_test.rs @@ -1,6 +1,7 @@ use { crate::{ cli::{GlobalArgs, SeatTestArgs}, + fixed::Fixed, ifs::wl_seat::wl_pointer::{PendingScroll, CONTINUOUS, FINGER, WHEEL}, tools::tool_client::{with_tool_client, Handle, ToolClient}, wire::{ @@ -8,7 +9,13 @@ use { jay_seat_events::{ Axis120, AxisFrame, AxisInverted, AxisPx, AxisSource, AxisStop, Button, HoldBegin, HoldEnd, Key, Modifiers, PinchBegin, PinchEnd, PinchUpdate, PointerAbs, PointerRel, - SwipeBegin, SwipeEnd, SwipeUpdate, SwitchEvent, + SwipeBegin, SwipeEnd, SwipeUpdate, SwitchEvent, TabletPadButton, + TabletPadModeSwitch, TabletPadRingAngle, TabletPadRingFrame, TabletPadRingSource, + TabletPadRingStop, TabletPadStripFrame, TabletPadStripPosition, + TabletPadStripSource, TabletPadStripStop, TabletToolButton, TabletToolDistance, + TabletToolDown, TabletToolFrame, TabletToolMotion, TabletToolPressure, + TabletToolProximityIn, TabletToolProximityOut, TabletToolRotation, + TabletToolSlider, TabletToolTilt, TabletToolUp, TabletToolWheel, }, }, }, @@ -42,6 +49,36 @@ impl SeatTest { } } +#[derive(Default, Debug, Copy, Clone)] +pub struct PendingTabletTool { + proximity_in: bool, + proximity_out: bool, + down: bool, + up: bool, + pos: Option<(Fixed, Fixed)>, + pressure: Option, + distance: Option, + tilt: Option<(f64, f64)>, + rotation: Option, + slider: Option, + wheel: Option<(f64, i32)>, + button: Option<(u32, u32)>, +} + +#[derive(Default, Debug, Copy, Clone)] +pub struct PendingTabletPadStrip { + source: u32, + pos: Option, + stop: bool, +} + +#[derive(Default, Debug, Copy, Clone)] +pub struct PendingTabletPadRing { + source: u32, + degrees: Option, + stop: bool, +} + async fn run(seat_test: Rc) { let tc = &seat_test.tc; let comp = tc.jay_compositor().await; @@ -344,6 +381,208 @@ async fn run(seat_test: Rc) { ); } }); + let tt = Rc::new(RefCell::new(PendingTabletTool::default())); + TabletToolProximityIn::handle(tc, se, tt.clone(), move |tt, _| { + tt.borrow_mut().proximity_in = true; + }); + TabletToolProximityOut::handle(tc, se, tt.clone(), move |tt, _| { + tt.borrow_mut().proximity_out = true; + }); + TabletToolDown::handle(tc, se, tt.clone(), move |tt, _| { + tt.borrow_mut().down = true; + }); + TabletToolUp::handle(tc, se, tt.clone(), move |tt, _| { + tt.borrow_mut().up = true; + }); + TabletToolMotion::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().pos = Some((ev.x, ev.y)); + }); + TabletToolPressure::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().pressure = Some(ev.pressure); + }); + TabletToolDistance::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().distance = Some(ev.distance); + }); + TabletToolTilt::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().tilt = Some((ev.tilt_x, ev.tilt_y)); + }); + TabletToolRotation::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().rotation = Some(ev.degrees); + }); + TabletToolSlider::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().slider = Some(ev.position); + }); + TabletToolWheel::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().wheel = Some((ev.degrees, ev.clicks)); + }); + TabletToolButton::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().button = Some((ev.button, ev.state)); + }); + let st = seat_test.clone(); + TabletToolFrame::handle(tc, se, tt.clone(), move |tt, ev| { + let tt = tt.take(); + if !all && ev.seat != seat { + return; + } + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + print!( + "Time: {:.4}, Device: {}, Tool: {}", + time(ev.time_usec), + ev.input_device, + ev.tool, + ); + if tt.proximity_in { + print!(", proximity in"); + } + if tt.proximity_out { + print!(", proximity out"); + } + if tt.down { + print!(", down"); + } + if tt.up { + print!(", up"); + } + if let Some((x, y)) = tt.pos { + print!(", pos: {x}x{y}"); + } + if let Some(val) = tt.pressure { + print!(", pressure: {val}"); + } + if let Some(val) = tt.distance { + print!(", distance: {val}"); + } + if let Some((x, y)) = tt.tilt { + print!(", tilt: {x}x{y}"); + } + if let Some(val) = tt.rotation { + print!(", rotation: {val}"); + } + if let Some(val) = tt.slider { + print!(", slider: {val}"); + } + if let Some((degrees, clicks)) = tt.wheel { + print!(", wheel degrees: {degrees}, wheel clicks: {clicks}"); + } + if let Some((button, state)) = tt.button { + let dir = match state { + 0 => "up", + _ => "down", + }; + print!(", button {button} {dir}"); + } + println!(); + }); + let st = seat_test.clone(); + TabletPadModeSwitch::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + println!( + "Time: {:.4}, Device: {}, mode switch: {}", + time(ev.time_usec), + ev.input_device, + ev.mode, + ); + } + }); + let st = seat_test.clone(); + TabletPadButton::handle(tc, se, (), move |_, ev| { + if all || ev.seat == seat { + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + let dir = match ev.state { + 0 => "up", + _ => "down", + }; + println!( + "Time: {:.4}, Device: {}, Button {} {dir}", + time(ev.time_usec), + ev.input_device, + ev.button, + ); + } + }); + let tt = Rc::new(RefCell::new(PendingTabletPadStrip::default())); + TabletPadStripSource::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().source = ev.source; + }); + TabletPadStripPosition::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().pos = Some(ev.position); + }); + TabletPadStripStop::handle(tc, se, tt.clone(), move |tt, _| { + tt.borrow_mut().stop = true; + }); + let st = seat_test.clone(); + TabletPadStripFrame::handle(tc, se, tt.clone(), move |tt, ev| { + let tt = tt.take(); + if !all && ev.seat != seat { + return; + } + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + print!( + "Time: {:.4}, Device: {}, Strip: {}", + time(ev.time_usec), + ev.input_device, + ev.strip, + ); + let source = match tt.source { + 1 => "finger", + _ => "unknown", + }; + print!(", source: {source}"); + if let Some(pos) = tt.pos { + print!(", pos: {pos}"); + } + if tt.stop { + print!(", stop"); + } + println!(); + }); + let tt = Rc::new(RefCell::new(PendingTabletPadRing::default())); + TabletPadRingSource::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().source = ev.source; + }); + TabletPadRingAngle::handle(tc, se, tt.clone(), move |tt, ev| { + tt.borrow_mut().degrees = Some(ev.degrees); + }); + TabletPadRingStop::handle(tc, se, tt.clone(), move |tt, _| { + tt.borrow_mut().stop = true; + }); + let st = seat_test.clone(); + TabletPadRingFrame::handle(tc, se, tt.clone(), move |tt, ev| { + let tt = tt.take(); + if !all && ev.seat != seat { + return; + } + if all { + print!("Seat: {}, ", st.name(ev.seat)); + } + print!( + "Time: {:.4}, Device: {}, Ring: {}", + time(ev.time_usec), + ev.input_device, + ev.ring, + ); + let source = match tt.source { + 1 => "finger", + _ => "unknown", + }; + print!(", source: {source}"); + if let Some(val) = tt.degrees { + print!(", degrees: {val}"); + } + if tt.stop { + print!(", stop"); + } + println!(); + }); pending::<()>().await; } diff --git a/src/client.rs b/src/client.rs index ed64086d..d964bbd9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -17,6 +17,7 @@ use { copyhashmap::{CopyHashMap, Locked}, errorfmt::ErrorFmt, numcell::NumCell, + pending_serial::PendingSerial, trim::AsciiTrim, }, wire::WlRegistryId, @@ -350,6 +351,10 @@ impl Client { self.state.next_serial(Some(self)) } + pub fn pending_serial(&self) -> PendingSerial<'_> { + PendingSerial::new(self) + } + pub fn new_id>(&self) -> Result { self.objects.id(self) } diff --git a/src/client/objects.rs b/src/client/objects.rs index 7309ee1f..4616e014 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -15,7 +15,7 @@ use { wl_output::WlOutput, wl_region::WlRegion, wl_registry::WlRegistry, - wl_seat::{wl_pointer::WlPointer, WlSeat}, + wl_seat::{tablet::zwp_tablet_tool_v2::ZwpTabletToolV2, wl_pointer::WlPointer, WlSeat}, wl_surface::{ xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface}, WlSurface, @@ -35,7 +35,7 @@ use { WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, - ZwpPrimarySelectionSourceV1Id, + ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, }, }, std::{cell::RefCell, mem, rc::Rc}, @@ -65,6 +65,7 @@ pub struct Objects { pub zwlr_data_sources: CopyHashMap>, pub jay_toplevels: CopyHashMap>, pub drm_lease_outputs: CopyHashMap>, + pub tablet_tools: CopyHashMap>, ids: RefCell>, } @@ -96,6 +97,7 @@ impl Objects { zwlr_data_sources: Default::default(), jay_toplevels: Default::default(), drm_lease_outputs: Default::default(), + tablet_tools: Default::default(), ids: RefCell::new(vec![]), } } @@ -131,6 +133,7 @@ impl Objects { self.zwlr_data_sources.clear(); self.jay_toplevels.clear(); self.drm_lease_outputs.clear(); + self.tablet_tools.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/compositor.rs b/src/compositor.rs index ae663ce0..065629a6 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -238,9 +238,12 @@ fn start_compositor2( security_context_acceptors: Default::default(), cursor_user_group_ids: Default::default(), cursor_user_ids: Default::default(), - cursor_users: Default::default(), cursor_user_groups: Default::default(), cursor_user_group_hardware_cursor: Default::default(), + input_device_group_ids: Default::default(), + tablet_ids: Default::default(), + tablet_tool_ids: Default::default(), + tablet_pad_ids: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/globals.rs b/src/globals.rs index 2989539b..d893a06c 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -18,6 +18,7 @@ use { wl_registry::WlRegistry, wl_seat::{ ext_transient_seat_manager_v1::ExtTransientSeatManagerV1Global, + tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2Global, text_input::{ zwp_input_method_manager_v2::ZwpInputMethodManagerV2Global, zwp_text_input_manager_v3::ZwpTextInputManagerV3Global, @@ -193,6 +194,7 @@ impl Globals { add_singleton!(XdgWmDialogV1Global); add_singleton!(ExtTransientSeatManagerV1Global); add_singleton!(ZwpPointerGesturesV1Global); + add_singleton!(ZwpTabletManagerV2Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs/jay_seat_events.rs b/src/ifs/jay_seat_events.rs index e9ddb35d..9df7d121 100644 --- a/src/ifs/jay_seat_events.rs +++ b/src/ifs/jay_seat_events.rs @@ -3,7 +3,14 @@ use { backend::{InputDeviceId, KeyState}, client::Client, fixed::Fixed, - ifs::wl_seat::{wl_pointer::PendingScroll, SeatId}, + ifs::wl_seat::{ + tablet::{ + PadButtonState, TabletRingEventSource, TabletStripEventSource, TabletTool, + TabletToolChanges, TabletToolId, ToolButtonState, + }, + wl_pointer::PendingScroll, + SeatId, + }, leaks::Tracker, object::{Object, Version}, wire::{jay_seat_events::*, JaySeatEventsId}, @@ -236,6 +243,231 @@ impl JaySeatEvents { event: event as _, }); } + + pub fn send_tablet_tool_proximity_in( + &self, + seat: SeatId, + tablet: InputDeviceId, + tool: TabletToolId, + time_usec: u64, + ) { + self.client + .event(TabletToolProximityIn { self_id: self.id }); + self.client.event(TabletToolFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: tablet.raw(), + tool: tool.raw() as _, + }); + } + + pub fn send_tablet_tool_proximity_out( + &self, + seat: SeatId, + tablet: InputDeviceId, + tool: TabletToolId, + time_usec: u64, + ) { + self.client + .event(TabletToolProximityOut { self_id: self.id }); + self.client.event(TabletToolFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: tablet.raw(), + tool: tool.raw() as _, + }); + } + + pub fn send_tablet_tool_changes( + &self, + seat: SeatId, + tablet: InputDeviceId, + tool: &TabletTool, + time_usec: u64, + changes: &TabletToolChanges, + ) { + let self_id = self.id; + if let Some(down) = changes.down { + match down { + true => self.client.event(TabletToolDown { self_id }), + false => self.client.event(TabletToolUp { self_id }), + } + } + if changes.pos.is_some() { + let (x, y) = tool.cursor().position(); + self.client.event(TabletToolMotion { self_id, x, y }); + } + if let Some(val) = changes.pressure { + self.client.event(TabletToolPressure { + self_id, + pressure: val, + }); + } + if let Some(val) = changes.distance { + self.client.event(TabletToolDistance { + self_id, + distance: val, + }); + } + if let Some(val) = changes.tilt { + self.client.event(TabletToolTilt { + self_id, + tilt_x: val.x, + tilt_y: val.y, + }); + } + if let Some(val) = changes.rotation { + self.client.event(TabletToolRotation { + self_id, + degrees: val, + }); + } + if let Some(val) = changes.slider { + self.client.event(TabletToolSlider { + self_id, + position: val, + }); + } + if let Some(val) = changes.wheel { + self.client.event(TabletToolWheel { + self_id, + degrees: val.degrees, + clicks: val.clicks, + }); + } + self.client.event(TabletToolFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: tablet.raw(), + tool: tool.id.raw() as _, + }); + } + + pub fn send_tablet_tool_button( + &self, + seat: SeatId, + tablet: InputDeviceId, + tool: &TabletTool, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + self.client.event(TabletToolButton { + self_id: self.id, + button, + state: state as _, + }); + self.client.event(TabletToolFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: tablet.raw(), + tool: tool.id.raw() as _, + }); + } + + pub fn send_tablet_pad_mode_switch( + &self, + seat: SeatId, + pad: InputDeviceId, + time_usec: u64, + group: u32, + mode: u32, + ) { + self.client.event(TabletPadModeSwitch { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: pad.raw(), + group, + mode, + }); + } + + pub fn send_tablet_pad_button( + &self, + seat: SeatId, + pad: InputDeviceId, + time_usec: u64, + button: u32, + state: PadButtonState, + ) { + self.client.event(TabletPadButton { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: pad.raw(), + button, + state: state as _, + }); + } + + pub fn send_tablet_pad_strip( + &self, + seat: SeatId, + pad: InputDeviceId, + time_usec: u64, + strip: u32, + source: Option, + position: Option, + ) { + if let Some(source) = source { + self.client.event(TabletPadStripSource { + self_id: self.id, + source: source as _, + }); + } + if let Some(position) = position { + self.client.event(TabletPadStripPosition { + self_id: self.id, + position, + }); + } else { + self.client.event(TabletPadStripStop { self_id: self.id }); + } + self.client.event(TabletPadStripFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: pad.raw(), + strip, + }); + } + + pub fn send_tablet_pad_ring( + &self, + seat: SeatId, + pad: InputDeviceId, + time_usec: u64, + ring: u32, + source: Option, + degrees: Option, + ) { + if let Some(source) = source { + self.client.event(TabletPadRingSource { + self_id: self.id, + source: source as _, + }); + } + if let Some(degrees) = degrees { + self.client.event(TabletPadRingAngle { + self_id: self.id, + degrees, + }); + } else { + self.client.event(TabletPadRingStop { self_id: self.id }); + } + self.client.event(TabletPadRingFrame { + self_id: self.id, + seat: seat.raw(), + time_usec, + input_device: pad.raw(), + ring, + }); + } } impl JaySeatEventsRequestHandler for JaySeatEvents { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index b79b039f..b3eb8cc7 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -4,6 +4,7 @@ pub mod ext_transient_seat_v1; mod gesture_owner; mod kb_owner; mod pointer_owner; +pub mod tablet; pub mod text_input; pub mod wl_keyboard; pub mod wl_pointer; @@ -46,6 +47,7 @@ use { gesture_owner::GestureOwnerHolder, kb_owner::KbOwnerHolder, pointer_owner::PointerOwnerHolder, + tablet::TabletSeatData, text_input::{ zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2, zwp_input_method_v2::ZwpInputMethodV2, zwp_text_input_v3::ZwpTextInputV3, @@ -64,6 +66,7 @@ use { }, leaks::Tracker, object::{Object, Version}, + rect::Rect, state::{DeviceHandlerData, State}, time::now_usec, tree::{ @@ -188,6 +191,7 @@ pub struct WlSeatGlobal { swipe_bindings: PerClientBindings, pinch_bindings: PerClientBindings, hold_bindings: PerClientBindings, + tablet: TabletSeatData, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; @@ -252,6 +256,7 @@ impl WlSeatGlobal { swipe_bindings: Default::default(), pinch_bindings: Default::default(), hold_bindings: Default::default(), + tablet: Default::default(), }); slf.pointer_cursor.set_owner(slf.clone()); let seat = slf.clone(); @@ -861,6 +866,7 @@ impl WlSeatGlobal { self.pinch_bindings.clear(); self.hold_bindings.clear(); self.cursor_user_group.detach(); + self.tablet_clear(); } pub fn id(&self) -> SeatId { @@ -1122,7 +1128,7 @@ impl DeviceHandlerData { pub fn set_seat(&self, seat: Option>) { let old = self.seat.set(seat.clone()); if let Some(old) = old { - if let Some(new) = seat { + if let Some(new) = &seat { if old.id() == new.id() { return; } @@ -1131,8 +1137,22 @@ impl DeviceHandlerData { let xkb_state = &mut *xkb_state.borrow_mut(); xkb_state.reset(); old.handle_xkb_state_change(xkb_state, xkb_state); + if let Some(info) = &self.tablet_init { + old.tablet_remove_tablet(info.id); + } + if let Some(info) = &self.tablet_pad_init { + old.tablet_remove_tablet_pad(info.id); + } } self.update_xkb_state(); + if let Some(seat) = &seat { + if let Some(info) = &self.tablet_init { + seat.tablet_add_tablet(self.device.id(), info); + } + if let Some(info) = &self.tablet_pad_init { + seat.tablet_add_tablet_pad(self.device.id(), info); + } + } } pub fn set_keymap(&self, keymap: Option>) { @@ -1177,4 +1197,13 @@ impl DeviceHandlerData { } } } + + pub fn get_rect(&self, state: &State) -> Rect { + if let Some(output) = self.output.get() { + if let Some(output) = output.get() { + return output.pos.get(); + } + } + state.root.extents.get() + } } diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 7bb9cd18..be5cab5a 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -15,6 +15,7 @@ use { DynDataSource, }, wl_seat::{ + tablet::{TabletPad, TabletPadId, TabletTool, TabletToolId}, text_input::TextDisconnectReason, wl_keyboard::{self, WlKeyboard}, wl_pointer::{ @@ -25,7 +26,7 @@ use { }, zwp_pointer_constraints_v1::{ConstraintType, SeatConstraintStatus}, zwp_relative_pointer_v1::ZwpRelativePointerV1, - Dnd, SeatId, WlSeat, WlSeatGlobal, CHANGE_CURSOR_MOVED, + Dnd, SeatId, WlSeat, WlSeatGlobal, CHANGE_CURSOR_MOVED, CHANGE_TREE, }, wl_surface::{xdg_surface::xdg_popup::XdgPopup, WlSurface}, }, @@ -55,6 +56,8 @@ pub struct NodeSeatState { gesture_foci: SmallMap, 1>, pointer_grabs: SmallMap, 1>, dnd_targets: SmallMap, 1>, + tablet_pad_foci: SmallMap, 1>, + tablet_tool_foci: SmallMap, 1>, } impl NodeSeatState { @@ -92,6 +95,22 @@ impl NodeSeatState { self.pointer_grabs.remove(&seat.id); } + pub(super) fn add_tablet_pad_focus(&self, pad: &Rc) { + self.tablet_pad_foci.insert(pad.id, pad.clone()); + } + + pub(super) fn remove_tablet_pad_focus(&self, pad: &TabletPad) { + self.tablet_pad_foci.remove(&pad.id); + } + + pub(super) fn add_tablet_tool_focus(&self, tool: &Rc) { + self.tablet_tool_foci.insert(tool.id, tool.clone()); + } + + pub(super) fn remove_tablet_tool_focus(&self, tool: &TabletTool) { + self.tablet_tool_foci.remove(&tool.id); + } + pub(super) fn add_dnd_target(&self, seat: &Rc) { self.dnd_targets.insert(seat.id, seat.clone()); } @@ -163,6 +182,12 @@ impl NodeSeatState { seat.pointer_stack_modified.set(true); seat.state.tree_changed(); } + while let Some((_, tool)) = self.tablet_tool_foci.pop() { + tool.tool_owner.focus_root(&tool); + } + while let Some((_, pad)) = self.tablet_pad_foci.pop() { + pad.pad_owner.focus_root(&pad); + } self.release_kb_focus2(focus_last); } @@ -203,7 +228,13 @@ impl WlSeatGlobal { | InputEvent::PinchEnd { time_usec, .. } | InputEvent::HoldBegin { time_usec, .. } | InputEvent::HoldEnd { time_usec, .. } - | InputEvent::SwitchEvent { time_usec, .. } => { + | InputEvent::SwitchEvent { time_usec, .. } + | InputEvent::TabletToolChanged { time_usec, .. } + | InputEvent::TabletToolButton { time_usec, .. } + | InputEvent::TabletPadButton { time_usec, .. } + | InputEvent::TabletPadModeSwitch { time_usec, .. } + | InputEvent::TabletPadRing { time_usec, .. } + | InputEvent::TabletPadStrip { time_usec, .. } => { self.last_input_usec.set(time_usec); if self.idle_notifications.is_not_empty() { for (_, notification) in self.idle_notifications.lock().drain() { @@ -214,7 +245,39 @@ impl WlSeatGlobal { InputEvent::AxisPx { .. } | InputEvent::AxisSource { .. } | InputEvent::AxisStop { .. } - | InputEvent::Axis120 { .. } => {} + | InputEvent::Axis120 { .. } + | InputEvent::TabletToolAdded { .. } + | InputEvent::TabletToolRemoved { .. } => {} + } + match event { + InputEvent::ConnectorPosition { .. } + | InputEvent::Motion { .. } + | InputEvent::Button { .. } + | InputEvent::AxisFrame { .. } + | InputEvent::SwipeBegin { .. } + | InputEvent::SwipeUpdate { .. } + | InputEvent::SwipeEnd { .. } + | InputEvent::PinchBegin { .. } + | InputEvent::PinchUpdate { .. } + | InputEvent::PinchEnd { .. } + | InputEvent::HoldBegin { .. } + | InputEvent::HoldEnd { .. } => { + self.pointer_cursor.activate(); + } + InputEvent::Key { .. } => {} + InputEvent::AxisPx { .. } => {} + InputEvent::AxisSource { .. } => {} + InputEvent::AxisStop { .. } => {} + InputEvent::Axis120 { .. } => {} + InputEvent::SwitchEvent { .. } => {} + InputEvent::TabletToolAdded { .. } => {} + InputEvent::TabletToolChanged { .. } => {} + InputEvent::TabletToolButton { .. } => {} + InputEvent::TabletToolRemoved { .. } => {} + InputEvent::TabletPadButton { .. } => {} + InputEvent::TabletPadModeSwitch { .. } => {} + InputEvent::TabletPadRing { .. } => {} + InputEvent::TabletPadStrip { .. } => {} } match event { InputEvent::Key { @@ -305,6 +368,49 @@ impl WlSeatGlobal { InputEvent::SwitchEvent { time_usec, event } => { self.switch_event(dev.device.id(), time_usec, event) } + InputEvent::TabletToolAdded { time_usec, init } => { + self.tablet_handle_new_tool(time_usec, &init) + } + InputEvent::TabletToolChanged { + time_usec, + id, + changes: change, + } => self.tablet_event_tool_changes(id, time_usec, dev.get_rect(&self.state), &change), + InputEvent::TabletToolButton { + time_usec, + id, + button, + state, + } => self.tablet_event_tool_button(id, time_usec, button, state), + InputEvent::TabletToolRemoved { time_usec, id } => { + self.tablet_handle_remove_tool(time_usec, id) + } + InputEvent::TabletPadButton { + time_usec, + id, + button, + state, + } => self.tablet_event_pad_button(id, time_usec, button, state), + InputEvent::TabletPadModeSwitch { + time_usec, + pad, + group, + mode, + } => self.tablet_event_pad_mode_switch(pad, time_usec, group, mode), + InputEvent::TabletPadRing { + time_usec, + pad, + ring, + source, + angle, + } => self.tablet_event_pad_ring(pad, ring, source, angle, time_usec), + InputEvent::TabletPadStrip { + time_usec, + pad, + strip, + source, + position, + } => self.tablet_event_pad_strip(pad, strip, source, position, time_usec), } } @@ -784,6 +890,9 @@ impl WlSeatGlobal { pub(super) fn apply_changes(self: &Rc) { self.state.damage(); self.pointer_owner.apply_changes(self); + if self.changes.get().contains(CHANGE_TREE) { + self.tablet_apply_changes(); + } self.changes.set(0); } } diff --git a/src/ifs/wl_seat/kb_owner.rs b/src/ifs/wl_seat/kb_owner.rs index cda1eeb5..796aeb57 100644 --- a/src/ifs/wl_seat/kb_owner.rs +++ b/src/ifs/wl_seat/kb_owner.rs @@ -79,6 +79,7 @@ impl KbOwner for DefaultKbOwner { // log::info!("focus {}", node.node_id()); node.clone().node_on_focus(seat); seat.keyboard_node.set(node.clone()); + seat.tablet_on_keyboard_node_change(); } } diff --git a/src/ifs/wl_seat/tablet.rs b/src/ifs/wl_seat/tablet.rs new file mode 100644 index 00000000..bd8517d6 --- /dev/null +++ b/src/ifs/wl_seat/tablet.rs @@ -0,0 +1,393 @@ +use { + crate::{ + backend::{InputDeviceGroupId, InputDeviceId}, + cursor_user::CursorUser, + ifs::{ + wl_seat::{ + tablet::{ + pad_owner::PadOwnerHolder, tablet_bindings::TabletBindings, + tool_owner::ToolOwnerHolder, zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2, + zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, + zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, + zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_seat_v2::ZwpTabletSeatV2, + zwp_tablet_tool_v2::ZwpTabletToolV2, zwp_tablet_v2::ZwpTabletV2, + }, + WlSeatGlobal, + }, + wl_surface::WlSurface, + }, + object::Version, + time::now_usec, + tree::{FoundNode, Node}, + utils::{bindings::PerClientBindings, clonecell::CloneCell, copyhashmap::CopyHashMap}, + }, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, +}; + +mod pad; +mod pad_owner; +mod tablet_bindings; +mod tool; +pub mod tool_owner; +pub mod zwp_tablet_manager_v2; +pub mod zwp_tablet_pad_group_v2; +pub mod zwp_tablet_pad_ring_v2; +pub mod zwp_tablet_pad_strip_v2; +pub mod zwp_tablet_pad_v2; +pub mod zwp_tablet_seat_v2; +pub mod zwp_tablet_tool_v2; +pub mod zwp_tablet_v2; + +#[derive(Default)] +pub struct TabletSeatData { + seats: PerClientBindings, + tablets: CopyHashMap>, + tools: CopyHashMap>, + pads: CopyHashMap>, +} + +#[derive(Debug, Clone)] +pub struct TabletInit { + pub id: TabletId, + pub group: InputDeviceGroupId, + pub name: String, + pub pid: u32, + pub vid: u32, + pub path: String, +} + +#[derive(Debug, Clone)] +pub struct TabletToolInit { + pub tablet_id: TabletId, + pub id: TabletToolId, + pub type_: TabletToolType, + pub hardware_serial: u64, + pub hardware_id_wacom: u64, + pub capabilities: Vec, +} + +#[derive(Debug, Clone)] +pub struct TabletPadInit { + pub id: TabletPadId, + pub group: InputDeviceGroupId, + pub path: String, + pub buttons: u32, + pub strips: u32, + pub rings: u32, + pub groups: Vec, +} + +#[derive(Debug, Clone)] +pub struct TabletPadGroupInit { + pub buttons: Vec, + pub rings: Vec, + pub strips: Vec, + pub modes: u32, + pub mode: u32, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PadButtonState { + Released, + Pressed, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ToolButtonState { + Released, + Pressed, +} + +linear_ids!(TabletIds, TabletId); + +pub struct Tablet { + _id: TabletId, + dev: InputDeviceId, + group: InputDeviceGroupId, + name: String, + pid: u32, + vid: u32, + path: String, + bindings: TabletBindings, + tools: CopyHashMap>, + pads: CopyHashMap>, + tree: RefCell>, + seat: Rc, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TabletToolType { + Pen, + Eraser, + Brush, + Pencil, + Airbrush, + #[allow(dead_code)] + Finger, + Mouse, + Lens, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TabletToolCapability { + Tilt, + Pressure, + Distance, + Rotation, + Slider, + Wheel, +} + +linear_ids!(TabletToolIds, TabletToolId, usize); + +#[derive(Default)] +pub struct TabletToolOpt { + tool: CloneCell>>, +} + +pub struct TabletTool { + pub id: TabletToolId, + opt: Rc, + tablet: Rc, + type_: TabletToolType, + hardware_serial: u64, + hardware_id_wacom: u64, + capabilities: Vec, + bindings: TabletBindings, + node: CloneCell>, + pub(super) tool_owner: ToolOwnerHolder, + cursor: Rc, + + down: Cell, + pressure: Cell, + distance: Cell, + tilt_x: Cell, + tilt_y: Cell, + rotation: Cell, + slider: Cell, +} + +linear_ids!(TabletPadIds, TabletPadId); + +pub struct TabletPad { + pub id: TabletPadId, + dev: InputDeviceId, + seat: Rc, + group: InputDeviceGroupId, + tablet: CloneCell>>, + path: String, + buttons: u32, + bindings: TabletBindings, + groups: Vec>, + strips: Vec>, + rings: Vec>, + node: CloneCell>, + pub(super) pad_owner: PadOwnerHolder, +} + +pub struct TabletPadGroup { + buttons: Vec, + mode: Cell, + modes: u32, + rings: Vec, + strips: Vec, + bindings: TabletBindings, +} + +pub struct TabletPadStrip { + bindings: TabletBindings, +} + +pub struct TabletPadRing { + bindings: TabletBindings, +} + +#[derive(Copy, Clone, Debug)] +pub enum TabletRingEventSource { + Finger, +} + +#[derive(Copy, Clone, Debug)] +pub enum TabletStripEventSource { + Finger, +} + +#[derive(Debug, Default)] +pub struct TabletToolChanges { + pub down: Option, + pub pos: Option>, + pub pressure: Option, + pub distance: Option, + pub tilt: Option>, + pub rotation: Option, + pub slider: Option, + pub wheel: Option, +} + +#[derive(Copy, Clone, Debug)] +pub struct TabletTool2dChange { + pub x: T, + pub y: T, +} + +#[derive(Copy, Clone, Debug)] +pub struct TabletToolPositionChange { + pub x: f64, + pub dx: f64, +} + +#[derive(Copy, Clone, Debug)] +pub struct TabletToolWheelChange { + pub degrees: f64, + pub clicks: i32, +} + +impl WlSeatGlobal { + fn tablet_add_seat(&self, seat: &Rc) { + self.tablet.seats.add(&seat.client, seat); + for tablet in self.tablet.tablets.lock().values() { + seat.announce_tablet(tablet); + } + for tool in self.tablet.tools.lock().values() { + seat.announce_tool(tool); + } + for pad in self.tablet.pads.lock().values() { + seat.announce_pad(pad); + } + } + + pub fn tablet_add_tablet(self: &Rc, dev: InputDeviceId, init: &TabletInit) { + let tablet = Rc::new(Tablet { + _id: init.id, + dev, + group: init.group, + name: init.name.clone(), + pid: init.pid, + vid: init.vid, + path: init.path.clone(), + bindings: Default::default(), + tools: Default::default(), + pads: Default::default(), + tree: Default::default(), + seat: self.clone(), + }); + self.tablet.tablets.set(init.id, tablet.clone()); + self.tablet_for_each_seat_obj(|s| s.announce_tablet(&tablet)); + for pad in self.tablet.pads.lock().values() { + if pad.tablet.is_none() && pad.group == init.group { + self.connect_tablet_and_pad(&tablet, pad); + } + } + } + + fn tablet_for_each_seat_obj(&self, mut f: impl FnMut(&Rc)) { + for seats in self.tablet.seats.borrow().values() { + for seat in seats.values() { + f(seat); + } + } + } + + pub fn tablet_clear(&self) { + self.tablet.seats.clear(); + for (_, tablet) in self.tablet.tablets.lock().drain() { + tablet.pads.clear(); + tablet.bindings.clear(); + tablet.tools.clear(); + } + for (_, tool) in self.tablet.tools.lock().drain() { + tool.cursor.detach(); + tool.opt.tool.take(); + tool.tool_owner.destroy(&tool); + tool.bindings.clear(); + } + for (_, pad) in self.tablet.pads.lock().drain() { + pad.pad_owner.destroy(&pad); + pad.tablet.take(); + pad.bindings.clear(); + for group in &pad.groups { + group.bindings.clear(); + } + for rings in &pad.rings { + rings.bindings.clear(); + } + for strips in &pad.strips { + strips.bindings.clear(); + } + } + } + + pub fn tablet_remove_tablet(self: &Rc, id: TabletId) { + let Some(tablet) = self.tablet.tablets.remove(&id) else { + return; + }; + for (_, tool) in tablet.tools.lock().drain() { + self.tablet_handle_remove_tool(now_usec(), tool.id); + } + for (_, pad) in tablet.pads.lock().drain() { + pad.pad_owner.destroy(&pad); + pad.tablet.take(); + } + for (_, binding) in tablet.bindings.lock().drain() { + binding.send_removed(); + } + } + + fn connect_tablet_and_pad(self: &Rc, tablet: &Rc, pad: &Rc) { + pad.tablet.set(Some(tablet.clone())); + tablet.pads.set(pad.id, pad.clone()); + pad.pad_owner.update_node(pad); + } + + pub fn tablet_on_keyboard_node_change(self: &Rc) { + if self.tablet.pads.is_empty() { + return; + } + for pad in self.tablet.pads.lock().values() { + if pad.tablet.is_some() { + pad.pad_owner.update_node(pad); + } + } + } + + fn tablet_for_each_seat(&self, surface: &WlSurface, f: impl FnMut(&ZwpTabletSeatV2)) { + self.tablet + .seats + .for_each(surface.client.id, Version::ALL, f) + } + + pub(super) fn tablet_apply_changes(self: &Rc) { + if self.tablet.tools.is_empty() { + return; + } + let now = now_usec(); + for tool in self.tablet.tools.lock().values() { + tool.tool_owner.apply_changes(tool, now, None); + } + } +} + +fn normalizei(n: f64) -> i32 { + (65535.0 * n) as i32 +} + +fn normalizeu(n: f64) -> u32 { + normalizei(n) as u32 +} + +impl TabletTool { + pub fn cursor(&self) -> &Rc { + &self.cursor + } + + pub fn node(&self) -> Rc { + self.node.get() + } + + pub fn seat(&self) -> &Rc { + &self.tablet.seat + } +} diff --git a/src/ifs/wl_seat/tablet/pad.rs b/src/ifs/wl_seat/tablet/pad.rs new file mode 100644 index 00000000..7eddb329 --- /dev/null +++ b/src/ifs/wl_seat/tablet/pad.rs @@ -0,0 +1,290 @@ +use { + crate::{ + backend::InputDeviceId, + fixed::Fixed, + ifs::{ + wl_seat::{ + tablet::{ + normalizeu, zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_v2::ZwpTabletV2, + PadButtonState, TabletPad, TabletPadGroup, TabletPadId, TabletPadInit, + TabletPadRing, TabletPadStrip, TabletRingEventSource, TabletStripEventSource, + }, + WlSeatGlobal, + }, + wl_surface::WlSurface, + }, + time::{now_usec, usec_to_msec}, + utils::clonecell::CloneCell, + }, + std::{cell::Cell, rc::Rc}, +}; + +impl WlSeatGlobal { + pub fn tablet_add_tablet_pad(self: &Rc, dev: InputDeviceId, init: &TabletPadInit) { + let mut strips = Vec::new(); + for _ in 0..init.strips { + strips.push(Rc::new(TabletPadStrip { + bindings: Default::default(), + })); + } + let mut rings = Vec::new(); + for _ in 0..init.rings { + rings.push(Rc::new(TabletPadRing { + bindings: Default::default(), + })); + } + let mut groups = Vec::new(); + for group_init in &init.groups { + groups.push(Rc::new(TabletPadGroup { + buttons: group_init.buttons.clone(), + mode: Cell::new(group_init.mode), + modes: group_init.modes, + rings: group_init.rings.clone(), + strips: group_init.strips.clone(), + bindings: Default::default(), + })); + } + let pad = Rc::new(TabletPad { + id: init.id, + dev, + seat: self.clone(), + group: init.group, + tablet: Default::default(), + path: init.path.clone(), + buttons: init.buttons, + bindings: Default::default(), + groups, + strips, + rings, + node: CloneCell::new(self.state.root.clone()), + pad_owner: Default::default(), + }); + self.tablet.pads.set(init.id, pad.clone()); + self.tablet_for_each_seat_obj(|s| s.announce_pad(&pad)); + for tablet in self.tablet.tablets.lock().values() { + if tablet.group == init.group { + self.connect_tablet_and_pad(tablet, &pad); + } + } + } + + pub fn tablet_remove_tablet_pad(self: &Rc, id: TabletPadId) { + let Some(pad) = self.tablet.pads.remove(&id) else { + return; + }; + pad.pad_owner.destroy(&pad); + if let Some(tablet) = pad.tablet.take() { + tablet.pads.remove(&pad.id); + } + for (_, binding) in pad.bindings.lock().drain() { + binding.send_removed(); + } + } + + pub fn tablet_event_pad_mode_switch( + self: &Rc, + pad: TabletPadId, + time_usec: u64, + group_idx: u32, + mode: u32, + ) { + if let Some(pad) = self.tablet.pads.get(&pad) { + if let Some(group) = pad.groups.get(group_idx as usize) { + if group.mode.replace(mode) != mode { + self.state.for_each_seat_tester(|t| { + t.send_tablet_pad_mode_switch(self.id, pad.dev, time_usec, group_idx, mode) + }); + if pad.tablet.is_some() { + let node = pad.node.get(); + node.node_on_tablet_pad_mode_switch(&pad, group, time_usec, mode); + } + } + } + } + } + + pub fn tablet_event_pad_button( + self: &Rc, + pad: TabletPadId, + time_usec: u64, + button: u32, + state: PadButtonState, + ) { + if let Some(pad) = self.tablet.pads.get(&pad) { + self.state.for_each_seat_tester(|t| { + t.send_tablet_pad_button(self.id, pad.dev, time_usec, button, state) + }); + if pad.tablet.is_some() { + pad.pad_owner.button(&pad, time_usec, button, state); + } + } + } + + pub fn tablet_event_pad_ring( + self: &Rc, + pad: TabletPadId, + ring: u32, + source: Option, + angle: Option, + time_usec: u64, + ) { + if let Some(pad) = self.tablet.pads.get(&pad) { + self.state.for_each_seat_tester(|t| { + t.send_tablet_pad_ring(self.id, pad.dev, time_usec, ring, source, angle) + }); + if pad.tablet.is_some() { + if let Some(ring) = pad.rings.get(ring as usize) { + let node = self.keyboard_node.get(); + node.node_on_tablet_pad_ring(&pad, ring, source, angle, time_usec); + } + } + } + } + + pub fn tablet_event_pad_strip( + self: &Rc, + pad: TabletPadId, + strip: u32, + source: Option, + position: Option, + time_usec: u64, + ) { + if let Some(pad) = self.tablet.pads.get(&pad) { + self.state.for_each_seat_tester(|t| { + t.send_tablet_pad_strip(self.id, pad.dev, time_usec, strip, source, position) + }); + if pad.tablet.is_some() { + if let Some(strip) = pad.strips.get(strip as usize) { + let node = pad.node.get(); + node.node_on_tablet_pad_strip(&pad, strip, source, position, time_usec); + } + } + } + } +} + +impl TabletPad { + fn for_each_pair(&self, n: &WlSurface, mut f: impl FnMut(&ZwpTabletV2, &ZwpTabletPadV2)) { + let Some(tablet) = self.tablet.get() else { + return; + }; + self.seat.tablet_for_each_seat(n, |s| { + let Some(tablet) = tablet.bindings.get(s) else { + return; + }; + let Some(pad) = self.bindings.get(s) else { + return; + }; + f(&tablet, &pad); + }) + } + + fn for_each_entered(&self, n: &WlSurface, mut f: impl FnMut(&ZwpTabletPadV2)) { + self.seat.tablet_for_each_seat(n, |s| { + let Some(pad) = self.bindings.get(s) else { + return; + }; + if pad.entered.get() { + f(&pad); + } + }) + } + + pub fn surface_enter(self: &Rc, n: &WlSurface) { + let mut serial = n.client.pending_serial(); + let time = usec_to_msec(now_usec()); + self.for_each_pair(n, |tablet, pad| { + pad.send_enter(serial.get(), &tablet, n); + for group in &self.groups { + let mode = group.mode.get(); + if let Some(group) = group.bindings.get(&pad.seat) { + group.send_mode_switch(time, serial.get(), mode); + } + } + }); + } + + pub fn surface_leave(self: &Rc, n: &WlSurface) { + let mut serial = n.client.pending_serial(); + self.for_each_entered(n, |pad| { + pad.send_leave(serial.get(), n); + }); + } + + pub fn surface_ring( + self: &Rc, + n: &WlSurface, + ring: &Rc, + source: Option, + angle: Option, + time_usec: u64, + ) { + let time = usec_to_msec(time_usec); + self.seat.tablet_for_each_seat(n, |s| { + if let Some(ring) = ring.bindings.get(&s) { + if let Some(source) = source { + ring.send_source(source); + } + if let Some(angle) = angle { + ring.send_angle(Fixed::from_f64(angle)); + } else { + ring.send_stop(); + } + ring.send_frame(time); + } + }); + } + + pub fn surface_strip( + self: &Rc, + n: &WlSurface, + strip: &Rc, + source: Option, + position: Option, + time_usec: u64, + ) { + let time = usec_to_msec(time_usec); + self.for_each_entered(n, |pad| { + if let Some(strip) = strip.bindings.get(&pad.seat) { + if let Some(source) = source { + strip.send_source(source); + } + if let Some(position) = position { + strip.send_position(normalizeu(position)); + } else { + strip.send_stop(); + } + strip.send_frame(time); + } + }); + } + + pub fn surface_mode_switch( + self: &Rc, + n: &WlSurface, + group: &Rc, + time_usec: u64, + mode: u32, + ) { + let time = usec_to_msec(time_usec); + let mut serial = n.client.pending_serial(); + self.for_each_entered(n, |pad| { + if let Some(group) = group.bindings.get(&pad.seat) { + group.send_mode_switch(time, serial.get(), mode); + } + }); + } + + pub fn surface_button( + self: &Rc, + n: &WlSurface, + time_usec: u64, + button: u32, + state: PadButtonState, + ) { + let time = usec_to_msec(time_usec); + self.for_each_entered(n, |pad| { + pad.send_button(time, button, state); + }) + } +} diff --git a/src/ifs/wl_seat/tablet/pad_owner.rs b/src/ifs/wl_seat/tablet/pad_owner.rs new file mode 100644 index 00000000..ab473fa3 --- /dev/null +++ b/src/ifs/wl_seat/tablet/pad_owner.rs @@ -0,0 +1,135 @@ +use { + crate::{ + ifs::wl_seat::tablet::{PadButtonState, TabletPad}, + time::now_usec, + tree::Node, + utils::{clonecell::CloneCell, smallmap::SmallMap}, + }, + std::rc::Rc, +}; + +pub struct PadOwnerHolder { + default: Rc, + owner: CloneCell>, +} + +trait PadOwner { + fn revert_to_default(&self, pad: &Rc, time_usec: u64); + fn update_node(&self, pad: &Rc); + fn button(&self, pad: &Rc, time_usec: u64, button: u32, state: PadButtonState); +} + +struct DefaultPadOwner; + +struct GrabPadOwner { + buttons: SmallMap, + node: Rc, +} + +impl Default for PadOwnerHolder { + fn default() -> Self { + let default = Rc::new(DefaultPadOwner); + Self { + owner: CloneCell::new(default.clone()), + default, + } + } +} + +impl PadOwnerHolder { + pub fn update_node(&self, pad: &Rc) { + self.owner.get().update_node(pad); + } + + pub fn destroy(&self, pad: &Rc) { + self.owner.get().revert_to_default(pad, now_usec()); + let prev = pad.node.set(pad.seat.state.root.clone()); + prev.node_on_tablet_pad_leave(pad); + prev.node_seat_state().remove_tablet_pad_focus(pad); + } + + pub fn button(&self, pad: &Rc, time_usec: u64, button: u32, state: PadButtonState) { + self.owner.get().button(pad, time_usec, button, state); + } + + pub fn focus_root(&self, pad: &Rc) { + self.owner.get().revert_to_default(pad, now_usec()); + let node = pad.seat.state.root.clone(); + pad.focus_node(node); + } + + fn set_default_owner(&self) { + self.owner.set(self.default.clone()); + } +} + +impl TabletPad { + fn focus_node(self: &Rc, node: Rc) { + let prev = self.node.set(node.clone()); + if node.node_id() != prev.node_id() { + prev.node_on_tablet_pad_leave(self); + prev.node_seat_state().remove_tablet_pad_focus(self); + node.node_seat_state().add_tablet_pad_focus(self); + node.node_on_tablet_pad_enter(self); + } + } +} + +impl PadOwner for DefaultPadOwner { + fn revert_to_default(&self, _pad: &Rc, _time_usec: u64) { + // nothing + } + + fn update_node(&self, pad: &Rc) { + let node = pad.seat.keyboard_node.get(); + pad.focus_node(node); + } + + fn button(&self, pad: &Rc, time_usec: u64, button: u32, state: PadButtonState) { + if state != PadButtonState::Pressed { + return; + } + let node = pad.node.get(); + let owner = Rc::new(GrabPadOwner { + buttons: Default::default(), + node, + }); + pad.pad_owner.owner.set(owner.clone()); + owner.button(pad, time_usec, button, state); + } +} + +impl PadOwner for GrabPadOwner { + fn revert_to_default(&self, pad: &Rc, time_usec: u64) { + for (button, _) in &self.buttons { + self.node + .node_on_tablet_pad_button(pad, time_usec, button, PadButtonState::Released); + } + pad.pad_owner.set_default_owner(); + } + + fn update_node(&self, _pad: &Rc) { + // nothing + } + + fn button(&self, pad: &Rc, time_usec: u64, button: u32, state: PadButtonState) { + match state { + PadButtonState::Released => { + if self.buttons.remove(&button).is_none() { + return; + } + } + PadButtonState::Pressed => { + if self.buttons.insert(button, ()).is_some() { + return; + } + } + } + self.node + .node_on_tablet_pad_button(pad, time_usec, button, state); + if self.buttons.is_empty() { + pad.pad_owner.set_default_owner(); + pad.pad_owner.default.update_node(pad); + } + } +} diff --git a/src/ifs/wl_seat/tablet/tablet_bindings.rs b/src/ifs/wl_seat/tablet/tablet_bindings.rs new file mode 100644 index 00000000..aa0c10e4 --- /dev/null +++ b/src/ifs/wl_seat/tablet/tablet_bindings.rs @@ -0,0 +1,43 @@ +use { + crate::{ + client::ClientId, + ifs::wl_seat::tablet::zwp_tablet_seat_v2::ZwpTabletSeatV2, + utils::copyhashmap::{CopyHashMap, Locked}, + wire::ZwpTabletSeatV2Id, + }, + std::rc::Rc, +}; + +pub struct TabletBindings { + bindings: CopyHashMap<(ClientId, ZwpTabletSeatV2Id), Rc>, +} + +impl Default for TabletBindings { + fn default() -> Self { + Self { + bindings: Default::default(), + } + } +} + +impl TabletBindings { + pub fn add(&self, seat: &ZwpTabletSeatV2, t: &Rc) { + self.bindings.set((seat.client.id, seat.id), t.clone()); + } + + pub fn get(&self, seat: &ZwpTabletSeatV2) -> Option> { + self.bindings.get(&(seat.client.id, seat.id)) + } + + pub fn remove(&self, seat: &ZwpTabletSeatV2) -> Option> { + self.bindings.remove(&(seat.client.id, seat.id)) + } + + pub fn lock(&self) -> Locked<'_, (ClientId, ZwpTabletSeatV2Id), Rc> { + self.bindings.lock() + } + + pub fn clear(&self) { + self.bindings.clear(); + } +} diff --git a/src/ifs/wl_seat/tablet/tool.rs b/src/ifs/wl_seat/tablet/tool.rs new file mode 100644 index 00000000..9f11ea5f --- /dev/null +++ b/src/ifs/wl_seat/tablet/tool.rs @@ -0,0 +1,274 @@ +use { + crate::{ + cursor::KnownCursor, + fixed::Fixed, + ifs::{ + wl_seat::{ + tablet::{ + normalizei, normalizeu, zwp_tablet_tool_v2::ZwpTabletToolV2, + zwp_tablet_v2::ZwpTabletV2, TabletTool, TabletToolChanges, TabletToolId, + TabletToolInit, TabletToolOpt, TabletToolType, ToolButtonState, + }, + WlSeatGlobal, + }, + wl_surface::WlSurface, + }, + rect::Rect, + time::usec_to_msec, + utils::clonecell::CloneCell, + }, + std::{cell::Cell, rc::Rc}, +}; + +impl WlSeatGlobal { + pub fn tablet_handle_remove_tool(self: &Rc, time_usec: u64, id: TabletToolId) { + let Some(tool) = self.tablet.tools.remove(&id) else { + return; + }; + self.state.for_each_seat_tester(|t| { + t.send_tablet_tool_proximity_out(self.id, tool.tablet.dev, tool.id, time_usec) + }); + tool.opt.tool.take(); + tool.cursor.detach(); + tool.tool_owner.destroy(&tool); + for (_, binding) in tool.bindings.lock().drain() { + binding.send_removed(); + } + tool.tablet.tools.remove(&id); + } + + pub fn tablet_handle_new_tool(self: &Rc, time_usec: u64, init: &TabletToolInit) { + let Some(tablet) = self.tablet.tablets.get(&init.tablet_id) else { + return; + }; + let tool = Rc::new(TabletTool { + id: init.id, + opt: Default::default(), + tablet, + type_: init.type_, + hardware_serial: init.hardware_serial, + hardware_id_wacom: init.hardware_id_wacom, + capabilities: init.capabilities.clone(), + bindings: Default::default(), + node: CloneCell::new(self.state.root.clone()), + tool_owner: Default::default(), + cursor: self.cursor_user_group.create_user(), + down: Cell::new(false), + pressure: Cell::new(0.0), + distance: Cell::new(0.0), + tilt_x: Cell::new(0.0), + tilt_y: Cell::new(0.0), + rotation: Cell::new(0.0), + slider: Cell::new(0.0), + }); + tool.opt.tool.set(Some(tool.clone())); + tool.cursor.set_known(KnownCursor::Default); + self.tablet.tools.set(init.id, tool.clone()); + self.state.for_each_seat_tester(|t| { + t.send_tablet_tool_proximity_in(self.id, tool.tablet.dev, tool.id, time_usec) + }); + self.tablet_for_each_seat_obj(|s| s.announce_tool(&tool)); + } + + pub fn tablet_event_tool_button( + self: &Rc, + id: TabletToolId, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + let Some(tool) = self.tablet.tools.get(&id) else { + return; + }; + self.state.for_each_seat_tester(|t| { + t.send_tablet_tool_button(self.id, tool.tablet.dev, &tool, time_usec, button, state); + }); + tool.cursor.activate(); + tool.tool_owner.button(&tool, time_usec, button, state); + } + + pub fn tablet_event_tool_changes( + self: &Rc, + id: TabletToolId, + time_usec: u64, + rect: Rect, + changes: &TabletToolChanges, + ) { + let Some(tool) = self.tablet.tools.get(&id) else { + return; + }; + self.state.for_each_seat_tester(|t| { + t.send_tablet_tool_changes(self.id, tool.tablet.dev, &tool, time_usec, changes); + }); + if let Some(val) = changes.down { + tool.down.set(val); + } + if let Some(val) = changes.pressure { + tool.pressure.set(val); + } + if let Some(val) = changes.distance { + tool.distance.set(val); + } + if let Some(val) = changes.tilt { + tool.tilt_x.set(val.x); + tool.tilt_y.set(val.y); + } + if let Some(val) = changes.rotation { + tool.rotation.set(val); + } + if let Some(val) = changes.slider { + tool.slider.set(val); + } + if let Some(delta) = changes.pos { + let (x, y) = match tool.type_ { + TabletToolType::Mouse | TabletToolType::Lens => { + let (mut x, mut y) = tool.cursor.position(); + x += Fixed::from_f64(delta.x.dx); + y += Fixed::from_f64(delta.y.dx); + (x, y) + } + TabletToolType::Pen + | TabletToolType::Eraser + | TabletToolType::Brush + | TabletToolType::Pencil + | TabletToolType::Airbrush + | TabletToolType::Finger => { + let x = Fixed::from_f64(rect.x1() as f64 + (rect.width() as f64 * delta.x.x)); + let y = Fixed::from_f64(rect.y1() as f64 + (rect.height() as f64 * delta.y.x)); + (x, y) + } + }; + tool.cursor.set_position(x, y); + } + tool.cursor.activate(); + tool.tool_owner + .apply_changes(&tool, time_usec, Some(changes)); + } +} + +impl TabletTool { + fn for_each_pair(&self, n: &WlSurface, mut f: impl FnMut(&ZwpTabletV2, &ZwpTabletToolV2)) { + self.tablet.seat.tablet_for_each_seat(n, |s| { + let Some(tablet) = self.tablet.bindings.get(s) else { + return; + }; + let Some(tool) = self.bindings.get(s) else { + return; + }; + f(&tablet, &tool); + }) + } + + fn for_each_entered(&self, n: &WlSurface, mut f: impl FnMut(&ZwpTabletToolV2)) { + self.tablet.seat.tablet_for_each_seat(n, |s| { + let Some(tool) = self.bindings.get(s) else { + return; + }; + if !tool.entered.get() { + return; + } + f(&tool); + }) + } + + pub fn surface_leave(&self, n: &WlSurface, time_usec: u64) { + let time = usec_to_msec(time_usec); + self.for_each_entered(n, |t| { + t.send_proximity_out(); + t.send_frame(time); + }) + } + + pub fn surface_enter(&self, n: &WlSurface, time_usec: u64, x: Fixed, y: Fixed) { + let time = usec_to_msec(time_usec); + let mut serial = n.client.pending_serial(); + self.for_each_pair(n, |tablet, tool| { + tool.send_proximity_in(serial.get(), tablet, n); + tool.send_motion(x, y); + tool.send_pressure(normalizeu(self.pressure.get())); + tool.send_distance(normalizeu(self.distance.get())); + tool.send_tilt( + Fixed::from_f64(self.tilt_x.get()), + Fixed::from_f64(self.tilt_y.get()), + ); + tool.send_rotation(Fixed::from_f64(self.rotation.get())); + tool.send_slider(normalizei(self.slider.get())); + tool.send_frame(time); + }) + } + + pub fn surface_button( + &self, + n: &WlSurface, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + let time = usec_to_msec(time_usec); + let mut serial = n.client.pending_serial(); + self.for_each_entered(n, |tool| { + tool.send_button(serial.get(), button, state); + tool.send_frame(time); + }); + if state == ToolButtonState::Pressed { + if let Some(node) = n.get_focus_node(self.tablet.seat.id) { + self.tablet.seat.focus_node(node); + } + } + } + + pub fn surface_apply_changes( + &self, + n: &WlSurface, + time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + let mut serial = n.client.pending_serial(); + let time = usec_to_msec(time_usec); + self.for_each_entered(n, |tool| { + if let Some(changes) = changes { + if let Some(val) = changes.down { + match val { + false => tool.send_up(), + true => tool.send_down(serial.get()), + } + } + if let Some(val) = changes.pressure { + tool.send_pressure(normalizeu(val)); + } + if let Some(val) = changes.distance { + tool.send_distance(normalizeu(val)); + } + if let Some(val) = changes.tilt { + tool.send_tilt(Fixed::from_f64(val.x), Fixed::from_f64(val.y)); + } + if let Some(val) = changes.rotation { + tool.send_rotation(Fixed::from_f64(val)); + } + if let Some(val) = changes.slider { + tool.send_slider(normalizei(val)); + } + if let Some(val) = changes.wheel { + tool.send_wheel(Fixed::from_f64(val.degrees), val.clicks); + } + } + tool.send_motion(x, y); + tool.send_frame(time); + }); + if let Some(changes) = changes { + if changes.down == Some(true) { + if let Some(node) = n.get_focus_node(self.tablet.seat.id) { + self.tablet.seat.focus_node(node); + } + } + } + } +} + +impl TabletToolOpt { + pub fn get(&self) -> Option> { + self.tool.get() + } +} diff --git a/src/ifs/wl_seat/tablet/tool_owner.rs b/src/ifs/wl_seat/tablet/tool_owner.rs new file mode 100644 index 00000000..2fd963d7 --- /dev/null +++ b/src/ifs/wl_seat/tablet/tool_owner.rs @@ -0,0 +1,213 @@ +use { + crate::{ + fixed::Fixed, + ifs::wl_seat::tablet::{TabletTool, TabletToolChanges, ToolButtonState}, + time::now_usec, + tree::{FindTreeUsecase, FoundNode, Node}, + utils::{clonecell::CloneCell, smallmap::SmallMap}, + }, + std::rc::Rc, +}; + +pub struct ToolOwnerHolder { + default: Rc, + owner: CloneCell>, +} + +struct DefaultToolOwner; + +struct GrabToolOwner { + buttons: SmallMap, + node: Rc, +} + +impl Default for ToolOwnerHolder { + fn default() -> Self { + let default = Rc::new(DefaultToolOwner); + Self { + owner: CloneCell::new(default.clone()), + default, + } + } +} + +impl ToolOwnerHolder { + pub fn destroy(&self, tool: &Rc) { + let root = tool.tablet.seat.state.root.clone(); + let prev = tool.node.set(root); + prev.node_on_tablet_tool_leave(tool, now_usec()); + prev.node_seat_state().remove_tablet_tool_focus(tool); + } + + pub fn focus_root(&self, tool: &Rc) { + self.owner.set(self.default.clone()); + let root = tool.tablet.seat.state.root.clone(); + tool.set_node(root, now_usec()); + } + + pub fn button( + &self, + tool: &Rc, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + self.owner.get().button(tool, time_usec, button, state); + } + + pub fn apply_changes( + &self, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + ) { + self.owner.get().apply_changes(tool, time_usec, changes); + } +} + +trait ToolOwner { + fn button(&self, tool: &Rc, time_usec: u64, button: u32, state: ToolButtonState); + fn apply_changes( + &self, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + ); +} + +impl TabletTool { + fn set_node(self: &Rc, node: Rc, time_usec: u64) { + let prev = self.node.set(node.clone()); + if prev.node_id() != node.node_id() { + prev.node_on_tablet_tool_leave(self, time_usec); + prev.node_seat_state().remove_tablet_tool_focus(self); + let (tool_x, tool_y) = self.cursor.position(); + let (node_x, node_y) = node.node_absolute_position().position(); + node.node_seat_state().add_tablet_tool_focus(self); + node.node_on_tablet_tool_enter(self, time_usec, tool_x - node_x, tool_y - node_y); + } + } +} + +impl ToolOwner for DefaultToolOwner { + fn button(&self, tool: &Rc, time_usec: u64, button: u32, state: ToolButtonState) { + if state == ToolButtonState::Released { + return; + } + let owner = Rc::new(GrabToolOwner { + buttons: Default::default(), + node: tool.node.get(), + }); + tool.tool_owner.owner.set(owner.clone()); + owner.button(tool, time_usec, button, state); + } + + fn apply_changes( + &self, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + ) { + let change = handle_position_change(tool); + let node = change.node; + if change.changed { + tool.set_node(node.clone(), time_usec); + } else { + node.clone() + .node_on_tablet_tool_apply_changes(tool, time_usec, changes, change.x, change.y); + } + if tool.down.get() { + tool.tool_owner.owner.set(Rc::new(GrabToolOwner { + buttons: Default::default(), + node, + })); + } + } +} + +impl GrabToolOwner { + fn maybe_revert(&self, tool: &Rc) { + if !tool.down.get() && self.buttons.is_empty() { + tool.tool_owner.owner.set(tool.tool_owner.default.clone()); + tool.tablet.seat.tree_changed.trigger(); + } + } +} + +impl ToolOwner for GrabToolOwner { + fn button(&self, tool: &Rc, time_usec: u64, button: u32, state: ToolButtonState) { + match state { + ToolButtonState::Released => { + if self.buttons.remove(&button).is_none() { + return; + } + } + ToolButtonState::Pressed => { + if self.buttons.insert(button, ()).is_some() { + return; + } + } + } + self.node + .node_on_tablet_tool_button(tool, time_usec, button, state); + self.maybe_revert(tool); + } + + fn apply_changes( + &self, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + ) { + let (x, y) = tool.cursor.position(); + let node_pos = self.node.node_absolute_position(); + self.node.clone().node_on_tablet_tool_apply_changes( + tool, + time_usec, + changes, + x - node_pos.x1(), + y - node_pos.y1(), + ); + self.maybe_revert(tool); + } +} + +fn handle_position_change(tool: &Rc) -> UpdatedNode { + let (x, y) = tool.cursor.position(); + let x_int = x.round_down(); + let y_int = y.round_down(); + let tree = &mut *tool.tablet.tree.borrow_mut(); + tree.push(FoundNode { + node: tool.tablet.seat.state.root.clone(), + x: x_int, + y: y_int, + }); + tool.tablet + .seat + .state + .root + .node_find_tree_at(x_int, y_int, tree, FindTreeUsecase::None); + let mut update = UpdatedNode { + node: tool.node.get(), + x, + y, + changed: false, + }; + if let Some(last) = tree.last() { + if last.node.node_id() != update.node.node_id() { + update.changed = true; + update.node = last.node.clone(); + } + update.x = x.apply_fract(last.x); + update.y = y.apply_fract(last.y); + } + tree.clear(); + update +} + +struct UpdatedNode { + node: Rc, + changed: bool, + x: Fixed, + y: Fixed, +} diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_manager_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_manager_v2.rs new file mode 100644 index 00000000..982f80e7 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_manager_v2.rs @@ -0,0 +1,104 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wl_seat::tablet::zwp_tablet_seat_v2::ZwpTabletSeatV2, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_manager_v2::*, ZwpTabletManagerV2Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpTabletManagerV2Global { + pub name: GlobalName, +} + +pub struct ZwpTabletManagerV2 { + pub id: ZwpTabletManagerV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ZwpTabletManagerV2Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZwpTabletManagerV2Id, + client: &Rc, + version: Version, + ) -> Result<(), ZwpTabletManagerV2Error> { + let obj = Rc::new(ZwpTabletManagerV2 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + ZwpTabletManagerV2Global, + ZwpTabletManagerV2, + ZwpTabletManagerV2Error +); + +impl Global for ZwpTabletManagerV2Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(ZwpTabletManagerV2Global); + +impl ZwpTabletManagerV2RequestHandler for ZwpTabletManagerV2 { + type Error = ZwpTabletManagerV2Error; + + fn get_tablet_seat(&self, req: GetTabletSeat, _slf: &Rc) -> Result<(), Self::Error> { + let seat = self.client.lookup(req.seat)?.global.clone(); + let obj = Rc::new(ZwpTabletSeatV2 { + id: req.tablet_seat, + client: self.client.clone(), + seat: seat.clone(), + tracker: Default::default(), + version: self.version, + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + seat.tablet_add_seat(&obj); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletManagerV2; + version = self.version; +} + +impl Object for ZwpTabletManagerV2 {} + +simple_add_obj!(ZwpTabletManagerV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletManagerV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletManagerV2Error, ClientError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_pad_group_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_pad_group_v2.rs new file mode 100644 index 00000000..4fd989a3 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_pad_group_v2.rs @@ -0,0 +1,101 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_seat::tablet::{ + zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, + zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_seat_v2::ZwpTabletSeatV2, + TabletPadGroup, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_pad_group_v2::*, ZwpTabletPadGroupV2Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpTabletPadGroupV2 { + pub id: ZwpTabletPadGroupV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub seat: Rc, + pub group: Rc, +} + +impl ZwpTabletPadGroupV2 { + pub fn detach(&self) { + self.group.bindings.remove(&self.seat); + } + + pub fn send_buttons(&self, buttons: &[u32]) { + self.client.event(Buttons { + self_id: self.id, + buttons, + }); + } + + pub fn send_ring(&self, ring: &ZwpTabletPadRingV2) { + self.client.event(Ring { + self_id: self.id, + ring: ring.id, + }); + } + + pub fn send_strip(&self, strip: &ZwpTabletPadStripV2) { + self.client.event(Strip { + self_id: self.id, + strip: strip.id, + }); + } + + pub fn send_modes(&self, modes: u32) { + self.client.event(Modes { + self_id: self.id, + modes, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_mode_switch(&self, time: u32, serial: u32, mode: u32) { + self.client.event(ModeSwitch { + self_id: self.id, + time, + serial, + mode, + }); + } +} + +impl ZwpTabletPadGroupV2RequestHandler for ZwpTabletPadGroupV2 { + type Error = ZwpTabletPadGroupV2Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletPadGroupV2; + version = self.version; +} + +impl Object for ZwpTabletPadGroupV2 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwpTabletPadGroupV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletPadGroupV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletPadGroupV2Error, ClientError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs new file mode 100644 index 00000000..180e0197 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs @@ -0,0 +1,90 @@ +use { + crate::{ + client::{Client, ClientError}, + fixed::Fixed, + ifs::wl_seat::tablet::{ + zwp_tablet_seat_v2::ZwpTabletSeatV2, TabletPadRing, TabletRingEventSource, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_pad_ring_v2::*, ZwpTabletPadRingV2Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpTabletPadRingV2 { + pub id: ZwpTabletPadRingV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub seat: Rc, + pub ring: Rc, +} + +impl ZwpTabletPadRingV2 { + pub fn detach(&self) { + self.ring.bindings.remove(&self.seat); + } + + pub fn send_source(&self, source: TabletRingEventSource) { + self.client.event(Source { + self_id: self.id, + source: match source { + TabletRingEventSource::Finger => 1, + }, + }); + } + + pub fn send_angle(&self, degrees: Fixed) { + self.client.event(Angle { + self_id: self.id, + degrees, + }); + } + + pub fn send_stop(&self) { + self.client.event(Stop { self_id: self.id }); + } + + pub fn send_frame(&self, time: u32) { + self.client.event(Frame { + self_id: self.id, + time, + }); + } +} + +impl ZwpTabletPadRingV2RequestHandler for ZwpTabletPadRingV2 { + type Error = ZwpTabletPadRingV2Error; + + fn set_feedback(&self, _req: SetFeedback<'_>, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletPadRingV2; + version = self.version; +} + +impl Object for ZwpTabletPadRingV2 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwpTabletPadRingV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletPadRingV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletPadRingV2Error, ClientError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_pad_strip_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_pad_strip_v2.rs new file mode 100644 index 00000000..ae0e8861 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_pad_strip_v2.rs @@ -0,0 +1,89 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_seat::tablet::{ + zwp_tablet_seat_v2::ZwpTabletSeatV2, TabletPadStrip, TabletStripEventSource, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_pad_strip_v2::*, ZwpTabletPadStripV2Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpTabletPadStripV2 { + pub id: ZwpTabletPadStripV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub seat: Rc, + pub strip: Rc, +} + +impl ZwpTabletPadStripV2 { + pub fn detach(&self) { + self.strip.bindings.remove(&self.seat); + } + + pub fn send_source(&self, source: TabletStripEventSource) { + self.client.event(Source { + self_id: self.id, + source: match source { + TabletStripEventSource::Finger => 1, + }, + }); + } + + pub fn send_position(&self, position: u32) { + self.client.event(Position { + self_id: self.id, + position, + }); + } + + pub fn send_stop(&self) { + self.client.event(Stop { self_id: self.id }); + } + + pub fn send_frame(&self, time: u32) { + self.client.event(Frame { + self_id: self.id, + time, + }); + } +} + +impl ZwpTabletPadStripV2RequestHandler for ZwpTabletPadStripV2 { + type Error = ZwpTabletPadStripV2Error; + + fn set_feedback(&self, _req: SetFeedback<'_>, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletPadStripV2; + version = self.version; +} + +impl Object for ZwpTabletPadStripV2 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwpTabletPadStripV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletPadStripV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletPadStripV2Error, ClientError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_pad_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_pad_v2.rs new file mode 100644 index 00000000..6dd37b23 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_pad_v2.rs @@ -0,0 +1,127 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::{ + wl_seat::tablet::{ + zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2, zwp_tablet_seat_v2::ZwpTabletSeatV2, + zwp_tablet_v2::ZwpTabletV2, PadButtonState, TabletPad, + }, + wl_surface::WlSurface, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_pad_v2::*, ZwpTabletPadV2Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ZwpTabletPadV2 { + pub id: ZwpTabletPadV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub seat: Rc, + pub pad: Rc, + pub entered: Cell, +} + +impl ZwpTabletPadV2 { + pub fn detach(&self) { + self.pad.bindings.remove(&self.seat); + } + + pub fn send_group(&self, group: &ZwpTabletPadGroupV2) { + self.client.event(Group { + self_id: self.id, + pad_group: group.id, + }); + } + + pub fn send_path(&self, path: &str) { + self.client.event(Path { + self_id: self.id, + path, + }); + } + + pub fn send_buttons(&self, buttons: u32) { + self.client.event(Buttons { + self_id: self.id, + buttons, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_button(&self, time: u32, button: u32, state: PadButtonState) { + self.client.event(Button { + self_id: self.id, + time, + button, + state: match state { + PadButtonState::Released => 0, + PadButtonState::Pressed => 1, + }, + }); + } + + pub fn send_enter(&self, serial: u32, tablet: &ZwpTabletV2, surface: &WlSurface) { + self.entered.set(true); + self.client.event(Enter { + self_id: self.id, + serial, + tablet: tablet.id, + surface: surface.id, + }); + } + + pub fn send_leave(&self, serial: u32, surface: &WlSurface) { + self.entered.set(false); + self.client.event(Leave { + self_id: self.id, + serial, + surface: surface.id, + }); + } + + pub fn send_removed(&self) { + self.client.event(Removed { self_id: self.id }); + } +} + +impl ZwpTabletPadV2RequestHandler for ZwpTabletPadV2 { + type Error = ZwpTabletPadV2Error; + + fn set_feedback(&self, _req: SetFeedback<'_>, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletPadV2; + version = self.version; +} + +impl Object for ZwpTabletPadV2 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwpTabletPadV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletPadV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletPadV2Error, ClientError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_seat_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_seat_v2.rs new file mode 100644 index 00000000..14c80ea9 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_seat_v2.rs @@ -0,0 +1,221 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_seat::{ + tablet::{ + zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2, + zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, + zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_pad_v2::ZwpTabletPadV2, + zwp_tablet_tool_v2::ZwpTabletToolV2, zwp_tablet_v2::ZwpTabletV2, Tablet, TabletPad, + TabletTool, + }, + WlSeatGlobal, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_seat_v2::*, ZwpTabletSeatV2Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ZwpTabletSeatV2 { + pub id: ZwpTabletSeatV2Id, + pub client: Rc, + pub seat: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ZwpTabletSeatV2 { + pub fn detach(&self) { + self.seat.tablet.seats.remove(&self.client, self); + } + + pub fn announce_tablet(self: &Rc, tablet: &Rc) { + let id = match self.client.new_id() { + Ok(id) => id, + Err(e) => { + self.client.error(e); + return; + } + }; + let obj = Rc::new(ZwpTabletV2 { + id, + client: self.client.clone(), + seat: self.clone(), + tracker: Default::default(), + version: self.version, + tablet: tablet.clone(), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + self.send_tablet_added(&obj); + obj.send_name(&tablet.name); + obj.send_id(tablet.vid, tablet.pid); + obj.send_path(&tablet.path); + obj.send_done(); + tablet.bindings.add(self, &obj); + } + + pub fn announce_tool(self: &Rc, tool: &Rc) { + let id = match self.client.new_id() { + Ok(id) => id, + Err(e) => { + self.client.error(e); + return; + } + }; + let obj = Rc::new(ZwpTabletToolV2 { + id, + client: self.client.clone(), + seat: self.clone(), + tool: tool.opt.clone(), + tracker: Default::default(), + version: self.version, + entered: Cell::new(false), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + self.send_tool_added(&obj); + obj.send_type(tool.type_); + obj.send_hardware_serial(tool.hardware_serial); + obj.send_hardware_id_wacom(tool.hardware_id_wacom); + for cap in &tool.capabilities { + obj.send_capability(*cap); + } + obj.send_done(); + tool.bindings.add(self, &obj); + } + + pub fn announce_pad(self: &Rc, pad: &Rc) { + macro_rules! id { + () => { + match self.client.new_id() { + Ok(id) => id, + Err(e) => { + self.client.error(e); + return; + } + } + }; + } + let obj = Rc::new(ZwpTabletPadV2 { + id: id!(), + client: self.client.clone(), + seat: self.clone(), + tracker: Default::default(), + version: self.version, + pad: pad.clone(), + entered: Cell::new(false), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + self.send_pad_added(&obj); + obj.send_path(&pad.path); + obj.send_buttons(pad.buttons); + for group in &pad.groups { + let group_obj = Rc::new(ZwpTabletPadGroupV2 { + id: id!(), + client: self.client.clone(), + seat: self.clone(), + tracker: Default::default(), + version: self.version, + group: group.clone(), + }); + track!(self.client, group_obj); + self.client.add_server_obj(&group_obj); + obj.send_group(&group_obj); + group_obj.send_buttons(&group.buttons); + group_obj.send_modes(group.modes); + for ring in &group.rings { + let Some(ring) = pad.rings.get(*ring as usize) else { + continue; + }; + let ring_obj = Rc::new(ZwpTabletPadRingV2 { + id: id!(), + client: self.client.clone(), + seat: self.clone(), + tracker: Default::default(), + version: self.version, + ring: ring.clone(), + }); + track!(self.client, ring_obj); + self.client.add_server_obj(&ring_obj); + group_obj.send_ring(&ring_obj); + ring.bindings.add(self, &ring_obj); + } + for strip in &group.strips { + let Some(strip) = pad.strips.get(*strip as usize) else { + continue; + }; + let strip_obj = Rc::new(ZwpTabletPadStripV2 { + id: id!(), + client: self.client.clone(), + seat: self.clone(), + tracker: Default::default(), + version: self.version, + strip: strip.clone(), + }); + track!(self.client, strip_obj); + self.client.add_server_obj(&strip_obj); + group_obj.send_strip(&strip_obj); + strip.bindings.add(self, &strip_obj); + } + group_obj.send_done(); + } + obj.send_done(); + pad.bindings.add(self, &obj); + } + + fn send_tablet_added(&self, tablet: &ZwpTabletV2) { + self.client.event(TabletAdded { + self_id: self.id, + id: tablet.id, + }); + } + + fn send_tool_added(&self, tool: &ZwpTabletToolV2) { + self.client.event(ToolAdded { + self_id: self.id, + id: tool.id, + }); + } + + fn send_pad_added(&self, pad: &ZwpTabletPadV2) { + self.client.event(PadAdded { + self_id: self.id, + id: pad.id, + }); + } +} + +impl ZwpTabletSeatV2RequestHandler for ZwpTabletSeatV2 { + type Error = ZwpTabletSeatV2Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletSeatV2; + version = self.version; +} + +impl Object for ZwpTabletSeatV2 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwpTabletSeatV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletSeatV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletSeatV2Error, ClientError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs new file mode 100644 index 00000000..199732bb --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs @@ -0,0 +1,252 @@ +use { + crate::{ + client::{Client, ClientError}, + cursor::Cursor, + fixed::Fixed, + ifs::{ + wl_seat::tablet::{ + zwp_tablet_seat_v2::ZwpTabletSeatV2, zwp_tablet_v2::ZwpTabletV2, + TabletToolCapability, TabletToolOpt, TabletToolType, ToolButtonState, + }, + wl_surface::{WlSurface, WlSurfaceError}, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_tool_v2::*, ZwpTabletToolV2Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ZwpTabletToolV2 { + pub id: ZwpTabletToolV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub seat: Rc, + pub tool: Rc, + pub entered: Cell, +} + +pub const BTN_TOOL_PEN: u32 = 0x140; +pub const BTN_TOOL_RUBBER: u32 = 0x141; +pub const BTN_TOOL_BRUSH: u32 = 0x142; +pub const BTN_TOOL_PENCIL: u32 = 0x143; +pub const BTN_TOOL_AIRBRUSH: u32 = 0x144; +pub const BTN_TOOL_FINGER: u32 = 0x145; +pub const BTN_TOOL_MOUSE: u32 = 0x146; +pub const BTN_TOOL_LENS: u32 = 0x147; + +impl ZwpTabletToolV2 { + pub fn detach(&self) { + if let Some(tool) = self.tool.get() { + tool.bindings.remove(&self.seat); + } + } + + pub fn send_type(&self, tool_type: TabletToolType) { + self.client.event(Type { + self_id: self.id, + tool_type: match tool_type { + TabletToolType::Pen => BTN_TOOL_PEN, + TabletToolType::Eraser => BTN_TOOL_RUBBER, + TabletToolType::Brush => BTN_TOOL_BRUSH, + TabletToolType::Pencil => BTN_TOOL_PENCIL, + TabletToolType::Airbrush => BTN_TOOL_AIRBRUSH, + TabletToolType::Finger => BTN_TOOL_FINGER, + TabletToolType::Mouse => BTN_TOOL_MOUSE, + TabletToolType::Lens => BTN_TOOL_LENS, + }, + }); + } + + pub fn send_hardware_serial(&self, serial: u64) { + self.client.event(HardwareSerial { + self_id: self.id, + hardware_serial_hi: (serial >> 32) as _, + hardware_serial_lo: serial as _, + }); + } + + pub fn send_hardware_id_wacom(&self, id: u64) { + self.client.event(HardwareIdWacom { + self_id: self.id, + hardware_id_hi: (id >> 32) as _, + hardware_id_lo: id as _, + }); + } + + pub fn send_capability(&self, capability: TabletToolCapability) { + self.client.event(Capability { + self_id: self.id, + capability: match capability { + TabletToolCapability::Tilt => 1, + TabletToolCapability::Pressure => 2, + TabletToolCapability::Distance => 3, + TabletToolCapability::Rotation => 4, + TabletToolCapability::Slider => 5, + TabletToolCapability::Wheel => 6, + }, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_removed(&self) { + self.client.event(Removed { self_id: self.id }); + } + + pub fn send_proximity_in(&self, serial: u32, tablet: &ZwpTabletV2, surface: &WlSurface) { + self.entered.set(true); + self.client.event(ProximityIn { + self_id: self.id, + serial, + tablet: tablet.id, + surface: surface.id, + }); + } + + pub fn send_proximity_out(&self) { + self.entered.set(false); + self.client.event(ProximityOut { self_id: self.id }); + } + + pub fn send_down(&self, serial: u32) { + self.client.event(Down { + self_id: self.id, + serial, + }); + } + + pub fn send_up(&self) { + self.client.event(Up { self_id: self.id }); + } + + pub fn send_motion(&self, x: Fixed, y: Fixed) { + self.client.event(Motion { + self_id: self.id, + x, + y, + }); + } + + pub fn send_pressure(&self, pressure: u32) { + self.client.event(Pressure { + self_id: self.id, + pressure, + }); + } + + pub fn send_distance(&self, distance: u32) { + self.client.event(Distance { + self_id: self.id, + distance, + }); + } + + pub fn send_tilt(&self, tilt_x: Fixed, tilt_y: Fixed) { + self.client.event(Tilt { + self_id: self.id, + tilt_x, + tilt_y, + }); + } + + pub fn send_rotation(&self, degrees: Fixed) { + self.client.event(Rotation { + self_id: self.id, + degrees, + }); + } + + pub fn send_slider(&self, position: i32) { + self.client.event(Slider { + self_id: self.id, + position, + }); + } + + pub fn send_wheel(&self, degrees: Fixed, clicks: i32) { + self.client.event(Wheel { + self_id: self.id, + degrees, + clicks, + }); + } + + pub fn send_button(&self, serial: u32, button: u32, state: ToolButtonState) { + self.client.event(Button { + self_id: self.id, + serial, + button, + state: match state { + ToolButtonState::Released => 0, + ToolButtonState::Pressed => 1, + }, + }); + } + + pub fn send_frame(&self, time: u32) { + self.client.event(Frame { + self_id: self.id, + time, + }); + } +} + +impl ZwpTabletToolV2RequestHandler for ZwpTabletToolV2 { + type Error = ZwpTabletToolV2Error; + + fn set_cursor(&self, req: SetCursor, _slf: &Rc) -> Result<(), Self::Error> { + let Some(tool) = self.tool.get() else { + return Ok(()); + }; + if !self.seat.client.valid_serial(req.serial) { + log::warn!("Client tried to set_cursor with an invalid serial"); + return Ok(()); + } + let mut cursor_opt = None; + if req.surface.is_some() { + let surface = self.seat.client.lookup(req.surface)?; + let cursor = surface.get_cursor(&tool.cursor)?; + cursor.set_hotspot(req.hotspot_x, req.hotspot_y); + cursor_opt = Some(cursor as Rc); + } + if tool.node.get().node_client_id() != Some(self.seat.client.id) { + return Ok(()); + } + tool.cursor.set(cursor_opt); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletToolV2; + version = self.version; +} + +impl Object for ZwpTabletToolV2 { + fn break_loops(&self) { + self.detach(); + } +} + +dedicated_add_obj!(ZwpTabletToolV2, ZwpTabletToolV2Id, tablet_tools); + +#[derive(Debug, Error)] +pub enum ZwpTabletToolV2Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + WlSurfaceError(Box), +} +efrom!(ZwpTabletToolV2Error, ClientError); +efrom!(ZwpTabletToolV2Error, WlSurfaceError); diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_v2.rs new file mode 100644 index 00000000..4a2157b5 --- /dev/null +++ b/src/ifs/wl_seat/tablet/zwp_tablet_v2.rs @@ -0,0 +1,86 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_seat::tablet::{zwp_tablet_seat_v2::ZwpTabletSeatV2, Tablet}, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_tablet_v2::*, ZwpTabletV2Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpTabletV2 { + pub id: ZwpTabletV2Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub seat: Rc, + pub tablet: Rc, +} + +impl ZwpTabletV2 { + fn detach(&self) { + self.tablet.bindings.remove(&self.seat); + } + + pub fn send_name(&self, name: &str) { + self.client.event(Name { + self_id: self.id, + name, + }); + } + + pub fn send_id(&self, vid: u32, pid: u32) { + self.client.event(Id { + self_id: self.id, + vid, + pid, + }); + } + + pub fn send_path(&self, path: &str) { + self.client.event(Path { + self_id: self.id, + path, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_removed(&self) { + self.client.event(Removed { self_id: self.id }); + } +} + +impl ZwpTabletV2RequestHandler for ZwpTabletV2 { + type Error = ZwpTabletV2Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpTabletV2; + version = self.version; +} + +impl Object for ZwpTabletV2 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwpTabletV2); + +#[derive(Debug, Error)] +pub enum ZwpTabletV2Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpTabletV2Error, ClientError); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index d6c22126..82b10908 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -26,9 +26,15 @@ use { wl_buffer::WlBuffer, wl_callback::WlCallback, wl_seat::{ - text_input::TextInputConnection, wl_pointer::PendingScroll, - zwp_pointer_constraints_v1::SeatConstraint, Dnd, NodeSeatState, SeatId, - WlSeatGlobal, + tablet::{ + PadButtonState, TabletPad, TabletPadGroup, TabletPadRing, TabletPadStrip, + TabletRingEventSource, TabletStripEventSource, TabletTool, TabletToolChanges, + ToolButtonState, + }, + text_input::TextInputConnection, + wl_pointer::PendingScroll, + zwp_pointer_constraints_v1::SeatConstraint, + Dnd, NodeSeatState, SeatId, WlSeatGlobal, }, wl_surface::{ commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError}, @@ -1497,14 +1503,6 @@ impl Node for WlSurface { dnd.seat.dnd_surface_motion(self, dnd, time_usec, x, y); } - fn node_into_surface(self: Rc) -> Option> { - Some(self.clone()) - } - - fn node_is_xwayland_surface(&self) -> bool { - self.client.is_xwayland - } - fn node_on_swipe_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { seat.swipe_begin_surface(self, time_usec, finger_count) } @@ -1544,6 +1542,99 @@ impl Node for WlSurface { fn node_on_hold_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { seat.hold_end_surface(self, time_usec, cancelled) } + + fn node_on_tablet_pad_enter(&self, pad: &Rc) { + pad.surface_enter(self); + } + + fn node_on_tablet_pad_leave(&self, pad: &Rc) { + pad.surface_leave(self); + } + + fn node_on_tablet_pad_button( + &self, + pad: &Rc, + time_usec: u64, + button: u32, + state: PadButtonState, + ) { + pad.surface_button(self, time_usec, button, state); + } + + fn node_on_tablet_pad_mode_switch( + &self, + pad: &Rc, + group: &Rc, + time_usec: u64, + mode: u32, + ) { + pad.surface_mode_switch(self, group, time_usec, mode); + } + + fn node_on_tablet_pad_ring( + &self, + pad: &Rc, + ring: &Rc, + source: Option, + angle: Option, + time_usec: u64, + ) { + pad.surface_ring(self, ring, source, angle, time_usec); + } + + fn node_on_tablet_pad_strip( + &self, + pad: &Rc, + strip: &Rc, + source: Option, + position: Option, + time_usec: u64, + ) { + pad.surface_strip(self, strip, source, position, time_usec); + } + + fn node_on_tablet_tool_leave(&self, tool: &Rc, time_usec: u64) { + tool.surface_leave(self, time_usec); + } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + time_usec: u64, + x: Fixed, + y: Fixed, + ) { + tool.surface_enter(&self, time_usec, x, y); + } + + fn node_on_tablet_tool_button( + &self, + tool: &Rc, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + tool.surface_button(self, time_usec, button, state); + } + + fn node_on_tablet_tool_apply_changes( + self: Rc, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + tool.surface_apply_changes(&self, time_usec, changes, x, y); + } + + fn node_into_surface(self: Rc) -> Option> { + Some(self.clone()) + } + + fn node_is_xwayland_surface(&self) -> bool { + self.client.is_xwayland + } } #[derive(Debug, Error)] diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 850f3625..d9506fbe 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -4,7 +4,7 @@ use { cursor::KnownCursor, fixed::Fixed, ifs::{ - wl_seat::{NodeSeatState, SeatId, WlSeatGlobal}, + wl_seat::{tablet::TabletTool, NodeSeatState, SeatId, WlSeatGlobal}, wl_surface::{x_surface::XSurface, WlSurface, WlSurfaceError}, }, rect::Rect, @@ -369,6 +369,16 @@ impl Node for Xwindow { seat.pointer_cursor().set_known(KnownCursor::Default); } + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + _x: Fixed, + _y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default) + } + fn node_into_toplevel(self: Rc) -> Option> { Some(self) } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 33c9b690..5b4f1ab8 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -4,7 +4,7 @@ use { cursor::KnownCursor, fixed::Fixed, ifs::{ - wl_seat::{NodeSeatState, WlSeatGlobal}, + wl_seat::{tablet::TabletTool, NodeSeatState, WlSeatGlobal}, wl_surface::xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}, xdg_positioner::{ XdgPositioned, XdgPositioner, CA_FLIP_X, CA_FLIP_Y, CA_RESIZE_X, CA_RESIZE_Y, @@ -346,6 +346,16 @@ impl Node for XdgPopup { // log::info!("xdg-popup focus"); seat.pointer_cursor().set_known(KnownCursor::Default); } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + _x: Fixed, + _y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default) + } } impl StackedNode for XdgPopup { diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 50f4d41f..ae1a377d 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -9,7 +9,7 @@ use { fixed::Fixed, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, - wl_seat::{NodeSeatState, SeatId, WlSeatGlobal}, + wl_seat::{tablet::TabletTool, NodeSeatState, SeatId, WlSeatGlobal}, wl_surface::{ xdg_surface::{ xdg_toplevel::xdg_dialog_v1::XdgDialogV1, XdgSurface, XdgSurfaceError, @@ -529,6 +529,16 @@ impl Node for XdgToplevel { seat.pointer_cursor().set_known(KnownCursor::Default); } + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + _x: Fixed, + _y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default) + } + fn node_into_toplevel(self: Rc) -> Option> { Some(self) } diff --git a/src/ifs/wp_cursor_shape_device_v1.rs b/src/ifs/wp_cursor_shape_device_v1.rs index 8524d2ae..aa47e892 100644 --- a/src/ifs/wp_cursor_shape_device_v1.rs +++ b/src/ifs/wp_cursor_shape_device_v1.rs @@ -2,7 +2,7 @@ use { crate::{ client::{Client, ClientError}, cursor::KnownCursor, - ifs::wl_seat::WlSeatGlobal, + ifs::wl_seat::{tablet::TabletToolOpt, WlSeatGlobal}, leaks::Tracker, object::{Object, Version}, wire::{wp_cursor_shape_device_v1::*, WpCursorShapeDeviceV1Id}, @@ -46,10 +46,15 @@ const ALL_SCROLL: u32 = 32; const ZOOM_IN: u32 = 33; const ZOOM_OUT: u32 = 34; +pub enum CursorShapeCursorUser { + Seat(Rc), + TabletTool(Rc), +} + pub struct WpCursorShapeDeviceV1 { pub id: WpCursorShapeDeviceV1Id, pub client: Rc, - pub seat: Rc, + pub cursor_user: CursorShapeCursorUser, pub tracker: Tracker, pub version: Version, } @@ -100,14 +105,24 @@ impl WpCursorShapeDeviceV1RequestHandler for WpCursorShapeDeviceV1 { ZOOM_OUT => KnownCursor::ZoomOut, _ => return Err(WpCursorShapeDeviceV1Error::UnknownShape(req.shape)), }; - let pointer_node = match self.seat.pointer_node() { - Some(n) => n, - _ => return Ok(()), + let tablet_tool; + let (node_client_id, user) = match &self.cursor_user { + CursorShapeCursorUser::Seat(s) => match s.pointer_node() { + Some(n) => (n.node_client_id(), s.pointer_cursor()), + _ => return Ok(()), + }, + CursorShapeCursorUser::TabletTool(t) => match t.get() { + Some(t) => { + tablet_tool = t; + (tablet_tool.node().node_client_id(), tablet_tool.cursor()) + } + _ => return Ok(()), + }, }; - if pointer_node.node_client_id() != Some(self.client.id) { + if node_client_id != Some(self.client.id) { return Ok(()); } - self.seat.pointer_cursor().set_known(cursor); + user.set_known(cursor); Ok(()) } } diff --git a/src/ifs/wp_cursor_shape_manager_v1.rs b/src/ifs/wp_cursor_shape_manager_v1.rs index 301a939b..b9c612cd 100644 --- a/src/ifs/wp_cursor_shape_manager_v1.rs +++ b/src/ifs/wp_cursor_shape_manager_v1.rs @@ -2,10 +2,10 @@ use { crate::{ client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1, + ifs::wp_cursor_shape_device_v1::{CursorShapeCursorUser, WpCursorShapeDeviceV1}, leaks::Tracker, object::{Object, Version}, - wire::{wp_cursor_shape_manager_v1::*, WpCursorShapeManagerV1Id}, + wire::{wp_cursor_shape_manager_v1::*, WpCursorShapeDeviceV1Id, WpCursorShapeManagerV1Id}, }, std::rc::Rc, thiserror::Error, @@ -63,6 +63,25 @@ pub struct WpCursorShapeManagerV1 { pub version: Version, } +impl WpCursorShapeManagerV1 { + fn get( + &self, + id: WpCursorShapeDeviceV1Id, + cursor_user: CursorShapeCursorUser, + ) -> Result<(), WpCursorShapeManagerV1Error> { + let device = Rc::new(WpCursorShapeDeviceV1 { + id, + client: self.client.clone(), + cursor_user, + tracker: Default::default(), + version: self.version, + }); + track!(self.client, device); + self.client.add_client_obj(&device)?; + Ok(()) + } +} + impl WpCursorShapeManagerV1RequestHandler for WpCursorShapeManagerV1 { type Error = WpCursorShapeManagerV1Error; @@ -73,24 +92,18 @@ impl WpCursorShapeManagerV1RequestHandler for WpCursorShapeManagerV1 { fn get_pointer(&self, req: GetPointer, _slf: &Rc) -> Result<(), Self::Error> { let pointer = self.client.lookup(req.pointer)?; - let device = Rc::new(WpCursorShapeDeviceV1 { - id: req.cursor_shape_device, - client: self.client.clone(), - seat: pointer.seat.global.clone(), - tracker: Default::default(), - version: self.version, - }); - track!(self.client, device); - self.client.add_client_obj(&device)?; - Ok(()) + self.get( + req.cursor_shape_device, + CursorShapeCursorUser::Seat(pointer.seat.global.clone()), + ) } - fn get_tablet_tool_v2( - &self, - _req: GetTabletToolV2, - _slf: &Rc, - ) -> Result<(), Self::Error> { - Err(WpCursorShapeManagerV1Error::TabletToolNotSupported) + fn get_tablet_tool_v2(&self, req: GetTabletToolV2, _slf: &Rc) -> Result<(), Self::Error> { + let tool = self.client.lookup(req.tablet_tool)?; + self.get( + req.cursor_shape_device, + CursorShapeCursorUser::TabletTool(tool.tool.clone()), + ) } } @@ -107,7 +120,5 @@ simple_add_obj!(WpCursorShapeManagerV1); pub enum WpCursorShapeManagerV1Error { #[error(transparent)] ClientError(Box), - #[error("This compositor does not support tablet tools")] - TabletToolNotSupported, } efrom!(WpCursorShapeManagerV1Error, ClientError); diff --git a/src/libinput/device.rs b/src/libinput/device.rs index 73eb1965..76f813f9 100644 --- a/src/libinput/device.rs +++ b/src/libinput/device.rs @@ -21,9 +21,19 @@ use { libinput_device_config_tap_get_enabled, libinput_device_config_tap_get_finger_count, libinput_device_config_tap_set_drag_enabled, libinput_device_config_tap_set_drag_lock_enabled, - libinput_device_config_tap_set_enabled, libinput_device_get_name, - libinput_device_get_user_data, libinput_device_has_capability, - libinput_device_set_user_data, libinput_device_unref, libinput_path_remove_device, + libinput_device_config_tap_set_enabled, libinput_device_get_device_group, + libinput_device_get_id_product, libinput_device_get_id_vendor, + libinput_device_get_name, libinput_device_get_user_data, libinput_device_group, + libinput_device_group_get_user_data, libinput_device_group_set_user_data, + libinput_device_has_capability, libinput_device_set_user_data, + libinput_device_tablet_pad_get_mode_group, libinput_device_tablet_pad_get_num_buttons, + libinput_device_tablet_pad_get_num_mode_groups, + libinput_device_tablet_pad_get_num_rings, libinput_device_tablet_pad_get_num_strips, + libinput_device_unref, libinput_path_remove_device, libinput_tablet_pad_mode_group, + libinput_tablet_pad_mode_group_get_index, libinput_tablet_pad_mode_group_get_mode, + libinput_tablet_pad_mode_group_get_num_modes, + libinput_tablet_pad_mode_group_has_button, libinput_tablet_pad_mode_group_has_ring, + libinput_tablet_pad_mode_group_has_strip, }, LibInput, }, @@ -36,6 +46,16 @@ pub struct LibInputDevice<'a> { pub(super) _phantom: PhantomData<&'a ()>, } +pub struct LibInputDeviceGroup<'a> { + pub(super) group: *mut libinput_device_group, + pub(super) _phantom: PhantomData<&'a ()>, +} + +pub struct LibInputTabletPadModeGroup<'a> { + pub(super) group: *mut libinput_tablet_pad_mode_group, + pub(super) _phantom: PhantomData<&'a ()>, +} + pub struct RegisteredDevice { pub(super) _li: Rc, pub(super) dev: *mut libinput_device, @@ -191,6 +211,96 @@ impl<'a> LibInputDevice<'a> { pub fn has_natural_scrolling(&self) -> bool { unsafe { libinput_device_config_scroll_has_natural_scroll(self.dev) != 0 } } + + pub fn device_group(&self) -> LibInputDeviceGroup<'_> { + LibInputDeviceGroup { + group: unsafe { libinput_device_get_device_group(self.dev) }, + _phantom: Default::default(), + } + } + + pub fn product(&self) -> u32 { + unsafe { libinput_device_get_id_product(self.dev) as u32 } + } + + pub fn vendor(&self) -> u32 { + unsafe { libinput_device_get_id_vendor(self.dev) as u32 } + } + + pub fn pad_num_buttons(&self) -> u32 { + match unsafe { libinput_device_tablet_pad_get_num_buttons(self.dev) } { + -1 => 0, + n => n as u32, + } + } + + pub fn pad_num_rings(&self) -> u32 { + match unsafe { libinput_device_tablet_pad_get_num_rings(self.dev) } { + -1 => 0, + n => n as u32, + } + } + + pub fn pad_num_strips(&self) -> u32 { + match unsafe { libinput_device_tablet_pad_get_num_strips(self.dev) } { + -1 => 0, + n => n as u32, + } + } + + pub fn pad_num_mode_groups(&self) -> u32 { + match unsafe { libinput_device_tablet_pad_get_num_mode_groups(self.dev) } { + -1 => 0, + n => n as u32, + } + } + + pub fn pad_mode_group(&self, group: u32) -> Option> { + let group = unsafe { libinput_device_tablet_pad_get_mode_group(self.dev, group as _) }; + if group.is_null() { + return None; + } + Some(LibInputTabletPadModeGroup { + group, + _phantom: Default::default(), + }) + } +} + +impl<'a> LibInputDeviceGroup<'a> { + pub fn user_data(&self) -> usize { + unsafe { libinput_device_group_get_user_data(self.group) } + } + + pub fn set_user_data(&self, user_data: usize) { + unsafe { libinput_device_group_set_user_data(self.group, user_data) } + } +} + +impl<'a> LibInputTabletPadModeGroup<'a> { + pub fn index(&self) -> u32 { + unsafe { libinput_tablet_pad_mode_group_get_index(self.group) as u32 } + } + + pub fn num_modes(&self) -> u32 { + unsafe { libinput_tablet_pad_mode_group_get_num_modes(self.group) as u32 } + } + + pub fn mode(&self) -> u32 { + unsafe { libinput_tablet_pad_mode_group_get_mode(self.group) as u32 } + } + + pub fn has_button(&self, button: u32) -> bool { + unsafe { libinput_tablet_pad_mode_group_has_button(self.group, button as _) != 0 } + } + + pub fn has_ring(&self, ring: u32) -> bool { + unsafe { libinput_tablet_pad_mode_group_has_ring(self.group, ring as _) != 0 } + } + + pub fn has_strip(&self, strip: u32) -> bool { + unsafe { libinput_tablet_pad_mode_group_has_strip(self.group, strip as _) != 0 } + } } impl RegisteredDevice { diff --git a/src/libinput/event.rs b/src/libinput/event.rs index 60038fb6..45885c48 100644 --- a/src/libinput/event.rs +++ b/src/libinput/event.rs @@ -1,7 +1,11 @@ use { crate::libinput::{ - consts::{ButtonState, EventType, KeyState, PointerAxis, Switch, SwitchState}, - device::LibInputDevice, + consts::{ + ButtonState, EventType, KeyState, PointerAxis, Switch, SwitchState, + TabletPadRingAxisSource, TabletPadStripAxisSource, TabletToolProximityState, + TabletToolTipState, TabletToolType, + }, + device::{LibInputDevice, LibInputTabletPadModeGroup}, sys::{ libinput_event, libinput_event_destroy, libinput_event_gesture, libinput_event_gesture_get_angle_delta, libinput_event_gesture_get_cancelled, @@ -11,6 +15,7 @@ use { libinput_event_gesture_get_time_usec, libinput_event_get_device, libinput_event_get_gesture_event, libinput_event_get_keyboard_event, libinput_event_get_pointer_event, libinput_event_get_switch_event, + libinput_event_get_tablet_pad_event, libinput_event_get_tablet_tool_event, libinput_event_get_type, libinput_event_keyboard, libinput_event_keyboard_get_key, libinput_event_keyboard_get_key_state, libinput_event_keyboard_get_time_usec, libinput_event_pointer, libinput_event_pointer_get_button, @@ -20,7 +25,25 @@ use { libinput_event_pointer_get_scroll_value_v120, libinput_event_pointer_get_time_usec, libinput_event_pointer_has_axis, libinput_event_switch, libinput_event_switch_get_switch, libinput_event_switch_get_switch_state, - libinput_event_switch_get_time_usec, + libinput_event_switch_get_time_usec, libinput_event_tablet_pad, + libinput_event_tablet_pad_get_button_number, + libinput_event_tablet_pad_get_button_state, libinput_event_tablet_pad_get_mode, + libinput_event_tablet_pad_get_mode_group, libinput_event_tablet_pad_get_ring_number, + libinput_event_tablet_pad_get_ring_position, libinput_event_tablet_pad_get_ring_source, + libinput_event_tablet_pad_get_strip_number, + libinput_event_tablet_pad_get_strip_position, + libinput_event_tablet_pad_get_strip_source, libinput_event_tablet_pad_get_time_usec, + libinput_event_tablet_tool, libinput_event_tablet_tool_get_button, + libinput_event_tablet_tool_get_button_state, + libinput_event_tablet_tool_get_proximity_state, + libinput_event_tablet_tool_get_time_usec, libinput_event_tablet_tool_get_tip_state, + libinput_event_tablet_tool_get_tool, + libinput_event_tablet_tool_get_wheel_delta_discrete, + libinput_event_tablet_tool_get_x_transformed, + libinput_event_tablet_tool_get_y_transformed, libinput_tablet_tool, + libinput_tablet_tool_get_serial, libinput_tablet_tool_get_tool_id, + libinput_tablet_tool_get_type, libinput_tablet_tool_get_user_data, + libinput_tablet_tool_set_user_data, }, }, std::marker::PhantomData, @@ -51,6 +74,21 @@ pub struct LibInputEventSwitch<'a> { pub(super) _phantom: PhantomData<&'a ()>, } +pub struct LibInputEventTabletTool<'a> { + pub(super) event: *mut libinput_event_tablet_tool, + pub(super) _phantom: PhantomData<&'a ()>, +} + +pub struct LibInputEventTabletPad<'a> { + pub(super) event: *mut libinput_event_tablet_pad, + pub(super) _phantom: PhantomData<&'a ()>, +} + +pub struct LibInputTabletTool<'a> { + pub(super) tool: *mut libinput_tablet_tool, + pub(super) _phantom: PhantomData<&'a ()>, +} + impl<'a> Drop for LibInputEvent<'a> { fn drop(&mut self) { unsafe { @@ -59,6 +97,22 @@ impl<'a> Drop for LibInputEvent<'a> { } } +macro_rules! converter { + ($name:ident, $out:ident, $f:ident) => { + pub fn $name(&self) -> Option<$out> { + let res = unsafe { $f(self.event) }; + if res.is_null() { + None + } else { + Some($out { + event: res, + _phantom: Default::default(), + }) + } + } + }; +} + impl<'a> LibInputEvent<'a> { pub fn ty(&self) -> EventType { unsafe { EventType(libinput_event_get_type(self.event)) } @@ -71,53 +125,36 @@ impl<'a> LibInputEvent<'a> { } } - pub fn keyboard_event(&self) -> Option { - let res = unsafe { libinput_event_get_keyboard_event(self.event) }; - if res.is_null() { - None - } else { - Some(LibInputEventKeyboard { - event: res, - _phantom: Default::default(), - }) - } - } - - pub fn pointer_event(&self) -> Option { - let res = unsafe { libinput_event_get_pointer_event(self.event) }; - if res.is_null() { - None - } else { - Some(LibInputEventPointer { - event: res, - _phantom: Default::default(), - }) - } - } - - pub fn gesture_event(&self) -> Option { - let res = unsafe { libinput_event_get_gesture_event(self.event) }; - if res.is_null() { - None - } else { - Some(LibInputEventGesture { - event: res, - _phantom: Default::default(), - }) - } - } - - pub fn switch_event(&self) -> Option { - let res = unsafe { libinput_event_get_switch_event(self.event) }; - if res.is_null() { - None - } else { - Some(LibInputEventSwitch { - event: res, - _phantom: Default::default(), - }) - } - } + converter!( + keyboard_event, + LibInputEventKeyboard, + libinput_event_get_keyboard_event + ); + converter!( + pointer_event, + LibInputEventPointer, + libinput_event_get_pointer_event + ); + converter!( + gesture_event, + LibInputEventGesture, + libinput_event_get_gesture_event + ); + converter!( + switch_event, + LibInputEventSwitch, + libinput_event_get_switch_event + ); + converter!( + tablet_tool_event, + LibInputEventTabletTool, + libinput_event_get_tablet_tool_event + ); + converter!( + tablet_pad_event, + LibInputEventTabletPad, + libinput_event_get_tablet_pad_event + ); } impl<'a> LibInputEventKeyboard<'a> { @@ -228,3 +265,205 @@ impl<'a> LibInputEventSwitch<'a> { unsafe { SwitchState(libinput_event_switch_get_switch_state(self.event)) } } } + +macro_rules! has_changed { + ($name:ident, $f:ident) => { + pub fn $name(&self) -> bool { + unsafe { crate::libinput::sys::$f(self.event) != 0 } + } + }; +} + +macro_rules! get_double { + ($name:ident, $f:ident) => { + pub fn $name(&self) -> f64 { + unsafe { crate::libinput::sys::$f(self.event) } + } + }; +} + +macro_rules! has_capability { + ($name:ident, $f:ident) => { + pub fn $name(&self) -> bool { + unsafe { crate::libinput::sys::$f(self.tool) != 0 } + } + }; +} + +impl<'a> LibInputTabletTool<'a> { + pub fn user_data(&self) -> usize { + unsafe { libinput_tablet_tool_get_user_data(self.tool) } + } + + pub fn set_user_data(&self, user_data: usize) { + unsafe { libinput_tablet_tool_set_user_data(self.tool, user_data) } + } + + pub fn type_(&self) -> TabletToolType { + unsafe { TabletToolType(libinput_tablet_tool_get_type(self.tool)) } + } + + pub fn tool_id(&self) -> u64 { + unsafe { libinput_tablet_tool_get_tool_id(self.tool) } + } + + pub fn serial(&self) -> u64 { + unsafe { libinput_tablet_tool_get_serial(self.tool) } + } + + has_capability!(has_pressure, libinput_tablet_tool_has_pressure); + has_capability!(has_distance, libinput_tablet_tool_has_distance); + has_capability!(has_tilt, libinput_tablet_tool_has_tilt); + has_capability!(has_rotation, libinput_tablet_tool_has_rotation); + has_capability!(has_slider, libinput_tablet_tool_has_slider); + // has_capability!(has_size, libinput_tablet_tool_has_size); + has_capability!(has_wheel, libinput_tablet_tool_has_wheel); +} + +impl<'a> LibInputEventTabletTool<'a> { + pub fn tool(&self) -> LibInputTabletTool<'_> { + LibInputTabletTool { + tool: unsafe { libinput_event_tablet_tool_get_tool(self.event) }, + _phantom: Default::default(), + } + } + + pub fn time_usec(&self) -> u64 { + unsafe { libinput_event_tablet_tool_get_time_usec(self.event) } + } + + has_changed!(x_has_changed, libinput_event_tablet_tool_x_has_changed); + has_changed!(y_has_changed, libinput_event_tablet_tool_y_has_changed); + has_changed!( + pressure_has_changed, + libinput_event_tablet_tool_pressure_has_changed + ); + has_changed!( + distance_has_changed, + libinput_event_tablet_tool_distance_has_changed + ); + has_changed!( + tilt_x_has_changed, + libinput_event_tablet_tool_tilt_x_has_changed + ); + has_changed!( + tilt_y_has_changed, + libinput_event_tablet_tool_tilt_y_has_changed + ); + has_changed!( + rotation_has_changed, + libinput_event_tablet_tool_rotation_has_changed + ); + has_changed!( + slider_has_changed, + libinput_event_tablet_tool_slider_has_changed + ); + // has_changed!( + // size_major_has_changed, + // libinput_event_tablet_tool_size_major_has_changed + // ); + // has_changed!( + // size_minor_has_changed, + // libinput_event_tablet_tool_size_minor_has_changed + // ); + has_changed!( + wheel_has_changed, + libinput_event_tablet_tool_wheel_has_changed + ); + + // get_double!(x, libinput_event_tablet_tool_get_x); + // get_double!(y, libinput_event_tablet_tool_get_y); + get_double!(dx, libinput_event_tablet_tool_get_dx); + get_double!(dy, libinput_event_tablet_tool_get_dy); + get_double!(pressure, libinput_event_tablet_tool_get_pressure); + get_double!(distance, libinput_event_tablet_tool_get_distance); + get_double!(tilt_x, libinput_event_tablet_tool_get_tilt_x); + get_double!(tilt_y, libinput_event_tablet_tool_get_tilt_y); + get_double!(rotation, libinput_event_tablet_tool_get_rotation); + get_double!( + slider_position, + libinput_event_tablet_tool_get_slider_position + ); + // get_double!(size_major, libinput_event_tablet_tool_get_size_major); + // get_double!(size_minor, libinput_event_tablet_tool_get_size_minor); + get_double!(wheel_delta, libinput_event_tablet_tool_get_wheel_delta); + + pub fn wheel_delta_discrete(&self) -> i32 { + unsafe { libinput_event_tablet_tool_get_wheel_delta_discrete(self.event) as _ } + } + + pub fn x_transformed(&self, width: u32) -> f64 { + unsafe { libinput_event_tablet_tool_get_x_transformed(self.event, width) } + } + + pub fn y_transformed(&self, width: u32) -> f64 { + unsafe { libinput_event_tablet_tool_get_y_transformed(self.event, width) } + } + + pub fn proximity_state(&self) -> TabletToolProximityState { + unsafe { + TabletToolProximityState(libinput_event_tablet_tool_get_proximity_state(self.event)) + } + } + + pub fn tip_state(&self) -> TabletToolTipState { + unsafe { TabletToolTipState(libinput_event_tablet_tool_get_tip_state(self.event)) } + } + + pub fn button(&self) -> u32 { + unsafe { libinput_event_tablet_tool_get_button(self.event) } + } + + pub fn button_state(&self) -> ButtonState { + unsafe { ButtonState(libinput_event_tablet_tool_get_button_state(self.event)) } + } +} + +impl<'a> LibInputEventTabletPad<'a> { + pub fn time_usec(&self) -> u64 { + unsafe { libinput_event_tablet_pad_get_time_usec(self.event) } + } + + pub fn ring_position(&self) -> f64 { + unsafe { libinput_event_tablet_pad_get_ring_position(self.event) } + } + + pub fn ring_number(&self) -> u32 { + unsafe { libinput_event_tablet_pad_get_ring_number(self.event) as u32 } + } + + pub fn ring_source(&self) -> TabletPadRingAxisSource { + unsafe { TabletPadRingAxisSource(libinput_event_tablet_pad_get_ring_source(self.event)) } + } + + pub fn strip_position(&self) -> f64 { + unsafe { libinput_event_tablet_pad_get_strip_position(self.event) } + } + + pub fn strip_number(&self) -> u32 { + unsafe { libinput_event_tablet_pad_get_strip_number(self.event) as u32 } + } + + pub fn strip_source(&self) -> TabletPadStripAxisSource { + unsafe { TabletPadStripAxisSource(libinput_event_tablet_pad_get_strip_source(self.event)) } + } + + pub fn button_number(&self) -> u32 { + unsafe { libinput_event_tablet_pad_get_button_number(self.event) } + } + + pub fn button_state(&self) -> ButtonState { + unsafe { ButtonState(libinput_event_tablet_pad_get_button_state(self.event)) } + } + + pub fn mode(&self) -> u32 { + unsafe { libinput_event_tablet_pad_get_mode(self.event) as u32 } + } + + pub fn mode_group(&self) -> LibInputTabletPadModeGroup { + LibInputTabletPadModeGroup { + group: unsafe { libinput_event_tablet_pad_get_mode_group(self.event) }, + _phantom: Default::default(), + } + } +} diff --git a/src/libinput/sys.rs b/src/libinput/sys.rs index 31d860f2..c937abb3 100644 --- a/src/libinput/sys.rs +++ b/src/libinput/sys.rs @@ -9,6 +9,8 @@ pub struct libinput(u8); #[repr(transparent)] pub struct libinput_device(u8); #[repr(transparent)] +pub struct libinput_device_group(u8); +#[repr(transparent)] pub struct libinput_event(u8); #[repr(transparent)] pub struct libinput_event_keyboard(u8); @@ -18,6 +20,16 @@ pub struct libinput_event_pointer(u8); pub struct libinput_event_gesture(u8); #[repr(transparent)] pub struct libinput_event_switch(u8); +#[repr(transparent)] +pub struct libinput_event_tablet_tool(u8); +#[repr(transparent)] +pub struct libinput_event_tablet_pad(u8); +#[repr(transparent)] +pub struct libinput_tablet_pad_mode_group(u8); +#[repr(transparent)] +pub struct libinput_tablet_tool(u8); +// #[repr(transparent)] +// pub struct libinput_tablet_pad(u8); #[link(name = "input")] extern "C" { @@ -166,6 +178,185 @@ extern "C" { event: *mut libinput_event_switch, ) -> libinput_switch_state; pub fn libinput_event_switch_get_time_usec(event: *mut libinput_event_switch) -> u64; + + pub fn libinput_device_get_device_group( + device: *mut libinput_device, + ) -> *mut libinput_device_group; + pub fn libinput_device_group_set_user_data(group: *mut libinput_device_group, user_data: usize); + pub fn libinput_device_group_get_user_data(group: *mut libinput_device_group) -> usize; + + pub fn libinput_device_get_id_product(device: *mut libinput_device) -> c::c_uint; + pub fn libinput_device_get_id_vendor(device: *mut libinput_device) -> c::c_uint; + + pub fn libinput_event_get_tablet_tool_event( + event: *mut libinput_event, + ) -> *mut libinput_event_tablet_tool; + pub fn libinput_event_get_tablet_pad_event( + event: *mut libinput_event, + ) -> *mut libinput_event_tablet_pad; + pub fn libinput_event_tablet_tool_get_tool( + event: *mut libinput_event_tablet_tool, + ) -> *mut libinput_tablet_tool; + pub fn libinput_event_tablet_pad_get_mode_group( + event: *mut libinput_event_tablet_pad, + ) -> *mut libinput_tablet_pad_mode_group; + pub fn libinput_event_tablet_tool_x_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_y_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_pressure_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_distance_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_tilt_x_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_tilt_y_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_rotation_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_slider_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + // pub fn libinput_event_tablet_tool_size_major_has_changed( + // event: *mut libinput_event_tablet_tool, + // ) -> c::c_int; + // pub fn libinput_event_tablet_tool_size_minor_has_changed( + // event: *mut libinput_event_tablet_tool, + // ) -> c::c_int; + pub fn libinput_event_tablet_tool_wheel_has_changed( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + // pub fn libinput_event_tablet_tool_get_x(event: *mut libinput_event_tablet_tool) -> f64; + // pub fn libinput_event_tablet_tool_get_y(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_dx(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_dy(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_pressure(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_distance(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_tilt_x(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_tilt_y(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_rotation(event: *mut libinput_event_tablet_tool) -> f64; + pub fn libinput_event_tablet_tool_get_slider_position( + event: *mut libinput_event_tablet_tool, + ) -> f64; + // pub fn libinput_event_tablet_tool_get_size_major(event: *mut libinput_event_tablet_tool) + // -> f64; + // pub fn libinput_event_tablet_tool_get_size_minor(event: *mut libinput_event_tablet_tool) + // -> f64; + pub fn libinput_event_tablet_tool_get_wheel_delta( + event: *mut libinput_event_tablet_tool, + ) -> f64; + pub fn libinput_event_tablet_tool_get_wheel_delta_discrete( + event: *mut libinput_event_tablet_tool, + ) -> c::c_int; + pub fn libinput_event_tablet_tool_get_x_transformed( + event: *mut libinput_event_tablet_tool, + width: u32, + ) -> f64; + pub fn libinput_event_tablet_tool_get_y_transformed( + event: *mut libinput_event_tablet_tool, + width: u32, + ) -> f64; + pub fn libinput_event_tablet_tool_get_proximity_state( + event: *mut libinput_event_tablet_tool, + ) -> libinput_tablet_tool_proximity_state; + pub fn libinput_event_tablet_tool_get_tip_state( + event: *mut libinput_event_tablet_tool, + ) -> libinput_tablet_tool_tip_state; + pub fn libinput_event_tablet_tool_get_button(event: *mut libinput_event_tablet_tool) -> u32; + pub fn libinput_event_tablet_tool_get_button_state( + event: *mut libinput_event_tablet_tool, + ) -> libinput_button_state; + // pub fn libinput_event_tablet_tool_get_seat_button_count( + // event: *mut libinput_event_tablet_tool, + // ) -> u32; + pub fn libinput_event_tablet_tool_get_time_usec(event: *mut libinput_event_tablet_tool) -> u64; + pub fn libinput_tablet_tool_get_type( + tool: *mut libinput_tablet_tool, + ) -> libinput_tablet_tool_type; + pub fn libinput_tablet_tool_get_tool_id(tool: *mut libinput_tablet_tool) -> u64; + pub fn libinput_tablet_tool_has_pressure(tool: *mut libinput_tablet_tool) -> c::c_int; + pub fn libinput_tablet_tool_has_distance(tool: *mut libinput_tablet_tool) -> c::c_int; + pub fn libinput_tablet_tool_has_tilt(tool: *mut libinput_tablet_tool) -> c::c_int; + pub fn libinput_tablet_tool_has_rotation(tool: *mut libinput_tablet_tool) -> c::c_int; + pub fn libinput_tablet_tool_has_slider(tool: *mut libinput_tablet_tool) -> c::c_int; + // pub fn libinput_tablet_tool_has_size(tool: *mut libinput_tablet_tool) -> c::c_int; + pub fn libinput_tablet_tool_has_wheel(tool: *mut libinput_tablet_tool) -> c::c_int; + // pub fn libinput_tablet_tool_has_button(tool: *mut libinput_tablet_tool, code: u32) -> c::c_int; + // pub fn libinput_tablet_tool_is_unique(tool: *mut libinput_tablet_tool) -> c::c_int; + pub fn libinput_tablet_tool_get_serial(tool: *mut libinput_tablet_tool) -> u64; + pub fn libinput_tablet_tool_get_user_data(tool: *mut libinput_tablet_tool) -> usize; + pub fn libinput_tablet_tool_set_user_data(tool: *mut libinput_tablet_tool, user_data: usize); + pub fn libinput_event_tablet_pad_get_ring_position( + event: *mut libinput_event_tablet_pad, + ) -> f64; + pub fn libinput_event_tablet_pad_get_ring_number( + event: *mut libinput_event_tablet_pad, + ) -> c::c_uint; + pub fn libinput_event_tablet_pad_get_ring_source( + event: *mut libinput_event_tablet_pad, + ) -> libinput_tablet_pad_ring_axis_source; + pub fn libinput_event_tablet_pad_get_strip_position( + event: *mut libinput_event_tablet_pad, + ) -> f64; + pub fn libinput_event_tablet_pad_get_strip_number( + event: *mut libinput_event_tablet_pad, + ) -> c::c_uint; + pub fn libinput_event_tablet_pad_get_strip_source( + event: *mut libinput_event_tablet_pad, + ) -> libinput_tablet_pad_strip_axis_source; + pub fn libinput_event_tablet_pad_get_button_number( + event: *mut libinput_event_tablet_pad, + ) -> u32; + pub fn libinput_event_tablet_pad_get_button_state( + event: *mut libinput_event_tablet_pad, + ) -> libinput_button_state; + // pub fn libinput_event_tablet_pad_get_key(event: *mut libinput_event_tablet_pad) -> u32; + // pub fn libinput_event_tablet_pad_get_key_state( + // event: *mut libinput_event_tablet_pad, + // ) -> libinput_key_state; + pub fn libinput_event_tablet_pad_get_mode(event: *mut libinput_event_tablet_pad) -> c::c_uint; + pub fn libinput_event_tablet_pad_get_time_usec(event: *mut libinput_event_tablet_pad) -> u64; + pub fn libinput_device_tablet_pad_get_mode_group( + device: *mut libinput_device, + index: c::c_uint, + ) -> *mut libinput_tablet_pad_mode_group; + pub fn libinput_device_tablet_pad_get_num_mode_groups(device: *mut libinput_device) + -> c::c_int; + pub fn libinput_device_tablet_pad_get_num_buttons(device: *mut libinput_device) -> c::c_int; + pub fn libinput_device_tablet_pad_get_num_rings(device: *mut libinput_device) -> c::c_int; + pub fn libinput_device_tablet_pad_get_num_strips(device: *mut libinput_device) -> c::c_int; + pub fn libinput_tablet_pad_mode_group_get_index( + group: *mut libinput_tablet_pad_mode_group, + ) -> c::c_uint; + pub fn libinput_tablet_pad_mode_group_get_num_modes( + group: *mut libinput_tablet_pad_mode_group, + ) -> c::c_uint; + pub fn libinput_tablet_pad_mode_group_get_mode( + group: *mut libinput_tablet_pad_mode_group, + ) -> c::c_uint; + pub fn libinput_tablet_pad_mode_group_has_button( + group: *mut libinput_tablet_pad_mode_group, + button: c::c_uint, + ) -> c::c_int; + pub fn libinput_tablet_pad_mode_group_has_ring( + group: *mut libinput_tablet_pad_mode_group, + ring: c::c_uint, + ) -> c::c_int; + pub fn libinput_tablet_pad_mode_group_has_strip( + group: *mut libinput_tablet_pad_mode_group, + strip: c::c_uint, + ) -> c::c_int; + // pub fn libinput_tablet_pad_mode_group_button_is_toggle( + // group: *mut libinput_tablet_pad_mode_group, + // button: c::c_uint, + // ) -> c::c_int; } #[repr(C)] diff --git a/src/state.rs b/src/state.rs index 097e11b2..73abb7df 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,7 +4,8 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, backend::{ Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorId, ConnectorIds, - DrmDeviceId, DrmDeviceIds, InputDevice, InputDeviceId, InputDeviceIds, MonitorInfo, + DrmDeviceId, DrmDeviceIds, InputDevice, InputDeviceGroupIds, InputDeviceId, + InputDeviceIds, MonitorInfo, }, backends::dummy::DummyBackend, cli::RunArgs, @@ -34,7 +35,10 @@ use { jay_workspace_watcher::JayWorkspaceWatcher, wl_drm::WlDrmGlobal, wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState}, - wl_seat::{SeatIds, WlSeatGlobal}, + wl_seat::{ + tablet::{TabletIds, TabletInit, TabletPadIds, TabletPadInit, TabletToolIds}, + SeatIds, WlSeatGlobal, + }, wl_surface::{ wl_subsurface::SubsurfaceIds, zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, @@ -188,9 +192,12 @@ pub struct State { pub security_context_acceptors: SecurityContextAcceptors, pub cursor_user_group_ids: CursorUserGroupIds, pub cursor_user_ids: CursorUserIds, - pub cursor_users: CopyHashMap>, pub cursor_user_groups: CopyHashMap>, pub cursor_user_group_hardware_cursor: CloneCell>>, + pub input_device_group_ids: InputDeviceGroupIds, + pub tablet_ids: TabletIds, + pub tablet_tool_ids: TabletToolIds, + pub tablet_pad_ids: TabletPadIds, } // impl Drop for State { @@ -263,6 +270,8 @@ pub struct DeviceHandlerData { pub keymap: CloneCell>>, pub xkb_state: CloneCell>>>, pub output: CloneCell>>, + pub tablet_init: Option>, + pub tablet_pad_init: Option>, } pub struct ConnectorData { diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index ee8d7250..28b53961 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -24,6 +24,8 @@ pub fn handle(state: &Rc, dev: Rc) { keymap: Default::default(), xkb_state: Default::default(), output: Default::default(), + tablet_init: dev.tablet_info(), + tablet_pad_init: dev.tablet_pad_info(), }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { diff --git a/src/time.rs b/src/time.rs index c4da837d..9537c2f0 100644 --- a/src/time.rs +++ b/src/time.rs @@ -122,3 +122,7 @@ impl Add for Time { pub fn now_usec() -> u64 { Time::now_unchecked().usec() } + +pub fn usec_to_msec(usec: u64) -> u32 { + (usec / 1000) as u32 +} diff --git a/src/tree.rs b/src/tree.rs index 89b6160c..58f679f0 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -4,7 +4,15 @@ use { client::{Client, ClientId}, fixed::Fixed, ifs::{ - wl_seat::{wl_pointer::PendingScroll, Dnd, NodeSeatState, WlSeatGlobal}, + wl_seat::{ + tablet::{ + PadButtonState, TabletPad, TabletPadGroup, TabletPadRing, TabletPadStrip, + TabletRingEventSource, TabletStripEventSource, TabletTool, TabletToolChanges, + ToolButtonState, + }, + wl_pointer::PendingScroll, + Dnd, NodeSeatState, WlSeatGlobal, + }, wl_surface::WlSurface, }, rect::Rect, @@ -344,6 +352,116 @@ pub trait Node: 'static { let _ = cancelled; } + fn node_on_tablet_pad_enter(&self, pad: &Rc) { + let _ = pad; + } + + fn node_on_tablet_pad_leave(&self, pad: &Rc) { + let _ = pad; + } + + fn node_on_tablet_pad_button( + &self, + pad: &Rc, + time_usec: u64, + button: u32, + state: PadButtonState, + ) { + let _ = pad; + let _ = time_usec; + let _ = button; + let _ = state; + } + + fn node_on_tablet_pad_mode_switch( + &self, + pad: &Rc, + group: &Rc, + time_usec: u64, + mode: u32, + ) { + let _ = pad; + let _ = group; + let _ = time_usec; + let _ = mode; + } + + fn node_on_tablet_pad_ring( + &self, + pad: &Rc, + ring: &Rc, + source: Option, + angle: Option, + time_usec: u64, + ) { + let _ = pad; + let _ = time_usec; + let _ = ring; + let _ = source; + let _ = angle; + } + + fn node_on_tablet_pad_strip( + &self, + pad: &Rc, + strip: &Rc, + source: Option, + position: Option, + time_usec: u64, + ) { + let _ = pad; + let _ = time_usec; + let _ = strip; + let _ = source; + let _ = position; + } + + fn node_on_tablet_tool_leave(&self, tool: &Rc, time_usec: u64) { + let _ = tool; + let _ = time_usec; + } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + time_usec: u64, + x: Fixed, + y: Fixed, + ) { + let _ = tool; + let _ = time_usec; + let _ = x; + let _ = y; + } + + fn node_on_tablet_tool_button( + &self, + tool: &Rc, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + let _ = tool; + let _ = time_usec; + let _ = button; + let _ = state; + } + + fn node_on_tablet_tool_apply_changes( + self: Rc, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + let _ = tool; + let _ = time_usec; + let _ = changes; + let _ = x; + let _ = y; + } + // TYPE CONVERTERS #[cfg_attr(not(feature = "it"), allow(dead_code))] diff --git a/src/tree/container.rs b/src/tree/container.rs index ba41875b..4ad79051 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -2,10 +2,13 @@ use { crate::{ backend::KeyState, cursor::KnownCursor, + cursor_user::CursorUser, fixed::Fixed, ifs::wl_seat::{ - collect_kb_foci, collect_kb_foci2, wl_pointer::PendingScroll, NodeSeatState, SeatId, - WlSeatGlobal, BTN_LEFT, + collect_kb_foci, collect_kb_foci2, + tablet::{TabletTool, TabletToolChanges, TabletToolId}, + wl_pointer::PendingScroll, + NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, }, rect::Rect, renderer::Renderer, @@ -112,7 +115,7 @@ pub struct ContainerNode { focus_history: LinkedList>, child_nodes: RefCell>>, workspace: CloneCell>, - seats: RefCell>, + cursors: RefCell>, state: Rc, pub render_data: RefCell, scroller: Scroller, @@ -141,7 +144,13 @@ pub struct ContainerChild { factor: Cell, } -struct SeatState { +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum CursorType { + Seat(SeatId), + TabletTool(TabletToolId), +} + +struct CursorState { cursor: KnownCursor, target: bool, x: i32, @@ -210,7 +219,7 @@ impl ContainerNode { focus_history: Default::default(), child_nodes: RefCell::new(child_nodes), workspace: CloneCell::new(workspace.clone()), - seats: RefCell::new(Default::default()), + cursors: RefCell::new(Default::default()), state: state.clone(), render_data: Default::default(), scroller: Default::default(), @@ -328,7 +337,7 @@ impl ContainerNode { } fn cancel_seat_ops(&self) { - let mut seats = self.seats.borrow_mut(); + let mut seats = self.cursors.borrow_mut(); for seat in seats.values_mut() { seat.op = None; } @@ -514,12 +523,21 @@ impl ContainerNode { ); } - fn pointer_move(self: &Rc, seat: &Rc, mut x: i32, mut y: i32) { + fn pointer_move( + self: &Rc, + id: CursorType, + cursor: &CursorUser, + x: Fixed, + y: Fixed, + target: bool, + ) { + let mut x = x.round_down(); + let mut y = y.round_down(); let title_height = self.state.theme.sizes.title_height.get(); - let mut seats = self.seats.borrow_mut(); - let seat_state = seats.entry(seat.id()).or_insert_with(|| SeatState { + let mut seats = self.cursors.borrow_mut(); + let seat_state = seats.entry(id).or_insert_with(|| CursorState { cursor: KnownCursor::Default, - target: false, + target, x, y, op: None, @@ -601,7 +619,7 @@ impl ContainerNode { }; if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) { if seat_state.target { - seat.pointer_cursor().set_known(new_cursor); + cursor.set_known(new_cursor); } } } @@ -1036,6 +1054,80 @@ impl ContainerNode { } } } + + fn button( + self: Rc, + id: CursorType, + seat: &Rc, + time_usec: u64, + pressed: bool, + ) { + let mut seat_datas = self.cursors.borrow_mut(); + let seat_data = match seat_datas.get_mut(&id) { + Some(s) => s, + _ => return, + }; + if seat_data.op.is_none() { + if !pressed { + return; + } + let (kind, child) = 'res: { + let mono = self.mono_child.is_some(); + for child in self.children.iter() { + let rect = child.title_rect.get(); + if rect.contains(seat_data.x, seat_data.y) { + self.activate_child(&child); + child + .node + .clone() + .node_do_focus(seat, Direction::Unspecified); + break 'res (SeatOpKind::Move, child); + } else if !mono { + if self.split.get() == ContainerSplit::Horizontal { + if seat_data.x < rect.x1() { + break 'res ( + SeatOpKind::Resize { + dist_left: seat_data.x + - child.prev().unwrap().body.get().x2(), + dist_right: child.body.get().x1() - seat_data.x, + }, + child, + ); + } + } else { + if seat_data.y < rect.y1() { + break 'res ( + SeatOpKind::Resize { + dist_left: seat_data.y + - child.prev().unwrap().body.get().y2(), + dist_right: child.body.get().y1() - seat_data.y, + }, + child, + ); + } + } + } + } + return; + }; + if seat_data + .double_click_state + .click(&self.state, time_usec, seat_data.x, seat_data.y) + && kind == SeatOpKind::Move + { + drop(seat_datas); + seat.set_tl_floating(child.node.clone(), true); + return; + } + seat_data.op = Some(SeatOp { child, kind }) + } else if !pressed { + let op = seat_data.op.take().unwrap(); + drop(seat_datas); + if op.kind == SeatOpKind::Move { + // todo + } + } + } } struct SeatOp { @@ -1194,76 +1286,14 @@ impl Node for ContainerNode { if button != BTN_LEFT { return; } - let mut seat_datas = self.seats.borrow_mut(); - let seat_data = match seat_datas.get_mut(&seat.id()) { - Some(s) => s, - _ => return, - }; - if seat_data.op.is_none() { - if state != KeyState::Pressed { - return; - } - let (kind, child) = 'res: { - let mono = self.mono_child.is_some(); - for child in self.children.iter() { - let rect = child.title_rect.get(); - if rect.contains(seat_data.x, seat_data.y) { - self.activate_child(&child); - child - .node - .clone() - .node_do_focus(seat, Direction::Unspecified); - break 'res (SeatOpKind::Move, child); - } else if !mono { - if self.split.get() == ContainerSplit::Horizontal { - if seat_data.x < rect.x1() { - break 'res ( - SeatOpKind::Resize { - dist_left: seat_data.x - - child.prev().unwrap().body.get().x2(), - dist_right: child.body.get().x1() - seat_data.x, - }, - child, - ); - } - } else { - if seat_data.y < rect.y1() { - break 'res ( - SeatOpKind::Resize { - dist_left: seat_data.y - - child.prev().unwrap().body.get().y2(), - dist_right: child.body.get().y1() - seat_data.y, - }, - child, - ); - } - } - } - } - return; - }; - if seat_data - .double_click_state - .click(&self.state, time_usec, seat_data.x, seat_data.y) - && kind == SeatOpKind::Move - { - drop(seat_datas); - seat.set_tl_floating(child.node.clone(), true); - return; - } - seat_data.op = Some(SeatOp { child, kind }) - } else if state == KeyState::Released { - let op = seat_data.op.take().unwrap(); - drop(seat_datas); - if op.kind == SeatOpKind::Move { - // todo - } - } + let id = CursorType::Seat(seat.id()); + self.button(id, seat, time_usec, state == KeyState::Pressed); } fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { - let mut seat_datas = self.seats.borrow_mut(); - let seat_data = match seat_datas.get_mut(&seat.id()) { + let mut seat_datas = self.cursors.borrow_mut(); + let id = CursorType::Seat(seat.id()); + let seat_data = match seat_datas.get_mut(&id) { Some(s) => s, _ => return, }; @@ -1299,21 +1329,29 @@ impl Node for ContainerNode { fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { // log::info!("node_on_pointer_enter"); - self.pointer_move(seat, x.round_down(), y.round_down()); + self.pointer_move( + CursorType::Seat(seat.id()), + seat.pointer_cursor(), + x, + y, + false, + ); } fn node_on_pointer_unfocus(&self, seat: &Rc) { // log::info!("unfocus"); - let mut seats = self.seats.borrow_mut(); - if let Some(seat_state) = seats.get_mut(&seat.id()) { + let mut seats = self.cursors.borrow_mut(); + let id = CursorType::Seat(seat.id()); + if let Some(seat_state) = seats.get_mut(&id) { seat_state.target = false; } } fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("container focus"); - let mut seats = self.seats.borrow_mut(); - if let Some(seat_state) = seats.get_mut(&seat.id()) { + let mut seats = self.cursors.borrow_mut(); + let id = CursorType::Seat(seat.id()); + if let Some(seat_state) = seats.get_mut(&id) { seat_state.target = true; seat.pointer_cursor().set_known(seat_state.cursor); } @@ -1321,7 +1359,46 @@ impl Node for ContainerNode { fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { // log::info!("node_on_pointer_motion"); - self.pointer_move(seat, x.round_down(), y.round_down()); + self.pointer_move( + CursorType::Seat(seat.id()), + seat.pointer_cursor(), + x, + y, + false, + ); + } + + fn node_on_tablet_tool_leave(&self, tool: &Rc, _time_usec: u64) { + let id = CursorType::TabletTool(tool.id); + self.cursors.borrow_mut().remove(&id); + } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + x: Fixed, + y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default); + self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, true); + } + + fn node_on_tablet_tool_apply_changes( + self: Rc, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + let id = CursorType::TabletTool(tool.id); + self.pointer_move(id, tool.cursor(), x, y, false); + if let Some(changes) = changes { + if let Some(pressed) = changes.down { + self.button(id, tool.seat(), time_usec, pressed); + } + } } fn node_into_container(self: Rc) -> Option> { @@ -1332,13 +1409,13 @@ impl Node for ContainerNode { Some(self) } - fn node_is_container(&self) -> bool { - true - } - fn node_into_toplevel(self: Rc) -> Option> { Some(self) } + + fn node_is_container(&self) -> bool { + true + } } impl ContainingNode for ContainerNode { @@ -1534,7 +1611,7 @@ impl ToplevelNodeBase for ContainerNode { } fn tl_destroy_impl(&self) { - mem::take(self.seats.borrow_mut().deref_mut()); + mem::take(self.cursors.borrow_mut().deref_mut()); let mut cn = self.child_nodes.borrow_mut(); for (_, n) in cn.drain() { n.node.tl_destroy(); diff --git a/src/tree/display.rs b/src/tree/display.rs index 610d6480..4cd2039f 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -2,7 +2,8 @@ use { crate::{ backend::ConnectorId, cursor::KnownCursor, - ifs::wl_seat::{NodeSeatState, WlSeatGlobal}, + fixed::Fixed, + ifs::wl_seat::{tablet::TabletTool, NodeSeatState, WlSeatGlobal}, rect::Rect, renderer::Renderer, state::State, @@ -142,4 +143,14 @@ impl Node for DisplayNode { // log::info!("display focus"); seat.pointer_cursor().set_known(KnownCursor::Default); } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + _x: Fixed, + _y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default) + } } diff --git a/src/tree/float.rs b/src/tree/float.rs index fdf8516b..571a99cf 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -2,8 +2,12 @@ use { crate::{ backend::KeyState, cursor::KnownCursor, + cursor_user::CursorUser, fixed::Fixed, - ifs::wl_seat::{NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT}, + ifs::wl_seat::{ + tablet::{TabletTool, TabletToolChanges, TabletToolId}, + NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, + }, rect::Rect, renderer::Renderer, scale::Scale, @@ -44,11 +48,17 @@ pub struct FloatNode { pub render_titles_scheduled: Cell, pub title: RefCell, pub title_textures: CopyHashMap, - seats: RefCell>, + cursors: RefCell>, pub attention_requested: Cell, } -struct SeatState { +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum CursorType { + Seat(SeatId), + TabletTool(TabletToolId), +} + +struct CursorState { cursor: KnownCursor, target: bool, x: i32, @@ -113,7 +123,7 @@ impl FloatNode { render_titles_scheduled: Cell::new(false), title: Default::default(), title_textures: Default::default(), - seats: Default::default(), + cursors: Default::default(), attention_requested: Cell::new(false), }); floater.pull_child_properties(); @@ -214,14 +224,23 @@ impl FloatNode { } } - fn pointer_move(self: &Rc, seat: &Rc, x: i32, y: i32) { + fn pointer_move( + self: &Rc, + id: CursorType, + cursor: &CursorUser, + x: Fixed, + y: Fixed, + target: bool, + ) { + let x = x.round_down(); + let y = y.round_down(); let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); let th = theme.sizes.title_height.get(); - let mut seats = self.seats.borrow_mut(); - let seat_state = seats.entry(seat.id()).or_insert_with(|| SeatState { + let mut seats = self.cursors.borrow_mut(); + let seat_state = seats.entry(id).or_insert_with(|| CursorState { cursor: KnownCursor::Default, - target: false, + target, x, y, op_type: OpType::Move, @@ -289,6 +308,7 @@ impl FloatNode { } } self.position.set(Rect::new(x1, y1, x2, y2).unwrap()); + self.state.damage(); self.schedule_layout(); return; } @@ -334,7 +354,7 @@ impl FloatNode { seat_state.op_type = op_type; if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) { if seat_state.target { - seat.pointer_cursor().set_known(new_cursor); + cursor.set_known(new_cursor); } } } @@ -398,6 +418,77 @@ impl FloatNode { self.state.tree_changed(); } } + + fn button( + self: Rc, + id: CursorType, + cursor: &CursorUser, + seat: &Rc, + time_usec: u64, + pressed: bool, + ) { + let mut cursors = self.cursors.borrow_mut(); + let cursor_data = match cursors.get_mut(&id) { + Some(s) => s, + _ => return, + }; + if !cursor_data.op_active { + if !pressed { + return; + } + if cursor_data.op_type == OpType::Move { + if let Some(tl) = self.child.get() { + tl.node_do_focus(seat, Direction::Unspecified); + } + } + if cursor_data.double_click_state.click( + &self.state, + time_usec, + cursor_data.x, + cursor_data.y, + ) && cursor_data.op_type == OpType::Move + { + if let Some(tl) = self.child.get() { + drop(cursors); + seat.set_tl_floating(tl, false); + return; + } + } + cursor_data.op_active = true; + let pos = self.position.get(); + match cursor_data.op_type { + OpType::Move => { + self.restack(); + cursor_data.dist_hor = cursor_data.x; + cursor_data.dist_ver = cursor_data.y; + } + OpType::ResizeLeft => cursor_data.dist_hor = cursor_data.x, + OpType::ResizeTop => cursor_data.dist_ver = cursor_data.y, + OpType::ResizeRight => cursor_data.dist_hor = pos.width() - cursor_data.x, + OpType::ResizeBottom => cursor_data.dist_ver = pos.height() - cursor_data.y, + OpType::ResizeTopLeft => { + cursor_data.dist_hor = cursor_data.x; + cursor_data.dist_ver = cursor_data.y; + } + OpType::ResizeTopRight => { + cursor_data.dist_hor = pos.width() - cursor_data.x; + cursor_data.dist_ver = cursor_data.y; + } + OpType::ResizeBottomLeft => { + cursor_data.dist_hor = cursor_data.x; + cursor_data.dist_ver = pos.height() - cursor_data.y; + } + OpType::ResizeBottomRight => { + cursor_data.dist_hor = pos.width() - cursor_data.x; + cursor_data.dist_ver = pos.height() - cursor_data.y; + } + } + } else if !pressed { + cursor_data.op_active = false; + let ws = cursor.output().ensure_workspace(); + self.set_workspace(&ws); + } + } } impl Debug for FloatNode { @@ -487,89 +578,89 @@ impl Node for FloatNode { if button != BTN_LEFT { return; } - let mut seat_datas = self.seats.borrow_mut(); - let seat_data = match seat_datas.get_mut(&seat.id()) { - Some(s) => s, - _ => return, - }; - if !seat_data.op_active { - if state != KeyState::Pressed { - return; - } - if seat_data.op_type == OpType::Move { - if let Some(tl) = self.child.get() { - tl.node_do_focus(seat, Direction::Unspecified); - } - } - if seat_data - .double_click_state - .click(&self.state, time_usec, seat_data.x, seat_data.y) - && seat_data.op_type == OpType::Move - { - if let Some(tl) = self.child.get() { - drop(seat_datas); - seat.set_tl_floating(tl, false); - return; - } - } - seat_data.op_active = true; - let pos = self.position.get(); - match seat_data.op_type { - OpType::Move => { - self.restack(); - seat_data.dist_hor = seat_data.x; - seat_data.dist_ver = seat_data.y; - } - OpType::ResizeLeft => seat_data.dist_hor = seat_data.x, - OpType::ResizeTop => seat_data.dist_ver = seat_data.y, - OpType::ResizeRight => seat_data.dist_hor = pos.width() - seat_data.x, - OpType::ResizeBottom => seat_data.dist_ver = pos.height() - seat_data.y, - OpType::ResizeTopLeft => { - seat_data.dist_hor = seat_data.x; - seat_data.dist_ver = seat_data.y; - } - OpType::ResizeTopRight => { - seat_data.dist_hor = pos.width() - seat_data.x; - seat_data.dist_ver = seat_data.y; - } - OpType::ResizeBottomLeft => { - seat_data.dist_hor = seat_data.x; - seat_data.dist_ver = pos.height() - seat_data.y; - } - OpType::ResizeBottomRight => { - seat_data.dist_hor = pos.width() - seat_data.x; - seat_data.dist_ver = pos.height() - seat_data.y; - } - } - } else if state == KeyState::Released { - seat_data.op_active = false; - let ws = seat.get_output().ensure_workspace(); - self.set_workspace(&ws); - } + self.button( + CursorType::Seat(seat.id()), + seat.pointer_cursor(), + seat, + time_usec, + state == KeyState::Pressed, + ); } fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { - self.pointer_move(seat, x.round_down(), y.round_down()); + self.pointer_move( + CursorType::Seat(seat.id()), + seat.pointer_cursor(), + x, + y, + false, + ); } fn node_on_pointer_unfocus(&self, seat: &Rc) { - let mut seats = self.seats.borrow_mut(); - if let Some(seat_state) = seats.get_mut(&seat.id()) { + let mut cursors = self.cursors.borrow_mut(); + let id = CursorType::Seat(seat.id()); + if let Some(seat_state) = cursors.get_mut(&id) { seat_state.target = false; } } fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("float focus"); - let mut seats = self.seats.borrow_mut(); - if let Some(seat_state) = seats.get_mut(&seat.id()) { + let mut cursors = self.cursors.borrow_mut(); + let id = CursorType::Seat(seat.id()); + if let Some(seat_state) = cursors.get_mut(&id) { seat_state.target = true; seat.pointer_cursor().set_known(seat_state.cursor); } } fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { - self.pointer_move(seat, x.round_down(), y.round_down()); + self.pointer_move( + CursorType::Seat(seat.id()), + seat.pointer_cursor(), + x, + y, + false, + ); + } + + fn node_on_tablet_tool_leave(&self, tool: &Rc, _time_usec: u64) { + let id = CursorType::TabletTool(tool.id); + self.cursors.borrow_mut().remove(&id); + } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + x: Fixed, + y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default); + self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, true); + } + + fn node_on_tablet_tool_apply_changes( + self: Rc, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, false); + if let Some(changes) = changes { + if let Some(pressed) = changes.down { + self.button( + CursorType::TabletTool(tool.id), + tool.cursor(), + tool.seat(), + time_usec, + pressed, + ); + } + } } fn node_into_float(self: Rc) -> Option> { diff --git a/src/tree/output.rs b/src/tree/output.rs index dfeac4ef..8c009de7 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -11,8 +11,10 @@ use { wl_buffer::WlBufferStorage, wl_output::WlOutputGlobal, wl_seat::{ - collect_kb_foci2, wl_pointer::PendingScroll, NodeSeatState, SeatId, WlSeatGlobal, - BTN_LEFT, + collect_kb_foci2, + tablet::{TabletTool, TabletToolChanges, TabletToolId}, + wl_pointer::PendingScroll, + NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, }, wl_surface::{ ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, @@ -63,7 +65,7 @@ pub struct OutputNode { pub is_dummy: bool, pub status: CloneCell>, pub scroll: Scroller, - pub pointer_positions: CopyHashMap, + pub pointer_positions: CopyHashMap, pub lock_surface: CloneCell>>, pub hardware_cursor: CloneCell>>, pub hardware_cursor_needs_render: Cell, @@ -72,6 +74,12 @@ pub struct OutputNode { pub screencopies: CopyHashMap<(ClientId, ZwlrScreencopyFrameV1Id), Rc>, } +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum PointerType { + Seat(SeatId), + TabletTool(TabletToolId), +} + pub async fn output_render_data(state: Rc) { loop { let container = state.pending_output_render_data.pop().await; @@ -593,8 +601,9 @@ impl OutputNode { self.schedule_update_render_data(); } - fn pointer_move(self: &Rc, seat: &Rc, x: i32, y: i32) { - self.pointer_positions.set(seat.id(), (x, y)); + fn pointer_move(self: &Rc, id: PointerType, x: Fixed, y: Fixed) { + self.pointer_positions + .set(id, (x.round_down(), y.round_down())); } pub fn has_fullscreen(&self) -> bool { @@ -641,6 +650,29 @@ impl OutputNode { set_layer_visible!(self.layers[2], visible); set_layer_visible!(self.layers[3], visible); } + + fn button(self: Rc, id: PointerType) { + let (x, y) = match self.pointer_positions.get(&id) { + Some(p) => p, + _ => return, + }; + if y >= self.state.theme.sizes.title_height.get() { + return; + } + let ws = 'ws: { + let rd = self.render_data.borrow_mut(); + for title in &rd.titles { + if x >= title.x1 && x < title.x2 { + break 'ws title.ws.clone(); + } + } + return; + }; + self.show_workspace(&ws); + ws.flush_jay_workspaces(); + self.schedule_update_render_data(); + self.state.tree_changed(); + } } pub struct OutputTitle { @@ -844,26 +876,7 @@ impl Node for OutputNode { if state != KeyState::Pressed || button != BTN_LEFT { return; } - let (x, y) = match self.pointer_positions.get(&seat.id()) { - Some(p) => p, - _ => return, - }; - if y >= self.state.theme.sizes.title_height.get() { - return; - } - let ws = 'ws: { - let rd = self.render_data.borrow_mut(); - for title in &rd.titles { - if x >= title.x1 && x < title.x2 { - break 'ws title.ws.clone(); - } - } - return; - }; - self.show_workspace(&ws); - ws.flush_jay_workspaces(); - self.schedule_update_render_data(); - self.state.tree_changed(); + self.button(PointerType::Seat(seat.id())); } fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { @@ -905,7 +918,7 @@ impl Node for OutputNode { } fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { - self.pointer_move(seat, x.round_down(), y.round_down()); + self.pointer_move(PointerType::Seat(seat.id()), x, y); } fn node_on_pointer_focus(&self, seat: &Rc) { @@ -914,7 +927,40 @@ impl Node for OutputNode { } fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { - self.pointer_move(seat, x.round_down(), y.round_down()); + self.pointer_move(PointerType::Seat(seat.id()), x, y); + } + + fn node_on_tablet_tool_leave(&self, tool: &Rc, _time_usec: u64) { + self.pointer_positions + .remove(&PointerType::TabletTool(tool.id)); + } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + x: Fixed, + y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default); + self.pointer_move(PointerType::TabletTool(tool.id), x, y); + } + + fn node_on_tablet_tool_apply_changes( + self: Rc, + tool: &Rc, + _time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + let id = PointerType::TabletTool(tool.id); + self.pointer_move(id, x, y); + if let Some(changes) = changes { + if changes.down == Some(true) { + self.button(id); + } + } } } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 9eac5444..f4f91d69 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -2,10 +2,11 @@ use { crate::{ client::ClientId, cursor::KnownCursor, + fixed::Fixed, ifs::{ jay_workspace::JayWorkspace, wl_output::OutputId, - wl_seat::{NodeSeatState, WlSeatGlobal}, + wl_seat::{tablet::TabletTool, NodeSeatState, WlSeatGlobal}, wl_surface::WlSurface, }, rect::Rect, @@ -277,6 +278,16 @@ impl Node for WorkspaceNode { seat.pointer_cursor().set_known(KnownCursor::Default); } + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + _time_usec: u64, + _x: Fixed, + _y: Fixed, + ) { + tool.cursor().set_known(KnownCursor::Default) + } + fn node_into_workspace(self: Rc) -> Option> { Some(self.clone()) } diff --git a/src/utils.rs b/src/utils.rs index 2d198bb7..8531378c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -31,6 +31,7 @@ pub mod opaque_cell; pub mod option_ext; pub mod oserror; pub mod page_size; +pub mod pending_serial; pub mod process_name; pub mod ptr_ext; pub mod queue; diff --git a/src/utils/bindings.rs b/src/utils/bindings.rs index c939f75b..899246ae 100644 --- a/src/utils/bindings.rs +++ b/src/utils/bindings.rs @@ -5,7 +5,11 @@ use { utils::copyhashmap::{CopyHashMap, Locked}, }, ahash::AHashMap, - std::{cell::RefCell, collections::hash_map::Entry, rc::Rc}, + std::{ + cell::{Ref, RefCell}, + collections::hash_map::Entry, + rc::Rc, + }, }; pub struct Bindings

{ @@ -84,4 +88,8 @@ impl PerClientBindings

{ } } } + + pub fn borrow(&self) -> Ref>>> { + self.bindings.borrow() + } } diff --git a/src/utils/copyhashmap.rs b/src/utils/copyhashmap.rs index a83140ff..48cc2f1a 100644 --- a/src/utils/copyhashmap.rs +++ b/src/utils/copyhashmap.rs @@ -66,7 +66,7 @@ impl CopyHashMap { unsafe { self.map.get().deref().contains_key(k) } } - pub fn lock(&self) -> Locked { + pub fn lock(&self) -> Locked<'_, K, V> { Locked { source: self, map: self.clear(), diff --git a/src/utils/pending_serial.rs b/src/utils/pending_serial.rs new file mode 100644 index 00000000..d7890014 --- /dev/null +++ b/src/utils/pending_serial.rs @@ -0,0 +1,19 @@ +use crate::client::Client; + +pub struct PendingSerial<'a> { + serial: Option, + client: &'a Client, +} + +impl<'a> PendingSerial<'a> { + pub fn new(client: &'a Client) -> Self { + Self { + serial: None, + client, + } + } + + pub fn get(&mut self) -> u32 { + *self.serial.get_or_insert_with(|| self.client.next_serial()) + } +} diff --git a/wire/jay_seat_events.txt b/wire/jay_seat_events.txt index 06bbdbb3..f7cb0163 100644 --- a/wire/jay_seat_events.txt +++ b/wire/jay_seat_events.txt @@ -132,3 +132,110 @@ event switch_event { input_device: u32, event: u32, } + +event tablet_tool_proximity_in { +} + +event tablet_tool_proximity_out { +} + +event tablet_tool_down { +} + +event tablet_tool_up { +} + +event tablet_tool_motion { + x: fixed, + y: fixed, +} + +event tablet_tool_pressure { + pressure: pod(f64), +} + +event tablet_tool_distance { + distance: pod(f64), +} + +event tablet_tool_tilt { + tilt_x: pod(f64), + tilt_y: pod(f64), +} + +event tablet_tool_rotation { + degrees: pod(f64), +} + +event tablet_tool_slider { + position: pod(f64), +} + +event tablet_tool_wheel { + degrees: pod(f64), + clicks: i32, +} + +event tablet_tool_button { + button: u32, + state: u32, +} + +event tablet_tool_frame { + seat: u32, + time_usec: pod(u64), + input_device: u32, + tool: u32, +} + +event tablet_pad_mode_switch { + seat: u32, + time_usec: pod(u64), + input_device: u32, + group: u32, + mode: u32, +} + +event tablet_pad_button { + seat: u32, + time_usec: pod(u64), + input_device: u32, + button: u32, + state: u32, +} + +event tablet_pad_strip_source { + source: u32, +} + +event tablet_pad_strip_position { + position: pod(f64), +} + +event tablet_pad_strip_stop { +} + +event tablet_pad_strip_frame { + seat: u32, + time_usec: pod(u64), + input_device: u32, + strip: u32, +} + +event tablet_pad_ring_source { + source: u32, +} + +event tablet_pad_ring_angle { + degrees: pod(f64), +} + +event tablet_pad_ring_stop { +} + +event tablet_pad_ring_frame { + seat: u32, + time_usec: pod(u64), + input_device: u32, + ring: u32, +} diff --git a/wire/zwp_tablet_manager_v2.txt b/wire/zwp_tablet_manager_v2.txt new file mode 100644 index 00000000..015a3da0 --- /dev/null +++ b/wire/zwp_tablet_manager_v2.txt @@ -0,0 +1,7 @@ +request get_tablet_seat { + tablet_seat: id(zwp_tablet_seat_v2), + seat: id(wl_seat), +} + +request destroy { +} diff --git a/wire/zwp_tablet_pad_group_v2.txt b/wire/zwp_tablet_pad_group_v2.txt new file mode 100644 index 00000000..320e165a --- /dev/null +++ b/wire/zwp_tablet_pad_group_v2.txt @@ -0,0 +1,27 @@ +request destroy { +} + +event buttons { + buttons: array(u32), +} + +event ring { + ring: id(zwp_tablet_pad_ring_v2), +} + +event strip { + strip: id(zwp_tablet_pad_strip_v2), +} + +event modes { + modes: u32, +} + +event done { +} + +event mode_switch { + time: u32, + serial: u32, + mode: u32, +} diff --git a/wire/zwp_tablet_pad_ring_v2.txt b/wire/zwp_tablet_pad_ring_v2.txt new file mode 100644 index 00000000..9013350e --- /dev/null +++ b/wire/zwp_tablet_pad_ring_v2.txt @@ -0,0 +1,22 @@ +request set_feedback { + description: str, + serial: u32, +} + +request destroy { +} + +event source { + source: u32, +} + +event angle { + degrees: fixed, +} + +event stop { +} + +event frame { + time: u32, +} diff --git a/wire/zwp_tablet_pad_strip_v2.txt b/wire/zwp_tablet_pad_strip_v2.txt new file mode 100644 index 00000000..222efda0 --- /dev/null +++ b/wire/zwp_tablet_pad_strip_v2.txt @@ -0,0 +1,22 @@ +request set_feedback { + description: str, + serial: u32, +} + +request destroy { +} + +event source { + source: u32, +} + +event position { + position: u32, +} + +event stop { +} + +event frame { + time: u32, +} diff --git a/wire/zwp_tablet_pad_v2.txt b/wire/zwp_tablet_pad_v2.txt new file mode 100644 index 00000000..08770652 --- /dev/null +++ b/wire/zwp_tablet_pad_v2.txt @@ -0,0 +1,43 @@ +request set_feedback { + button: u32, + description: str, + serial: u32, +} + +request destroy { +} + +event group { + pad_group: id(zwp_tablet_pad_group_v2), +} + +event path { + path: str, +} + +event buttons { + buttons: u32, +} + +event done { +} + +event button { + time: u32, + button: u32, + state: u32, +} + +event enter { + serial: u32, + tablet: id(zwp_tablet_v2), + surface: id(wl_surface), +} + +event leave { + serial: u32, + surface: id(wl_surface), +} + +event removed { +} diff --git a/wire/zwp_tablet_seat_v2.txt b/wire/zwp_tablet_seat_v2.txt new file mode 100644 index 00000000..bbc4e331 --- /dev/null +++ b/wire/zwp_tablet_seat_v2.txt @@ -0,0 +1,14 @@ +request destroy { +} + +event tablet_added { + id: id(zwp_tablet_v2), +} + +event tool_added { + id: id(zwp_tablet_tool_v2), +} + +event pad_added { + id: id(zwp_tablet_pad_v2), +} diff --git a/wire/zwp_tablet_tool_v2.txt b/wire/zwp_tablet_tool_v2.txt index 67f0da77..9351dc80 100644 --- a/wire/zwp_tablet_tool_v2.txt +++ b/wire/zwp_tablet_tool_v2.txt @@ -1,5 +1,3 @@ -# requests - request set_cursor { serial: u32, surface: id(wl_surface), @@ -9,3 +7,84 @@ request set_cursor { request destroy { } + +event type { + tool_type: u32, +} + +event hardware_serial { + hardware_serial_hi: u32, + hardware_serial_lo: u32, +} + +event hardware_id_wacom { + hardware_id_hi: u32, + hardware_id_lo: u32, +} + +event capability { + capability: u32, +} + +event done { +} + +event removed { +} + +event proximity_in { + serial: u32, + tablet: id(zwp_tablet_v2), + surface: id(wl_surface), +} + +event proximity_out { +} + +event down { + serial: u32, +} + +event up { +} + +event motion { + x: fixed, + y: fixed, +} + +event pressure { + pressure: u32, +} + +event distance { + distance: u32, +} + +event tilt { + tilt_x: fixed, + tilt_y: fixed, +} + +event rotation { + degrees: fixed, +} + +event slider { + position: i32, +} + +event wheel { + degrees: fixed, + clicks: i32, +} + +event button { + serial: u32, + button: u32, + state: u32, +} + +event frame { + time: u32, +} diff --git a/wire/zwp_tablet_v2.txt b/wire/zwp_tablet_v2.txt new file mode 100644 index 00000000..5c758d0d --- /dev/null +++ b/wire/zwp_tablet_v2.txt @@ -0,0 +1,21 @@ +request destroy { +} + +event name { + name: str, +} + +event id { + vid: u32, + pid: u32, +} + +event path { + path: str, +} + +event done { +} + +event removed { +}