-
-
Notifications
You must be signed in to change notification settings - Fork 200
Feat/linked host switching #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e9ff5e3
66fdb0b
b11c0c8
b3694b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| //! Keep configured keyboard → pointing-device host-switch links armed. | ||
|
|
||
| use std::sync::{Arc, RwLock}; | ||
| use std::thread; | ||
| use std::time::Duration; | ||
|
|
||
| use openlogi_hid::{DeviceRoute, run_host_switch_session}; | ||
| use tokio::sync::{mpsc, oneshot}; | ||
| use tracing::{debug, warn}; | ||
|
|
||
| /// One resolved link. Config keys are converted to live routes by the | ||
| /// orchestrator so the transport watcher never needs to understand inventory. | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub struct HostSwitchLink { | ||
| /// Keyboard whose host switch keys initiate the transition. | ||
| pub keyboard: DeviceRoute, | ||
| /// Pointing devices that follow the keyboard. | ||
| pub targets: Vec<DeviceRoute>, | ||
| } | ||
|
|
||
| /// Shared resolved links, refreshed with config and inventory. | ||
| pub type HostSwitchLinks = Arc<RwLock<Vec<HostSwitchLink>>>; | ||
|
|
||
| /// Spawn the host switch session manager. | ||
| pub fn spawn(links: HostSwitchLinks) { | ||
| thread::spawn(move || { | ||
| let runtime = match tokio::runtime::Builder::new_current_thread() | ||
| .enable_all() | ||
| .build() | ||
| { | ||
| Ok(runtime) => runtime, | ||
| Err(error) => { | ||
| warn!(%error, "host switch watcher: could not build tokio runtime"); | ||
| return; | ||
| } | ||
| }; | ||
| runtime.block_on(manage(links)); | ||
| }); | ||
| } | ||
|
|
||
| async fn manage(links: HostSwitchLinks) { | ||
| let mut current = Vec::new(); | ||
| let mut stops = Vec::new(); | ||
| let (done_tx, mut done_rx) = mpsc::unbounded_channel(); | ||
| let mut ticker = tokio::time::interval(Duration::from_secs(1)); | ||
|
|
||
| loop { | ||
| tokio::select! { | ||
| _ = ticker.tick() => { | ||
| let wanted = links.read().map_or_else(|_| Vec::new(), |guard| guard.clone()); | ||
| if wanted == current { | ||
| continue; | ||
| } | ||
| stop_all(&mut stops); | ||
| current.clone_from(&wanted); | ||
| for link in wanted { | ||
| let (stop_tx, stop_rx) = oneshot::channel(); | ||
| stops.push(stop_tx); | ||
| let done = done_tx.clone(); | ||
| tokio::spawn(async move { | ||
| let keyboard = link.keyboard.clone(); | ||
| if let Err(error) = | ||
| run_host_switch_session(link.keyboard, link.targets, stop_rx).await | ||
| { | ||
| debug!(%error, route = %keyboard, "host switch session ended"); | ||
| } | ||
| let _ = done.send(()); | ||
| }); | ||
| } | ||
| } | ||
| Some(()) = done_rx.recv() => { | ||
| // A host switch intentionally disconnects the keyboard. Clear | ||
| // the local snapshot so the next tick attempts to arm it again; | ||
| // this also recovers transient setup/read failures. | ||
| stop_all(&mut stops); | ||
| current.clear(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn stop_all(stops: &mut Vec<oneshot::Sender<()>>) { | ||
| for stop in stops.drain(..) { | ||
| let _ = stop.send(()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -163,6 +163,7 @@ async fn run(config: Config) { | |
| shared.thumbwheel_sensitivity.clone(), | ||
| shared.receiver_access.clone(), | ||
| ); | ||
| watchers::host_switch::spawn(shared.host_switch_links.clone()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a configured Bolt or Unifying receiver is already used by gesture capture or pairing, this watcher opens another channel without participating in Knowledge Base Used: |
||
|
|
||
| let mut inventory_rx = watchers::inventory::spawn(Duration::from_secs(2)); | ||
| let mut app_rx = watchers::foreground_app::spawn(Duration::from_secs(1)); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a link change stops existing sessions and starts replacements, the stopped tasks later send indistinguishable
()completions; the receive branch then stops the replacement sessions and clears their state, repeatedly leaving host keys unmonitored between re-arm attempts.Knowledge Base Used: openlogi-agent-core