Separate "vanilla" Volume mode from Media + Volume mode
This commit is contained in:
17
README.md
17
README.md
@@ -28,14 +28,15 @@ At the moment, these modes (along with their meta-menu ordering) are hard-coded
|
||||
|
||||
Modes in **bold** should be considered **EXPERIMENTAL**. They seem to work alright (some of the time), but they would really benefit from some more polish / bug fixes.
|
||||
|
||||
| Mode | Click | Rotate | Notes |
|
||||
| ---------------------------- | ----------------- | -------------------- | ------------------------------------------------------------------------------------- |
|
||||
| Scroll | - | Scroll | Fakes chunky mouse-wheel scrolling <sup>1</sup> |
|
||||
| **Scroll (Fake Multitouch)** | Reset Touch Event | Scroll | Fakes smooth two-finger scrolling |
|
||||
| Zoom | - | Zoom | |
|
||||
| Volume | Play/Pause | Volume | Double-click to mute |
|
||||
| Media | Play/Pause | Next/Prev Track | |
|
||||
| **Paddle Controller** | Space | Left/Right Arrow Key | Like those old [paddle controllers](https://www.google.com/search?q=arkanoid+paddle). |
|
||||
| Mode | Click | Rotate | Notes |
|
||||
| ---------------------------- | ----------------- | -------------------- | -------------------------------------------------------------------------------------- |
|
||||
| Scroll | - | Scroll | Fakes chunky mouse-wheel scrolling <sup>1</sup> |
|
||||
| **Scroll (Fake Multitouch)** | Reset Touch Event | Scroll | Fakes smooth two-finger scrolling |
|
||||
| Zoom | - | Zoom | |
|
||||
| Volume | Mute | Volume | |
|
||||
| Media | Play/Pause | Next/Prev Track | |
|
||||
| Media + Volume | Play/Pause | Volume | Double-click = Next Track |
|
||||
| **Paddle Controller** | Space | Left/Right Arrow Key | Play [arkanoid](https://www.google.com/search?q=arkanoid+paddle) as the devs intended! |
|
||||
|
||||
<sup>1</sup> At the time of writing, almost all Linux userspace programs don't take advantage of the newer high-resolution scroll wheel events, and only support the older, chunkier scroll wheel events. Check out [this blog post](https://who-t.blogspot.com/2020/04/high-resolution-wheel-scrolling-in.html) for more details.
|
||||
|
||||
|
||||
108
src/controller/controls/media_with_volume.rs
Normal file
108
src/controller/controls/media_with_volume.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::fake_input;
|
||||
|
||||
use evdev_rs::enums::EV_KEY;
|
||||
|
||||
fn double_click_worker(click: mpsc::Receiver<()>, release: mpsc::Receiver<()>) -> Result<()> {
|
||||
loop {
|
||||
// drain any spurious clicks/releases
|
||||
for _ in click.try_iter() {}
|
||||
for _ in release.try_iter() {}
|
||||
|
||||
click.recv().unwrap();
|
||||
// recv with timeout, in case this is a long-press
|
||||
match release.recv_timeout(Duration::from_secs(1)) {
|
||||
Ok(()) => {}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => continue,
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => panic!(),
|
||||
}
|
||||
|
||||
match click.recv_timeout(Duration::from_millis(250)) {
|
||||
Ok(()) => {
|
||||
// double click
|
||||
release.recv().unwrap(); // should only fire after button is released
|
||||
eprintln!("next track");
|
||||
fake_input::key_click(&[EV_KEY::KEY_NEXTSONG]).map_err(Error::Evdev)?;
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
// single click
|
||||
eprintln!("play/pause");
|
||||
fake_input::key_click(&[EV_KEY::KEY_PLAYPAUSE]).map_err(Error::Evdev)?;
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MediaWithVolume {
|
||||
click_tx: mpsc::Sender<()>,
|
||||
release_tx: mpsc::Sender<()>,
|
||||
worker_handle: Option<std::thread::JoinHandle<Result<()>>>,
|
||||
}
|
||||
|
||||
impl MediaWithVolume {
|
||||
pub fn new() -> MediaWithVolume {
|
||||
let (click_tx, click_rx) = mpsc::channel();
|
||||
let (release_tx, release_rx) = mpsc::channel();
|
||||
|
||||
let worker_handle = std::thread::spawn(move || double_click_worker(click_rx, release_rx));
|
||||
|
||||
MediaWithVolume {
|
||||
click_tx,
|
||||
release_tx,
|
||||
worker_handle: Some(worker_handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlMode for MediaWithVolume {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
ControlModeMeta {
|
||||
name: "Media + Volume",
|
||||
icon: "applications-multimedia",
|
||||
haptics: true,
|
||||
steps: 36 * 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_btn_press(&mut self, _: &DialHaptics) -> Result<()> {
|
||||
if self.click_tx.send(()).is_err() {
|
||||
self.worker_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("panic on thread join")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
||||
if self.release_tx.send(()).is_err() {
|
||||
self.worker_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("panic on thread join")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> Result<()> {
|
||||
if delta > 0 {
|
||||
eprintln!("volume up");
|
||||
fake_input::key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])
|
||||
.map_err(Error::Evdev)?;
|
||||
} else {
|
||||
eprintln!("volume down");
|
||||
fake_input::key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])
|
||||
.map_err(Error::Evdev)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod media;
|
||||
mod media_with_volume;
|
||||
mod null;
|
||||
mod paddle;
|
||||
mod scroll;
|
||||
@@ -7,6 +8,7 @@ mod volume;
|
||||
mod zoom;
|
||||
|
||||
pub use self::media::*;
|
||||
pub use self::media_with_volume::*;
|
||||
pub use self::null::*;
|
||||
pub use self::paddle::*;
|
||||
pub use self::scroll::*;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::error::{Error, Result};
|
||||
@@ -8,55 +5,11 @@ use crate::fake_input;
|
||||
|
||||
use evdev_rs::enums::EV_KEY;
|
||||
|
||||
fn double_click_worker(click: mpsc::Receiver<()>, release: mpsc::Receiver<()>) -> Result<()> {
|
||||
loop {
|
||||
// drain any spurious clicks/releases
|
||||
for _ in click.try_iter() {}
|
||||
for _ in release.try_iter() {}
|
||||
|
||||
click.recv().unwrap();
|
||||
// recv with timeout, in case this is a long-press
|
||||
match release.recv_timeout(Duration::from_secs(1)) {
|
||||
Ok(()) => {}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => continue,
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => panic!(),
|
||||
}
|
||||
|
||||
match click.recv_timeout(Duration::from_millis(250)) {
|
||||
Ok(()) => {
|
||||
// double click
|
||||
release.recv().unwrap(); // should only fire after button is released
|
||||
eprintln!("mute");
|
||||
fake_input::key_click(&[EV_KEY::KEY_MUTE]).map_err(Error::Evdev)?;
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
// single click
|
||||
eprintln!("play/pause");
|
||||
fake_input::key_click(&[EV_KEY::KEY_PLAYPAUSE]).map_err(Error::Evdev)?;
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Volume {
|
||||
click_tx: mpsc::Sender<()>,
|
||||
release_tx: mpsc::Sender<()>,
|
||||
worker_handle: Option<std::thread::JoinHandle<Result<()>>>,
|
||||
}
|
||||
pub struct Volume {}
|
||||
|
||||
impl Volume {
|
||||
pub fn new() -> Volume {
|
||||
let (click_tx, click_rx) = mpsc::channel();
|
||||
let (release_tx, release_rx) = mpsc::channel();
|
||||
|
||||
let worker_handle = std::thread::spawn(move || double_click_worker(click_rx, release_rx));
|
||||
|
||||
Volume {
|
||||
click_tx,
|
||||
release_tx,
|
||||
worker_handle: Some(worker_handle),
|
||||
}
|
||||
Volume {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,24 +24,12 @@ impl ControlMode for Volume {
|
||||
}
|
||||
|
||||
fn on_btn_press(&mut self, _: &DialHaptics) -> Result<()> {
|
||||
if self.click_tx.send(()).is_err() {
|
||||
self.worker_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("panic on thread join")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
||||
if self.release_tx.send(()).is_err() {
|
||||
self.worker_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("panic on thread join")?;
|
||||
}
|
||||
eprintln!("mute");
|
||||
fake_input::key_click(&[EV_KEY::KEY_MUTE]).map_err(Error::Evdev)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ fn controller_main() -> Result<()> {
|
||||
Box::new(controller::controls::Zoom::new()),
|
||||
Box::new(controller::controls::Volume::new()),
|
||||
Box::new(controller::controls::Media::new()),
|
||||
Box::new(controller::controls::MediaWithVolume::new()),
|
||||
Box::new(controller::controls::Paddle::new()),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user