diff --git a/autoswitch.go b/autoswitch.go index 9b3625a..d380040 100644 --- a/autoswitch.go +++ b/autoswitch.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" "sync" + "sync/atomic" "time" "streamdeck-lets-go/internal/config" @@ -357,6 +358,7 @@ type AutoSwitchManager struct { lastManualPage string autoPage string autoStay bool + paused atomic.Bool // disables auto-switch when device is disconnected } type compiledRule struct { @@ -410,6 +412,25 @@ func (m *AutoSwitchManager) NotifyManualPage(name string) { slog.Debug("auto-switch: manual page set", "page", name) } +// Pause disables auto-switch evaluation — use when the device is disconnected +// to prevent unnecessary page switches on a closed deck. +func (m *AutoSwitchManager) Pause() { + m.paused.Store(true) + slog.Debug("auto-switch: paused") +} + +// Resume re-enables auto-switch evaluation after reconnect. +// Stale window events should be drained from the channel before resuming. +func (m *AutoSwitchManager) Resume() { + m.paused.Store(false) + slog.Debug("auto-switch: resumed") +} + +// IsPaused returns true when auto-switch evaluation is paused. +func (m *AutoSwitchManager) IsPaused() bool { + return m.paused.Load() +} + func (m *AutoSwitchManager) Evaluate(win Window, currentPage string) (page string, shouldSwitch bool) { m.mu.Lock() defer m.mu.Unlock() diff --git a/daemon.go b/daemon.go index 9d38b5b..056297f 100644 --- a/daemon.go +++ b/daemon.go @@ -143,15 +143,27 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error { case evt, ok := <-primaryDeck.Events(): if !ok { slog.Warn("deck event channel closed, attempting reconnect...") + + // Stop all periodic scripts to avoid wasting cycles on a closed device. + primaryPM.stopPeriodicKeys() + asm.Pause() + primaryDeck.Close() - newDeck := reconnectDeck(ctx, cfg, &primaryPM, asm) + newDeck := reconnectDeck(ctx, cfg, &primaryPM) if newDeck == nil { return ctx.Err() } + + // Update all references to the new device and page manager. + pageMgrs[0] = primaryPM decks[0] = newDeck primaryDeck = newDeck web.SetDecks(decks) + web.SetPageManager(primaryPM) + newDeck.SetBrightness(deviceBrightness(cfg, newDeck.Serial())) + asm.NotifyManualPage(cfg.DefaultPage) + asm.Resume() continue } @@ -194,6 +206,13 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error { } case win := <-windowCh: + if asm.IsPaused() { + // Drain buffered window events accumulated while disconnected. + for len(windowCh) > 0 { + <-windowCh + } + continue + } if page, ok := asm.Evaluate(win, primaryPM.ActivePageName()); ok { for _, pm := range pageMgrs { if err := pm.ActivatePage(page); err != nil { @@ -263,15 +282,27 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error { case <-reconnectTicker.C: if err := primaryDeck.SetBrightness(primaryDeck.Brightness()); err != nil { slog.Warn("deck connection lost, reconnecting...", "error", err) + + // Stop all periodic scripts to avoid wasting cycles on a closed device. + primaryPM.stopPeriodicKeys() + asm.Pause() + primaryDeck.Close() - newDeck := reconnectDeck(ctx, cfg, &primaryPM, asm) + newDeck := reconnectDeck(ctx, cfg, &primaryPM) if newDeck == nil { return ctx.Err() } + + // Update all references to the new device and page manager. + pageMgrs[0] = primaryPM decks[0] = newDeck primaryDeck = newDeck web.SetDecks(decks) + web.SetPageManager(primaryPM) + newDeck.SetBrightness(deviceBrightness(cfg, newDeck.Serial())) + asm.NotifyManualPage(cfg.DefaultPage) + asm.Resume() } case <-ssTicker.C: @@ -322,7 +353,7 @@ func deviceBrightness(cfg *config.Config, serial string) int { return 75 } -func reconnectDeck(ctx context.Context, cfg *config.Config, pm **PageManager, asm *AutoSwitchManager) *Deck { +func reconnectDeck(ctx context.Context, cfg *config.Config, pm **PageManager) *Deck { for { newDeck, err := OpenDeck("") if err == nil { @@ -332,7 +363,6 @@ func reconnectDeck(ctx context.Context, cfg *config.Config, pm **PageManager, as (*pm).LoadPages(cfg.Pages) (*pm).ActivatePage(cfg.DefaultPage) (*pm).startPeriodicKeys() - asm.NotifyManualPage(cfg.DefaultPage) return newDeck } select {