From fd617698f32c6f85bfcea89b57477073aa23afec Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:37:59 +1300 Subject: [PATCH 1/9] feat: refactor to support commands in db and proto --- Cargo.lock | 1 + backend-process/Cargo.toml | 1 + backend-process/src/database/mappers.rs | 17 +- backend-process/src/database/models.rs | 13 +- backend-process/src/database/operations.rs | 13 +- backend-process/src/input_handler.rs | 19 +- backend-process/src/protobuf_conversion.rs | 72 +++-- messaging/protobufs/commands/key_config.proto | 10 +- messaging/src/protos/key_config.rs | 247 ++++++++++++++++-- 9 files changed, 335 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54babd8..ed70731 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,6 +400,7 @@ dependencies = [ "protobuf", "ron", "rusqlite", + "serde", ] [[package]] diff --git a/backend-process/Cargo.toml b/backend-process/Cargo.toml index 9ee4730..491165a 100644 --- a/backend-process/Cargo.toml +++ b/backend-process/Cargo.toml @@ -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 diff --git a/backend-process/src/database/mappers.rs b/backend-process/src/database/mappers.rs index 41257a2..ba3a3f4 100644 --- a/backend-process/src/database/mappers.rs +++ b/backend-process/src/database/mappers.rs @@ -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; @@ -51,7 +50,7 @@ impl TryFrom for InputMappingStorageFormat { impl TryFrom for InputMapping { type Error = String; fn try_from(input: InputMappingStorageFormat) -> Result { - let deserialized_actions: Vec = + let deserialized_actions: Vec = ron::from_str(&input.actions).map_err(|e| e.to_string())?; Ok(InputMapping::new( @@ -87,17 +86,25 @@ impl TryFrom<&Row<'_>> for ImageMapping { #[cfg(test)] mod tests { + use enigo::Key; use super::*; 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), + Action::Key(Key::Backspace), + Action::Command(String::from("rm -rf ~/")), + ], + ); 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 ~/\")]") } ) } diff --git a/backend-process/src/database/models.rs b/backend-process/src/database/models.rs index ad617eb..231c855 100644 --- a/backend-process/src/database/models.rs +++ b/backend-process/src/database/models.rs @@ -1,12 +1,19 @@ 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(Key), + Command(String), +} /// The format we want to use inside the backend to handle actions #[derive(Debug, PartialEq, Clone)] pub struct InputMapping { input: InputActions, - actions: Vec, + actions: Vec, } #[derive(Debug, PartialEq, Clone)] @@ -16,14 +23,14 @@ pub struct ImageMapping { } impl InputMapping { - pub fn new(input: InputActions, actions: Vec) -> Self { + pub fn new(input: InputActions, actions: Vec) -> Self { Self { input, actions } } pub fn input(&self) -> InputActions { self.input.clone() } - pub fn actions(&self) -> Vec { + pub fn actions(&self) -> Vec { self.actions.clone() } } diff --git a/backend-process/src/database/operations.rs b/backend-process/src/database/operations.rs index a2c0f81..492446b 100644 --- a/backend-process/src/database/operations.rs +++ b/backend-process/src/database/operations.rs @@ -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; @@ -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)], + ), + InputMapping::new( + InputActions::Button(Button1Pressed), + vec![Action::Key(Key::Backspace)], + ), ]; operations.create_input_mapping_table().unwrap(); @@ -252,7 +259,7 @@ mod tests { operations .set_mapping_for_input(InputMapping::new( InputActions::Button(Button4Pressed), - vec![Key::Add, Key::Backspace], + vec![Action::Key(Key::Add), Action::Key(Key::Backspace)], )) .unwrap(); diff --git a/backend-process/src/input_handler.rs b/backend-process/src/input_handler.rs index 9356f95..eadc238 100644 --- a/backend-process/src/input_handler.rs +++ b/backend-process/src/input_handler.rs @@ -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; @@ -8,8 +10,6 @@ use firmware_api::inputs::touchscreen::TouchscreenAction; use std::collections::HashMap; use std::sync::Mutex; -use crate::database::models; - pub trait KeyActionExecutor { fn execute(&self, actions: &[Key]) -> Result<(), String>; } @@ -29,7 +29,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>); +pub struct InputMapping(HashMap>); impl InputMapping { /// Used to set new configs for the keys @@ -43,7 +43,7 @@ impl Default for InputMapping { fn default() -> Self { Self(HashMap::from([( InputActions::Button(Button1Pressed), - vec![Key::VolumeDown], + vec![Action::Key(Key::VolumeDown)], )])) } } @@ -99,7 +99,16 @@ 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) => { + self.key_action_executor.execute(&[*key]).ok(); + } + Action::Command(command) => { + todo!("{}", command) + } + } + } } } diff --git a/backend-process/src/protobuf_conversion.rs b/backend-process/src/protobuf_conversion.rs index 722dfeb..72b2bdb 100644 --- a/backend-process/src/protobuf_conversion.rs +++ b/backend-process/src/protobuf_conversion.rs @@ -1,4 +1,4 @@ -use crate::database::models::{ImageMapping, InputMapping}; +use crate::database::models::{Action, ImageMapping, InputMapping}; use enigo::Key; use firmware_api::display_zones::DisplayZones; use firmware_api::inputs::InputActions; @@ -7,6 +7,7 @@ use firmware_api::inputs::buttons::ButtonActions; use firmware_api::inputs::knobs::KnobActions; use firmware_api::inputs::touchscreen::TouchscreenAction; use messaging::protos; +use messaging::protos::key_config::action::Action_data; use protobuf::Enum; use std::io::{Error, ErrorKind}; @@ -153,16 +154,28 @@ impl TryFrom for InputMapping { fn try_from(value: protos::key_config::KeyConfig) -> Result { let input_id: InputActionWrapper = value.input_id.enum_value().unwrap().into(); - let actions: Vec = value + let actions = value .actions .iter() - .map(|a| a.key_action().clone().try_into().map_err(|_| ())) - .collect::, _>>()?; + .map(|a| { + match a.clone().action_data { + Some(item) => match item { + Action_data::CommandAction(command) => Action::Command(command.command), + Action_data::KeyAction(key) => { + if let Ok(key_val) = KeyWrapper::try_from(key) { + return Action::Key(key_val.0); + } + // Stub out invalid values + Action::Command(String::new()) + } + _ => Action::Command(String::new()), + }, + _ => Action::Command(String::new()), + } + }) + .collect::>(); - Ok(InputMapping::new( - input_id.0, - actions.iter().map(|a| a.0).collect(), - )) + Ok(InputMapping::new(input_id.0, actions)) } } @@ -384,19 +397,17 @@ mod tests { fn create_proto_fixture( proto_input_id: protos::inputs::InputId, - proto_key: protos::keys::Key, + action_data: Vec, ) -> protos::key_config::KeyConfig { protos::key_config::KeyConfig { input_id: protobuf::EnumOrUnknown::new(proto_input_id), - actions: vec![protos::key_config::Action { - action_data: Some(protos::key_config::action::Action_data::KeyAction( - protos::key_config::KeyAction { - key: protobuf::EnumOrUnknown::from(proto_key), - ..protos::key_config::KeyAction::default() - }, - )), - ..protos::key_config::Action::default() - }], + actions: action_data + .iter() + .map(|item| protos::key_config::Action { + action_data: Some(item.clone()), + ..protos::key_config::Action::default() + }) + .collect(), ..protos::key_config::KeyConfig::default() } } @@ -404,14 +415,17 @@ mod tests { fn converts_mapping_into_model() { let proto = create_proto_fixture( protos::inputs::InputId::KNOB_1_CLOCKWISE, - protos::keys::Key::KEY_ADD, + vec![Action_data::KeyAction(protos::key_config::KeyAction { + key: protos::keys::Key::KEY_ADD.into(), + ..protos::key_config::KeyAction::default() + })], ); assert_eq!( InputMapping::try_from(proto).unwrap(), InputMapping::new( - InputActions::Knob(KnobActions::Knob1Clockwise), - vec![Key::Add] + Knob(KnobActions::Knob1Clockwise), + vec![Action::Key(Key::Add)] ) ) } @@ -420,12 +434,24 @@ mod tests { fn converts_mapping_into_model_with_invalid_input() { let proto = create_proto_fixture( protos::inputs::InputId::INPUT_ACTION_UNSPECIFIED, - protos::keys::Key::KEY_ADD, + vec![ + Action_data::KeyAction(protos::key_config::KeyAction { + key: protos::keys::Key::KEY_ADD.into(), + ..protos::key_config::KeyAction::default() + }), + Action_data::CommandAction(protos::key_config::CommandAction { + command: String::from("NAH"), + ..protos::key_config::CommandAction::default() + }), + ], ); assert_eq!( InputMapping::try_from(proto).unwrap(), - InputMapping::new(Unknown, vec![Key::Add]) + InputMapping::new( + Unknown, + vec![Action::Key(Key::Add), Action::Command(String::from("NAH"))] + ) ) } } diff --git a/messaging/protobufs/commands/key_config.proto b/messaging/protobufs/commands/key_config.proto index e22d837..4d2d468 100644 --- a/messaging/protobufs/commands/key_config.proto +++ b/messaging/protobufs/commands/key_config.proto @@ -14,6 +14,7 @@ message Action { ActionType type = 1; oneof action_data { KeyAction key_action = 2; + CommandAction command_action = 3; } } @@ -23,6 +24,11 @@ enum ActionType { message KeyAction { Key key = 1; // e.g., "v", "f1", "enter" - optional uint32 unicode = 2; // only required if we are using a key input - optional uint32 other_key_code = 3; + optional Key modifier = 2; + optional uint32 unicode = 3; // only required if we are using a key input + optional uint32 other_key_code = 4; +} + +message CommandAction { + string command = 2; } diff --git a/messaging/src/protos/key_config.rs b/messaging/src/protos/key_config.rs index 1307768..77da297 100644 --- a/messaging/src/protos/key_config.rs +++ b/messaging/src/protos/key_config.rs @@ -238,8 +238,57 @@ impl Action { } } + // .key_config.CommandAction command_action = 3; + + pub fn command_action(&self) -> &CommandAction { + match self.action_data { + ::std::option::Option::Some(action::Action_data::CommandAction(ref v)) => v, + _ => ::default_instance(), + } + } + + pub fn clear_command_action(&mut self) { + self.action_data = ::std::option::Option::None; + } + + pub fn has_command_action(&self) -> bool { + match self.action_data { + ::std::option::Option::Some(action::Action_data::CommandAction(..)) => true, + _ => false, + } + } + + // Param is passed by value, moved + pub fn set_command_action(&mut self, v: CommandAction) { + self.action_data = ::std::option::Option::Some(action::Action_data::CommandAction(v)) + } + + // Mutable pointer to the field. + pub fn mut_command_action(&mut self) -> &mut CommandAction { + if let ::std::option::Option::Some(action::Action_data::CommandAction(_)) = self.action_data { + } else { + self.action_data = ::std::option::Option::Some(action::Action_data::CommandAction(CommandAction::new())); + } + match self.action_data { + ::std::option::Option::Some(action::Action_data::CommandAction(ref mut v)) => v, + _ => panic!(), + } + } + + // Take field + pub fn take_command_action(&mut self) -> CommandAction { + if self.has_command_action() { + match self.action_data.take() { + ::std::option::Option::Some(action::Action_data::CommandAction(v)) => v, + _ => panic!(), + } + } else { + CommandAction::new() + } + } + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(2); + let mut fields = ::std::vec::Vec::with_capacity(3); let mut oneofs = ::std::vec::Vec::with_capacity(1); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "type", @@ -253,6 +302,13 @@ impl Action { Action::mut_key_action, Action::set_key_action, )); + fields.push(::protobuf::reflect::rt::v2::make_oneof_message_has_get_mut_set_accessor::<_, CommandAction>( + "command_action", + Action::has_command_action, + Action::command_action, + Action::mut_command_action, + Action::set_command_action, + )); oneofs.push(action::Action_data::generated_oneof_descriptor_data()); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "Action", @@ -278,6 +334,9 @@ impl ::protobuf::Message for Action { 18 => { self.action_data = ::std::option::Option::Some(action::Action_data::KeyAction(is.read_message()?)); }, + 26 => { + self.action_data = ::std::option::Option::Some(action::Action_data::CommandAction(is.read_message()?)); + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -299,6 +358,10 @@ impl ::protobuf::Message for Action { let len = v.compute_size(); my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; }, + &action::Action_data::CommandAction(ref v) => { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }, }; } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); @@ -315,6 +378,9 @@ impl ::protobuf::Message for Action { &action::Action_data::KeyAction(ref v) => { ::protobuf::rt::write_message_field_with_cached_size(2, v, os)?; }, + &action::Action_data::CommandAction(ref v) => { + ::protobuf::rt::write_message_field_with_cached_size(3, v, os)?; + }, }; } os.write_unknown_fields(self.special_fields.unknown_fields())?; @@ -336,6 +402,7 @@ impl ::protobuf::Message for Action { fn clear(&mut self) { self.type_ = ::protobuf::EnumOrUnknown::new(ActionType::ACTION_TYPE_KEY); self.action_data = ::std::option::Option::None; + self.action_data = ::std::option::Option::None; self.special_fields.clear(); } @@ -375,6 +442,8 @@ pub mod action { pub enum Action_data { // @@protoc_insertion_point(oneof_field:key_config.Action.key_action) KeyAction(super::KeyAction), + // @@protoc_insertion_point(oneof_field:key_config.Action.command_action) + CommandAction(super::CommandAction), } impl ::protobuf::Oneof for Action_data { @@ -400,6 +469,8 @@ pub struct KeyAction { // message fields // @@protoc_insertion_point(field:key_config.KeyAction.key) pub key: ::protobuf::EnumOrUnknown, + // @@protoc_insertion_point(field:key_config.KeyAction.modifier) + pub modifier: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:key_config.KeyAction.unicode) pub unicode: ::std::option::Option, // @@protoc_insertion_point(field:key_config.KeyAction.other_key_code) @@ -421,13 +492,18 @@ impl KeyAction { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(3); + let mut fields = ::std::vec::Vec::with_capacity(4); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "key", |m: &KeyAction| { &m.key }, |m: &mut KeyAction| { &mut m.key }, )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "modifier", + |m: &KeyAction| { &m.modifier }, + |m: &mut KeyAction| { &mut m.modifier }, + )); fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( "unicode", |m: &KeyAction| { &m.unicode }, @@ -460,9 +536,12 @@ impl ::protobuf::Message for KeyAction { self.key = is.read_enum_or_unknown()?; }, 16 => { - self.unicode = ::std::option::Option::Some(is.read_uint32()?); + self.modifier = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, 24 => { + self.unicode = ::std::option::Option::Some(is.read_uint32()?); + }, + 32 => { self.other_key_code = ::std::option::Option::Some(is.read_uint32()?); }, tag => { @@ -480,11 +559,14 @@ impl ::protobuf::Message for KeyAction { if self.key != ::protobuf::EnumOrUnknown::new(super::keys::Key::KEY_UNSPECIFIED) { my_size += ::protobuf::rt::int32_size(1, self.key.value()); } + if let Some(v) = self.modifier { + my_size += ::protobuf::rt::int32_size(2, v.value()); + } if let Some(v) = self.unicode { - my_size += ::protobuf::rt::uint32_size(2, v); + my_size += ::protobuf::rt::uint32_size(3, v); } if let Some(v) = self.other_key_code { - my_size += ::protobuf::rt::uint32_size(3, v); + my_size += ::protobuf::rt::uint32_size(4, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -495,11 +577,14 @@ impl ::protobuf::Message for KeyAction { if self.key != ::protobuf::EnumOrUnknown::new(super::keys::Key::KEY_UNSPECIFIED) { os.write_enum(1, ::protobuf::EnumOrUnknown::value(&self.key))?; } + if let Some(v) = self.modifier { + os.write_enum(2, ::protobuf::EnumOrUnknown::value(&v))?; + } if let Some(v) = self.unicode { - os.write_uint32(2, v)?; + os.write_uint32(3, v)?; } if let Some(v) = self.other_key_code { - os.write_uint32(3, v)?; + os.write_uint32(4, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -519,6 +604,7 @@ impl ::protobuf::Message for KeyAction { fn clear(&mut self) { self.key = ::protobuf::EnumOrUnknown::new(super::keys::Key::KEY_UNSPECIFIED); + self.modifier = ::std::option::Option::None; self.unicode = ::std::option::Option::None; self.other_key_code = ::std::option::Option::None; self.special_fields.clear(); @@ -527,6 +613,7 @@ impl ::protobuf::Message for KeyAction { fn default_instance() -> &'static KeyAction { static instance: KeyAction = KeyAction { key: ::protobuf::EnumOrUnknown::from_i32(0), + modifier: ::std::option::Option::None, unicode: ::std::option::Option::None, other_key_code: ::std::option::Option::None, special_fields: ::protobuf::SpecialFields::new(), @@ -552,6 +639,128 @@ impl ::protobuf::reflect::ProtobufValue for KeyAction { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:key_config.CommandAction) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct CommandAction { + // message fields + // @@protoc_insertion_point(field:key_config.CommandAction.command) + pub command: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:key_config.CommandAction.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a CommandAction { + fn default() -> &'a CommandAction { + ::default_instance() + } +} + +impl CommandAction { + pub fn new() -> CommandAction { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "command", + |m: &CommandAction| { &m.command }, + |m: &mut CommandAction| { &mut m.command }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "CommandAction", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for CommandAction { + const NAME: &'static str = "CommandAction"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 18 => { + self.command = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.command.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.command); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.command.is_empty() { + os.write_string(2, &self.command)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> CommandAction { + CommandAction::new() + } + + fn clear(&mut self) { + self.command.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static CommandAction { + static instance: CommandAction = CommandAction { + command: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for CommandAction { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("CommandAction").unwrap()).clone() + } +} + +impl ::std::fmt::Display for CommandAction { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for CommandAction { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] // @@protoc_insertion_point(enum:key_config.ActionType) pub enum ActionType { @@ -613,15 +822,18 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \n\x19commands/key_config.proto\x12\nkey_config\x1a\x1acommands/common/k\ eys.proto\x1a\x1ccommands/common/inputs.proto\"^\n\tKeyConfig\x12#\n\x08\ input_id\x18\x01\x20\x01(\x0e2\x08.InputIdR\x07inputId\x12,\n\x07actions\ - \x18\x02\x20\x03(\x0b2\x12.key_config.ActionR\x07actions\"{\n\x06Action\ - \x12*\n\x04type\x18\x01\x20\x01(\x0e2\x16.key_config.ActionTypeR\x04type\ - \x126\n\nkey_action\x18\x02\x20\x01(\x0b2\x15.key_config.KeyActionH\0R\t\ - keyActionB\r\n\x0baction_data\"\x8c\x01\n\tKeyAction\x12\x16\n\x03key\ - \x18\x01\x20\x01(\x0e2\x04.KeyR\x03key\x12\x1d\n\x07unicode\x18\x02\x20\ - \x01(\rH\0R\x07unicode\x88\x01\x01\x12)\n\x0eother_key_code\x18\x03\x20\ - \x01(\rH\x01R\x0cotherKeyCode\x88\x01\x01B\n\n\x08_unicodeB\x11\n\x0f_ot\ - her_key_code*!\n\nActionType\x12\x13\n\x0fACTION_TYPE_KEY\x10\0b\x06prot\ - o3\ + \x18\x02\x20\x03(\x0b2\x12.key_config.ActionR\x07actions\"\xbf\x01\n\x06\ + Action\x12*\n\x04type\x18\x01\x20\x01(\x0e2\x16.key_config.ActionTypeR\ + \x04type\x126\n\nkey_action\x18\x02\x20\x01(\x0b2\x15.key_config.KeyActi\ + onH\0R\tkeyAction\x12B\n\x0ecommand_action\x18\x03\x20\x01(\x0b2\x19.key\ + _config.CommandActionH\0R\rcommandActionB\r\n\x0baction_data\"\xc0\x01\n\ + \tKeyAction\x12\x16\n\x03key\x18\x01\x20\x01(\x0e2\x04.KeyR\x03key\x12%\ + \n\x08modifier\x18\x02\x20\x01(\x0e2\x04.KeyH\0R\x08modifier\x88\x01\x01\ + \x12\x1d\n\x07unicode\x18\x03\x20\x01(\rH\x01R\x07unicode\x88\x01\x01\ + \x12)\n\x0eother_key_code\x18\x04\x20\x01(\rH\x02R\x0cotherKeyCode\x88\ + \x01\x01B\x0b\n\t_modifierB\n\n\x08_unicodeB\x11\n\x0f_other_key_code\")\ + \n\rCommandAction\x12\x18\n\x07command\x18\x02\x20\x01(\tR\x07command*!\ + \n\nActionType\x12\x13\n\x0fACTION_TYPE_KEY\x10\0b\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -641,10 +853,11 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { let mut deps = ::std::vec::Vec::with_capacity(2); deps.push(super::keys::file_descriptor().clone()); deps.push(super::inputs::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(3); + let mut messages = ::std::vec::Vec::with_capacity(4); messages.push(KeyConfig::generated_message_descriptor_data()); messages.push(Action::generated_message_descriptor_data()); messages.push(KeyAction::generated_message_descriptor_data()); + messages.push(CommandAction::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(1); enums.push(ActionType::generated_enum_descriptor_data()); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( From f212899cecc85f842238366c19ba4421c284e8db Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Wed, 26 Nov 2025 21:37:24 +1300 Subject: [PATCH 2/9] feat: support different command types --- backend-process/src/database/mappers.rs | 4 +- backend-process/src/database/models.rs | 4 +- backend-process/src/input_handler.rs | 6 +- backend-process/src/protobuf_conversion.rs | 40 +- messaging/protobufs/commands/key_config.proto | 14 +- messaging/src/protos/key_config.rs | 467 +++++++++++++++++- 6 files changed, 503 insertions(+), 32 deletions(-) diff --git a/backend-process/src/database/mappers.rs b/backend-process/src/database/mappers.rs index ba3a3f4..d254ca7 100644 --- a/backend-process/src/database/mappers.rs +++ b/backend-process/src/database/mappers.rs @@ -97,14 +97,14 @@ mod tests { vec![ Action::Key(Key::Add), Action::Key(Key::Backspace), - Action::Command(String::from("rm -rf ~/")), + Action::Command(String::from("rm -rf ~/"), vec![]), ], ); assert_eq!( InputMappingStorageFormat::try_from(rust).unwrap(), InputMappingStorageFormat { input_id: 8, - actions: String::from("[Key(Add),Key(Backspace),Command(\"rm -rf ~/\")]") + actions: String::from("[Key(Add),Key(Backspace),Command(\"rm -rf ~/\",[])]") } ) } diff --git a/backend-process/src/database/models.rs b/backend-process/src/database/models.rs index 231c855..757de8c 100644 --- a/backend-process/src/database/models.rs +++ b/backend-process/src/database/models.rs @@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Action { Key(Key), - Command(String), + /// (command, args) + Command(String, Vec), + Noop } /// The format we want to use inside the backend to handle actions diff --git a/backend-process/src/input_handler.rs b/backend-process/src/input_handler.rs index eadc238..5ffbfff 100644 --- a/backend-process/src/input_handler.rs +++ b/backend-process/src/input_handler.rs @@ -8,6 +8,7 @@ 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; pub trait KeyActionExecutor { @@ -104,9 +105,10 @@ impl<'a> LaunchpadInputHandler<'a> { Action::Key(key) => { self.key_action_executor.execute(&[*key]).ok(); } - Action::Command(command) => { - todo!("{}", command) + Action::Command(command, args) => { + Command::new(command).args(args).spawn().ok(); } + _ => {} } } } diff --git a/backend-process/src/protobuf_conversion.rs b/backend-process/src/protobuf_conversion.rs index 72b2bdb..7a2b74d 100644 --- a/backend-process/src/protobuf_conversion.rs +++ b/backend-process/src/protobuf_conversion.rs @@ -8,6 +8,7 @@ use firmware_api::inputs::knobs::KnobActions; use firmware_api::inputs::touchscreen::TouchscreenAction; use messaging::protos; use messaging::protos::key_config::action::Action_data; +use messaging::protos::key_config::command_action; use protobuf::Enum; use std::io::{Error, ErrorKind}; @@ -160,17 +161,33 @@ impl TryFrom for InputMapping { .map(|a| { match a.clone().action_data { Some(item) => match item { - Action_data::CommandAction(command) => Action::Command(command.command), + Action_data::CommandAction(command) => { + if let Some(command_type) = command.command { + return match command_type { + command_action::Command::FreeformCommand(command) => { + Action::Command(command.command, command.args) + } + command_action::Command::OpenAppCommand(command) => { + Action::Command( + String::from("Open command"), + vec![command.app_path], + ) + } + _ => Action::Noop, + }; + } + Action::Noop + } Action_data::KeyAction(key) => { if let Ok(key_val) = KeyWrapper::try_from(key) { return Action::Key(key_val.0); } // Stub out invalid values - Action::Command(String::new()) + Action::Noop } - _ => Action::Command(String::new()), + _ => Action::Noop, }, - _ => Action::Command(String::new()), + _ => Action::Noop, } }) .collect::>(); @@ -354,6 +371,7 @@ impl TryFrom for KeyWrapper { mod tests { use super::*; use firmware_api::inputs::InputActions::Knob; + use messaging::protos::key_config::FreeformCommand; #[test] fn parse_key_action_properly() { @@ -440,7 +458,11 @@ mod tests { ..protos::key_config::KeyAction::default() }), Action_data::CommandAction(protos::key_config::CommandAction { - command: String::from("NAH"), + command: Some(command_action::Command::FreeformCommand(FreeformCommand { + command: String::from("command"), + args: vec![String::from("arg1"), String::from("arg2")], + ..FreeformCommand::default() + })), ..protos::key_config::CommandAction::default() }), ], @@ -450,7 +472,13 @@ mod tests { InputMapping::try_from(proto).unwrap(), InputMapping::new( Unknown, - vec![Action::Key(Key::Add), Action::Command(String::from("NAH"))] + vec![ + Action::Key(Key::Add), + Action::Command( + String::from("command"), + vec![String::from("arg1"), String::from("arg2")] + ) + ] ) ) } diff --git a/messaging/protobufs/commands/key_config.proto b/messaging/protobufs/commands/key_config.proto index 4d2d468..edd07cb 100644 --- a/messaging/protobufs/commands/key_config.proto +++ b/messaging/protobufs/commands/key_config.proto @@ -29,6 +29,18 @@ message KeyAction { optional uint32 other_key_code = 4; } +message OpenAppCommand { + string app_path = 1; +} + +message FreeformCommand { + string command = 1; + repeated string args = 2; +} + message CommandAction { - string command = 2; + oneof command { + OpenAppCommand open_app_command = 1; + FreeformCommand freeform_command = 2; + } } diff --git a/messaging/src/protos/key_config.rs b/messaging/src/protos/key_config.rs index 77da297..3909c02 100644 --- a/messaging/src/protos/key_config.rs +++ b/messaging/src/protos/key_config.rs @@ -639,12 +639,273 @@ impl ::protobuf::reflect::ProtobufValue for KeyAction { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } -// @@protoc_insertion_point(message:key_config.CommandAction) +// @@protoc_insertion_point(message:key_config.OpenAppCommand) #[derive(PartialEq,Clone,Default,Debug)] -pub struct CommandAction { +pub struct OpenAppCommand { + // message fields + // @@protoc_insertion_point(field:key_config.OpenAppCommand.app_path) + pub app_path: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:key_config.OpenAppCommand.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a OpenAppCommand { + fn default() -> &'a OpenAppCommand { + ::default_instance() + } +} + +impl OpenAppCommand { + pub fn new() -> OpenAppCommand { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "app_path", + |m: &OpenAppCommand| { &m.app_path }, + |m: &mut OpenAppCommand| { &mut m.app_path }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "OpenAppCommand", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for OpenAppCommand { + const NAME: &'static str = "OpenAppCommand"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.app_path = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.app_path.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.app_path); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.app_path.is_empty() { + os.write_string(1, &self.app_path)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> OpenAppCommand { + OpenAppCommand::new() + } + + fn clear(&mut self) { + self.app_path.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static OpenAppCommand { + static instance: OpenAppCommand = OpenAppCommand { + app_path: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for OpenAppCommand { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("OpenAppCommand").unwrap()).clone() + } +} + +impl ::std::fmt::Display for OpenAppCommand { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for OpenAppCommand { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:key_config.FreeformCommand) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct FreeformCommand { // message fields - // @@protoc_insertion_point(field:key_config.CommandAction.command) + // @@protoc_insertion_point(field:key_config.FreeformCommand.command) pub command: ::std::string::String, + // @@protoc_insertion_point(field:key_config.FreeformCommand.args) + pub args: ::std::vec::Vec<::std::string::String>, + // special fields + // @@protoc_insertion_point(special_field:key_config.FreeformCommand.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a FreeformCommand { + fn default() -> &'a FreeformCommand { + ::default_instance() + } +} + +impl FreeformCommand { + pub fn new() -> FreeformCommand { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "command", + |m: &FreeformCommand| { &m.command }, + |m: &mut FreeformCommand| { &mut m.command }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "args", + |m: &FreeformCommand| { &m.args }, + |m: &mut FreeformCommand| { &mut m.args }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "FreeformCommand", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for FreeformCommand { + const NAME: &'static str = "FreeformCommand"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.command = is.read_string()?; + }, + 18 => { + self.args.push(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.command.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.command); + } + for value in &self.args { + my_size += ::protobuf::rt::string_size(2, &value); + }; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.command.is_empty() { + os.write_string(1, &self.command)?; + } + for v in &self.args { + os.write_string(2, &v)?; + }; + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> FreeformCommand { + FreeformCommand::new() + } + + fn clear(&mut self) { + self.command.clear(); + self.args.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static FreeformCommand { + static instance: FreeformCommand = FreeformCommand { + command: ::std::string::String::new(), + args: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for FreeformCommand { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("FreeformCommand").unwrap()).clone() + } +} + +impl ::std::fmt::Display for FreeformCommand { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for FreeformCommand { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:key_config.CommandAction) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct CommandAction { + // message oneof groups + pub command: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:key_config.CommandAction.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -661,14 +922,122 @@ impl CommandAction { ::std::default::Default::default() } + // .key_config.OpenAppCommand open_app_command = 1; + + pub fn open_app_command(&self) -> &OpenAppCommand { + match self.command { + ::std::option::Option::Some(command_action::Command::OpenAppCommand(ref v)) => v, + _ => ::default_instance(), + } + } + + pub fn clear_open_app_command(&mut self) { + self.command = ::std::option::Option::None; + } + + pub fn has_open_app_command(&self) -> bool { + match self.command { + ::std::option::Option::Some(command_action::Command::OpenAppCommand(..)) => true, + _ => false, + } + } + + // Param is passed by value, moved + pub fn set_open_app_command(&mut self, v: OpenAppCommand) { + self.command = ::std::option::Option::Some(command_action::Command::OpenAppCommand(v)) + } + + // Mutable pointer to the field. + pub fn mut_open_app_command(&mut self) -> &mut OpenAppCommand { + if let ::std::option::Option::Some(command_action::Command::OpenAppCommand(_)) = self.command { + } else { + self.command = ::std::option::Option::Some(command_action::Command::OpenAppCommand(OpenAppCommand::new())); + } + match self.command { + ::std::option::Option::Some(command_action::Command::OpenAppCommand(ref mut v)) => v, + _ => panic!(), + } + } + + // Take field + pub fn take_open_app_command(&mut self) -> OpenAppCommand { + if self.has_open_app_command() { + match self.command.take() { + ::std::option::Option::Some(command_action::Command::OpenAppCommand(v)) => v, + _ => panic!(), + } + } else { + OpenAppCommand::new() + } + } + + // .key_config.FreeformCommand freeform_command = 2; + + pub fn freeform_command(&self) -> &FreeformCommand { + match self.command { + ::std::option::Option::Some(command_action::Command::FreeformCommand(ref v)) => v, + _ => ::default_instance(), + } + } + + pub fn clear_freeform_command(&mut self) { + self.command = ::std::option::Option::None; + } + + pub fn has_freeform_command(&self) -> bool { + match self.command { + ::std::option::Option::Some(command_action::Command::FreeformCommand(..)) => true, + _ => false, + } + } + + // Param is passed by value, moved + pub fn set_freeform_command(&mut self, v: FreeformCommand) { + self.command = ::std::option::Option::Some(command_action::Command::FreeformCommand(v)) + } + + // Mutable pointer to the field. + pub fn mut_freeform_command(&mut self) -> &mut FreeformCommand { + if let ::std::option::Option::Some(command_action::Command::FreeformCommand(_)) = self.command { + } else { + self.command = ::std::option::Option::Some(command_action::Command::FreeformCommand(FreeformCommand::new())); + } + match self.command { + ::std::option::Option::Some(command_action::Command::FreeformCommand(ref mut v)) => v, + _ => panic!(), + } + } + + // Take field + pub fn take_freeform_command(&mut self) -> FreeformCommand { + if self.has_freeform_command() { + match self.command.take() { + ::std::option::Option::Some(command_action::Command::FreeformCommand(v)) => v, + _ => panic!(), + } + } else { + FreeformCommand::new() + } + } + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(1); - let mut oneofs = ::std::vec::Vec::with_capacity(0); - fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "command", - |m: &CommandAction| { &m.command }, - |m: &mut CommandAction| { &mut m.command }, + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(1); + fields.push(::protobuf::reflect::rt::v2::make_oneof_message_has_get_mut_set_accessor::<_, OpenAppCommand>( + "open_app_command", + CommandAction::has_open_app_command, + CommandAction::open_app_command, + CommandAction::mut_open_app_command, + CommandAction::set_open_app_command, + )); + fields.push(::protobuf::reflect::rt::v2::make_oneof_message_has_get_mut_set_accessor::<_, FreeformCommand>( + "freeform_command", + CommandAction::has_freeform_command, + CommandAction::freeform_command, + CommandAction::mut_freeform_command, + CommandAction::set_freeform_command, )); + oneofs.push(command_action::Command::generated_oneof_descriptor_data()); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "CommandAction", fields, @@ -687,8 +1056,11 @@ impl ::protobuf::Message for CommandAction { fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { while let Some(tag) = is.read_raw_tag_or_eof()? { match tag { + 10 => { + self.command = ::std::option::Option::Some(command_action::Command::OpenAppCommand(is.read_message()?)); + }, 18 => { - self.command = is.read_string()?; + self.command = ::std::option::Option::Some(command_action::Command::FreeformCommand(is.read_message()?)); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -702,8 +1074,17 @@ impl ::protobuf::Message for CommandAction { #[allow(unused_variables)] fn compute_size(&self) -> u64 { let mut my_size = 0; - if !self.command.is_empty() { - my_size += ::protobuf::rt::string_size(2, &self.command); + if let ::std::option::Option::Some(ref v) = self.command { + match v { + &command_action::Command::OpenAppCommand(ref v) => { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }, + &command_action::Command::FreeformCommand(ref v) => { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }, + }; } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -711,8 +1092,15 @@ impl ::protobuf::Message for CommandAction { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { - if !self.command.is_empty() { - os.write_string(2, &self.command)?; + if let ::std::option::Option::Some(ref v) = self.command { + match v { + &command_action::Command::OpenAppCommand(ref v) => { + ::protobuf::rt::write_message_field_with_cached_size(1, v, os)?; + }, + &command_action::Command::FreeformCommand(ref v) => { + ::protobuf::rt::write_message_field_with_cached_size(2, v, os)?; + }, + }; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -731,13 +1119,14 @@ impl ::protobuf::Message for CommandAction { } fn clear(&mut self) { - self.command.clear(); + self.command = ::std::option::Option::None; + self.command = ::std::option::Option::None; self.special_fields.clear(); } fn default_instance() -> &'static CommandAction { static instance: CommandAction = CommandAction { - command: ::std::string::String::new(), + command: ::std::option::Option::None, special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -761,6 +1150,36 @@ impl ::protobuf::reflect::ProtobufValue for CommandAction { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +/// Nested message and enums of message `CommandAction` +pub mod command_action { + + #[derive(Clone,PartialEq,Debug)] + #[non_exhaustive] + // @@protoc_insertion_point(oneof:key_config.CommandAction.command) + pub enum Command { + // @@protoc_insertion_point(oneof_field:key_config.CommandAction.open_app_command) + OpenAppCommand(super::OpenAppCommand), + // @@protoc_insertion_point(oneof_field:key_config.CommandAction.freeform_command) + FreeformCommand(super::FreeformCommand), + } + + impl ::protobuf::Oneof for Command { + } + + impl ::protobuf::OneofFull for Command { + fn descriptor() -> ::protobuf::reflect::OneofDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::OneofDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| ::descriptor().oneof_by_name("command").unwrap()).clone() + } + } + + impl Command { + pub(in super) fn generated_oneof_descriptor_data() -> ::protobuf::reflect::GeneratedOneofDescriptorData { + ::protobuf::reflect::GeneratedOneofDescriptorData::new::("command") + } + } +} + #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] // @@protoc_insertion_point(enum:key_config.ActionType) pub enum ActionType { @@ -831,9 +1250,15 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \n\x08modifier\x18\x02\x20\x01(\x0e2\x04.KeyH\0R\x08modifier\x88\x01\x01\ \x12\x1d\n\x07unicode\x18\x03\x20\x01(\rH\x01R\x07unicode\x88\x01\x01\ \x12)\n\x0eother_key_code\x18\x04\x20\x01(\rH\x02R\x0cotherKeyCode\x88\ - \x01\x01B\x0b\n\t_modifierB\n\n\x08_unicodeB\x11\n\x0f_other_key_code\")\ - \n\rCommandAction\x12\x18\n\x07command\x18\x02\x20\x01(\tR\x07command*!\ - \n\nActionType\x12\x13\n\x0fACTION_TYPE_KEY\x10\0b\x06proto3\ + \x01\x01B\x0b\n\t_modifierB\n\n\x08_unicodeB\x11\n\x0f_other_key_code\"+\ + \n\x0eOpenAppCommand\x12\x19\n\x08app_path\x18\x01\x20\x01(\tR\x07appPat\ + h\"?\n\x0fFreeformCommand\x12\x18\n\x07command\x18\x01\x20\x01(\tR\x07co\ + mmand\x12\x12\n\x04args\x18\x02\x20\x03(\tR\x04args\"\xac\x01\n\rCommand\ + Action\x12F\n\x10open_app_command\x18\x01\x20\x01(\x0b2\x1a.key_config.O\ + penAppCommandH\0R\x0eopenAppCommand\x12H\n\x10freeform_command\x18\x02\ + \x20\x01(\x0b2\x1b.key_config.FreeformCommandH\0R\x0ffreeformCommandB\t\ + \n\x07command*!\n\nActionType\x12\x13\n\x0fACTION_TYPE_KEY\x10\0b\x06pro\ + to3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -853,10 +1278,12 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { let mut deps = ::std::vec::Vec::with_capacity(2); deps.push(super::keys::file_descriptor().clone()); deps.push(super::inputs::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(4); + let mut messages = ::std::vec::Vec::with_capacity(6); messages.push(KeyConfig::generated_message_descriptor_data()); messages.push(Action::generated_message_descriptor_data()); messages.push(KeyAction::generated_message_descriptor_data()); + messages.push(OpenAppCommand::generated_message_descriptor_data()); + messages.push(FreeformCommand::generated_message_descriptor_data()); messages.push(CommandAction::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(1); enums.push(ActionType::generated_enum_descriptor_data()); From b0cb6246770df5018aa30a979287a206eed8511b Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:43:53 +1300 Subject: [PATCH 3/9] feat: support modifier keys in key config (untested) --- backend-process/src/database/mappers.rs | 8 +- backend-process/src/database/models.rs | 3 +- backend-process/src/database/operations.rs | 9 +- backend-process/src/input_handler.rs | 21 +- backend-process/src/protobuf_conversion.rs | 189 ++++++++++-------- config-frontend/src/mappers.rs | 37 +++- messaging/protobufs/commands/key_config.proto | 2 +- messaging/src/protos/key_config.rs | 48 +++-- 8 files changed, 194 insertions(+), 123 deletions(-) diff --git a/backend-process/src/database/mappers.rs b/backend-process/src/database/mappers.rs index d254ca7..478da89 100644 --- a/backend-process/src/database/mappers.rs +++ b/backend-process/src/database/mappers.rs @@ -86,8 +86,8 @@ impl TryFrom<&Row<'_>> for ImageMapping { #[cfg(test)] mod tests { - use enigo::Key; use super::*; + use enigo::Key; use firmware_api::display_zones::DisplayZones; #[test] @@ -95,8 +95,8 @@ mod tests { let rust = InputMapping::new( InputActions::from(8), vec![ - Action::Key(Key::Add), - Action::Key(Key::Backspace), + Action::Key(Key::Add, vec![]), + Action::Key(Key::Backspace, vec![]), Action::Command(String::from("rm -rf ~/"), vec![]), ], ); @@ -104,7 +104,7 @@ mod tests { InputMappingStorageFormat::try_from(rust).unwrap(), InputMappingStorageFormat { input_id: 8, - actions: String::from("[Key(Add),Key(Backspace),Command(\"rm -rf ~/\",[])]") + actions: String::from("[Key(Add,[]),Key(Backspace,[]),Command(\"rm -rf ~/\",[])]") } ) } diff --git a/backend-process/src/database/models.rs b/backend-process/src/database/models.rs index 757de8c..7142a18 100644 --- a/backend-process/src/database/models.rs +++ b/backend-process/src/database/models.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Action { - Key(Key), + /// (key, modifiers) + Key(Key, Vec), /// (command, args) Command(String, Vec), Noop diff --git a/backend-process/src/database/operations.rs b/backend-process/src/database/operations.rs index 492446b..7350888 100644 --- a/backend-process/src/database/operations.rs +++ b/backend-process/src/database/operations.rs @@ -234,11 +234,11 @@ mod tests { let to_add = &[ InputMapping::new( InputActions::Button(Button4Pressed), - vec![Action::Key(Key::Option)], + vec![Action::Key(Key::Option, vec![])], ), InputMapping::new( InputActions::Button(Button1Pressed), - vec![Action::Key(Key::Backspace)], + vec![Action::Key(Key::Backspace, vec![])], ), ]; @@ -259,7 +259,10 @@ mod tests { operations .set_mapping_for_input(InputMapping::new( InputActions::Button(Button4Pressed), - vec![Action::Key(Key::Add), Action::Key(Key::Backspace)], + vec![ + Action::Key(Key::Add, vec![]), + Action::Key(Key::Backspace, vec![]), + ], )) .unwrap(); diff --git a/backend-process/src/input_handler.rs b/backend-process/src/input_handler.rs index 5ffbfff..fa5e650 100644 --- a/backend-process/src/input_handler.rs +++ b/backend-process/src/input_handler.rs @@ -12,7 +12,7 @@ use std::process::Command; use std::sync::Mutex; pub trait KeyActionExecutor { - fn execute(&self, actions: &[Key]) -> Result<(), String>; + fn execute(&self, actions: &[(Key, Vec)]) -> Result<(), String>; } pub struct EnigoKeyActionHandler { @@ -44,7 +44,7 @@ impl Default for InputMapping { fn default() -> Self { Self(HashMap::from([( InputActions::Button(Button1Pressed), - vec![Action::Key(Key::VolumeDown)], + vec![Action::Key(Key::VolumeDown, vec![])], )])) } } @@ -69,10 +69,17 @@ impl From for InputMapping { } } impl KeyActionExecutor for EnigoKeyActionHandler { - fn execute(&self, actions: &[Key]) -> Result<(), String> { + fn execute(&self, actions: &[(Key, Vec)]) -> 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(()) } @@ -102,8 +109,10 @@ impl<'a> LaunchpadInputHandler<'a> { if let Some(actions) = self.input_mapping.0.get(&input_action) { for action in actions { match action { - Action::Key(key) => { - self.key_action_executor.execute(&[*key]).ok(); + Action::Key(key, modifiers) => { + self.key_action_executor + .execute(&[(*key, modifiers.clone())]) + .ok(); } Action::Command(command, args) => { Command::new(command).args(args).spawn().ok(); diff --git a/backend-process/src/protobuf_conversion.rs b/backend-process/src/protobuf_conversion.rs index 7a2b74d..b866b45 100644 --- a/backend-process/src/protobuf_conversion.rs +++ b/backend-process/src/protobuf_conversion.rs @@ -9,7 +9,7 @@ use firmware_api::inputs::touchscreen::TouchscreenAction; use messaging::protos; use messaging::protos::key_config::action::Action_data; use messaging::protos::key_config::command_action; -use protobuf::Enum; +use std::char; use std::io::{Error, ErrorKind}; /// Util struct for mapping the protobuf key into an `Enigo` key @@ -179,8 +179,21 @@ impl TryFrom for InputMapping { Action::Noop } Action_data::KeyAction(key) => { - if let Ok(key_val) = KeyWrapper::try_from(key) { - return Action::Key(key_val.0); + if let Ok(key_val) = KeyWrapper::try_from(key.clone()) { + return Action::Key( + key_val.0, + key.modifier + .iter() + .filter_map(|modifier_key| { + match modifier_key.enum_value() { + Ok(modifier_key_value) => { + Some(KeyWrapper::from(modifier_key_value).0) + } + Err(_) => None, + } + }) + .collect::>(), + ); } // Stub out invalid values Action::Noop @@ -275,89 +288,101 @@ impl TryFrom for DisplayZoneWrapper { } } +impl From for KeyWrapper { + fn from(value: protos::keys::Key) -> Self { + match value { + protos::keys::Key::KEY_ADD => KeyWrapper(Key::Add), + protos::keys::Key::KEY_ALT => KeyWrapper(Key::Alt), + protos::keys::Key::KEY_BACKSPACE => KeyWrapper(Key::Backspace), + protos::keys::Key::KEY_CAPS_LOCK => KeyWrapper(Key::CapsLock), + protos::keys::Key::KEY_CONTROL => KeyWrapper(Key::Control), + protos::keys::Key::KEY_DECIMAL => KeyWrapper(Key::Decimal), + protos::keys::Key::KEY_DELETE => KeyWrapper(Key::Delete), + protos::keys::Key::KEY_DIVIDE => KeyWrapper(Key::Divide), + protos::keys::Key::KEY_DOWN_ARROW => KeyWrapper(Key::DownArrow), + protos::keys::Key::KEY_END => KeyWrapper(Key::End), + protos::keys::Key::KEY_ESCAPE => KeyWrapper(Key::Escape), + protos::keys::Key::KEY_F1 => KeyWrapper(Key::F1), + protos::keys::Key::KEY_F2 => KeyWrapper(Key::F2), + protos::keys::Key::KEY_F3 => KeyWrapper(Key::F3), + protos::keys::Key::KEY_F4 => KeyWrapper(Key::F4), + protos::keys::Key::KEY_F5 => KeyWrapper(Key::F5), + protos::keys::Key::KEY_F6 => KeyWrapper(Key::F6), + protos::keys::Key::KEY_F7 => KeyWrapper(Key::F7), + protos::keys::Key::KEY_F8 => KeyWrapper(Key::F8), + protos::keys::Key::KEY_F9 => KeyWrapper(Key::F9), + protos::keys::Key::KEY_F10 => KeyWrapper(Key::F10), + protos::keys::Key::KEY_F11 => KeyWrapper(Key::F11), + protos::keys::Key::KEY_F12 => KeyWrapper(Key::F12), + protos::keys::Key::KEY_F13 => KeyWrapper(Key::F13), + protos::keys::Key::KEY_F14 => KeyWrapper(Key::F14), + protos::keys::Key::KEY_F15 => KeyWrapper(Key::F15), + protos::keys::Key::KEY_F16 => KeyWrapper(Key::F16), + protos::keys::Key::KEY_F17 => KeyWrapper(Key::F17), + protos::keys::Key::KEY_F18 => KeyWrapper(Key::F18), + protos::keys::Key::KEY_F19 => KeyWrapper(Key::F19), + protos::keys::Key::KEY_F20 => KeyWrapper(Key::F20), + protos::keys::Key::KEY_HELP => KeyWrapper(Key::Help), + protos::keys::Key::KEY_HOME => KeyWrapper(Key::Home), + protos::keys::Key::KEY_L_CONTROL => KeyWrapper(Key::LControl), + protos::keys::Key::KEY_LEFT_ARROW => KeyWrapper(Key::LeftArrow), + protos::keys::Key::KEY_L_SHIFT => KeyWrapper(Key::LShift), + protos::keys::Key::KEY_MEDIA_NEXT_TRACK => KeyWrapper(Key::MediaNextTrack), + protos::keys::Key::KEY_MEDIA_PLAY_PAUSE => KeyWrapper(Key::MediaPlayPause), + protos::keys::Key::KEY_MEDIA_PREV_TRACK => KeyWrapper(Key::MediaPrevTrack), + protos::keys::Key::KEY_META => KeyWrapper(Key::Meta), + protos::keys::Key::KEY_MULTIPLY => KeyWrapper(Key::Multiply), + protos::keys::Key::KEY_NUMPAD0 => KeyWrapper(Key::Numpad0), + protos::keys::Key::KEY_NUMPAD1 => KeyWrapper(Key::Numpad1), + protos::keys::Key::KEY_NUMPAD2 => KeyWrapper(Key::Numpad2), + protos::keys::Key::KEY_NUMPAD3 => KeyWrapper(Key::Numpad3), + protos::keys::Key::KEY_NUMPAD4 => KeyWrapper(Key::Numpad4), + protos::keys::Key::KEY_NUMPAD5 => KeyWrapper(Key::Numpad5), + protos::keys::Key::KEY_NUMPAD6 => KeyWrapper(Key::Numpad6), + protos::keys::Key::KEY_NUMPAD7 => KeyWrapper(Key::Numpad7), + protos::keys::Key::KEY_NUMPAD8 => KeyWrapper(Key::Numpad8), + protos::keys::Key::KEY_NUMPAD9 => KeyWrapper(Key::Numpad9), + protos::keys::Key::KEY_OPTION => KeyWrapper(Key::Option), + protos::keys::Key::KEY_PAGE_DOWN => KeyWrapper(Key::PageDown), + protos::keys::Key::KEY_PAGE_UP => KeyWrapper(Key::PageUp), + protos::keys::Key::KEY_R_CONTROL => KeyWrapper(Key::RControl), + protos::keys::Key::KEY_RETURN => KeyWrapper(Key::Return), + protos::keys::Key::KEY_RIGHT_ARROW => KeyWrapper(Key::RightArrow), + protos::keys::Key::KEY_R_SHIFT => KeyWrapper(Key::RShift), + protos::keys::Key::KEY_SHIFT => KeyWrapper(Key::Shift), + protos::keys::Key::KEY_SPACE => KeyWrapper(Key::Space), + protos::keys::Key::KEY_SUBTRACT => KeyWrapper(Key::Subtract), + protos::keys::Key::KEY_TAB => KeyWrapper(Key::Tab), + protos::keys::Key::KEY_UP_ARROW => KeyWrapper(Key::UpArrow), + protos::keys::Key::KEY_VOLUME_DOWN => KeyWrapper(Key::VolumeDown), + protos::keys::Key::KEY_VOLUME_MUTE => KeyWrapper(Key::VolumeMute), + protos::keys::Key::KEY_VOLUME_UP => KeyWrapper(Key::VolumeUp), + protos::keys::Key::KEY_UNICODE => KeyWrapper(Key::Unicode(char::default())), + protos::keys::Key::KEY_OTHER => KeyWrapper(Key::Other(u32::default())), + _ => KeyWrapper(Key::Other(u32::default())), + } + } +} + impl TryFrom for KeyWrapper { type Error = String; fn try_from(value: protos::key_config::KeyAction) -> Result { match value.key.enum_value() { - Ok(key) => match key { - protos::keys::Key::KEY_ADD => Ok(KeyWrapper(Key::Add)), - protos::keys::Key::KEY_ALT => Ok(KeyWrapper(Key::Alt)), - protos::keys::Key::KEY_BACKSPACE => Ok(KeyWrapper(Key::Backspace)), - protos::keys::Key::KEY_CAPS_LOCK => Ok(KeyWrapper(Key::CapsLock)), - protos::keys::Key::KEY_CONTROL => Ok(KeyWrapper(Key::Control)), - protos::keys::Key::KEY_DECIMAL => Ok(KeyWrapper(Key::Decimal)), - protos::keys::Key::KEY_DELETE => Ok(KeyWrapper(Key::Delete)), - protos::keys::Key::KEY_DIVIDE => Ok(KeyWrapper(Key::Divide)), - protos::keys::Key::KEY_DOWN_ARROW => Ok(KeyWrapper(Key::DownArrow)), - protos::keys::Key::KEY_END => Ok(KeyWrapper(Key::End)), - protos::keys::Key::KEY_ESCAPE => Ok(KeyWrapper(Key::Escape)), - protos::keys::Key::KEY_F1 => Ok(KeyWrapper(Key::F1)), - protos::keys::Key::KEY_F2 => Ok(KeyWrapper(Key::F2)), - protos::keys::Key::KEY_F3 => Ok(KeyWrapper(Key::F3)), - protos::keys::Key::KEY_F4 => Ok(KeyWrapper(Key::F4)), - protos::keys::Key::KEY_F5 => Ok(KeyWrapper(Key::F5)), - protos::keys::Key::KEY_F6 => Ok(KeyWrapper(Key::F6)), - protos::keys::Key::KEY_F7 => Ok(KeyWrapper(Key::F7)), - protos::keys::Key::KEY_F8 => Ok(KeyWrapper(Key::F8)), - protos::keys::Key::KEY_F9 => Ok(KeyWrapper(Key::F9)), - protos::keys::Key::KEY_F10 => Ok(KeyWrapper(Key::F10)), - protos::keys::Key::KEY_F11 => Ok(KeyWrapper(Key::F11)), - protos::keys::Key::KEY_F12 => Ok(KeyWrapper(Key::F12)), - protos::keys::Key::KEY_F13 => Ok(KeyWrapper(Key::F13)), - protos::keys::Key::KEY_F14 => Ok(KeyWrapper(Key::F14)), - protos::keys::Key::KEY_F15 => Ok(KeyWrapper(Key::F15)), - protos::keys::Key::KEY_F16 => Ok(KeyWrapper(Key::F16)), - protos::keys::Key::KEY_F17 => Ok(KeyWrapper(Key::F17)), - protos::keys::Key::KEY_F18 => Ok(KeyWrapper(Key::F18)), - protos::keys::Key::KEY_F19 => Ok(KeyWrapper(Key::F19)), - protos::keys::Key::KEY_F20 => Ok(KeyWrapper(Key::F20)), - protos::keys::Key::KEY_HELP => Ok(KeyWrapper(Key::Help)), - protos::keys::Key::KEY_HOME => Ok(KeyWrapper(Key::Home)), - protos::keys::Key::KEY_L_CONTROL => Ok(KeyWrapper(Key::LControl)), - protos::keys::Key::KEY_LEFT_ARROW => Ok(KeyWrapper(Key::LeftArrow)), - protos::keys::Key::KEY_L_SHIFT => Ok(KeyWrapper(Key::LShift)), - protos::keys::Key::KEY_MEDIA_NEXT_TRACK => Ok(KeyWrapper(Key::MediaNextTrack)), - protos::keys::Key::KEY_MEDIA_PLAY_PAUSE => Ok(KeyWrapper(Key::MediaPlayPause)), - protos::keys::Key::KEY_MEDIA_PREV_TRACK => Ok(KeyWrapper(Key::MediaPrevTrack)), - protos::keys::Key::KEY_META => Ok(KeyWrapper(Key::Meta)), - protos::keys::Key::KEY_MULTIPLY => Ok(KeyWrapper(Key::Multiply)), - protos::keys::Key::KEY_NUMPAD0 => Ok(KeyWrapper(Key::Numpad0)), - protos::keys::Key::KEY_NUMPAD1 => Ok(KeyWrapper(Key::Numpad1)), - protos::keys::Key::KEY_NUMPAD2 => Ok(KeyWrapper(Key::Numpad2)), - protos::keys::Key::KEY_NUMPAD3 => Ok(KeyWrapper(Key::Numpad3)), - protos::keys::Key::KEY_NUMPAD4 => Ok(KeyWrapper(Key::Numpad4)), - protos::keys::Key::KEY_NUMPAD5 => Ok(KeyWrapper(Key::Numpad5)), - protos::keys::Key::KEY_NUMPAD6 => Ok(KeyWrapper(Key::Numpad6)), - protos::keys::Key::KEY_NUMPAD7 => Ok(KeyWrapper(Key::Numpad7)), - protos::keys::Key::KEY_NUMPAD8 => Ok(KeyWrapper(Key::Numpad8)), - protos::keys::Key::KEY_NUMPAD9 => Ok(KeyWrapper(Key::Numpad9)), - protos::keys::Key::KEY_OPTION => Ok(KeyWrapper(Key::Option)), - protos::keys::Key::KEY_PAGE_DOWN => Ok(KeyWrapper(Key::PageDown)), - protos::keys::Key::KEY_PAGE_UP => Ok(KeyWrapper(Key::PageUp)), - protos::keys::Key::KEY_R_CONTROL => Ok(KeyWrapper(Key::RControl)), - protos::keys::Key::KEY_RETURN => Ok(KeyWrapper(Key::Return)), - protos::keys::Key::KEY_RIGHT_ARROW => Ok(KeyWrapper(Key::RightArrow)), - protos::keys::Key::KEY_R_SHIFT => Ok(KeyWrapper(Key::RShift)), - protos::keys::Key::KEY_SHIFT => Ok(KeyWrapper(Key::Shift)), - protos::keys::Key::KEY_SPACE => Ok(KeyWrapper(Key::Space)), - protos::keys::Key::KEY_SUBTRACT => Ok(KeyWrapper(Key::Subtract)), - protos::keys::Key::KEY_TAB => Ok(KeyWrapper(Key::Tab)), - protos::keys::Key::KEY_UP_ARROW => Ok(KeyWrapper(Key::UpArrow)), - protos::keys::Key::KEY_VOLUME_DOWN => Ok(KeyWrapper(Key::VolumeDown)), - protos::keys::Key::KEY_VOLUME_MUTE => Ok(KeyWrapper(Key::VolumeMute)), - protos::keys::Key::KEY_VOLUME_UP => Ok(KeyWrapper(Key::VolumeUp)), - protos::keys::Key::KEY_UNICODE => match value.unicode { - Some(unicode) => match char::try_from(unicode) { - Ok(c) => Ok(KeyWrapper(Key::Unicode(c))), - Err(e) => Err(e.to_string()), + Ok(key) => match KeyWrapper::from(key) { + key_wrapper => match key_wrapper { + KeyWrapper(Key::Unicode(_)) => match value.unicode { + Some(unicode) => match char::try_from(unicode) { + Ok(c) => Ok(KeyWrapper(Key::Unicode(c))), + Err(e) => Err(e.to_string()), + }, + None => Err("Unicode value not found".to_string()), }, - None => Err("Unicode value not found".to_string()), - }, - protos::keys::Key::KEY_OTHER => match value.other_key_code { - Some(key_code) => Ok(KeyWrapper(Key::Other(key_code))), - None => Err("Other key code not found".to_string()), + KeyWrapper(Key::Other(_)) => match value.other_key_code { + Some(key_code) => Ok(KeyWrapper(Key::Other(key_code))), + None => Err("Other key code not found".to_string()), + }, + _ => Ok(key_wrapper), }, - _ => Err(format!("Unsupported key format {}", key.value())), }, Err(e) => Err(format!( "Error matching key, an unsupported format may have been provided: {:?}", @@ -443,7 +468,7 @@ mod tests { InputMapping::try_from(proto).unwrap(), InputMapping::new( Knob(KnobActions::Knob1Clockwise), - vec![Action::Key(Key::Add)] + vec![Action::Key(Key::Add, vec![])] ) ) } @@ -473,7 +498,7 @@ mod tests { InputMapping::new( Unknown, vec![ - Action::Key(Key::Add), + Action::Key(Key::Add, vec![]), Action::Command( String::from("command"), vec![String::from("arg1"), String::from("arg2")] diff --git a/config-frontend/src/mappers.rs b/config-frontend/src/mappers.rs index 2387909..d73d6f2 100644 --- a/config-frontend/src/mappers.rs +++ b/config-frontend/src/mappers.rs @@ -11,6 +11,8 @@ use protobuf::EnumOrUnknown; /// Util type to assist with conversion to the protobuf Key format pub struct ProtoKeyWrapper(Key); +pub struct ProtoModifierWrapper(Vec); + impl ProtoKeyWrapper { pub fn key(&self) -> Key { self.0 @@ -26,7 +28,7 @@ impl ProtoKeyActionWrapper { } impl From<(IcedKey, Modifiers)> for ProtoKeyActionWrapper { - fn from((key, _): (IcedKey, Modifiers)) -> Self { + fn from((key, modifiers): (IcedKey, Modifiers)) -> Self { ProtoKeyActionWrapper(KeyAction { key: EnumOrUnknown::from(ProtoKeyWrapper::from(key.clone()).key()), unicode: match key { @@ -35,11 +37,44 @@ impl From<(IcedKey, Modifiers)> for ProtoKeyActionWrapper { } _ => None, }, + modifier: ProtoModifierWrapper::from(modifiers) + .0 + .iter() + .map(|modifier_key| EnumOrUnknown::new(modifier_key.clone())) + .collect(), ..KeyAction::default() }) } } +impl From for ProtoModifierWrapper { + fn from(modifiers: Modifiers) -> Self { + let mut list = vec![]; + + if modifiers.command() { + list.push(Key::KEY_COMMAND); + } + + if modifiers.shift() { + list.push(Key::KEY_SHIFT); + } + + if modifiers.alt() { + list.push(Key::KEY_ALT); + } + + if modifiers.macos_command() { + list.push(Key::KEY_COMMAND); + } + + if modifiers.jump() { + list.push(Key::KEY_CONTROL); + } + + ProtoModifierWrapper(list) + } +} + impl From for ProtoKeyWrapper { fn from(value: IcedKey) -> Self { ProtoKeyWrapper(match value { diff --git a/messaging/protobufs/commands/key_config.proto b/messaging/protobufs/commands/key_config.proto index edd07cb..36944f7 100644 --- a/messaging/protobufs/commands/key_config.proto +++ b/messaging/protobufs/commands/key_config.proto @@ -24,7 +24,7 @@ enum ActionType { message KeyAction { Key key = 1; // e.g., "v", "f1", "enter" - optional Key modifier = 2; + repeated Key modifier = 2; optional uint32 unicode = 3; // only required if we are using a key input optional uint32 other_key_code = 4; } diff --git a/messaging/src/protos/key_config.rs b/messaging/src/protos/key_config.rs index 3909c02..1b180d1 100644 --- a/messaging/src/protos/key_config.rs +++ b/messaging/src/protos/key_config.rs @@ -470,7 +470,7 @@ pub struct KeyAction { // @@protoc_insertion_point(field:key_config.KeyAction.key) pub key: ::protobuf::EnumOrUnknown, // @@protoc_insertion_point(field:key_config.KeyAction.modifier) - pub modifier: ::std::option::Option<::protobuf::EnumOrUnknown>, + pub modifier: ::std::vec::Vec<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:key_config.KeyAction.unicode) pub unicode: ::std::option::Option, // @@protoc_insertion_point(field:key_config.KeyAction.other_key_code) @@ -499,7 +499,7 @@ impl KeyAction { |m: &KeyAction| { &m.key }, |m: &mut KeyAction| { &mut m.key }, )); - fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( "modifier", |m: &KeyAction| { &m.modifier }, |m: &mut KeyAction| { &mut m.modifier }, @@ -536,7 +536,10 @@ impl ::protobuf::Message for KeyAction { self.key = is.read_enum_or_unknown()?; }, 16 => { - self.modifier = ::std::option::Option::Some(is.read_enum_or_unknown()?); + self.modifier.push(is.read_enum_or_unknown()?); + }, + 18 => { + ::protobuf::rt::read_repeated_packed_enum_or_unknown_into(is, &mut self.modifier)? }, 24 => { self.unicode = ::std::option::Option::Some(is.read_uint32()?); @@ -559,9 +562,7 @@ impl ::protobuf::Message for KeyAction { if self.key != ::protobuf::EnumOrUnknown::new(super::keys::Key::KEY_UNSPECIFIED) { my_size += ::protobuf::rt::int32_size(1, self.key.value()); } - if let Some(v) = self.modifier { - my_size += ::protobuf::rt::int32_size(2, v.value()); - } + my_size += ::protobuf::rt::vec_packed_enum_or_unknown_size(2, &self.modifier); if let Some(v) = self.unicode { my_size += ::protobuf::rt::uint32_size(3, v); } @@ -577,9 +578,7 @@ impl ::protobuf::Message for KeyAction { if self.key != ::protobuf::EnumOrUnknown::new(super::keys::Key::KEY_UNSPECIFIED) { os.write_enum(1, ::protobuf::EnumOrUnknown::value(&self.key))?; } - if let Some(v) = self.modifier { - os.write_enum(2, ::protobuf::EnumOrUnknown::value(&v))?; - } + os.write_repeated_packed_enum_or_unknown(2, &self.modifier)?; if let Some(v) = self.unicode { os.write_uint32(3, v)?; } @@ -604,7 +603,7 @@ impl ::protobuf::Message for KeyAction { fn clear(&mut self) { self.key = ::protobuf::EnumOrUnknown::new(super::keys::Key::KEY_UNSPECIFIED); - self.modifier = ::std::option::Option::None; + self.modifier.clear(); self.unicode = ::std::option::Option::None; self.other_key_code = ::std::option::Option::None; self.special_fields.clear(); @@ -613,7 +612,7 @@ impl ::protobuf::Message for KeyAction { fn default_instance() -> &'static KeyAction { static instance: KeyAction = KeyAction { key: ::protobuf::EnumOrUnknown::from_i32(0), - modifier: ::std::option::Option::None, + modifier: ::std::vec::Vec::new(), unicode: ::std::option::Option::None, other_key_code: ::std::option::Option::None, special_fields: ::protobuf::SpecialFields::new(), @@ -1245,20 +1244,19 @@ static file_descriptor_proto_data: &'static [u8] = b"\ Action\x12*\n\x04type\x18\x01\x20\x01(\x0e2\x16.key_config.ActionTypeR\ \x04type\x126\n\nkey_action\x18\x02\x20\x01(\x0b2\x15.key_config.KeyActi\ onH\0R\tkeyAction\x12B\n\x0ecommand_action\x18\x03\x20\x01(\x0b2\x19.key\ - _config.CommandActionH\0R\rcommandActionB\r\n\x0baction_data\"\xc0\x01\n\ - \tKeyAction\x12\x16\n\x03key\x18\x01\x20\x01(\x0e2\x04.KeyR\x03key\x12%\ - \n\x08modifier\x18\x02\x20\x01(\x0e2\x04.KeyH\0R\x08modifier\x88\x01\x01\ - \x12\x1d\n\x07unicode\x18\x03\x20\x01(\rH\x01R\x07unicode\x88\x01\x01\ - \x12)\n\x0eother_key_code\x18\x04\x20\x01(\rH\x02R\x0cotherKeyCode\x88\ - \x01\x01B\x0b\n\t_modifierB\n\n\x08_unicodeB\x11\n\x0f_other_key_code\"+\ - \n\x0eOpenAppCommand\x12\x19\n\x08app_path\x18\x01\x20\x01(\tR\x07appPat\ - h\"?\n\x0fFreeformCommand\x12\x18\n\x07command\x18\x01\x20\x01(\tR\x07co\ - mmand\x12\x12\n\x04args\x18\x02\x20\x03(\tR\x04args\"\xac\x01\n\rCommand\ - Action\x12F\n\x10open_app_command\x18\x01\x20\x01(\x0b2\x1a.key_config.O\ - penAppCommandH\0R\x0eopenAppCommand\x12H\n\x10freeform_command\x18\x02\ - \x20\x01(\x0b2\x1b.key_config.FreeformCommandH\0R\x0ffreeformCommandB\t\ - \n\x07command*!\n\nActionType\x12\x13\n\x0fACTION_TYPE_KEY\x10\0b\x06pro\ - to3\ + _config.CommandActionH\0R\rcommandActionB\r\n\x0baction_data\"\xae\x01\n\ + \tKeyAction\x12\x16\n\x03key\x18\x01\x20\x01(\x0e2\x04.KeyR\x03key\x12\ + \x20\n\x08modifier\x18\x02\x20\x03(\x0e2\x04.KeyR\x08modifier\x12\x1d\n\ + \x07unicode\x18\x03\x20\x01(\rH\0R\x07unicode\x88\x01\x01\x12)\n\x0eothe\ + r_key_code\x18\x04\x20\x01(\rH\x01R\x0cotherKeyCode\x88\x01\x01B\n\n\x08\ + _unicodeB\x11\n\x0f_other_key_code\"+\n\x0eOpenAppCommand\x12\x19\n\x08a\ + pp_path\x18\x01\x20\x01(\tR\x07appPath\"?\n\x0fFreeformCommand\x12\x18\n\ + \x07command\x18\x01\x20\x01(\tR\x07command\x12\x12\n\x04args\x18\x02\x20\ + \x03(\tR\x04args\"\xac\x01\n\rCommandAction\x12F\n\x10open_app_command\ + \x18\x01\x20\x01(\x0b2\x1a.key_config.OpenAppCommandH\0R\x0eopenAppComma\ + nd\x12H\n\x10freeform_command\x18\x02\x20\x01(\x0b2\x1b.key_config.Freef\ + ormCommandH\0R\x0ffreeformCommandB\t\n\x07command*!\n\nActionType\x12\ + \x13\n\x0fACTION_TYPE_KEY\x10\0b\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file From a144bbdd7228305536227b71c2014f43808c8551 Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 13:12:08 +1300 Subject: [PATCH 4/9] feat: support modifier keys in client wrapper examples --- .../client_sending_key_config_to_server.rs | 15 +++++++++++++-- messaging/src/proto_builders.rs | 10 +++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/messaging/examples/client_sending_key_config_to_server.rs b/messaging/examples/client_sending_key_config_to_server.rs index d1320d6..8f63d25 100644 --- a/messaging/examples/client_sending_key_config_to_server.rs +++ b/messaging/examples/client_sending_key_config_to_server.rs @@ -48,7 +48,7 @@ fn main() { .send_key_config( InputId::KNOB_1_CLOCKWISE, KeyConfigActionBuilder::new() - .add_key_action(Key::KEY_VOLUME_UP) + .add_key_action(Key::KEY_VOLUME_UP, vec![Key::KEY_SHIFT, Key::KEY_ALT]) .actions() .clone(), ) @@ -58,7 +58,10 @@ fn main() { .send_key_config( InputId::KNOB_1_COUNTER_CLOCKWISE, KeyConfigActionBuilder::new() - .add_key_action(Key::KEY_VOLUME_DOWN) + .add_key_action( + Key::KEY_VOLUME_DOWN, + vec![Key::KEY_SHIFT, Key::KEY_ALT], + ) .actions() .clone(), ) @@ -99,4 +102,12 @@ fn main() { panic!("Did not enter a valid choice") } } + + println!("Press any key to quit"); + loop { + io::stdin().read_line(&mut buffer).unwrap(); + if !buffer.trim().is_empty() { + break; + } + } } diff --git a/messaging/src/proto_builders.rs b/messaging/src/proto_builders.rs index 8a04238..bd38d49 100644 --- a/messaging/src/proto_builders.rs +++ b/messaging/src/proto_builders.rs @@ -28,11 +28,19 @@ impl KeyConfigActionBuilder { } } /// Appends the given `key` to the current protobuf - pub fn add_key_action(mut self, key: protos::keys::Key) -> Self { + pub fn add_key_action( + mut self, + key: protos::keys::Key, + modifiers: Vec, + ) -> Self { let action = protos::key_config::Action { action_data: Some(protos::key_config::action::Action_data::KeyAction( protos::key_config::KeyAction { key: protobuf::EnumOrUnknown::from(key), + modifier: modifiers + .iter() + .map(|m| protobuf::EnumOrUnknown::from(*m)) + .collect(), ..protos::key_config::KeyAction::default() }, )), From cc64bb9125ba81b261067abd6dbda5cc3f4d9050 Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 20:05:52 +1300 Subject: [PATCH 5/9] feat: support command in client wrapper examples --- .../client_sending_key_config_to_server.rs | 17 +++++++++++++++++ messaging/src/proto_builders.rs | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/messaging/examples/client_sending_key_config_to_server.rs b/messaging/examples/client_sending_key_config_to_server.rs index 8f63d25..cb0af12 100644 --- a/messaging/examples/client_sending_key_config_to_server.rs +++ b/messaging/examples/client_sending_key_config_to_server.rs @@ -12,6 +12,8 @@ use messaging::client_wrapper::{ClientCommands, ClientWrapper}; use messaging::proto_builders::KeyConfigActionBuilder; use messaging::protos::display_zones::DisplayZone; use messaging::protos::inputs::InputId; +use messaging::protos::key_config::command_action::Command; +use messaging::protos::key_config::FreeformCommand; use messaging::protos::keys::Key; fn main() { @@ -66,6 +68,21 @@ fn main() { .clone(), ) .unwrap(); + + handler + .send_key_config( + InputId::BUTTON_1_PRESSED, + KeyConfigActionBuilder::new() + .add_command_action(Command::FreeformCommand( + FreeformCommand { + command: String::from("zed"), + ..FreeformCommand::default() + } + )) + .actions() + .clone(), + ) + .unwrap(); } 2 => handler diff --git a/messaging/src/proto_builders.rs b/messaging/src/proto_builders.rs index bd38d49..13686a6 100644 --- a/messaging/src/proto_builders.rs +++ b/messaging/src/proto_builders.rs @@ -50,6 +50,24 @@ impl KeyConfigActionBuilder { self } + /// Appends the given `command` to the current protobuf + pub fn add_command_action( + mut self, + command: protos::key_config::command_action::Command, + ) -> Self { + let action = protos::key_config::Action { + action_data: Some(protos::key_config::action::Action_data::CommandAction( + protos::key_config::CommandAction { + command: Some(command), + ..protos::key_config::CommandAction::default() + }, + )), + ..protos::key_config::Action::default() + }; + self.actions.push(action); + self + } + /// Vector of built protobuf actions pub fn actions(&self) -> &Vec { &self.actions From 15535035afaf48c740e6ec9e0e24887f93994989 Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:02:06 +1300 Subject: [PATCH 6/9] fix: address linter errors --- backend-process/src/protobuf_conversion.rs | 9 +++++---- config-frontend/src/mappers.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend-process/src/protobuf_conversion.rs b/backend-process/src/protobuf_conversion.rs index b866b45..eec8ce6 100644 --- a/backend-process/src/protobuf_conversion.rs +++ b/backend-process/src/protobuf_conversion.rs @@ -368,8 +368,9 @@ impl TryFrom for KeyWrapper { type Error = String; fn try_from(value: protos::key_config::KeyAction) -> Result { match value.key.enum_value() { - Ok(key) => match KeyWrapper::from(key) { - key_wrapper => match key_wrapper { + Ok(key) => { + let key_wrapper = KeyWrapper::from(key); + match key_wrapper { KeyWrapper(Key::Unicode(_)) => match value.unicode { Some(unicode) => match char::try_from(unicode) { Ok(c) => Ok(KeyWrapper(Key::Unicode(c))), @@ -382,8 +383,8 @@ impl TryFrom for KeyWrapper { None => Err("Other key code not found".to_string()), }, _ => Ok(key_wrapper), - }, - }, + } + } Err(e) => Err(format!( "Error matching key, an unsupported format may have been provided: {:?}", e.to_string() diff --git a/config-frontend/src/mappers.rs b/config-frontend/src/mappers.rs index d73d6f2..70e9b06 100644 --- a/config-frontend/src/mappers.rs +++ b/config-frontend/src/mappers.rs @@ -40,7 +40,7 @@ impl From<(IcedKey, Modifiers)> for ProtoKeyActionWrapper { modifier: ProtoModifierWrapper::from(modifiers) .0 .iter() - .map(|modifier_key| EnumOrUnknown::new(modifier_key.clone())) + .map(|modifier_key| EnumOrUnknown::new(*modifier_key)) .collect(), ..KeyAction::default() }) From 0bfafd0a20eff893b9e04ca97c03038c5fd1d8a7 Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:40:35 +1300 Subject: [PATCH 7/9] fix: run rustfmt --- backend-process/src/database/models.rs | 2 +- .../examples/client_sending_key_config_to_server.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/backend-process/src/database/models.rs b/backend-process/src/database/models.rs index 7142a18..a9f0f09 100644 --- a/backend-process/src/database/models.rs +++ b/backend-process/src/database/models.rs @@ -9,7 +9,7 @@ pub enum Action { Key(Key, Vec), /// (command, args) Command(String, Vec), - Noop + Noop, } /// The format we want to use inside the backend to handle actions diff --git a/messaging/examples/client_sending_key_config_to_server.rs b/messaging/examples/client_sending_key_config_to_server.rs index cb0af12..c661dee 100644 --- a/messaging/examples/client_sending_key_config_to_server.rs +++ b/messaging/examples/client_sending_key_config_to_server.rs @@ -12,8 +12,8 @@ use messaging::client_wrapper::{ClientCommands, ClientWrapper}; use messaging::proto_builders::KeyConfigActionBuilder; use messaging::protos::display_zones::DisplayZone; use messaging::protos::inputs::InputId; -use messaging::protos::key_config::command_action::Command; use messaging::protos::key_config::FreeformCommand; +use messaging::protos::key_config::command_action::Command; use messaging::protos::keys::Key; fn main() { @@ -73,12 +73,10 @@ fn main() { .send_key_config( InputId::BUTTON_1_PRESSED, KeyConfigActionBuilder::new() - .add_command_action(Command::FreeformCommand( - FreeformCommand { - command: String::from("zed"), - ..FreeformCommand::default() - } - )) + .add_command_action(Command::FreeformCommand(FreeformCommand { + command: String::from("zed"), + ..FreeformCommand::default() + })) .actions() .clone(), ) From 40c5c0c0b78a09503a6f9e52deea3f899cd988ff Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:54:49 +1300 Subject: [PATCH 8/9] fix: update error message to fix test --- backend-process/src/protobuf_conversion.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend-process/src/protobuf_conversion.rs b/backend-process/src/protobuf_conversion.rs index eec8ce6..9af6d6d 100644 --- a/backend-process/src/protobuf_conversion.rs +++ b/backend-process/src/protobuf_conversion.rs @@ -376,11 +376,11 @@ impl TryFrom for KeyWrapper { Ok(c) => Ok(KeyWrapper(Key::Unicode(c))), Err(e) => Err(e.to_string()), }, - None => Err("Unicode value not found".to_string()), + None => Err("Unicode value not found when unicode key provided".to_string()), }, KeyWrapper(Key::Other(_)) => match value.other_key_code { Some(key_code) => Ok(KeyWrapper(Key::Other(key_code))), - None => Err("Other key code not found".to_string()), + None => Err("Other key code not found when key is other".to_string()), }, _ => Ok(key_wrapper), } @@ -418,7 +418,7 @@ mod tests { assert_eq!( KeyWrapper::try_from(proto).err().unwrap(), - "Unsupported key format 54" + "Other key code not found when key is other" ); } From d66c5ef04641833b41bdc2a260e6bc32cfb91fd7 Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+choden-dev@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:00:13 +1300 Subject: [PATCH 9/9] fix: run rustfmt --- backend-process/src/protobuf_conversion.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend-process/src/protobuf_conversion.rs b/backend-process/src/protobuf_conversion.rs index 9af6d6d..61506c0 100644 --- a/backend-process/src/protobuf_conversion.rs +++ b/backend-process/src/protobuf_conversion.rs @@ -376,7 +376,9 @@ impl TryFrom for KeyWrapper { Ok(c) => Ok(KeyWrapper(Key::Unicode(c))), Err(e) => Err(e.to_string()), }, - None => Err("Unicode value not found when unicode key provided".to_string()), + None => { + Err("Unicode value not found when unicode key provided".to_string()) + } }, KeyWrapper(Key::Other(_)) => match value.other_key_code { Some(key_code) => Ok(KeyWrapper(Key::Other(key_code))),