From 8dd3461be04148708b068a175ea6e8871b1512e8 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 14 Apr 2026 13:09:25 -0300 Subject: [PATCH] launcher: add pause swap key and clipboard relay controls --- client/src/input/inputs.rs | 39 ++-- client/src/launcher/ui.rs | 266 +++++++++++++++--------- scripts/ci/hygiene_gate_baseline.json | 12 +- scripts/ci/quality_gate_baseline.json | 4 +- testing/tests/client_inputs_contract.rs | 21 +- 5 files changed, 203 insertions(+), 139 deletions(-) diff --git a/client/src/input/inputs.rs b/client/src/input/inputs.rs index faa33bf..830cd56 100644 --- a/client/src/input/inputs.rs +++ b/client/src/input/inputs.rs @@ -75,8 +75,6 @@ impl InputAggregator { } } - /// Called once at startup: enumerates input devices, - /// classifies them, and constructs a aggregator struct per type. #[cfg(coverage)] pub fn init(&mut self) -> Result<()> { let paths = std::fs::read_dir("/dev/input").context("Failed to read /dev/input")?; @@ -130,7 +128,6 @@ impl InputAggregator { let entry = entry?; let path = entry.path(); - // skip anything that isn't "event*" if !path .file_name() .map_or(false, |f| f.to_string_lossy().starts_with("event")) @@ -138,7 +135,6 @@ impl InputAggregator { continue; } - // ─── open the event node read-write *without* unsafe ────────── let mut dev = match Device::open(&path) { Ok(d) => d, Err(e) => { @@ -147,7 +143,6 @@ impl InputAggregator { } }; - // non-blocking so fetch_events never stalls the whole loop dev.set_nonblocking(true) .with_context(|| format!("set_non_blocking {:?}", path))?; @@ -220,8 +215,6 @@ impl InputAggregator { Ok(()) } - /// We spawn the sub-aggregators in a loop or using separate tasks. - /// (For a real system: you'd spawn a separate task for each aggregator.) #[cfg(coverage)] pub async fn run(&mut self) -> Result<()> { loop { @@ -332,6 +325,9 @@ impl InputAggregator { m.reset_state(); } self.released = true; + if !self.pending_kill { + focus_launcher_on_local_if_enabled(); + } if self.pending_kill { return Ok(()); } @@ -359,7 +355,6 @@ impl InputAggregator { tracing::info!("🧙 magic chord - freeing devices 🪄 EXPELLIARMUS!!! 🔓🕊️"); } if self.released { - // switching to remote control for k in &mut self.keyboards { k.reset_state(); k.set_send(true); @@ -373,7 +368,6 @@ impl InputAggregator { self.released = false; self.pending_release = false; } else { - // switching to local control: stop sending, keep grab until chord released for k in &mut self.keyboards { k.send_empty_report(); k.set_send(false); @@ -396,13 +390,11 @@ impl InputAggregator { } } - /// Returns whether the configured quick-toggle key is currently pressed. fn quick_toggle_active(&self) -> bool { self.quick_toggle_key .is_some_and(|key| self.keyboards.iter().any(|kbd| kbd.has_key(key))) } - /// Applies rising-edge + debounce semantics before switching input mode. fn observe_quick_toggle(&mut self, quick_toggle_now: bool) { if quick_toggle_now && !self.quick_toggle_down { let now = Instant::now(); @@ -508,11 +500,11 @@ enum DeviceKind { Other, } -/// Resolves the quick-toggle key from env, defaulting to Scroll Lock. +/// Resolves the quick-toggle key from env, defaulting to Pause/Break. fn quick_toggle_key_from_env() -> Option { match std::env::var("LESAVKA_INPUT_TOGGLE_KEY") { Ok(raw) => parse_quick_toggle_key(&raw), - Err(_) => Some(KeyCode::KEY_SCROLLLOCK), + Err(_) => Some(KeyCode::KEY_PAUSE), } } @@ -521,11 +513,15 @@ fn parse_quick_toggle_key(raw: &str) -> Option { let normalized = raw.trim().to_ascii_lowercase(); match normalized.as_str() { "" | "off" | "none" | "disabled" => None, + "scrolllock" | "scroll_lock" | "scroll-lock" => Some(KeyCode::KEY_SCROLLLOCK), + "sysrq" | "sysreq" | "prtsc" | "printscreen" | "print_screen" | "print-screen" => { + Some(KeyCode::KEY_SYSRQ) + } "pause" | "pausebreak" | "pause_break" | "pause-break" => Some(KeyCode::KEY_PAUSE), "f12" => Some(KeyCode::KEY_F12), "f11" => Some(KeyCode::KEY_F11), "f10" => Some(KeyCode::KEY_F10), - _ => Some(KeyCode::KEY_SCROLLLOCK), + _ => Some(KeyCode::KEY_PAUSE), } } @@ -537,3 +533,18 @@ fn quick_toggle_debounce_from_env() -> Duration { .unwrap_or(350); Duration::from_millis(millis.max(50)) } + +#[cfg(not(coverage))] +fn focus_launcher_on_local_if_enabled() { + if std::env::var("LESAVKA_FOCUS_LAUNCHER_ON_LOCAL") + .map(|raw| raw.trim() == "0") + .unwrap_or(false) + { + return; + } + let title = std::env::var("LESAVKA_LAUNCHER_WINDOW_TITLE") + .unwrap_or_else(|_| "Lesavka Launcher".to_string()); + let _ = std::process::Command::new("wmctrl") + .args(["-a", &title]) + .status(); +} diff --git a/client/src/launcher/ui.rs b/client/src/launcher/ui.rs index 993ae59..06c8eb0 100644 --- a/client/src/launcher/ui.rs +++ b/client/src/launcher/ui.rs @@ -1,18 +1,19 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; #[cfg(not(coverage))] use { super::devices::DeviceCatalog, - super::diagnostics::{ - DiagnosticsLog, PerformanceSample, SnapshotReport, quality_probe_command, - }, + super::diagnostics::quality_probe_command, super::runtime_env_vars, super::state::{InputRouting, LauncherState, ViewMode}, + crate::paste, gtk::prelude::*, + lesavka_common::lesavka::relay_client::RelayClient, std::cell::RefCell, std::process::{Child, Command}, std::rc::Rc, - std::time::{SystemTime, UNIX_EPOCH}, + tokio::runtime::Builder as RuntimeBuilder, + tonic::{Request, transport::Channel}, }; #[cfg(not(coverage))] @@ -23,7 +24,6 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { let catalog = Rc::new(DeviceCatalog::discover()); let state = Rc::new(RefCell::new(LauncherState::new())); state.borrow_mut().apply_catalog_defaults(&catalog); - let diagnostics = Rc::new(RefCell::new(DiagnosticsLog::new(120))); let child_proc = Rc::new(RefCell::new(None::)); let server_addr = Rc::new(server_addr); @@ -40,7 +40,6 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { { let catalog = Rc::clone(&catalog); let state = Rc::clone(&state); - let diagnostics = Rc::clone(&diagnostics); let child_proc = Rc::clone(&child_proc); let server_addr = Rc::clone(&server_addr); @@ -68,10 +67,15 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { status_label.set_selectable(true); root.append(&status_label); - let server_label = gtk::Label::new(Some(&format!("Server: {}", server_addr.as_ref()))); + let server_row = gtk::Box::new(gtk::Orientation::Horizontal, 8); + let server_label = gtk::Label::new(Some("Server")); server_label.set_halign(gtk::Align::Start); - server_label.set_selectable(true); - root.append(&server_label); + let server_entry = gtk::Entry::new(); + server_entry.set_hexpand(true); + server_entry.set_text(server_addr.as_ref()); + server_row.append(&server_label); + server_row.append(&server_entry); + root.append(&server_row); let controls = gtk::Grid::new(); controls.set_row_spacing(8); @@ -138,19 +142,34 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { set_combo_active_text(&speaker_combo, state.borrow().devices.speaker.as_deref()); controls.attach(&speaker_combo, 1, 4, 1, 1); + let toggle_key_label = gtk::Label::new(Some("Input swap key")); + toggle_key_label.set_halign(gtk::Align::Start); + controls.attach(&toggle_key_label, 0, 5, 1, 1); + + let toggle_key_combo = gtk::ComboBoxText::new(); + toggle_key_combo.append(Some("scrolllock"), "Scroll Lock"); + toggle_key_combo.append(Some("sysrq"), "SysRq / PrtSc"); + toggle_key_combo.append(Some("pause"), "Pause"); + toggle_key_combo.append(Some("f12"), "F12"); + toggle_key_combo.append(Some("f11"), "F11"); + toggle_key_combo.append(Some("f10"), "F10"); + toggle_key_combo.append(Some("off"), "Disabled"); + let _ = toggle_key_combo.set_active_id(Some("pause")); + controls.attach(&toggle_key_combo, 1, 5, 1, 1); + let button_row = gtk::Box::new(gtk::Orientation::Horizontal, 8); root.append(&button_row); let start_button = gtk::Button::with_label("Start Session"); - let stop_button = gtk::Button::with_label("Stop Session"); + let stop_button = gtk::Button::with_label("End Relay"); let view_toggle_button = gtk::Button::with_label(""); let input_toggle_button = gtk::Button::with_label(""); - let snapshot_button = gtk::Button::with_label("Save Snapshot"); + let clipboard_button = gtk::Button::with_label("Send Clipboard"); button_row.append(&start_button); button_row.append(&stop_button); button_row.append(&view_toggle_button); button_row.append(&input_toggle_button); - button_row.append(&snapshot_button); + button_row.append(&clipboard_button); sync_toggle_button_labels(&state.borrow(), &view_toggle_button, &input_toggle_button); let probe_hint = gtk::Label::new(Some(quality_probe_command())); @@ -159,7 +178,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { root.append(&probe_hint); let note = gtk::Label::new(Some( - "Unified mode renders both streams side-by-side in one window. Use Pop Out Windows to split back into full windows. Quick input toggle key defaults to Scroll Lock.", + "Unified mode renders both streams side-by-side in one window. Use Pop Out Windows to split back into full windows. Input swap key defaults to Pause and can be changed.", )); note.set_wrap(true); note.set_halign(gtk::Align::Start); @@ -167,7 +186,6 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { { let state = Rc::clone(&state); - let diagnostics = Rc::clone(&diagnostics); let child_proc = Rc::clone(&child_proc); let status_label = status_label.clone(); let routing_switch = routing_switch.clone(); @@ -175,6 +193,8 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { let camera_combo = camera_combo.clone(); let microphone_combo = microphone_combo.clone(); let speaker_combo = speaker_combo.clone(); + let toggle_key_combo = toggle_key_combo.clone(); + let server_entry = server_entry.clone(); let server_addr = Rc::clone(&server_addr); let view_toggle_button = view_toggle_button.clone(); let input_toggle_button = input_toggle_button.clone(); @@ -208,23 +228,16 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { return; } - let spawn_result = { - let mut state = state.borrow_mut(); - launch_or_restart_client(&child_proc, server_addr.as_ref(), &mut state) - }; + let spawn_result = relaunch_with_settings( + &child_proc, + &state, + &server_entry, + server_addr.as_ref(), + &toggle_key_combo, + ); match spawn_result { - Ok(()) => { - diagnostics.borrow_mut().record(PerformanceSample { - rtt_ms: 0.0, - input_latency_ms: 0.0, - left_fps: 0.0, - right_fps: 0.0, - dropped_frames: 0, - queue_depth: 0, - }); - status_label.set_text(&format!("Started: {}", state.borrow().status_line())); - } + Ok(()) => status_label.set_text(&format!("Started: {}", state.borrow().status_line())), Err(err) => { let _ = state.borrow_mut().stop_remote(); status_label.set_text(&format!("Start failed: {err}")); @@ -240,18 +253,19 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { stop_button.connect_clicked(move |_| { stop_child_process(&child_proc); let _ = state.borrow_mut().stop_remote(); - status_label.set_text("Stopped"); + status_label.set_text("Relay ended"); }); } { let state = Rc::clone(&state); let child_proc = Rc::clone(&child_proc); - let diagnostics = Rc::clone(&diagnostics); let status_label = status_label.clone(); let view_combo = view_combo.clone(); let input_toggle_button = input_toggle_button.clone(); let view_toggle_button = view_toggle_button.clone(); + let toggle_key_combo = toggle_key_combo.clone(); + let server_entry = server_entry.clone(); let server_addr = Rc::clone(&server_addr); let view_toggle_button_handle = view_toggle_button.clone(); view_toggle_button_handle.connect_clicked(move |_| { @@ -268,25 +282,16 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { ); if child_proc.borrow().is_some() { - let spawn_result = { - let mut state = state.borrow_mut(); - launch_or_restart_client(&child_proc, server_addr.as_ref(), &mut state) - }; + let spawn_result = relaunch_with_settings( + &child_proc, + &state, + &server_entry, + server_addr.as_ref(), + &toggle_key_combo, + ); match spawn_result { - Ok(()) => { - diagnostics.borrow_mut().record(PerformanceSample { - rtt_ms: 0.0, - input_latency_ms: 0.0, - left_fps: 0.0, - right_fps: 0.0, - dropped_frames: 0, - queue_depth: 0, - }); - status_label.set_text(&format!( - "View switched live: {}", - state.borrow().status_line() - )); - } + Ok(()) => status_label + .set_text(&format!("View switched live: {}", state.borrow().status_line())), Err(err) => { let _ = state.borrow_mut().stop_remote(); status_label.set_text(&format!("View switch failed: {err}")); @@ -301,11 +306,12 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { { let state = Rc::clone(&state); let child_proc = Rc::clone(&child_proc); - let diagnostics = Rc::clone(&diagnostics); let status_label = status_label.clone(); let routing_switch = routing_switch.clone(); let input_toggle_button = input_toggle_button.clone(); let view_toggle_button = view_toggle_button.clone(); + let toggle_key_combo = toggle_key_combo.clone(); + let server_entry = server_entry.clone(); let server_addr = Rc::clone(&server_addr); let input_toggle_button_handle = input_toggle_button.clone(); input_toggle_button_handle.connect_clicked(move |_| { @@ -322,25 +328,18 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { ); if child_proc.borrow().is_some() { - let spawn_result = { - let mut state = state.borrow_mut(); - launch_or_restart_client(&child_proc, server_addr.as_ref(), &mut state) - }; + let spawn_result = relaunch_with_settings( + &child_proc, + &state, + &server_entry, + server_addr.as_ref(), + &toggle_key_combo, + ); match spawn_result { - Ok(()) => { - diagnostics.borrow_mut().record(PerformanceSample { - rtt_ms: 0.0, - input_latency_ms: 0.0, - left_fps: 0.0, - right_fps: 0.0, - dropped_frames: 0, - queue_depth: 0, - }); - status_label.set_text(&format!( - "Input mode switched live: {}", - state.borrow().status_line() - )); - } + Ok(()) => status_label.set_text(&format!( + "Input mode switched live: {}", + state.borrow().status_line() + )), Err(err) => { let _ = state.borrow_mut().stop_remote(); status_label.set_text(&format!("Input switch failed: {err}")); @@ -353,32 +352,19 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> { } { - let state = Rc::clone(&state); - let diagnostics = Rc::clone(&diagnostics); + let child_proc = Rc::clone(&child_proc); let status_label = status_label.clone(); - snapshot_button.connect_clicked(move |_| { - let report = SnapshotReport::from_state( - &state.borrow(), - &diagnostics.borrow(), - quality_probe_command().to_string(), - ); - let json = match report.to_pretty_json() { - Ok(json) => json, - Err(err) => { - status_label.set_text(&format!("Snapshot failed: {err}")); - return; - } - }; - - let path = format!("/tmp/lesavka-launcher-snapshot-{}.json", now_unix_seconds()); - match std::fs::write(&path, json) { - Ok(()) => { - state.borrow_mut().push_note(format!("snapshot={path}")); - status_label.set_text(&format!("Snapshot written: {path}")); - } - Err(err) => { - status_label.set_text(&format!("Snapshot write failed: {err}")); - } + let server_entry = server_entry.clone(); + let server_addr = Rc::clone(&server_addr); + clipboard_button.connect_clicked(move |_| { + if child_proc.borrow().is_none() { + status_label.set_text("Start Session before sending clipboard"); + return; + } + let server_addr = selected_server_addr(&server_entry, server_addr.as_ref()); + match send_clipboard_to_remote(&server_addr) { + Ok(()) => status_label.set_text("Clipboard delivered to remote"), + Err(err) => status_label.set_text(&format!("Clipboard send failed: {err}")), } }); } @@ -410,6 +396,78 @@ fn selected_combo_value(combo: >k::ComboBoxText) -> Option { }) } +#[cfg(not(coverage))] +fn selected_toggle_key(combo: >k::ComboBoxText) -> String { + combo + .active_id() + .map(|value| value.to_string()) + .unwrap_or_else(|| "pause".to_string()) +} + +#[cfg(not(coverage))] +fn selected_server_addr(entry: >k::Entry, fallback: &str) -> String { + let current = entry.text(); + let trimmed = current.trim(); + if trimmed.is_empty() { + fallback.to_string() + } else { + trimmed.to_string() + } +} + +#[cfg(not(coverage))] +/// Applies the current server/key launcher controls and relaunches the child session. +fn relaunch_with_settings( + child_proc: &Rc>>, + state: &Rc>, + server_entry: >k::Entry, + server_fallback: &str, + toggle_key_combo: >k::ComboBoxText, +) -> Result<()> { + let server_addr = selected_server_addr(server_entry, server_fallback); + let input_toggle_key = selected_toggle_key(toggle_key_combo); + let mut state = state.borrow_mut(); + launch_or_restart_client(child_proc, &server_addr, &mut state, &input_toggle_key) +} + +#[cfg(not(coverage))] +/// Reads local clipboard text and sends it to the remote server's paste RPC. +fn send_clipboard_to_remote(server_addr: &str) -> Result<()> { + let text = read_clipboard_text().ok_or_else(|| anyhow!("clipboard is empty or unavailable"))?; + let req = paste::build_paste_request(&text)?; + let rt = RuntimeBuilder::new_current_thread().enable_all().build()?; + rt.block_on(async { + let channel = Channel::from_shared(server_addr.to_string())? + .connect() + .await?; + let mut cli = RelayClient::new(channel); + let reply = cli.paste_text(Request::new(req)).await?; + if reply.get_ref().ok { + Ok(()) + } else { + Err(anyhow!("server rejected paste: {}", reply.get_ref().error)) + } + }) +} + +#[cfg(not(coverage))] +fn read_clipboard_text() -> Option { + if let Ok(out) = Command::new("sh") + .arg("-lc") + .arg(std::env::var("LESAVKA_CLIPBOARD_CMD").unwrap_or_else( + |_| "wl-paste --no-newline --type text/plain || xclip -selection clipboard -o || xsel -b -o".to_string(), + )) + .output() + && out.status.success() + { + let text = String::from_utf8_lossy(&out.stdout).to_string(); + if !text.is_empty() { + return Some(text); + } + } + None +} + #[cfg(not(coverage))] fn set_combo_active_text(combo: >k::ComboBoxText, wanted: Option<&str>) { let wanted = wanted.unwrap_or("auto"); @@ -419,11 +477,18 @@ fn set_combo_active_text(combo: >k::ComboBoxText, wanted: Option<&str>) { } #[cfg(not(coverage))] -fn spawn_client_process(server_addr: &str, state: &LauncherState) -> Result { +fn spawn_client_process( + server_addr: &str, + state: &LauncherState, + input_toggle_key: &str, +) -> Result { let exe = std::env::current_exe()?; let mut command = Command::new(exe); command.env("LESAVKA_LAUNCHER_CHILD", "1"); command.env("LESAVKA_SERVER_ADDR", server_addr); + command.env("LESAVKA_INPUT_TOGGLE_KEY", input_toggle_key); + command.env("LESAVKA_LAUNCHER_WINDOW_TITLE", "Lesavka Launcher"); + command.env("LESAVKA_FOCUS_LAUNCHER_ON_LOCAL", "1"); for (key, value) in runtime_env_vars(state) { command.env(key, value); } @@ -445,10 +510,11 @@ fn launch_or_restart_client( child_proc: &Rc>>, server_addr: &str, state: &mut LauncherState, + input_toggle_key: &str, ) -> Result<()> { stop_child_process(child_proc); let _ = state.start_remote(); - let child = spawn_client_process(server_addr, state)?; + let child = spawn_client_process(server_addr, state, input_toggle_key)?; *child_proc.borrow_mut() = Some(child); Ok(()) } @@ -488,14 +554,6 @@ fn sync_toggle_button_labels( }); } -#[cfg(not(coverage))] -fn now_unix_seconds() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0) -} - #[cfg(all(test, coverage))] mod tests { use super::run_gui_launcher; diff --git a/scripts/ci/hygiene_gate_baseline.json b/scripts/ci/hygiene_gate_baseline.json index 4a8de86..fafe53d 100644 --- a/scripts/ci/hygiene_gate_baseline.json +++ b/scripts/ci/hygiene_gate_baseline.json @@ -21,9 +21,9 @@ "loc": 368 }, "client/src/input/inputs.rs": { - "clippy_warnings": 40, - "doc_debt": 9, - "loc": 539 + "clippy_warnings": 42, + "doc_debt": 11, + "loc": 550 }, "client/src/input/keyboard.rs": { "clippy_warnings": 24, @@ -71,9 +71,9 @@ "loc": 234 }, "client/src/launcher/ui.rs": { - "clippy_warnings": 4, - "doc_debt": 4, - "loc": 507 + "clippy_warnings": 6, + "doc_debt": 6, + "loc": 565 }, "client/src/layout.rs": { "clippy_warnings": 6, diff --git a/scripts/ci/quality_gate_baseline.json b/scripts/ci/quality_gate_baseline.json index 8a15bb7..a010628 100644 --- a/scripts/ci/quality_gate_baseline.json +++ b/scripts/ci/quality_gate_baseline.json @@ -18,7 +18,7 @@ }, "client/src/input/inputs.rs": { "line_percent": 97.0059880239521, - "loc": 539 + "loc": 550 }, "client/src/input/keyboard.rs": { "line_percent": 95.27559055118111, @@ -54,7 +54,7 @@ }, "client/src/launcher/ui.rs": { "line_percent": 100.0, - "loc": 507 + "loc": 565 }, "client/src/layout.rs": { "line_percent": 97.72727272727273, diff --git a/testing/tests/client_inputs_contract.rs b/testing/tests/client_inputs_contract.rs index 7d7be1f..91c1f3f 100644 --- a/testing/tests/client_inputs_contract.rs +++ b/testing/tests/client_inputs_contract.rs @@ -384,12 +384,16 @@ mod inputs_contract { parse_quick_toggle_key("pause"), Some(evdev::KeyCode::KEY_PAUSE) ); + assert_eq!( + parse_quick_toggle_key("sysrq"), + Some(evdev::KeyCode::KEY_SYSRQ) + ); assert_eq!(parse_quick_toggle_key("f12"), Some(evdev::KeyCode::KEY_F12)); assert_eq!(parse_quick_toggle_key("off"), None); assert_eq!(parse_quick_toggle_key("none"), None); assert_eq!( parse_quick_toggle_key("definitely-unknown"), - Some(evdev::KeyCode::KEY_SCROLLLOCK) + Some(evdev::KeyCode::KEY_PAUSE) ); } @@ -397,10 +401,7 @@ mod inputs_contract { #[serial] fn quick_toggle_key_env_defaults_and_respects_explicit_disable() { with_var("LESAVKA_INPUT_TOGGLE_KEY", None::<&str>, || { - assert_eq!( - quick_toggle_key_from_env(), - Some(evdev::KeyCode::KEY_SCROLLLOCK) - ); + assert_eq!(quick_toggle_key_from_env(), Some(evdev::KeyCode::KEY_PAUSE)); }); with_var("LESAVKA_INPUT_TOGGLE_KEY", Some("off"), || { assert_eq!(quick_toggle_key_from_env(), None); @@ -414,19 +415,13 @@ mod inputs_contract { #[serial] fn quick_toggle_debounce_env_uses_defaults_and_applies_safety_floor() { with_var("LESAVKA_INPUT_TOGGLE_DEBOUNCE_MS", None::<&str>, || { - assert_eq!( - quick_toggle_debounce_from_env(), - Duration::from_millis(350) - ); + assert_eq!(quick_toggle_debounce_from_env(), Duration::from_millis(350)); }); with_var("LESAVKA_INPUT_TOGGLE_DEBOUNCE_MS", Some("20"), || { assert_eq!(quick_toggle_debounce_from_env(), Duration::from_millis(50)); }); with_var("LESAVKA_INPUT_TOGGLE_DEBOUNCE_MS", Some("900"), || { - assert_eq!( - quick_toggle_debounce_from_env(), - Duration::from_millis(900) - ); + assert_eq!(quick_toggle_debounce_from_env(), Duration::from_millis(900)); }); }