diff --git a/src/cli/connection.rs b/src/cli/connection.rs index 840e637..1b7b0b2 100644 --- a/src/cli/connection.rs +++ b/src/cli/connection.rs @@ -79,8 +79,23 @@ fn spawn_daemon(opts: &GlobalOpts) -> Result<()> { Ok(()) } +fn request_read_timeout(request: &Request) -> Duration { + let default_timeout = Duration::from_secs(30); + match request.action.as_str() { + "wait-window" | "wait-focus" => { + let wait_timeout = request + .extra + .get("timeout_ms") + .and_then(|value| value.as_u64()) + .unwrap_or(10_000); + Duration::from_millis(wait_timeout.saturating_add(5_000)) + } + _ => default_timeout, + } +} + fn send_request_over_stream(mut stream: UnixStream, request: &Request) -> Result { - stream.set_read_timeout(Some(Duration::from_secs(30)))?; + stream.set_read_timeout(Some(request_read_timeout(request)))?; stream.set_write_timeout(Some(Duration::from_secs(5)))?; let json = serde_json::to_string(request)?; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a9c8d47..ccd5b28 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -245,9 +245,13 @@ pub fn run() -> Result<()> { // All other commands need a daemon connection let request = build_request(&app.command)?; let response = connection::send_command(&app.global, &request)?; + let success = response.success; if app.global.json { println!("{}", serde_json::to_string_pretty(&response)?); + if !success { + std::process::exit(1); + } } else { print_response(&app.command, &response)?; } @@ -392,11 +396,7 @@ fn print_response(cmd: &Command, response: &Response) -> Result<()> { } else { "visible" }; - let display_title = if title.len() > 30 { - format!("{}...", &title[..27]) - } else { - title.to_string() - }; + let display_title = truncate_display(title, 30); println!( "@{:<4} {:<30} ({:<7}) {},{} {}x{}", ref_id, display_title, state, x, y, width, height @@ -496,11 +496,7 @@ fn print_window_line(window: &serde_json::Value, stderr: bool) { let line = format!( "@{:<4} {:<30} ({:<7}) {},{} {}x{} [{}]", ref_id, - if title.len() > 30 { - format!("{}...", &title[..27]) - } else { - title.to_string() - }, + truncate_display(title, 30), state, x, y, @@ -514,3 +510,13 @@ fn print_window_line(window: &serde_json::Value, stderr: bool) { println!("{line}"); } } + +fn truncate_display(value: &str, max_chars: usize) -> String { + let char_count = value.chars().count(); + if char_count <= max_chars { + return value.to_string(); + } + + let truncated: String = value.chars().take(max_chars.saturating_sub(3)).collect(); + format!("{truncated}...") +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index c3d5b7f..5c6f0be 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -21,6 +21,13 @@ pub fn env_lock() -> &'static Mutex<()> { LOCK.get_or_init(|| Mutex::new(())) } +pub fn env_lock_guard() -> std::sync::MutexGuard<'static, ()> { + match env_lock().lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } +} + pub struct SessionEnvGuard { old_session_type: Option, } diff --git a/tests/x11_runtime.rs b/tests/x11_runtime.rs index 3c06ed5..2aac58c 100644 --- a/tests/x11_runtime.rs +++ b/tests/x11_runtime.rs @@ -8,13 +8,13 @@ use deskctl::core::doctor; use deskctl::core::protocol::Request; use self::support::{ - deskctl_tmp_screenshot_count, env_lock, json_response, successful_json_response, FixtureWindow, - SessionEnvGuard, TestSession, + deskctl_tmp_screenshot_count, env_lock_guard, json_response, successful_json_response, + FixtureWindow, SessionEnvGuard, TestSession, }; #[test] fn doctor_reports_healthy_x11_environment() -> Result<()> { - let _guard = env_lock().lock().unwrap(); + let _guard = env_lock_guard(); let Some(_env) = SessionEnvGuard::prepare() else { eprintln!("Skipping X11 integration test because DISPLAY is not set"); return Ok(()); @@ -46,7 +46,7 @@ fn doctor_reports_healthy_x11_environment() -> Result<()> { #[test] fn list_windows_is_side_effect_free() -> Result<()> { - let _guard = env_lock().lock().unwrap(); + let _guard = env_lock_guard(); let Some(_env) = SessionEnvGuard::prepare() else { eprintln!("Skipping X11 integration test because DISPLAY is not set"); return Ok(()); @@ -84,7 +84,7 @@ fn list_windows_is_side_effect_free() -> Result<()> { #[test] fn daemon_start_recovers_from_stale_socket() -> Result<()> { - let _guard = env_lock().lock().unwrap(); + let _guard = env_lock_guard(); let Some(_env) = SessionEnvGuard::prepare() else { eprintln!("Skipping X11 integration test because DISPLAY is not set"); return Ok(()); @@ -116,7 +116,7 @@ fn daemon_start_recovers_from_stale_socket() -> Result<()> { #[test] fn wait_window_returns_matched_window_payload() -> Result<()> { - let _guard = env_lock().lock().unwrap(); + let _guard = env_lock_guard(); let Some(_env) = SessionEnvGuard::prepare() else { eprintln!("Skipping X11 integration test because DISPLAY is not set"); return Ok(()); @@ -158,7 +158,7 @@ fn wait_window_returns_matched_window_payload() -> Result<()> { #[test] fn ambiguous_fuzzy_selector_returns_candidates() -> Result<()> { - let _guard = env_lock().lock().unwrap(); + let _guard = env_lock_guard(); let Some(_env) = SessionEnvGuard::prepare() else { eprintln!("Skipping X11 integration test because DISPLAY is not set"); return Ok(()); @@ -194,7 +194,7 @@ fn ambiguous_fuzzy_selector_returns_candidates() -> Result<()> { #[test] fn wait_focus_timeout_is_structured() -> Result<()> { - let _guard = env_lock().lock().unwrap(); + let _guard = env_lock_guard(); let Some(_env) = SessionEnvGuard::prepare() else { eprintln!("Skipping X11 integration test because DISPLAY is not set"); return Ok(());