Send keyDown, keyUp and dialRotate events

This commit is contained in:
ninjadev64 committed 2023-12-12 19:38:26 +00:00
1 parent f15ed576a1
commit e523a30aac
11 files changed
+217 -60

No files matched your search

+1 -5
View File
@@ -3,7 +3,7 @@ mod prontokey;
use std::collections::HashMap;
use lazy_static::lazy_static;
use std::sync::Mutex;
use tokio::sync::Mutex;
use serde::Serialize;
@@ -19,10 +19,6 @@ pub trait BaseDevice {
fn id(&self) -> String;
fn name(&self) -> String;
fn r#type(&self) -> u8;
fn key_down(&self, key: u8);
fn key_up(&self, key: u8);
fn dial_rotate(&self, dial: u8, ticks: i16);
}
/// Metadata of a device.
+9 -15
View File
@@ -1,5 +1,7 @@
use super::BaseDevice;
use crate::events::outbound;
use std::thread;
use std::time::Duration;
@@ -22,16 +24,6 @@ impl BaseDevice for ProntoKeyDevice {
String::from("ProntoKey")
}
fn r#type(&self) -> u8 { 7 }
fn key_down(&self, key: u8) {
println!("{} down {}", &self.address, key);
}
fn key_up(&self, key: u8) {
println!("{} up {}", &self.address, key);
}
fn dial_rotate(&self, dial: u8, ticks: i16) {
println!("{} rotate {} by {}", &self.address, dial, ticks);
}
}
impl ProntoKeyDevice {
@@ -76,7 +68,7 @@ impl ProntoKeyDevice {
if let Value::String(address) = &j["address"] {
device = Some(ProntoKeyDevice { address: address.clone() });
if let Some(device) = &device {
super::DEVICES.lock().unwrap().insert(device.id(), super::DeviceInfo::new(device));
super::DEVICES.lock().await.insert(device.id(), super::DeviceInfo::new(device));
}
}
continue;
@@ -87,9 +79,11 @@ impl ProntoKeyDevice {
// Handle key presses and releases.
if let Value::Number(num) = &j["key"] {
match num.as_u64().unwrap_or_default() as u8 {
0 => device.key_up(last_key),
0 => {
let _ = outbound::keypad::key_up(device.id(), last_key - 1).await;
},
val => {
device.key_down(val);
let _ = outbound::keypad::key_down(device.id(), val - 1).await;
last_key = val;
}
}
@@ -101,7 +95,7 @@ impl ProntoKeyDevice {
Some(v) => v as i16,
_ => last_sliders[0]
};
device.dial_rotate(0, val - last_sliders[0]);
let _ = outbound::encoder::dial_rotate(device.id(), 0, val - last_sliders[0]).await;
last_sliders[0] = val;
}
if let Value::Number(val) = &j["slider1"] {
@@ -109,7 +103,7 @@ impl ProntoKeyDevice {
Some(v) => v as i16,
_ => last_sliders[1]
};
device.dial_rotate(1, val - last_sliders[1]);
let _ = outbound::encoder::dial_rotate(device.id(), 1, val - last_sliders[1]).await;
last_sliders[1] = val;
}
}
+19 -19
View File
@@ -9,10 +9,10 @@ struct Error {
pub description: String
}
fn serialise_mutex_hashmap<T>(map: &std::sync::Mutex<HashMap<String, T>>) -> String where T: serde::Serialize {
async fn serialise_mutex_hashmap<T>(map: &tokio::sync::Mutex<HashMap<String, T>>) -> String where T: serde::Serialize {
// Here, we "duplicate" the HashMap so it isn't captured in a MutexGuard, allowing it to be serialised
let mut hash_map: HashMap<String, &T> = HashMap::new();
let locked = map.lock().unwrap();
let locked = map.lock().await;
for key in locked.keys() {
hash_map.insert(key.to_owned(), locked.get(key).unwrap());
@@ -23,13 +23,13 @@ fn serialise_mutex_hashmap<T>(map: &std::sync::Mutex<HashMap<String, T>>) -> Str
// Strings are returned from many of these commands as their return values often reference static Mutexes.
#[tauri::command]
pub fn get_devices() -> String {
serialise_mutex_hashmap(&*DEVICES)
pub async fn get_devices() -> String {
serialise_mutex_hashmap(&*DEVICES).await
}
#[tauri::command]
pub fn get_categories() -> String {
serialise_mutex_hashmap(&*CATEGORIES)
pub async fn get_categories() -> String {
serialise_mutex_hashmap(&*CATEGORIES).await
}
#[tauri::command]
@@ -41,11 +41,11 @@ pub fn get_profiles(app: tauri::AppHandle, device: &str) -> String {
}
#[tauri::command]
pub fn get_selected_profile(app: tauri::AppHandle, device: &str) -> String {
match DEVICE_STORES.lock().unwrap().get_device_store(device, &app) {
pub async fn get_selected_profile(app: tauri::AppHandle, device: String) -> String {
match DEVICE_STORES.lock().await.get_device_store(&device, &app) {
Ok(store) => {
match PROFILE_STORES.lock().unwrap().get_profile_store(
DEVICES.lock().unwrap().get(device).unwrap(),
match PROFILE_STORES.lock().await.get_profile_store(
DEVICES.lock().await.get(&device).unwrap(),
&store.value.selected_profile,
&app
) {
@@ -58,15 +58,15 @@ pub fn get_selected_profile(app: tauri::AppHandle, device: &str) -> String {
}
#[tauri::command]
pub fn set_selected_profile(app: tauri::AppHandle, device: &str, profile: &str) {
let mut device_stores = DEVICE_STORES.lock().unwrap();
let store = device_stores.get_device_store(device, &app).unwrap();
pub async fn set_selected_profile(app: tauri::AppHandle, device: String, profile: String) {
let mut device_stores = DEVICE_STORES.lock().await;
let store = device_stores.get_device_store(&device, &app).unwrap();
store.value.selected_profile = profile.to_owned();
let _ = store.save();
}
#[tauri::command]
pub fn create_instance(app: tauri::AppHandle, action: Action, context: ActionContext) -> String {
pub async fn create_instance(app: tauri::AppHandle, action: Action, context: ActionContext) -> String {
let instance = ActionInstance {
action: action.clone(),
context: context.clone(),
@@ -75,9 +75,9 @@ pub fn create_instance(app: tauri::AppHandle, action: Action, context: ActionCon
settings: serde_json::Value::Null
};
let mut profile_stores = PROFILE_STORES.lock().unwrap();
let mut profile_stores = PROFILE_STORES.lock().await;
let store = match profile_stores.get_profile_store(
DEVICES.lock().unwrap().get(&context.device).unwrap(),
DEVICES.lock().await.get(&context.device).unwrap(),
&context.profile,
&app
) {
@@ -102,10 +102,10 @@ pub fn create_instance(app: tauri::AppHandle, action: Action, context: ActionCon
}
#[tauri::command]
pub fn clear_slot(app: tauri::AppHandle, context: ActionContext) -> String {
let mut profile_stores = PROFILE_STORES.lock().unwrap();
pub async fn clear_slot(app: tauri::AppHandle, context: ActionContext) -> String {
let mut profile_stores = PROFILE_STORES.lock().await;
let store = match profile_stores.get_profile_store(
DEVICES.lock().unwrap().get(&context.device).unwrap(),
DEVICES.lock().await.get(&context.device).unwrap(),
&context.profile,
&app
) {
+12 -11
View File
@@ -1,27 +1,28 @@
mod inbound;
pub mod outbound;
pub mod frontend;
use std::collections::HashMap;
use tokio::sync::Mutex;
use tokio::net::TcpStream;
use tokio_tungstenite::WebSocketStream;
use futures_util::StreamExt;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
use futures_util::{stream::SplitSink, StreamExt, TryStreamExt};
use lazy_static::lazy_static;
lazy_static! {
static ref SOCKETS: Mutex<HashMap<String, WebSocketStream<TcpStream>>> = Mutex::new(HashMap::new());
static ref SOCKETS: Mutex<HashMap<String, SplitSink<WebSocketStream<TcpStream>, Message>>> = Mutex::new(HashMap::new());
}
/// Register a plugin to send and receive events with its WebSocket.
pub async fn register_plugin(data: inbound::RegisterEvent, stream: WebSocketStream<TcpStream>) {
SOCKETS.lock().await.insert(data.uuid.clone(), stream);
tokio::spawn(handle_stream(data.uuid));
}
async fn handle_stream(uuid: String) {
while let Some(data) = SOCKETS.lock().await.get_mut(&uuid).unwrap().next().await {
println!("{}", data.unwrap());
}
let (read, write) = stream.split();
tokio::spawn(write.try_for_each(|value| {
if let Message::Text(text) = value {
println!("{}", text);
}
futures_util::future::ready(Ok(()))
}));
SOCKETS.lock().await.insert(data.uuid.clone(), read);
}
+45
View File
@@ -0,0 +1,45 @@
use super::{Coordinates, send_to_plugin, get_instance};
use crate::shared::ActionContext;
use serde::Serialize;
#[derive(Serialize)]
struct DialRotatePayload {
settings: serde_json::Value,
coordinates: Coordinates,
ticks: i16,
pressed: bool
}
#[derive(Serialize)]
struct DialRotateEvent {
action: String,
event: String,
context: ActionContext,
device: String,
payload: DialRotatePayload
}
pub async fn dial_rotate(device: String, index: u8, ticks: i16) -> Result<(), anyhow::Error> {
let instance = match get_instance(&device, index, "Encoder").await? {
Some(instance) => instance,
None => return Ok(())
};
send_to_plugin(&instance.action.plugin, DialRotateEvent {
action: instance.action.uuid.clone(),
event: "keyDown".to_owned(),
context: instance.context.clone(),
device: instance.context.device.clone(),
payload: DialRotatePayload {
settings: instance.settings.clone(),
coordinates: Coordinates {
row: instance.context.position / 3,
column: instance.context.position % 3
},
ticks,
pressed: false
}
}).await
}
+62
View File
@@ -0,0 +1,62 @@
use super::{Coordinates, send_to_plugin, get_instance};
use crate::shared::{ActionContext, ActionInstance};
use serde::Serialize;
#[derive(Serialize)]
struct KeyPayload {
settings: serde_json::Value,
coordinates: Coordinates,
state: u16
}
#[derive(Serialize)]
struct KeyEvent {
action: String,
event: String,
context: ActionContext,
device: String,
payload: KeyPayload
}
fn create_payload(instance: &ActionInstance) -> KeyPayload {
KeyPayload {
settings: instance.settings.clone(),
coordinates: Coordinates {
row: instance.context.position / 3,
column: instance.context.position % 3
},
state: instance.current_state
}
}
pub async fn key_down(device: String, key: u8) -> Result<(), anyhow::Error> {
let instance = match get_instance(&device, key, "Keypad").await? {
Some(instance) => instance,
None => return Ok(())
};
send_to_plugin(&instance.action.plugin, KeyEvent {
action: instance.action.uuid.clone(),
event: "keyDown".to_owned(),
context: instance.context.clone(),
device: instance.context.device.clone(),
payload: create_payload(&instance)
}).await
}
pub async fn key_up(device: String, key: u8) -> Result<(), anyhow::Error> {
let instance = match get_instance(&device, key, "Keypad").await? {
Some(instance) => instance,
None => return Ok(())
};
send_to_plugin(&instance.action.plugin, KeyEvent {
action: instance.action.uuid.clone(),
event: "keyUp".to_owned(),
context: instance.context.clone(),
device: instance.context.device.clone(),
payload: create_payload(&instance)
}).await
}
+47
View File
@@ -0,0 +1,47 @@
pub mod keypad;
pub mod encoder;
use super::SOCKETS;
use crate::APP_HANDLE;
use crate::devices::DEVICES;
use crate::shared::ActionInstance;
use crate::store::profiles::{DEVICE_STORES, PROFILE_STORES};
use serde::Serialize;
use futures_util::SinkExt;
#[derive(Serialize)]
struct Coordinates {
row: u8,
column: u8
}
async fn send_to_plugin(plugin: &str, data: impl Serialize) -> Result<(), anyhow::Error> {
let message = tokio_tungstenite::tungstenite::Message::Text(serde_json::to_string(&data).unwrap());
SOCKETS.lock().await.get_mut(plugin).unwrap().send(message).await?;
Ok(())
}
async fn get_instance(device: &str, index: u8, controller: &str) -> Result<Option<ActionInstance>, anyhow::Error> {
let app = APP_HANDLE.lock().await;
let app = app.as_ref().unwrap();
let mut device_stores = DEVICE_STORES.lock().await;
let selected_profile = &device_stores.get_device_store(device, app)?.value.selected_profile;
let devices = DEVICES.lock().await;
let device = devices.get(device).unwrap();
let mut profile_stores = PROFILE_STORES.lock().await;
let profile = &profile_stores.get_profile_store(device, selected_profile, app)?.value;
let configured = match controller {
"Encoder" => profile.sliders[index as usize].as_ref(),
_ => profile.keys[index as usize].as_ref()
};
match configured {
Some(configured) => Ok(Some(configured.clone())),
None => Ok(None)
}
}
+9
View File
@@ -9,6 +9,13 @@ mod devices;
use events::frontend;
use tokio::sync::Mutex;
use lazy_static::lazy_static;
lazy_static! {
pub static ref APP_HANDLE: Mutex<Option<tauri::AppHandle>> = Mutex::new(None);
}
#[tokio::main]
async fn main() {
let app = match tauri::Builder::default()
@@ -27,6 +34,8 @@ async fn main() {
Err(error) => panic!("Failed to create Tauri application: {}", error)
};
*APP_HANDLE.lock().await = Some(app.handle());
devices::initialise_devices();
plugins::initialise_plugins(app.handle());
+10 -7
View File
@@ -1,11 +1,11 @@
mod manifest;
mod info_param;
use crate::APP_HANDLE;
use crate::shared::{Action, CATEGORIES, convert_icon};
use std::{fs, path};
use std::process::{Command, Stdio};
use std::collections::HashMap;
use tauri::AppHandle;
@@ -15,7 +15,7 @@ use futures_util::StreamExt;
use anyhow::{Context, anyhow};
/// Initialise a plugin from a given directory.
fn initialise_plugin(path: path::PathBuf, app: &AppHandle, categories: &mut HashMap<String, Vec<Action>>) -> anyhow::Result<()> {
async fn initialise_plugin(path: path::PathBuf) -> anyhow::Result<()> {
let plugin_uuid = path.file_name().unwrap().to_str().unwrap();
let mut manifest_path = path.clone();
manifest_path.push("manifest.json");
@@ -45,6 +45,7 @@ fn initialise_plugin(path: path::PathBuf, app: &AppHandle, categories: &mut Hash
}
}
let mut categories = CATEGORIES.lock().await;
if let Some(category) = categories.get_mut(&manifest.category) {
for action in manifest.actions {
category.push(action);
@@ -97,7 +98,7 @@ fn initialise_plugin(path: path::PathBuf, app: &AppHandle, categories: &mut Hash
}
let mut devices: Vec<info_param::DeviceInfo> = vec![];
for device in crate::devices::DEVICES.lock().unwrap().values() {
for device in crate::devices::DEVICES.lock().await.values() {
devices.push(info_param::DeviceInfo::new(device));
}
@@ -131,7 +132,7 @@ fn initialise_plugin(path: path::PathBuf, app: &AppHandle, categories: &mut Hash
// Create a webview window for the plugin and call its registration function.
let url = String::from("http://localhost:57118/") + path.join(code_path).to_str().unwrap();
let window = tauri::WindowBuilder::new(
app,
APP_HANDLE.lock().await.as_ref().unwrap(),
plugin_uuid.replace('.', "_"),
tauri::WindowUrl::External(url.parse().unwrap())
)
@@ -200,9 +201,11 @@ pub fn initialise_plugins(app: AppHandle) {
for entry in entries {
if let Ok(entry) = entry {
if entry.metadata().unwrap().is_dir() {
if let Err(error) = initialise_plugin(entry.path(), &app, &mut CATEGORIES.lock().unwrap()) {
eprintln!("{}\n\tCaused by: {}", error, error.root_cause());
}
tokio::spawn(async move {
if let Err(error) = initialise_plugin(entry.path()).await {
eprintln!("{}\n\tCaused by: {}", error, error.root_cause());
}
});
} else {
eprintln!("Failed to initialise plugin at {}: is a file", entry.path().display());
}
+2 -2
View File
@@ -1,11 +1,11 @@
use std::collections::HashMap;
use std::sync::Mutex;
use std::path::Path;
use serde::{Serialize, Deserialize};
use serde_inline_default::serde_inline_default;
use lazy_static::lazy_static;
use tokio::sync::Mutex;
/// Convert an icon specified in a plugin manifest to its full path.
pub fn convert_icon(path: String) -> String {
@@ -110,7 +110,7 @@ impl ToString for ActionContext {
}
/// An instance of an action.
#[derive(Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize)]
pub struct ActionInstance {
pub action: Action,
pub context: ActionContext,
+1 -1
View File
@@ -2,7 +2,6 @@ use super::Store;
use crate::shared::Profile;
use std::fs;
use std::sync::Mutex;
use std::collections::HashMap;
use std::iter::repeat_with;
@@ -10,6 +9,7 @@ use serde::{Serialize, Deserialize};
use anyhow::Context;
use lazy_static::lazy_static;
use tokio::sync::Mutex;
pub struct ProfileStores {
stores: HashMap<String, Store<Profile>>