mirror of https://github.com/liamg/aminal.git
Sixel Part 2. Ignore embedded by Windows CSI and OSC sequences. (#132)
* Sixel Part 2. Ignore embedded by Windows CSI and OSC sequences. * Sixel Step 3. Recover original Sixel sequence from Windows formatting
This commit is contained in:
parent
0555569cbf
commit
3d07409232
|
@ -0,0 +1,69 @@
|
||||||
|
package matrix
|
||||||
|
|
||||||
|
// AutoMatrix -- automatically growing matrix
|
||||||
|
type AutoMatrix struct {
|
||||||
|
lines [][]rune
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAutoMatrix creates a new auto-matrix
|
||||||
|
func NewAutoMatrix() *AutoMatrix {
|
||||||
|
m := &AutoMatrix{
|
||||||
|
lines: make([][]rune, 0),
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractFrom extracts from (x1, y1) until the end
|
||||||
|
func (matrix *AutoMatrix) ExtractFrom(x1, y1 int) []rune {
|
||||||
|
result := make([]rune, 0)
|
||||||
|
y := y1
|
||||||
|
for y < len(matrix.lines) {
|
||||||
|
if matrix.lines[y] != nil {
|
||||||
|
xMin := 0
|
||||||
|
if y == y1 {
|
||||||
|
xMin = x1
|
||||||
|
}
|
||||||
|
result = append(result, matrix.lines[y][xMin:]...)
|
||||||
|
}
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract from (x1, y1) until (x2, y2)
|
||||||
|
func (matrix *AutoMatrix) Extract(x1, y1, x2, y2 int) []rune {
|
||||||
|
result := make([]rune, 0)
|
||||||
|
y := y1
|
||||||
|
for y <= y2 && y < len(matrix.lines) {
|
||||||
|
xMin := 0
|
||||||
|
if y == y1 {
|
||||||
|
xMin = x1
|
||||||
|
}
|
||||||
|
if matrix.lines[y] != nil {
|
||||||
|
xMax := x2
|
||||||
|
if y != y2 {
|
||||||
|
xMax = len(matrix.lines[y]) - 1
|
||||||
|
}
|
||||||
|
result = append(result, matrix.lines[y][xMin:xMax]...)
|
||||||
|
}
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matrix *AutoMatrix) setAtLine(value rune, x int, line []rune) []rune {
|
||||||
|
if x >= len(line) {
|
||||||
|
line = append(line, make([]rune, x-len(line)+1)...)
|
||||||
|
}
|
||||||
|
line[x] = value
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAt sets a value at (x, y) growing the matrix as necessary
|
||||||
|
func (matrix *AutoMatrix) SetAt(value rune, x int, y int) {
|
||||||
|
|
||||||
|
if y >= len(matrix.lines) {
|
||||||
|
matrix.lines = append(matrix.lines, make([][]rune, y-len(matrix.lines)+1)...)
|
||||||
|
}
|
||||||
|
matrix.lines[y] = matrix.setAtLine(value, x, matrix.lines[y])
|
||||||
|
}
|
|
@ -49,11 +49,17 @@ var csiSequences = []csiMapping{
|
||||||
{id: '@', handler: csiInsertBlankCharactersHandler, description: "Insert Ps (Blank) Character(s) (default = 1) (ICH)"},
|
{id: '@', handler: csiInsertBlankCharactersHandler, description: "Insert Ps (Blank) Character(s) (default = 1) (ICH)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiHandler(pty chan rune, terminal *Terminal) error {
|
type runeRange struct {
|
||||||
var final rune
|
min rune
|
||||||
|
max rune
|
||||||
|
}
|
||||||
|
|
||||||
|
var csiTerminators = runeRange{0x40, 0x7e}
|
||||||
|
|
||||||
|
func loadCSI(pty chan rune) (final rune, param string, intermediate string) {
|
||||||
var b rune
|
var b rune
|
||||||
param := ""
|
param = ""
|
||||||
intermediate := ""
|
intermediate = ""
|
||||||
CSI:
|
CSI:
|
||||||
for {
|
for {
|
||||||
b = <-pty
|
b = <-pty
|
||||||
|
@ -63,16 +69,27 @@ CSI:
|
||||||
case b >= 0x20 && b <= 0x2F:
|
case b >= 0x20 && b <= 0x2F:
|
||||||
//intermediate? useful?
|
//intermediate? useful?
|
||||||
intermediate += string(b)
|
intermediate += string(b)
|
||||||
case b >= 0x40 && b <= 0x7e:
|
case b >= csiTerminators.min && b <= csiTerminators.max:
|
||||||
final = b
|
final = b
|
||||||
break CSI
|
break CSI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
params := strings.Split(param, ";")
|
return final, param, intermediate
|
||||||
if param == "" {
|
}
|
||||||
|
|
||||||
|
func splitParams(paramString string) []string {
|
||||||
|
params := strings.Split(paramString, ";")
|
||||||
|
if paramString == "" {
|
||||||
params = []string{}
|
params = []string{}
|
||||||
}
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
func csiHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
final, param, intermediate := loadCSI(pty)
|
||||||
|
|
||||||
|
params := splitParams(param)
|
||||||
|
|
||||||
for _, sequence := range csiSequences {
|
for _, sequence := range csiSequences {
|
||||||
if sequence.id == final {
|
if sequence.id == final {
|
||||||
|
@ -221,8 +238,8 @@ func csiCursorCharacterAbsoluteHandler(params []string, intermediate string, ter
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorPositionHandler(params []string, intermediate string, terminal *Terminal) error {
|
func parseCursorPosition(params []string) (x, y int) {
|
||||||
x, y := 1, 1
|
x, y = 1, 1
|
||||||
if len(params) == 2 {
|
if len(params) == 2 {
|
||||||
var err error
|
var err error
|
||||||
if params[0] != "" {
|
if params[0] != "" {
|
||||||
|
@ -238,6 +255,11 @@ func csiCursorPositionHandler(params []string, intermediate string, terminal *Te
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
func csiCursorPositionHandler(params []string, intermediate string, terminal *Terminal) error {
|
||||||
|
x, y := parseCursorPosition(params)
|
||||||
|
|
||||||
terminal.ActiveBuffer().SetPosition(uint16(x-1), uint16(y-1))
|
terminal.ActiveBuffer().SetPosition(uint16(x-1), uint16(y-1))
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -7,32 +7,130 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/liamg/aminal/matrix"
|
||||||
"github.com/liamg/aminal/sixel"
|
"github.com/liamg/aminal/sixel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sixelHandler(pty chan rune, terminal *Terminal) error {
|
type boolFormRuneFunc func(rune) bool
|
||||||
|
|
||||||
data := []rune{}
|
|
||||||
|
|
||||||
|
func swallowByFunction(pty chan rune, isTerminator boolFormRuneFunc) {
|
||||||
for {
|
for {
|
||||||
b := <-pty
|
b := <-pty
|
||||||
if b == 0x1b { // terminated by ESC bell or ESC \
|
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
|
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 {
|
if t != 0x07 && t != 0x5c {
|
||||||
return fmt.Errorf("Incorrect terminator in sixel sequence: 0x%02X [%c]", t, t)
|
return fmt.Errorf("Incorrect terminator in sixel sequence: 0x%02X [%c]", t, t)
|
||||||
}
|
}
|
||||||
break
|
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 {
|
if b >= 33 {
|
||||||
data = append(data, b)
|
data = append(data, b)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
six, err := sixel.ParseString(string(data))
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to parse sixel data: %s", err)
|
return fmt.Errorf("Failed to parse sixel data: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawSixel(six, terminal)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawSixel(six *sixel.Sixel, terminal *Terminal) {
|
||||||
originalImage := six.RGBA()
|
originalImage := six.RGBA()
|
||||||
|
|
||||||
w := originalImage.Bounds().Size().X
|
w := originalImage.Bounds().Size().X
|
||||||
|
@ -70,6 +168,4 @@ func sixelHandler(pty chan rune, terminal *Terminal) error {
|
||||||
cell.SetImage(rgba)
|
cell.SetImage(rgba)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue