1
0

initial commit

This commit is contained in:
Daniel Prilik
2020-10-29 00:30:06 -04:00
commit c4039edc55
17 changed files with 783 additions and 0 deletions

34
src/common.rs Normal file
View File

@@ -0,0 +1,34 @@
pub enum DialDir {
Left,
Right,
}
pub struct ThresholdHelper {
sensitivity: i32,
pos: i32,
}
impl ThresholdHelper {
pub fn new(sensitivity: i32) -> ThresholdHelper {
ThresholdHelper {
sensitivity,
pos: 0,
}
}
pub fn update(&mut self, delta: i32) -> Option<DialDir> {
self.pos += delta;
if self.pos > self.sensitivity {
self.pos -= self.sensitivity;
return Some(DialDir::Right);
}
if self.pos < -self.sensitivity {
self.pos += self.sensitivity;
return Some(DialDir::Left);
}
None
}
}

View File

@@ -0,0 +1,145 @@
use std::cmp::Ordering;
use std::sync::mpsc;
use std::thread::JoinHandle;
use std::time::Duration;
use crate::controller::ControlMode;
use crate::fake_input::FakeInput;
use crate::DynResult;
use evdev_rs::enums::EV_KEY;
enum Msg {
Kill,
Delta(i32),
}
struct Worker {
msg: mpsc::Receiver<Msg>,
fake_input: FakeInput,
timeout: u64,
falloff: i32,
cap: i32,
deadzone: i32,
last_delta: i32,
velocity: i32,
}
impl Worker {
pub fn new(msg: mpsc::Receiver<Msg>) -> Worker {
Worker {
msg,
fake_input: FakeInput::new(),
// tweak these for "feel"
timeout: 5,
falloff: 10,
cap: 250,
deadzone: 10,
last_delta: 0,
velocity: 0,
}
}
pub fn run(&mut self) {
loop {
let falloff = self.velocity.abs() / self.falloff + 1;
match self.msg.recv_timeout(Duration::from_millis(self.timeout)) {
Ok(Msg::Kill) => return,
Ok(Msg::Delta(delta)) => {
// abrupt direction change!
if (delta < 0) != (self.last_delta < 0) {
self.velocity = 0
}
self.last_delta = delta;
self.velocity += delta
}
Err(mpsc::RecvTimeoutError::Timeout) => match self.velocity.cmp(&0) {
Ordering::Equal => {}
Ordering::Less => self.velocity += falloff,
Ordering::Greater => self.velocity -= falloff,
},
Err(other) => panic!("{}", other),
}
// clamp velocity within the cap bounds
if self.velocity > self.cap {
self.velocity = self.cap;
} else if self.velocity < -self.cap {
self.velocity = -self.cap;
}
if self.velocity.abs() < self.deadzone {
self.fake_input
.key_release(&[EV_KEY::KEY_LEFT, EV_KEY::KEY_RIGHT])
.unwrap();
continue;
}
match self.velocity.cmp(&0) {
Ordering::Equal => {}
Ordering::Less => self.fake_input.key_press(&[EV_KEY::KEY_LEFT]).unwrap(),
Ordering::Greater => self.fake_input.key_press(&[EV_KEY::KEY_RIGHT]).unwrap(),
}
eprintln!("{:?}", self.velocity);
}
}
}
/// A bit of a misnomer, since it's only left-right.
pub struct DPad {
_worker: JoinHandle<()>,
msg: mpsc::Sender<Msg>,
fake_input: FakeInput,
}
impl Drop for DPad {
fn drop(&mut self) {
let _ = self.msg.send(Msg::Kill);
}
}
impl Default for DPad {
fn default() -> Self {
Self::new()
}
}
impl DPad {
pub fn new() -> DPad {
let (msg_tx, msg_rx) = mpsc::channel();
let worker = std::thread::spawn(move || Worker::new(msg_rx).run());
DPad {
_worker: worker,
msg: msg_tx,
fake_input: FakeInput::new(),
}
}
}
impl ControlMode for DPad {
fn on_btn_press(&mut self) -> DynResult<()> {
eprintln!("space");
self.fake_input.key_click(&[EV_KEY::KEY_SPACE])?;
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
self.msg.send(Msg::Delta(delta))?;
Ok(())
}
}

View File

