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()?;