term_emulator.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package winconsole
  2. import (
  3. "fmt"
  4. "io"
  5. "strconv"
  6. "strings"
  7. )
  8. // http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
  9. const (
  10. ANSI_ESCAPE_PRIMARY = 0x1B
  11. ANSI_ESCAPE_SECONDARY = 0x5B
  12. ANSI_COMMAND_FIRST = 0x40
  13. ANSI_COMMAND_LAST = 0x7E
  14. ANSI_PARAMETER_SEP = ";"
  15. ANSI_CMD_G0 = '('
  16. ANSI_CMD_G1 = ')'
  17. ANSI_CMD_G2 = '*'
  18. ANSI_CMD_G3 = '+'
  19. ANSI_CMD_DECPNM = '>'
  20. ANSI_CMD_DECPAM = '='
  21. ANSI_CMD_OSC = ']'
  22. ANSI_CMD_STR_TERM = '\\'
  23. ANSI_BEL = 0x07
  24. KEY_EVENT = 1
  25. )
  26. // Interface that implements terminal handling
  27. type terminalEmulator interface {
  28. HandleOutputCommand(fd uintptr, command []byte) (n int, err error)
  29. HandleInputSequence(fd uintptr, command []byte) (n int, err error)
  30. WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error)
  31. ReadChars(fd uintptr, w io.Reader, p []byte) (n int, err error)
  32. }
  33. type terminalWriter struct {
  34. wrappedWriter io.Writer
  35. emulator terminalEmulator
  36. command []byte
  37. inSequence bool
  38. fd uintptr
  39. }
  40. type terminalReader struct {
  41. wrappedReader io.ReadCloser
  42. emulator terminalEmulator
  43. command []byte
  44. inSequence bool
  45. fd uintptr
  46. }
  47. // http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
  48. func isAnsiCommandChar(b byte) bool {
  49. switch {
  50. case ANSI_COMMAND_FIRST <= b && b <= ANSI_COMMAND_LAST && b != ANSI_ESCAPE_SECONDARY:
  51. return true
  52. case b == ANSI_CMD_G1 || b == ANSI_CMD_OSC || b == ANSI_CMD_DECPAM || b == ANSI_CMD_DECPNM:
  53. // non-CSI escape sequence terminator
  54. return true
  55. case b == ANSI_CMD_STR_TERM || b == ANSI_BEL:
  56. // String escape sequence terminator
  57. return true
  58. }
  59. return false
  60. }
  61. func isCharacterSelectionCmdChar(b byte) bool {
  62. return (b == ANSI_CMD_G0 || b == ANSI_CMD_G1 || b == ANSI_CMD_G2 || b == ANSI_CMD_G3)
  63. }
  64. func isXtermOscSequence(command []byte, current byte) bool {
  65. return (len(command) >= 2 && command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_CMD_OSC && current != ANSI_BEL)
  66. }
  67. // Write writes len(p) bytes from p to the underlying data stream.
  68. // http://golang.org/pkg/io/#Writer
  69. func (tw *terminalWriter) Write(p []byte) (n int, err error) {
  70. if len(p) == 0 {
  71. return 0, nil
  72. }
  73. if tw.emulator == nil {
  74. return tw.wrappedWriter.Write(p)
  75. }
  76. // Emulate terminal by extracting commands and executing them
  77. totalWritten := 0
  78. start := 0 // indicates start of the next chunk
  79. end := len(p)
  80. for current := 0; current < end; current++ {
  81. if tw.inSequence {
  82. // inside escape sequence
  83. tw.command = append(tw.command, p[current])
  84. if isAnsiCommandChar(p[current]) {
  85. if !isXtermOscSequence(tw.command, p[current]) {
  86. // found the last command character.
  87. // Now we have a complete command.
  88. nchar, err := tw.emulator.HandleOutputCommand(tw.fd, tw.command)
  89. totalWritten += nchar
  90. if err != nil {
  91. return totalWritten, err
  92. }
  93. // clear the command
  94. // don't include current character again
  95. tw.command = tw.command[:0]
  96. start = current + 1
  97. tw.inSequence = false
  98. }
  99. }
  100. } else {
  101. if p[current] == ANSI_ESCAPE_PRIMARY {
  102. // entering escape sequnce
  103. tw.inSequence = true
  104. // indicates end of "normal sequence", write whatever you have so far
  105. if len(p[start:current]) > 0 {
  106. nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:current])
  107. totalWritten += nw
  108. if err != nil {
  109. return totalWritten, err
  110. }
  111. }
  112. // include the current character as part of the next sequence
  113. tw.command = append(tw.command, p[current])
  114. }
  115. }
  116. }
  117. // note that so far, start of the escape sequence triggers writing out of bytes to console.
  118. // For the part _after_ the end of last escape sequence, it is not written out yet. So write it out
  119. if !tw.inSequence {
  120. // assumption is that we can't be inside sequence and therefore command should be empty
  121. if len(p[start:]) > 0 {
  122. nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:])
  123. totalWritten += nw
  124. if err != nil {
  125. return totalWritten, err
  126. }
  127. }
  128. }
  129. return totalWritten, nil
  130. }
  131. // Read reads up to len(p) bytes into p.
  132. // http://golang.org/pkg/io/#Reader
  133. func (tr *terminalReader) Read(p []byte) (n int, err error) {
  134. //Implementations of Read are discouraged from returning a zero byte count
  135. // with a nil error, except when len(p) == 0.
  136. if len(p) == 0 {
  137. return 0, nil
  138. }
  139. if nil == tr.emulator {
  140. return tr.readFromWrappedReader(p)
  141. }
  142. return tr.emulator.ReadChars(tr.fd, tr.wrappedReader, p)
  143. }
  144. // Close the underlying stream
  145. func (tr *terminalReader) Close() (err error) {
  146. return tr.wrappedReader.Close()
  147. }
  148. func (tr *terminalReader) readFromWrappedReader(p []byte) (n int, err error) {
  149. return tr.wrappedReader.Read(p)
  150. }
  151. type ansiCommand struct {
  152. CommandBytes []byte
  153. Command string
  154. Parameters []string
  155. IsSpecial bool
  156. }
  157. func parseAnsiCommand(command []byte) *ansiCommand {
  158. if isCharacterSelectionCmdChar(command[1]) {
  159. // Is Character Set Selection commands
  160. return &ansiCommand{
  161. CommandBytes: command,
  162. Command: string(command),
  163. IsSpecial: true,
  164. }
  165. }
  166. // last char is command character
  167. lastCharIndex := len(command) - 1
  168. retValue := &ansiCommand{
  169. CommandBytes: command,
  170. Command: string(command[lastCharIndex]),
  171. IsSpecial: false,
  172. }
  173. // more than a single escape
  174. if lastCharIndex != 0 {
  175. start := 1
  176. // skip if double char escape sequence
  177. if command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_ESCAPE_SECONDARY {
  178. start++
  179. }
  180. // convert this to GetNextParam method
  181. retValue.Parameters = strings.Split(string(command[start:lastCharIndex]), ANSI_PARAMETER_SEP)
  182. }
  183. return retValue
  184. }
  185. func (c *ansiCommand) getParam(index int) string {
  186. if len(c.Parameters) > index {
  187. return c.Parameters[index]
  188. }
  189. return ""
  190. }
  191. func (ac *ansiCommand) String() string {
  192. return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
  193. bytesToHex(ac.CommandBytes),
  194. ac.Command,
  195. strings.Join(ac.Parameters, "\",\""))
  196. }
  197. func bytesToHex(b []byte) string {
  198. hex := make([]string, len(b))
  199. for i, ch := range b {
  200. hex[i] = fmt.Sprintf("%X", ch)
  201. }
  202. return strings.Join(hex, "")
  203. }
  204. func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) {
  205. if s == "" {
  206. return defaultValue, nil
  207. }
  208. parsedValue, err := strconv.ParseInt(s, 10, 16)
  209. if err != nil {
  210. return defaultValue, err
  211. }
  212. return int16(parsedValue), nil
  213. }