//! Host adapter traits use async_trait::async_trait; use shepherd_api::EntryKind; use shepherd_util::SessionId; use std::time::Duration; use thiserror::Error; use tokio::sync::mpsc; use crate::{ExitStatus, HostCapabilities, HostSessionHandle}; /// Errors from host adapter operations #[derive(Debug, Error)] pub enum HostError { #[error("Spawn failed: {0}")] SpawnFailed(String), #[error("Stop failed: {0}")] StopFailed(String), #[error("Unsupported entry kind")] UnsupportedKind, #[error("Session not found")] SessionNotFound, #[error("Permission denied: {0}")] PermissionDenied(String), #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Internal error: {0}")] Internal(String), } pub type HostResult = Result; /// Stop mode for session termination #[derive(Debug, Clone, Copy)] pub enum StopMode { /// Try graceful stop with timeout, then force Graceful { timeout: Duration }, /// Force immediate termination Force, } impl Default for StopMode { fn default() -> Self { Self::Graceful { timeout: Duration::from_secs(5), } } } /// Options for spawning a session #[derive(Debug, Clone, Default)] pub struct SpawnOptions { /// Capture stdout to log file pub capture_stdout: bool, /// Capture stderr to log file pub capture_stderr: bool, /// Log file path (if capturing) pub log_path: Option, /// Request fullscreen (if supported) pub fullscreen: bool, /// Request foreground focus (if supported) pub foreground: bool, } /// Events from the host adapter #[derive(Debug, Clone)] pub enum HostEvent { /// Process/session has exited Exited { handle: HostSessionHandle, status: ExitStatus, }, /// Window is ready (for UI notification) WindowReady { handle: HostSessionHandle, }, /// Spawn failed after handle was created SpawnFailed { session_id: SessionId, error: String, }, } /// Host adapter trait - implemented by platform-specific adapters #[async_trait] pub trait HostAdapter: Send + Sync { /// Get the capabilities of this host adapter fn capabilities(&self) -> &HostCapabilities; /// Spawn a new session async fn spawn( &self, session_id: SessionId, entry_kind: &EntryKind, options: SpawnOptions, ) -> HostResult; /// Stop a running session async fn stop(&self, handle: &HostSessionHandle, mode: StopMode) -> HostResult<()>; /// Subscribe to host events fn subscribe(&self) -> mpsc::UnboundedReceiver; /// Optional: set foreground focus (if supported) async fn set_foreground(&self, _handle: &HostSessionHandle) -> HostResult<()> { Err(HostError::Internal("Not supported".into())) } /// Optional: set fullscreen mode (if supported) async fn set_fullscreen(&self, _handle: &HostSessionHandle) -> HostResult<()> { Err(HostError::Internal("Not supported".into())) } /// Optional: ensure the shell/launcher is visible async fn ensure_shell_visible(&self) -> HostResult<()> { Ok(()) } /// Optional: check if the host adapter is healthy fn is_healthy(&self) -> bool { true } } #[cfg(test)] mod tests { use super::*; #[test] fn stop_mode_default() { let mode = StopMode::default(); assert!(matches!(mode, StopMode::Graceful { timeout } if timeout == Duration::from_secs(5))); } }