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  }