mirror of https://github.com/liamg/aminal.git
177 lines
4.5 KiB
Go
177 lines
4.5 KiB
Go
package terminal
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"image/draw"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/liamg/aminal/matrix"
|
|
"github.com/liamg/aminal/sixel"
|
|
)
|
|
|
|
type boolFormRuneFunc func(rune) bool
|
|
|
|
func swallowByFunction(pty chan rune, isTerminator boolFormRuneFunc) {
|
|
for {
|
|
b := <-pty
|
|
if isTerminator(b) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func filter(src []rune) []rune {
|
|
result := make([]rune, 0, len(src))
|
|
for _, v := range src {
|
|
if v >= 33 {
|
|
result = append(result, v)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func sixelHandler(pty chan rune, terminal *Terminal) error {
|
|
debug := ""
|
|
|
|
// data := []rune{}
|
|
|
|
// track for Windows formatting workaround
|
|
scrollOffset := uint16(terminal.GetScrollOffset())
|
|
x := terminal.ActiveBuffer().CursorColumn() + 2 // reserve two bytes for Sixel prefix (ESC P)
|
|
y := terminal.ActiveBuffer().CursorLine()
|
|
scrollingLine := terminal.ActiveBuffer().ViewHeight() - 1
|
|
xStart := x
|
|
yStartWithOffset := y + scrollOffset
|
|
matrix := matrix.NewAutoMatrix() // a simplified version of Buffer
|
|
for {
|
|
b := <-pty
|
|
if b == 0x1b {
|
|
t := <-pty
|
|
if t == '[' { // Windows injected a CSI sequence
|
|
final, param, _ := loadCSI(pty)
|
|
|
|
if final == 'H' {
|
|
// position cursor
|
|
params := splitParams(param)
|
|
{
|
|
xT, yT := parseCursorPosition(params) // 1 based
|
|
x = uint16(xT - 1) // 0 based
|
|
y = uint16(yT - 1) // 0 based
|
|
}
|
|
}
|
|
debug += "[CSI " + param + string(final) + "]"
|
|
continue
|
|
}
|
|
if t == ']' { // Windows injected an OSC sequence
|
|
// TODO: pass through as if it came via normal stream
|
|
swallowByFunction(pty, terminal.IsOSCTerminator)
|
|
debug += "[OSC]"
|
|
continue
|
|
}
|
|
// if re-drawing a region beforethe start of sixel sequencce,
|
|
// ignore all possible ESC pairs (including ESC P)
|
|
if y+scrollOffset < yStartWithOffset || (y+scrollOffset == yStartWithOffset && x < xStart) {
|
|
x += 2
|
|
continue
|
|
}
|
|
if t != 0x07 && t != 0x5c {
|
|
return fmt.Errorf("Incorrect terminator in sixel sequence: 0x%02X [%c]", t, t)
|
|
}
|
|
break // terminated by ESC bell or ESC \
|
|
}
|
|
|
|
if b == 0x0d {
|
|
// skip
|
|
} else if b == 0x0a {
|
|
terminal.logger.Debugf("Sixel line: %s", debug)
|
|
debug = ""
|
|
if y == scrollingLine {
|
|
scrollOffset++
|
|
} else {
|
|
y++
|
|
}
|
|
x = 0
|
|
} else if y+scrollOffset < yStartWithOffset || (y+scrollOffset == yStartWithOffset && x < xStart) {
|
|
x++
|
|
} else if b < 32 {
|
|
x++ // always?
|
|
} else {
|
|
debug += string(b)
|
|
matrix.SetAt(b, int(x), int(y+scrollOffset-yStartWithOffset))
|
|
x++
|
|
}
|
|
/*
|
|
if b >= 33 {
|
|
data = append(data, b)
|
|
}
|
|
*/
|
|
}
|
|
|
|
if debug != "" {
|
|
terminal.logger.Debugf("Sixel last line: %s", debug)
|
|
}
|
|
|
|
newData := matrix.ExtractFrom(int(xStart), 0) // , int(x), int(y+scrollOffset))
|
|
|
|
terminal.logger.Debugf("Sixel data: %s", string(newData))
|
|
|
|
filteredData := filter(newData)
|
|
|
|
six, err := sixel.ParseString(string(filteredData))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse sixel data: %s", err)
|
|
}
|
|
|
|
isNewLineMode := terminal.terminalState.IsNewLineMode()
|
|
if isNewLineMode == false {
|
|
terminal.SetNewLineMode()
|
|
defer terminal.SetLineFeedMode()
|
|
}
|
|
|
|
drawSixel(six, terminal)
|
|
|
|
return nil
|
|
}
|
|
|
|
func drawSixel(six *sixel.Sixel, terminal *Terminal) {
|
|
originalImage := six.RGBA()
|
|
|
|
w := originalImage.Bounds().Size().X
|
|
h := originalImage.Bounds().Size().Y
|
|
|
|
x, y := terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine()
|
|
|
|
fromBottom := int(terminal.ActiveBuffer().ViewHeight() - y)
|
|
lines := int(math.Ceil(float64(h) / float64(terminal.charHeight)))
|
|
if fromBottom < lines+2 {
|
|
y -= (uint16(lines+2) - uint16(fromBottom))
|
|
}
|
|
for l := 0; l <= int(lines); l++ {
|
|
terminal.ActiveBuffer().Write([]rune(strings.Repeat(" ", int(terminal.ActiveBuffer().ViewWidth())))...)
|
|
terminal.ActiveBuffer().NewLine()
|
|
}
|
|
cols := int(math.Ceil(float64(w) / float64(terminal.charWidth)))
|
|
|
|
for offsetY := 0; offsetY < lines-1; offsetY++ {
|
|
for offsetX := 0; offsetX < cols-1; offsetX++ {
|
|
|
|
cell := terminal.ActiveBuffer().GetCell(x+uint16(offsetX), y+uint16((lines-2)-offsetY))
|
|
if cell == nil {
|
|
continue
|
|
}
|
|
img := originalImage.SubImage(image.Rect(
|
|
offsetX*int(terminal.charWidth),
|
|
offsetY*int(terminal.charHeight),
|
|
(offsetX*int(terminal.charWidth))+int(terminal.charWidth),
|
|
(offsetY*int(terminal.charHeight))+int(terminal.charHeight),
|
|
))
|
|
|
|
rgba := image.NewRGBA(image.Rect(0, 0, int(terminal.charWidth), int(terminal.charHeight)))
|
|
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
|
|
cell.SetImage(rgba)
|
|
}
|
|
}
|
|
}
|