Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# (Unreleased)
* New variant `Backend::XorgXinput2`
* New default feature `xorg-xinput2`
22 changes: 5 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,22 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
# Common deps.
bitflags = "2.4.2"
enum_dispatch = "0.3.12"
raw-window-handle = "0.6.0"
strum = { version = "0.26.2", features = ["derive"] }
thiserror = "1.0.58"
smallvec = "1.13.1"

# Wayland `tablet_unstable_v2` deps.
# Crazy `cfg` stolen verbatim from winit's Cargo.toml as I assume they have more wisdom than I
[target.'cfg(any(docsrs, all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos")))))'.dependencies]
# Wayland `tablet_unstable_v2` deps.
wayland-backend = { version = "0.3.3", features = ["client_system"], optional = true }
wayland-client = { version = "0.31.2", optional = true }
wayland-protocols = { version = "0.31.2", features = ["client", "unstable"], optional = true }

# Xorg `xinput2` deps. We use x11rb RustConnection as xcb doesn't support xinput2 out-of-box
# and xlib cannot be soundly used due to its use of global variables.
x11rb = { version = "0.13.1", features = ["xinput", "extra-traits"], optional = true }

# Windows Ink `RealTimeStylus`
[target.'cfg(any(docsrs, target_os = "windows"))'.dependencies.windows]
version = "0.54.0"
Expand All @@ -62,12 +65,14 @@ features = [
]

[features]
default = ["wayland-tablet-unstable-v2", "windows-ink"]
default = ["wayland-tablet-unstable-v2", "xorg-xinput2", "windows-ink"]

# Wayland `tablet_unstable_v2` support
# Note: "unstable" here refers to the protocol itself, not to the stability of it's integration into this crate!
wayland-tablet-unstable-v2 = ["dep:wayland-backend", "dep:wayland-client", "dep:wayland-protocols"]

xorg-xinput2 = ["dep:x11rb"]

# Windows Ink `RealTimeStylus` support
windows-ink = ["dep:windows"]

Expand Down
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ fn main() {
// Wayland tablet is requested and available. Adapted from winit.
// lonngg cfg = The feature is on, and (docs or (supported platform and not unsupported platform))
wl_tablet: { all(feature = "wayland-tablet-unstable-v2", any(docsrs, all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))) },
// Same as above but for xlib `xinput2` support.
xinput2: { all(feature = "xorg-xinput2", any(docsrs, all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))) },
// Ink RealTimeStylus is requested and available
ink_rts: { all(feature = "windows-ink", any(docsrs, target_os = "windows")) },
}
Expand Down
16 changes: 8 additions & 8 deletions examples/eframe-viewer/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,12 @@ impl EventFilter {
},
Event::Tablet { .. } => true,
Event::Pad { event, .. } => match event {
PadEvent::Group { event, .. } => match event {
PadGroupEvent::Ring { event, .. } | PadGroupEvent::Strip { event, .. } => {
match event {
TouchStripEvent::Frame(..) => self.frames,
TouchStripEvent::Pose(..) => self.poses,
_ => true,
}
}
PadEvent::Group {
event: PadGroupEvent::Ring { event, .. } | PadGroupEvent::Strip { event, .. },
..
} => match event {
TouchStripEvent::Frame(..) => self.frames,
TouchStripEvent::Pose(..) => self.poses,
_ => true,
},
_ => true,
Expand Down Expand Up @@ -215,6 +213,8 @@ impl eframe::App for Viewer {
});
});

// Set the scale factor. Notably, this does *not* include the window's scale factor!
self.state.egui_scale_factor = ctx.zoom_factor();
// update the state with the new events!
self.state.extend(events);

Expand Down
20 changes: 18 additions & 2 deletions examples/eframe-viewer/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ fn radial_delta(from: f32, to: f32) -> f32 {
nearest_delta.unwrap()
}

