1
0

experimental multitouch based smooth scrolling

high-resolution mouse wheels aren't supported in userland yet, so why
don't we fake a high resolution touchpad instead 😄

it's somewhat working, though it seems to crash on startup when
installed (among other weird bugs).
This commit is contained in:
Daniel Prilik
2020-11-02 18:50:45 -05:00
parent 3285021903
commit 4f25a73eb3
7 changed files with 4905 additions and 57 deletions

79
Cargo.lock generated
View File

@@ -71,6 +71,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.19"
@@ -84,6 +90,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
"bitflags 1.2.1",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@@ -97,7 +112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if", "cfg-if 0.1.10",
"lazy_static", "lazy_static",
] ]
@@ -169,7 +184,7 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"wasi 0.9.0+wasi-snapshot-preview1", "wasi 0.9.0+wasi-snapshot-preview1",
] ]
@@ -185,6 +200,15 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "instant"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -206,13 +230,22 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "lock_api"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
dependencies = [
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.11" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
] ]
[[package]] [[package]]
@@ -295,6 +328,32 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "parking_lot"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
dependencies = [
"cfg-if 0.1.10",
"cloudabi",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.19" version = "0.3.19"
@@ -336,6 +395,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.1.16" version = "0.1.16"
@@ -356,6 +421,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "smallvec"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.8.0" version = "0.8.0"
@@ -379,7 +450,9 @@ dependencies = [
"directories", "directories",
"evdev-rs", "evdev-rs",
"hidapi", "hidapi",
"lazy_static",
"notify-rust", "notify-rust",
"parking_lot",
"signal-hook", "signal-hook",
] ]

View File

@@ -9,5 +9,7 @@ directories = "3.0"
# master includes a PR that implements `Send` for `Device` and `UInputDevice` # master includes a PR that implements `Send` for `Device` and `UInputDevice`
evdev-rs = { git = "https://github.com/ndesh26/evdev-rs.git", rev = "8e995b8bf" } evdev-rs = { git = "https://github.com/ndesh26/evdev-rs.git", rev = "8e995b8bf" }
hidapi = { version = "1.2.3", default-features = false, features = ["linux-shared-hidraw"] } hidapi = { version = "1.2.3", default-features = false, features = ["linux-shared-hidraw"] }
lazy_static = "1.4"
notify-rust = "4" notify-rust = "4"
parking_lot = "0.11.0"
signal-hook = "0.1.16" signal-hook = "0.1.16"

4559
notes/touchpad_evtest.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ mod media;
mod null; mod null;
mod paddle; mod paddle;
mod scroll; mod scroll;
mod scroll_mt;
mod volume; mod volume;
mod zoom; mod zoom;
@@ -9,5 +10,6 @@ pub use self::media::*;
pub use self::null::*; pub use self::null::*;
pub use self::paddle::*; pub use self::paddle::*;
pub use self::scroll::*; pub use self::scroll::*;
pub use self::scroll_mt::*;
pub use self::volume::*; pub use self::volume::*;
pub use self::zoom::*; pub use self::zoom::*;

View File

@@ -0,0 +1,61 @@
use crate::controller::{ControlMode, ControlModeMeta};
use crate::dial_device::DialHaptics;
use crate::fake_input::FakeInput;
use crate::DynResult;
pub struct ScrollMT {
acc_delta: i32,
fake_input: FakeInput,
}
impl ScrollMT {
pub fn new() -> ScrollMT {
ScrollMT {
acc_delta: 0,
fake_input: FakeInput::new(),
}
}
}
impl ControlMode for ScrollMT {
fn meta(&self) -> ControlModeMeta {
ControlModeMeta {
name: "Scroll",
icon: "input-mouse",
}
}
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
haptics.set_mode(false, Some(3600))?;
self.acc_delta = 0;
self.fake_input.scroll_mt_start()?;
Ok(())
}
fn on_end(&mut self, _haptics: &DialHaptics) -> DynResult<()> {
self.fake_input.scroll_mt_end()?;
Ok(())
}
// HACK: the button will reset the scroll event, which sometimes helps
fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> {
self.fake_input.scroll_mt_end()?;
Ok(())
}
fn on_btn_release(&mut self, _haptics: &DialHaptics) -> DynResult<()> {
self.fake_input.scroll_mt_start()?;
Ok(())
}
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> {
self.acc_delta += delta;
self.fake_input.scroll_mt_step(self.acc_delta)?;
Ok(())
}
}

View File

