shepherd-launcher/crates/shepherd-host-api/README.md
Albert Armea 9da95a27b3 Add "steam"-specific type
This implementation allows each platform to choose how to launch Steam (on Linux, we use the snap as the examples suggested before), and keeps Steam alive after an activity exits so that save sync, game updates, etc. can continue to run.

Change written by Codex 5.2 on medium:

Consider this GitHub issue https://github.com/aarmea/shepherd-launcher/issues/4. On Linux, an activity that uses the "steam" type should launch Steam via the snap as shown in the example configuration in this repository.

Go ahead and implement the feature. I'm expecting one of the tricky bits to be killing the activity while keeping Steam alive, as we can no longer just kill the Steam snap cgroup.
2026-02-07 16:22:55 -05:00

203 lines
5.8 KiB
Markdown

# 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<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:
```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<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`:
```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