//go:build windows // backend\tray.go package main import ( "context" _ "embed" "os/exec" "path/filepath" "runtime" "strings" "time" "github.com/getlantern/systray" ) //go:embed assets/img/tray.ico var trayIcon []byte func runTray(ctx context.Context, onQuit func(), statsFn func() TrayStats) { systray.Run(func() { if len(trayIcon) > 0 { systray.SetIcon(trayIcon) } systray.SetTitle("Recorder") systray.SetTooltip("Recorder Backend läuft") mStatus := systray.AddMenuItem("Status wird geladen…", "Aktueller Recorder-Status") mStatus.Disable() mAutostart := systray.AddMenuItem("Autostart: …", "Autostart-Status") mAutostart.Disable() mDownloads := systray.AddMenuItem("Downloads: …", "Download-Status") mDownloads.Disable() mPostwork := systray.AddMenuItem("Postwork: …", "Postwork-Status") mPostwork.Disable() mEnrich := systray.AddMenuItem("Enrich: …", "Enrich-Status") mEnrich.Disable() mDisk := systray.AddMenuItem("Freier Speicher: …", "Speicherplatz") mDisk.Disable() mUptime := systray.AddMenuItem("Uptime: …", "Laufzeit") mUptime.Disable() systray.AddSeparator() mOpenFrontend := systray.AddMenuItem("Frontend öffnen", "Öffnet das Web-Frontend") mOpenRecordDir := systray.AddMenuItem("Record-Ordner öffnen", "Öffnet den Aufnahmeordner") mOpenDoneDir := systray.AddMenuItem("Done-Ordner öffnen", "Öffnet den Done-Ordner") systray.AddSeparator() mToggleAutostart := systray.AddMenuItem("Autostart pausieren", "Autostart pausieren oder fortsetzen") mStopAll := systray.AddMenuItem( "Alle Downloads stoppen + Autostart pausieren", "Stoppt alle aktiven Downloads und pausiert den Autostart", ) systray.AddSeparator() mQuit := systray.AddMenuItem("Beenden", "Backend sauber beenden") updateStatus := func() { if statsFn == nil { return } stats := statsFn() mStatus.SetTitle(stats.MenuTitle()) mAutostart.SetTitle(stats.AutostartLine()) mDownloads.SetTitle(stats.DownloadsLine()) mPostwork.SetTitle(stats.PostworkLine()) mEnrich.SetTitle(stats.EnrichLine()) mDisk.SetTitle(stats.DiskLine()) mUptime.SetTitle(stats.UptimeLine()) mToggleAutostart.SetTitle(stats.ToggleAutostartTitle()) if stats.AutostartPausedByDisk { mToggleAutostart.Disable() } else { mToggleAutostart.Enable() } if stats.ActiveDownloads > 0 || !stats.AutostartPaused { mStopAll.Enable() } else { mStopAll.Disable() } systray.SetTooltip(stats.Tooltip()) } updateStatus() go func() { <-ctx.Done() systray.Quit() }() go func() { ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: updateStatus() case <-mOpenFrontend.ClickedCh: _ = openBrowser("https://l14pbbk95100006.tegdssd.de:9999") case <-mOpenRecordDir.ClickedCh: _ = openFolder(resolveConfiguredDir(getSettings().RecordDir)) case <-mOpenDoneDir.ClickedCh: _ = openFolder(resolveConfiguredDir(getSettings().DoneDir)) case <-mToggleAutostart.ClickedCh: state := getAutostartStatePayload() if state.PausedByDisk { updateStatus() continue } if state.Paused { setAutostartPaused(false) } else { setAutostartPaused(true) } updateStatus() case <-mStopAll.ClickedCh: _ = stopAllDownloadsAndPauseAutostart() updateStatus() case <-mQuit.ClickedCh: onQuit() systray.Quit() return } } }() }, func() { // optional cleanup }) } func stopAllDownloadsAndPauseAutostart() int { // Immer zuerst Autostart pausieren, damit nichts direkt wieder neu startet. setAutostartPaused(true) // Danach aktive Downloads stoppen. return stopAllActiveDownloads() } func stopAllActiveDownloads() int { jobsMu.RLock() toStop := make([]*RecordJob, 0, len(jobs)) for _, job := range jobs { if job == nil { continue } if isPostworkJob(job) { continue } if isTerminalJobStatus(job.Status) { continue } if job.EndedAt != nil { continue } toStop = append(toStop, job) } jobsMu.RUnlock() if len(toStop) > 0 { stopJobsInternal(toStop) } return len(toStop) } func resolveConfiguredDir(raw string) string { raw = strings.TrimSpace(raw) if raw == "" { return "" } if abs, err := resolvePathRelativeToApp(raw); err == nil && strings.TrimSpace(abs) != "" { return abs } return raw } func openFolder(dir string) error { dir = filepath.Clean(strings.TrimSpace(dir)) if dir == "" { return nil } switch runtime.GOOS { case "windows": return exec.Command("explorer", dir).Start() case "darwin": return exec.Command("open", dir).Start() default: return exec.Command("xdg-open", dir).Start() } } func openBrowser(url string) error { switch runtime.GOOS { case "windows": return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() case "darwin": return exec.Command("open", url).Start() default: return exec.Command("xdg-open", url).Start() } }