add long-press + notif based mode switching
oooooh boy, this is looking pretty slick. I'm kind-of impressed I was able to throw this together in ~2 days of post-work hacking (though I guess they were some pretty late-nights...) There's really only one feature left that I _need_ to implement, which is the on-disk persistence for selected mode. That shouldn't be too tricky though...
This commit is contained in:
@@ -2,6 +2,7 @@ use notify_rust::error::Result as NotifyResult;
|
||||
use notify_rust::{Hint, Notification, NotificationHandle, Timeout};
|
||||
|
||||
pub fn action_notification(msg: &str, icon: &str) -> NotifyResult<NotificationHandle> {
|
||||
eprintln!("sending notification: {}", msg);
|
||||
Notification::new()
|
||||
.hint(Hint::Transient(true))
|
||||
.hint(Hint::Category("device".into()))
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::mpsc;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::controller::ControlMode;
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::fake_input::FakeInput;
|
||||
use crate::DynResult;
|
||||
@@ -123,6 +123,13 @@ impl DPad {
|
||||
}
|
||||
|
||||
impl ControlMode for DPad {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
ControlModeMeta {
|
||||
name: "Paddle",
|
||||
icon: "input-gaming",
|
||||
}
|
||||
}
|
||||
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
haptics.set_mode(false, Some(3600))?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::controller::ControlMode;
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::fake_input::FakeInput;
|
||||
use crate::DynResult;
|
||||
@@ -18,6 +18,13 @@ impl Media {
|
||||
}
|
||||
|
||||
impl ControlMode for Media {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
ControlModeMeta {
|
||||
name: "Media",
|
||||
icon: "applications-multimedia",
|
||||
}
|
||||
}
|
||||
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
haptics.set_mode(false, Some(36))?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use crate::controller::ControlMode;
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::DynResult;
|
||||
|
||||
pub struct Null {}
|
||||
|
||||
impl Null {
|
||||
pub fn new() -> Null {
|
||||
Null {}
|
||||
impl ControlMode for () {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
ControlModeMeta {
|
||||
name: "null",
|
||||
icon: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlMode for Null {
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
haptics.set_mode(false, Some(0))?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::common::action_notification;
|
||||
use crate::controller::ControlMode;
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::fake_input::{FakeInput, ScrollStep};
|
||||
use crate::DynResult;
|
||||
@@ -26,6 +26,13 @@ const ZOOM_SENSITIVITY: u16 = 36;
|
||||
const SCROLL_SENSITIVITY: u16 = 90;
|
||||
|
||||
impl ControlMode for ScrollZoom {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
ControlModeMeta {
|
||||
name: "Scroll/Zoom",
|
||||
icon: "input-mouse",
|
||||
}
|
||||
}
|
||||
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
haptics.set_mode(false, Some(SCROLL_SENSITIVITY))?;
|
||||
Ok(())
|
||||
@@ -43,7 +50,7 @@ impl ControlMode for ScrollZoom {
|
||||
action_notification("Zoom Mode", "zoom-in")?;
|
||||
haptics.set_mode(false, Some(ZOOM_SENSITIVITY))?;
|
||||
} else {
|
||||
action_notification("ScrollZoom Mode", "input-mouse")?;
|
||||
action_notification("Scroll Mode", "input-mouse")?;
|
||||
haptics.set_mode(false, Some(SCROLL_SENSITIVITY))?;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::controller::ControlMode;
|
||||
use crate::controller::{ControlMode, ControlModeMeta};
|
||||
use crate::dial_device::DialHaptics;
|
||||
use crate::fake_input::FakeInput;
|
||||
use crate::DynResult;
|
||||
@@ -18,8 +18,15 @@ impl Volume {
|
||||
}
|
||||
|
||||
impl ControlMode for Volume {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
ControlModeMeta {
|
||||
name: "Volume",
|
||||
icon: "audio-volume-high",
|
||||
}
|
||||
}
|
||||
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
haptics.set_mode(false, Some(36 * 2))?;
|
||||
haptics.set_mode(true, Some(36 * 2))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,191 @@
|
||||
use crate::DynResult;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::dial_device::{DialDevice, DialEventKind, DialHaptics};
|
||||
use crate::DynResult;
|
||||
|
||||
pub mod controls;
|
||||
|
||||
pub struct ControlModeMeta {
|
||||
name: &'static str,
|
||||
icon: &'static str,
|
||||
}
|
||||
|
||||
pub trait ControlMode {
|
||||
fn meta(&self) -> ControlModeMeta;
|
||||
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()>;
|
||||
fn on_end(&mut self, _haptics: &DialHaptics) -> DynResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_btn_press(&mut self, haptics: &DialHaptics) -> DynResult<()>;
|
||||
fn on_btn_release(&mut self, haptics: &DialHaptics) -> DynResult<()>;
|
||||
fn on_dial(&mut self, haptics: &DialHaptics, delta: i32) -> DynResult<()>;
|
||||
}
|
||||
|
||||
enum ActiveMode {
|
||||
Normal(usize),
|
||||
Meta,
|
||||
}
|
||||
|
||||
pub struct DialController {
|
||||
device: DialDevice,
|
||||
|
||||
mode: Box<dyn ControlMode>,
|
||||
modes: Vec<Box<dyn ControlMode>>,
|
||||
active_mode: ActiveMode,
|
||||
|
||||
new_mode: Arc<Mutex<Option<usize>>>,
|
||||
meta_mode: Box<dyn ControlMode>, // always MetaMode
|
||||
}
|
||||
|
||||
impl DialController {
|
||||
pub fn new(device: DialDevice, default_mode: Box<dyn ControlMode>) -> DialController {
|
||||
DialController {
|
||||
mode: default_mode,
|
||||
pub fn new(device: DialDevice, modes: Vec<Box<dyn ControlMode>>) -> DialController {
|
||||
let metas = modes.iter().map(|m| m.meta()).collect();
|
||||
|
||||
let new_mode = Arc::new(Mutex::new(None));
|
||||
|
||||
DialController {
|
||||
device,
|
||||
|
||||
modes,
|
||||
active_mode: ActiveMode::Normal(0),
|
||||
|
||||
new_mode: new_mode.clone(),
|
||||
meta_mode: Box::new(MetaMode::new(new_mode, 0, metas)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> DynResult<()> {
|
||||
let haptics = self.device.haptics();
|
||||
|
||||
self.mode.on_start(haptics)?;
|
||||
self.modes[0].on_start(self.device.haptics())?;
|
||||
|
||||
loop {
|
||||
let evt = self.device.next_event()?;
|
||||
let haptics = self.device.haptics();
|
||||
|
||||
if let Some(new_mode) = self.new_mode.lock().unwrap().take() {
|
||||
self.active_mode = ActiveMode::Normal(new_mode);
|
||||
self.modes[new_mode].on_start(haptics)?;
|
||||
}
|
||||
|
||||
let mode = match self.active_mode {
|
||||
ActiveMode::Normal(idx) => &mut self.modes[idx],
|
||||
ActiveMode::Meta => &mut self.meta_mode,
|
||||
};
|
||||
|
||||
// TODO: press and hold (+ rotate?) to switch between modes
|
||||
|
||||
match evt.kind {
|
||||
DialEventKind::Ignored => {}
|
||||
DialEventKind::ButtonPress => self.mode.on_btn_press(haptics)?,
|
||||
DialEventKind::ButtonRelease => self.mode.on_btn_release(haptics)?,
|
||||
DialEventKind::Dial(delta) => self.mode.on_dial(haptics, delta)?,
|
||||
DialEventKind::ButtonPress => mode.on_btn_press(haptics)?,
|
||||
DialEventKind::ButtonRelease => mode.on_btn_release(haptics)?,
|
||||
DialEventKind::Dial(delta) => mode.on_dial(haptics, delta)?,
|
||||
DialEventKind::ButtonLongPress => {
|
||||
eprintln!("long press!");
|
||||
if !matches!(self.active_mode, ActiveMode::Meta) {
|
||||
mode.on_end(haptics)?;
|
||||
self.active_mode = ActiveMode::Meta;
|
||||
self.meta_mode.on_start(haptics)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mode for switching between modes.
|
||||
struct MetaMode {
|
||||
// constant
|
||||
metas: Vec<ControlModeMeta>,
|
||||
|
||||
// stateful (across invocations)
|
||||
current_mode: usize,
|
||||
new_mode: Arc<Mutex<Option<usize>>>,
|
||||
|
||||
// reset in on_start
|
||||
first_release: bool,
|
||||
notif: Option<notify_rust::NotificationHandle>,
|
||||
}
|
||||
|
||||
impl MetaMode {
|
||||
fn new(
|
||||
new_mode: Arc<Mutex<Option<usize>>>,
|
||||
current_mode: usize,
|
||||
metas: Vec<ControlModeMeta>,
|
||||
) -> MetaMode {
|
||||
MetaMode {
|
||||
metas,
|
||||
|
||||
current_mode,
|
||||
new_mode,
|
||||
|
||||
first_release: true,
|
||||
notif: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlMode for MetaMode {
|
||||
fn meta(&self) -> ControlModeMeta {
|
||||
unreachable!() // meta mode never queries itself
|
||||
}
|
||||
|
||||
fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
use notify_rust::*;
|
||||
self.notif = Some(
|
||||
Notification::new()
|
||||
.hint(Hint::Resident(true))
|
||||
.hint(Hint::Category("device".into()))
|
||||
.timeout(Timeout::Never)
|
||||
.summary("Surface Dial")
|
||||
.body(&format!(
|
||||
"Entered Meta Mode (From Mode: {})",
|
||||
self.metas[self.current_mode].name
|
||||
))
|
||||
.icon("emblem-system")
|
||||
.show()?,
|
||||
);
|
||||
|
||||
haptics.buzz(1)?;
|
||||
|
||||
self.first_release = true;
|
||||
|
||||
haptics.set_mode(true, Some(36))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_btn_press(&mut self, _haptics: &DialHaptics) -> DynResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_btn_release(&mut self, haptics: &DialHaptics) -> DynResult<()> {
|
||||
if self.first_release {
|
||||
self.first_release = false;
|
||||
} else {
|
||||
*self.new_mode.lock().unwrap() = Some(self.current_mode);
|
||||
haptics.buzz(1)?;
|
||||
|
||||
self.notif.take().unwrap().close();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_dial(&mut self, _haptics: &DialHaptics, delta: i32) -> DynResult<()> {
|
||||
if delta > 0 {
|
||||
self.current_mode += 1;
|
||||
} else {
|
||||
self.current_mode -= 1;
|
||||
}
|
||||
|
||||
self.current_mode %= self.metas.len();
|
||||
|
||||
let mode_meta = &self.metas[self.current_mode];
|
||||
if let Some(ref mut notification) = self.notif {
|
||||
notification
|
||||
.body(&format!("New Mode: {}", mode_meta.name))
|
||||
.icon(mode_meta.icon);
|
||||
notification.update();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::fs;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use evdev_rs::{Device, InputEvent};
|
||||
@@ -7,10 +8,11 @@ use hidapi::{HidApi, HidDevice};
|
||||
use crate::error::Error;
|
||||
|
||||
pub struct DialDevice {
|
||||
// TODO: explore what the control channel can be used for...
|
||||
_control: Device,
|
||||
axis: Device,
|
||||
long_press_timeout: Duration,
|
||||
haptics: DialHaptics,
|
||||
events: mpsc::Receiver<DialEvent>,
|
||||
|
||||
possible_long_press: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -25,10 +27,11 @@ pub enum DialEventKind {
|
||||
ButtonPress,
|
||||
ButtonRelease,
|
||||
Dial(i32),
|
||||
ButtonLongPress,
|
||||
}
|
||||
|
||||
impl DialDevice {
|
||||
pub fn new() -> Result<DialDevice, crate::Error> {
|
||||
pub fn new(long_press_timeout: Duration) -> Result<DialDevice, crate::Error> {
|
||||
let mut control = None;
|
||||
let mut axis = None;
|
||||
|
||||
@@ -62,24 +65,66 @@ impl DialDevice {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: explore what the control channel can be used for...
|
||||
let _control = control.ok_or(Error::MissingDial)?;
|
||||
let axis = axis.ok_or(Error::MissingDial)?;
|
||||
|
||||
let (events_tx, events_rx) = mpsc::channel();
|
||||
|
||||
// TODO: interleave control events with regular events
|
||||
|
||||
std::thread::spawn({
|
||||
let events = events_tx;
|
||||
move || {
|
||||
loop {
|
||||
let (_axis_status, axis_evt) = axis
|
||||
.next_event(evdev_rs::ReadFlag::NORMAL)
|
||||
.expect("Error::Evdev");
|
||||
// assert!(matches!(axis_status, ReadStatus::Success));
|
||||
let event = DialEvent::from_raw_evt(axis_evt.clone())
|
||||
.expect("Error::UnexpectedEvt(axis_evt)");
|
||||
|
||||
events.send(event).expect("failed to send axis event");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(DialDevice {
|
||||
_control: control.ok_or(Error::MissingDial)?,
|
||||
axis: axis.ok_or(Error::MissingDial)?,
|
||||
long_press_timeout,
|
||||
events: events_rx,
|
||||
haptics: DialHaptics::new()?,
|
||||
|
||||
possible_long_press: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_event(&self) -> Result<DialEvent, Error> {
|
||||
// TODO: figure out how to interleave control events into the same event stream.
|
||||
pub fn next_event(&mut self) -> Result<DialEvent, Error> {
|
||||
let evt = if self.possible_long_press {
|
||||
self.events.recv_timeout(self.long_press_timeout)
|
||||
} else {
|
||||
self.events
|
||||
.recv()
|
||||
.map_err(|_| mpsc::RecvTimeoutError::Disconnected)
|
||||
};
|
||||
|
||||
let (_axis_status, axis_evt) = self
|
||||
.axis
|
||||
.next_event(evdev_rs::ReadFlag::NORMAL)
|
||||
.map_err(Error::Evdev)?;
|
||||
// assert!(matches!(axis_status, ReadStatus::Success));
|
||||
|
||||
let event =
|
||||
DialEvent::from_raw_evt(axis_evt.clone()).ok_or(Error::UnexpectedEvt(axis_evt))?;
|
||||
let event = match evt {
|
||||
Ok(event) => {
|
||||
match event.kind {
|
||||
DialEventKind::ButtonPress => self.possible_long_press = true,
|
||||
DialEventKind::ButtonRelease => self.possible_long_press = false,
|
||||
_ => {}
|
||||
}
|
||||
event
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
self.possible_long_press = false;
|
||||
DialEvent {
|
||||
time: Duration::from_secs(0), // this could be improved...
|
||||
kind: DialEventKind::ButtonLongPress,
|
||||
}
|
||||
}
|
||||
Err(_e) => panic!("Could not recv event"),
|
||||
};
|
||||
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
20
src/main.rs
20
src/main.rs
@@ -24,7 +24,7 @@ fn main() {
|
||||
fn true_main() -> DynResult<()> {
|
||||
println!("Started.");
|
||||
|
||||
let dial = DialDevice::new()?;
|
||||
let dial = DialDevice::new(std::time::Duration::from_millis(750))?;
|
||||
println!("Found the dial.");
|
||||
|
||||
std::thread::spawn(move || {
|
||||
@@ -34,7 +34,7 @@ fn true_main() -> DynResult<()> {
|
||||
.timeout(Timeout::Never)
|
||||
.summary("Surface Dial")
|
||||
.body("Active!")
|
||||
.icon("input-mouse")
|
||||
.icon("media-optical") // it should be vaguely circular :P
|
||||
.show()
|
||||
.expect("failed to send notification");
|
||||
|
||||
@@ -46,13 +46,15 @@ fn true_main() -> DynResult<()> {
|
||||
}
|
||||
});
|
||||
|
||||
// let default_mode = Box::new(controller::controls::Null::new());
|
||||
let default_mode = Box::new(controller::controls::ScrollZoom::new());
|
||||
// let default_mode = Box::new(controller::controls::Volume::new());
|
||||
// let default_mode = Box::new(controller::controls::Media::new());
|
||||
// let default_mode = Box::new(controller::controls::DPad::new());
|
||||
|
||||
let mut controller = DialController::new(dial, default_mode);
|
||||
let mut controller = DialController::new(
|
||||
dial,
|
||||
vec![
|
||||
Box::new(controller::controls::ScrollZoom::new()),
|
||||
Box::new(controller::controls::Volume::new()),
|
||||
Box::new(controller::controls::Media::new()),
|
||||
Box::new(controller::controls::DPad::new()),
|
||||
],
|
||||
);
|
||||
|
||||
controller.run()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user