package daemon import ( "github.com/bnema/vev/internal/domain/terminalcap" "github.com/bnema/vev/internal/usecase/theme" ) func (d *Daemon) applyHostTheme(sess *session, ac *attachedClient, t theme.Theme, clearUnknownScheme bool) bool { if ac != nil { ac.sendMu.Lock() defer ac.sendMu.Unlock() } sess.themeMu.Lock() defer sess.themeMu.Unlock() return d.applyHostThemeLocked(sess, ac, t, clearUnknownScheme) } // applyHostThemeLocked requires sess.themeMu (and ac.sendMu when ac is non-nil), // not d.mu. func (d *Daemon) applyHostThemeLocked(sess *session, ac *attachedClient, t theme.Theme, clearUnknownScheme bool) bool { // Resolve while applying, never while composing. The theme mutex only // publishes the completed value and is not held across session/tab/pane // locks, preserving the daemon lock order. var color terminalcap.ColorCapabilities if ac != nil { color = ac.terminalCapabilities.Color } applied := d.resolveAppliedTheme(t, color) t = applied.Raw sess.mu.Lock() if ac != nil { if _, attached := sess.attachments[ac]; !attached { sess.mu.Unlock() return false } } tabs := append([]*tab(nil), sess.tabs...) sess.mu.Unlock() if ac != nil { ac.setAppliedTheme(applied) } for _, tb := range tabs { tb.mu.Lock() panes := tb.panesSnapshot() if floating := tb.floating.pane; floating != nil { panes = append(panes, floating) } tb.mu.Unlock() for _, p := range panes { p.mu.Lock() applyPaneThemeLocked(p, t, clearUnknownScheme) p.mu.Unlock() } } // An unclaimed spare answers color queries too, so it follows the theme. if spare := sess.floatingSparePane(); spare != nil { spare.mu.Lock() applyPaneThemeLocked(spare, t, clearUnknownScheme) spare.mu.Unlock() } return true } func applyPaneThemeLocked(p *pane, t theme.Theme, clearUnknownScheme bool) { p.screen.SetDefaultColors(t.Foreground, t.Background, t.HasFG && t.HasBG) if t.SchemeKnown { p.screen.SetColorScheme(t.Light) } else if clearUnknownScheme { p.screen.ClearColorScheme() } }