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:
10
Cargo.toml
10
Cargo.toml
@@ -13,3 +13,13 @@ lazy_static = "1.4"
|
|||||||
notify-rust = "4"
|
notify-rust = "4"
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
signal-hook = "0.1.16"
|
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
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ It would be cool to create some sort of GUI overlay (similar to the Windows one)
|
|||||||
- Operating Modes
|
- Operating Modes
|
||||||
- [x] Volume Controls
|
- [x] Volume Controls
|
||||||
- [x] Media Controls
|
- [x] Media Controls
|
||||||
- [x] D-Pad (emulated left, right, and space key)
|
- [x] Scrolling - using a virtual mouse-wheel
|
||||||
- [x] Scrolling / Zooming
|
- [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)
|
- [ ] \(meta\) custom modes specified via config file(s)
|
||||||
- [x] Dynamically switch between operating modes
|
- [x] Dynamically switch between operating modes
|
||||||
- [x] Using some-sort of on-device mechanism (e.g: long-press)
|
- [x] Using some-sort of on-device mechanism (e.g: long-press)
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
use crate::controller::{ControlMode, ControlModeMeta};
|
use crate::controller::{ControlMode, ControlModeMeta};
|
||||||
use crate::dial_device::DialHaptics;
|
use crate::dial_device::DialHaptics;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::fake_input::FakeInput;
|
use crate::fake_input;
|
||||||
|
|
||||||
use evdev_rs::enums::EV_KEY;
|
use evdev_rs::enums::EV_KEY;
|
||||||
|
|
||||||
pub struct Media {
|
pub struct Media {}
|
||||||
fake_input: FakeInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Media {
|
impl Media {
|
||||||
pub fn new() -> Media {
|
pub fn new() -> Media {
|
||||||
Media {
|
Media {}
|
||||||
fake_input: FakeInput::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,23 +31,17 @@ impl ControlMode for Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_PLAYPAUSE]).map_err(Error::Evdev)?;
|
||||||
.key_click(&[EV_KEY::KEY_PLAYPAUSE])
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
eprintln!("next song");
|
eprintln!("next song");
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_NEXTSONG]).map_err(Error::Evdev)?;
|
||||||
.key_click(&[EV_KEY::KEY_NEXTSONG])
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("last song");
|
eprintln!("last song");
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_PREVIOUSSONG]).map_err(Error::Evdev)?;
|
||||||
.key_click(&[EV_KEY::KEY_PREVIOUSSONG])
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::time::Duration;
|
|||||||
use crate::controller::{ControlMode, ControlModeMeta};
|
use crate::controller::{ControlMode, ControlModeMeta};
|
||||||
use crate::dial_device::DialHaptics;
|
use crate::dial_device::DialHaptics;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::fake_input::FakeInput;
|
use crate::fake_input;
|
||||||
|
|
||||||
use evdev_rs::enums::EV_KEY;
|
use evdev_rs::enums::EV_KEY;
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ enum Msg {
|
|||||||
|
|
||||||
struct Worker {
|
struct Worker {
|
||||||
msg: mpsc::Receiver<Msg>,
|
msg: mpsc::Receiver<Msg>,
|
||||||
fake_input: FakeInput,
|
|
||||||
|
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
falloff: i32,
|
falloff: i32,
|
||||||
@@ -39,7 +38,6 @@ impl Worker {
|
|||||||
pub fn new(msg: mpsc::Receiver<Msg>) -> Worker {
|
pub fn new(msg: mpsc::Receiver<Msg>) -> Worker {
|
||||||
Worker {
|
Worker {
|
||||||
msg,
|
msg,
|
||||||
fake_input: FakeInput::new(),
|
|
||||||
|
|
||||||
// tweak these for "feel"
|
// tweak these for "feel"
|
||||||
timeout: 5,
|
timeout: 5,
|
||||||
@@ -70,13 +68,16 @@ impl Worker {
|
|||||||
Ok(Msg::Enabled(enabled)) => {
|
Ok(Msg::Enabled(enabled)) => {
|
||||||
self.enabled = enabled;
|
self.enabled = enabled;
|
||||||
if !enabled {
|
if !enabled {
|
||||||
self.fake_input
|
fake_input::key_release(&[
|
||||||
.key_release(&[EV_KEY::KEY_SPACE, EV_KEY::KEY_LEFT, EV_KEY::KEY_RIGHT])
|
EV_KEY::KEY_SPACE,
|
||||||
.unwrap()
|
EV_KEY::KEY_LEFT,
|
||||||
|
EV_KEY::KEY_RIGHT,
|
||||||
|
])
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Msg::ButtonDown) => self.fake_input.key_press(&[EV_KEY::KEY_SPACE]).unwrap(),
|
Ok(Msg::ButtonDown) => fake_input::key_press(&[EV_KEY::KEY_SPACE]).unwrap(),
|
||||||
Ok(Msg::ButtonUp) => self.fake_input.key_release(&[EV_KEY::KEY_SPACE]).unwrap(),
|
Ok(Msg::ButtonUp) => fake_input::key_release(&[EV_KEY::KEY_SPACE]).unwrap(),
|
||||||
Ok(Msg::Delta(delta)) => {
|
Ok(Msg::Delta(delta)) => {
|
||||||
// abrupt direction change!
|
// abrupt direction change!
|
||||||
if (delta < 0) != (self.last_delta < 0) {
|
if (delta < 0) != (self.last_delta < 0) {
|
||||||
@@ -102,16 +103,14 @@ impl Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.velocity.abs() < self.deadzone {
|
if self.velocity.abs() < self.deadzone {
|
||||||
self.fake_input
|
fake_input::key_release(&[EV_KEY::KEY_LEFT, EV_KEY::KEY_RIGHT]).unwrap();
|
||||||
.key_release(&[EV_KEY::KEY_LEFT, EV_KEY::KEY_RIGHT])
|
|
||||||
.unwrap();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.velocity.cmp(&0) {
|
match self.velocity.cmp(&0) {
|
||||||
Ordering::Equal => {}
|
Ordering::Equal => {}
|
||||||
Ordering::Less => self.fake_input.key_press(&[EV_KEY::KEY_LEFT]).unwrap(),
|
Ordering::Less => fake_input::key_press(&[EV_KEY::KEY_LEFT]).unwrap(),
|
||||||
Ordering::Greater => self.fake_input.key_press(&[EV_KEY::KEY_RIGHT]).unwrap(),
|
Ordering::Greater => fake_input::key_press(&[EV_KEY::KEY_RIGHT]).unwrap(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// eprintln!("{:?}", self.velocity);
|
// eprintln!("{:?}", self.velocity);
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
use crate::controller::{ControlMode, ControlModeMeta};
|
use crate::controller::{ControlMode, ControlModeMeta};
|
||||||
use crate::dial_device::DialHaptics;
|
use crate::dial_device::DialHaptics;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::fake_input::{FakeInput, ScrollStep};
|
use crate::fake_input::{self, ScrollStep};
|
||||||
|
|
||||||
pub struct Scroll {
|
pub struct Scroll {}
|
||||||
fake_input: FakeInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scroll {
|
impl Scroll {
|
||||||
pub fn new() -> Scroll {
|
pub fn new() -> Scroll {
|
||||||
Scroll {
|
Scroll {}
|
||||||
fake_input: FakeInput::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,14 +35,10 @@ impl ControlMode for Scroll {
|
|||||||
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
eprintln!("scroll down");
|
eprintln!("scroll down");
|
||||||
self.fake_input
|
fake_input::scroll_step(ScrollStep::Down).map_err(Error::Evdev)?;
|
||||||
.scroll_step(ScrollStep::Down)
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("scroll up");
|
eprintln!("scroll up");
|
||||||
self.fake_input
|
fake_input::scroll_step(ScrollStep::Up).map_err(Error::Evdev)?;
|
||||||
.scroll_step(ScrollStep::Up)
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
use crate::controller::{ControlMode, ControlModeMeta};
|
use crate::controller::{ControlMode, ControlModeMeta};
|
||||||
use crate::dial_device::DialHaptics;
|
use crate::dial_device::DialHaptics;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::fake_input::FakeInput;
|
use crate::fake_input;
|
||||||
|
|
||||||
pub struct ScrollMT {
|
pub struct ScrollMT {
|
||||||
acc_delta: i32,
|
acc_delta: i32,
|
||||||
|
|
||||||
fake_input: FakeInput,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollMT {
|
impl ScrollMT {
|
||||||
pub fn new() -> ScrollMT {
|
pub fn new() -> ScrollMT {
|
||||||
ScrollMT {
|
ScrollMT { acc_delta: 0 }
|
||||||
acc_delta: 0,
|
|
||||||
fake_input: FakeInput::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,33 +25,31 @@ impl ControlMode for ScrollMT {
|
|||||||
haptics.set_mode(false, Some(3600))?;
|
haptics.set_mode(false, Some(3600))?;
|
||||||
self.acc_delta = 0;
|
self.acc_delta = 0;
|
||||||
|
|
||||||
self.fake_input.scroll_mt_start().map_err(Error::Evdev)?;
|
fake_input::scroll_mt_start().map_err(Error::Evdev)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_end(&mut self, _haptics: &DialHaptics) -> Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: the button will reset the scroll event, which sometimes helps
|
// HACK: the button will reset the scroll event, which sometimes helps
|
||||||
|
|
||||||
fn on_btn_press(&mut self, _: &DialHaptics) -> Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_btn_release(&mut self, _haptics: &DialHaptics) -> Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
||||||
self.acc_delta += delta;
|
self.acc_delta += delta;
|
||||||
self.fake_input
|
fake_input::scroll_mt_step(self.acc_delta).map_err(Error::Evdev)?;
|
||||||
.scroll_mt_step(self.acc_delta)
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
use crate::controller::{ControlMode, ControlModeMeta};
|
use crate::controller::{ControlMode, ControlModeMeta};
|
||||||
use crate::dial_device::DialHaptics;
|
use crate::dial_device::DialHaptics;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::fake_input::FakeInput;
|
use crate::fake_input;
|
||||||
|
|
||||||
use evdev_rs::enums::EV_KEY;
|
use evdev_rs::enums::EV_KEY;
|
||||||
|
|
||||||
pub struct Volume {
|
pub struct Volume {}
|
||||||
fake_input: FakeInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Volume {
|
impl Volume {
|
||||||
pub fn new() -> Volume {
|
pub fn new() -> Volume {
|
||||||
Volume {
|
Volume {}
|
||||||
fake_input: FakeInput::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,23 +33,19 @@ impl ControlMode for Volume {
|
|||||||
|
|
||||||
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
||||||
eprintln!("play/pause");
|
eprintln!("play/pause");
|
||||||
// self.fake_input.mute()?
|
// fake_input::mute()?
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_PLAYPAUSE]).map_err(Error::Evdev)?;
|
||||||
.key_click(&[EV_KEY::KEY_PLAYPAUSE])
|
|
||||||
.map_err(Error::Evdev)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
eprintln!("volume up");
|
eprintln!("volume up");
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])
|
||||||
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])
|
|
||||||
.map_err(Error::Evdev)?;
|
.map_err(Error::Evdev)?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("volume down");
|
eprintln!("volume down");
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])
|
||||||
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])
|
|
||||||
.map_err(Error::Evdev)?;
|
.map_err(Error::Evdev)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
use crate::controller::{ControlMode, ControlModeMeta};
|
use crate::controller::{ControlMode, ControlModeMeta};
|
||||||
use crate::dial_device::DialHaptics;
|
use crate::dial_device::DialHaptics;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::fake_input::FakeInput;
|
use crate::fake_input;
|
||||||
|
|
||||||
use evdev_rs::enums::EV_KEY;
|
use evdev_rs::enums::EV_KEY;
|
||||||
|
|
||||||
pub struct Zoom {
|
pub struct Zoom {}
|
||||||
fake_input: FakeInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Zoom {
|
impl Zoom {
|
||||||
pub fn new() -> Zoom {
|
pub fn new() -> Zoom {
|
||||||
Zoom {
|
Zoom {}
|
||||||
fake_input: FakeInput::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,13 +37,11 @@ impl ControlMode for Zoom {
|
|||||||
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
eprintln!("zoom in");
|
eprintln!("zoom in");
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL])
|
||||||
.key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL])
|
|
||||||
.map_err(Error::Evdev)?;
|
.map_err(Error::Evdev)?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("zoom out");
|
eprintln!("zoom out");
|
||||||
self.fake_input
|
fake_input::key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS])
|
||||||
.key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS])
|
|
||||||
.map_err(Error::Evdev)?;
|
.map_err(Error::Evdev)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub enum Error {
|
|||||||
UnexpectedEvt(InputEvent),
|
UnexpectedEvt(InputEvent),
|
||||||
Evdev(io::Error),
|
Evdev(io::Error),
|
||||||
Notif(notify_rust::error::Error),
|
Notif(notify_rust::error::Error),
|
||||||
|
TermSig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@@ -30,6 +31,7 @@ impl fmt::Display for Error {
|
|||||||
Error::UnexpectedEvt(evt) => write!(f, "Unexpected event: {:?}", evt),
|
Error::UnexpectedEvt(evt) => write!(f, "Unexpected event: {:?}", evt),
|
||||||
Error::Evdev(e) => write!(f, "Evdev error: {}", e),
|
Error::Evdev(e) => write!(f, "Evdev error: {}", e),
|
||||||
Error::Notif(e) => write!(f, "Notification error: {}", e),
|
Error::Notif(e) => write!(f, "Notification error: {}", e),
|
||||||
|
Error::TermSig => write!(f, "Received termination signal (either SIGTERM or SIGINT)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ use evdev_rs::{Device, InputEvent, TimeVal, UInputDevice};
|
|||||||
use parking_lot::ReentrantMutex;
|
use parking_lot::ReentrantMutex;
|
||||||
|
|
||||||
// this should be a fairly high number, as the axis is from 0..(MT_BASELINE*2)
|
// 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
|
// higher = more sensitive
|
||||||
const MT_SENSITIVITY: i32 = 64;
|
const MT_SENSITIVITY: i32 = 64;
|
||||||
|
|
||||||
|
pub struct FakeInputs {
|
||||||
|
keyboard: ReentrantMutex<UInputDevice>,
|
||||||
|
touchpad: ReentrantMutex<UInputDevice>,
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref FAKE_KEYBOARD: ReentrantMutex<UInputDevice> = {
|
pub static ref FAKE_INPUTS: FakeInputs = {
|
||||||
(|| -> io::Result<_> {
|
let keyboard = (|| -> io::Result<_> {
|
||||||
let device = Device::new().unwrap();
|
let device = Device::new().unwrap();
|
||||||
device.set_name("Surface Dial Virtual Keyboard/Mouse");
|
device.set_name("Surface Dial Virtual Keyboard/Mouse");
|
||||||
|
|
||||||
@@ -48,11 +53,9 @@ lazy_static::lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(ReentrantMutex::new(UInputDevice::create_from_device(&device)?))
|
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> = {
|
let touchpad = (|| -> io::Result<_> {
|
||||||
(|| -> io::Result<_> {
|
|
||||||
let device = Device::new().unwrap();
|
let device = Device::new().unwrap();
|
||||||
device.set_name("Surface Dial Virtual Touchpad");
|
device.set_name("Surface Dial Virtual Touchpad");
|
||||||
|
|
||||||
@@ -115,14 +118,15 @@ lazy_static::lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(ReentrantMutex::new(UInputDevice::create_from_device(&device)?))
|
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 {
|
macro_rules! input_event {
|
||||||
($type:ident, $code:ident, $value:expr) => {
|
($type:ident, $code:ident, $value:expr) => {
|
||||||
InputEvent {
|
InputEvent {
|
||||||
@@ -134,158 +138,146 @@ macro_rules! input_event {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FakeInput {
|
fn kbd_syn_report() -> io::Result<()> {
|
||||||
fn default() -> Self {
|
(FAKE_INPUTS.keyboard.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FakeInput {
|
pub fn key_click(keys: &[EV_KEY]) -> io::Result<()> {
|
||||||
pub fn new() -> FakeInput {
|
key_press(keys)?;
|
||||||
FakeInput {}
|
key_release(keys)?;
|
||||||
}
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn kbd_syn_report(&self) -> io::Result<()> {
|
pub fn key_press(keys: &[EV_KEY]) -> io::Result<()> {
|
||||||
(FAKE_KEYBOARD.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
|
let keyboard = FAKE_INPUTS.keyboard.lock();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key_click(&self, keys: &[EV_KEY]) -> io::Result<()> {
|
for key in keys {
|
||||||
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
|
|
||||||
keyboard.write_event(&InputEvent {
|
keyboard.write_event(&InputEvent {
|
||||||
time: TimeVal::new(0, 0),
|
time: TimeVal::new(0, 0),
|
||||||
event_code: EventCode::EV_REL(EV_REL::REL_WHEEL),
|
event_code: EventCode::EV_KEY(*key),
|
||||||
event_type: EventType::EV_REL,
|
event_type: EventType::EV_KEY,
|
||||||
value: match dir {
|
value: 1,
|
||||||
ScrollStep::Down => -1,
|
|
||||||
ScrollStep::Up => 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 {
|
keyboard.write_event(&InputEvent {
|
||||||
time: TimeVal::new(0, 0),
|
time: TimeVal::new(0, 0),
|
||||||
event_code: EventCode::EV_REL(EV_REL::REL_WHEEL_HI_RES),
|
event_code: EventCode::EV_KEY(*key),
|
||||||
event_type: EventType::EV_REL,
|
event_type: EventType::EV_KEY,
|
||||||
value: match dir {
|
value: 0,
|
||||||
ScrollStep::Down => -120,
|
|
||||||
ScrollStep::Up => 120,
|
|
||||||
},
|
|
||||||
})?;
|
})?;
|
||||||
self.kbd_syn_report()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
kbd_syn_report()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn touch_syn_report(&self) -> io::Result<()> {
|
pub fn scroll_step(dir: ScrollStep) -> io::Result<()> {
|
||||||
(FAKE_TOUCHPAD.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
|
let keyboard = FAKE_INPUTS.keyboard.lock();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll_mt_start(&self) -> io::Result<()> {
|
// copied from my razer blackwidow chroma mouse
|
||||||
let touchpad = FAKE_TOUCHPAD.lock();
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
{
|
fn touch_syn_report() -> io::Result<()> {
|
||||||
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?;
|
(FAKE_INPUTS.touchpad.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 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))?;
|
|
||||||
|
|
||||||
touchpad.write_event(&input_event!(EV_KEY, BTN_TOUCH, 1))?;
|
pub fn scroll_mt_start() -> io::Result<()> {
|
||||||
touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_FINGER, 1))?;
|
let touchpad = FAKE_INPUTS.touchpad.lock();
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
|
{
|
||||||
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?;
|
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_SLOT, 0))?;
|
||||||
touchpad.write_event(&input_event!(
|
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_TRACKING_ID, 1))?;
|
||||||
EV_ABS,
|
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_X, MT_BASELINE))?;
|
||||||
ABS_MT_POSITION_Y,
|
touchpad.write_event(&input_event!(EV_ABS, ABS_MT_POSITION_Y, MT_BASELINE))?;
|
||||||
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))?;
|
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()?;
|
touchpad.write_event(&input_event!(EV_ABS, ABS_X, MT_BASELINE))?;
|
||||||
|
touchpad.write_event(&input_event!(EV_ABS, ABS_Y, MT_BASELINE))?;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scroll_mt_end(&self) -> io::Result<()> {
|
touch_syn_report()?;
|
||||||
let touchpad = FAKE_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_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_FINGER, 0))?;
|
||||||
touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 0))?;
|
touchpad.write_event(&input_event!(EV_KEY, BTN_TOOL_DOUBLETAP, 1))?;
|
||||||
|
|
||||||
self.touch_syn_report()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
pub enum ScrollStep {
|
||||||
|
|||||||
98
src/main.rs
98
src/main.rs
@@ -11,38 +11,62 @@ use std::sync::mpsc;
|
|||||||
|
|
||||||
use crate::controller::DialController;
|
use crate::controller::DialController;
|
||||||
use crate::dial_device::DialDevice;
|
use crate::dial_device::DialDevice;
|
||||||
use crate::error::Result;
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
use notify_rust::{Hint, Notification, Timeout};
|
use notify_rust::{Hint, Notification, Timeout};
|
||||||
use signal_hook::{iterator::Signals, SIGINT, SIGTERM};
|
use signal_hook::{iterator::Signals, SIGINT, SIGTERM};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let (kill_notif_tx, kill_notif_rx) = mpsc::channel::<Option<(String, &'static str)>>();
|
let (terminate_tx, terminate_rx) = mpsc::channel::<Result<()>>();
|
||||||
|
|
||||||
let handle = std::thread::spawn(move || {
|
std::thread::spawn({
|
||||||
let active_notification = Notification::new()
|
let terminate_tx = terminate_tx.clone();
|
||||||
.hint(Hint::Resident(true))
|
move || {
|
||||||
.hint(Hint::Category("device".into()))
|
let signals = Signals::new(&[SIGTERM, SIGINT]).unwrap();
|
||||||
.timeout(Timeout::Never)
|
for sig in signals.forever() {
|
||||||
.summary("Surface Dial")
|
eprintln!("received signal {:?}", sig);
|
||||||
.body("Active!")
|
let _ = terminate_tx.send(Err(Error::TermSig));
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
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()
|
Notification::new()
|
||||||
.hint(Hint::Transient(true))
|
.hint(Hint::Transient(true))
|
||||||
.hint(Hint::Category("device".into()))
|
.hint(Hint::Category("device".into()))
|
||||||
@@ -52,33 +76,13 @@ fn main() {
|
|||||||
.icon(icon)
|
.icon(icon)
|
||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.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
|
// cleaning up threads is hard...
|
||||||
let _ = handle.join();
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn true_main() -> Result<()> {
|
fn controller_main() -> Result<()> {
|
||||||
println!("Started");
|
println!("Started");
|
||||||
|
|
||||||
let cfg = config::Config::from_disk()?;
|
let cfg = config::Config::from_disk()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user