Apple2-IO-RPi/RaspberryPi/apple2driver/a2io/vt100.go

256 lines
5.7 KiB
Go

// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file contains VT100 terminal emulation
package a2io
import (
"fmt"
)
var escapeSequence string
var operatingSystemSequence bool
var windowTop int
var windowBottom = 23
var applicationMode = false
func sendCharacter(comm A2Io, b byte) {
if b == 0x1b {
escapeSequence = "^["
return
}
if b == 0x0d {
comm.WriteByte('H')
comm.WriteByte(0)
return
}
if b > 0x0d && b < 0x20 || b > 0x80 && b < 0xa0 {
return
}
if b >= 0xa0 {
comm.WriteByte('+' | 0x80)
return
}
if len(escapeSequence) > 0 {
// Parse the escape codes that don't end with a letter
escapeSequence += string(b)
if escapeSequence == "^[]" {
operatingSystemSequence = true
return
}
if operatingSystemSequence {
if b == 0x07 {
operatingSystemSequence = false
escapeSequence = ""
}
return
}
// save cursor
if escapeSequence == "^[7" {
escapeSequence = ""
}
// restore cursor
if escapeSequence == "^[8" {
escapeSequence = ""
}
if escapeSequence == "^[>" {
// this sequence is undocumented and shows up exiting "top" command
escapeSequence = ""
}
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') {
// Parse simple escape codes
switch escapeSequence {
// Set/clear application mode for cursor
case "^[[?1h":
applicationMode = true
escapeSequence = ""
return
case "^[[?1l":
applicationMode = false
comm.WriteByte('T')
comm.WriteByte(0x00)
comm.WriteByte('B')
comm.WriteByte(0x18)
escapeSequence = ""
return
// Tab to home position
case "^[[f", "^[[;f":
comm.WriteByte(0x19) // ^Y moves to home position
escapeSequence = ""
return
// Clear screen
case "^[[2J", "^[[c":
comm.WriteByte(0x0c) // ^L clears the screen
escapeSequence = ""
return
case "^[D":
comm.WriteByte(0x17) // ^W scrolls up
escapeSequence = ""
return
case "^[[K", "^[[0K":
comm.WriteByte(0x1d) // ^] clears to end of line
escapeSequence = ""
return
case "^[[2K":
comm.WriteByte(0x1a) // ^Z clears line
escapeSequence = ""
return
case "^[M":
comm.WriteByte(0x16) // ^V scrolls down
escapeSequence = ""
return
case "^[[J":
comm.WriteByte(0x0b) // ^K clears to end of screen
escapeSequence = ""
return
case "^[[7m":
comm.WriteByte(0x0f) // ^O inverse video
escapeSequence = ""
return
case "^[[m", "^[[0m", "^[[0;7m", "^[[0;1m":
comm.WriteByte(0x0e) // ^N normal video
escapeSequence = ""
return
}
// Parse escape codes that need further parsing
switch b {
// Set cursor location
case 'H', 'f':
if escapeSequence == "^[[H" || escapeSequence == "^[[;H" {
comm.WriteByte(0x19) // ^Y moves to home position
escapeSequence = ""
} else {
var ignore string
var htab, vtab int
fmt.Sscanf(escapeSequence, "^[[%d;%d%s", &vtab, &htab, &ignore)
htab--
vtab--
if htab < 0 { // this occastionally gets called with 0 that becomes -1
htab = 0
}
if vtab > 23 { // top command sets vtab 25 on exit even in 24 line mode
vtab = 23
}
comm.WriteByte('H')
comm.WriteByte(byte(htab))
comm.WriteByte('V')
comm.WriteByte(byte(vtab))
escapeSequence = ""
}
case 'r':
fmt.Sscanf(escapeSequence, "^[[%d;%dr", &windowTop, &windowBottom)
windowTop--
comm.WriteByte('T')
comm.WriteByte(byte(windowTop))
comm.WriteByte('B')
comm.WriteByte(byte(windowBottom))
escapeSequence = ""
case 'A':
if escapeSequence == "^[[A" || escapeSequence == "^[A" {
comm.WriteByte('U')
} else {
var up int
fmt.Sscanf(escapeSequence, "^[[%dA", &up)
for i := 0; i < up; i++ {
comm.WriteByte('U')
}
}
escapeSequence = ""
case 'B':
if escapeSequence == "^[(B" || escapeSequence == "^[)B" || escapeSequence == "^[B" {
escapeSequence = ""
} else if escapeSequence == "^[[B" {
comm.WriteByte(0x0a)
} else {
var down int
fmt.Sscanf(escapeSequence, "^[[%dB", &down)
for i := 0; i < down; i++ {
comm.WriteByte(0x0a)
}
}
escapeSequence = ""
case 'C':
if escapeSequence == "^[[C" || escapeSequence == "^[C" {
comm.WriteByte(0x1c)
} else {
var right int
fmt.Sscanf(escapeSequence, "^[[%dC", &right)
for i := 0; i < right; i++ {
comm.WriteByte(0x1c)
}
}
escapeSequence = ""
case 'D':
if escapeSequence == "^[[D" {
comm.WriteByte(0x08)
} else {
var left int
fmt.Sscanf(escapeSequence, "^[[%dD", &left)
for i := 0; i < left; i++ {
comm.WriteByte(0x08)
}
}
escapeSequence = ""
}
if len(escapeSequence) > 0 {
fmt.Printf("Unhandled escape sequence: %s\n", escapeSequence)
}
escapeSequence = ""
return
}
return
}
switch b {
// convert LF to CR for Apple II compatiblity
case 10:
b = 13
case 13:
return
// convert TAB to spaces
case 9:
b = ' '
b |= 0x80
comm.WriteByte(b)
}
b |= 0x80
comm.WriteByte(b)
}
func readCharacter(comm A2Io) (string, error) {
b, err := comm.ReadByte(true)
var s = string(b)
if err == nil {
if applicationMode {
switch b {
case 0x0b: // up
s = "\033OA"
case 0x0a: // down
s = "\033OB"
case 0x15: // right
s = "\033OC"
case 0x08: // left
s = "\033OD"
case 0x0d: // return
s = string(byte(0x0d))
}
} else {
switch b {
case 0x0b: // up
s = "\033[A"
case 0x0a: // down
s = "\033[B"
case 0x15: // right
s = "\033[C"
case 0x08: // left
s = "\033[D"
case 0x0d: // return
s = string(byte(0x0d))
}
}
}
return s, err
}