diff --git a/README.md b/README.md
index 319b7c8..bf338e7 100644
--- a/README.md
+++ b/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 1 |
-| **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 1 |
+| **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! |
1 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.
diff --git a/src/controller/controls/media_with_volume.rs b/src/controller/controls/media_with_volume.rs
new file mode 100644
index 0000000..2bbaba1
--- /dev/null
+++ b/src/controller/controls/media_with_volume.rs
@@ -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>>,
+}
+
+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(())
+ }
+}
diff --git a/src/controller/controls/mod.rs b/src/controller/controls/mod.rs
index c690bf5..7469789 100644
--- a/src/controller/controls/mod.rs
+++ b/src/controller/controls/mod.rs
@@ -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::*;
diff --git a/src/controller/controls/volume.rs b/src/controller/controls/volume.rs
index df0be0d..c767721 100644
--- a/src/controller/controls/volume.rs
+++ b/src/controller/controls/volume.rs
@@ -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>>,
-}
+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(())
}
diff --git a/src/main.rs b/src/main.rs
index 7da541c..ef0ef3d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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()),
],
);