@@ -0,0 +1,48 @@
use crate::common::{DialDir, ThresholdHelper};
use crate::controller::ControlMode;
use crate::fake_input::FakeInput;
use crate::DynResult;
use evdev_rs::enums::EV_KEY;
pub struct Media {
thresh: ThresholdHelper,
fake_input: FakeInput,
}
impl Media {
pub fn new(sensitivity: i32) -> Media {
Media {
thresh: ThresholdHelper::new(sensitivity),
fake_input: FakeInput::new(),
}
}
}
impl ControlMode for Media {
fn on_btn_press(&mut self) -> DynResult<()> {
self.fake_input.key_click(&[EV_KEY::KEY_PLAYPAUSE])?;
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
match self.thresh.update(delta) {
Some(DialDir::Left) => {
eprintln!("next song");
self.fake_input.key_click(&[EV_KEY::KEY_NEXTSONG])?;
}
Some(DialDir::Right) => {
eprintln!("last song");
self.fake_input.key_click(&[EV_KEY::KEY_PREVIOUSSONG])?;
}
None => {}
}
Ok(())
}
}

View File

@@ -0,0 +1,7 @@
mod dpad;
mod media;
mod volume;
pub use self::dpad::*;
pub use self::media::*;
pub use self::volume::*;

View File

@@ -0,0 +1,54 @@
use crate::common::{DialDir, ThresholdHelper};
use crate::controller::ControlMode;
use crate::fake_input::FakeInput;
use crate::DynResult;
use evdev_rs::enums::EV_KEY;
pub struct Volume {
thresh: ThresholdHelper,
fake_input: FakeInput,
}
impl Volume {
pub fn new(sensitivity: i32) -> Volume {
Volume {
thresh: ThresholdHelper::new(sensitivity),
fake_input: FakeInput::new(),
}
}
}
impl ControlMode for Volume {
fn on_btn_press(&mut self) -> DynResult<()> {
// TODO: support double-click to mute
eprintln!("play/pause");
// self.fake_input.mute()?
self.fake_input.key_click(&[EV_KEY::KEY_PLAYPAUSE])?;
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
match self.thresh.update(delta) {
Some(DialDir::Left) => {
eprintln!("volume down");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])?
}
Some(DialDir::Right) => {
eprintln!("volume up");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])?
}
None => {}
}
Ok(())
}
}

42
src/controller/mod.rs Normal file
View File

@@ -0,0 +1,42 @@
use crate::DynResult;
use crate::dial_device::{DialDevice, DialEventKind};
pub mod controls;
pub trait ControlMode {
fn on_btn_press(&mut self) -> DynResult<()>;
fn on_btn_release(&mut self) -> DynResult<()>;
fn on_dial(&mut self, delta: i32) -> DynResult<()>;
}
pub struct DialController {
device: DialDevice,
mode: Box<dyn ControlMode>,
}
impl DialController {
pub fn new(device: DialDevice, default_mode: Box<dyn ControlMode>) -> DialController {
DialController {
mode: default_mode,
device,
}
}
pub fn run(&mut self) -> DynResult<()> {
loop {
let evt = self.device.next_event()?;
// TODO: press and hold + rotate to switch between modes
match evt.kind {
DialEventKind::Ignored => {}
DialEventKind::ButtonPress => self.mode.on_btn_press()?,
DialEventKind::ButtonRelease => self.mode.on_btn_release()?,
DialEventKind::Dial(delta) => self.mode.on_dial(delta)?,
}
}
}
}

111
src/dial_device.rs Normal file
View File

@@ -0,0 +1,111 @@
use std::fs;
use std::time::Duration;
use evdev_rs::{Device, InputEvent};
use crate::error::Error;
pub struct DialDevice {
// TODO: explore what the control channel can be used for...
_control: Device,
axis: Device,
}
#[derive(Debug)]
pub struct DialEvent {
pub time: Duration,
pub kind: DialEventKind,
}
#[derive(Debug)]
pub enum DialEventKind {
Ignored,
ButtonPress,
ButtonRelease,
Dial(i32),
}
impl DialDevice {
pub fn new() -> Result<DialDevice, crate::Error> {
let mut control = None;
let mut axis = None;
for e in fs::read_dir("/dev/input/").map_err(Error::Io)? {
let e = e.map_err(Error::Io)?;
if !e.file_name().to_str().unwrap().starts_with("event") {
continue;
}
let file = fs::File::open(e.path()).map_err(Error::Io)?;
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;
}
}
Ok(DialDevice {
_control: control.ok_or(Error::MissingDial)?,
axis: axis.ok_or(Error::MissingDial)?,
})
}
pub fn next_event(&self) -> Result<DialEvent, Error> {
// TODO: figure out how to interleave control events into the same event stream.
let (_axis_status, axis_evt) = self
.axis
.next_event(evdev_rs::ReadFlag::NORMAL)
.map_err(Error::Evdev)?;
// assert!(matches!(axis_status, ReadStatus::Success));
let event =
DialEvent::from_raw_evt(axis_evt.clone()).ok_or(Error::UnexpectedEvt(axis_evt))?;
Ok(event)
}
}
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)
}
}