@@ -2,12 +2,13 @@ use std::io;
use evdev_rs::enums::*; use evdev_rs::enums::*;
use evdev_rs::{Device, InputEvent, TimeVal, UInputDevice}; use evdev_rs::{Device, InputEvent, TimeVal, UInputDevice};
use parking_lot::ReentrantMutex;
static mut FAKE_INPUT: Option<UInputDevice> = None; lazy_static::lazy_static! {
fn get_fake_input() -> io::Result<&'static UInputDevice> { static ref FAKE_KEYBOARD: ReentrantMutex<UInputDevice> = {
if unsafe { FAKE_INPUT.is_none() } { (|| -> io::Result<_> {
let device = Device::new().unwrap(); let device = Device::new().unwrap();
device.set_name("Surface Dial Virtual Input"); device.set_name("Surface Dial Virtual Keyboard/Mouse");
device.enable(&EventType::EV_SYN)?; device.enable(&EventType::EV_SYN)?;
device.enable(&EventCode::EV_SYN(EV_SYN::SYN_REPORT))?; device.enable(&EventCode::EV_SYN(EV_SYN::SYN_REPORT))?;
@@ -41,15 +42,82 @@ fn get_fake_input() -> io::Result<&'static UInputDevice> {
device.enable(&EventCode::EV_REL(EV_REL::REL_WHEEL_HI_RES))?; device.enable(&EventCode::EV_REL(EV_REL::REL_WHEEL_HI_RES))?;
} }
unsafe { FAKE_INPUT = Some(UInputDevice::create_from_device(&device)?) } Ok(ReentrantMutex::new(UInputDevice::create_from_device(&device)?))
})().expect("failed to install virtual mouse/keyboard device")
};
static ref FAKE_TOUCHPAD: ReentrantMutex<UInputDevice> = {
(|| -> io::Result<_> {
let device = Device::new().unwrap();
device.set_name("Surface Dial Virtual Touchpad");
device.enable(&InputProp::INPUT_PROP_BUTTONPAD)?;
device.enable(&InputProp::INPUT_PROP_POINTER)?;
device.enable(&EventType::EV_SYN)?;
device.enable(&EventCode::EV_SYN(EV_SYN::SYN_REPORT))?;
device.enable(&EventType::EV_KEY)?;
{
device.enable(&EventCode::EV_KEY(EV_KEY::BTN_LEFT))?;
device.enable(&EventCode::EV_KEY(EV_KEY::BTN_TOOL_FINGER))?;
device.enable(&EventCode::EV_KEY(EV_KEY::BTN_TOUCH))?;
device.enable(&EventCode::EV_KEY(EV_KEY::BTN_TOOL_DOUBLETAP))?;
device.enable(&EventCode::EV_KEY(EV_KEY::BTN_TOOL_TRIPLETAP))?;
device.enable(&EventCode::EV_KEY(EV_KEY::BTN_TOOL_QUADTAP))?;
} }
unsafe { Ok(FAKE_INPUT.as_ref().unwrap()) }
// roughly copied from my laptop's trackpad (Aero 15x)
device.enable(&EventType::EV_ABS)?;
{
let mut abs_info = evdev_rs::AbsInfo {
value: 0,
minimum: 0,
maximum: 0,
fuzz: 0,
flat: 0,
resolution: 0,
};
abs_info.minimum = 0;
abs_info.maximum = 4;
device.enable_event_code(&EventCode::EV_ABS(EV_ABS::ABS_MT_SLOT), Some(&abs_info))?;
abs_info.minimum = 0;
abs_info.maximum = 65535;
device.enable_event_code(
&EventCode::EV_ABS(EV_ABS::ABS_MT_TRACKING_ID),
Some(&abs_info),
)?;
// higher = more sensitive
const SENSITIVITY: i32 = 64;
abs_info.minimum = 0;
abs_info.maximum = std::i32::MAX;
abs_info.resolution = SENSITIVITY;
device.enable_event_code(
&EventCode::EV_ABS(EV_ABS::ABS_MT_POSITION_X),
Some(&abs_info),
)?;
abs_info.minimum = 0;
abs_info.maximum = std::i32::MAX;
abs_info.resolution = SENSITIVITY;
device.enable_event_code(
&EventCode::EV_ABS(EV_ABS::ABS_MT_POSITION_Y),
Some(&abs_info),
)?;
}
Ok(ReentrantMutex::new(UInputDevice::create_from_device(&device)?))
})().expect("failed to install virtual touchpad device")
};
} }
#[non_exhaustive] #[non_exhaustive]
pub struct FakeInput { pub struct FakeInput {}
uinput: &'static UInputDevice,
}
macro_rules! input_event { macro_rules! input_event {
($type:ident, $code:ident, $value:expr) => { ($type:ident, $code:ident, $value:expr) => {
@@ -70,14 +138,11 @@ impl Default for FakeInput {
impl FakeInput { impl FakeInput {
pub fn new() -> FakeInput { pub fn new() -> FakeInput {
FakeInput { FakeInput {}
uinput: get_fake_input().expect("could not install fake input device"),
}
} }
fn syn_report(&self) -> io::Result<()> { fn kbd_syn_report(&self) -> io::Result<()> {
self.uinput (FAKE_KEYBOARD.lock()).write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
.write_event(&input_event!(EV_SYN, SYN_REPORT, 0))
} }
pub fn key_click(&self, keys: &[EV_KEY]) -> io::Result<()> { pub fn key_click(&self, keys: &[EV_KEY]) -> io::Result<()> {
@@ -87,34 +152,40 @@ impl FakeInput {
} }
pub fn key_press(&self, keys: &[EV_KEY]) -> io::Result<()> { pub fn key_press(&self, keys: &[EV_KEY]) -> io::Result<()> {
let keyboard = FAKE_KEYBOARD.lock();
for key in keys { for key in keys {
self.uinput.write_event(&InputEvent { keyboard.write_event(&InputEvent {
time: TimeVal::new(0, 0), time: TimeVal::new(0, 0),
event_code: EventCode::EV_KEY(*key), event_code: EventCode::EV_KEY(*key),
event_type: EventType::EV_KEY, event_type: EventType::EV_KEY,
value: 1, value: 1,
})?; })?;
} }
self.syn_report()?; self.kbd_syn_report()?;
Ok(()) Ok(())
} }
pub fn key_release(&self, keys: &[EV_KEY]) -> io::Result<()> { pub fn key_release(&self, keys: &[EV_KEY]) -> io::Result<()> {
let keyboard = FAKE_KEYBOARD.lock();
for key in keys.iter().clone() { for key in keys.iter().clone() {
self.uinput.write_event(&InputEvent { keyboard.write_event(&InputEvent {
time: TimeVal::new(0, 0), time: TimeVal::new(0, 0),
event_code: EventCode::EV_KEY(*key), event_code: EventCode::EV_KEY(*key),
event_type: EventType::EV_KEY, event_type: EventType::EV_KEY,
value: 0, value: 0,
})?; })?;
} }
self.syn_report()?; self.kbd_syn_report()?;
Ok(()) Ok(())
} }
pub fn scroll_step(&self, dir: ScrollStep) -> io::Result<()> { pub fn scroll_step(&self, dir: ScrollStep) -> io::Result<()> {
let keyboard = FAKE_KEYBOARD.lock();
// copied from my razer blackwidow chroma mouse // copied from my razer blackwidow chroma mouse
self.uinput.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_REL(EV_REL::REL_WHEEL),
event_type: EventType::EV_REL, event_type: EventType::EV_REL,
@@ -123,7 +194,7 @@ impl FakeInput {
ScrollStep::Up => 1, ScrollStep::Up => 1,
}, },
})?; })?;
self.uinput.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_REL(EV_REL::REL_WHEEL_HI_RES),
event_type: EventType::EV_REL, event_type: EventType::EV_REL,
@@ -132,11 +203,89 @@ impl FakeInput {
ScrollStep::Up => 120, ScrollStep::Up => 120,
}, },
})?; })?;
self.syn_report()?; self.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_mt_start(&self) -> 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_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))?;
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, std::i32::MAX / 3))?;
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_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))?;
self.touch_syn_report()?;
Ok(())
}
pub fn scroll_mt_end(&self) -> 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_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))?;
self.touch_syn_report()?;
Ok(()) Ok(())
} }
} }
const MT_BASELINE: i32 = std::i32::MAX / 2;
pub enum ScrollStep { pub enum ScrollStep {
Up, Up,
Down, Down,

View File

@@ -62,8 +62,8 @@ fn main() {
println!("{}", e); println!("{}", e);
} }
kill_notif_tx.send(None).unwrap(); // silently shut down let _ = kill_notif_tx.send(None); // silently shut down
handle.join().unwrap(); let _ = handle.join();
} }
fn true_main(kill_notif_tx: mpsc::Sender<Option<(String, &'static str)>>) -> DynResult<()> { fn true_main(kill_notif_tx: mpsc::Sender<Option<(String, &'static str)>>) -> DynResult<()> {
@@ -78,9 +78,10 @@ fn true_main(kill_notif_tx: mpsc::Sender<Option<(String, &'static str)>>) -> Dyn
let signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); let signals = Signals::new(&[SIGTERM, SIGINT]).unwrap();
for sig in signals.forever() { for sig in signals.forever() {
eprintln!("received signal {:?}", sig); eprintln!("received signal {:?}", sig);
kill_notif_tx match kill_notif_tx.send(Some(("Terminated!".into(), "dialog-warning"))) {
.send(Some(("Terminated!".into(), "dialog-warning"))) Ok(_) => {}
.unwrap(); Err(_) => std::process::exit(1),
}
} }
}); });
@@ -88,6 +89,7 @@ fn true_main(kill_notif_tx: mpsc::Sender<Option<(String, &'static str)>>) -> Dyn
dial, dial,
cfg.last_mode, cfg.last_mode,
vec![ vec![
// Box::new(controller::controls::ScrollMT::new()),
Box::new(controller::controls::Scroll::new()), Box::new(controller::controls::Scroll::new()),
Box::new(controller::controls::Zoom::new()), Box::new(controller::controls::Zoom::new()),
Box::new(controller::controls::Volume::new()), Box::new(controller::controls::Volume::new()),