fix(server): survive unattached HID gadget startup

This commit is contained in:
Brad Stein 2026-04-21 13:08:20 -03:00
parent b8e43cac6f
commit c65fcd1137
12 changed files with 174 additions and 83 deletions

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.11.34"
version = "0.11.35"
edition = "2024"
[dependencies]

View File

@ -182,7 +182,8 @@ pub fn build_launcher_view(
let operations = gtk::Box::new(gtk::Orientation::Vertical, 8);
operations.set_size_request(OPERATIONS_RAIL_WIDTH, -1);
operations.set_hexpand(false);
operations.set_vexpand(true);
operations.set_vexpand(false);
operations.set_valign(gtk::Align::Start);
content.append(&operations);
let display_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
@ -320,14 +321,14 @@ pub fn build_launcher_view(
let (preview_panel, preview_body) = build_panel("Device Testing");
preview_panel.set_hexpand(true);
preview_panel.set_vexpand(false);
preview_panel.set_vexpand(true);
preview_panel.set_valign(gtk::Align::Fill);
preview_body.set_vexpand(false);
preview_body.set_vexpand(true);
preview_body.set_spacing(8);
let testing_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
testing_row.set_hexpand(true);
testing_row.set_vexpand(false);
testing_row.set_valign(gtk::Align::Start);
testing_row.set_vexpand(true);
testing_row.set_valign(gtk::Align::Fill);
let camera_preview = gtk::Picture::new();
camera_preview.set_can_shrink(false);
camera_preview.set_hexpand(false);
@ -363,15 +364,15 @@ pub fn build_launcher_view(
camera_preview_shell.append(&camera_preview_frame);
let webcam_group = build_subgroup("Webcam Preview");
webcam_group.set_hexpand(true);
webcam_group.set_vexpand(false);
webcam_group.set_valign(gtk::Align::Start);
webcam_group.set_vexpand(true);
webcam_group.set_valign(gtk::Align::Fill);
webcam_group.append(&camera_preview_shell);
testing_row.append(&webcam_group);
let playback_group = build_subgroup("Mic Playback");
playback_group.set_hexpand(false);
playback_group.set_vexpand(false);
playback_group.set_valign(gtk::Align::Start);
playback_group.set_vexpand(true);
playback_group.set_valign(gtk::Align::Fill);
playback_group.set_size_request(72, -1);
let playback_body = gtk::Box::new(gtk::Orientation::Vertical, 6);
playback_body.set_halign(gtk::Align::Center);
@ -512,7 +513,8 @@ pub fn build_launcher_view(
let diagnostics_scroll = gtk::ScrolledWindow::builder()
.hexpand(true)
.vexpand(false)
.min_content_height(190)
.min_content_height(150)
.max_content_height(150)
.child(&diagnostics_shell)
.build();
diagnostics_body.append(&diagnostics_toolbar);
@ -520,7 +522,7 @@ pub fn build_launcher_view(
operations.append(&diagnostics_panel);
let (console_panel, console_body) = build_panel("Session Console");
console_panel.set_vexpand(true);
console_panel.set_vexpand(false);
let console_toolbar = gtk::Box::new(gtk::Orientation::Horizontal, 8);
console_toolbar.set_homogeneous(true);
let console_copy_button = gtk::Button::with_label("Copy Log");
@ -550,8 +552,9 @@ pub fn build_launcher_view(
session_log_view.set_wrap_mode(gtk::WrapMode::WordChar);
let log_scroll = gtk::ScrolledWindow::builder()
.hexpand(true)
.vexpand(true)
.min_content_height(220)
.vexpand(false)
.min_content_height(150)
.max_content_height(150)
.child(&session_log_view)
.build();
console_body.append(&console_toolbar);

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.11.34"
version = "0.11.35"
edition = "2024"
build = "build.rs"

View File

@ -17,6 +17,6 @@ mod tests {
#[test]
fn banner_includes_version() {
assert_eq!(banner("0.11.34"), "lesavka-common CLI (v0.11.34)");
assert_eq!(banner("0.11.35"), "lesavka-common CLI (v0.11.35)");
}
}

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.11.34"
version = "0.11.35"
edition = "2024"
autobins = false

View File

@ -51,8 +51,8 @@ fn live_keyboard_report_delay() -> Duration {
/*──────────────── Handler ───────────────────*/
struct Handler {
kb: Arc<Mutex<tokio::fs::File>>,
ms: Arc<Mutex<tokio::fs::File>>,
kb: Arc<Mutex<Option<tokio::fs::File>>>,
ms: Arc<Mutex<Option<tokio::fs::File>>>,
gadget: UsbGadget,
did_cycle: Arc<AtomicBool>,
camera_rt: Arc<CameraRuntime>,
@ -91,10 +91,16 @@ impl Handler {
}
info!("🛠️ opening HID endpoints …");
}
let kb = runtime_support::open_with_retry(&hid_endpoint(0)).await?;
let ms = runtime_support::open_with_retry(&hid_endpoint(1)).await?;
let kb_path = hid_endpoint(0);
let ms_path = hid_endpoint(1);
let kb = runtime_support::open_hid_if_ready(&kb_path).await?;
let ms = runtime_support::open_hid_if_ready(&ms_path).await?;
#[cfg(not(coverage))]
if kb.is_some() && ms.is_some() {
info!("✅ HID endpoints ready");
} else {
warn!("⌛ HID endpoints are not ready; relay will keep running and open them lazily");
}
Ok(Self {
kb: Arc::new(Mutex::new(kb)),
@ -108,8 +114,8 @@ impl Handler {
}
async fn reopen_hid(&self) -> anyhow::Result<()> {
let kb_new = runtime_support::open_with_retry(&hid_endpoint(0)).await?;
let ms_new = runtime_support::open_with_retry(&hid_endpoint(1)).await?;
let kb_new = runtime_support::open_hid_if_ready(&hid_endpoint(0)).await?;
let ms_new = runtime_support::open_hid_if_ready(&hid_endpoint(1)).await?;
*self.kb.lock().await = kb_new;
*self.ms.lock().await = ms_new;
Ok(())
@ -311,7 +317,7 @@ impl Handler {
) -> Result<Response<PasteReply>, Status> {
let req = req.into_inner();
let text = paste::decrypt(&req).map_err(|e| Status::unauthenticated(format!("{e}")))?;
if let Err(e) = paste::type_text(self.kb.as_ref(), &text).await {
if let Err(e) = paste::type_text(&self.kb, &hid_endpoint(0), &text).await {
return Ok(Response::new(PasteReply {
ok: false,
error: format!("{e}"),
@ -528,6 +534,8 @@ impl Relay for Handler {
let (tx, rx) = tokio::sync::mpsc::channel(32);
let kb = self.kb.clone();
let ms = self.ms.clone();
let kb_path = hid_endpoint(0);
let ms_path = hid_endpoint(1);
let gadget = self.gadget.clone();
let did_cycle = self.did_cycle.clone();
let session_lease = self.capture_power.acquire_session().await;
@ -537,7 +545,7 @@ impl Relay for Handler {
let _session_lease = session_lease;
let mut s = req.into_inner();
while let Some(pkt) = s.next().await.transpose()? {
if let Err(e) = runtime_support::write_hid_report(&kb, &pkt.data).await {
if let Err(e) = runtime_support::write_hid_report(&kb, &kb_path, &pkt.data).await {
if e.raw_os_error() == Some(libc::EAGAIN) {
debug!(rpc_id, "⌨️ write would block (dropped)");
} else {
@ -547,6 +555,8 @@ impl Relay for Handler {
gadget.clone(),
kb.clone(),
ms.clone(),
kb_path.clone(),
ms_path.clone(),
did_cycle.clone(),
)
.await;
@ -573,6 +583,8 @@ impl Relay for Handler {
let (tx, rx) = tokio::sync::mpsc::channel(1024);
let ms = self.ms.clone();
let kb = self.kb.clone();
let kb_path = hid_endpoint(0);
let ms_path = hid_endpoint(1);
let gadget = self.gadget.clone();
let did_cycle = self.did_cycle.clone();
let session_lease = self.capture_power.acquire_session().await;
@ -581,7 +593,7 @@ impl Relay for Handler {
let _session_lease = session_lease;
let mut s = req.into_inner();
while let Some(pkt) = s.next().await.transpose()? {
if let Err(e) = runtime_support::write_hid_report(&ms, &pkt.data).await {
if let Err(e) = runtime_support::write_hid_report(&ms, &ms_path, &pkt.data).await {
if e.raw_os_error() == Some(libc::EAGAIN) {
debug!(rpc_id, "🖱️ write would block (dropped)");
} else {
@ -591,6 +603,8 @@ impl Relay for Handler {
gadget.clone(),
kb.clone(),
ms.clone(),
kb_path.clone(),
ms_path.clone(),
did_cycle.clone(),
)
.await;
@ -753,7 +767,7 @@ impl Relay for Handler {
tokio::spawn(async move {
let mut s = req.into_inner();
while let Some(pkt) = s.next().await.transpose()? {
let _ = runtime_support::write_hid_report(&kb, &pkt.data).await;
let _ = runtime_support::write_hid_report(&kb, &hid_endpoint(0), &pkt.data).await;
tx.send(Ok(pkt)).await.ok();
if !report_delay.is_zero() {
tokio::time::sleep(report_delay).await;
@ -775,7 +789,7 @@ impl Relay for Handler {
tokio::spawn(async move {
let mut s = req.into_inner();
while let Some(pkt) = s.next().await.transpose()? {
let _ = runtime_support::write_hid_report(&ms, &pkt.data).await;
let _ = runtime_support::write_hid_report(&ms, &hid_endpoint(1), &pkt.data).await;
tx.send(Ok(pkt)).await.ok();
}
Ok::<(), Status>(())

View File

@ -4,11 +4,10 @@
use anyhow::{Context, Result};
use chacha20poly1305::aead::{Aead, KeyInit};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use std::path::PathBuf;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use std::{path::PathBuf, sync::Arc};
use tokio::sync::Mutex;
use crate::runtime_support;
use lesavka_common::hid::{append_char_reports, char_to_usage};
use lesavka_common::lesavka::PasteRequest;
use lesavka_common::paste::decode_shared_key;
@ -41,7 +40,11 @@ pub fn decrypt(req: &PasteRequest) -> Result<String> {
/// supported character up to the configured maximum.
/// Why: paste injection must rate-limit itself so slower hosts do not drop
/// HID reports under bursty clipboard loads.
pub async fn type_text(kb: &Mutex<File>, text: &str) -> Result<()> {
pub async fn type_text(
kb: &Arc<Mutex<Option<tokio::fs::File>>>,
hid_path: &str,
text: &str,
) -> Result<()> {
let max = std::env::var("LESAVKA_PASTE_MAX")
.ok()
.and_then(|v| v.parse::<usize>().ok())
@ -59,12 +62,11 @@ pub async fn type_text(kb: &Mutex<File>, text: &str) -> Result<()> {
);
}
let mut kb = kb.lock().await;
for c in text.chars().take(max) {
let mut reports = Vec::with_capacity(4);
if append_char_reports(&mut reports, c) {
for report in reports {
kb.write_all(&report).await?;
runtime_support::write_hid_report(kb, hid_path, &report).await?;
if delay_ms > 0 {
tokio::time::sleep(delay).await;
}
@ -128,6 +130,7 @@ mod tests {
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use lesavka_common::lesavka::PasteRequest;
use serial_test::serial;
use std::sync::Arc;
use temp_env::with_var;
use tempfile::tempdir;
use tokio::fs::{File, OpenOptions};
@ -183,8 +186,10 @@ mod tests {
.open(&path)
.await
.expect("open temp file");
let kb = Mutex::new(file);
type_text(&kb, "A!?").await.expect("type text");
let kb = Arc::new(Mutex::new(Some(file)));
type_text(&kb, path.to_str().unwrap(), "A!?")
.await
.expect("type text");
let mut bytes = Vec::new();
let mut file = File::open(&path).await.expect("reopen temp file");
@ -218,8 +223,8 @@ mod tests {
.open(&path)
.await
.expect("open temp file");
let kb = Mutex::new(file);
let err = type_text(&kb, "pw🙂")
let kb = Arc::new(Mutex::new(Some(file)));
let err = type_text(&kb, path.to_str().unwrap(), "pw🙂")
.await
.expect_err("unsupported char should fail");
assert!(err.to_string().contains("unsupported character"));

View File

@ -66,10 +66,7 @@ pub fn init_tracing() -> anyhow::Result<WorkerGuard> {
/// must wait for readiness instead of failing the whole process immediately.
#[cfg(coverage)]
pub async fn open_with_retry(path: &str) -> anyhow::Result<tokio::fs::File> {
OpenOptions::new()
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
open_hid_file(path)
.await
.with_context(|| format!("opening {path}"))
}
@ -77,18 +74,16 @@ pub async fn open_with_retry(path: &str) -> anyhow::Result<tokio::fs::File> {
#[cfg(not(coverage))]
pub async fn open_with_retry(path: &str) -> anyhow::Result<tokio::fs::File> {
for attempt in 1..=200 {
match OpenOptions::new()
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
.await
{
match open_hid_file(path).await {
Ok(file) => {
info!("✅ {path} opened on attempt #{attempt}");
return Ok(file);
}
Err(error) if error.raw_os_error() == Some(libc::EBUSY) => {
trace!("⏳ {path} busy… retry #{attempt}");
Err(error)
if hid_endpoint_open_is_temporarily_unavailable(error.raw_os_error())
|| error.raw_os_error() == Some(libc::EBUSY) =>
{
trace!("⏳ {path} unavailable ({error})… retry #{attempt}");
tokio::time::sleep(Duration::from_millis(50)).await;
}
Err(error) => return Err(error).with_context(|| format!("opening {path}")),
@ -98,6 +93,36 @@ pub async fn open_with_retry(path: &str) -> anyhow::Result<tokio::fs::File> {
Err(anyhow::anyhow!("timeout waiting for {path}"))
}
async fn open_hid_file(path: &str) -> std::io::Result<tokio::fs::File> {
OpenOptions::new()
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
.await
}
pub async fn open_hid_if_ready(path: &str) -> anyhow::Result<Option<tokio::fs::File>> {
match open_hid_file(path).await {
Ok(file) => {
info!("✅ {path} opened");
Ok(Some(file))
}
Err(error) if hid_endpoint_open_is_temporarily_unavailable(error.raw_os_error()) => {
warn!("⌛ {path} is not ready yet ({error}); relay will retry lazily");
Ok(None)
}
Err(error) => Err(error).with_context(|| format!("opening {path}")),
}
}
#[must_use]
pub fn hid_endpoint_open_is_temporarily_unavailable(code: Option<i32>) -> bool {
matches!(
code,
Some(libc::ENOENT) | Some(libc::ENODEV) | Some(libc::ENXIO)
)
}
/// Check whether gadget auto-recovery is enabled.
///
/// Inputs: none.
@ -120,7 +145,7 @@ pub fn should_recover_hid_error(code: Option<i32>) -> bool {
matches!(
code,
Some(libc::ENOTCONN) | Some(libc::ESHUTDOWN) | Some(libc::EPIPE)
)
) || hid_endpoint_open_is_temporarily_unavailable(code)
}
/// Recover the HID endpoints after a transport failure.
@ -134,8 +159,10 @@ pub fn should_recover_hid_error(code: Option<i32>) -> bool {
pub async fn recover_hid_if_needed(
err: &std::io::Error,
gadget: UsbGadget,
kb: Arc<Mutex<tokio::fs::File>>,
ms: Arc<Mutex<tokio::fs::File>>,
kb: Arc<Mutex<Option<tokio::fs::File>>>,
ms: Arc<Mutex<Option<tokio::fs::File>>>,
_kb_path: String,
_ms_path: String,
did_cycle: Arc<AtomicBool>,
) {
let code = err.raw_os_error();
@ -166,8 +193,10 @@ pub async fn recover_hid_if_needed(
pub async fn recover_hid_if_needed(
err: &std::io::Error,
gadget: UsbGadget,
kb: Arc<Mutex<tokio::fs::File>>,
ms: Arc<Mutex<tokio::fs::File>>,
kb: Arc<Mutex<Option<tokio::fs::File>>>,
ms: Arc<Mutex<Option<tokio::fs::File>>>,
kb_path: String,
ms_path: String,
did_cycle: Arc<AtomicBool>,
) {
let code = err.raw_os_error();
@ -198,8 +227,8 @@ pub async fn recover_hid_if_needed(
}
if let Err(error) = async {
let kb_new = open_with_retry("/dev/hidg0").await?;
let ms_new = open_with_retry("/dev/hidg1").await?;
let kb_new = open_hid_if_ready(&kb_path).await?;
let ms_new = open_hid_if_ready(&ms_path).await?;
*kb.lock().await = kb_new;
*ms.lock().await = ms_new;
Ok::<(), anyhow::Error>(())
@ -496,16 +525,28 @@ pub fn next_stream_id() -> u64 {
/// stalls without blocking the stream task indefinitely.
#[cfg(coverage)]
pub async fn write_hid_report(
dev: &Arc<Mutex<tokio::fs::File>>,
dev: &Arc<Mutex<Option<tokio::fs::File>>>,
path: &str,
data: &[u8],
) -> std::io::Result<()> {
let mut file = dev.lock().await;
if file.is_none() {
*file = Some(open_hid_file(path).await?);
}
if let Some(file) = file.as_mut() {
file.write_all(data).await
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"HID endpoint is not open",
))
}
}
#[cfg(not(coverage))]
pub async fn write_hid_report(
dev: &Arc<Mutex<tokio::fs::File>>,
dev: &Arc<Mutex<Option<tokio::fs::File>>>,
path: &str,
data: &[u8],
) -> std::io::Result<()> {
let attempts = std::env::var("LESAVKA_HID_WRITE_RETRIES")
@ -521,7 +562,22 @@ pub async fn write_hid_report(
let mut last_error: Option<std::io::Error> = None;
for attempt in 0..attempts {
let mut file = dev.lock().await;
match file.write_all(data).await {
if file.is_none() {
match open_hid_file(path).await {
Ok(opened) => {
info!("✅ {path} opened lazily");
*file = Some(opened);
}
Err(error) => return Err(error),
}
}
let Some(file_handle) = file.as_mut() else {
return Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"HID endpoint is not open",
));
};
match file_handle.write_all(data).await {
Ok(()) => return Ok(()),
Err(error)
if error.kind() == std::io::ErrorKind::WouldBlock
@ -529,7 +585,12 @@ pub async fn write_hid_report(
{
last_error = Some(error);
}
Err(error) => return Err(error),
Err(error) => {
if should_recover_hid_error(error.raw_os_error()) {
*file = None;
}
return Err(error);
}
}
drop(file);
tokio::time::sleep(Duration::from_millis((attempt as u64 + 1) * base_delay_ms)).await;
@ -661,9 +722,9 @@ mod tests {
.open(tmp.path())
.await
.expect("open temp file");
let shared = Arc::new(Mutex::new(file));
let shared = Arc::new(Mutex::new(Some(file)));
write_hid_report(&shared, &[1, 2, 3, 4])
write_hid_report(&shared, tmp.path().to_str().unwrap(), &[1, 2, 3, 4])
.await
.expect("write succeeds");

View File

@ -44,8 +44,8 @@ mod server_main_binary {
(
dir,
Handler {
kb: std::sync::Arc::new(tokio::sync::Mutex::new(kb)),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(ms)),
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
gadget: UsbGadget::new("lesavka"),
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
camera_rt: std::sync::Arc::new(CameraRuntime::new()),

View File

@ -86,8 +86,8 @@ mod server_main_binary_extra {
(
dir,
Handler {
kb: std::sync::Arc::new(tokio::sync::Mutex::new(kb)),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(ms)),
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
gadget: UsbGadget::new("lesavka"),
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
camera_rt: std::sync::Arc::new(CameraRuntime::new()),
@ -455,8 +455,8 @@ mod server_main_binary_extra {
.expect("open hidg1"),
);
let handler = Handler {
kb: std::sync::Arc::new(tokio::sync::Mutex::new(kb)),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(ms)),
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
gadget: UsbGadget::new("lesavka"),
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
camera_rt: std::sync::Arc::new(CameraRuntime::new()),

View File

@ -45,8 +45,8 @@ mod server_main_rpc {
(
dir,
Handler {
kb: std::sync::Arc::new(tokio::sync::Mutex::new(kb)),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(ms)),
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
gadget: UsbGadget::new("lesavka"),
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
camera_rt: std::sync::Arc::new(CameraRuntime::new()),
@ -399,8 +399,8 @@ mod server_main_rpc {
Some(dir.path().join("cfg").to_string_lossy().to_string()),
|| {
let handler = Handler {
kb: std::sync::Arc::new(tokio::sync::Mutex::new(kb)),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(ms)),
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
gadget: UsbGadget::new("lesavka"),
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
false,

View File

@ -187,8 +187,8 @@ fn runtime_recover_hid_ignores_non_transport_errors() {
.await
.expect("open temp ms");
let kb = Arc::new(Mutex::new(kb));
let ms = Arc::new(Mutex::new(ms));
let kb = Arc::new(Mutex::new(Some(kb)));
let ms = Arc::new(Mutex::new(Some(ms)));
let did_cycle = Arc::new(AtomicBool::new(false));
let err = std::io::Error::from_raw_os_error(libc::EAGAIN);
@ -197,6 +197,8 @@ fn runtime_recover_hid_ignores_non_transport_errors() {
UsbGadget::new("lesavka"),
kb,
ms,
kb_tmp.path().to_string_lossy().to_string(),
ms_tmp.path().to_string_lossy().to_string(),
did_cycle.clone(),
)
.await;
@ -227,8 +229,8 @@ fn runtime_recover_hid_short_circuits_when_cycle_already_in_progress() {
.await
.expect("open temp ms");
let kb = Arc::new(Mutex::new(kb));
let ms = Arc::new(Mutex::new(ms));
let kb = Arc::new(Mutex::new(Some(kb)));
let ms = Arc::new(Mutex::new(Some(ms)));
let did_cycle = Arc::new(AtomicBool::new(true));
let err = std::io::Error::from_raw_os_error(libc::EPIPE);
@ -237,6 +239,8 @@ fn runtime_recover_hid_short_circuits_when_cycle_already_in_progress() {
UsbGadget::new("lesavka"),
kb,
ms,
kb_tmp.path().to_string_lossy().to_string(),
ms_tmp.path().to_string_lossy().to_string(),
did_cycle.clone(),
)
.await;
@ -268,8 +272,8 @@ fn runtime_recover_hid_resets_cycle_flag_after_async_recovery_path() {
.await
.expect("open temp ms");
let kb = Arc::new(Mutex::new(kb));
let ms = Arc::new(Mutex::new(ms));
let kb = Arc::new(Mutex::new(Some(kb)));
let ms = Arc::new(Mutex::new(Some(ms)));
let did_cycle = Arc::new(AtomicBool::new(false));
let err = std::io::Error::from_raw_os_error(libc::EPIPE);
@ -278,6 +282,8 @@ fn runtime_recover_hid_resets_cycle_flag_after_async_recovery_path() {
UsbGadget::new("lesavka"),
kb.clone(),
ms.clone(),
kb_tmp.path().to_string_lossy().to_string(),
ms_tmp.path().to_string_lossy().to_string(),
did_cycle.clone(),
)
.await;
@ -316,8 +322,8 @@ fn runtime_recover_hid_attempts_cycle_when_enabled() {
.await
.expect("open temp ms");
let kb = Arc::new(Mutex::new(kb));
let ms = Arc::new(Mutex::new(ms));
let kb = Arc::new(Mutex::new(Some(kb)));
let ms = Arc::new(Mutex::new(Some(ms)));
let did_cycle = Arc::new(AtomicBool::new(false));
let err = std::io::Error::from_raw_os_error(libc::EPIPE);
@ -326,6 +332,8 @@ fn runtime_recover_hid_attempts_cycle_when_enabled() {
UsbGadget::new("lesavka"),
kb.clone(),
ms.clone(),
kb_tmp.path().to_string_lossy().to_string(),
ms_tmp.path().to_string_lossy().to_string(),
did_cycle.clone(),
)
.await;