Surface Dial

evtest data

MAC: C4:9D:ED:00:3B:8F
Available devices:
/dev/input/event0:  Power Button
/dev/input/event1:  AT Translated Set 2 keyboard
/dev/input/event2:  ImExPS/2 Generic Explorer Mouse
/dev/input/event3:  Surface Dial System Multi Axis
/dev/input/event4:  Surface Dial System Control
Select the device event number [0-4]: 3
Input driver version is 1.0.1
Input device ID: bus 0x5 vendor 0x45e product 0x91b version 0x108
Input device name: "Surface Dial System Multi Axis"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 256 (BTN_0)
  Event type 2 (EV_REL)
    Event code 7 (REL_DIAL)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
Properties:
Event: time 1735727359.044172, type 2 (EV_REL), code 7 (REL_DIAL), value -1
Event: time 1735727359.044172, -------------- SYN_REPORT ------------
Event: time 1735727359.207239, type 2 (EV_REL), code 7 (REL_DIAL), value -1
Event: time 1735727359.207239, -------------- SYN_REPORT ------------
Event: time 1735727359.244200, type 2 (EV_REL), code 7 (REL_DIAL), value -1
Event: time 1735727359.244200, -------------- SYN_REPORT ------------
Event: time 1735727359.269217, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1735727359.269217, type 1 (EV_KEY), code 256 (BTN_0), value 1
Event: time 1735727359.269217, -------------- SYN_REPORT ------------
Event: time 1735727359.481240, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1735727359.481240, type 1 (EV_KEY), code 256 (BTN_0), value 0
Event: time 1735727359.481240, -------------- SYN_REPORT ------------
Event: time 1735727359.492606, type 2 (EV_REL), code 7 (REL_DIAL), value 1

Haptic feedback

Reference: https://github.com/daniel5151/surface-dial-linux/blob/43dd973092871d8ef0c452b0797b519f5a9ddfac/src/dial_device/haptics.rs

In case repo is down:
use std::sync::mpsc;

use hidapi::{HidApi, HidDevice};

use crate::error::{Error, Result};

/// Proxy object - forwards requests to the DialHapticsWorker task
pub struct DialHaptics {
    msg: mpsc::Sender<DialHapticsWorkerMsg>,
}

impl DialHaptics {
    pub(super) fn new(msg: mpsc::Sender<DialHapticsWorkerMsg>) -> Result<DialHaptics> {
        Ok(DialHaptics { msg })
    }

    /// `steps` should be a value between 0 and 3600, which corresponds to the
    /// number of subdivisions the dial should use.
    pub fn set_mode(&self, haptics: bool, steps: u16) -> Result<()> {
        let _ = (self.msg).send(DialHapticsWorkerMsg::SetMode { haptics, steps });
        Ok(())
    }

    pub fn buzz(&self, repeat: u8) -> Result<()> {
        let _ = (self.msg).send(DialHapticsWorkerMsg::Manual { repeat });
        Ok(())
    }
}

#[derive(Debug)]
pub(super) enum DialHapticsWorkerMsg {
    DialConnected,
    DialDisconnected,
    SetMode { haptics: bool, steps: u16 },
    Manual { repeat: u8 },
}

pub(super) struct DialHapticsWorker {
    msg: mpsc::Receiver<DialHapticsWorkerMsg>,
}

impl DialHapticsWorker {
    pub(super) fn new(msg: mpsc::Receiver<DialHapticsWorkerMsg>) -> Result<DialHapticsWorker> {
        Ok(DialHapticsWorker { msg })
    }

    pub(super) fn run(&mut self) -> Result<()> {
        loop {
            eprintln!("haptics worker is waiting...");

            loop {
                match self.msg.recv().unwrap() {
                    DialHapticsWorkerMsg::DialConnected => break,
                    other => eprintln!("haptics worker dropped an event: {:?}", other),
                }
            }

            eprintln!("haptics worker is ready");

            let api = HidApi::new().map_err(Error::HidError)?;
            let hid_device = api.open(0x045e, 0x091b).map_err(Error::HidError)?;
            let wrapper = DialHidWrapper { hid_device };

            loop {
                match self.msg.recv().unwrap() {
                    DialHapticsWorkerMsg::DialConnected => {
                        eprintln!("Unexpected haptics worker ready event.");
                        // should be fine though?
                    }
                    DialHapticsWorkerMsg::DialDisconnected => break,
                    DialHapticsWorkerMsg::SetMode { haptics, steps } => {
                        wrapper.set_mode(haptics, steps)?
                    }
                    DialHapticsWorkerMsg::Manual { repeat } => wrapper.buzz(repeat)?,
                }
            }
        }
    }
}

struct DialHidWrapper {
    hid_device: HidDevice,
}

impl DialHidWrapper {
    /// `steps` should be a value between 0 and 3600, which corresponds to the
    /// number of subdivisions the dial should use. If left unspecified, this
    /// defaults to 36 (an arbitrary choice that "feels good" most of the time)
    fn set_mode(&self, haptics: bool, steps: u16) -> Result<()> {
        assert!(steps <= 3600);

        let steps_lo = steps & 0xff;
        let steps_hi = (steps >> 8) & 0xff;

        let mut buf = [0; 8];
        buf[0] = 1;
        buf[1] = steps_lo as u8; // steps
        buf[2] = steps_hi as u8; // steps
        buf[3] = 0x00; // Repeat Count
        buf[4] = if haptics { 0x03 } else { 0x02 }; // auto trigger
        buf[5] = 0x00; // Waveform Cutoff Time
        buf[6] = 0x00; // retrigger period
        buf[7] = 0x00; // retrigger period
        self.hid_device
            .send_feature_report(&buf[..8])
            .map_err(Error::HidError)?;

        Ok(())
    }

    fn buzz(&self, repeat: u8) -> Result<()> {
        let mut buf = [0; 5];
        buf[0] = 0x01; // Report ID
        buf[1] = repeat; // RepeatCount
        buf[2] = 0x03; // ManualTrigger
        buf[3] = 0x00; // RetriggerPeriod (lo)
        buf[4] = 0x00; // RetriggerPeriod (hi)
        self.hid_device.write(&buf).map_err(Error::HidError)?;
        Ok(())
    }
}
Tag(s): IoT
Profile picture
斟酌 鵬兄
Thu Jan 02 2025 07:06:21 GMT+0000 (Coordinated Universal Time)
Last modified: Thu Jan 02 2025 07:08:35 GMT+0000 (Coordinated Universal Time)
Comments
No comments here.
Do you even comment?
website: 
Not a valid website
Invalid email format
Please enter your email
*Name: 
Please enter a name
Submit
抱歉,Google Recaptcha 服務被牆掉了,所以不能回覆了