Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cc89205
Support most of tablet-v2
chris-morgan Mar 16, 2025
8b7e66c
[WIP] Expand the tablet example for pseudo-drawing
chris-morgan Mar 18, 2025
0f46927
One approach to collecting tablet metadata
chris-morgan Mar 19, 2025
3726fe1
Tablet info events: new approach, always collect
chris-morgan Mar 19, 2025
46935e5
Naming: “description”
chris-morgan Mar 19, 2025
23a61b9
Restructure code somewhat
chris-morgan Mar 19, 2025
bbe0c36
Try a more radical module and naming structure
chris-morgan Mar 19, 2025
c078af0
Fiddle with tablet_tool::InitEvent naming
chris-morgan Mar 19, 2025
bb8f021
Accumulate description for tablet tool handlers
chris-morgan Mar 19, 2025
bef0765
Use bitflags for tool capabilities
chris-morgan Mar 19, 2025
0a182c1
Quite a bit of poorly-separated work
chris-morgan Mar 26, 2025
00824ba
Remove dead code
chris-morgan Mar 26, 2025
399941c
Revise some tablet naming
chris-morgan Mar 26, 2025
e23dea0
Update some docs, remove obsolete bits
chris-morgan Mar 26, 2025
908437f
Refine how Tools is exposed
chris-morgan Mar 26, 2025
e2048b2
Make tablet seat handler methods optional
chris-morgan Mar 26, 2025
f7e34e0
(Advice sought) Remove seat from tablet seat handler methods
chris-morgan Mar 26, 2025
93afc63
Document that no other axis methods are warranted
chris-morgan Mar 28, 2025
ac0b3de
Remove more dead code
chris-morgan Mar 29, 2025
36aaf47
More shuffling tool stuff around, for the better
chris-morgan Mar 29, 2025
107f662
Add cursors to the tablet tool example
chris-morgan Mar 29, 2025
0e92ecd
Make TabletManager largely implicit, via SeatState
chris-morgan Mar 29, 2025
34a9a20
Refactor manager globals, expose cursor shape manager
chris-morgan Mar 29, 2025
522845e
Tidy up example a bit, improve logging
chris-morgan Mar 29, 2025
20875b3
Support cursor shapes on tablet tools, a bit
chris-morgan Mar 29, 2025
155147b
Refactor SeatState::new for subjective prettiness
chris-morgan Mar 29, 2025
6ea3530
Make {tablet, tablet_tool}::Info non-exhaustive
chris-morgan Mar 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
746 changes: 746 additions & 0 deletions examples/tablet.rs

Large diffs are not rendered by default.

151 changes: 98 additions & 53 deletions src/seat/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::{
fmt::{self, Display, Formatter},
slice,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};

use crate::reexports::client::{
globals::{Global, GlobalList},
globals::{BindError, Global, GlobalList},
protocol::{wl_pointer, wl_registry::WlRegistry, wl_seat, wl_shm, wl_surface, wl_touch},
Connection, Dispatch, Proxy, QueueHandle,
};
Expand All @@ -28,11 +27,22 @@ pub mod pointer;
pub mod pointer_constraints;
pub mod relative_pointer;
pub mod touch;
mod tablet_manager;
pub use tablet_manager::TabletManager;
pub mod tablet_seat;
pub mod tablet;
pub mod tablet_tool;
pub mod tablet_pad;

use pointer::cursor_shape::CursorShapeManager;
use pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes};
use touch::{TouchData, TouchDataExt, TouchHandler};

use wayland_protocols::wp::tablet::zv2::client::{
zwp_tablet_manager_v2::ZwpTabletManagerV2,
zwp_tablet_seat_v2::ZwpTabletSeatV2,
};

