use libudev to handle device disconnect/reconnect
This change has substantially bumped up the daemon's overall robustness, as the code now ensures that the controller will only start once the /dev/input/eventXX file is set up, which was causing all sorts of issues in the past. Additionally, this change enables the daemon to run as a proper background task that _doesn't_ constantly die / need to be restarted, which removes the need for any janky udev "on add" rules, and instead, a simple systemd user service will suffice.
This commit is contained in:
@@ -35,7 +35,7 @@ pub struct DialController {
|
||||
active_mode: ActiveMode,
|
||||
|
||||
new_mode: Arc<Mutex<Option<usize>>>,
|
||||
meta_mode: Box<dyn ControlMode>, // always MetaMode
|
||||
meta_mode: Box<dyn ControlMode>, // concrete type is always `MetaMode`
|
||||
}
|
||||
|
||||
impl DialController {
|
||||
@@ -60,12 +60,6 @@ impl DialController {
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
let initial_mode = match self.active_mode {
|
||||
ActiveMode::Normal(i) => i,
|
||||
ActiveMode::Meta => 0,
|
||||
};
|
||||
self.modes[initial_mode].on_start(self.device.haptics())?;
|
||||
|
||||
loop {
|
||||
let evt = self.device.next_event()?;
|
||||
let haptics = self.device.haptics();
|
||||
@@ -80,13 +74,22 @@ impl DialController {
|
||||
ActiveMode::Meta => &mut self.meta_mode,
|
||||
};
|
||||
|
||||
// TODO: press and hold (+ rotate?) to switch between modes
|
||||
|
||||
match evt.kind {
|
||||
DialEventKind::Ignored => {}
|
||||
|
||||
DialEventKind::Connect => {
|
||||
eprintln!("Dial Connected");
|
||||
mode.on_start(haptics)?
|
||||
}
|
||||
DialEventKind::Disconnect => {
|
||||
eprintln!("Dial Disconnected");
|
||||
mode.on_end(haptics)?
|
||||
}
|
||||
|
||||
DialEventKind::ButtonPress => mode.on_btn_press(haptics)?,
|
||||
DialEventKind::ButtonRelease => mode.on_btn_release(haptics)?,
|
||||
DialEventKind::Dial(delta) => mode.on_dial(haptics, delta)?,
|
||||
|
||||
DialEventKind::ButtonLongPress => {
|
||||
eprintln!("long press!");
|
||||
if !matches!(self.active_mode, ActiveMode::Meta) {
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
use std::fs;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use evdev_rs::{Device, InputEvent, ReadStatus};
|
||||
use hidapi::{HidApi, HidDevice};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct DialDevice {
|
||||
long_press_timeout: Duration,
|
||||
haptics: DialHaptics,
|
||||
events: mpsc::Receiver<std::io::Result<(ReadStatus, InputEvent)>>,
|
||||
|
||||
possible_long_press: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DialEvent {
|
||||
pub time: Duration,
|
||||
pub kind: DialEventKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DialEventKind {
|
||||
Ignored,
|
||||
ButtonPress,
|
||||
ButtonRelease,
|
||||
Dial(i32),
|
||||
ButtonLongPress,
|
||||
}
|
||||
|
||||
impl DialDevice {
|
||||
pub fn new(long_press_timeout: Duration) -> Result<DialDevice> {
|
||||
let mut control = None;
|
||||
let mut axis = None;
|
||||
|
||||
// discover the evdev devices
|
||||
for e in fs::read_dir("/dev/input/").map_err(Error::OpenDevInputDir)? {
|
||||
let e = e.map_err(Error::OpenDevInputDir)?;
|
||||
if !e.file_name().to_str().unwrap().starts_with("event") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file =
|
||||
fs::File::open(e.path()).map_err(|err| Error::OpenEventFile(e.path(), err))?;
|
||||
let dev = Device::new_from_fd(file).map_err(Error::Evdev)?;
|
||||
|
||||
match dev.name() {
|
||||
Some("Surface Dial System Control") => match control {
|
||||
None => control = Some(dev),
|
||||
Some(_) => return Err(Error::MultipleDials),
|
||||
},
|
||||
Some("Surface Dial System Multi Axis") => match axis {
|
||||
None => axis = Some(dev),
|
||||
Some(_) => return Err(Error::MultipleDials),
|
||||
},
|
||||
// Some(other) => println!("{:?}", other),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// early return once both were found
|
||||
if control.is_some() && axis.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: explore what the control channel can be used for...
|
||||
let _control = control.ok_or(Error::MissingDial)?;
|
||||
let axis = axis.ok_or(Error::MissingDial)?;
|
||||
|
||||
let (events_tx, events_rx) = mpsc::channel();
|
||||
|
||||
// TODO: interleave control events with regular events
|
||||
// (once we figure out what control events actually do...)
|
||||
|
||||
// inb4 "y not async/await"
|
||||
std::thread::spawn({
|
||||
let events = events_tx;
|
||||
move || loop {
|
||||
let _ = events.send(axis.next_event(evdev_rs::ReadFlag::NORMAL));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(DialDevice {
|
||||
long_press_timeout,
|
||||
events: events_rx,
|
||||
haptics: DialHaptics::new()?,
|
||||
|
||||
possible_long_press: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_event(&mut self) -> Result<DialEvent> {
|
||||
let evt = if self.possible_long_press {
|
||||
self.events.recv_timeout(self.long_press_timeout)
|
||||
} else {
|
||||
self.events
|
||||
.recv()
|
||||
.map_err(|_| mpsc::RecvTimeoutError::Disconnected)
|
||||
};
|
||||
|
||||
let event = match evt {
|
||||
Ok(Ok((_event_status, event))) => {
|
||||
// assert!(matches!(axis_status, ReadStatus::Success));
|
||||
let event =
|
||||
DialEvent::from_raw_evt(event.clone()).ok_or(Error::UnexpectedEvt(event))?;
|
||||
match event.kind {
|
||||
DialEventKind::ButtonPress => self.possible_long_press = true,
|
||||
DialEventKind::ButtonRelease => self.possible_long_press = false,
|
||||
_ => {}
|
||||
}
|
||||
event
|
||||
}
|
||||
Ok(Err(e)) => return Err(Error::Evdev(e)),
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
self.possible_long_press = false;
|
||||
DialEvent {
|
||||
time: Duration::from_secs(0), // this could be improved...
|
||||
kind: DialEventKind::ButtonLongPress,
|
||||
}
|
||||
}
|
||||
Err(_e) => panic!("Could not recv event"),
|
||||
};
|
||||
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
pub fn haptics(&self) -> &DialHaptics {
|
||||
&self.haptics
|
||||
}
|
||||
}
|
||||
|
||||
impl DialEvent {
|
||||
fn from_raw_evt(evt: InputEvent) -> Option<DialEvent> {
|
||||
use evdev_rs::enums::*;
|
||||
|
||||
let evt_kind = match evt.event_type {
|
||||
EventType::EV_SYN | EventType::EV_MSC => DialEventKind::Ignored,
|
||||
EventType::EV_KEY => match evt.event_code {
|
||||
EventCode::EV_KEY(EV_KEY::BTN_0) => match evt.value {
|
||||
0 => DialEventKind::ButtonRelease,
|
||||
1 => DialEventKind::ButtonPress,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
},
|
||||
EventType::EV_REL => match evt.event_code {
|
||||
EventCode::EV_REL(EV_REL::REL_DIAL) => DialEventKind::Dial(evt.value),
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let evt = DialEvent {
|
||||
time: Duration::new(evt.time.tv_sec as u64, (evt.time.tv_usec * 1000) as u32),
|
||||
kind: evt_kind,
|
||||
};
|
||||
|
||||
Some(evt)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DialHaptics {
|
||||
hid_device: HidDevice,
|
||||
}
|
||||
|
||||
impl DialHaptics {
|
||||
fn new() -> Result<DialHaptics> {
|
||||
let api = HidApi::new().map_err(Error::HidError)?;
|
||||
let hid_device = api.open(0x045e, 0x091b).map_err(|_| Error::MissingDial)?;
|
||||
|
||||
// let mut buf = [0; 256];
|
||||
|
||||
// buf[0] = 1;
|
||||
// let len = device
|
||||
// .get_feature_report(&mut buf)
|
||||
// .map_err(Error::HidError)?;
|
||||
// eprintln!("1: {:02x?}", &buf[..len]);
|
||||
|
||||
// buf[0] = 2;
|
||||
// let len = device
|
||||
// .get_feature_report(&mut buf)
|
||||
// .map_err(Error::HidError)?;
|
||||
// eprintln!("2: {:02x?}", &buf[..len]);
|
||||
|
||||
Ok(DialHaptics { hid_device })
|
||||
}
|
||||
|
||||
/// `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)
|
||||
pub fn set_mode(&self, haptics: bool, steps: Option<u16>) -> Result<()> {
|
||||
let steps = steps.unwrap_or(36);
|
||||
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(())
|
||||
}
|
||||
|
||||
pub 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(())
|
||||
}
|
||||
}
|
||||
154
src/dial_device/events.rs
Normal file
154
src/dial_device/events.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use std::fs;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use evdev_rs::{InputEvent, ReadStatus};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use super::DialHapticsWorkerMsg;
|
||||
|
||||
pub enum RawInputEvent {
|
||||
Event(ReadStatus, InputEvent),
|
||||
Connect,
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum DialInputKind {
|
||||
Control,
|
||||
MultiAxis,
|
||||
}
|
||||
|
||||
pub struct EventsWorker {
|
||||
events: mpsc::Sender<RawInputEvent>,
|
||||
haptics_msg: mpsc::Sender<DialHapticsWorkerMsg>,
|
||||
input_kind: DialInputKind,
|
||||
}
|
||||
|
||||
impl EventsWorker {
|
||||
pub(super) fn new(
|
||||
input_kind: DialInputKind,
|
||||
events: mpsc::Sender<RawInputEvent>,
|
||||
haptics_msg: mpsc::Sender<DialHapticsWorkerMsg>,
|
||||
) -> EventsWorker {
|
||||
EventsWorker {
|
||||
input_kind,
|
||||
events,
|
||||
haptics_msg,
|
||||
}
|
||||
}
|
||||
|
||||
fn udev_to_evdev(&self, device: &udev::Device) -> std::io::Result<Option<evdev_rs::Device>> {
|
||||
let devnode = match device.devnode() {
|
||||
Some(path) => path,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// we care about the `/dev/input/eventXX` device, which is a child of the
|
||||
// actual input device (that has a nice name we can match against)
|
||||
match device.parent() {
|
||||
None => return Ok(None),
|
||||
Some(parent) => {
|
||||
let name = parent
|
||||
.property_value("NAME")
|
||||
.unwrap_or_else(|| std::ffi::OsStr::new(""))
|
||||
.to_string_lossy();
|
||||
|
||||
match (self.input_kind, name.as_ref()) {
|
||||
(DialInputKind::Control, r#""Surface Dial System Control""#) => {}
|
||||
(DialInputKind::MultiAxis, r#""Surface Dial System Multi Axis""#) => {}
|
||||
_ => return Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let file = fs::File::open(devnode)?;
|
||||
evdev_rs::Device::new_from_fd(file).map(Some)
|
||||
}
|
||||
|
||||
fn event_loop(&mut self, device: evdev_rs::Device) -> std::io::Result<()> {
|
||||
// HACK: don't want to double-send these events
|
||||
if self.input_kind != DialInputKind::Control {
|
||||
self.haptics_msg
|
||||
.send(DialHapticsWorkerMsg::DialConnected)
|
||||
.unwrap();
|
||||
self.events.send(RawInputEvent::Connect).unwrap();
|
||||
}
|
||||
|
||||
loop {
|
||||
let _ = self
|
||||
.events
|
||||
.send(match device.next_event(evdev_rs::ReadFlag::NORMAL) {
|
||||
Ok((read_status, event)) => RawInputEvent::Event(read_status, event),
|
||||
// this error corresponds to the device disconnecting, which is fine
|
||||
Err(e) if e.raw_os_error() == Some(19) => break,
|
||||
Err(e) => return Err(e),
|
||||
});
|
||||
}
|
||||
|
||||
// HACK: don't want to double-send these events
|
||||
if self.input_kind != DialInputKind::Control {
|
||||
self.haptics_msg
|
||||
.send(DialHapticsWorkerMsg::DialDisconnected)
|
||||
.unwrap();
|
||||
self.events.send(RawInputEvent::Disconnect).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> std::io::Result<()> {
|
||||
// eagerly check if the device already exists
|
||||
|
||||
let mut enumerator = {
|
||||
let mut e = udev::Enumerator::new()?;
|
||||
e.match_subsystem("input")?;
|
||||
e
|
||||
};
|
||||
for device in enumerator.scan_devices()? {
|
||||
let dev = match self.udev_to_evdev(&device)? {
|
||||
None => continue,
|
||||
Some(dev) => dev,
|
||||
};
|
||||
|
||||
self.event_loop(dev)?;
|
||||
}
|
||||
|
||||
// enter udev event loop to gracefully handle disconnect/reconnect
|
||||
|
||||
let mut socket = udev::MonitorBuilder::new()?
|
||||
.match_subsystem("input")?
|
||||
.listen()?;
|
||||
|
||||
loop {
|
||||
nix::poll::ppoll(
|
||||
&mut [nix::poll::PollFd::new(
|
||||
socket.as_raw_fd(),
|
||||
nix::poll::PollFlags::POLLIN,
|
||||
)],
|
||||
None,
|
||||
nix::sys::signal::SigSet::empty(),
|
||||
)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
|
||||
let event = match socket.next() {
|
||||
Some(evt) => evt,
|
||||
None => {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if !matches!(event.event_type(), udev::EventType::Add) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dev = match self.udev_to_evdev(&event.device())? {
|
||||
None => continue,
|
||||
Some(dev) => dev,
|
||||
};
|
||||
|
||||
self.event_loop(dev)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/dial_device/haptics.rs
Normal file
123
src/dial_device/haptics.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
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. If left unspecified, this
|
||||
/// defaults to 36 (an arbitrary choice that "feels good" most of the time)
|
||||
pub fn set_mode(&self, haptics: bool, steps: Option<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: Option<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::MissingDial)?;
|
||||
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: Option<u16>) -> Result<()> {
|
||||
let steps = steps.unwrap_or(36);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
170
src/dial_device/mod.rs
Normal file
170
src/dial_device/mod.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
mod events;
|
||||
mod haptics;
|
||||
|
||||
use haptics::{DialHapticsWorker, DialHapticsWorkerMsg};
|
||||
|
||||
pub use haptics::DialHaptics;
|
||||
|
||||
/// Encapsulates all the the nitty-gritty (and pretty gnarly) device handling
|
||||
/// code, exposing a simple interface to wait for incoming [`DialEvent`]s.
|
||||
pub struct DialDevice {
|
||||
// configurable constants
|
||||
long_press_timeout: Duration,
|
||||
|
||||
// handles
|
||||
haptics: DialHaptics,
|
||||
events: mpsc::Receiver<events::RawInputEvent>,
|
||||
|
||||
// mutable state
|
||||
possible_long_press: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DialEvent {
|
||||
pub time: Duration,
|
||||
pub kind: DialEventKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DialEventKind {
|
||||
Connect,
|
||||
Disconnect,
|
||||
|
||||
Ignored,
|
||||
ButtonPress,
|
||||
ButtonRelease,
|
||||
Dial(i32),
|
||||
|
||||
/// NOTE: this is a synthetic event, and is _not_ directly provided by the
|
||||
/// dial itself.
|
||||
ButtonLongPress,
|
||||
}
|
||||
|
||||
impl DialDevice {
|
||||
pub fn new(long_press_timeout: Duration) -> Result<DialDevice> {
|
||||
let (events_tx, events_rx) = mpsc::channel();
|
||||
let (haptics_msg_tx, haptics_msg_rx) = mpsc::channel();
|
||||
|
||||
// TODO: interleave control events with regular events
|
||||
// (once we figure out what control events actually do...)
|
||||
|
||||
std::thread::spawn({
|
||||
let haptics_msg_tx = haptics_msg_tx.clone();
|
||||
let mut worker = events::EventsWorker::new(
|
||||
events::DialInputKind::MultiAxis,
|
||||
events_tx,
|
||||
haptics_msg_tx,
|
||||
);
|
||||
move || {
|
||||
worker.run().unwrap();
|
||||
eprintln!("the events worker died!");
|
||||
}
|
||||
});
|
||||
|
||||
std::thread::spawn({
|
||||
let mut worker = DialHapticsWorker::new(haptics_msg_rx)?;
|
||||
move || {
|
||||
worker.run().unwrap();
|
||||
eprintln!("the haptics worker died!");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(DialDevice {
|
||||
long_press_timeout,
|
||||
events: events_rx,
|
||||
haptics: DialHaptics::new(haptics_msg_tx)?,
|
||||
|
||||
possible_long_press: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Blocks until a new dial event comes occurs.
|
||||
// TODO?: rewrite code using async/await?
|
||||
// TODO?: "cheat" by exposing an async interface to the current next_event impl
|
||||
pub fn next_event(&mut self) -> Result<DialEvent> {
|
||||
let evt = if self.possible_long_press {
|
||||
self.events.recv_timeout(self.long_press_timeout)
|
||||
} else {
|
||||
self.events
|
||||
.recv()
|
||||
.map_err(|_| mpsc::RecvTimeoutError::Disconnected)
|
||||
};
|
||||
|
||||
let event = match evt {
|
||||
Ok(events::RawInputEvent::Event(_event_status, event)) => {
|
||||
// assert!(matches!(axis_status, ReadStatus::Success));
|
||||
let event =
|
||||
DialEvent::from_raw_evt(event.clone()).ok_or(Error::UnexpectedEvt(event))?;
|
||||
|
||||
match event.kind {
|
||||
DialEventKind::ButtonPress => self.possible_long_press = true,
|
||||
DialEventKind::ButtonRelease => self.possible_long_press = false,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
Ok(events::RawInputEvent::Connect) => {
|
||||
DialEvent {
|
||||
time: Duration::from_secs(0), // this could be improved...
|
||||
kind: DialEventKind::Connect,
|
||||
}
|
||||
}
|
||||
Ok(events::RawInputEvent::Disconnect) => {
|
||||
DialEvent {
|
||||
time: Duration::from_secs(0), // this could be improved...
|
||||
kind: DialEventKind::Disconnect,
|
||||
}
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
self.possible_long_press = false;
|
||||
DialEvent {
|
||||
time: Duration::from_secs(0), // this could be improved...
|
||||
kind: DialEventKind::ButtonLongPress,
|
||||
}
|
||||
}
|
||||
Err(_e) => panic!("Could not recv event"),
|
||||
};
|
||||
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
pub fn haptics(&self) -> &DialHaptics {
|
||||
&self.haptics
|
||||
}
|
||||
}
|
||||
|
||||
impl DialEvent {
|
||||
fn from_raw_evt(evt: evdev_rs::InputEvent) -> Option<DialEvent> {
|
||||
use evdev_rs::enums::*;
|
||||
|
||||
let evt_kind = match evt.event_type {
|
||||
EventType::EV_SYN | EventType::EV_MSC => DialEventKind::Ignored,
|
||||
EventType::EV_KEY => match evt.event_code {
|
||||
EventCode::EV_KEY(EV_KEY::BTN_0) => match evt.value {
|
||||
0 => DialEventKind::ButtonRelease,
|
||||
1 => DialEventKind::ButtonPress,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
},
|
||||
EventType::EV_REL => match evt.event_code {
|
||||
EventCode::EV_REL(EV_REL::REL_DIAL) => DialEventKind::Dial(evt.value),
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let evt = DialEvent {
|
||||
time: Duration::new(evt.time.tv_sec as u64, (evt.time.tv_usec * 1000) as u32),
|
||||
kind: evt_kind,
|
||||
};
|
||||
|
||||
Some(evt)
|
||||
}
|
||||
}
|
||||
14
src/main.rs
14
src/main.rs
@@ -1,3 +1,4 @@
|
||||
#![deny(unsafe_code)]
|
||||
#![allow(clippy::collapsible_if, clippy::new_without_default)]
|
||||
|
||||
pub mod common;
|
||||
@@ -37,16 +38,6 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
let active_notification = Notification::new()
|
||||
.hint(Hint::Resident(true))
|
||||
.hint(Hint::Category("device".into()))
|
||||
.timeout(Timeout::Never)
|
||||
.summary("Surface Dial")
|
||||
.body("Active!")
|
||||
.icon("media-optical") // it should be vaguely circular :P
|
||||
.show()
|
||||
.expect("Failed to send notification. NOTE: this daemon (probably) can't run as root!");
|
||||
|
||||
let (silent, msg, icon) = match terminate_rx.recv() {
|
||||
Ok(Ok(())) => (true, "".into(), ""),
|
||||
Ok(Err(e)) => {
|
||||
@@ -64,8 +55,6 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
active_notification.close();
|
||||
|
||||
if !silent {
|
||||
Notification::new()
|
||||
.hint(Hint::Transient(true))
|
||||
@@ -88,7 +77,6 @@ fn controller_main() -> Result<()> {
|
||||
let cfg = config::Config::from_disk()?;
|
||||
|
||||
let dial = DialDevice::new(std::time::Duration::from_millis(750))?;
|
||||
println!("Found the dial");
|
||||
|
||||
let mut controller = DialController::new(
|
||||
dial,
|
||||
|
||||
Reference in New Issue
Block a user