26
src/error.rs Normal file
View File

@@ -0,0 +1,26 @@
use std::fmt;
use evdev_rs::InputEvent;
#[derive(Debug)]
pub enum Error {
MissingDial,
MultipleDials,
UnexpectedEvt(InputEvent),
Evdev(std::io::Error),
Io(std::io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::MissingDial => write!(f, "Could not find the Surface Dial"),
Error::MultipleDials => write!(f, "Found multiple dials"),
Error::UnexpectedEvt(evt) => write!(f, "Unexpected event: {:?}", evt),
Error::Evdev(e) => write!(f, "Evdev error: {:?}", e),
Error::Io(e) => write!(f, "Io error: {:?}", e),
}
}
}
impl std::error::Error for Error {}

102
src/fake_input.rs Normal file
View File

@@ -0,0 +1,102 @@
use std::io;
use evdev_rs::enums::*;
use evdev_rs::{Device, InputEvent, TimeVal, UInputDevice};
static mut FAKE_INPUT: Option<UInputDevice> = None;
fn get_fake_input() -> io::Result<&'static UInputDevice> {
if unsafe { FAKE_INPUT.is_none() } {
let device = Device::new().unwrap();
device.set_name("Surface Dial Virtual Input");
device.enable(&EventType::EV_SYN)?;
device.enable(&EventCode::EV_SYN(EV_SYN::SYN_REPORT))?;
device.enable(&EventType::EV_KEY)?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_LEFTSHIFT))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_MUTE))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_VOLUMEDOWN))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_VOLUMEUP))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_NEXTSONG))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_PLAYPAUSE))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_PREVIOUSSONG))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_LEFT))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_RIGHT))?;
device.enable(&EventCode::EV_KEY(EV_KEY::KEY_SPACE))?;
device.enable(&EventType::EV_MSC)?;
device.enable(&EventCode::EV_MSC(EV_MSC::MSC_SCAN))?;
unsafe { FAKE_INPUT = Some(UInputDevice::create_from_device(&device)?) }
}
unsafe { Ok(FAKE_INPUT.as_ref().unwrap()) }
}
#[non_exhaustive]
pub struct FakeInput {
uinput: &'static UInputDevice,
}
macro_rules! input_event {
($type:ident, $code:ident, $value:expr) => {
InputEvent {
time: TimeVal::new(0, 0),
event_code: EventCode::$type($type::$code),
event_type: EventType::$type,
value: $value,
}
};
}
impl Default for FakeInput {
fn default() -> Self {
Self::new()
}
}
impl FakeInput {
pub fn new() -> FakeInput {
FakeInput {
uinput: get_fake_input().expect("could not install fake input device"),
}
}
fn syn_report(&self) -> io::Result<()> {
self.uinput
.write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
}
pub fn key_click(&self, keys: &[EV_KEY]) -> io::Result<()> {
self.key_press(keys)?;
self.key_release(keys)?;
Ok(())
}
pub fn key_press(&self, keys: &[EV_KEY]) -> io::Result<()> {
for key in keys {
self.uinput.write_event(&InputEvent {
time: TimeVal::new(0, 0),
event_code: EventCode::EV_KEY(*key),
event_type: EventType::EV_KEY,
value: 1,
})?;
}
self.syn_report()?;
Ok(())
}
pub fn key_release(&self, keys: &[EV_KEY]) -> io::Result<()> {
for key in keys.iter().clone() {
self.uinput.write_event(&InputEvent {
time: TimeVal::new(0, 0),
event_code: EventCode::EV_KEY(*key),
event_type: EventType::EV_KEY,
value: 0,
})?;
}
self.syn_report()?;
Ok(())
}
}

24
src/main.rs Normal file
View File

@@ -0,0 +1,24 @@
mod common;
pub mod controller;
mod dial_device;
mod error;
mod fake_input;
pub type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
use crate::controller::DialController;
use crate::dial_device::DialDevice;
use crate::error::Error;
fn main() -> DynResult<()> {
let dial = DialDevice::new()?;
println!("Found the dial.");
let default_mode = Box::new(controller::controls::Volume::new(30));
// let default_mode = Box::new(controls::Media::new(50));
// let default_mode = Box::new(controls::DPad::new());
let mut controller = DialController::new(dial, default_mode);
controller.run()
}