82 lines
3.3 KiB
Rust
82 lines
3.3 KiB
Rust
//! A client of the 3Dconnexion driver (3DxWare) on macOS.
|
|
//!
|
|
//! The driver's helper app opens the device exclusively while it runs, so reading the HID device
|
|
//! directly fails then. The driver offers its own client API instead, in
|
|
//! `3DconnexionClient.framework`: an app registers with the driver and gets the cap's six axes
|
|
//! (after the user's 3DxWare settings) and the buttons as messages on a run loop.
|
|
//!
|
|
//! The framework is loaded with `dlopen` when [`start`] is called, so nothing links against it and
|
|
//! the app runs without the driver installed ([`Error::NotInstalled`]). The framework is never
|
|
//! bundled or redistributed; its declarations here are our own. On other platforms [`start`]
|
|
//! returns [`Error::Unsupported`].
|
|
|
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
|
|
|
use std::fmt;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
mod macos;
|
|
|
|
/// What the driver reports.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Event {
|
|
/// A 3D mouse was plugged in or the driver connected to one.
|
|
DeviceAdded,
|
|
/// The 3D mouse was unplugged or the driver went away.
|
|
DeviceRemoved,
|
|
/// The cap moved: push left/right, forward/back, lift/press, then tilt forward/back,
|
|
/// tilt left/right and twist, each about -350..350 as the HID device reports them.
|
|
Axes { translation: [i16; 3], rotation: [i16; 3] },
|
|
/// The buttons that are down, one bit each.
|
|
Buttons(u32),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum Error {
|
|
/// Not macOS.
|
|
Unsupported,
|
|
/// The driver's framework is not installed (or not loadable).
|
|
NotInstalled,
|
|
/// The framework lacks a function this client needs (another driver version).
|
|
MissingSymbol(&'static str),
|
|
/// The driver refused the handlers (its `OSErr`).
|
|
Handlers(i16),
|
|
/// The driver refused the client registration.
|
|
Register,
|
|
/// [`start`] was called before.
|
|
AlreadyStarted,
|
|
/// The client's thread could not be started.
|
|
Thread(String),
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Error::Unsupported => write!(f, "the 3Dconnexion driver client only exists on macOS"),
|
|
Error::NotInstalled => write!(f, "the 3Dconnexion driver is not installed"),
|
|
Error::MissingSymbol(name) => write!(f, "the 3Dconnexion driver lacks {name}"),
|
|
Error::Handlers(code) => write!(f, "the 3Dconnexion driver refused the handlers (error {code})"),
|
|
Error::Register => write!(f, "the 3Dconnexion driver refused the client"),
|
|
Error::AlreadyStarted => write!(f, "the 3Dconnexion client is already started"),
|
|
Error::Thread(reason) => write!(f, "the 3Dconnexion client thread did not start: {reason}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for Error {}
|
|
|
|
/// Connects to the driver and calls `sink` (on the client's own thread) for everything it reports.
|
|
/// `name` is how the driver lists this app. Only one client per process; the connection lasts
|
|
/// until the process ends.
|
|
pub fn start(name: &str, sink: impl Fn(Event) + Send + Sync + 'static) -> Result<(), Error> {
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
macos::start(name, Box::new(sink))
|
|
}
|
|
#[cfg(not(target_os = "macos"))]
|
|
{
|
|
let _ = (name, sink);
|
|
Err(Error::Unsupported)
|
|
}
|
|
}
|