tray icon and open on login

This commit is contained in:
Boof2015
2026-08-15 21:20:48 -04:00
parent 6debfb8b34
commit 5236a92763
23 changed files with 1940 additions and 60 deletions
@@ -0,0 +1,136 @@
import { useEffect, type JSX } from 'react'
import type { TrayRendererCommand, TrayRendererState } from '../../types/desktopIntegration'
import { useAudioStore } from '../stores/audioStore'
import { useSettingsStore } from '../stores/settingsStore'
import { useUiStore } from '../stores/uiStore'
interface TrayControlBridgeProps {
ready: boolean
}
const DEFAULT_SYSTEM_SOURCE_ID = '__default_system_output__'
function getErrorMessage(error: unknown, fallback: string): string {
return error instanceof Error && error.message ? error.message : fallback
}
async function handleTrayCommand(command: TrayRendererCommand): Promise<void> {
if (command.type === 'open-settings') {
useUiStore.getState().setSettingsOpen(true)
return
}
if (command.type === 'load-profile') {
const settings = useSettingsStore.getState()
try {
await settings.guardProfileTransition(async () => {
await useSettingsStore.getState().loadProfile(command.profileId)
})
} catch (error) {
useUiStore.getState().showBanner({
tone: 'error',
message: getErrorMessage(error, 'Could not load the profile.'),
actions: [],
})
}
return
}
const audio = useAudioStore.getState()
if (command.type === 'select-system-source') {
await audio.selectSystemSource(command.sourceId)
await useAudioStore.getState().startCapture()
return
}
if (command.type === 'select-input-source') {
await audio.selectDevice(command.deviceId)
await useAudioStore.getState().startCapture()
return
}
if (command.type === 'set-capture-running') {
if (command.running) {
await audio.startCapture()
} else {
audio.stopCapture()
}
}
}
export default function TrayControlBridge({ ready }: TrayControlBridgeProps): JSX.Element | null {
const profiles = useSettingsStore((state) => state.profiles)
const activeProfileId = useSettingsStore((state) => state.activeProfileId)
const hasUnsavedProfileChanges = useSettingsStore((state) => state.hasUnsavedProfileChanges)
const captureStatus = useAudioStore((state) => state.captureStatus)
const activeSourceLabel = useAudioStore((state) => state.activeSourceLabel)
const captureMode = useAudioStore((state) => state.captureMode)
const selectedSystemSourceId = useAudioStore((state) => state.selectedSystemSourceId)
const selectedDeviceId = useAudioStore((state) => state.selectedDeviceId)
const systemSources = useAudioStore((state) => state.systemSources)
const devices = useAudioStore((state) => state.devices)
useEffect(() => {
const unsubscribe = window.electronAPI.trayControls.onCommand((command) => {
void handleTrayCommand(command).catch((error) => {
useUiStore.getState().showBanner({
tone: 'error',
message: getErrorMessage(error, 'The tray action could not be completed.'),
actions: [],
})
})
})
return unsubscribe
}, [])
useEffect(() => {
if (!ready) {
window.electronAPI.trayControls.markNotReady()
return
}
window.electronAPI.trayControls.markReady()
return () => window.electronAPI.trayControls.markNotReady()
}, [ready])
useEffect(() => {
const visibleSystemSources = systemSources.length > 0
? systemSources
: [{ id: DEFAULT_SYSTEM_SOURCE_ID, label: 'Default Output', isDefault: true }]
const state: TrayRendererState = {
profiles: Object.entries(profiles).map(([id, profile]) => ({ id, name: profile.name })),
activeProfileId,
hasUnsavedProfileChanges,
captureStatus,
activeSourceLabel,
captureMode,
selectedSystemSourceId,
selectedDeviceId,
systemSources: visibleSystemSources.map((source) => ({
id: source.id,
label: source.label,
isDefault: source.isDefault,
})),
inputSources: [
{ id: '', label: 'Default Input', isDefault: true },
...devices
.filter((device) => device.deviceId !== 'default')
.map((device) => ({
id: device.deviceId,
label: device.label || `Input ${device.deviceId.slice(0, 8)}`,
})),
],
}
window.electronAPI.trayControls.publishState(state)
}, [
activeProfileId,
activeSourceLabel,
captureMode,
captureStatus,
devices,
hasUnsavedProfileChanges,
profiles,
selectedDeviceId,
selectedSystemSourceId,
systemSources,
])
return null
}