diff --git a/src-tauri/res/www/index-go.html b/src-tauri/res/www/index-go.html new file mode 100644 index 0000000..67d85f4 --- /dev/null +++ b/src-tauri/res/www/index-go.html @@ -0,0 +1,117 @@ + + + + slidershim-brokenithm + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/src-tauri/res/www/index.html b/src-tauri/res/www/index.html index 8728f08..b4dd603 100644 --- a/src-tauri/res/www/index.html +++ b/src-tauri/res/www/index.html @@ -1,7 +1,7 @@ - brokenithm-kb + slidershim-brokenithm (handle: &AppHandle) { + handle.emit_all("ackShow", ""); handle.get_window("main").unwrap().show().ok(); } fn hide_window(handle: &AppHandle) { + handle.emit_all("ackHide", ""); handle.get_window("main").unwrap().hide().ok(); } @@ -36,14 +38,13 @@ fn main() { .init(); let config = Arc::new(Mutex::new(Some(slider_io::Config::default()))); - let manager: Arc>> = Arc::new(Mutex::new(None)); + let manager = Arc::new(Mutex::new(slider_io::Manager::new())); { let config_handle = config.lock().unwrap(); let config_handle_ref = config_handle.as_ref().unwrap(); config_handle_ref.save(); - // let mut manager_handle = manager.lock().unwrap(); - // manager_handle.take(); - // manager_handle.replace(slider_io::Manager::new(config_handle_ref.clone())); + let manager_handle = manager.lock().unwrap(); + manager_handle.update_config(config_handle_ref.clone()); } tauri::Builder::default() @@ -95,12 +96,9 @@ fn main() { // UI ready event let app_handle = app.handle(); let config_clone = Arc::clone(&config); - app.listen_global("heartbeat", move |_| { - let handle = AsyncHandle::try_current(); - println!("handle, {:?}", handle); - + app.listen_global("ready", move |_| { let config_handle = config_clone.lock().unwrap(); - info!("Heartbeat received"); + info!("Start signal received"); app_handle .emit_all( "showConfig", @@ -109,6 +107,23 @@ fn main() { .unwrap(); }); + // UI update event + let app_handle = app.handle(); + let manager_clone = Arc::clone(&manager); + app.listen_global("queryState", move |event| { + // app_handle.emit_all("showState", "@@@"); + let snapshot = { + let manager_handle = manager_clone.lock().unwrap(); + manager_handle.try_get_state().map(|x| x.snapshot()) + }; + match snapshot { + Some(snapshot) => { + app_handle.emit_all("showState", snapshot); + } + _ => {} + } + }); + // Config set event let config_clone = Arc::clone(&config); let manager_clone = Arc::clone(&manager); @@ -121,9 +136,8 @@ fn main() { config_handle.replace(new_config); let config_handle_ref = config_handle.as_ref().unwrap(); config_handle_ref.save(); - let mut manager_handle = manager_clone.lock().unwrap(); - manager_handle.take(); - manager_handle.replace(slider_io::Manager::new(config_handle_ref.clone())); + let manager_handle = manager_clone.lock().unwrap(); + manager_handle.update_config(config_handle_ref.clone()); } }); diff --git a/src-tauri/src/slider_io/brokenithm.rs b/src-tauri/src/slider_io/brokenithm.rs index 6708f0b..a3c13c2 100644 --- a/src-tauri/src/slider_io/brokenithm.rs +++ b/src-tauri/src/slider_io/brokenithm.rs @@ -9,7 +9,7 @@ use hyper::{ }; use log::{error, info}; use path_clean::PathClean; -use std::{convert::Infallible, future::Future, net::SocketAddr, path::PathBuf}; +use std::{convert::Infallible, env::current_exe, future::Future, net::SocketAddr}; use tokio::fs::File; use tokio_tungstenite::WebSocketStream; use tokio_util::codec::{BytesCodec, FramedRead}; @@ -29,15 +29,17 @@ async fn error_response() -> Result, Infallible> { } async fn serve_file(path: &str) -> Result, Infallible> { - let mut pb = PathBuf::from("res/www/"); + let mut pb = current_exe().unwrap(); + pb.pop(); + pb.push("res/www"); pb.push(path); pb.clean(); // println!("CWD {:?}", std::env::current_dir()); - // println!("Serving file {:?}", pb); - match File::open(pb).await { + match File::open(&pb).await { Ok(f) => { + info!("Serving file {:?}", pb); let stream = FramedRead::new(f, BytesCodec::new()); let body = Body::wrap_stream(stream); Ok(Response::new(body)) @@ -154,6 +156,7 @@ async fn handle_request( request: Request, remote_addr: SocketAddr, state: FullState, + ground_only: bool, ) -> Result, Infallible> { let method = request.method(); let path = request.uri().path(); @@ -170,7 +173,10 @@ async fn handle_request( request.uri().path(), request.headers().contains_key(header::UPGRADE), ) { - ("/", false) | ("/index.html", false) => serve_file("index.html").await, + ("/", false) | ("/index.html", false) => match ground_only { + false => serve_file("index.html").await, + true => serve_file("index-go.html").await, + }, (filename, false) => serve_file(&filename[1..]).await, ("/ws", true) => handle_websocket(request, state).await, _ => error_response().await, @@ -179,12 +185,14 @@ async fn handle_request( pub struct BrokenithmJob { state: FullState, + ground_only: bool, } impl BrokenithmJob { - pub fn new(state: &FullState) -> Self { + pub fn new(state: &FullState, ground_only: &bool) -> Self { Self { state: state.clone(), + ground_only: *ground_only, } } } @@ -193,13 +201,14 @@ impl BrokenithmJob { impl AsyncJob for BrokenithmJob { async fn run + Send>(self, stop_signal: F) { let state = self.state.clone(); + let ground_only = self.ground_only; let make_svc = make_service_fn(|conn: &AddrStream| { let remote_addr = conn.remote_addr(); let make_svc_state = state.clone(); async move { Ok::<_, Infallible>(service_fn(move |request: Request| { let svc_state = make_svc_state.clone(); - handle_request(request, remote_addr, svc_state) + handle_request(request, remote_addr, svc_state, ground_only) })) } }); diff --git a/src-tauri/src/slider_io/context.rs b/src-tauri/src/slider_io/context.rs new file mode 100644 index 0000000..cb8f4e6 --- /dev/null +++ b/src-tauri/src/slider_io/context.rs @@ -0,0 +1,64 @@ +use log::info; + +use crate::slider_io::{ + brokenithm::BrokenithmJob, + config::{Config, DeviceMode}, + controller_state::FullState, + device::HidDeviceJob, + led::LedJob, + output::OutputJob, + worker::{AsyncWorker, ThreadWorker}, +}; + +#[allow(dead_code)] +pub struct Context { + state: FullState, + config: Config, + device_worker: Option, + brokenithm_worker: Option, + output_worker: ThreadWorker, + led_worker: ThreadWorker, +} + +impl Context { + pub fn new(config: Config) -> Self { + info!("Context creating"); + info!("Device config {:?}", config.device_mode); + info!("Output config {:?}", config.output_mode); + info!("LED config {:?}", config.led_mode); + + let state = FullState::new(); + + let (device_worker, brokenithm_worker) = match &config.device_mode { + DeviceMode::Brokenithm { ground_only } => ( + None, + Some(AsyncWorker::new( + "brokenithm", + BrokenithmJob::new(&state, ground_only), + )), + ), + other => ( + Some(ThreadWorker::new( + "device", + HidDeviceJob::from_config(&state, other), + )), + None, + ), + }; + let output_worker = ThreadWorker::new("output", OutputJob::new(&state, &config.output_mode)); + let led_worker = ThreadWorker::new("led", LedJob::new(&state, &config.led_mode)); + + Self { + state, + config, + device_worker, + brokenithm_worker, + output_worker, + led_worker, + } + } + + pub fn clone_state(&self) -> FullState { + self.state.clone() + } +} diff --git a/src-tauri/src/slider_io/controller_state.rs b/src-tauri/src/slider_io/controller_state.rs index aa85df4..1a09bfc 100644 --- a/src-tauri/src/slider_io/controller_state.rs +++ b/src-tauri/src/slider_io/controller_state.rs @@ -74,6 +74,21 @@ impl FullState { pub fn clone_led(&self) -> Arc> { Arc::clone(&self.led_state) } + + pub fn snapshot(&self) -> Vec { + let mut buf: Vec = vec![]; + { + let controller_state_handle = self.controller_state.lock().unwrap(); + buf.extend(controller_state_handle.ground_state); + buf.extend(controller_state_handle.air_state); + }; + { + let led_state_handle = self.led_state.lock().unwrap(); + buf.extend(led_state_handle.led_state); + }; + + buf + } } impl Clone for FullState { diff --git a/src-tauri/src/slider_io/keyboard.rs b/src-tauri/src/slider_io/keyboard.rs index 162f99e..1a8ebf3 100644 --- a/src-tauri/src/slider_io/keyboard.rs +++ b/src-tauri/src/slider_io/keyboard.rs @@ -31,8 +31,8 @@ const YUANCON_KB_MAP: [usize; 41] = [ #[rustfmt::skip] const DEEMO_KB_MAP: [usize; 41] = [ 0x41, 0x41, 0x41, 0x41, // A - 0x35, 0x35, 0x35, 0x35, // S - 0x4f, 0x4f, 0x4f, 0x4f, // D + 0x53, 0x53, 0x53, 0x53, // S + 0x44, 0x44, 0x44, 0x44, // D 0x46, 0x46, 0x46, 0x46, // F 0x4a, 0x4a, 0x4a, 0x4a, // J 0x4b, 0x4b, 0x4b, 0x4b, // K diff --git a/src-tauri/src/slider_io/manager.rs b/src-tauri/src/slider_io/manager.rs index 1e093c3..ab3f34c 100644 --- a/src-tauri/src/slider_io/manager.rs +++ b/src-tauri/src/slider_io/manager.rs @@ -1,57 +1,94 @@ use log::info; - -use crate::slider_io::{ - brokenithm::BrokenithmJob, - config::{Config, DeviceMode}, - controller_state::FullState, - device::HidDeviceJob, - led::LedJob, - output::OutputJob, - worker::{AsyncWorker, ThreadWorker}, +use std::{ + sync::{Arc, Mutex}, + thread::{self, JoinHandle}, +}; +use tokio::{ + select, + sync::{mpsc, oneshot}, }; -#[allow(dead_code)] +use crate::slider_io::{config::Config, context::Context}; + +use super::controller_state::FullState; + pub struct Manager { - state: FullState, - config: Config, - device_worker: Option, - brokenithm_worker: Option, - output_worker: ThreadWorker, - led_worker: ThreadWorker, + state: Arc>>, + join_handle: Option>, + tx_config: mpsc::UnboundedSender, + tx_stop: Option>, } impl Manager { - pub fn new(config: Config) -> Self { - info!("Starting manager"); - info!("Device config {:?}", config.device_mode); - info!("Output config {:?}", config.output_mode); - info!("LED config {:?}", config.led_mode); + pub fn new() -> Self { + let state = Arc::new(Mutex::new(None)); + let (tx_config, mut rx_config) = mpsc::unbounded_channel::(); + let (tx_stop, rx_stop) = oneshot::channel::<()>(); - let state = FullState::new(); + let context: Arc>> = Arc::new(Mutex::new(None)); - let (device_worker, brokenithm_worker) = match &config.device_mode { - DeviceMode::Brokenithm { .. } => ( - None, - Some(AsyncWorker::new("brokenithm", BrokenithmJob::new(&state))), - ), - other => ( - Some(ThreadWorker::new( - "device", - HidDeviceJob::from_config(&state, other), - )), - None, - ), - }; - let output_worker = ThreadWorker::new("output", OutputJob::new(&state, &config.output_mode)); - let led_worker = ThreadWorker::new("led", LedJob::new(&state, &config.led_mode)); + let state_cloned = Arc::clone(&state); + let context_cloned = Arc::clone(&context); + + let join_handle = thread::spawn(move || { + info!("Manager thread started"); + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .unwrap(); + runtime.block_on(async move { + info!("Manager runtime started"); + + select! { + _ = async { + loop { + match rx_config.recv().await { + Some(config) => { + info!("Rebuilding context"); + let mut context_handle = context_cloned.lock().unwrap(); + context_handle.take(); + + let new_context = Context::new(config); + let new_state = new_context.clone_state(); + context_handle.replace(new_context); + + let mut state_handle = state_cloned.lock().unwrap(); + state_handle.replace(new_state); + }, + None => { + let mut context_handle = context_cloned.lock().unwrap(); + context_handle.take(); + } + } + } + } => {}, + _ = rx_stop => {} + } + }); + }); Self { state, - config, - device_worker, - brokenithm_worker, - output_worker, - led_worker, + join_handle: Some(join_handle), + tx_config, + tx_stop: Some(tx_stop), } } + + pub fn update_config(&self, config: Config) { + self.tx_config.send(config).unwrap(); + } + + pub fn try_get_state(&self) -> Option { + let state_handle = self.state.lock().unwrap(); + state_handle.as_ref().map(|x| x.clone()) + } +} + +impl Drop for Manager { + fn drop(&mut self) { + self.tx_stop.take().unwrap().send(()).unwrap(); + self.join_handle.take().unwrap().join().unwrap(); + } } diff --git a/src-tauri/src/slider_io/mod.rs b/src-tauri/src/slider_io/mod.rs index b29931e..9469911 100644 --- a/src-tauri/src/slider_io/mod.rs +++ b/src-tauri/src/slider_io/mod.rs @@ -13,6 +13,7 @@ mod device; mod led; mod output; +mod context; mod manager; pub use config::Config; diff --git a/src-tauri/src/slider_io/worker.rs b/src-tauri/src/slider_io/worker.rs index 9a8452e..b0145da 100644 --- a/src-tauri/src/slider_io/worker.rs +++ b/src-tauri/src/slider_io/worker.rs @@ -9,7 +9,7 @@ use std::{ thread, }; -use tokio::{runtime::Runtime, sync::oneshot, task}; +use tokio::{sync::oneshot, task}; pub trait ThreadJob: Send { fn setup(&mut self) -> bool; @@ -68,7 +68,6 @@ pub trait AsyncJob: Send + 'static { pub struct AsyncWorker { name: &'static str, - runtime: Runtime, task: Option>, stop_signal: Option>, } @@ -81,13 +80,8 @@ impl AsyncWorker { info!("Async worker starting {}", name); let (send_stop, recv_stop) = oneshot::channel::<()>(); - let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(1) - .enable_all() - .build() - .unwrap(); - let task = runtime.spawn(async move { + let task = tokio::spawn(async move { job .run(async move { recv_stop.await.unwrap(); @@ -97,7 +91,6 @@ impl AsyncWorker { AsyncWorker { name, - runtime, task: Some(task), stop_signal: Some(send_stop), } @@ -108,20 +101,7 @@ impl Drop for AsyncWorker { fn drop(&mut self) { info!("Async worker stopping {}", self.name); - if self.stop_signal.is_some() { - let send_stop = self.stop_signal.take().unwrap(); - send_stop.send(()).unwrap(); - // self.runtime.block_on(async move { - // send_stop.send(()).unwrap(); - // }); - } - - if self.task.is_some() { - // let task = self.task.take().unwrap(); - // self.runtime.block_on(async move { - // task.await; - // info!("Async worker stopping internal {}", name); - // }); - } + self.stop_signal.take().unwrap().send(()).unwrap(); + self.task.take(); } } diff --git a/src/App.svelte b/src/App.svelte index 8281afa..ba354eb 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -16,10 +16,27 @@ let debugstr = ""; + let polling = null; + let tick = 0; + let previewData = Array(131).fill(0); + + function updatePolling(enabled) { + if (!!polling) { + clearInterval(polling); + polling = null; + } + if (enabled) { + polling = setInterval(async () => { + tick += 1; + await emit("queryState", ""); + }, 50); + } + // console.log(enabled, polling, tick); + } + onMount(async () => { // console.log(emit, listen); await listen("showConfig", (event) => { - console.log("heartbeat", event); debugstr = event.payload; const payload: any = JSON.parse(event.payload as any); deviceMode = payload.deviceMode || "none"; @@ -32,7 +49,22 @@ ledWebsocketUrl = payload.ledWebsocketUrl || "http://localhost:3001"; ledSerialPort = payload.ledSerialPort || "COM5"; }); - await emit("heartbeat", ""); + + await listen("showState", (event) => { + previewData = event.payload; + }); + + await emit("ready", ""); + + updatePolling(true); + await listen("ackShow", (event) => { + console.log("ackShow"); + updatePolling(true); + }); + await listen("ackHide", (event) => { + console.log("ackHide"); + updatePolling(false); + }); }); async function setConfig() { @@ -73,7 +105,7 @@ {debugstr} -->
- +
Input Device
@@ -84,7 +116,7 @@ - +
diff --git a/src/Preview.svelte b/src/Preview.svelte index dd81eff..70a3133 100644 --- a/src/Preview.svelte +++ b/src/Preview.svelte @@ -1,22 +1,44 @@
-
+
-
- {#each ledDatas as {color, spec}, idx (idx)} -
+
+ {#each ledDatas as { color, spec }, idx (idx)} +
{/each} -
+