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.
|
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 |
|
| Mode | Click | Rotate | Notes |
|
||||||
| ---------------------------- | ----------------- | -------------------- | ------------------------------------------------------------------------------------- |
|
| ---------------------------- | ----------------- | -------------------- | -------------------------------------------------------------------------------------- |
|
||||||
| Scroll | - | Scroll | Fakes chunky mouse-wheel scrolling <sup>1</sup> |
|
| Scroll | - | Scroll | Fakes chunky mouse-wheel scrolling <sup>1</sup> |
|
||||||
| **Scroll (Fake Multitouch)** | Reset Touch Event | Scroll | Fakes smooth two-finger scrolling |
|
| **Scroll (Fake Multitouch)** | Reset Touch Event | Scroll | Fakes smooth two-finger scrolling |
|
||||||
| Zoom | - | Zoom | |
|
| Zoom | - | Zoom | |
|
||||||
| Volume | Play/Pause | Volume | Double-click to mute |
|
| Volume | Mute | Volume | |
|
||||||
| Media | Play/Pause | Next/Prev Track | |
|
| 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). |
|
| 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.
|
<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;
|
||||||
|
mod media_with_volume;
|
||||||
mod null;
|
mod null;
|
||||||
mod paddle;
|
mod paddle;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
@@ -7,6 +8,7 @@ mod volume;
|
|||||||
mod zoom;
|
mod zoom;
|
||||||
|
|
||||||
pub use self::media::*;
|
pub use self::media::*;
|
||||||
|
pub use self::media_with_volume::*;
|
||||||
pub use self::null::*;
|
pub use self::null::*;
|
||||||
pub use self::paddle::*;
|
pub use self::paddle::*;
|
||||||
pub use self::scroll::*;
|
pub use self::scroll::*;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
use std::sync::mpsc;
|
|
||||||
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::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
@@ -8,55 +5,11 @@ use crate::fake_input;
|
|||||||
|
|
||||||
use evdev_rs::enums::EV_KEY;
|
use evdev_rs::enums::EV_KEY;
|
||||||
|
|
||||||
fn double_click_worker(click: mpsc::Receiver<()>, release: mpsc::Receiver<()>) -> Result<()> {
|
pub struct Volume {}
|
||||||
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<()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Volume {
|
impl Volume {
|
||||||
pub fn new() -> Volume {
|
pub fn new() -> Volume {
|
||||||
let (click_tx, click_rx) = mpsc::channel();
|
Volume {}
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,24 +24,12 @@ impl ControlMode for Volume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_btn_press(&mut self, _: &DialHaptics) -> Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
fn on_btn_release(&mut self, _: &DialHaptics) -> Result<()> {
|
||||||
if self.release_tx.send(()).is_err() {
|
eprintln!("mute");
|
||||||
self.worker_handle
|
fake_input::key_click(&[EV_KEY::KEY_MUTE]).map_err(Error::Evdev)?;
|
||||||
.take()
|
|
||||||
.unwrap()
|
|
||||||
.join()
|
|
||||||
.expect("panic on thread join")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ fn controller_main() -> Result<()> {
|
|||||||
Box::new(controller::controls::Zoom::new()),
|
Box::new(controller::controls::Zoom::new()),
|
||||||
Box::new(controller::controls::Volume::new()),
|
Box::new(controller::controls::Volume::new()),
|
||||||
Box::new(controller::controls::Media::new()),
|
Box::new(controller::controls::Media::new()),
|
||||||
|
Box::new(controller::controls::MediaWithVolume::new()),
|
||||||
Box::new(controller::controls::Paddle::new()),
|
Box::new(controller::controls::Paddle::new()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user