From fed2060872d099b928c5c4b254954f1626f6fa49 Mon Sep 17 00:00:00 2001 From: Daniel Prilik Date: Thu, 5 Nov 2020 10:26:11 -0500 Subject: [PATCH] refactor fakeinput + invert main thread function - fakeinput is now just a series of free functions that access the global static, which makes more sense than having some arbitrary zero-sized-struct with associated methods on it. - instead of having the active notification run in its own thread, it now runs on the main thread, and the controller runs in a separate thread. This results in midly nicer code, at least IMO. Also, I've had to disable optimizations on the evdev_rs crate, as it seems to be segfaulting on release builds. Very spooky, but it's not something I'm super interested in debugging right now, so I'll just work around it. It's not like I need the _blazing fast performance_ from a Rust wrapper around a C library anyways... --- Cargo.toml | 10 + README.md | 6 +- src/controller/controls/media.rs | 22 +-- src/controller/controls/paddle.rs | 25 ++- src/controller/controls/scroll.rs | 18 +- src/controller/controls/scroll_mt.rs | 21 +-- src/controller/controls/volume.rs | 22 +-- src/controller/controls/zoom.rs | 16 +- src/error.rs | 2 + src/fake_input.rs | 270 +++++++++++++-------------- src/main.rs | 98 +++++----- 11 files changed, 240 insertions(+), 270 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4f9eb0..c85a47f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,13 @@ lazy_static = "1.4" notify-rust = "4" parking_lot = "0.11.0" signal-hook = "0.1.16" + +# HACK: Using >1 virtual uinput devices will segfault in release builds. +# +# While spooky, this isn't a show-stopper for us Pragmatic Programmers™, as we +# can simply disable optimizations for `evdev-rs` and have things work Okay™. +# +# That said, I would like to find some time to narrow down why this is happening +# and fix it. Maybe later... +[profile.release.package.evdev-rs] +opt-level = 0 diff --git a/README.md b/README.md index 2f63012..156367e 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,10 @@ It would be cool to create some sort of GUI overlay (similar to the Windows one) - Operating Modes - [x] Volume Controls - [x] Media Controls - - [x] D-Pad (emulated left, right, and space key) - - [x] Scrolling / Zooming + - [x] Scrolling - using a virtual mouse-wheel + - [x] Scrolling - using a virtual touchpad (for [smoother scrolling](https://who-t.blogspot.com/2020/04/high-resolution-wheel-scrolling-in.html)) + - [x] Zooming + - [x] [Paddle](https://www.google.com/search?q=arkanoid+paddle) (emulated left, right, and space key) - [ ] \(meta\) custom modes specified via config file(s) - [x] Dynamically switch between operating modes - [x] Using some-sort of on-device mechanism (e.g: long-press) diff --git a/src/controller/controls/media.rs b/src/controller/controls/media.rs index 323ee3a..767fe45 100644 --- a/src/controller/controls/media.rs +++ b/src/controller/controls/media.rs @@ -1,19 +1,15 @@ use crate::controller::{ControlMode, ControlModeMeta}; use crate::dial_device::DialHaptics; use crate::error::{Error, Result}; -use crate::fake_input::FakeInput; +use crate::fake_input; use evdev_rs::enums::EV_KEY; -pub struct Media { - fake_input: FakeInput, -} +pub struct Media {} impl Media { pub fn new() -> Media { - Media { - fake_input: FakeInput::new(), - } + Media {} } } @@ -35,23 +31,17 @@ impl ControlMode for Media { } fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> { - self.fake_input - .key_click(&[EV_KEY::KEY_PLAYPAUSE]) - .map_err(Error::Evdev)?; + fake_input::key_click(&[EV_KEY::KEY_PLAYPAUSE]).map_err(Error::Evdev)?; Ok(()) } fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> { if delta > 0 { eprintln!("next song"); - self.fake_input - .key_click(&[EV_KEY::KEY_NEXTSONG]) - .map_err(Error::Evdev)?; + fake_input::key_click(&[EV_KEY::KEY_NEXTSONG]).map_err(Error::Evdev)?; } else { eprintln!("last song"); - self.fake_input - .key_click(&[EV_KEY::KEY_PREVIOUSSONG]) - .map_err(Error::Evdev)?; + fake_input::key_click(&[EV_KEY::KEY_PREVIOUSSONG]).map_err(Error::Evdev)?; } Ok(()) } diff --git a/src/controller/controls/paddle.rs b/src/controller/controls/paddle.rs index c406bb4..c2d7422 100644 --- a/src/controller/controls/paddle.rs +++ b/src/controller/controls/paddle.rs @@ -6,7 +6,7 @@ use std::time::Duration; use crate::controller::{ControlMode, ControlModeMeta}; use crate::dial_device::DialHaptics; use crate::error::Result; -use crate::fake_input::FakeInput; +use crate::fake_input; use evdev_rs::enums::EV_KEY; @@ -23,7 +23,6 @@ enum Msg { struct Worker { msg: mpsc::Receiver, - fake_input: FakeInput, timeout: u64, falloff: i32, @@ -39,7 +38,6 @@ impl Worker { pub fn new(msg: mpsc::Receiver) -> Worker { Worker { msg, - fake_input: FakeInput::new(), // tweak these for "feel" timeout: 5, @@ -70,13 +68,16 @@ impl Worker { Ok(Msg::Enabled(enabled)) => { self.enabled = enabled; if !enabled { - self.fake_input - .key_release(&[EV_KEY::KEY_SPACE, EV_KEY::KEY_LEFT, EV_KEY::KEY_RIGHT]) - .unwrap() + fake_input::key_release(&[ + EV_KEY::KEY_SPACE, + EV_KEY::KEY_LEFT, + EV_KEY::KEY_RIGHT, + ]) + .unwrap() } } - Ok(Msg::ButtonDown) => self.fake_input.key_press(&[EV_KEY::KEY_SPACE]).unwrap(), - Ok(Msg::ButtonUp) => self.fake_input.key_release(&[EV_KEY::KEY_SPACE]).unwrap(), + Ok(Msg::ButtonDown) => fake_input::key_press(&[EV_KEY::KEY_SPACE]).unwrap(), + Ok(Msg::ButtonUp) => fake_input::key_release(&[EV_KEY::KEY_SPACE]).unwrap(), Ok(Msg::Delta(delta)) => { // abrupt direction change! if (delta < 0) != (self.last_delta < 0) { @@ -102,16 +103,14 @@ impl Worker { } if self.velocity.abs() < self.deadzone { - self.fake_input - .key_release(&[EV_KEY::KEY_LEFT, EV_KEY::KEY_RIGHT]) - .unwrap(); + 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(), + Ordering::Less => fake_input::key_press(&[EV_KEY::KEY_LEFT]).unwrap(), + Ordering::Greater => fake_input::key_press(&[EV_KEY::KEY_RIGHT]).unwrap(), } // eprintln!("{:?}", self.velocity); diff --git a/src/controller/controls/scroll.rs b/src/controller/controls/scroll.rs index 50e8af0..c313bc3 100644 --- a/src/controller/controls/scroll.rs +++ b/src/controller/controls/scroll.rs @@ -1,17 +1,13 @@ use crate::controller::{ControlMode, ControlModeMeta}; use crate::dial_device::DialHaptics; use crate::error::{Error, Result}; -use crate::fake_input::{FakeInput, ScrollStep}; +use crate::fake_input::{self, ScrollStep}; -pub struct Scroll { - fake_input: FakeInput, -} +pub struct Scroll {} impl Scroll { pub fn new() -> Scroll { - Scroll { - fake_input: FakeInput::new(), - } + Scroll {} } } @@ -39,14 +35,10 @@ impl ControlMode for Scroll { fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> { if delta > 0 { eprintln!("scroll down"); - self.fake_input - .scroll_step(ScrollStep::Down) - .map_err(Error::Evdev)?; + fake_input::scroll_step(ScrollStep::Down).map_err(Error::Evdev)?; } else { eprintln!("scroll up"); - self.fake_input - .scroll_step(ScrollStep::Up) - .map_err(Error::Evdev)?; + fake_input::scroll_step(ScrollStep::Up).map_err(Error::Evdev)?; } Ok(()) diff --git a/src/controller/controls/scroll_mt.rs b/src/controller/controls/scroll_mt.rs index ce23101..cbebac9 100644 --- a/src/controller/controls/scroll_mt.rs +++ b/src/controller/controls/scroll_mt.rs @@ -1,20 +1,15 @@ use crate::controller::{ControlMode, ControlModeMeta}; use crate::dial_device::DialHaptics; use crate::error::{Error, Result}; -use crate::fake_input::FakeInput; +use crate::fake_input; pub struct ScrollMT { acc_delta: i32, - - fake_input: FakeInput, } impl ScrollMT { pub fn new() -> ScrollMT { - ScrollMT { - acc_delta: 0, - fake_input: FakeInput::new(), - } + ScrollMT { acc_delta: 0 } } } @@ -30,33 +25,31 @@ impl ControlMode for ScrollMT { haptics.set_mode(false, Some(3600))?; self.acc_delta = 0; - self.fake_input.scroll_mt_start().map_err(Error::Evdev)?; + fake_input::scroll_mt_start().map_err(Error::Evdev)?; Ok(()) } fn on_end(&mut self, _haptics: &DialHaptics) -> Result<()> { - self.fake_input.scroll_mt_end().map_err(Error::Evdev)?; + fake_input::scroll_mt_end().map_err(Error::Evdev)?; Ok(()) } // HACK: the button will reset the scroll event, which sometimes helps fn on_btn_press(&mut self, _: &DialHaptics) -> Result<()> { - self.fake_input.scroll_mt_end().map_err(Error::Evdev)?; + fake_input::scroll_mt_end().map_err(Error::Evdev)?; Ok(()) } fn on_btn_release(&mut self, _haptics: &DialHaptics) -> Result<()> { - self.fake_input.scroll_mt_start().map_err(Error::Evdev)?; + fake_input::scroll_mt_start().map_err(Error::Evdev)?; Ok(()) } fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> { self.acc_delta += delta; - self.fake_input - .scroll_mt_step(self.acc_delta) - .map_err(Error::Evdev)?; + fake_input::scroll_mt_step(self.acc_delta).map_err(Error::Evdev)?; Ok(()) } diff --git a/src/controller/controls/volume.rs b/src/controller/controls/volume.rs index e337b84..ef28ffe 100644 --- a/src/controller/controls/volume.rs +++ b/src/controller/controls/volume.rs @@ -1,19 +1,15 @@ use crate::controller::{ControlMode, ControlModeMeta}; use crate::dial_device::DialHaptics; use crate::error::{Error, Result}; -use crate::fake_input::FakeInput; +use crate::fake_input; use evdev_rs::enums::EV_KEY; -pub struct Volume { - fake_input: FakeInput, -} +pub struct Volume {} impl Volume { pub fn new() -> Volume { - Volume { - fake_input: FakeInput::new(), - } + Volume {} } } @@ -37,23 +33,19 @@ impl ControlMode for Volume { fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> { eprintln!("play/pause"); - // self.fake_input.mute()? - self.fake_input - .key_click(&[EV_KEY::KEY_PLAYPAUSE]) - .map_err(Error::Evdev)?; + // fake_input::mute()? + fake_input::key_click(&[EV_KEY::KEY_PLAYPAUSE]).map_err(Error::Evdev)?; Ok(()) } fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> { if delta > 0 { eprintln!("volume up"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP]) + fake_input::key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP]) .map_err(Error::Evdev)?; } else { eprintln!("volume down"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN]) + fake_input::key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN]) .map_err(Error::Evdev)?; } diff --git a/src/controller/controls/zoom.rs b/src/controller/controls/zoom.rs index a622c26..f1379ba 100644 --- a/src/controller/controls/zoom.rs +++ b/src/controller/controls/zoom.rs @@ -1,19 +1,15 @@ use crate::controller::{ControlMode, ControlModeMeta}; use crate::dial_device::DialHaptics; use crate::error::{Error, Result}; -use crate::fake_input::FakeInput; +use crate::fake_input; use evdev_rs::enums::EV_KEY; -pub struct Zoom { - fake_input: FakeInput, -} +pub struct Zoom {} impl Zoom { pub fn new() -> Zoom { - Zoom { - fake_input: FakeInput::new(), - } + Zoom {} } } @@ -41,13 +37,11 @@ impl ControlMode for Zoom { fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> { if delta > 0 { eprintln!("zoom in"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL]) + fake_input::key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL]) .map_err(Error::Evdev)?; } else { eprintln!("zoom out"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS]) + fake_input::key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS]) .map_err(Error::Evdev)?; } diff --git a/src/error.rs b/src/error.rs index be16810..e56a110 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,6 +16,7 @@ pub enum Error { UnexpectedEvt(InputEvent), Evdev(io::Error), Notif(notify_rust::error::Error), + TermSig, } impl fmt::Display for Error { @@ -30,6 +31,7 @@ impl fmt::Display for Error { Error::UnexpectedEvt(evt) => write!(f, "Unexpected event: {:?}", evt), Error::Evdev(e) => write!(f, "Evdev error: {}", e), Error::Notif(e) => write!(f, "Notification error: {}", e), + Error::TermSig => write!(f, "Received termination signal (either SIGTERM or SIGINT)"), } } } diff --git a/src/fake_input.rs b/src/fake_input.rs index e30a155..383e81f 100644 --- a/src/fake_input.rs +++ b/src/fake_input.rs @@ -5,13 +5,18 @@ use evdev_rs::{Device, InputEvent, TimeVal, UInputDevice}; use parking_lot::ReentrantMutex; // this should be a fairly high number, as the axis is from 0..(MT_BASELINE*2) -const MT_BASELINE: i32 = std::i32::MAX / 4; +const MT_BASELINE: i32 = std::i32::MAX / 8; // higher = more sensitive const MT_SENSITIVITY: i32 = 64; +pub struct FakeInputs { + keyboard: ReentrantMutex, + touchpad: ReentrantMutex, +} + lazy_static::lazy_static! { - static ref FAKE_KEYBOARD: ReentrantMutex = { - (|| -> io::Result<_> { + pub static ref FAKE_INPUTS: FakeInputs = { + let keyboard = (|| -> io::Result<_> { let device = Device::new().unwrap(); device.set_name("Surface Dial Virtual Keyboard/Mouse"); @@ -48,11 +53,9 @@ lazy_static::lazy_static! { } Ok(ReentrantMutex::new(UInputDevice::create_from_device(&device)?)) - })().expect("failed to install virtual mouse/keyboard device") - }; + })().expect("failed to install virtual mouse/keyboard device"); - static ref FAKE_TOUCHPAD: ReentrantMutex = { - (|| -> io::Result<_> { + let touchpad = (|| -> io::Result<_> { let device = Device::new().unwrap(); device.set_name("Surface Dial Virtual Touchpad"); @@ -115,14 +118,15 @@ lazy_static::lazy_static! { } Ok(ReentrantMutex::new(UInputDevice::create_from_device(&device)?)) - })().expect("failed to install virtual touchpad device") + })().expect("failed to install virtual touchpad device"); + + FakeInputs { + keyboard, + touchpad + } }; - } -#[non_exhaustive] -pub struct FakeInput {} - macro_rules! input_event { ($type:ident, $code:ident, $value:expr) => { InputEvent { @@ -134,158 +138,146 @@ macro_rules! input_event { }; } -impl Default for FakeInput { - fn default() -> Self { - Self::new() - } +fn kbd_syn_report() -> io::Result<()> { + (FAKE_INPUTS.keyboard.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0)) } -impl FakeInput { - pub fn new() -> FakeInput { - FakeInput {} - } +pub fn key_click(keys: &[EV_KEY]) -> io::Result<()> { + key_press(keys)?; + key_release(keys)?; + Ok(()) +} - fn kbd_syn_report(&self) -> io::Result<()> { - (FAKE_KEYBOARD.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0)) - } +pub fn key_press(keys: &[EV_KEY]) -> io::Result<()> { + let keyboard = FAKE_INPUTS.keyboard.lock(); - 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<()> { - let keyboard = FAKE_KEYBOARD.lock(); - - for key in keys { - keyboard.write_event(&InputEvent { - time: TimeVal::new(0, 0), - event_code: EventCode::EV_KEY(*key), - event_type: EventType::EV_KEY, - value: 1, - })?; - } - self.kbd_syn_report()?; - Ok(()) - } - - pub fn key_release(&self, keys: &[EV_KEY]) -> io::Result<()> { - let keyboard = FAKE_KEYBOARD.lock(); - - for key in keys.iter().clone() { - keyboard.write_event(&InputEvent { - time: TimeVal::new(0, 0), - event_code: EventCode::EV_KEY(*key), - event_type: EventType::EV_KEY, - value: 0, - })?; - } - self.kbd_syn_report()?; - Ok(()) - } - - pub fn scroll_step(&self, dir: ScrollStep) -> io::Result<()> { - let keyboard = FAKE_KEYBOARD.lock(); - - // copied from my razer blackwidow chroma mouse + for key in keys { keyboard.write_event(&InputEvent { time: TimeVal::new(0, 0), - event_code: EventCode::EV_REL(EV_REL::REL_WHEEL), - event_type: EventType::EV_REL, - value: match dir { - ScrollStep::Down => -1, - ScrollStep::Up => 1, - }, + event_code: EventCode::EV_KEY(*key), + event_type: EventType::EV_KEY, + value: 1, })?; + } + kbd_syn_report()?; + Ok(()) +} + +pub fn key_release(keys: &[EV_KEY]) -> io::Result<()> { + let keyboard = FAKE_INPUTS.keyboard.lock(); + + for key in keys.iter().clone() { keyboard.write_event(&InputEvent { time: TimeVal::new(0, 0), - event_code: EventCode::EV_REL(EV_REL::REL_WHEEL_HI_RES), - event_type: EventType::EV_REL, - value: match dir { - ScrollStep::Down => -120, - ScrollStep::Up => 120, - }, + event_code: EventCode::EV_KEY(*key), + event_type: EventType::EV_KEY, + value: 0, })?; - self.kbd_syn_report()?; - Ok(()) } + kbd_syn_report()?; + Ok(()) +} - fn touch_syn_report(&self) -> io::Result<()> { - (FAKE_TOUCHPAD.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0)) - } +pub fn scroll_step(dir: ScrollStep) -> io::Result<()> { + let keyboard = FAKE_INPUTS.keyboard.lock(); - pub fn scroll_mt_start(&self) -> io::Result<()> { - let touchpad = FAKE_TOUCHPAD.lock(); + // copied from my razer blackwidow chroma mouse + keyboard.write_event(&InputEvent { + time: TimeVal::new(0, 0), + event_code: EventCode::EV_REL(EV_REL::REL_WHEEL), + event_type: EventType::EV_REL, + value: match dir { + ScrollStep::Down => -1, + ScrollStep::Up => 1, + }, + })?; + keyboard.write_event(&InputEvent { + time: TimeVal::new(0, 0), + event_code: EventCode::EV_REL(EV_REL::REL_WHEEL_HI_RES), + event_type: EventType::EV_REL, + value: match dir { + ScrollStep::Down => -120, + ScrollStep::Up => 120, + }, + })?; + kbd_syn_report()?; + Ok(()) +} - { - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, 1))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_X, MT_BASELINE))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_Y, MT_BASELINE))?; +fn touch_syn_report() -> io::Result<()> { + (FAKE_INPUTS.touchpad.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0)) +} - touchpad.write_event(&input_event!(EV_KEY, BTN_TOUCH, 1))?; - touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_FINGER, 1))?; - - touchpad.write_event(&input_event!(EV_ABS, ABS_X, MT_BASELINE))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE))?; - } - - self.touch_syn_report()?; - - { - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 1))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, 2))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_X, MT_BASELINE / 2))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_Y, MT_BASELINE))?; - - touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_FINGER, 0))?; - touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 1))?; - } - - self.touch_syn_report()?; - - Ok(()) - } - - pub fn scroll_mt_step(&self, delta: i32) -> io::Result<()> { - let touchpad = FAKE_TOUCHPAD.lock(); +pub fn scroll_mt_start() -> io::Result<()> { + let touchpad = FAKE_INPUTS.touchpad.lock(); + { touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?; - touchpad.write_event(&input_event!( - EV_ABS, - ABS_MT_POSITION_Y, - MT_BASELINE + delta - ))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 1))?; - touchpad.write_event(&input_event!( - EV_ABS, - ABS_MT_POSITION_Y, - MT_BASELINE + delta - ))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, 1))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_X, MT_BASELINE))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_Y, MT_BASELINE))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE + delta))?; + touchpad.write_event(&input_event!(EV_KEY, BTN_TOUCH, 1))?; + touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_FINGER, 1))?; - self.touch_syn_report()?; - - Ok(()) + touchpad.write_event(&input_event!(EV_ABS, ABS_X, MT_BASELINE))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE))?; } - pub fn scroll_mt_end(&self) -> io::Result<()> { - let touchpad = FAKE_TOUCHPAD.lock(); + touch_syn_report()?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, -1))?; + { touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 1))?; - touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, -1))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, 2))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_X, MT_BASELINE / 2))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_Y, MT_BASELINE))?; - touchpad.write_event(&input_event!(EV_KEY, BTN_TOUCH, 0))?; - touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 0))?; - - self.touch_syn_report()?; - - Ok(()) + touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_FINGER, 0))?; + touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 1))?; } + + touch_syn_report()?; + + Ok(()) +} + +pub fn scroll_mt_step(delta: i32) -> io::Result<()> { + let touchpad = FAKE_INPUTS.touchpad.lock(); + + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?; + touchpad.write_event(&input_event!( + EV_ABS, + ABS_MT_POSITION_Y, + MT_BASELINE + delta + ))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 1))?; + touchpad.write_event(&input_event!( + EV_ABS, + ABS_MT_POSITION_Y, + MT_BASELINE + delta + ))?; + + touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE + delta))?; + + touch_syn_report()?; + + Ok(()) +} + +pub fn scroll_mt_end() -> io::Result<()> { + let touchpad = FAKE_INPUTS.touchpad.lock(); + + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, -1))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 1))?; + touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, -1))?; + + touchpad.write_event(&input_event!(EV_KEY, BTN_TOUCH, 0))?; + touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 0))?; + + touch_syn_report()?; + + Ok(()) } pub enum ScrollStep { diff --git a/src/main.rs b/src/main.rs index 4bcbbb5..850d4fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,38 +11,62 @@ use std::sync::mpsc; use crate::controller::DialController; use crate::dial_device::DialDevice; -use crate::error::Result; +use crate::error::{Error, Result}; use notify_rust::{Hint, Notification, Timeout}; use signal_hook::{iterator::Signals, SIGINT, SIGTERM}; fn main() { - let (kill_notif_tx, kill_notif_rx) = mpsc::channel::>(); + let (terminate_tx, terminate_rx) = mpsc::channel::>(); - let handle = std::thread::spawn(move || { - 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"); - - let kill_notif = kill_notif_rx.recv(); - - active_notification.close(); - - let (msg, icon) = match kill_notif { - Ok(None) => { - // shutdown immediately - std::process::exit(1); + std::thread::spawn({ + let terminate_tx = terminate_tx.clone(); + move || { + let signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); + for sig in signals.forever() { + eprintln!("received signal {:?}", sig); + let _ = terminate_tx.send(Err(Error::TermSig)); } - Ok(Some((msg, icon))) => (msg, icon), - Err(_) => ("Unexpected Error".into(), "dialog-error"), - }; + } + }); + std::thread::spawn({ + let terminate_tx = terminate_tx; + move || { + let _ = terminate_tx.send(controller_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)) => { + println!("Error: {}", e); + match e { + Error::TermSig => (false, "Terminated!".into(), "dialog-warning"), + // HACK: silently exit if the dial disconnects + Error::Evdev(e) if e.raw_os_error() == Some(19) => (true, "".into(), ""), + other => (false, format!("Error: {}", other), "dialog-error"), + } + } + Err(_) => { + println!("Error: Unexpected Error"); + (false, "Unexpected Error".into(), "dialog-error") + } + }; + + active_notification.close(); + + if !silent { Notification::new() .hint(Hint::Transient(true)) .hint(Hint::Category("device".into())) @@ -52,33 +76,13 @@ fn main() { .icon(icon) .show() .unwrap(); - - std::process::exit(1); - }); - - std::thread::spawn({ - let kill_notif_tx = kill_notif_tx.clone(); - move || { - let signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); - for sig in signals.forever() { - eprintln!("received signal {:?}", sig); - match kill_notif_tx.send(Some(("Terminated!".into(), "dialog-warning"))) { - Ok(_) => {} - Err(_) => std::process::exit(1), - } - } - } - }); - - if let Err(e) = true_main() { - println!("{}", e); } - let _ = kill_notif_tx.send(None); // silently shut down - let _ = handle.join(); + // cleaning up threads is hard... + std::process::exit(1); } -fn true_main() -> Result<()> { +fn controller_main() -> Result<()> { println!("Started"); let cfg = config::Config::from_disk()?;