From 2f5f1df3f67fd214f8b98ac89b9852e277f76e69 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 7 Nov 2024 21:47:12 -0500 Subject: [PATCH] fix: manually query terminal for background color 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: https://github.com/charmbracelet/x/issues/248 Supersedes: https://github.com/charmbracelet/x/pull/249 --- go.mod | 5 ++- go.sum | 6 +--- terminal.go | 87 ++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index ae24ce9b..d1367dd0 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 37bca442..53444c90 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/terminal.go b/terminal.go index d5c663e6..5e27cabe 100644 --- a/terminal.go +++ b/terminal.go @@ -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. @@ -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 + parts := strings.Split(string(pa.Data[:pa.DataLen]), ";") + if len(parts) != 2 { + break // invalid, but we still need to parse the next sequence + } + c = xParseColor(parts[1]) + } + case ansi.HasCsiPrefix(seq): + switch pa.Cmd { + case 'c' | '?'< 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 { + 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 { + 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 { + 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 }