#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Capability {
Expand Down Expand Up @@ -68,48 +78,80 @@ pub enum SeatError {
pub struct SeatState {
// (name, seat)
seats: Vec<SeatInner>,
cursor_shape_manager_state: CursorShapeManagerState,
cursor_shape_manager_state: ManagerState<CursorShapeManager>,
tablet_manager_state: ManagerState<TabletManager>,
}

#[derive(Debug)]
enum CursorShapeManagerState {
NotPresent,
enum ManagerState<T> {
Failed(BindError),
Pending { registry: WlRegistry, global: Global },
Bound(CursorShapeManager),
Bound(T),
}

impl<T> ManagerState<T> {
fn bind<I, D>(&mut self, qh: &QueueHandle<D>, version: std::ops::RangeInclusive<u32>, f: impl FnOnce(I) -> T) -> Result<&T, BindError>
where
I: Proxy + 'static,
D: Dispatch<I, GlobalData> + 'static,
{
*self = match std::mem::replace(self, ManagerState::Failed(BindError::NotPresent)) {
ManagerState::Pending { registry, global } => {
match crate::registry::bind_one(&registry, &[global], qh, version, GlobalData) {
Ok(bound) => ManagerState::Bound(f(bound)),
Err(e) => ManagerState::Failed(e),
}
}
other => other,
};

match self {
ManagerState::Bound(bound) => Ok(bound),
// FIXME: make BindError impl Clone
//ManagerState::Failed(e) => Err(e.clone()),
ManagerState::Failed(BindError::UnsupportedVersion) => Err(BindError::UnsupportedVersion),
ManagerState::Failed(BindError::NotPresent) => Err(BindError::NotPresent),
ManagerState::Pending { .. } => unreachable!(),
}
}
}

impl SeatState {
pub fn new<D: Dispatch<wl_seat::WlSeat, SeatData> + 'static>(
global_list: &GlobalList,
qh: &QueueHandle<D>,
) -> SeatState {
let (seats, cursor_shape_manager) = global_list.contents().with_list(|globals| {
let global = globals
.iter()
.find(|global| global.interface == WpCursorShapeManagerV1::interface().name)
.map(|global| CursorShapeManagerState::Pending {
registry: global_list.registry().clone(),
global: global.clone(),
})
.unwrap_or(CursorShapeManagerState::NotPresent);

(
crate::registry::bind_all(global_list.registry(), globals, qh, 1..=10, |id| {
SeatData {
has_keyboard: Arc::new(AtomicBool::new(false)),
has_pointer: Arc::new(AtomicBool::new(false)),
has_touch: Arc::new(AtomicBool::new(false)),
name: Arc::new(Mutex::new(None)),
id,
}
})
.expect("failed to bind global"),
global,
)
let mut cursor_shape_manager_state = ManagerState::Failed(BindError::NotPresent);
let mut tablet_manager_state = ManagerState::Failed(BindError::NotPresent);
let seats = global_list.contents().with_list(|globals| {
for global in globals {
if global.interface == WpCursorShapeManagerV1::interface().name {
cursor_shape_manager_state = ManagerState::Pending {
registry: global_list.registry().clone(),
global: global.clone(),
};
} else if global.interface == ZwpTabletManagerV2::interface().name {
tablet_manager_state = ManagerState::Pending {
registry: global_list.registry().clone(),
global: global.clone(),
};
}
}

crate::registry::bind_all(global_list.registry(), globals, qh, 1..=10, |id| {
SeatData {
has_keyboard: Arc::new(AtomicBool::new(false)),
has_pointer: Arc::new(AtomicBool::new(false)),
has_touch: Arc::new(AtomicBool::new(false)),
name: Arc::new(Mutex::new(None)),
id,
}
})
.expect("failed to bind global")
});

let mut state =
SeatState { seats: vec![], cursor_shape_manager_state: cursor_shape_manager };
SeatState { seats: vec![], cursor_shape_manager_state, tablet_manager_state };

for seat in seats {
let data = seat.data::<SeatData>().unwrap().clone();
Expand Down Expand Up @@ -248,29 +290,8 @@ impl SeatState {

let wl_ptr = seat.get_pointer(qh, pointer_data);

if let CursorShapeManagerState::Pending { registry, global } =
&self.cursor_shape_manager_state
{
self.cursor_shape_manager_state = match crate::registry::bind_one(
registry,
slice::from_ref(global),
qh,
1..=2,
GlobalData,
) {
Ok(bound) => {
CursorShapeManagerState::Bound(CursorShapeManager::from_existing(bound))
}
Err(_) => CursorShapeManagerState::NotPresent,
}
}

let shape_device =
if let CursorShapeManagerState::Bound(ref bound) = self.cursor_shape_manager_state {
Some(bound.get_shape_device(&wl_ptr, qh))
} else {
None
};
let shape_device = self.cursor_shape_manager(qh).ok()
.map(|csm| csm.get_shape_device(&wl_ptr, qh));

Ok(ThemedPointer {
themes: Arc::new(Mutex::new(Themes::new(theme))),
Expand All @@ -283,6 +304,16 @@ impl SeatState {
})
}

pub fn cursor_shape_manager<D>(&mut self, qh: &QueueHandle<D>)
-> Result<&CursorShapeManager, BindError>
where
D: Dispatch<WpCursorShapeManagerV1, GlobalData>,
D: Dispatch<WpCursorShapeDeviceV1, GlobalData>,
D: 'static,
{
self.cursor_shape_manager_state.bind(qh, 1..=2, CursorShapeManager::from_existing)
}

/// Creates a touch handle from a seat.
///
/// ## Errors
Expand Down Expand Up @@ -323,6 +354,20 @@ impl SeatState {

Ok(seat.get_touch(qh, udata))
}

/// Get a tablet seat, to gain access to tablets, tools, et cetera.
pub fn get_tablet_seat<D>(
&mut self,
qh: &QueueHandle<D>,
seat: &wl_seat::WlSeat,
) -> Result<ZwpTabletSeatV2, BindError>
where
D: Dispatch<ZwpTabletSeatV2, ()> + 'static,
D: Dispatch<ZwpTabletManagerV2, GlobalData> + 'static,
{
let tm = self.tablet_manager_state.bind(qh, 1..=1, TabletManager::from_existing)?;
Ok(tm.get_tablet_seat(seat, qh))
}
}

pub trait SeatHandler: Sized {
Expand Down
12 changes: 12 additions & 0 deletions src/seat/pointer/cursor_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use cursor_icon::CursorIcon;
use crate::globals::GlobalData;
use crate::reexports::client::globals::{BindError, GlobalList};
use crate::reexports::client::protocol::wl_pointer::WlPointer;
use crate::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_tool_v2::ZwpTabletToolV2;
use crate::reexports::client::{Connection, Dispatch, Proxy, QueueHandle};
use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
Expand Down Expand Up @@ -40,6 +41,17 @@ impl CursorShapeManager {
self.cursor_shape_manager.get_pointer(pointer, queue_handle, GlobalData)
}

pub fn get_shape_device_for_tablet_tool<State>(
&self,
tablet_tool: &ZwpTabletToolV2,
queue_handle: &QueueHandle<State>,
) -> WpCursorShapeDeviceV1
where
State: Dispatch<WpCursorShapeDeviceV1, GlobalData> + 'static,
{
self.cursor_shape_manager.get_tablet_tool_v2(tablet_tool, queue_handle, GlobalData)
}

pub fn inner(&self) -> &WpCursorShapeManagerV1 {
&self.cursor_shape_manager
}
Expand Down
90 changes: 90 additions & 0 deletions src/seat/tablet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::mem;
use std::sync::Mutex;

use wayland_client::{
Connection,
Dispatch,
QueueHandle,
};
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_v2::{self, ZwpTabletV2};

pub trait Handler: Sized {
/// This is fired at the time of the `zwp_tablet_v2.done` event,
/// and collects any preceding `name`, `id` and `path` events into an [`Info`].
fn info(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
tablet: &ZwpTabletV2,
info: Info,
);

/// Sent when the tablet has been removed from the system.
/// When a tablet is removed, some tools may be removed.
///
/// This method is responsible for running `tablet.destroy()`. ← TODO: true or not?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a good time to destroy the object, but I suppose it isn't strictly responsible for it.

fn removed(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
tablet: &ZwpTabletV2,
);
}

/// The description of a tablet device.
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Info {
/// The descriptive name of the tablet device.
pub name: Option<String>,
/// The USB vendor and product IDs for the tablet device.
pub id: Option<(u32, u32)>,
/// System-specific device paths for the tablet.
///
/// Path format is unspecified.
/// Clients must figure out what to do with them, if they care.
pub paths: Vec<String>,
}

#[doc(hidden)]
#[derive(Debug)]
pub struct Data {
info: Mutex<Info>,
}

impl Data {
pub fn new() -> Self {
Self { info: Default::default() }
}
}

impl<D> Dispatch<ZwpTabletV2, Data, D>
for super::TabletManager
where
D: Dispatch<ZwpTabletV2, Data> + Handler,
{
fn event(
data: &mut D,
tablet: &ZwpTabletV2,
event: zwp_tablet_v2::Event,
udata: &Data,
conn: &Connection,
qh: &QueueHandle<D>,
) {
let mut guard = udata.info.lock().unwrap();
match event {
zwp_tablet_v2::Event::Name { name } => guard.name = Some(name),
zwp_tablet_v2::Event::Id { vid, pid } => guard.id = Some((vid, pid)),
zwp_tablet_v2::Event::Path { path } => guard.paths.push(path),
zwp_tablet_v2::Event::Done => {
let info = mem::take(&mut *guard);
drop(guard);
data.info(conn, qh, tablet, info);
},
zwp_tablet_v2::Event::Removed => {
data.removed(conn, qh, tablet);
},
_ => unreachable!(),
}
}
}
Loading