From a1f0bd3c4fa7acd8259b798a164c4d2de1f9f508 Mon Sep 17 00:00:00 2001 From: Unknow Date: Thu, 15 Jan 2026 00:12:29 +0100 Subject: [PATCH 1/7] add goto key & static client ordering --- rkvm-server/src/config.rs | 12 ++- rkvm-server/src/main.rs | 3 +- rkvm-server/src/server.rs | 153 ++++++++++++++++++++++++++++---------- 3 files changed, 126 insertions(+), 42 deletions(-) diff --git a/rkvm-server/src/config.rs b/rkvm-server/src/config.rs index 98ef27f..8880db5 100644 --- a/rkvm-server/src/config.rs +++ b/rkvm-server/src/config.rs @@ -1,7 +1,7 @@ use rkvm_input::key::{Button, Key, Keyboard}; use serde::Deserialize; use std::collections::HashSet; -use std::net::SocketAddr; +use std::net::{SocketAddr, IpAddr}; use std::path::PathBuf; #[derive(Deserialize)] @@ -13,6 +13,16 @@ pub struct Config { pub password: String, pub switch_keys: HashSet, pub propagate_switch_keys: Option, + + pub goto_keys: Option>, + pub clients: Option>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct ClientConfig { + pub addr: IpAddr, + pub goto_keys: Option>, } #[derive(Deserialize, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/rkvm-server/src/main.rs b/rkvm-server/src/main.rs index eb3f402..c6cfc3e 100644 --- a/rkvm-server/src/main.rs +++ b/rkvm-server/src/main.rs @@ -68,10 +68,11 @@ async fn main() -> ExitCode { }; let switch_keys = config.switch_keys.into_iter().map(Into::into).collect(); + let server_goto_keys = config.goto_keys.map(|keys_vec| keys_vec.into_iter().map(Into::into).collect()); let propagate_switch_keys = config.propagate_switch_keys.unwrap_or(true); tokio::select! { - result = server::run(config.listen, acceptor, &config.password, &switch_keys, propagate_switch_keys) => { + result = server::run(config.listen, acceptor, &config.password, &switch_keys, propagate_switch_keys, &server_goto_keys, &config.clients) => { if let Err(err) = result { tracing::error!("Error: {}", err); return ExitCode::FAILURE; diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index b783181..8da13ae 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -12,7 +12,7 @@ use slab::Slab; use std::collections::{HashMap, HashSet, VecDeque}; use std::ffi::CString; use std::io::{self, ErrorKind}; -use std::net::SocketAddr; +use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use std::time::Instant; use thiserror::Error; use tokio::io::{AsyncWriteExt, BufStream}; @@ -23,6 +23,10 @@ use tokio::time; use tokio_rustls::TlsAcceptor; use tracing::Instrument; +use crate::config::ClientConfig; + +const ADDR_UNKNOWN: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED),0); + #[derive(Error, Debug)] pub enum Error { #[error("Network error: {0}")] @@ -39,17 +43,38 @@ pub async fn run( password: &str, switch_keys: &HashSet, propagate_switch_keys: bool, + server_goto_keys: &Option>, + clients_config: &Option>, ) -> Result<(), Error> { let listener = TcpListener::bind(&listen).await.map_err(Error::Network)?; tracing::info!("Listening on {}", listen); let mut monitor = Monitor::new(); let mut devices = Slab::::new(); - let mut clients = Slab::<(Sender<_>, SocketAddr)>::new(); + let mut clients = Slab::, SocketAddr)>>::new(); let mut current = 0; - let mut previous = 0; let mut changed = false; let mut pressed_keys = HashSet::new(); + let mut all_switch_keys = switch_keys.clone(); + let mut static_client = Vec::new(); + let mut goto_keys: HashMap,usize> = HashMap::new(); + + if let Some(keys) = server_goto_keys { + goto_keys.insert(keys.clone(), 0); + all_switch_keys.extend(keys); + } + + if let Some(list) = clients_config { + for c in list { + clients.insert(None); + static_client.push(c.addr); + if let Some(k) = &c.goto_keys { + let keys:Vec = k.clone().into_iter().map(Into::into).collect(); + goto_keys.insert(keys.clone(), static_client.len()); + all_switch_keys.extend(keys); + } + } + } let (events_sender, mut events_receiver) = mpsc::channel(1); @@ -63,8 +88,24 @@ pub async fn run( let password = password.to_owned(); // Remove dead clients. - clients.retain(|_, (client, _)| !client.is_closed()); - if !clients.contains(current) { + for id in 0..static_client.len() { + if let Some((client, _)) = &clients[id] { + if client.is_closed() { + clients[id] = None + } + } + } + clients.retain(|idx, e| + if idx < static_client.len() { + true + } else { + match e { + Some((client, _)) => !client.is_closed(), + None => true, + } + } + ); + if !clients.contains(current) || clients[current].is_none() { current = 0; } @@ -85,9 +126,22 @@ pub async fn run( .collect(); let (sender, receiver) = mpsc::channel(1); - clients.insert((sender, addr)); - let span = tracing::info_span!("connection", addr = %addr); + let index = static_client.iter().position(|ip| *ip == addr.ip()); + let idx = match index { + Some(idx) => { + if clients[idx].is_some() { + tracing::warn!("client {} already connected", addr); + clients.insert(Some((sender, addr))) + } else { + clients[idx] = Some((sender, addr)); + idx + } + }, + None => clients.insert(Some((sender, addr))), + }; + + let span = tracing::info_span!("connection", addr = %addr, idx = %idx); tokio::spawn( async move { tracing::info!("Connected"); @@ -113,21 +167,26 @@ pub async fn run( let keys = interceptor.key().collect::>(); let repeat = interceptor.repeat(); - for (_, (sender, _)) in &clients { - let update = Update::CreateDevice { - id, - name: name.clone(), - version: version.clone(), - vendor: vendor.clone(), - product: product.clone(), - rel: rel.clone(), - abs: abs.clone(), - keys: keys.clone(), - delay: repeat.delay, - period: repeat.period, - }; - - let _ = sender.send(update).await; + for (_, e) in &clients { + match e { + Some((sender, _)) => { + let update = Update::CreateDevice { + id, + name: name.clone(), + version: version.clone(), + vendor: vendor.clone(), + product: product.clone(), + rel: rel.clone(), + abs: abs.clone(), + keys: keys.clone(), + delay: repeat.delay, + period: repeat.period, + }; + + let _ = sender.send(update).await; + }, + None => {} + } } let (interceptor_sender, mut interceptor_receiver) = mpsc::channel(32); @@ -189,7 +248,7 @@ pub async fn run( let mut press = false; if let Event::Key(KeyEvent { key, down }) = event { - if switch_keys.contains(&key) { + if all_switch_keys.contains(&key) { press = true; match down { @@ -200,11 +259,20 @@ pub async fn run( } // Who to send this event to. - let mut idx = current; + let idx = current; if press { - if pressed_keys.len() == switch_keys.len() { - let exists = |idx| idx == 0 || clients.contains(idx - 1); + let exists = |idx| idx == 0 || clients.contains(idx - 1) && clients[idx-1].is_some(); + + for (keys,&i) in &goto_keys { + if exists(i) && keys.iter().all(|k| pressed_keys.contains(k)) { + current = i; + changed = true; + break; + } + } + + if !changed && switch_keys.is_subset(&pressed_keys) { loop { current = (current + 1) % (clients.len() + 1); if exists(current) { @@ -212,20 +280,16 @@ pub async fn run( } } - previous = idx; changed = true; - + } + if changed { if current != 0 { - tracing::info!(idx = %current, addr = %clients[current - 1].1, "Switched client"); + tracing::info!(idx = %current, addr = %clients[current - 1].as_ref().map_or_else(|| &ADDR_UNKNOWN, |(_,a)| a), "Switched client"); } else { tracing::info!(idx = %current, "Switched client"); } - } else if changed { - idx = previous; - if pressed_keys.is_empty() { - changed = false; - } + changed = false; } } @@ -254,18 +318,27 @@ pub async fn run( } for event in events { - if clients[idx - 1].0.send(Update::Event { id, event }).await.is_err() { - clients.remove(idx - 1); + if let Some((s,_)) = &clients[idx -1] { + if s.send(Update::Event { id, event }).await.is_err() { + if idx - 1 < static_client.len() { + clients[idx -1] = None + } else { + clients.remove(idx - 1); + } - if current == idx { - current = 0; + if current == idx { + current = 0; + } } } } } Err(err) if err.kind() == ErrorKind::BrokenPipe => { - for (_, (sender, _)) in &clients { - let _ = sender.send(Update::DestroyDevice { id }).await; + for (_, e) in &clients { + let _ = match e { + Some((sender,_)) => sender.send(Update::DestroyDevice { id }).await, + None => Ok(()), + }; } devices.remove(id); From a16292420b010d62a5b858bcf2c04664da791bf0 Mon Sep 17 00:00:00 2001 From: Unknow Date: Thu, 15 Jan 2026 16:03:04 +0100 Subject: [PATCH 2/7] updater exemple --- example/server.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/example/server.toml b/example/server.toml index 1c00141..5f5d965 100644 --- a/example/server.toml +++ b/example/server.toml @@ -12,3 +12,14 @@ key = "/etc/rkvm/key.pem" # # Change this to your own value before deploying rkvm. password = "123456789" + +# Optional switch to the server (same keys list as switch-keys) +# goto-keys = [ "left-alt", "f1"] + +# Client will keep the order (non listed client will be after) +# so switch-keys will cycle server -> 10.10.0.1 -> 10.10.0.2 -> -> other -> server +# [[clients]] +# addr = "10.10.0.1" # identify the client +# goto-keys = [ "left-alt", "f2"] # optional: go to this client directly if it's connected +# [[clients]] +# addr = "10.10.0.2" From 130fbbfd7b4f4825dd26bed53739030ff33f92dd Mon Sep 17 00:00:00 2001 From: Unknow Date: Thu, 15 Jan 2026 16:46:09 +0100 Subject: [PATCH 3/7] replace option onj clients with default empty value --- rkvm-server/src/config.rs | 3 ++- rkvm-server/src/server.rs | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/rkvm-server/src/config.rs b/rkvm-server/src/config.rs index 8880db5..40e56a1 100644 --- a/rkvm-server/src/config.rs +++ b/rkvm-server/src/config.rs @@ -15,7 +15,8 @@ pub struct Config { pub propagate_switch_keys: Option, pub goto_keys: Option>, - pub clients: Option>, + #[serde(default)] + pub clients: Vec, } #[derive(Deserialize)] diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 8da13ae..417d71e 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -44,7 +44,7 @@ pub async fn run( switch_keys: &HashSet, propagate_switch_keys: bool, server_goto_keys: &Option>, - clients_config: &Option>, + clients_config: &Vec, ) -> Result<(), Error> { let listener = TcpListener::bind(&listen).await.map_err(Error::Network)?; tracing::info!("Listening on {}", listen); @@ -64,17 +64,15 @@ pub async fn run( all_switch_keys.extend(keys); } - if let Some(list) = clients_config { - for c in list { - clients.insert(None); - static_client.push(c.addr); - if let Some(k) = &c.goto_keys { - let keys:Vec = k.clone().into_iter().map(Into::into).collect(); - goto_keys.insert(keys.clone(), static_client.len()); - all_switch_keys.extend(keys); - } - } - } + for c in clients_config { + clients.insert(None); + static_client.push(c.addr); + if let Some(k) = &c.goto_keys { + let keys:Vec = k.clone().into_iter().map(Into::into).collect(); + goto_keys.insert(keys.clone(), static_client.len()); + all_switch_keys.extend(keys); + } + } let (events_sender, mut events_receiver) = mpsc::channel(1); From 356cf8b42d84420ec10927f50827cc1451d6e71d Mon Sep 17 00:00:00 2001 From: Unknow Date: Fri, 16 Jan 2026 12:21:23 +0100 Subject: [PATCH 4/7] fix missing keyup event on switch --- rkvm-server/src/server.rs | 67 ++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 417d71e..8001d16 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -53,12 +53,14 @@ pub async fn run( let mut devices = Slab::::new(); let mut clients = Slab::, SocketAddr)>>::new(); let mut current = 0; + let mut previous = 0; let mut changed = false; let mut pressed_keys = HashSet::new(); let mut all_switch_keys = switch_keys.clone(); let mut static_client = Vec::new(); let mut goto_keys: HashMap,usize> = HashMap::new(); + if let Some(keys) = server_goto_keys { goto_keys.insert(keys.clone(), 0); all_switch_keys.extend(keys); @@ -257,37 +259,44 @@ pub async fn run( } // Who to send this event to. - let idx = current; + let mut idx = current; if press { - let exists = |idx| idx == 0 || clients.contains(idx - 1) && clients[idx-1].is_some(); - - for (keys,&i) in &goto_keys { - if exists(i) && keys.iter().all(|k| pressed_keys.contains(k)) { - current = i; - changed = true; - break; - } - } - - if !changed && switch_keys.is_subset(&pressed_keys) { - loop { - current = (current + 1) % (clients.len() + 1); - if exists(current) { - break; - } - } - - changed = true; - } - if changed { - if current != 0 { - tracing::info!(idx = %current, addr = %clients[current - 1].as_ref().map_or_else(|| &ADDR_UNKNOWN, |(_,a)| a), "Switched client"); - } else { - tracing::info!(idx = %current, "Switched client"); - } - - changed = false; + let exists = |idx| idx == 0 || clients.get(idx - 1).is_some_and(Option::is_some); + + // we change in previous event keyup should be send to previous + if changed { + idx = previous; + + if pressed_keys.is_empty() { + changed = false + } + } else { + for (keys,&i) in &goto_keys { + if exists(i) && keys.iter().all(|k| pressed_keys.contains(k)) { + current = i; + changed = true; + break; + } + } + + if !changed && switch_keys.is_subset(&pressed_keys) { + loop { + current = (current + 1) % (clients.len() + 1); + if exists(current) { + break; + } + } + + changed = true; + } + if changed { + if current != 0 { + tracing::info!(idx = %current, addr = %clients[current - 1].as_ref().map_or_else(|| &ADDR_UNKNOWN, |(_,a)| a), "Switched client"); + } else { + tracing::info!(idx = %current, "Switched client"); + } + } } } From 45028574ac4d5097db6e4cf4c84730863f480bab Mon Sep 17 00:00:00 2001 From: Unknow Date: Fri, 16 Jan 2026 12:22:36 +0100 Subject: [PATCH 5/7] fix indent --- rkvm-server/src/config.rs | 2 +- rkvm-server/src/server.rs | 90 +++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/rkvm-server/src/config.rs b/rkvm-server/src/config.rs index 40e56a1..e01f488 100644 --- a/rkvm-server/src/config.rs +++ b/rkvm-server/src/config.rs @@ -15,7 +15,7 @@ pub struct Config { pub propagate_switch_keys: Option, pub goto_keys: Option>, - #[serde(default)] + #[serde(default)] pub clients: Vec, } diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 8001d16..5453304 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -53,7 +53,7 @@ pub async fn run( let mut devices = Slab::::new(); let mut clients = Slab::, SocketAddr)>>::new(); let mut current = 0; - let mut previous = 0; + let mut previous = 0; let mut changed = false; let mut pressed_keys = HashSet::new(); let mut all_switch_keys = switch_keys.clone(); @@ -66,15 +66,15 @@ pub async fn run( all_switch_keys.extend(keys); } - for c in clients_config { - clients.insert(None); - static_client.push(c.addr); - if let Some(k) = &c.goto_keys { - let keys:Vec = k.clone().into_iter().map(Into::into).collect(); - goto_keys.insert(keys.clone(), static_client.len()); - all_switch_keys.extend(keys); - } - } + for c in clients_config { + clients.insert(None); + static_client.push(c.addr); + if let Some(k) = &c.goto_keys { + let keys:Vec = k.clone().into_iter().map(Into::into).collect(); + goto_keys.insert(keys.clone(), static_client.len()); + all_switch_keys.extend(keys); + } + } let (events_sender, mut events_receiver) = mpsc::channel(1); @@ -262,41 +262,41 @@ pub async fn run( let mut idx = current; if press { - let exists = |idx| idx == 0 || clients.get(idx - 1).is_some_and(Option::is_some); - - // we change in previous event keyup should be send to previous - if changed { - idx = previous; - - if pressed_keys.is_empty() { - changed = false - } - } else { - for (keys,&i) in &goto_keys { - if exists(i) && keys.iter().all(|k| pressed_keys.contains(k)) { - current = i; - changed = true; - break; - } - } - - if !changed && switch_keys.is_subset(&pressed_keys) { - loop { - current = (current + 1) % (clients.len() + 1); - if exists(current) { - break; - } - } - - changed = true; - } - if changed { - if current != 0 { - tracing::info!(idx = %current, addr = %clients[current - 1].as_ref().map_or_else(|| &ADDR_UNKNOWN, |(_,a)| a), "Switched client"); - } else { - tracing::info!(idx = %current, "Switched client"); - } - } + let exists = |idx| idx == 0 || clients.get(idx - 1).is_some_and(Option::is_some); + + // we change in previous event keyup should be send to previous + if changed { + idx = previous; + + if pressed_keys.is_empty() { + changed = false + } + } else { + for (keys,&i) in &goto_keys { + if exists(i) && keys.iter().all(|k| pressed_keys.contains(k)) { + current = i; + changed = true; + break; + } + } + + if !changed && switch_keys.is_subset(&pressed_keys) { + loop { + current = (current + 1) % (clients.len() + 1); + if exists(current) { + break; + } + } + + changed = true; + } + if changed { + if current != 0 { + tracing::info!(idx = %current, addr = %clients[current - 1].as_ref().map_or_else(|| &ADDR_UNKNOWN, |(_,a)| a), "Switched client"); + } else { + tracing::info!(idx = %current, "Switched client"); + } + } } } From 204b3ec10ec7cdee6b5fed7944bc937499998e11 Mon Sep 17 00:00:00 2001 From: Unknow Date: Fri, 16 Jan 2026 12:52:08 +0100 Subject: [PATCH 6/7] fix previous idx --- rkvm-server/src/server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 5453304..49b4b0a 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -291,6 +291,7 @@ pub async fn run( changed = true; } if changed { + previous = idx; if current != 0 { tracing::info!(idx = %current, addr = %clients[current - 1].as_ref().map_or_else(|| &ADDR_UNKNOWN, |(_,a)| a), "Switched client"); } else { From c6c84120edbe47b08308914298fe9a56dcb336b0 Mon Sep 17 00:00:00 2001 From: Unknow Date: Sun, 22 Feb 2026 21:40:20 +0100 Subject: [PATCH 7/7] invalid back to server --- rkvm-server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 49b4b0a..d804cef 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -105,7 +105,7 @@ pub async fn run( } } ); - if !clients.contains(current) || clients[current].is_none() { + if current > 0 && (!clients.contains(current) || clients[current].is_none()) { current = 0; }