diff --git a/CHANGELOG.md b/CHANGELOG.md index db3c122..53940cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ - **The terminal's own background:** the panes used color 0 of the terminal's palette ("black"), which many color themes set to a grey lighter than their background (e.g. Catppuccin), so the whole program looked washed out and grey text almost disappeared. The terminal is now asked for - its background color at startup and that is used (with 24-bit color and a dark background). + its background color at startup and that is used (with 24-bit color and a dark background). The + Log says which background is used and why. - **Smooth scrolling on huge terminals:** while an arrow key is held, the poster waits until the selection rests (120 ms). Every poster rewrites all of its cells, which on a terminal zoomed far out was more output per step than the terminal could draw, so moving through a list lagged. diff --git a/SPECS.md b/SPECS.md index 0329a3e..68cd861 100644 --- a/SPECS.md +++ b/SPECS.md @@ -103,7 +103,7 @@ Layout sketch (Library view; the other views follow the same frame): Popups for action feedback appear in the bottom right and fade away; the Log view (F7) keeps the history. -Colors: the panes' background is the terminal's own background color (asked with OSC 11 at startup) when the terminal has 24-bit color and a dark background; otherwise tview's default, palette color 0. Color themes often make color 0 a grey lighter than their background (Catppuccin: `#45475a` on `#1e1e2e`), on which grey text almost disappears. Light backgrounds keep color 0 because the text colors are made for dark ones. +Colors: the panes' background is the terminal's own background color (asked with OSC 11 at startup) when the terminal has 24-bit color (by tcell's rules: `TCELL_TRUECOLOR`, `COLORTERM`, or a terminal description with direct color such as `xterm-ghostty`, which Ghostty on macOS uses without setting `COLORTERM`) and a dark background, and the Log says which and why; otherwise tview's default, palette color 0. Color themes often make color 0 a grey lighter than their background (Catppuccin: `#45475a` on `#1e1e2e`), on which grey text almost disappears. Light backgrounds keep color 0 because the text colors are made for dark ones. tview primitives: `Pages` (views and stacked overlays), `Flex` (layout), `InputField` (search), `List`/`Table` (results, queues, library), `TextView` (details, log), `Form` (add dialog), `Modal` (confirmations). Popups are drawn by a wrapper around the root primitive. diff --git a/internal/ui/app.go b/internal/ui/app.go index dd5f2a3..16f24d7 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -102,7 +102,7 @@ type overlay struct { func New(cfg *config.Config) *App { // Ask the terminal before the screen takes it over and before any widget // copies the default background. - ownBackground := useTerminalBackground(terminalAnswers(), os.Getenv) + backgroundNote := useTerminalBackground(terminalAnswers(), os.Getenv) a := &App{ app: tview.NewApplication(), tabs: tview.NewTextView().SetDynamicColors(true), @@ -139,9 +139,7 @@ func New(cfg *config.Config) *App { if a.covers { a.logs.add(levelInfo, coverNote) } - if ownBackground { - a.logs.add(levelInfo, "Background: the terminal's own color") - } + a.logs.add(levelInfo, backgroundNote) sabClient := sabnzbd.NewClient(cfg.SABnzbd.URL, cfg.SABnzbd.APIKey) a.sab = sabClient a.probes = []probe{ diff --git a/internal/ui/terminal.go b/internal/ui/terminal.go index e69a541..cab75a1 100644 --- a/internal/ui/terminal.go +++ b/internal/ui/terminal.go @@ -2,6 +2,7 @@ package ui import ( "bytes" + "fmt" "image/color" "strconv" "strings" @@ -9,6 +10,7 @@ import ( "time" "github.com/gdamore/tcell/v2" + "github.com/gdamore/tcell/v2/terminfo" "github.com/rivo/tview" ) @@ -67,22 +69,42 @@ func parseTerminalAnswers(got []byte) (info terminalInfo) { // on which grey text almost disappears). Only for dark backgrounds: the text // colors are made for those. Needs true color, else the exact color cannot be // drawn. Must run before any widget is created, they copy the style. It -// reports whether it changed anything. -func useTerminalBackground(info terminalInfo, env func(string) string) bool { - if !info.hasBG || !hasTrueColor(env) || luminance(info.bg) > 0.4 { - return false +// returns a line for the log that says what was decided and why. +func useTerminalBackground(info terminalInfo, env func(string) string) string { + if !info.hasBG { + return "Background: palette color 0 (the terminal did not report its background color)" + } + hex := fmt.Sprintf("#%02x%02x%02x", info.bg.R, info.bg.G, info.bg.B) + switch { + case !hasTrueColor(env): + return "Background: palette color 0 (the terminal's " + hex + " needs 24-bit color, which " + env("TERM") + " does not have; COLORTERM=truecolor enables it)" + case luminance(info.bg) > 0.4: + return "Background: palette color 0 (the terminal's " + hex + " is light; the text colors are made for dark backgrounds)" } tview.Styles.PrimitiveBackgroundColor = tcell.NewRGBColor(int32(info.bg.R), int32(info.bg.G), int32(info.bg.B)) - return true + return "Background: the terminal's own color " + hex } -// hasTrueColor reports whether the terminal says it draws 24-bit colors. +// hasTrueColor reports whether tcell will draw 24-bit colors, by the same +// rules: TCELL_TRUECOLOR, COLORTERM, or a terminal description with direct +// color (e.g. xterm-ghostty and xterm-kitty, also without COLORTERM). func hasTrueColor(env func(string) string) bool { - switch strings.ToLower(env("COLORTERM")) { - case "truecolor", "24bit": + switch env("TCELL_TRUECOLOR") { + case "": + case "disable": + return false + default: return true } - return strings.HasSuffix(env("TERM"), "-direct") + switch strings.ToLower(env("COLORTERM")) { + case "truecolor", "24bit", "24-bit": + return true + } + if strings.HasSuffix(env("TERM"), "-direct") { + return true + } + ti, err := terminfo.LookupTerminfo(env("TERM")) + return err == nil && (ti.TrueColor || ti.SetFgBgRGB != "") } // luminance is the relative brightness of c, from 0 (black) to 1 (white). diff --git a/internal/ui/terminal_test.go b/internal/ui/terminal_test.go index 7a7eb91..95e5f9d 100644 --- a/internal/ui/terminal_test.go +++ b/internal/ui/terminal_test.go @@ -2,6 +2,7 @@ package ui import ( "image/color" + "strings" "testing" "github.com/gdamore/tcell/v2" @@ -31,6 +32,11 @@ func TestParseTerminalAnswers(t *testing.T) { } func TestUseTerminalBackground(t *testing.T) { + // tcell's terminal database also reads these from the real environment + // (and keeps what it added to an entry because of them, so the test uses + // a terminal nothing else looks up). + t.Setenv("COLORTERM", "") + t.Setenv("TCELL_TRUECOLOR", "") defer func(old tcell.Color) { tview.Styles.PrimitiveBackgroundColor = old }(tview.Styles.PrimitiveBackgroundColor) env := func(m map[string]string) func(string) string { return func(k string) string { return m[k] } } dark := terminalInfo{bg: color.RGBA{30, 30, 46, 255}, hasBG: true} @@ -44,13 +50,16 @@ func TestUseTerminalBackground(t *testing.T) { want bool }{ {"dark background, true color", dark, truecolor, true}, - {"no true color", dark, env(map[string]string{"TERM": "xterm-256color"}), false}, + {"no true color", dark, env(map[string]string{"TERM": "vt100"}), false}, + {"ghostty without COLORTERM", dark, env(map[string]string{"TERM": "xterm-ghostty"}), true}, + {"true color disabled for tcell", dark, env(map[string]string{"TERM": "xterm-ghostty", "TCELL_TRUECOLOR": "disable"}), false}, {"light background keeps the default", light, truecolor, false}, {"no answer", terminalInfo{}, truecolor, false}, } { tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack - if got := useTerminalBackground(tc.info, tc.env); got != tc.want { - t.Errorf("%s: %v", tc.name, got) + note := useTerminalBackground(tc.info, tc.env) + if got := strings.Contains(note, "own color"); got != tc.want { + t.Errorf("%s: %s", tc.name, note) } want := tcell.ColorBlack if tc.want {