aboutsummaryrefslogtreecommitdiff
path: root/widgets/terminal.go
diff options
context:
space:
mode:
Diffstat (limited to 'widgets/terminal.go')
-rw-r--r--widgets/terminal.go179
1 files changed, 179 insertions, 0 deletions
diff --git a/widgets/terminal.go b/widgets/terminal.go
new file mode 100644
index 0000000..4cf7d9a
--- /dev/null
+++ b/widgets/terminal.go
@@ -0,0 +1,179 @@
+package widgets
+
+import (
+ "os"
+ "os/exec"
+
+ "git.sr.ht/~sircmpwn/aerc2/lib/ui"
+
+ "git.sr.ht/~sircmpwn/go-libvterm"
+ "github.com/gdamore/tcell"
+ "github.com/kr/pty"
+)
+
+type Terminal struct {
+ closed bool
+ cmd *exec.Cmd
+ ctx *ui.Context
+ cursorPos vterm.Pos
+ cursorShown bool
+ damage []vterm.Rect
+ focus bool
+ onInvalidate func(d ui.Drawable)
+ pty *os.File
+ vterm *vterm.VTerm
+}
+
+func NewTerminal(cmd *exec.Cmd) (*Terminal, error) {
+ term := &Terminal{}
+ term.cmd = cmd
+ tty, err := pty.Start(cmd)
+ if err != nil {
+ return nil, err
+ }
+ term.pty = tty
+ rows, cols, err := pty.Getsize(term.pty)
+ if err != nil {
+ return nil, err
+ }
+ term.vterm = vterm.New(rows, cols)
+ term.vterm.SetUTF8(true)
+ go func() {
+ buf := make([]byte, 2048)
+ for {
+ n, err := term.pty.Read(buf)
+ if err != nil {
+ term.Close()
+ }
+ n, err = term.vterm.Write(buf[:n])
+ if err != nil {
+ term.Close()
+ }
+ term.Invalidate()
+ }
+ }()
+ screen := term.vterm.ObtainScreen()
+ screen.OnDamage = term.onDamage
+ screen.OnMoveCursor = term.onMoveCursor
+ screen.Reset(true)
+ return term, nil
+}
+
+func (term *Terminal) Close() {
+ if term.closed {
+ return
+ }
+ term.closed = true
+ term.vterm.Close()
+ term.pty.Close()
+ term.cmd.Process.Kill()
+}
+
+func (term *Terminal) OnInvalidate(cb func(d ui.Drawable)) {
+ term.onInvalidate = cb
+}
+
+func (term *Terminal) Invalidate() {
+ if term.onInvalidate != nil {
+ term.onInvalidate(term)
+ }
+}
+
+func (term *Terminal) Draw(ctx *ui.Context) {
+ term.ctx = ctx // gross
+ if term.closed {
+ return
+ }
+
+ rows, cols, err := pty.Getsize(term.pty)
+ if err != nil {
+ return
+ }
+ if ctx.Width() != cols || ctx.Height() != rows {
+ winsize := pty.Winsize{
+ Cols: uint16(ctx.Width()),
+ Rows: uint16(ctx.Height()),
+ }
+ pty.Setsize(term.pty, &winsize)
+ term.vterm.SetSize(ctx.Height(), ctx.Width())
+ return
+ }
+
+ screen := term.vterm.ObtainScreen()
+ screen.Flush()
+
+ type coords struct {
+ x int
+ y int
+ }
+
+ // naive optimization
+ visited := make(map[coords]interface{})
+
+ for _, rect := range term.damage {
+ for x := rect.StartCol(); x < rect.EndCol() && x < ctx.Width(); x += 1 {
+
+ for y := rect.StartCol(); y < rect.EndCol() && y < ctx.Height(); y += 1 {
+
+ coords := coords{x, y}
+ if _, ok := visited[coords]; ok {
+ continue
+ }
+ visited[coords] = nil
+
+ cell, err := screen.GetCellAt(y, x)
+ if err != nil {
+ continue
+ }
+ style := styleFromCell(cell)
+ ctx.Printf(x, y, style, "%s", string(cell.Chars()))
+ }
+ }
+ }
+}
+
+func (term *Terminal) Focus(focus bool) {
+ term.focus = focus
+ term.resetCursor()
+}
+
+func (term *Terminal) Event(event tcell.Event) bool {
+ // TODO
+ return false
+}
+
+func styleFromCell(cell *vterm.ScreenCell) tcell.Style {
+ background := cell.Bg()
+ br, bg, bb := background.GetRGB()
+ foreground := cell.Fg()
+ fr, fg, fb := foreground.GetRGB()
+ style := tcell.StyleDefault.
+ Background(tcell.NewRGBColor(int32(br), int32(bg), int32(bb))).
+ Foreground(tcell.NewRGBColor(int32(fr), int32(fg), int32(fb)))
+ return style
+}
+
+func (term *Terminal) onDamage(rect *vterm.Rect) int {
+ term.damage = append(term.damage, *rect)
+ term.Invalidate()
+ return 1
+}
+
+func (term *Terminal) resetCursor() {
+ if term.ctx != nil && term.focus {
+ if !term.cursorShown {
+ term.ctx.HideCursor()
+ } else {
+ term.ctx.SetCursor(term.cursorPos.Col(), term.cursorPos.Row())
+ }
+ }
+}
+
+func (term *Terminal) onMoveCursor(old *vterm.Pos,
+ pos *vterm.Pos, visible bool) int {
+
+ term.cursorShown = visible
+ term.cursorPos = *pos
+ term.resetCursor()
+ return 1
+}