fix(server): survive unattached HID gadget startup
This commit is contained in:
parent
b8e43cac6f
commit
c65fcd1137
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.11.34"
|
||||
version = "0.11.35"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.11.34"
|
||||
version = "0.11.35"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -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)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.11.34"
|
||||
version = "0.11.35"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -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))]
|
||||
info!("✅ HID endpoints ready");
|
||||
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>(())
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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;
|
||||
file.write_all(data).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");
|
||||
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user