1
0
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/radial-controller-protocol-implementation

With a little bit of trial and error (and a crash-course in how the heck
HID even works), I figured out how to get the dial to provide haptic
feedback!

Along the way, I also learned that you can take advantage of the
(incorrectly named) Resolution Multiplier field to customize how many
"steps" the dial should have, offloading the work to the device itself!

Very cool!!
This commit is contained in:
Daniel Prilik
2020-10-30 19:59:23 -04:00
parent 858209484f
commit e6fa6845fe
16 changed files with 1139 additions and 365 deletions

View File

@@ -4,6 +4,7 @@ use std::thread::JoinHandle;
use std::time::Duration;
use crate::controller::ControlMode;
use crate::dial_device::DialHaptics;
use crate::fake_input::FakeInput;
use crate::DynResult;
@@ -106,12 +107,6 @@ impl Drop for DPad {
}
}
impl Default for DPad {
fn default() -> Self {
Self::new()
}
}
impl DPad {
pub fn new() -> DPad {
let (msg_tx, msg_rx) = mpsc::channel();
@@ -128,17 +123,22 @@ impl DPad {
}
impl ControlMode for DPad {
fn on_btn_press(&mut self) -> DynResult<()> {
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
haptics.set_mode(false, Some(3600))?;
Ok(())
}
fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> {
eprintln!("space");
self.fake_input.key_click(&[EV_KEY::KEY_SPACE])?;
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
fn on_btn_release(&mut self, _: &DialHaptics) -> DynResult<()> {
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> {
self.msg.send(Msg::Delta(delta))?;
Ok(())
}

View File

@@ -1,47 +1,44 @@
use crate::common::{DialDir, ThresholdHelper};
use crate::controller::ControlMode;
use crate::dial_device::DialHaptics;
use crate::fake_input::FakeInput;
use crate::DynResult;
use evdev_rs::enums::EV_KEY;
pub struct Media {
thresh: ThresholdHelper,
fake_input: FakeInput,
}
impl Media {
pub fn new(sensitivity: i32) -> Media {
pub fn new() -> Media {
Media {
thresh: ThresholdHelper::new(sensitivity),
fake_input: FakeInput::new(),
}
}
}
impl ControlMode for Media {
fn on_btn_press(&mut self) -> DynResult<()> {
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
haptics.set_mode(false, Some(36))?;
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> {
Ok(())
}
fn on_btn_release(&mut self, _: &DialHaptics) -> DynResult<()> {
self.fake_input.key_click(&[EV_KEY::KEY_PLAYPAUSE])?;
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
match self.thresh.update(delta) {
Some(DialDir::Left) => {
eprintln!("next song");
self.fake_input.key_click(&[EV_KEY::KEY_NEXTSONG])?;
}
Some(DialDir::Right) => {
eprintln!("last song");
self.fake_input.key_click(&[EV_KEY::KEY_PREVIOUSSONG])?;
}
None => {}
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> {
if delta > 0 {
eprintln!("last song");
self.fake_input.key_click(&[EV_KEY::KEY_PREVIOUSSONG])?;
} else {
eprintln!("next song");
self.fake_input.key_click(&[EV_KEY::KEY_NEXTSONG])?;
}
Ok(())
}

View File

@@ -1,9 +1,11 @@
mod dpad;
mod media;
mod null;
mod scroll_zoom;
mod volume;
pub use self::dpad::*;
pub use self::media::*;
pub use self::null::*;
pub use self::scroll_zoom::*;
pub use self::volume::*;

View File

@@ -0,0 +1,30 @@
use crate::controller::ControlMode;
use crate::dial_device::DialHaptics;
use crate::DynResult;
pub struct Null {}
impl Null {
pub fn new() -> Null {
Null {}
}
}
impl ControlMode for Null {
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
haptics.set_mode(false, Some(0))?;
Ok(())
}
fn on_btn_press(&mut self, _haptics: &DialHaptics) -> DynResult<()> {
Ok(())
}
fn on_btn_release(&mut self, _haptics: &DialHaptics) -> DynResult<()> {
Ok(())
}
fn on_dial(&mut self, _haptics: &DialHaptics, _delta: i32) -> DynResult<()> {
Ok(())
}
}

View File

@@ -1,21 +1,20 @@
use crate::common::{action_notification, DialDir, ThresholdHelper};
use crate::common::action_notification;
use crate::controller::ControlMode;
use crate::dial_device::DialHaptics;
use crate::fake_input::{FakeInput, ScrollStep};
use crate::DynResult;
use evdev_rs::enums::EV_KEY;
pub struct ScrollZoom {
thresh: ThresholdHelper,
zoom: bool,
fake_input: FakeInput,
}
impl ScrollZoom {
pub fn new(sensitivity: i32) -> ScrollZoom {
pub fn new() -> ScrollZoom {
ScrollZoom {
thresh: ThresholdHelper::new(sensitivity),
zoom: false,
fake_input: FakeInput::new(),
@@ -23,46 +22,55 @@ impl ScrollZoom {
}
}
const ZOOM_SENSITIVITY: u16 = 36;
const SCROLL_SENSITIVITY: u16 = 90;
impl ControlMode for ScrollZoom {
fn on_btn_press(&mut self) -> DynResult<()> {
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
haptics.set_mode(false, Some(SCROLL_SENSITIVITY))?;
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> {
Ok(())
}
fn on_btn_release(&mut self, haptics: &DialHaptics) -> DynResult<()> {
self.zoom = !self.zoom;
haptics.buzz(1)?;
if self.zoom {
action_notification("Zoom Mode", "zoom-in")?;
haptics.set_mode(false, Some(ZOOM_SENSITIVITY))?;
} else {
action_notification("ScrollZoom Mode", "input-mouse")?;
haptics.set_mode(false, Some(SCROLL_SENSITIVITY))?;
}
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
match self.thresh.update(delta) {
None => {}
Some(DialDir::Left) => {
if self.zoom {
eprintln!("zoom out");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS])?;
} else {
eprintln!("scroll up");
self.fake_input.scroll_step(ScrollStep::Up)?;
}
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> {
if delta > 0 {
if self.zoom {
eprintln!("zoom in");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL])?;
} else {
eprintln!("scroll down");
self.fake_input.scroll_step(ScrollStep::Down)?;
}
Some(DialDir::Right) => {
if self.zoom {
eprintln!("zoom in");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL])?;
} else {
eprintln!("scroll down");
self.fake_input.scroll_step(ScrollStep::Down)?;
}
} else {
if self.zoom {
eprintln!("zoom out");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS])?;
} else {
eprintln!("scroll up");
self.fake_input.scroll_step(ScrollStep::Up)?;
}
}
Ok(())
}
}

View File

@@ -1,53 +1,51 @@
use crate::common::{DialDir, ThresholdHelper};
use crate::controller::ControlMode;
use crate::dial_device::DialHaptics;
use crate::fake_input::FakeInput;
use crate::DynResult;
use evdev_rs::enums::EV_KEY;
pub struct Volume {
thresh: ThresholdHelper,
fake_input: FakeInput,
}
impl Volume {
pub fn new(sensitivity: i32) -> Volume {
pub fn new() -> Volume {
Volume {
thresh: ThresholdHelper::new(sensitivity),
fake_input: FakeInput::new(),
}
}
}
impl ControlMode for Volume {
fn on_btn_press(&mut self) -> DynResult<()> {
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
haptics.set_mode(false, Some(36 * 2))?;
Ok(())
}
fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> {
// TODO: support double-click to mute
Ok(())
}
fn on_btn_release(&mut self) -> DynResult<()> {
fn on_btn_release(&mut self, _: &DialHaptics) -> DynResult<()> {
eprintln!("play/pause");
// self.fake_input.mute()?
self.fake_input.key_click(&[EV_KEY::KEY_PLAYPAUSE])?;
Ok(())
}
fn on_dial(&mut self, delta: i32) -> DynResult<()> {
match self.thresh.update(delta) {
Some(DialDir::Left) => {
eprintln!("volume down");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])?
}
Some(DialDir::Right) => {
eprintln!("volume up");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])?
}
None => {}
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> {
if delta > 0 {
eprintln!("volume up");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])?;
} else {
eprintln!("volume down");
self.fake_input
.key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])?;
}
Ok(())
}
}