From b15cbc58900d544d7d7cfc3392e0651e09d23781 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 18 Nov 2025 01:48:45 +0200 Subject: [PATCH] Clean up redundant comments for maintainability Removed obvious comments that don't add value: - cgo_source.go: Removed redundant status check comments - audio.go: Consolidated mutex pattern comments Kept important comments that explain non-obvious patterns: - Why mutex is released before C calls (deadlock prevention) - Why operations happen outside mutex (avoid blocking on CGO) - Why single critical section is used (race condition prevention) --- .github/copilot-instructions.md | 115 ++++++++++++++++++++++++++++++++ audio.go | 4 +- internal/audio/cgo_source.go | 8 +-- 3 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..72cdff65 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,115 @@ +# JetKVM Copilot Instructions + +## Architecture Overview + +JetKVM is a high-performance KVM-over-IP solution with **bidirectional architecture**: a Go backend running on ARM hardware and a React/TypeScript frontend served by the device. The system provides real-time video streaming, HID input, virtual media mounting, and **2-way audio support** via WebRTC. + +### Core Components + +- **Backend**: Go application (`main.go`, `web.go`, `webrtc.go`) handling WebRTC, HTTP APIs, hardware interfaces +- **Frontend**: React/TypeScript SPA (`ui/src/`) with Vite build system, served as embedded static files +- **Hardware Layer**: CGO bridge (`internal/native/`) to ARM SoC for video capture, USB gadget, display control +- **Audio System**: In-process CGO implementation (`internal/audio/`) with ALSA/Opus for USB Audio Gadget streaming +- **USB Gadget**: Configurable emulation (`internal/usbgadget/`) - keyboard, mouse, mass storage, **audio device** + +## Development Workflows + +### Quick Development Commands +```bash +# Full development deployment (most common) +./dev_deploy.sh -r + +# UI-only changes (faster iteration) +cd ui && ./dev_device.sh + +# Backend-only changes (skip UI build) +./dev_deploy.sh -r --skip-ui-build + +# Build for release +make build_release +``` + +### Critical Build Dependencies +- **Audio libraries**: `make build_audio_deps` builds ALSA and Opus static libs for ARM +- **Native CGO**: `make build_native` compiles hardware interface layer +- **Cross-compilation**: ARM7 with buildkit toolchain at `/opt/jetkvm-native-buildkit` + +### Testing +```bash +# Run Go tests on device +./dev_deploy.sh -r --run-go-tests + +# UI linting +cd ui && npm run lint +``` + +## Project-Specific Patterns + +### Configuration Management +- **Single config file**: `/userdata/kvm_config.json` persisted across updates +- **Config struct**: `config.go` with JSON tags, atomic operations for thread safety +- **Migration pattern**: Use `ensureConfigLoaded()` before config access + +### WebRTC Architecture +- **Session management**: `webrtc.go` handles peer connections with connection pooling +- **Media tracks**: Separate video and **stereo audio tracks** in independent MediaStreams +- **Data channels**: RPC (ordered), HID (unordered), upload, terminal, serial channels +- **SDP munging**: Frontend modifies SDP for Opus stereo parameters (`OPUS_STEREO_PARAMS`) + +### Audio Implementation Details +- **USB Audio Gadget**: UAC1 device presenting as stereo speakers + microphone +- **Bidirectional streaming**: Output (device→browser) and input (browser→device) +- **CGO integration**: `internal/audio/cgo_source.go` bridges Go↔C for ALSA operations +- **Frame-based processing**: 20ms Opus frames (960 samples @ 48kHz) for low latency + +### Frontend State Management +- **Zustand stores**: `hooks/stores.ts` - RTC, UI, Settings, Video state with persistence +- **JSON-RPC communication**: `useJsonRpc` hook for backend API calls +- **Localization**: Paraglide.js with compile-time validation - all UI strings use `m.key()` pattern + +### USB Gadget Configuration +- **Configfs-based**: Dynamic reconfiguration via `/sys/kernel/config/usb_gadget/` +- **Transaction pattern**: `WithTransaction()` ensures atomic gadget changes +- **Device composition**: Keyboard + mouse + mass storage + **audio** with individual toggle support + +## Integration Patterns + +### Backend API Structure +- **JSON-RPC over WebSocket**: Primary communication via data channels +- **HTTP endpoints**: `/api/*` for REST operations, `/static/*` for UI assets +- **Configuration endpoints**: `rpc*` functions in `jsonrpc.go` with parameter validation + +### Cross-Component Communication +- **Video pipeline**: `native.go` → WebRTC track → browser via H.264 streaming +- **HID input**: Browser → WebRTC data channel → `internal/usbgadget/hid_*.go` → Linux gadget +- **Audio pipeline**: ALSA capture → Opus encode → WebRTC → browser speakers (and reverse for mic) +- **Virtual media**: HTTP upload → storage → NBD server → USB mass storage gadget + +### Error Handling Patterns +- **Graceful degradation**: Audio/video failures don't crash core functionality +- **Session isolation**: Per-connection logging with `connectionID` context +- **Recovery mechanisms**: USB gadget timeouts, audio restart on device changes + +## Key Files for Common Tasks + +### Adding new RPC endpoints +1. Add handler function in `jsonrpc.go` +2. Register in `rpcHandlers` map +3. Add frontend hook in `useJsonRpc` + +### USB device modifications +- `internal/usbgadget/config.go` - gadget composition and attributes +- `internal/usbgadget/hid_*.go` - HID device implementations + +### Audio feature development +- `audio.go` - high-level audio management and session integration +- `internal/audio/` - CGO sources, relays, and C audio processing + +### Frontend feature development +- `ui/src/routes/` - page components +- `ui/src/components/` - reusable UI components +- `ui/localization/messages/en.json` - localizable strings + +--- + +*This codebase uses advanced patterns like CGO, USB gadgets, and real-time media streaming. Focus on understanding the session lifecycle and cross-compilation requirements for effective development.* \ No newline at end of file diff --git a/audio.go b/audio.go index 73f76ce9..88f16c96 100644 --- a/audio.go +++ b/audio.go @@ -171,7 +171,6 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) { outputRelay = nil outputSource = nil - // Prepare new relay if needed var newRelay *audio.OutputRelay var newSource audio.AudioSource if currentAudioTrack != nil && audioOutputEnabled.Load() { @@ -188,7 +187,7 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) { } audioMutex.Unlock() - // Stop old resources outside mutex to avoid blocking during CGO calls + // Stop/start resources outside mutex to avoid blocking on CGO calls if oldRelay != nil { oldRelay.Stop() } @@ -196,7 +195,6 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) { oldSource.Disconnect() } - // Start new relay outside mutex if newRelay != nil { if err := newRelay.Start(); err != nil { audioLogger.Error().Err(err).Msg("Failed to start output relay") diff --git a/internal/audio/cgo_source.go b/internal/audio/cgo_source.go index 9cc07a4a..45f08be7 100644 --- a/internal/audio/cgo_source.go +++ b/internal/audio/cgo_source.go @@ -167,7 +167,6 @@ func (c *CgoSource) IsConnected() bool { } func (c *CgoSource) ReadMessage() (uint8, []byte, error) { - // Check connection status with mutex c.mu.Lock() if !c.connected { c.mu.Unlock() @@ -180,8 +179,7 @@ func (c *CgoSource) ReadMessage() (uint8, []byte, error) { } c.mu.Unlock() - // Call C function without holding mutex to avoid deadlock - // The C layer has its own locking and handles stop requests + // Call C function without holding mutex to avoid deadlock - C layer has its own locking opusSize := C.jetkvm_audio_read_encode(unsafe.Pointer(&c.opusBuf[0])) if opusSize < 0 { return 0, nil, fmt.Errorf("jetkvm_audio_read_encode failed: %d", opusSize) @@ -199,7 +197,6 @@ func (c *CgoSource) ReadMessage() (uint8, []byte, error) { } func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error { - // Check connection status and validate parameters with mutex c.mu.Lock() if !c.connected { c.mu.Unlock() @@ -224,8 +221,7 @@ func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error { return fmt.Errorf("opus packet too large: %d bytes (max 1500)", len(payload)) } - // Call C function without holding mutex to avoid deadlock - // The C layer has its own locking and handles stop requests + // Call C function without holding mutex to avoid deadlock - C layer has its own locking rc := C.jetkvm_audio_decode_write(unsafe.Pointer(&payload[0]), C.int(len(payload))) if rc < 0 { return fmt.Errorf("jetkvm_audio_decode_write failed: %d", rc)