#[derive(Default)]
pub struct State {
/// State of any `In` tools, removed when they go `Out`.
/// Note that this isn't a singleton! Several tools can be active at the same time
Expand All @@ -49,6 +48,20 @@ pub struct State {
strips: collections::HashMap<pad::strip::ID, f32>,
/// The position of a virtual knob to show off slider/ring states.
knob_pos: f32,
/// Egui's scale factor. octotablet gives us positions in logical window space, but we need
/// to draw in egui's coordinate space.
pub egui_scale_factor: f32,
}
impl Default for State {
fn default() -> Self {
Self {
tools: collections::HashMap::new(),
rings: collections::HashMap::new(),
strips: collections::HashMap::new(),
knob_pos: 0.0,
egui_scale_factor: 1.0,
}
}
}
impl<'a> Extend<octotablet::events::Event<'a>> for State {
fn extend<T: IntoIterator<Item = octotablet::events::Event<'a>>>(&mut self, iter: T) {
Expand Down Expand Up @@ -80,10 +93,13 @@ impl<'a> Extend<octotablet::events::Event<'a>> for State {
};
tool.down = false;
}
ToolEvent::Pose(pose) => {
ToolEvent::Pose(mut pose) => {
let Some(tool) = self.tools.get_mut(&tool.id()) else {
continue;
};
// Remap from logical window pixels to egui points.
pose.position[0] /= self.egui_scale_factor;
pose.position[1] /= self.egui_scale_factor;
// Limited size circular buf - pop to make room if full.
if tool.path.len() == PATH_LEN {
tool.path.pop_front();
Expand Down
42 changes: 42 additions & 0 deletions examples/sdl2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ mod rwh_bridge {
std::ptr::NonNull::new(display).expect("null wayland handle"),
)
.into(),
// Xlib...
rwh_05::RawDisplayHandle::Xlib(rwh_05::XlibDisplayHandle {
display,
screen,
..
}) => raw_window_handle::XlibDisplayHandle::new(
std::ptr::NonNull::new(display),
screen,
)
.into(),
// Xcb...
rwh_05::RawDisplayHandle::Xcb(rwh_05::XcbDisplayHandle {
connection,
screen,
..
}) => raw_window_handle::XcbDisplayHandle::new(
std::ptr::NonNull::new(connection),
screen,
)
.into(),
// Windows 32... Has no display handle!
rwh_05::RawDisplayHandle::Windows(_) => {
raw_window_handle::WindowsDisplayHandle::new().into()
Expand All @@ -49,6 +69,28 @@ mod rwh_bridge {
std::ptr::NonNull::new(surface).expect("null wayland handle"),
)
.into(),
// Xlib...
rwh_05::RawWindowHandle::Xlib(rwh_05::XlibWindowHandle {
window,
visual_id,
..
}) => {
let mut rwh = raw_window_handle::XlibWindowHandle::new(window);
rwh.visual_id = visual_id;
rwh
}
.into(),
// Xcb...
rwh_05::RawWindowHandle::Xcb(rwh_05::XcbWindowHandle {
window, visual_id, ..
}) => {
let mut rwh = raw_window_handle::XcbWindowHandle::new(
std::num::NonZeroU32::new(window).expect("null xcb window"),
);
rwh.visual_id = std::num::NonZeroU32::new(visual_id);
rwh
}
.into(),
// Windows 32...
rwh_05::RawWindowHandle::Win32(rwh_05::Win32WindowHandle {
hinstance,
Expand Down
11 changes: 10 additions & 1 deletion examples/winit-paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ impl Painter {
}
// Positioning data, continue drawing!
ToolEvent::Pose(mut pose) => {
println!("{:?} - x {}", tool.name, pose.position[0]);
// If there's a painter, paint on it!
// If not, we haven't hit the `Down` event yet.
if let Some(painter) = self.tools.get_mut(&tool.id()) {
Expand Down Expand Up @@ -199,6 +200,7 @@ fn main() {
let event_loop = winit::event_loop::EventLoopBuilder::<()>::default()
.build()
.expect("start event loop");
event_loop.listen_device_events(winit::event_loop::DeviceEvents::Always);
let window = std::sync::Arc::new(
winit::window::WindowBuilder::default()
.with_inner_size(PhysicalSize::new(512u32, 512u32))
Expand All @@ -209,10 +211,17 @@ fn main() {

// To allow us to draw on the screen without pulling in a whole GPU package,
// we use `softbuffer` for presentation and `tiny-skia` for drawing
let mut pixmap = tiny_skia::Pixmap::new(512, 512).unwrap();
let mut pixmap =
tiny_skia::Pixmap::new(window.inner_size().width, window.inner_size().height).unwrap();
let softbuffer = softbuffer::Context::new(window.as_ref()).expect("init softbuffer");
let mut surface =
softbuffer::Surface::new(&softbuffer, &window).expect("make presentation surface");
surface
.resize(
window.inner_size().width.try_into().unwrap(),
window.inner_size().height.try_into().unwrap(),
)
.unwrap();

// Fetch the tablets, using our window's handle for access.
// Since we `Arc'd` our window, we get the safety of `build_shared`. Where this is not possible,
Expand Down
35 changes: 35 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,41 @@ impl Builder {
},
))
}
#[cfg(xinput2)]
raw_window_handle::RawDisplayHandle::Xlib(_)
| raw_window_handle::RawDisplayHandle::Xcb(_) => {
// We don't actually care about the dispaly handle for xlib or xcb! We need the *window* id instead.
// (We manage our own connection and snoop the window events from there.)
// As such, we accept both Xlib and Xcb, since we only care about the numeric window ID which is *server* defined,
// not client library defined.
let window = match rwh.window_handle()?.as_raw() {
raw_window_handle::RawWindowHandle::Xlib(
raw_window_handle::XlibWindowHandle { window, .. },
) => {
// u64 -> NonZeroU32
u32::try_from(window)
.ok()
.and_then(|window| window.try_into().ok())
}
raw_window_handle::RawWindowHandle::Xcb(
raw_window_handle::XcbWindowHandle { window, .. },
) => Some(window),
// The display handle said it was one of these!!
_ => None,
};

let Some(window) = window else {
return Err(BuildError::HandleError(
raw_window_handle::HandleError::Unavailable,
));
};

Ok(crate::platform::PlatformManager::XInput2(
// Safety: forwarded to this fn's contract.
// Fixme: unwrap.
unsafe { crate::platform::xinput2::Manager::build_window(self, window) },
))
}
#[cfg(ink_rts)]
raw_window_handle::RawDisplayHandle::Windows(_) => {
// We need the window handle for this :V
Expand Down
2 changes: 1 addition & 1 deletion src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl<'manager> EventIterator<'manager> {
.tablets()
.iter()
.find(|t| t.internal_id == tablet)
.unwrap(),
.ok_or(())?,
},
RawTool::Down => ToolEvent::Down,
RawTool::Button { button_id, pressed } => ToolEvent::Button {
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub enum Backend {
///
/// **Note**: "unstable" here refers to the protocol itself, not to the stability of its integration into this crate!
WaylandTabletUnstableV2,
/// [`XInput2`](https://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt)
XorgXInput2,
/// [`RealTimeStylus`](https://learn.microsoft.com/en-us/windows/win32/tablet/realtimestylus-reference)
///
/// The use of this interface avoids some common problems with the use of Windows Ink in drawing applications,
Expand Down Expand Up @@ -124,6 +126,8 @@ impl Manager {
match self.internal {
#[cfg(wl_tablet)]
platform::PlatformManager::Wayland(_) => Backend::WaylandTabletUnstableV2,
#[cfg(xinput2)]
platform::PlatformManager::XInput2(_) => Backend::XorgXInput2,
#[cfg(ink_rts)]
platform::PlatformManager::Ink(_) => Backend::WindowsInkRealTimeStylus,
}
Expand Down
Loading