mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 18:02:23 +00:00
Send keyDown, keyUp and dialRotate events
This commit is contained in:
1 parent
f15ed576a1
commit
e523a30aac
11 files changed
+217
-60
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,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());
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>>
|
||||
|
||||
Reference in new issue
Block a user