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