| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
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:
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) { /* ... */ }
// 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:
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<HostSessionHandle>;
/// 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<HostEvent>;
// 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:
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:
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:
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:
use shepherd_host_api::VolumeController;
#[async_trait]
pub trait VolumeController: Send + Sync {
/// Get current volume (0-100)
async fn get_volume(&self) -> HostResult<u8>;
/// Set volume (0-100)
async fn set_volume(&self, level: u8) -> HostResult<()>;
/// Check if muted
async fn is_muted(&self) -> HostResult<bool>;
/// Set mute state
async fn set_muted(&self, muted: bool) -> HostResult<()>;
/// Subscribe to volume changes
fn subscribe(&self) -> mpsc::UnboundedReceiver<VolumeEvent>;
}
Mock Implementation
For testing, the crate provides MockHost:
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 supporttokio- Async runtime typesserde- Serialization for handlesshepherd-api- Entry kind typesshepherd-util- ID types