query.go

 1package lipgloss
 2
 3import (
 4	"fmt"
 5	"image/color"
 6	"os"
 7	"runtime"
 8
 9	"github.com/charmbracelet/x/term"
10)
11
12func backgroundColor(in term.File, out term.File) (color.Color, error) {
13	state, err := term.MakeRaw(in.Fd())
14	if err != nil {
15		return nil, fmt.Errorf("error setting raw state to detect background color: %w", err)
16	}
17
18	defer term.Restore(in.Fd(), state) //nolint:errcheck
19
20	bg, err := queryBackgroundColor(in, out)
21	if err != nil {
22		return nil, err
23	}
24
25	return bg, nil
26}
27
28// BackgroundColor queries the terminal's background color. Typically, you'll
29// want to query against stdin and either stdout or stderr, depending on what
30// you're writing to.
31//
32// This function is intended for standalone Lip Gloss use only. If you're using
33// Bubble Tea, listen for tea.BackgroundColorMsg in your update function.
34func BackgroundColor(in term.File, out term.File) (bg color.Color, err error) {
35	if runtime.GOOS == "windows" { //nolint:nestif
36		// On Windows, when the input/output is redirected or piped, we need to
37		// open the console explicitly.
38		// See https://learn.microsoft.com/en-us/windows/console/getstdhandle#remarks
39		if !term.IsTerminal(in.Fd()) {
40			f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec
41			if err != nil {
42				return nil, fmt.Errorf("error opening CONIN$: %w", err)
43			}
44			in = f
45		}
46		if !term.IsTerminal(out.Fd()) {
47			f, err := os.OpenFile("CONOUT$", os.O_RDWR, 0o644) //nolint:gosec
48			if err != nil {
49				return nil, fmt.Errorf("error opening CONOUT$: %w", err)
50			}
51			out = f
52		}
53		return backgroundColor(in, out)
54	}
55
56	// NOTE: On Unix, one of the given files must be a tty.
57	for _, f := range []term.File{in, out} {
58		if bg, err = backgroundColor(f, f); err == nil {
59			return bg, nil
60		}
61	}
62
63	return
64}
65
66// HasDarkBackground detects whether the terminal has a light or dark
67// background.
68//
69// Typically, you'll want to query against stdin and either stdout or stderr
70// depending on what you're writing to.
71//
72//	hasDarkBG := HasDarkBackground(os.Stdin, os.Stdout)
73//	lightDark := LightDark(hasDarkBG)
74//	myHotColor := lightDark("#ff0000", "#0000ff")
75//
76// This is intended for use in standalone Lip Gloss only. In Bubble Tea, listen
77// for tea.BackgroundColorMsg in your Update function.
78//
79//	case tea.BackgroundColorMsg:
80//	    hasDarkBackground = msg.IsDark()
81//
82// By default, this function will return true if it encounters an error.
83func HasDarkBackground(in term.File, out term.File) bool {
84	bg, err := BackgroundColor(in, out)
85	if err != nil || bg == nil {
86		return true
87	}
88	return isDarkColor(bg)
89}