# shepherd-host-api Host adapter trait interfaces for Shepherd. ## Overview This crate defines the capability-based interface between the Shepherd core and platform-specific implementations. It contains **no platform code itself**—only traits, types, and a mock implementation for testing. ### Purpose Desktop operating systems have fundamentally different process control models: - **Linux** can kill process groups and use cgroups - **macOS** requires MDM for true kiosk mode - **Windows** uses job objects and shell policies - **Android** has managed launcher and device-owner workflows `shepherd-host-api` acknowledges these differences honestly through a capability-based design rather than pretending all platforms are equivalent. ## Key Concepts ### Capabilities The `HostCapabilities` struct declares what a host adapter can actually do: ```rust use shepherd_host_api::HostCapabilities; let caps = host.capabilities(); // Check supported entry kinds if caps.supports_kind(EntryKindTag::Process) { /* ... */ } if caps.supports_kind(EntryKindTag::Snap) { /* ... */ } if caps.supports_kind(EntryKindTag::Steam) { /* ... */ } // Check enforcement capabilities if caps.can_kill_forcefully { /* Can use SIGKILL/TerminateProcess */ } if caps.can_graceful_stop { /* Can request graceful shutdown */ } if caps.can_group_process_tree { /* Can kill entire process tree */ } // Check optional features if caps.can_observe_window_ready { /* Get notified when GUI appears */ } if caps.can_force_foreground { /* Can bring window to front */ } if caps.can_force_fullscreen { /* Can set fullscreen mode */ } ``` The core engine uses these capabilities to: - Filter available entries (don't show what can't be run) - Choose termination strategies - Decide whether to attempt optional behaviors ### Host Adapter Trait Platform adapters implement this trait: ```rust use shepherd_host_api::{HostAdapter, SpawnOptions, StopMode, HostEvent}; #[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 (exits, window ready, etc.) fn subscribe(&self) -> mpsc::UnboundedReceiver; // Optional methods with default implementations async fn set_foreground(&self, handle: &HostSessionHandle) -> HostResult<()>; async fn set_fullscreen(&self, handle: &HostSessionHandle) -> HostResult<()>; async fn ensure_shell_visible(&self) -> HostResult<()>; } ``` ### Session Handles `HostSessionHandle` is an opaque container for platform-specific identifiers: ```rust use shepherd_host_api::HostSessionHandle; // Created by spawn(), contains platform-specific data let handle: HostSessionHandle = host.spawn(session_id, &entry_kind, options).await?; // On Linux, internally contains pid, pgid // On Windows, would contain job object handle // On macOS, might contain bundle identifier ``` ### Stop Modes Session termination can be graceful or forced: ```rust use shepherd_host_api::StopMode; use std::time::Duration; // Try graceful shutdown with timeout, then force host.stop(&handle, StopMode::Graceful { timeout: Duration::from_secs(5) }).await?; // Immediate termination host.stop(&handle, StopMode::Force).await?; ``` ### Host Events Adapters emit events via an async channel: ```rust use shepherd_host_api::HostEvent; let mut events = host.subscribe(); while let Some(event) = events.recv().await { match event { HostEvent::Exited { handle, status } => { // Process ended (normally or killed) } HostEvent::WindowReady { handle } => { // GUI window appeared (if observable) } HostEvent::SpawnFailed { session_id, error } => { // Launch failed after handle was created } } } ``` ### Volume Control The crate also defines a volume controller interface: ```rust use shepherd_host_api::VolumeController; #[async_trait] pub trait VolumeController: Send + Sync { /// Get current volume (0-100) async fn get_volume(&self) -> HostResult; /// Set volume (0-100) async fn set_volume(&self, level: u8) -> HostResult<()>; /// Check if muted async fn is_muted(&self) -> HostResult; /// Set mute state async fn set_muted(&self, muted: bool) -> HostResult<()>; /// Subscribe to volume changes fn subscribe(&self) -> mpsc::UnboundedReceiver; } ``` ## Mock Implementation For testing, the crate provides `MockHost`: ```rust use shepherd_host_api::MockHost; let mock = MockHost::new(); // Spawns will "succeed" with fake handles let handle = mock.spawn(session_id, &entry_kind, options).await?; // Inject events for testing mock.inject_exit(handle.clone(), ExitStatus::Code(0)); ``` ## Design Philosophy - **Honest capabilities** - Don't pretend all platforms are equal - **Platform code stays out** - This crate is pure interface - **Extensible** - New capabilities can be added without breaking existing adapters - **Testable** - Mock implementation enables unit testing ## Available Implementations | Adapter | Crate | Status | |---------|-------|--------| | Linux | `shepherd-host-linux` | Implemented | | macOS | `shepherd-host-macos` | Planned | | Windows | `shepherd-host-windows` | Planned | | Android | `shepherd-host-android` | Planned | ## Dependencies - `async-trait` - Async trait support - `tokio` - Async runtime types - `serde` - Serialization for handles - `shepherd-api` - Entry kind types - `shepherd-util` - ID types