From a68403d3aef9792f506fdf4027e4b8f0ef711a27 Mon Sep 17 00:00:00 2001 From: Avii Date: Sun, 22 Feb 2026 09:55:19 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 1 + Cargo.lock | 292 ++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++ src/lib.rs | 68 ++++++++ src/main.rs | 11 ++ src/messages/debug_message.rs | 16 ++ src/messages/device_info.rs | 47 ++++++ src/messages/experimental.rs | 16 ++ src/messages/header.rs | 28 ++++ src/messages/hello.rs | 68 ++++++++ src/messages/message_type.rs | 33 ++++ src/messages/mod.rs | 37 +++++ src/messages/ping.rs | 24 +++ src/messages/state.rs | 145 +++++++++++++++++ src/metadata.rs | 53 ++++++ 15 files changed, 851 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/messages/debug_message.rs create mode 100644 src/messages/device_info.rs create mode 100644 src/messages/experimental.rs create mode 100644 src/messages/header.rs create mode 100644 src/messages/hello.rs create mode 100644 src/messages/message_type.rs create mode 100644 src/messages/mod.rs create mode 100644 src/messages/ping.rs create mode 100644 src/messages/state.rs create mode 100644 src/metadata.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..54e515b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,292 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "otd-ipc" +version = "0.1.0" +dependencies = [ + "bytes", + "dirs", + "serde", + "serde_bytes", + "slug", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f31553e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "otd-ipc" +version = "0.1.0" +edition = "2024" +authors = ["AviiNL "] + +[dependencies] +bytes = "1.11.1" +dirs = "6.0.0" +serde = { version = "1.0.228", features = ["derive"] } +serde_bytes = "0.11.19" +slug = "0.1.6" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8bbd48b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,68 @@ +mod messages; +mod metadata; + +use std::{ + io::{Read, Write}, + os::unix::net::UnixStream, +}; + +use crate::{messages::*, metadata::OtdIpcMetadata}; + +pub struct OtdIpc { + stream: UnixStream, +} + +impl OtdIpc { + pub fn new(name: &str, version: &str) -> Result> { + let meta = OtdIpcMetadata::new()?; + + let mut stream = UnixStream::connect(meta.socket)?; + let hello = Hello::new( + 0x022026020501, + name, + version, + &format!("{}.{}", slug::slugify(name), meta.id), + 1, + )?; + + stream.write_all(&Vec::from(hello))?; + + Ok(Self { stream }) + } +} + +impl Iterator for OtdIpc { + type Item = Message; + + fn next(&mut self) -> Option { + let mut raw_header = [0; size_of::
()]; + self.stream.read_exact(&mut raw_header).ok()?; + + let header: Header = raw_header[..].try_into().ok()?; + let mut raw_buffer = vec![0; (header.size as usize) - size_of::
()]; + self.stream.read_exact(&mut raw_buffer).ok()?; + + match header.message_type { + messages::MessageType::None => None, + messages::MessageType::DeviceInfo => Some(Message::DeviceInfo(Box::new( + DeviceInfo::try_from(&raw_buffer[..]).ok()?, + ))), + messages::MessageType::State => { + Some(Message::State(State::try_from(&raw_buffer[..]).ok()?)) + } + messages::MessageType::Ping => { + Some(Message::Ping(Ping::try_from(&raw_buffer[..]).ok()?)) + } + messages::MessageType::DebugMessage => { + Some(Message::DebugMessage(DebugMessage::from(&raw_buffer[..]))) + } + messages::MessageType::Experimental => { + Some(Message::Experimental(Experimental::from(&raw_buffer[..]))) + } + messages::MessageType::Hello => { + // Some(Message::Hello(Hello::from(&raw_buffer[..]))) + None + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..19366d4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,11 @@ +use otd_ipc::OtdIpc; + +fn main() -> Result<(), Box> { + let otd_ipc = OtdIpc::new("otd-ipc-rs", "master")?; + + for msg in otd_ipc { + dbg!(msg); + } + + Ok(()) +} diff --git a/src/messages/debug_message.rs b/src/messages/debug_message.rs new file mode 100644 index 0000000..1bf59d2 --- /dev/null +++ b/src/messages/debug_message.rs @@ -0,0 +1,16 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DebugMessage { + pub message: String, // this can not be known at compile time +} + +impl From<&[u8]> for DebugMessage { + fn from(value: &[u8]) -> Self { + Self { + message: String::from_utf8_lossy(value).to_string(), + } + } +} diff --git a/src/messages/device_info.rs b/src/messages/device_info.rs new file mode 100644 index 0000000..673c93a --- /dev/null +++ b/src/messages/device_info.rs @@ -0,0 +1,47 @@ +use std::fmt::Debug; + +use super::take; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeviceInfo { + pub max_x: f32, + pub max_y: f32, + pub max_pressure: u32, + pub persistent_id: String, + pub name: String, +} + +impl TryFrom<&[u8]> for DeviceInfo { + type Error = &'static str; + + fn try_from(mut bytes: &[u8]) -> Result { + if bytes.len() < 16 + 256 + 256 { + return Err("buffer too small"); + } + + let max_x = f32::from_le_bytes(take::<4>(&mut bytes)?); + let max_y = f32::from_le_bytes(take::<4>(&mut bytes)?); + let max_pressure = u32::from_le_bytes(take::<4>(&mut bytes)?); + + // skip ignored field + let _ = take::<4>(&mut bytes)?; + + let (id_bytes, name_bytes) = bytes.split_at(256); + let persistent_id = String::from_utf8_lossy(id_bytes) + .trim_end_matches('\0') + .to_string(); + + let name = String::from_utf8_lossy(name_bytes) + .trim_end_matches('\0') + .to_string(); + + Ok(Self { + max_x, + max_y, + max_pressure, + persistent_id, + name, + }) + } +} diff --git a/src/messages/experimental.rs b/src/messages/experimental.rs new file mode 100644 index 0000000..6d0688e --- /dev/null +++ b/src/messages/experimental.rs @@ -0,0 +1,16 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Experimental { + // pub sequence_number: u32, +} + +impl From<&[u8]> for Experimental { + fn from(_value: &[u8]) -> Self { + Self { + // .. + } + } +} diff --git a/src/messages/header.rs b/src/messages/header.rs new file mode 100644 index 0000000..925946b --- /dev/null +++ b/src/messages/header.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +use super::take; + +use super::MessageType; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Header { + pub message_type: MessageType, + pub size: u32, + pub non_persistent_tablet_id: u32, +} + +impl TryFrom<&[u8]> for Header { + type Error = &'static str; + + fn try_from(mut bytes: &[u8]) -> Result { + if bytes.len() < 12 { + return Err("buffer too small"); + } + + Ok(Self { + message_type: u32::from_le_bytes(take::<4>(&mut bytes)?).into(), + size: u32::from_le_bytes(take::<4>(&mut bytes)?), + non_persistent_tablet_id: u32::from_le_bytes(take::<4>(&mut bytes)?), + }) + } +} diff --git a/src/messages/hello.rs b/src/messages/hello.rs new file mode 100644 index 0000000..a969bf8 --- /dev/null +++ b/src/messages/hello.rs @@ -0,0 +1,68 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +use super::MessageType; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Hello { + pub protocol_version: u64, + pub human_readable_name: String, + pub human_readable_version: String, + pub implementation_id: String, + pub compatibility_version: u8, +} + +impl Hello { + pub fn new( + protocol_version: u64, + human_readable_name: &str, + human_readable_version: &str, + implementation_id: &str, + compatibility_version: u8, + ) -> Result> { + Ok(Hello { + protocol_version, + human_readable_name: human_readable_name.to_string(), + human_readable_version: human_readable_version.to_string(), + implementation_id: implementation_id.to_string(), + compatibility_version, + }) + } +} + +impl From for Vec { + fn from(value: Hello) -> Self { + let mut output = Vec::with_capacity(std::mem::size_of::()); + + output.extend_from_slice(&(MessageType::Hello as u32).to_le_bytes()); + output.extend_from_slice(&(std::mem::size_of::() as u32).to_le_bytes()); + + output.push(0u8); // non_persistent_tablet_id + + output.extend_from_slice(&value.protocol_version.to_le_bytes()); + + output.extend_from_slice(&str_to_fixed(&value.human_readable_name).unwrap()); + output.extend_from_slice(&str_to_fixed(&value.human_readable_version).unwrap()); + output.extend_from_slice(&str_to_fixed(&value.implementation_id).unwrap()); + + output.push(value.compatibility_version); + + output + } +} + +fn str_to_fixed(input: &str) -> Result<[u8; 256], String> { + let bytes = input.as_bytes(); + + if bytes.len() > 256 { + return Err(format!( + "String too long ({} bytes), max is 256", + bytes.len() + )); + } + + let mut buffer = [0u8; 256]; + buffer[..bytes.len()].copy_from_slice(bytes); + Ok(buffer) +} diff --git a/src/messages/message_type.rs b/src/messages/message_type.rs new file mode 100644 index 0000000..9fd5d33 --- /dev/null +++ b/src/messages/message_type.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +pub enum MessageType { + None, + DeviceInfo, + State, + Ping, + DebugMessage, + Experimental, + Hello, +} + +impl From for u32 { + fn from(value: MessageType) -> Self { + value as u32 + } +} + +impl From for MessageType { + fn from(value: u32) -> Self { + match value { + 0 => Self::None, + 1 => Self::DeviceInfo, + 2 => Self::State, + 3 => Self::Ping, + 4 => Self::DebugMessage, + 5 => Self::Experimental, + 6 => Self::Hello, + _ => panic!("Invalid value"), + } + } +} diff --git a/src/messages/mod.rs b/src/messages/mod.rs new file mode 100644 index 0000000..9ae2845 --- /dev/null +++ b/src/messages/mod.rs @@ -0,0 +1,37 @@ +mod debug_message; +mod device_info; +mod experimental; +mod header; +mod hello; +mod message_type; +mod ping; +mod state; + +pub use debug_message::*; +pub use device_info::*; +pub use experimental::*; +pub use header::*; +pub use hello::*; +pub use message_type::*; +pub use ping::*; +pub use state::*; + +#[derive(Debug)] +pub enum Message { + DeviceInfo(Box), + State(State), + Ping(Ping), + DebugMessage(DebugMessage), + Experimental(Experimental), + // Hello(Hello), +} + +// helper to read fixed-size chunks +pub(super) fn take(bytes: &mut &[u8]) -> Result<[u8; N], &'static str> { + if bytes.len() < N { + return Err("buffer too small"); + } + let (head, tail) = bytes.split_at(N); + *bytes = tail; + Ok(head.try_into().unwrap()) +} diff --git a/src/messages/ping.rs b/src/messages/ping.rs new file mode 100644 index 0000000..8be1bd8 --- /dev/null +++ b/src/messages/ping.rs @@ -0,0 +1,24 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +use super::take; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Ping { + pub sequence_number: u32, +} + +impl TryFrom<&[u8]> for Ping { + type Error = &'static str; + + fn try_from(mut bytes: &[u8]) -> Result { + if bytes.len() < 4 { + return Err("buffer too small"); + } + + Ok(Self { + sequence_number: u32::from_le_bytes(take::<4>(&mut bytes)?), + }) + } +} diff --git a/src/messages/state.rs b/src/messages/state.rs new file mode 100644 index 0000000..5931e17 --- /dev/null +++ b/src/messages/state.rs @@ -0,0 +1,145 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +use super::take; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ValidMask(pub u32); + +impl ValidMask { + // pub const NONE: Self = Self(0); + pub const POSITION_X: Self = Self(1 << 0); + pub const POSITION_Y: Self = Self(1 << 1); + pub const PRESSURE: Self = Self(1 << 2); + pub const PEN_BUTTONS: Self = Self(1 << 3); + pub const AUX_BUTTONS: Self = Self(1 << 4); + pub const PEN_IS_NEAR_SURFACE: Self = Self(1 << 5); + pub const HOVER_DISTANCE: Self = Self(1 << 6); + + // pub const POSITION: Self = Self(Self::POSITION_X.0 | Self::POSITION_Y.0); + + #[inline] + pub fn has(self, mask: ValidMask) -> bool { + (self.0 & mask.0) == mask.0 + } +} + +impl From for ValidMask { + fn from(value: u32) -> Self { + Self(value) + } +} + +// Bitwise operators +impl std::ops::BitOr for ValidMask { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { + Self(self.0 | rhs.0) + } +} + +impl std::ops::BitAnd for ValidMask { + type Output = Self; + fn bitand(self, rhs: Self) -> Self { + Self(self.0 & rhs.0) + } +} + +impl std::ops::Not for ValidMask { + type Output = Self; + fn not(self) -> Self { + Self(!self.0) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct State { + valid_bits: ValidMask, + + x: f32, + y: f32, + + pressure: u32, + pen_buttons: u32, + aux_buttons: u32, + hover_distance: u32, + pen_is_near_surface: bool, +} + +impl State { + pub fn x(&self) -> f32 { + if !self.valid_bits.has(ValidMask::POSITION_X) { + return 0.0; + } + + self.x + } + + pub fn y(&self) -> f32 { + if !self.valid_bits.has(ValidMask::POSITION_Y) { + return 0.0; + } + + self.y + } + + pub fn pressure(&self) -> u32 { + if !self.valid_bits.has(ValidMask::PRESSURE) { + return 0; + } + + self.pressure + } + pub fn pen_buttons(&self) -> u32 { + if !self.valid_bits.has(ValidMask::PEN_BUTTONS) { + return 0; + } + + self.pen_buttons + } + pub fn aux_buttons(&self) -> u32 { + if !self.valid_bits.has(ValidMask::AUX_BUTTONS) { + return 0; + } + + self.aux_buttons + } + pub fn hover_distance(&self) -> u32 { + if !self.valid_bits.has(ValidMask::HOVER_DISTANCE) { + return 0; + } + + self.hover_distance + } + + pub fn pen_is_near_surface(&self) -> bool { + if !self.valid_bits.has(ValidMask::PEN_IS_NEAR_SURFACE) { + return true; + } + + self.pen_is_near_surface + } +} + +impl TryFrom<&[u8]> for State { + type Error = &'static str; + + fn try_from(mut bytes: &[u8]) -> Result { + if bytes.len() < 29 { + return Err("buffer too small"); + } + + Ok(Self { + valid_bits: u32::from_le_bytes(take::<4>(&mut bytes)?).into(), + x: f32::from_le_bytes(take::<4>(&mut bytes)?), + y: f32::from_le_bytes(take::<4>(&mut bytes)?), + pressure: u32::from_le_bytes(take::<4>(&mut bytes)?), + pen_buttons: u32::from_le_bytes(take::<4>(&mut bytes)?), + aux_buttons: u32::from_le_bytes(take::<4>(&mut bytes)?), + hover_distance: u32::from_le_bytes(take::<4>(&mut bytes)?), + pen_is_near_surface: u8::from_le_bytes(take::<1>(&mut bytes)?) != 0, + }) + } +} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..cad3f13 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,53 @@ +use std::{collections::HashMap, fs::read_to_string, path::PathBuf}; + +#[derive(Debug)] +#[allow(unused)] +pub(crate) struct OtdIpcMetadata { + pub id: String, + pub name: String, + pub semver: String, + pub debug_version: String, + pub homepage: String, + pub socket: PathBuf, +} + +impl OtdIpcMetadata { + pub fn new() -> std::io::Result { + let Some(mut otd_ipc_dir) = dirs::data_local_dir() else { + panic!("User data local dir should exist!"); + }; + + otd_ipc_dir.push("otd-ipc"); + otd_ipc_dir.push("servers"); + otd_ipc_dir.push("v2"); + + let mut default = otd_ipc_dir.clone(); + default.push("default.txt"); + + let default_id = read_to_string(default)?; + + let mut available = otd_ipc_dir.clone(); + available.push("available"); + available.push(format!("{}.txt", default_id)); + + let metadata = read_to_string(available)?; + + let mut data = HashMap::new(); + + for line in metadata.lines() { + let Some((key, value)) = line.split_once('=') else { + continue; // not a valid kvp + }; + data.insert(key, value.to_owned()); + } + + Ok(Self { + id: data["ID"].clone(), + name: data["NAME"].clone(), + semver: data["SEMVER"].clone(), + debug_version: data["DEBUG_VERSION"].clone(), + homepage: data["HOMEPAGE"].clone(), + socket: data["SOCKET"].clone().into(), + }) + } +}