183 lines
5.6 KiB
Markdown
183 lines
5.6 KiB
Markdown
# Fox Desktop — Design Spec
|
||
**Datum:** 2026-05-11
|
||
**Status:** Genehmigt
|
||
|
||
## Überblick
|
||
|
||
Fox Desktop ist eine native Mac-App (Electron), die als dünne Hülle um die bestehende Laravel-HUD-App (`fox.pxo.at/hud`) fungiert. Electron fügt native Mac-Fähigkeiten hinzu, ohne das HUD neu zu schreiben.
|
||
|
||
## Architektur
|
||
|
||
```
|
||
Mac OS
|
||
├── Tray Icon (Menu Bar)
|
||
├── BrowserWindow (Overlay, fullscreen, always-on-top)
|
||
│ └── lädt https://fox.pxo.at/hud
|
||
│ └── preload.js injiziert window.foxBridge
|
||
├── Global Shortcut: Cmd+Shift+F
|
||
└── Python Subprocess: openwakeword ("hey_jarvis")
|
||
└── stdout "WAKE" → IPC → showHud()
|
||
```
|
||
|
||
Der Electron-Main-Process hat **keine** eigene WebSocket-Verbindung zu Reverb. Der WebSocket-Kanal gehört der HUD-Seite selbst. Electron ist reiner Plattform-Layer.
|
||
|
||
## Prinzipien
|
||
|
||
- **Pure Shell:** Electron kapselt, erweitert nicht die App-Logik
|
||
- **Bridge ist optional:** `window.foxBridge` wird nur in Electron injiziert; das HUD funktioniert im normalen Browser unverändert weiter
|
||
- **Kein Vite Build:** Es gibt kein eigenes Frontend — nur Electron-Module und Python-Script
|
||
- **Kein electron-builder** vorerst — erst nach stabilem Dev-Setup
|
||
|
||
## Dateien
|
||
|
||
| Datei | Aufgabe |
|
||
|---|---|
|
||
| `package.json` | Electron-App-Konfiguration, npm-Scripts |
|
||
| `electron/main.js` | App-Lifecycle, IPC-Handler, globaler Shortcut |
|
||
| `electron/hud.js` | BrowserWindow erstellen, show/hide mit Transition |
|
||
| `electron/tray.js` | Menu Bar Icon (Template Image) + Kontextmenü |
|
||
| `electron/wakeword.js` | Python-Subprocess starten, Auto-Restart bei Absturz |
|
||
| `electron/mac-agent.js` | AppleScript-Ausführung via osascript |
|
||
| `electron/preload.js` | `window.foxBridge` in HUD-Seite injizieren |
|
||
| `python/wakeword.py` | OpenWakeWord Loop, schreibt "WAKE" auf stdout |
|
||
| `assets/fox-tray.png` | 16×16 Menu Bar Icon (Template Image) |
|
||
|
||
## Datenflüsse
|
||
|
||
### HUD öffnen (Wakeword)
|
||
```
|
||
"hey jarvis" gesprochen
|
||
→ python/wakeword.py erkennt Wakeword
|
||
→ stdout: "WAKE\n"
|
||
→ electron/wakeword.js liest stdout
|
||
→ IPC intern → showHud()
|
||
→ BrowserWindow.show() + focus()
|
||
→ webContents.send('hud-show')
|
||
→ HUD CSS-Transition startet
|
||
```
|
||
|
||
### HUD öffnen (Tastaturkürzel / Tray)
|
||
```
|
||
Cmd+Shift+F gedrückt ODER Tray-Icon geklickt
|
||
→ main.js globalShortcut / tray.on('click')
|
||
→ showHud()
|
||
```
|
||
|
||
### Mac-Befehl aus HUD
|
||
```
|
||
HUD JavaScript: await window.foxBridge.macCommand('spotify_play')
|
||
→ preload.js: ipcRenderer.invoke('mac-command', 'spotify_play')
|
||
→ main.js IPC Handler
|
||
→ electron/mac-agent.js: executeMacCommand('spotify_play')
|
||
→ osascript: tell application "Spotify" to play
|
||
→ Rückgabewert { success: true, output: '' } → HUD
|
||
```
|
||
|
||
### HUD schließen
|
||
```
|
||
ESC im HUD ODER foxBridge.hideHud() ODER Tray-Menü "Verstecken"
|
||
→ ipcRenderer.send('hide-hud')
|
||
→ main.js → hideHud()
|
||
→ webContents.send('hud-hide') ← CSS-Transition startet
|
||
→ setTimeout 600ms → BrowserWindow.hide()
|
||
```
|
||
|
||
## BrowserWindow Konfiguration
|
||
|
||
```javascript
|
||
{
|
||
width: screenWidth,
|
||
height: screenHeight,
|
||
x: 0, y: 0,
|
||
frame: false,
|
||
transparent: true,
|
||
alwaysOnTop: true,
|
||
show: false,
|
||
skipTaskbar: true,
|
||
hasShadow: false,
|
||
webPreferences: {
|
||
preload: path.join(__dirname, 'preload.js'),
|
||
contextIsolation: true,
|
||
nodeIntegration: false,
|
||
webSecurity: false, // Remote URL + preload injection
|
||
allowRunningInsecureContent: true,
|
||
}
|
||
}
|
||
```
|
||
|
||
`setAlwaysOnTop(true, 'screen-saver')` und `setVisibleOnAllWorkspaces(true)` nach dem Erstellen.
|
||
|
||
## window.foxBridge API
|
||
|
||
```javascript
|
||
window.foxBridge = {
|
||
sendMessage(text), // Text an Fox Laravel via IPC
|
||
hideHud(), // HUD verstecken
|
||
macCommand(cmd), // AppleScript-Befehl ausführen (async, gibt Result zurück)
|
||
onFoxAction(callback), // Fox-Aktionen empfangen
|
||
onHudShow(callback), // HUD-Show-Event
|
||
onHudHide(callback), // HUD-Hide-Event
|
||
onStartListening(callback), // Mikrofon-Start-Signal
|
||
onPlayAudio(callback), // Audio-URL zum Abspielen
|
||
}
|
||
```
|
||
|
||
## Mac-Agent Befehle
|
||
|
||
Vordefinierte Shortcuts (z.B. `spotify_play`, `mail_open`, `volume_up`) sowie generische Formen:
|
||
- `script:<applescript>` — direktes AppleScript
|
||
- `open:<url>` — URL im Standardbrowser öffnen
|
||
- `app:<name>` — Mac-App öffnen
|
||
- `save:<json>` — Datei auf Desktop schreiben
|
||
|
||
## Wakeword (OpenWakeWord)
|
||
|
||
- Python 3, `openwakeword`, `pyaudio`, `numpy`, `tflite-runtime`
|
||
- Eingebautes Modell: `hey_jarvis` (kein API-Key, kein Training nötig)
|
||
- Electron spawnt `python3 python/wakeword.py` als Child-Process
|
||
- Auto-Restart nach Absturz (2s Delay)
|
||
- Später: eigenes "hey fox" Modell trainierbar
|
||
|
||
## Laravel HUD — Anpassungsstellen
|
||
|
||
Diese 3 optionalen Punkte müssen in `fox-hud.blade.php` / HUD-JavaScript hinzugefügt werden:
|
||
|
||
1. **ESC schließt HUD:**
|
||
```javascript
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') window.foxBridge?.hideHud();
|
||
});
|
||
```
|
||
|
||
2. **Mac-Aktionen in `onAction()`:**
|
||
```javascript
|
||
if (payload.action === 'spotify_play') {
|
||
await window.foxBridge?.macCommand('spotify_play');
|
||
return;
|
||
}
|
||
// ... weitere Mac-Aktionen
|
||
```
|
||
|
||
3. **Nachricht senden (optional, wenn direkt ohne Livewire):**
|
||
```javascript
|
||
window.foxBridge?.sendMessage(text);
|
||
```
|
||
|
||
## Deployment
|
||
|
||
- Dev: `npm start` (Electron lädt fox.pxo.at/hud direkt)
|
||
- LaunchAgent `~/Library/LaunchAgents/at.pxo.fox.plist` für Auto-Start beim Login
|
||
- `electron-builder` für DMG-Build — später
|
||
|
||
## Abhängigkeiten
|
||
|
||
**npm:**
|
||
- `electron` ^28
|
||
- `axios` ^1.6 (für sendMessage POST)
|
||
|
||
**Python:**
|
||
- `openwakeword`
|
||
- `pyaudio`
|
||
- `numpy`
|
||
- `tflite-runtime`
|