Skip to content

Commit

Permalink
fix: manually query terminal for background color
Browse files Browse the repository at this point in the history
This drops the dependency on `github.com/charmbracelet/x/input` and
`github.com/erikgeiser/coninput` by manually parsing the terminal
response to the background color query using `ansi`.

Since we moved `x/input` to Bubble Tea, and the code is no longer
maintained, we need a different approach to query the terminal for the
background color. This is basically doing the same thing as before, but
manually parsing the response instead of using the `x/input` package.

Fixes: charmbracelet/x#248
Supersedes: charmbracelet/x#249
  • Loading branch information
aymanbagabas committed Nov 8, 2024
1 parent c8e32d1 commit 2f5f1df
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 23 deletions.
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ require (
github.com/charmbracelet/colorprofile v0.1.2
github.com/charmbracelet/x/ansi v0.4.2
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a
github.com/charmbracelet/x/input v0.2.0
github.com/charmbracelet/x/term v0.2.0
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/muesli/cancelreader v0.2.2
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.24.0
)

require (
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
)
6 changes: 1 addition & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@ github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41k
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/input v0.2.0 h1:1Sv+y/flcqUfUH2PXNIDKDIdT2G8smOnGOgawqhwy8A=
github.com/charmbracelet/x/input v0.2.0/go.mod h1:KUSFIS6uQymtnr5lHVSOK9j8RvwTD4YHnWnzJUYnd/M=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
Expand All @@ -21,6 +17,6 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
87 changes: 72 additions & 15 deletions terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"fmt"
"image/color"
"io"
"strconv"
"strings"
"time"

"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/input"
"github.com/charmbracelet/x/ansi/parser"
"github.com/muesli/cancelreader"
)

// queryBackgroundColor queries the terminal for the background color.
Expand All @@ -24,13 +27,20 @@ import (
func queryBackgroundColor(in io.Reader, out io.Writer) (c color.Color, err error) {
//nolint: errcheck
err = queryTerminal(in, out, defaultQueryTimeout,
func(events []input.Event) bool {
for _, e := range events {
switch e := e.(type) {
case input.BackgroundColorEvent:
c = e.Color
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
func(seq string, pa *ansi.Parser) bool {
switch {
case ansi.HasOscPrefix(seq):
switch pa.Cmd {
case 11: // OSC 11

Check failure on line 34 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 11, in <case> detected (mnd)

Check failure on line 34 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 11, in <case> detected (mnd)

Check failure on line 34 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 11, in <case> detected (mnd)

Check failure on line 34 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 11, in <case> detected (mnd)

Check failure on line 34 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 11, in <case> detected (mnd)

Check failure on line 34 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 11, in <case> detected (mnd)
parts := strings.Split(string(pa.Data[:pa.DataLen]), ";")
if len(parts) != 2 {

Check failure on line 36 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 2, in <condition> detected (mnd)

Check failure on line 36 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 2, in <condition> detected (mnd)

Check failure on line 36 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 2, in <condition> detected (mnd)

Check failure on line 36 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 2, in <condition> detected (mnd)

Check failure on line 36 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 2, in <condition> detected (mnd)

Check failure on line 36 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 2, in <condition> detected (mnd)
break // invalid, but we still need to parse the next sequence
}
c = xParseColor(parts[1])
}
case ansi.HasCsiPrefix(seq):
switch pa.Cmd {
case 'c' | '?'<<parser.MarkerShift: // DA1
return false
}
}
Expand All @@ -44,7 +54,7 @@ const defaultQueryTimeout = time.Second * 2
// queryTerminalFilter is a function that filters input events using a type
// switch. If false is returned, the QueryTerminal function will stop reading
// input.
type queryTerminalFilter func(events []input.Event) bool
type queryTerminalFilter func(seq string, pa *ansi.Parser) bool

// queryTerminal queries the terminal for support of various features and
// returns a list of response events.
Expand All @@ -60,9 +70,9 @@ func queryTerminal(
filter queryTerminalFilter,
query string,
) error {
rd, err := input.NewDriver(in, "", 0)
rd, err := cancelreader.NewReader(in)
if err != nil {
return fmt.Errorf("could not create driver: %w", err)
return fmt.Errorf("could not create cancel reader: %w", err)
}

defer rd.Close() //nolint: errcheck
Expand All @@ -81,16 +91,63 @@ func queryTerminal(
return fmt.Errorf("could not write query: %w", err)
}

pa := ansi.GetParser()
defer ansi.PutParser(pa)

var buf [256]byte // 256 bytes should be enough for most responses
for {
events, err := rd.ReadEvents()
n, err := rd.Read(buf[:])
if err != nil {
return fmt.Errorf("could not read events: %s", err)
return fmt.Errorf("could not read from input: %w", err)
}

if !filter(events) {
break
var state byte
p := buf[:]
for n > 0 {
seq, _, read, newState := ansi.DecodeSequence(p[:n], state, pa)
if !filter(string(seq), pa) {
return nil
}

state = newState
n -= read
p = p[read:]
}
}
}

func shift(x uint64) uint64 {
if x > 0xff {

Check failure on line 120 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 0xff, in <condition> detected (mnd)

Check failure on line 120 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 0xff, in <condition> detected (mnd)

Check failure on line 120 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 0xff, in <condition> detected (mnd)

Check failure on line 120 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 0xff, in <condition> detected (mnd)

Check failure on line 120 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 0xff, in <condition> detected (mnd)

Check failure on line 120 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 0xff, in <condition> detected (mnd)
x >>= 8
}
return x
}

func xParseColor(s string) color.Color {
switch {
case strings.HasPrefix(s, "rgb:"):
parts := strings.Split(s[4:], "/")
if len(parts) != 3 {

Check failure on line 130 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 3, in <condition> detected (mnd)

Check failure on line 130 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 3, in <condition> detected (mnd)

Check failure on line 130 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 3, in <condition> detected (mnd)

Check failure on line 130 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 3, in <condition> detected (mnd)

Check failure on line 130 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 3, in <condition> detected (mnd)

Check failure on line 130 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 3, in <condition> detected (mnd)
return color.Black
}

r, _ := strconv.ParseUint(parts[0], 16, 32)
g, _ := strconv.ParseUint(parts[1], 16, 32)
b, _ := strconv.ParseUint(parts[2], 16, 32)

return color.RGBA{uint8(shift(r)), uint8(shift(g)), uint8(shift(b)), 255} //nolint:gosec
case strings.HasPrefix(s, "rgba:"):
parts := strings.Split(s[5:], "/")
if len(parts) != 4 {

Check failure on line 141 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 4, in <condition> detected (mnd)

Check failure on line 141 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 4, in <condition> detected (mnd)

Check failure on line 141 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 4, in <condition> detected (mnd)

Check failure on line 141 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 4, in <condition> detected (mnd)

Check failure on line 141 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 4, in <condition> detected (mnd)

Check failure on line 141 in terminal.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 4, in <condition> detected (mnd)
return color.Black
}

r, _ := strconv.ParseUint(parts[0], 16, 32)
g, _ := strconv.ParseUint(parts[1], 16, 32)
b, _ := strconv.ParseUint(parts[2], 16, 32)
a, _ := strconv.ParseUint(parts[3], 16, 32)

return color.RGBA{uint8(shift(r)), uint8(shift(g)), uint8(shift(b)), uint8(shift(a))} //nolint:gosec
}
return nil
}

0 comments on commit 2f5f1df

Please sign in to comment.