feat(desktop): add automatic game/app activity detection
Add cross-platform process scanning that detects running games and apps, then broadcasts them as Rich Presence activities via the existing Phase 1 pipeline (activityStore -> WS -> other users). - games.json dictionary with 26 entries (games, Spotify, VLC, OBS) - activityDetector module: polls OS process list every 15s via execFile, matches against dictionary, emits IPC on state change only - IPC bridge in main.ts/preload.ts with cleanup on quit - Frontend activityBridge subscribes to IPC and feeds activityStore - Supports Windows (tasklist CSV), macOS (ps -c), Linux (ps)
This commit is contained in:
@@ -31,6 +31,7 @@ import { useWebSocket } from '../../hooks/useWebSocket';
|
||||
import { useFederationToasts } from '../../hooks/useFederationToasts';
|
||||
import { useLiveKit } from '../../hooks/useLiveKit';
|
||||
import { useDeepLinkHandler } from '../../platform/deepLink';
|
||||
import { initActivityBridge, teardownActivityBridge } from '../../platform/activityBridge';
|
||||
import { useSpaceStore } from '../../stores/spaceStore';
|
||||
import { useChatStore } from '../../stores/chatStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
@@ -122,6 +123,12 @@ export function AppLayout() {
|
||||
// Deep link handler for Electron (backspace:// protocol)
|
||||
useDeepLinkHandler();
|
||||
|
||||
// Electron activity detection bridge (game/app process scanning → activityStore)
|
||||
useEffect(() => {
|
||||
initActivityBridge();
|
||||
return () => teardownActivityBridge();
|
||||
}, []);
|
||||
|
||||
// Track the last channel we attempted to connect to, to prevent effect loops
|
||||
const lastAttemptedRef = React.useRef<string | null>(null);
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Activity } from '@backspace/shared';
|
||||
import { useActivityStore } from '../stores/activityStore';
|
||||
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
|
||||
export function initActivityBridge(): void {
|
||||
if (unsubscribe) return; // already initialized
|
||||
if (!window.backspace?.onActivityDetected) return; // not Electron
|
||||
|
||||
// Subscribe to future activity changes from main process
|
||||
unsubscribe = window.backspace.onActivityDetected((activity) => {
|
||||
if (activity) {
|
||||
useActivityStore.getState().pushActivities([activity as Activity]);
|
||||
} else {
|
||||
useActivityStore.getState().pushActivities([]);
|
||||
}
|
||||
});
|
||||
|
||||
// Request current state (handles instance-switch: game was already running)
|
||||
window.backspace.getCurrentActivity?.().then((activity: unknown) => {
|
||||
if (activity) {
|
||||
useActivityStore.getState().pushActivities([activity as Activity]);
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
export function teardownActivityBridge(): void {
|
||||
unsubscribe?.();
|
||||
unsubscribe = null;
|
||||
}
|
||||
+4
@@ -48,6 +48,10 @@ interface BackspaceElectronAPI {
|
||||
getAutoLaunchSettings: () => Promise<{ openAtLogin: boolean; startMinimized: boolean }>;
|
||||
setAutoLaunchSettings: (settings: { openAtLogin?: boolean; startMinimized?: boolean }) =>
|
||||
Promise<{ openAtLogin: boolean; startMinimized: boolean }>;
|
||||
|
||||
// Activity detection (game/app process scanning)
|
||||
onActivityDetected: (callback: (activity: unknown) => void) => (() => void);
|
||||
getCurrentActivity: () => Promise<unknown>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
|
||||
Reference in New Issue
Block a user