Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/img2term/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"flag"
"fmt"
"image"
"io"
"log"
Expand All @@ -13,10 +14,16 @@ import (

"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/ansi/sixel"
"github.com/charmbracelet/x/graphics"
)

// $ go run . ./../../ansi/fixtures/graphics/JigokudaniMonkeyPark.png
func main() {
imageProtocols := graphics.DetectImageProtocols()
fmt.Println("sixel supported:", imageProtocols.Sixel)
fmt.Println("iTerm2 supported:", imageProtocols.ITerm2)
fmt.Println("kitty supported", imageProtocols.Kitty)

flag.Parse()
args := flag.Args()
if len(args) == 0 {
Expand Down
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use (
./exp/strings
./exp/teatest
./exp/teatest/v2
./graphics
./input
./json
./mosaic
Expand Down
111 changes: 111 additions & 0 deletions graphics/detect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package graphics

import (
"bytes"
"os"
"time"

"github.com/charmbracelet/x/term"
)

// TODO: Verify if it's running with tmux for Kitty and ITerm2
// TODO: Write a func for preferred protocol for the terminal
// TODO: Additional check if the terminal supports cell-size by `[16t` or `[14t`
// TODO: Write tests by mocking a terminal context ?

const (
termProgramVariable = "TERM_PROGRAM"
lcTerminalVariable = "LC_TERMINAL"
)

// Returns the availability of each image protocol.
type ImageProtocols struct {
Sixel bool
ITerm2 bool
Kitty bool
// Mosaic (Halfblocks) should work in all terminals,
// even if the font size could not be detected, with a 4:8 pixel ratio.
Mosaic bool
}

// Detect all availables image protocols and return as [ImageProtocols].
func DetectImageProtocols() ImageProtocols {
return ImageProtocols{
Sixel: detectSixel(),
// TODO: `_Gi=...`: Kitty graphics support.
Kitty: detectKitty(),
// TODO: `[1337n`: iTerm2 (some terminals implement the protocol but sadly not this custom CSI)
ITerm2: detectIterm2() || detectIterm2FromEnv(),
Mosaic: true,
}
}

func detectKitty() bool {
return false
}

func detectIterm2() bool {
return false
}

// This function detects iTerm2 protocol from environment variable.
func detectIterm2FromEnv() bool {
termProgram := os.Getenv(termProgramVariable)
if termProgram == "iTerm" ||
termProgram == "WezTerm" ||
termProgram == "mintty" ||
termProgram == "vscode" ||
termProgram == "Tabby" ||
termProgram == "Hyper" ||
termProgram == "rio" {
return true
}

lcTerminal := os.Getenv(lcTerminalVariable)
return lcTerminal == "iTerm"
}

func detectSixel() bool {
sixelSupportedTerminals := []string{
"\x1b[?62;", // VT240
"\x1b[?63;", // wsltty
"\x1b[?64;", // mintty
"\x1b[?65;", // RLogin
// NOTE: tmux does not return VT name.
"\x1b[?1;2;4c", // Tmux
}

if term.IsTerminal(os.Stdout.Fd()) {
return true
}
s, err := term.MakeRaw(1)
if err == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err != nil should probably stop here?

defer term.Restore(1, s) // nolint:errcheck
}
_, err = os.Stdout.Write([]byte("\x1b[c"))
if err != nil {
return false
}
defer os.Stdout.SetReadDeadline(time.Time{}) // nolint:errcheck

var b [100]byte
n, err := os.Stdout.Read(b[:])
if err != nil {
return false
}

for _, t := range bytes.Split(b[6:n], []byte(";")) {
// Check if 4 is present in terminal capabilities.
if len(t) == 1 && t[0] == '4' {
return true
}
}

for _, supportedTerminal := range sixelSupportedTerminals {
if bytes.HasPrefix(b[:n], []byte(supportedTerminal)) {
return true
}
}

return false
}
7 changes: 7 additions & 0 deletions graphics/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/charmbracelet/x/graphics

go 1.21

require github.com/charmbracelet/x/term v0.2.1

require golang.org/x/sys v0.26.0 // indirect
4 changes: 4 additions & 0 deletions graphics/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=