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