Ashiedu's picture
Sync unified workbench
0490201 verified
# T-013: Audio + Camera Device Enumeration IPC β€” Hot-Plug Detection, `device_list_updated` Event
**Type:** Task | **Phase:** 0 | **Autonomy:** `agent:autonomous` | **Stack:** `stack:rust`
**Version:** v0.1 | **Iteration:** iter-1 | **Effort:** XS (1 hour)
> ⚠️ **Scope:** T-004 implemented `device_list` for audio devices only. T-013 extends it to include cameras and adds a background watcher that emits `device_list_updated` when devices are plugged or unplugged. Camera enumeration uses Windows Media Foundation (MSMF) via the `nokhwa` crate.
---
## Prerequisites
- [ ] T-002 merged β€” `DeviceInfo`, `DeviceKind`, `DeviceListResult` types exist
- [ ] T-004 merged β€” audio `device_list` command exists in `kansas/src/commands/devices.rs`
## New Workspace Dependency
```toml
nokhwa = { version = "*", features = ["input-msmf"] } # Windows camera enumeration
```
## Acceptance Criteria
- [ ] `device_list` returns cameras alongside audio devices (kind: `"camera"`)
- [ ] Plugging in a USB camera within 3 seconds emits `device_list_updated` event to frontend
- [ ] Unplugging emits `device_list_updated`
- [ ] No panic if zero cameras present
- [ ] `cargo check --workspace` β€” zero errors
## Changes to `kansas/src/commands/devices.rs`
Extend the existing `device_list` command to append camera devices:
```rust
use nokhwa::{query, utils::ApiBackend};
// After audio device collection, add:
if let Ok(cameras) = query(ApiBackend::MediaFoundation) {
for (idx, info) in cameras.iter().enumerate() {
devices.push(DeviceInfo {
id: id,
name: info.human_name().to_string(),
kind: DeviceKind::Camera,
is_default: idx == 0, // first camera is default
});
id += 1;
}
}
```
## Add Hot-Plug Watcher
In `kansas/src/devices.rs` (new file):
```rust
use std::{sync::Arc, time::Duration, thread};
use tauri::AppHandle;
use crate::ipc::types::DeviceListResult;
/// Polls device list every 2s; emits `device_list_updated` on change.
/// Real hot-plug via WM_DEVICECHANGE is a T-013 iter-2 enhancement.
pub fn watch_devices(app: AppHandle) {
thread::Builder::new()
.name("synesthesia-device-watcher".into())
.spawn(move || {
let mut last_count = 0usize;
loop {
thread::sleep(Duration::from_secs(2));
if let Ok(result) = crate::commands::devices::enumerate_all() {
let count = result.devices.len();
if count != last_count {
last_count = count;
app.emit("device_list_updated", &result).ok();
tracing::info!("[devices] Device list changed β€” {} devices", count);
}
}
}
})
.ok();
}
```
Register in `main.rs`: `devices::watch_devices(app_handle.clone());`
---
## GitHub CLI
```bash
gh issue create \
--title "T-013: Audio + camera device enumeration IPC β€” hot-plug detection, device_list_updated" \
--label "type:task,stack:rust,agent:autonomous,priority:high,status:ready,day:1" \
--body-file T-013.md
```
**Parent:** GENESIS | **Blocks:** T-031 (camera config uses device IDs) | **Blocked By:** T-002, T-004