summary refs log tree commit diff
diff options
context:
space:
mode:
authorMaxwell Beck <max@rastertail.net>2025-06-28 20:11:42 -0500
committerMaxwell Beck <max@rastertail.net>2025-06-28 20:11:42 -0500
commitf563cd4905cf6909a80eb973df52ba2b4e63800b (patch)
treea5d549033ab6f690f240ceb20441b30203aedbbe
parente15b681db958182ae112dc11f48330c0ece793a9 (diff)
Refactor
-rw-r--r--Cargo.lock191
-rw-r--r--Cargo.toml2
-rw-r--r--src/main.rs264
3 files changed, 206 insertions, 251 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ac1596b..a873ec4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -45,6 +45,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
 
 [[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
+[[package]]
 name = "bluer"
 version = "0.17.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -61,7 +73,7 @@ dependencies = [
  "libc",
  "log",
  "macaddr",
- "nix 0.29.0",
+ "nix",
  "num-derive",
  "num-traits",
  "pin-project",
@@ -93,12 +105,6 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "cfg_aliases"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
-
-[[package]]
-name = "cfg_aliases"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
@@ -126,12 +132,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "custom_derive"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
-
-[[package]]
 name = "darling"
 version = "0.20.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -211,10 +211,16 @@ dependencies = [
 ]
 
 [[package]]
-name = "enum_derive"
-version = "0.1.7"
+name = "evdev"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "406ac2a8c9eedf8af9ee1489bee9e50029278a6456c740f7454cf8a158abc816"
+checksum = "a3c10865aeab1a7399b3c2d6046e8dcc7f5227b656f235ed63ef5ee45a47b8f8"
+dependencies = [
+ "bitvec",
+ "cfg-if",
+ "libc",
+ "nix",
+]
 
 [[package]]
 name = "fnv"
@@ -223,6 +229,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
 [[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+[[package]]
 name = "futures"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -348,12 +360,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
 [[package]]
-name = "ioctl-sys"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a"
-
-[[package]]
 name = "itoa"
 version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -391,36 +397,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "libudev"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
-dependencies = [
- "libc",
- "libudev-sys",
-]
-
-[[package]]
-name = "libudev-sys"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
-dependencies = [
- "libc",
- "pkg-config",
-]
-
-[[package]]
-name = "lock_api"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
-
-[[package]]
 name = "log"
 version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -460,25 +436,13 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
-dependencies = [
- "bitflags",
- "cfg-if",
- "cfg_aliases 0.1.1",
- "libc",
-]
-
-[[package]]
-name = "nix"
 version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
 dependencies = [
  "bitflags",
  "cfg-if",
- "cfg_aliases 0.2.1",
+ "cfg_aliases",
  "libc",
 ]
 
@@ -518,29 +482,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
 [[package]]
-name = "parking_lot"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-targets",
-]
-
-[[package]]
 name = "pin-project"
 version = "1.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -603,13 +544,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
 
 [[package]]
-name = "redox_syscall"
-version = "0.5.12"
+name = "radium"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
-dependencies = [
- "bitflags",
-]
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
 
 [[package]]
 name = "rustc-demangle"
@@ -630,12 +568,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
 
 [[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
 name = "serde"
 version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -668,22 +600,13 @@ dependencies = [
 ]
 
 [[package]]
-name = "signal-hook-registry"
-version = "1.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
-dependencies = [
- "libc",
-]
-
-[[package]]
 name = "siriremote"
 version = "0.1.0"
 dependencies = [
  "bluer",
+ "evdev",
  "tokio",
  "tokio-stream",
- "uinput-tokio",
  "uuid",
 ]
 
@@ -697,12 +620,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
 name = "socket2"
 version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -763,6 +680,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
 name = "tokio"
 version = "1.45.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -772,9 +695,7 @@ dependencies = [
  "bytes",
  "libc",
  "mio",
- "parking_lot",
  "pin-project-lite",
- "signal-hook-registry",
  "socket2",
  "tokio-macros",
  "windows-sys 0.52.0",
@@ -803,31 +724,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "uinput-sys"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aabddd8174ccadd600afeab346bb276cb1db5fafcf6a7c5c5708b8cc4b2cac7"
-dependencies = [
- "ioctl-sys",
- "libc",
-]
-
-[[package]]
-name = "uinput-tokio"
-version = "0.1.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21c66e97fd950d42439ab3695229e65631af82d2e6ffd418293583d5c06a5f56"
-dependencies = [
- "custom_derive",
- "enum_derive",
- "libc",
- "libudev",
- "nix 0.28.0",
- "tokio",
- "uinput-sys",
-]
-
-[[package]]
 name = "unicode-ident"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1029,3 +925,12 @@ checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
 dependencies = [
  "bitflags",
 ]
+
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 66582e5..5dc7f97 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2024"
 
 [dependencies]
 bluer = { version = "0.17.4", features = ["bluetoothd"] }
+evdev = "0.13.1"
 tokio = "1.45.1"
 tokio-stream = "0.1.17"
-uinput-tokio = "0.1.36"
 uuid = "1.17.0"
diff --git a/src/main.rs b/src/main.rs
index 81fa7a8..56586ee 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,16 @@
-use std::{
-    env::{self},
-    pin::pin,
-    time::Duration,
-};
+use std::{collections::HashSet, pin::pin, time::Duration};
 
-use bluer::{Session, Uuid};
+use bluer::{
+    Adapter, AdapterEvent, Device, DiscoveryFilter, DiscoveryTransport, Session, Uuid,
+    gatt::{local::Profile, remote::Characteristic},
+};
+use evdev::{
+    AbsInfo, AbsoluteAxisCode, AttributeSet, EventType, InputEvent, KeyCode, MiscCode, PropType,
+    SynchronizationCode, UinputAbsSetup,
+    uinput::{VirtualDevice, VirtualDeviceBuilder},
+};
 use tokio::{select, time::interval};
 use tokio_stream::StreamExt;
-use uinput_tokio::event::{controller::Mouse, relative};
 
 const HID_SERVICE: Result<Uuid, uuid::Error> =
     Uuid::try_parse("00001812-0000-1000-8000-00805f9b34fb");
@@ -19,120 +22,167 @@ const MAGIC_REPORT: u8 = 240;
 const BUTTON_REPORT: u8 = 251;
 const TOUCH_REPORT: u8 = 252;
 
-#[tokio::main(flavor = "current_thread")]
-async fn main() -> bluer::Result<()> {
-    let mut udevice = uinput_tokio::default()
-        .unwrap()
-        .name("siriremote")
-        .unwrap()
-        .event(Mouse::Left)
-        .unwrap()
-        .event(relative::Position::X)
-        .unwrap()
-        .event(relative::Position::Y)
-        .unwrap()
-        .create()
-        .await
-        .unwrap();
+struct RemoteCharateristics {
+    touch: Characteristic,
+    button: Characteristic,
+}
 
-    let session = Session::new().await?;
-    let adapter = session.default_adapter().await?;
+impl RemoteCharateristics {
+    async fn get(device: &Device) -> bluer::Result<Self> {
+        let mut touch = None;
+        let mut button = None;
 
-    let addr = env::args()
-        .nth(1)
-        .expect("must pass address")
-        .parse()
-        .expect("invalid address");
-
-    eprintln!("connecting...");
-    let device = adapter.device(addr)?;
-    device.connect().await?;
-
-    let mut touch_char = None;
-    let mut button_char = None;
-    for service in device.services().await? {
-        if service.uuid().await? == HID_SERVICE.unwrap() {
-            for characteristic in service.characteristics().await? {
-                for descriptor in characteristic.descriptors().await? {
-                    if descriptor.uuid().await? == REPORT_REF.unwrap() {
-                        let resp = descriptor.read().await?;
-                        match resp[0] {
-                            MAGIC_REPORT => characteristic.write(&[0xf0, 0x00]).await?,
-                            TOUCH_REPORT => touch_char = Some(characteristic.clone()),
-                            BUTTON_REPORT => button_char = Some(characteristic.clone()),
-                            _ => {}
+        for service in device.services().await? {
+            if service.uuid().await? == HID_SERVICE.unwrap() {
+                for characteristic in service.characteristics().await? {
+                    for descriptor in characteristic.descriptors().await? {
+                        if descriptor.uuid().await? == REPORT_REF.unwrap() {
+                            let resp = descriptor.read().await?;
+                            match resp[0] {
+                                // MAGIC_REPORT => characteristic.write(&[0xf0, 0x00]).await?,
+                                TOUCH_REPORT => touch = Some(characteristic.clone()),
+                                BUTTON_REPORT => button = Some(characteristic.clone()),
+                                _ => {}
+                            }
                         }
                     }
                 }
             }
         }
+
+        Ok(Self {
+            touch: touch.expect("did not find touch characteristic"),
+            button: button.expect("did not find button characteristic"),
+        })
+    }
+}
+
+async fn find_remote(adapter: &Adapter, rssi_thresh: i16) -> bluer::Result<Device> {
+    let mut uuids = HashSet::new();
+    uuids.insert(HID_SERVICE.unwrap());
+
+    adapter
+        .set_discovery_filter(DiscoveryFilter {
+            transport: DiscoveryTransport::Le,
+            rssi: Some(rssi_thresh),
+            uuids,
+            ..Default::default()
+        })
+        .await?;
+    let mut discover = pin!(adapter.discover_devices().await?);
+    while let Some(evt) = discover.next().await {
+        if let AdapterEvent::DeviceAdded(mac) = evt {
+            let device = adapter.device(mac)?;
+            if let Some(data) = device.manufacturer_data().await? {
+                if data.contains_key(&0x4c) {
+                    return Ok(device);
+                }
+            }
+        }
+    }
+    panic!("discovery ended");
+}
+
+async fn run(adapter: &Adapter) -> bluer::Result<()> {
+    eprintln!("DBG searching");
+    let remote = find_remote(&adapter, -25).await?;
+
+    if !remote.is_paired().await? {
+        eprintln!("DBG pairing");
+        remote.pair().await?;
     }
 
-    eprintln!("connected");
-    let mut touch = pin!(touch_char.unwrap().notify().await?);
-    let mut button = pin!(button_char.unwrap().notify().await?);
-    let mut last = None;
-    let mut sdx = 0f32;
-    let mut sdy = 0f32;
+    eprintln!("DBG connecting");
+    remote.connect().await?;
+
+    let chars = RemoteCharateristics::get(&remote).await?;
+    let mut touch_stream = pin!(chars.touch.notify().await?);
+
+    eprintln!("DBG starting");
+    let mut keys = AttributeSet::new();
+    keys.insert(KeyCode::BTN_TOUCH);
+    keys.insert(KeyCode::BTN_TOOL_FINGER);
+
+    let mut msc = AttributeSet::new();
+    msc.insert(MiscCode::MSC_TIMESTAMP);
+
+    let mut props = AttributeSet::new();
+    props.insert(PropType::POINTER);
+
+    let mut uinput = VirtualDevice::builder()
+        .unwrap()
+        .name("Siri Remote")
+        .with_properties(&props)
+        .unwrap()
+        .with_keys(&keys)
+        .unwrap()
+        .with_absolute_axis(&UinputAbsSetup::new(
+            AbsoluteAxisCode::ABS_X,
+            AbsInfo::new(0, -2048, 2048, 32, 0, 16),
+        ))
+        .unwrap()
+        .with_absolute_axis(&UinputAbsSetup::new(
+            AbsoluteAxisCode::ABS_Y,
+            AbsInfo::new(0, -2048, 2048, 32, 0, 16),
+        ))
+        .unwrap()
+        .with_msc(&msc)
+        .unwrap()
+        .build()
+        .unwrap();
+
     let mut timer = interval(Duration::from_millis(1));
+    let mut x = 0f32;
+    let mut y = 0f32;
+    let mut tx = 0f32;
+    let mut ty = 0f32;
+    let mut touch = 0;
     loop {
         select! {
+            ev = touch_stream.next() => {
+                let ev = ev.unwrap();
+                tx = ((((ev[5] & 15) as u16) << 8) + ev[4] as u16) as f32;
+                ty = ((((ev[5] & !15) as u16) >> 4) + ((ev[6] as u16) << 4)) as f32;
+                if tx >= 2048.0 {
+                    tx -= 4096.0;
+                }
+                if ty >= 2048.0 {
+                    ty -= 4096.0;
+                }
+
+                touch = if ev[3] > 0 { 1 } else { 0 };
+                // ts = (((ev[2] as u16) << 8) + (ev[1] as u16)) as i32;
+            },
             _ = timer.tick() => {
-                udevice.send(relative::Position::X, sdx as i32).await.unwrap();
-                udevice.send(relative::Position::Y, sdy as i32).await.unwrap();
-                udevice.synchronize().await.unwrap();
+                x = 0.5 * x + 0.5 * tx;
+                y = 0.5 * y + 0.5 * ty;
 
-                sdx *= 0.97;
-                sdy *= 0.97;
+                uinput
+                    .emit(&[
+                        InputEvent::new(EventType::KEY.0, KeyCode::BTN_TOUCH.0, touch),
+                        InputEvent::new(EventType::KEY.0, KeyCode::BTN_TOOL_FINGER.0, touch),
+                        InputEvent::new(EventType::ABSOLUTE.0, AbsoluteAxisCode::ABS_X.0, x as i32),
+                        InputEvent::new(EventType::ABSOLUTE.0, AbsoluteAxisCode::ABS_Y.0, -y as i32),
+                        InputEvent::new(
+                            EventType::SYNCHRONIZATION.0,
+                            SynchronizationCode::SYN_REPORT.0,
+                            0,
+                        ),
+                    ])
+                    .unwrap();
             },
-            v = button.next() => {
-                let v = v.unwrap();
-                match (v[0], v[1]) {
-                    (8, 0) => {
-                        udevice.send(Mouse::Left, 1).await.unwrap();
-                    },
-                    (0, 0) => {
-                        udevice.send(Mouse::Left, 0).await.unwrap();
-                    },
-                    (_, _) => (),
-                }
-            }
-            v = touch.next() => {
-                let v = v.unwrap();
-                let ts = (((v[2] as u16) << 8) + (v[1] as u16)) as i32;
-                let touched = v[3] > 0;
-
-                let x = ((((v[5] & 15) as u16) << 8) + v[4] as u16) as i32;
-                let y = ((((v[5] & !15) as u16) >> 4) + ((v[6] as u16) << 4)) as i32;
-
-                if touched {
-                    if let Some(&(xp, yp, tsp)) = last.as_ref() {
-                        let mut dx: i32 = x - xp;
-                        let mut dy: i32 = y - yp;
-                        let mut dts: i32 = ts - tsp;
-                        if dx >= 2048 {
-                            dx -= 4096;
-                        }
-                        if dx <= -2048 {
-                            dx += 4096;
-                        }
-                        if dy >= -2048 {
-                            dy -= 4096;
-                        }
-                        if dy <= -2048 {
-                            dy += 4096;
-                        }
-                        if dts <= -32768 {
-                            dts += 65536;
-                        }
-                        sdx += (dx as f32) / (dts as f32);
-                        sdy -= (dy as f32) / (dts as f32);
-                    }
-                    last = Some((x, y, ts));
-                } else {
-                    last = None;
-                }
-            }
-        }
+        };
     }
 }
+
+#[tokio::main(flavor = "current_thread")]
+async fn main() -> bluer::Result<()> {
+    let session = Session::new().await?;
+    let adapter = session.default_adapter().await?;
+
+    if let Err(e) = run(&adapter).await {
+        eprintln!("ERR {e}");
+    }
+
+    Ok(())
+}