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