1
0

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...
This commit is contained in:
Daniel Prilik
2020-11-05 10:26:11 -05:00
parent 905c33ad4a
commit fed2060872
11 changed files with 240 additions and 270 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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(())
}

View File

@@ -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<Msg>,
fake_input: FakeInput,
timeout: u64,
falloff: i32,
@@ -39,7 +38,6 @@ impl Worker {
pub fn new(msg: mpsc::Receiver<Msg>) -> 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])
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);

View File

@@ -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(())

View File

@@ -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(())
}

View File

@@ -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)?;
}

View File

@@ -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)?;
}

View File

@@ -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)"),
}
}
}

View File

@@ -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<UInputDevice>,
touchpad: ReentrantMutex<UInputDevice>,
}
lazy_static::lazy_static! {
static ref FAKE_KEYBOARD: ReentrantMutex<UInputDevice> = {
(|| -> 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<UInputDevice> = {
(|| -> io::Result<_> {
let touchpad = (|| -> io::Result<_> {
let device = Device::new().unwrap();
device.set_name("Surface Dial Virtual Touchpad");
@@ -115,13 +118,14 @@ 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) => {
@@ -134,29 +138,18 @@ 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 {}
}
fn kbd_syn_report(&self) -> io::Result<()> {
(FAKE_KEYBOARD.lock()).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)?;
pub fn key_click(keys: &[EV_KEY]) -> io::Result<()> {
key_press(keys)?;
key_release(keys)?;
Ok(())
}
pub fn key_press(&self, keys: &[EV_KEY]) -> io::Result<()> {
let keyboard = FAKE_KEYBOARD.lock();
pub fn key_press(keys: &[EV_KEY]) -> io::Result<()> {
let keyboard = FAKE_INPUTS.keyboard.lock();
for key in keys {
keyboard.write_event(&InputEvent {
@@ -166,12 +159,12 @@ impl FakeInput {
value: 1,
})?;
}
self.kbd_syn_report()?;
kbd_syn_report()?;
Ok(())
}
pub fn key_release(&self, keys: &[EV_KEY]) -> io::Result<()> {
let keyboard = FAKE_KEYBOARD.lock();
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 {
@@ -181,12 +174,12 @@ impl FakeInput {
value: 0,
})?;
}
self.kbd_syn_report()?;
kbd_syn_report()?;
Ok(())
}
pub fn scroll_step(&self, dir: ScrollStep) -> io::Result<()> {
let keyboard = FAKE_KEYBOARD.lock();
pub fn scroll_step(dir: ScrollStep) -> io::Result<()> {
let keyboard = FAKE_INPUTS.keyboard.lock();
// copied from my razer blackwidow chroma mouse
keyboard.write_event(&InputEvent {
@@ -207,16 +200,16 @@ impl FakeInput {
ScrollStep::Up => 120,
},
})?;
self.kbd_syn_report()?;
kbd_syn_report()?;
Ok(())
}
fn touch_syn_report(&self) -> io::Result<()> {
(FAKE_TOUCHPAD.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
fn touch_syn_report() -> io::Result<()> {
(FAKE_INPUTS.touchpad.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
}
pub fn scroll_mt_start(&self) -> 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))?;
@@ -231,7 +224,7 @@ impl FakeInput {
touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE))?;
}
self.touch_syn_report()?;
touch_syn_report()?;
{
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 1))?;
@@ -243,13 +236,13 @@ impl FakeInput {
touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 1))?;
}
self.touch_syn_report()?;
touch_syn_report()?;
Ok(())
}
pub fn scroll_mt_step(&self, delta: i32) -> io::Result<()> {
let touchpad = FAKE_TOUCHPAD.lock();
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!(
@@ -266,13 +259,13 @@ impl FakeInput {
touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE + delta))?;
self.touch_syn_report()?;
touch_syn_report()?;
Ok(())
}
pub fn scroll_mt_end(&self) -> io::Result<()> {
let touchpad = FAKE_TOUCHPAD.lock();
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))?;
@@ -282,11 +275,10 @@ impl FakeInput {
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()?;
touch_syn_report()?;
Ok(())
}
}
pub enum ScrollStep {
Up,

View File

@@ -11,15 +11,32 @@ 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::<Option<(String, &'static str)>>();
let (terminate_tx, terminate_rx) = mpsc::channel::<Result<()>>();
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));
}
}
});
std::thread::spawn({
let terminate_tx = terminate_tx;
move || {
let _ = terminate_tx.send(controller_main());
}
});
let handle = std::thread::spawn(move || {
let active_notification = Notification::new()
.hint(Hint::Resident(true))
.hint(Hint::Category("device".into()))
@@ -28,21 +45,28 @@ fn main() {
.body("Active!")
.icon("media-optical") // it should be vaguely circular :P
.show()
.expect("failed to send notification");
.expect("Failed to send notification. NOTE: this daemon (probably) can't run as root!");
let kill_notif = kill_notif_rx.recv();
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();
let (msg, icon) = match kill_notif {
Ok(None) => {
// shutdown immediately
std::process::exit(1);
}
Ok(Some((msg, icon))) => (msg, icon),
Err(_) => ("Unexpected Error".into(), "dialog-error"),
};
if !silent {
Notification::new()
.hint(Hint::Transient(true))
.hint(Hint::Category("device".into()))
@@ -52,33 +76,13 @@ fn main() {
.icon(icon)
.show()
.unwrap();
}
// cleaning up threads is hard...
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();
}
fn true_main() -> Result<()> {
fn controller_main() -> Result<()> {
println!("Started");
let cfg = config::Config::from_disk()?;