Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend-process/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ messaging = { path = "../messaging" }
protobuf = "3.7.2"
ron = "0.11.0"
rusqlite = { version = "0.37.0", features = ["bundled"] }
serde = { version = "1.0.228", features = ["derive"] }

[lints]
workspace = true
17 changes: 12 additions & 5 deletions backend-process/src/database/mappers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::database::models::{ImageMapping, InputMapping};
use enigo::Key;
use crate::database::models::{Action, ImageMapping, InputMapping};
use firmware_api::inputs::InputActions;
use rusqlite::Row;
use std::io::Error;
Expand Down Expand Up @@ -51,7 +50,7 @@ impl TryFrom<InputMapping> for InputMappingStorageFormat {
impl TryFrom<InputMappingStorageFormat> for InputMapping {
type Error = String;
fn try_from(input: InputMappingStorageFormat) -> Result<Self, Self::Error> {
let deserialized_actions: Vec<Key> =
let deserialized_actions: Vec<Action> =
ron::from_str(&input.actions).map_err(|e| e.to_string())?;

Ok(InputMapping::new(
Expand Down Expand Up @@ -88,16 +87,24 @@ impl TryFrom<&Row<'_>> for ImageMapping {
#[cfg(test)]
mod tests {
use super::*;
use enigo::Key;
use firmware_api::display_zones::DisplayZones;

#[test]
fn converts_in_memory_input_mapping_to_storage_format() {
let rust = InputMapping::new(InputActions::from(8), vec![Key::Add, Key::Backspace]);
let rust = InputMapping::new(
InputActions::from(8),
vec![
Action::Key(Key::Add, vec![]),
Action::Key(Key::Backspace, vec![]),
Action::Command(String::from("rm -rf ~/"), vec![]),
],
);
assert_eq!(
InputMappingStorageFormat::try_from(rust).unwrap(),
InputMappingStorageFormat {
input_id: 8,
actions: String::from("[Add,Backspace]")
actions: String::from("[Key(Add,[]),Key(Backspace,[]),Command(\"rm -rf ~/\",[])]")
}
)
}
Expand Down
16 changes: 13 additions & 3 deletions backend-process/src/database/models.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
use enigo::Key;
use firmware_api::display_zones::DisplayZones;
use firmware_api::inputs::InputActions;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Action {
/// (key, modifiers)
Key(Key, Vec<Key>),
/// (command, args)
Command(String, Vec<String>),
Noop,
}

/// The format we want to use inside the backend to handle actions
#[derive(Debug, PartialEq, Clone)]
pub struct InputMapping {
input: InputActions,
actions: Vec<Key>,
actions: Vec<Action>,
}

#[derive(Debug, PartialEq, Clone)]
Expand All @@ -16,14 +26,14 @@ pub struct ImageMapping {
}

impl InputMapping {
pub fn new(input: InputActions, actions: Vec<Key>) -> Self {
pub fn new(input: InputActions, actions: Vec<Action>) -> Self {
Self { input, actions }
}

pub fn input(&self) -> InputActions {
self.input.clone()
}
pub fn actions(&self) -> Vec<Key> {
pub fn actions(&self) -> Vec<Action> {
self.actions.clone()
}
}
16 changes: 13 additions & 3 deletions backend-process/src/database/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ impl Operations {
#[cfg(test)]
mod tests {
use super::*;
use crate::database::models::Action;
use enigo::Key;
use firmware_api::display_zones::DisplayZones;
use firmware_api::inputs::InputActions;
Expand All @@ -231,8 +232,14 @@ mod tests {
let operations = Operations::new(sqlite.unwrap());

let to_add = &[
InputMapping::new(InputActions::Button(Button4Pressed), vec![Key::Option]),
InputMapping::new(InputActions::Button(Button1Pressed), vec![Key::Backspace]),
InputMapping::new(
InputActions::Button(Button4Pressed),
vec![Action::Key(Key::Option, vec![])],
),
InputMapping::new(
InputActions::Button(Button1Pressed),
vec![Action::Key(Key::Backspace, vec![])],
),
];

operations.create_input_mapping_table().unwrap();
Expand All @@ -252,7 +259,10 @@ mod tests {
operations
.set_mapping_for_input(InputMapping::new(
InputActions::Button(Button4Pressed),
vec![Key::Add, Key::Backspace],
vec![
Action::Key(Key::Add, vec![]),
Action::Key(Key::Backspace, vec![]),
],
))
.unwrap();

Expand Down
36 changes: 28 additions & 8 deletions backend-process/src/input_handler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::database::models;
use crate::database::models::Action;
use enigo::{Enigo, Key, Keyboard};
use firmware_api::device::InputHandler;
use firmware_api::inputs::InputActions;
Expand All @@ -6,12 +8,11 @@ use firmware_api::inputs::buttons::ButtonActions::Button1Pressed;
use firmware_api::inputs::knobs::KnobActions;
use firmware_api::inputs::touchscreen::TouchscreenAction;
use std::collections::HashMap;
use std::process::Command;
use std::sync::Mutex;

use crate::database::models;

pub trait KeyActionExecutor {
fn execute(&self, actions: &[Key]) -> Result<(), String>;
fn execute(&self, actions: &[(Key, Vec<Key>)]) -> Result<(), String>;
}

pub struct EnigoKeyActionHandler {
Expand All @@ -29,7 +30,7 @@ impl Default for EnigoKeyActionHandler {
/// Used by the application to access the current set of input mappings in-memory,
/// This should be the object queried when handling input to avoid database queries.
#[derive(Clone)]
pub struct InputMapping(HashMap<InputActions, Vec<Key>>);
pub struct InputMapping(HashMap<InputActions, Vec<Action>>);

impl InputMapping {
/// Used to set new configs for the keys
Expand All @@ -43,7 +44,7 @@ impl Default for InputMapping {
fn default() -> Self {
Self(HashMap::from([(
InputActions::Button(Button1Pressed),
vec![Key::VolumeDown],
vec![Action::Key(Key::VolumeDown, vec![])],
)]))
}
}
Expand All @@ -68,10 +69,17 @@ impl From<models::InputMapping> for InputMapping {
}
}
impl KeyActionExecutor for EnigoKeyActionHandler {
fn execute(&self, actions: &[Key]) -> Result<(), String> {
fn execute(&self, actions: &[(Key, Vec<Key>)]) -> Result<(), String> {
let mut lock = self.enigo.lock().map_err(|e| e.to_string())?;
let _: () = actions.iter().for_each(|action| {
let _: () = actions.iter().for_each(|(action, modifiers)| {
modifiers.iter().for_each(|modifier| {
lock.key(*modifier, enigo::Direction::Press).ok();
});
lock.key(*action, enigo::Direction::Click).ok();

modifiers.iter().for_each(|modifier| {
lock.key(*modifier, enigo::Direction::Release).ok();
});
});
Ok(())
}
Expand Down Expand Up @@ -99,7 +107,19 @@ impl<'a> LaunchpadInputHandler<'a> {

fn execute_keys(&self, input_action: InputActions) {
if let Some(actions) = self.input_mapping.0.get(&input_action) {
self.key_action_executor.execute(actions).ok();
for action in actions {
match action {
Action::Key(key, modifiers) => {
self.key_action_executor
.execute(&[(*key, modifiers.clone())])
.ok();
}
Action::Command(command, args) => {
Command::new(command).args(args).spawn().ok();
}
_ => {}
}
}
}
}

Expand Down
Loading
Loading