Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| 1 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,67 +0,0 @@ |
| 1 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "strings" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-// ASCII list the possible supported ASCII key sequence |
|
| 9 |
-var ASCII = []string{
|
|
| 10 |
- "ctrl-@", |
|
| 11 |
- "ctrl-a", |
|
| 12 |
- "ctrl-b", |
|
| 13 |
- "ctrl-c", |
|
| 14 |
- "ctrl-d", |
|
| 15 |
- "ctrl-e", |
|
| 16 |
- "ctrl-f", |
|
| 17 |
- "ctrl-g", |
|
| 18 |
- "ctrl-h", |
|
| 19 |
- "ctrl-i", |
|
| 20 |
- "ctrl-j", |
|
| 21 |
- "ctrl-k", |
|
| 22 |
- "ctrl-l", |
|
| 23 |
- "ctrl-m", |
|
| 24 |
- "ctrl-n", |
|
| 25 |
- "ctrl-o", |
|
| 26 |
- "ctrl-p", |
|
| 27 |
- "ctrl-q", |
|
| 28 |
- "ctrl-r", |
|
| 29 |
- "ctrl-s", |
|
| 30 |
- "ctrl-t", |
|
| 31 |
- "ctrl-u", |
|
| 32 |
- "ctrl-v", |
|
| 33 |
- "ctrl-w", |
|
| 34 |
- "ctrl-x", |
|
| 35 |
- "ctrl-y", |
|
| 36 |
- "ctrl-z", |
|
| 37 |
- "ctrl-[", |
|
| 38 |
- "ctrl-\\", |
|
| 39 |
- "ctrl-]", |
|
| 40 |
- "ctrl-^", |
|
| 41 |
- "ctrl-_", |
|
| 42 |
-} |
|
| 43 |
- |
|
| 44 |
-// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. |
|
| 45 |
-// Deprecated: use github.com/moby/term.ToBytes |
|
| 46 |
-func ToBytes(keys string) ([]byte, error) {
|
|
| 47 |
- codes := []byte{}
|
|
| 48 |
-next: |
|
| 49 |
- for _, key := range strings.Split(keys, ",") {
|
|
| 50 |
- if len(key) != 1 {
|
|
| 51 |
- for code, ctrl := range ASCII {
|
|
| 52 |
- if ctrl == key {
|
|
| 53 |
- codes = append(codes, byte(code)) |
|
| 54 |
- continue next |
|
| 55 |
- } |
|
| 56 |
- } |
|
| 57 |
- if key == "DEL" {
|
|
| 58 |
- codes = append(codes, 127) |
|
| 59 |
- } else {
|
|
| 60 |
- return nil, fmt.Errorf("Unknown character: '%s'", key)
|
|
| 61 |
- } |
|
| 62 |
- } else {
|
|
| 63 |
- codes = append(codes, key[0]) |
|
| 64 |
- } |
|
| 65 |
- } |
|
| 66 |
- return codes, nil |
|
| 67 |
-} |
| 68 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,25 +0,0 @@ |
| 1 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- |
|
| 6 |
- "gotest.tools/v3/assert" |
|
| 7 |
- is "gotest.tools/v3/assert/cmp" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func TestToBytes(t *testing.T) {
|
|
| 11 |
- codes, err := ToBytes("ctrl-a,a")
|
|
| 12 |
- assert.NilError(t, err) |
|
| 13 |
- assert.Check(t, is.DeepEqual([]byte{1, 97}, codes))
|
|
| 14 |
- |
|
| 15 |
- _, err = ToBytes("shift-z")
|
|
| 16 |
- assert.Check(t, is.ErrorContains(err, "")) |
|
| 17 |
- |
|
| 18 |
- codes, err = ToBytes("ctrl-@,ctrl-[,~,ctrl-o")
|
|
| 19 |
- assert.NilError(t, err) |
|
| 20 |
- assert.Check(t, is.DeepEqual([]byte{0, 27, 126, 15}, codes))
|
|
| 21 |
- |
|
| 22 |
- codes, err = ToBytes("DEL,+")
|
|
| 23 |
- assert.NilError(t, err) |
|
| 24 |
- assert.Check(t, is.DeepEqual([]byte{127, 43}, codes))
|
|
| 25 |
-} |
| 26 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,84 @@ |
| 0 |
+// Package term provides structures and helper functions to work with |
|
| 1 |
+// terminal (state, sizes). |
|
| 2 |
+// |
|
| 3 |
+// Deprecated: use github.com/moby/term instead |
|
| 4 |
+package term // import "github.com/docker/docker/pkg/term" |
|
| 5 |
+ |
|
| 6 |
+import ( |
|
| 7 |
+ "github.com/moby/term" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// EscapeError is special error which returned by a TTY proxy reader's Read() |
|
| 11 |
+// method in case its detach escape sequence is read. |
|
| 12 |
+// Deprecated: use github.com/moby/term.EscapeError |
|
| 13 |
+type EscapeError = term.EscapeError |
|
| 14 |
+ |
|
| 15 |
+// State represents the state of the terminal. |
|
| 16 |
+// Deprecated: use github.com/moby/term.State |
|
| 17 |
+type State = term.State |
|
| 18 |
+ |
|
| 19 |
+// Winsize represents the size of the terminal window. |
|
| 20 |
+// Deprecated: use github.com/moby/term.Winsize |
|
| 21 |
+type Winsize = term.Winsize |
|
| 22 |
+ |
|
| 23 |
+var ( |
|
| 24 |
+ // ASCII list the possible supported ASCII key sequence |
|
| 25 |
+ ASCII = term.ASCII |
|
| 26 |
+ |
|
| 27 |
+ // ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. |
|
| 28 |
+ // Deprecated: use github.com/moby/term.ToBytes |
|
| 29 |
+ ToBytes = term.ToBytes |
|
| 30 |
+ |
|
| 31 |
+ // StdStreams returns the standard streams (stdin, stdout, stderr). |
|
| 32 |
+ // Deprecated: use github.com/moby/term.StdStreams |
|
| 33 |
+ StdStreams = term.StdStreams |
|
| 34 |
+ |
|
| 35 |
+ // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. |
|
| 36 |
+ // Deprecated: use github.com/moby/term.GetFdInfo |
|
| 37 |
+ GetFdInfo = term.GetFdInfo |
|
| 38 |
+ |
|
| 39 |
+ // GetWinsize returns the window size based on the specified file descriptor. |
|
| 40 |
+ // Deprecated: use github.com/moby/term.GetWinsize |
|
| 41 |
+ GetWinsize = term.GetWinsize |
|
| 42 |
+ |
|
| 43 |
+ // IsTerminal returns true if the given file descriptor is a terminal. |
|
| 44 |
+ // Deprecated: use github.com/moby/term.IsTerminal |
|
| 45 |
+ IsTerminal = term.IsTerminal |
|
| 46 |
+ |
|
| 47 |
+ // RestoreTerminal restores the terminal connected to the given file descriptor |
|
| 48 |
+ // to a previous state. |
|
| 49 |
+ // Deprecated: use github.com/moby/term.RestoreTerminal |
|
| 50 |
+ RestoreTerminal = term.RestoreTerminal |
|
| 51 |
+ |
|
| 52 |
+ // SaveState saves the state of the terminal connected to the given file descriptor. |
|
| 53 |
+ // Deprecated: use github.com/moby/term.SaveState |
|
| 54 |
+ SaveState = term.SaveState |
|
| 55 |
+ |
|
| 56 |
+ // DisableEcho applies the specified state to the terminal connected to the file |
|
| 57 |
+ // descriptor, with echo disabled. |
|
| 58 |
+ // Deprecated: use github.com/moby/term.DisableEcho |
|
| 59 |
+ DisableEcho = term.DisableEcho |
|
| 60 |
+ |
|
| 61 |
+ // SetRawTerminal puts the terminal connected to the given file descriptor into |
|
| 62 |
+ // raw mode and returns the previous state. On UNIX, this puts both the input |
|
| 63 |
+ // and output into raw mode. On Windows, it only puts the input into raw mode. |
|
| 64 |
+ // Deprecated: use github.com/moby/term.SetRawTerminal |
|
| 65 |
+ SetRawTerminal = term.SetRawTerminal |
|
| 66 |
+ |
|
| 67 |
+ // SetRawTerminalOutput puts the output of terminal connected to the given file |
|
| 68 |
+ // descriptor into raw mode. On UNIX, this does nothing and returns nil for the |
|
| 69 |
+ // state. On Windows, it disables LF -> CRLF translation. |
|
| 70 |
+ // Deprecated: use github.com/moby/term.SetRawTerminalOutput |
|
| 71 |
+ SetRawTerminalOutput = term.SetRawTerminalOutput |
|
| 72 |
+ |
|
| 73 |
+ // MakeRaw puts the terminal connected to the given file descriptor into raw |
|
| 74 |
+ // mode and returns the previous state of the terminal so that it can be restored. |
|
| 75 |
+ // Deprecated: use github.com/moby/term.MakeRaw |
|
| 76 |
+ MakeRaw = term.MakeRaw |
|
| 77 |
+ |
|
| 78 |
+ // NewEscapeProxy returns a new TTY proxy reader which wraps the given reader |
|
| 79 |
+ // and detects when the specified escape keys are read, in which case the Read |
|
| 80 |
+ // method will return an error of type EscapeError. |
|
| 81 |
+ // Deprecated: use github.com/moby/term.NewEscapeProxy |
|
| 82 |
+ NewEscapeProxy = term.NewEscapeProxy |
|
| 83 |
+) |
| 0 | 84 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,20 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package term // import "github.com/docker/docker/pkg/term" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "github.com/moby/term" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// Termios is the Unix API for terminal I/O. |
|
| 9 |
+// Deprecated: use github.com/moby/term.Termios |
|
| 10 |
+type Termios = term.Termios |
|
| 11 |
+ |
|
| 12 |
+var ( |
|
| 13 |
+ // ErrInvalidState is returned if the state of the terminal is invalid. |
|
| 14 |
+ ErrInvalidState = term.ErrInvalidState |
|
| 15 |
+ |
|
| 16 |
+ // SetWinsize tries to set the specified window size for the specified file descriptor. |
|
| 17 |
+ // Deprecated: use github.com/moby/term.GetWinsize |
|
| 18 |
+ SetWinsize = term.SetWinsize |
|
| 19 |
+) |
| 0 | 20 |
deleted file mode 100644 |
| ... | ... |
@@ -1,90 +0,0 @@ |
| 1 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// EscapeError is special error which returned by a TTY proxy reader's Read() |
|
| 8 |
-// method in case its detach escape sequence is read. |
|
| 9 |
-// Deprecated: use github.com/moby/term.EscapeError |
|
| 10 |
-type EscapeError struct{}
|
|
| 11 |
- |
|
| 12 |
-func (EscapeError) Error() string {
|
|
| 13 |
- return "read escape sequence" |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-// escapeProxy is used only for attaches with a TTY. It is used to proxy |
|
| 17 |
-// stdin keypresses from the underlying reader and look for the passed in |
|
| 18 |
-// escape key sequence to signal a detach. |
|
| 19 |
-type escapeProxy struct {
|
|
| 20 |
- escapeKeys []byte |
|
| 21 |
- escapeKeyPos int |
|
| 22 |
- r io.Reader |
|
| 23 |
- buf []byte |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader |
|
| 27 |
-// and detects when the specified escape keys are read, in which case the Read |
|
| 28 |
-// method will return an error of type EscapeError. |
|
| 29 |
-// Deprecated: use github.com/moby/term.NewEscapeProxy |
|
| 30 |
-func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
|
|
| 31 |
- return &escapeProxy{
|
|
| 32 |
- escapeKeys: escapeKeys, |
|
| 33 |
- r: r, |
|
| 34 |
- } |
|
| 35 |
-} |
|
| 36 |
- |
|
| 37 |
-func (r *escapeProxy) Read(buf []byte) (n int, err error) {
|
|
| 38 |
- if len(r.escapeKeys) > 0 && r.escapeKeyPos == len(r.escapeKeys) {
|
|
| 39 |
- return 0, EscapeError{}
|
|
| 40 |
- } |
|
| 41 |
- |
|
| 42 |
- if len(r.buf) > 0 {
|
|
| 43 |
- n = copy(buf, r.buf) |
|
| 44 |
- r.buf = r.buf[n:] |
|
| 45 |
- } |
|
| 46 |
- |
|
| 47 |
- nr, err := r.r.Read(buf[n:]) |
|
| 48 |
- n += nr |
|
| 49 |
- if len(r.escapeKeys) == 0 {
|
|
| 50 |
- return n, err |
|
| 51 |
- } |
|
| 52 |
- |
|
| 53 |
- for i := 0; i < n; i++ {
|
|
| 54 |
- if buf[i] == r.escapeKeys[r.escapeKeyPos] {
|
|
| 55 |
- r.escapeKeyPos++ |
|
| 56 |
- |
|
| 57 |
- // Check if the full escape sequence is matched. |
|
| 58 |
- if r.escapeKeyPos == len(r.escapeKeys) {
|
|
| 59 |
- n = i + 1 - r.escapeKeyPos |
|
| 60 |
- if n < 0 {
|
|
| 61 |
- n = 0 |
|
| 62 |
- } |
|
| 63 |
- return n, EscapeError{}
|
|
| 64 |
- } |
|
| 65 |
- continue |
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- // If we need to prepend a partial escape sequence from the previous |
|
| 69 |
- // read, make sure the new buffer size doesn't exceed len(buf). |
|
| 70 |
- // Otherwise, preserve any extra data in a buffer for the next read. |
|
| 71 |
- if i < r.escapeKeyPos {
|
|
| 72 |
- preserve := make([]byte, 0, r.escapeKeyPos+n) |
|
| 73 |
- preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...) |
|
| 74 |
- preserve = append(preserve, buf[:n]...) |
|
| 75 |
- n = copy(buf, preserve) |
|
| 76 |
- i += r.escapeKeyPos |
|
| 77 |
- r.buf = append(r.buf, preserve[n:]...) |
|
| 78 |
- } |
|
| 79 |
- r.escapeKeyPos = 0 |
|
| 80 |
- } |
|
| 81 |
- |
|
| 82 |
- // If we're in the middle of reading an escape sequence, make sure we don't |
|
| 83 |
- // let the caller read it. If later on we find that this is not the escape |
|
| 84 |
- // sequence, we'll prepend it back to buf. |
|
| 85 |
- n -= r.escapeKeyPos |
|
| 86 |
- if n < 0 {
|
|
| 87 |
- n = 0 |
|
| 88 |
- } |
|
| 89 |
- return n, err |
|
| 90 |
-} |
| 91 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,208 +0,0 @@ |
| 1 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "testing" |
|
| 6 |
- |
|
| 7 |
- "gotest.tools/v3/assert" |
|
| 8 |
- is "gotest.tools/v3/assert/cmp" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-func TestEscapeProxyRead(t *testing.T) {
|
|
| 12 |
- t.Run("no escape keys, keys [a]", func(t *testing.T) {
|
|
| 13 |
- escapeKeys, _ := ToBytes("")
|
|
| 14 |
- keys, _ := ToBytes("a")
|
|
| 15 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 16 |
- |
|
| 17 |
- buf := make([]byte, len(keys)) |
|
| 18 |
- nr, err := reader.Read(buf) |
|
| 19 |
- assert.NilError(t, err) |
|
| 20 |
- assert.Equal(t, nr, len(keys)) |
|
| 21 |
- assert.DeepEqual(t, keys, buf) |
|
| 22 |
- }) |
|
| 23 |
- |
|
| 24 |
- t.Run("no escape keys, keys [a,b,c]", func(t *testing.T) {
|
|
| 25 |
- escapeKeys, _ := ToBytes("")
|
|
| 26 |
- keys, _ := ToBytes("a,b,c")
|
|
| 27 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 28 |
- |
|
| 29 |
- buf := make([]byte, len(keys)) |
|
| 30 |
- nr, err := reader.Read(buf) |
|
| 31 |
- assert.NilError(t, err) |
|
| 32 |
- assert.Equal(t, nr, len(keys)) |
|
| 33 |
- assert.DeepEqual(t, keys, buf) |
|
| 34 |
- }) |
|
| 35 |
- |
|
| 36 |
- t.Run("no escape keys, no keys", func(t *testing.T) {
|
|
| 37 |
- escapeKeys, _ := ToBytes("")
|
|
| 38 |
- keys, _ := ToBytes("")
|
|
| 39 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 40 |
- |
|
| 41 |
- buf := make([]byte, len(keys)) |
|
| 42 |
- nr, err := reader.Read(buf) |
|
| 43 |
- assert.Assert(t, is.ErrorContains(err, ""), "Should throw error when no keys are to read") |
|
| 44 |
- assert.Equal(t, nr, 0) |
|
| 45 |
- assert.Check(t, is.Len(keys, 0)) |
|
| 46 |
- assert.Check(t, is.Len(buf, 0)) |
|
| 47 |
- }) |
|
| 48 |
- |
|
| 49 |
- t.Run("DEL escape key, keys [a,b,c,+]", func(t *testing.T) {
|
|
| 50 |
- escapeKeys, _ := ToBytes("DEL")
|
|
| 51 |
- keys, _ := ToBytes("a,b,c,+")
|
|
| 52 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 53 |
- |
|
| 54 |
- buf := make([]byte, len(keys)) |
|
| 55 |
- nr, err := reader.Read(buf) |
|
| 56 |
- assert.NilError(t, err) |
|
| 57 |
- assert.Equal(t, nr, len(keys)) |
|
| 58 |
- assert.DeepEqual(t, keys, buf) |
|
| 59 |
- }) |
|
| 60 |
- |
|
| 61 |
- t.Run("DEL escape key, no keys", func(t *testing.T) {
|
|
| 62 |
- escapeKeys, _ := ToBytes("DEL")
|
|
| 63 |
- keys, _ := ToBytes("")
|
|
| 64 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 65 |
- |
|
| 66 |
- buf := make([]byte, len(keys)) |
|
| 67 |
- nr, err := reader.Read(buf) |
|
| 68 |
- assert.Assert(t, is.ErrorContains(err, ""), "Should throw error when no keys are to read") |
|
| 69 |
- assert.Equal(t, nr, 0) |
|
| 70 |
- assert.Check(t, is.Len(keys, 0)) |
|
| 71 |
- assert.Check(t, is.Len(buf, 0)) |
|
| 72 |
- }) |
|
| 73 |
- |
|
| 74 |
- t.Run("ctrl-x,ctrl-@ escape key, keys [DEL]", func(t *testing.T) {
|
|
| 75 |
- escapeKeys, _ := ToBytes("ctrl-x,ctrl-@")
|
|
| 76 |
- keys, _ := ToBytes("DEL")
|
|
| 77 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 78 |
- |
|
| 79 |
- buf := make([]byte, len(keys)) |
|
| 80 |
- nr, err := reader.Read(buf) |
|
| 81 |
- assert.NilError(t, err) |
|
| 82 |
- assert.Equal(t, nr, 1) |
|
| 83 |
- assert.DeepEqual(t, keys, buf) |
|
| 84 |
- }) |
|
| 85 |
- |
|
| 86 |
- t.Run("ctrl-c escape key, keys [ctrl-c]", func(t *testing.T) {
|
|
| 87 |
- escapeKeys, _ := ToBytes("ctrl-c")
|
|
| 88 |
- keys, _ := ToBytes("ctrl-c")
|
|
| 89 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 90 |
- |
|
| 91 |
- buf := make([]byte, len(keys)) |
|
| 92 |
- nr, err := reader.Read(buf) |
|
| 93 |
- assert.Error(t, err, "read escape sequence") |
|
| 94 |
- assert.Equal(t, nr, 0) |
|
| 95 |
- assert.DeepEqual(t, keys, buf) |
|
| 96 |
- }) |
|
| 97 |
- |
|
| 98 |
- t.Run("ctrl-c,ctrl-z escape key, keys [ctrl-c],[ctrl-z]", func(t *testing.T) {
|
|
| 99 |
- escapeKeys, _ := ToBytes("ctrl-c,ctrl-z")
|
|
| 100 |
- keys, _ := ToBytes("ctrl-c,ctrl-z")
|
|
| 101 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 102 |
- |
|
| 103 |
- buf := make([]byte, 1) |
|
| 104 |
- nr, err := reader.Read(buf) |
|
| 105 |
- assert.NilError(t, err) |
|
| 106 |
- assert.Equal(t, nr, 0) |
|
| 107 |
- assert.DeepEqual(t, keys[0:1], buf) |
|
| 108 |
- |
|
| 109 |
- nr, err = reader.Read(buf) |
|
| 110 |
- assert.Error(t, err, "read escape sequence") |
|
| 111 |
- assert.Equal(t, nr, 0) |
|
| 112 |
- assert.DeepEqual(t, keys[1:], buf) |
|
| 113 |
- }) |
|
| 114 |
- |
|
| 115 |
- t.Run("ctrl-c,ctrl-z escape key, keys [ctrl-c,ctrl-z]", func(t *testing.T) {
|
|
| 116 |
- escapeKeys, _ := ToBytes("ctrl-c,ctrl-z")
|
|
| 117 |
- keys, _ := ToBytes("ctrl-c,ctrl-z")
|
|
| 118 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 119 |
- |
|
| 120 |
- buf := make([]byte, 2) |
|
| 121 |
- nr, err := reader.Read(buf) |
|
| 122 |
- assert.Error(t, err, "read escape sequence") |
|
| 123 |
- assert.Equal(t, nr, 0, "nr should be equal to 0") |
|
| 124 |
- assert.DeepEqual(t, keys, buf) |
|
| 125 |
- }) |
|
| 126 |
- |
|
| 127 |
- t.Run("ctrl-c,ctrl-z escape key, keys [ctrl-c],[DEL,+]", func(t *testing.T) {
|
|
| 128 |
- escapeKeys, _ := ToBytes("ctrl-c,ctrl-z")
|
|
| 129 |
- keys, _ := ToBytes("ctrl-c,DEL,+")
|
|
| 130 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 131 |
- |
|
| 132 |
- buf := make([]byte, 1) |
|
| 133 |
- nr, err := reader.Read(buf) |
|
| 134 |
- assert.NilError(t, err) |
|
| 135 |
- assert.Equal(t, nr, 0) |
|
| 136 |
- assert.DeepEqual(t, keys[0:1], buf) |
|
| 137 |
- |
|
| 138 |
- buf = make([]byte, len(keys)) |
|
| 139 |
- nr, err = reader.Read(buf) |
|
| 140 |
- assert.NilError(t, err) |
|
| 141 |
- assert.Equal(t, nr, len(keys)) |
|
| 142 |
- assert.DeepEqual(t, keys, buf) |
|
| 143 |
- }) |
|
| 144 |
- |
|
| 145 |
- t.Run("ctrl-c,ctrl-z escape key, keys [ctrl-c],[DEL]", func(t *testing.T) {
|
|
| 146 |
- escapeKeys, _ := ToBytes("ctrl-c,ctrl-z")
|
|
| 147 |
- keys, _ := ToBytes("ctrl-c,DEL")
|
|
| 148 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 149 |
- |
|
| 150 |
- buf := make([]byte, 1) |
|
| 151 |
- nr, err := reader.Read(buf) |
|
| 152 |
- assert.NilError(t, err) |
|
| 153 |
- assert.Equal(t, nr, 0) |
|
| 154 |
- assert.DeepEqual(t, keys[0:1], buf) |
|
| 155 |
- |
|
| 156 |
- buf = make([]byte, len(keys)) |
|
| 157 |
- nr, err = reader.Read(buf) |
|
| 158 |
- assert.NilError(t, err) |
|
| 159 |
- assert.Equal(t, nr, len(keys)) |
|
| 160 |
- assert.DeepEqual(t, keys, buf) |
|
| 161 |
- }) |
|
| 162 |
- |
|
| 163 |
- t.Run("a,b,c,d escape key, keys [a,b],[c,d]", func(t *testing.T) {
|
|
| 164 |
- escapeKeys, _ := ToBytes("a,b,c,d")
|
|
| 165 |
- keys, _ := ToBytes("a,b,c,d")
|
|
| 166 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 167 |
- |
|
| 168 |
- buf := make([]byte, 2) |
|
| 169 |
- nr, err := reader.Read(buf) |
|
| 170 |
- assert.NilError(t, err) |
|
| 171 |
- assert.Equal(t, 0, nr) |
|
| 172 |
- assert.DeepEqual(t, keys[0:2], buf) |
|
| 173 |
- |
|
| 174 |
- buf = make([]byte, 2) |
|
| 175 |
- nr, err = reader.Read(buf) |
|
| 176 |
- assert.Error(t, err, "read escape sequence") |
|
| 177 |
- assert.Equal(t, 0, nr) |
|
| 178 |
- assert.DeepEqual(t, keys[2:4], buf) |
|
| 179 |
- }) |
|
| 180 |
- |
|
| 181 |
- t.Run("ctrl-p,ctrl-q escape key, keys [ctrl-p],[a],[ctrl-p,ctrl-q]", func(t *testing.T) {
|
|
| 182 |
- escapeKeys, _ := ToBytes("ctrl-p,ctrl-q")
|
|
| 183 |
- keys, _ := ToBytes("ctrl-p,a,ctrl-p,ctrl-q")
|
|
| 184 |
- reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) |
|
| 185 |
- |
|
| 186 |
- buf := make([]byte, 1) |
|
| 187 |
- nr, err := reader.Read(buf) |
|
| 188 |
- assert.NilError(t, err) |
|
| 189 |
- assert.Equal(t, 0, nr) |
|
| 190 |
- |
|
| 191 |
- buf = make([]byte, 1) |
|
| 192 |
- nr, err = reader.Read(buf) |
|
| 193 |
- assert.NilError(t, err) |
|
| 194 |
- assert.Equal(t, 1, nr) |
|
| 195 |
- assert.DeepEqual(t, keys[:1], buf) |
|
| 196 |
- |
|
| 197 |
- buf = make([]byte, 2) |
|
| 198 |
- nr, err = reader.Read(buf) |
|
| 199 |
- assert.NilError(t, err) |
|
| 200 |
- assert.Equal(t, 1, nr) |
|
| 201 |
- assert.DeepEqual(t, keys[1:3], buf) |
|
| 202 |
- |
|
| 203 |
- buf = make([]byte, 2) |
|
| 204 |
- nr, err = reader.Read(buf) |
|
| 205 |
- assert.Error(t, err, "read escape sequence") |
|
| 206 |
- assert.Equal(t, 0, nr) |
|
| 207 |
- }) |
|
| 208 |
-} |
| 209 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,20 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "syscall" |
|
| 7 |
- "unsafe" |
|
| 8 |
- |
|
| 9 |
- "golang.org/x/sys/unix" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-func tcget(fd uintptr, p *Termios) syscall.Errno {
|
|
| 13 |
- _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p))) |
|
| 14 |
- return err |
|
| 15 |
-} |
|
| 16 |
- |
|
| 17 |
-func tcset(fd uintptr, p *Termios) syscall.Errno {
|
|
| 18 |
- _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p))) |
|
| 19 |
- return err |
|
| 20 |
-} |
| 21 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,136 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-// Package term provides structures and helper functions to work with |
|
| 4 |
-// terminal (state, sizes). |
|
| 5 |
-// |
|
| 6 |
-// Deprecated: use github.com/moby/term instead |
|
| 7 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 8 |
- |
|
| 9 |
-import ( |
|
| 10 |
- "errors" |
|
| 11 |
- "fmt" |
|
| 12 |
- "io" |
|
| 13 |
- "os" |
|
| 14 |
- "os/signal" |
|
| 15 |
- |
|
| 16 |
- "golang.org/x/sys/unix" |
|
| 17 |
-) |
|
| 18 |
- |
|
| 19 |
-var ( |
|
| 20 |
- // ErrInvalidState is returned if the state of the terminal is invalid. |
|
| 21 |
- ErrInvalidState = errors.New("Invalid terminal state")
|
|
| 22 |
-) |
|
| 23 |
- |
|
| 24 |
-// State represents the state of the terminal. |
|
| 25 |
-// Deprecated: use github.com/moby/term.State |
|
| 26 |
-type State struct {
|
|
| 27 |
- termios Termios |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 |
-// Winsize represents the size of the terminal window. |
|
| 31 |
-// Deprecated: use github.com/moby/term.Winsize |
|
| 32 |
-type Winsize struct {
|
|
| 33 |
- Height uint16 |
|
| 34 |
- Width uint16 |
|
| 35 |
- x uint16 |
|
| 36 |
- y uint16 |
|
| 37 |
-} |
|
| 38 |
- |
|
| 39 |
-// StdStreams returns the standard streams (stdin, stdout, stderr). |
|
| 40 |
-// Deprecated: use github.com/moby/term.StdStreams |
|
| 41 |
-func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
| 42 |
- return os.Stdin, os.Stdout, os.Stderr |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. |
|
| 46 |
-// Deprecated: use github.com/moby/term.GetFdInfo |
|
| 47 |
-func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
| 48 |
- var inFd uintptr |
|
| 49 |
- var isTerminalIn bool |
|
| 50 |
- if file, ok := in.(*os.File); ok {
|
|
| 51 |
- inFd = file.Fd() |
|
| 52 |
- isTerminalIn = IsTerminal(inFd) |
|
| 53 |
- } |
|
| 54 |
- return inFd, isTerminalIn |
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-// IsTerminal returns true if the given file descriptor is a terminal. |
|
| 58 |
-// Deprecated: use github.com/moby/term.IsTerminal |
|
| 59 |
-func IsTerminal(fd uintptr) bool {
|
|
| 60 |
- var termios Termios |
|
| 61 |
- return tcget(fd, &termios) == 0 |
|
| 62 |
-} |
|
| 63 |
- |
|
| 64 |
-// RestoreTerminal restores the terminal connected to the given file descriptor |
|
| 65 |
-// to a previous state. |
|
| 66 |
-// Deprecated: use github.com/moby/term.RestoreTerminal |
|
| 67 |
-func RestoreTerminal(fd uintptr, state *State) error {
|
|
| 68 |
- if state == nil {
|
|
| 69 |
- return ErrInvalidState |
|
| 70 |
- } |
|
| 71 |
- if err := tcset(fd, &state.termios); err != 0 {
|
|
| 72 |
- return err |
|
| 73 |
- } |
|
| 74 |
- return nil |
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 |
-// SaveState saves the state of the terminal connected to the given file descriptor. |
|
| 78 |
-// Deprecated: use github.com/moby/term.SaveState |
|
| 79 |
-func SaveState(fd uintptr) (*State, error) {
|
|
| 80 |
- var oldState State |
|
| 81 |
- if err := tcget(fd, &oldState.termios); err != 0 {
|
|
| 82 |
- return nil, err |
|
| 83 |
- } |
|
| 84 |
- |
|
| 85 |
- return &oldState, nil |
|
| 86 |
-} |
|
| 87 |
- |
|
| 88 |
-// DisableEcho applies the specified state to the terminal connected to the file |
|
| 89 |
-// descriptor, with echo disabled. |
|
| 90 |
-// Deprecated: use github.com/moby/term.DisableEcho |
|
| 91 |
-func DisableEcho(fd uintptr, state *State) error {
|
|
| 92 |
- newState := state.termios |
|
| 93 |
- newState.Lflag &^= unix.ECHO |
|
| 94 |
- |
|
| 95 |
- if err := tcset(fd, &newState); err != 0 {
|
|
| 96 |
- return err |
|
| 97 |
- } |
|
| 98 |
- handleInterrupt(fd, state) |
|
| 99 |
- return nil |
|
| 100 |
-} |
|
| 101 |
- |
|
| 102 |
-// SetRawTerminal puts the terminal connected to the given file descriptor into |
|
| 103 |
-// raw mode and returns the previous state. On UNIX, this puts both the input |
|
| 104 |
-// and output into raw mode. On Windows, it only puts the input into raw mode. |
|
| 105 |
-// Deprecated: use github.com/moby/term.SetRawTerminal |
|
| 106 |
-func SetRawTerminal(fd uintptr) (*State, error) {
|
|
| 107 |
- oldState, err := MakeRaw(fd) |
|
| 108 |
- if err != nil {
|
|
| 109 |
- return nil, err |
|
| 110 |
- } |
|
| 111 |
- handleInterrupt(fd, oldState) |
|
| 112 |
- return oldState, err |
|
| 113 |
-} |
|
| 114 |
- |
|
| 115 |
-// SetRawTerminalOutput puts the output of terminal connected to the given file |
|
| 116 |
-// descriptor into raw mode. On UNIX, this does nothing and returns nil for the |
|
| 117 |
-// state. On Windows, it disables LF -> CRLF translation. |
|
| 118 |
-// Deprecated: use github.com/moby/term.SetRawTerminalOutput |
|
| 119 |
-func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
|
| 120 |
- return nil, nil |
|
| 121 |
-} |
|
| 122 |
- |
|
| 123 |
-func handleInterrupt(fd uintptr, state *State) {
|
|
| 124 |
- sigchan := make(chan os.Signal, 1) |
|
| 125 |
- signal.Notify(sigchan, os.Interrupt) |
|
| 126 |
- go func() {
|
|
| 127 |
- for range sigchan {
|
|
| 128 |
- // quit cleanly and the new terminal item is on a new line |
|
| 129 |
- fmt.Println() |
|
| 130 |
- signal.Stop(sigchan) |
|
| 131 |
- close(sigchan) |
|
| 132 |
- RestoreTerminal(fd, state) |
|
| 133 |
- os.Exit(1) |
|
| 134 |
- } |
|
| 135 |
- }() |
|
| 136 |
-} |
| 137 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,117 +0,0 @@ |
| 1 |
-//+build linux |
|
| 2 |
- |
|
| 3 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "os" |
|
| 8 |
- "testing" |
|
| 9 |
- |
|
| 10 |
- "github.com/google/go-cmp/cmp" |
|
| 11 |
- "gotest.tools/v3/assert" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-// RequiresRoot skips tests that require root, unless the test.root flag has |
|
| 15 |
-// been set |
|
| 16 |
-func RequiresRoot(t *testing.T) {
|
|
| 17 |
- if os.Getuid() != 0 {
|
|
| 18 |
- t.Skip("skipping test that requires root")
|
|
| 19 |
- return |
|
| 20 |
- } |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func newTtyForTest(t *testing.T) (*os.File, error) {
|
|
| 24 |
- RequiresRoot(t) |
|
| 25 |
- return os.OpenFile("/dev/tty", os.O_RDWR, os.ModeDevice)
|
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-func newTempFile() (*os.File, error) {
|
|
| 29 |
- return ioutil.TempFile(os.TempDir(), "temp") |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-func TestGetWinsize(t *testing.T) {
|
|
| 33 |
- tty, err := newTtyForTest(t) |
|
| 34 |
- assert.NilError(t, err) |
|
| 35 |
- defer tty.Close() |
|
| 36 |
- winSize, err := GetWinsize(tty.Fd()) |
|
| 37 |
- assert.NilError(t, err) |
|
| 38 |
- assert.Assert(t, winSize != nil) |
|
| 39 |
- |
|
| 40 |
- newSize := Winsize{Width: 200, Height: 200, x: winSize.x, y: winSize.y}
|
|
| 41 |
- err = SetWinsize(tty.Fd(), &newSize) |
|
| 42 |
- assert.NilError(t, err) |
|
| 43 |
- winSize, err = GetWinsize(tty.Fd()) |
|
| 44 |
- assert.NilError(t, err) |
|
| 45 |
- assert.DeepEqual(t, *winSize, newSize, cmpWinsize) |
|
| 46 |
-} |
|
| 47 |
- |
|
| 48 |
-var cmpWinsize = cmp.AllowUnexported(Winsize{})
|
|
| 49 |
- |
|
| 50 |
-func TestSetWinsize(t *testing.T) {
|
|
| 51 |
- tty, err := newTtyForTest(t) |
|
| 52 |
- assert.NilError(t, err) |
|
| 53 |
- defer tty.Close() |
|
| 54 |
- winSize, err := GetWinsize(tty.Fd()) |
|
| 55 |
- assert.NilError(t, err) |
|
| 56 |
- assert.Assert(t, winSize != nil) |
|
| 57 |
- newSize := Winsize{Width: 200, Height: 200, x: winSize.x, y: winSize.y}
|
|
| 58 |
- err = SetWinsize(tty.Fd(), &newSize) |
|
| 59 |
- assert.NilError(t, err) |
|
| 60 |
- winSize, err = GetWinsize(tty.Fd()) |
|
| 61 |
- assert.NilError(t, err) |
|
| 62 |
- assert.DeepEqual(t, *winSize, newSize, cmpWinsize) |
|
| 63 |
-} |
|
| 64 |
- |
|
| 65 |
-func TestGetFdInfo(t *testing.T) {
|
|
| 66 |
- tty, err := newTtyForTest(t) |
|
| 67 |
- assert.NilError(t, err) |
|
| 68 |
- defer tty.Close() |
|
| 69 |
- inFd, isTerminal := GetFdInfo(tty) |
|
| 70 |
- assert.Equal(t, inFd, tty.Fd()) |
|
| 71 |
- assert.Equal(t, isTerminal, true) |
|
| 72 |
- tmpFile, err := newTempFile() |
|
| 73 |
- assert.NilError(t, err) |
|
| 74 |
- defer tmpFile.Close() |
|
| 75 |
- inFd, isTerminal = GetFdInfo(tmpFile) |
|
| 76 |
- assert.Equal(t, inFd, tmpFile.Fd()) |
|
| 77 |
- assert.Equal(t, isTerminal, false) |
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 |
-func TestIsTerminal(t *testing.T) {
|
|
| 81 |
- tty, err := newTtyForTest(t) |
|
| 82 |
- assert.NilError(t, err) |
|
| 83 |
- defer tty.Close() |
|
| 84 |
- isTerminal := IsTerminal(tty.Fd()) |
|
| 85 |
- assert.Equal(t, isTerminal, true) |
|
| 86 |
- tmpFile, err := newTempFile() |
|
| 87 |
- assert.NilError(t, err) |
|
| 88 |
- defer tmpFile.Close() |
|
| 89 |
- isTerminal = IsTerminal(tmpFile.Fd()) |
|
| 90 |
- assert.Equal(t, isTerminal, false) |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-func TestSaveState(t *testing.T) {
|
|
| 94 |
- tty, err := newTtyForTest(t) |
|
| 95 |
- assert.NilError(t, err) |
|
| 96 |
- defer tty.Close() |
|
| 97 |
- state, err := SaveState(tty.Fd()) |
|
| 98 |
- assert.NilError(t, err) |
|
| 99 |
- assert.Assert(t, state != nil) |
|
| 100 |
- tty, err = newTtyForTest(t) |
|
| 101 |
- assert.NilError(t, err) |
|
| 102 |
- defer tty.Close() |
|
| 103 |
- err = RestoreTerminal(tty.Fd(), state) |
|
| 104 |
- assert.NilError(t, err) |
|
| 105 |
-} |
|
| 106 |
- |
|
| 107 |
-func TestDisableEcho(t *testing.T) {
|
|
| 108 |
- tty, err := newTtyForTest(t) |
|
| 109 |
- assert.NilError(t, err) |
|
| 110 |
- defer tty.Close() |
|
| 111 |
- state, err := SetRawTerminal(tty.Fd()) |
|
| 112 |
- defer RestoreTerminal(tty.Fd(), state) |
|
| 113 |
- assert.NilError(t, err) |
|
| 114 |
- assert.Assert(t, state != nil) |
|
| 115 |
- err = DisableEcho(tty.Fd(), state) |
|
| 116 |
- assert.NilError(t, err) |
|
| 117 |
-} |
| 118 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,233 +0,0 @@ |
| 1 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io" |
|
| 5 |
- "os" |
|
| 6 |
- "os/signal" |
|
| 7 |
- "syscall" // used for STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and STD_ERROR_HANDLE |
|
| 8 |
- |
|
| 9 |
- "github.com/Azure/go-ansiterm/winterm" |
|
| 10 |
- windowsconsole "github.com/docker/docker/pkg/term/windows" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-// State holds the console mode for the terminal. |
|
| 14 |
-// Deprecated: use github.com/moby/term.State |
|
| 15 |
-type State struct {
|
|
| 16 |
- mode uint32 |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-// Winsize is used for window size. |
|
| 20 |
-// Deprecated: use github.com/moby/term.Winsize |
|
| 21 |
-type Winsize struct {
|
|
| 22 |
- Height uint16 |
|
| 23 |
- Width uint16 |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// vtInputSupported is true if winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported by the console |
|
| 27 |
-var vtInputSupported bool |
|
| 28 |
- |
|
| 29 |
-// StdStreams returns the standard streams (stdin, stdout, stderr). |
|
| 30 |
-// Deprecated: use github.com/moby/term.StdStreams |
|
| 31 |
-func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
| 32 |
- // Turn on VT handling on all std handles, if possible. This might |
|
| 33 |
- // fail, in which case we will fall back to terminal emulation. |
|
| 34 |
- var emulateStdin, emulateStdout, emulateStderr bool |
|
| 35 |
- fd := os.Stdin.Fd() |
|
| 36 |
- if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
| 37 |
- // Validate that winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it. |
|
| 38 |
- if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil {
|
|
| 39 |
- emulateStdin = true |
|
| 40 |
- } else {
|
|
| 41 |
- vtInputSupported = true |
|
| 42 |
- } |
|
| 43 |
- // Unconditionally set the console mode back even on failure because SetConsoleMode |
|
| 44 |
- // remembers invalid bits on input handles. |
|
| 45 |
- winterm.SetConsoleMode(fd, mode) |
|
| 46 |
- } |
|
| 47 |
- |
|
| 48 |
- fd = os.Stdout.Fd() |
|
| 49 |
- if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
| 50 |
- // Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it. |
|
| 51 |
- if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING|winterm.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
|
|
| 52 |
- emulateStdout = true |
|
| 53 |
- } else {
|
|
| 54 |
- winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
|
| 55 |
- } |
|
| 56 |
- } |
|
| 57 |
- |
|
| 58 |
- fd = os.Stderr.Fd() |
|
| 59 |
- if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
| 60 |
- // Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it. |
|
| 61 |
- if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING|winterm.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
|
|
| 62 |
- emulateStderr = true |
|
| 63 |
- } else {
|
|
| 64 |
- winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
|
| 65 |
- } |
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- // Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and |
|
| 69 |
- // STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as |
|
| 70 |
- // go-ansiterm hasn't switch to x/sys/windows. |
|
| 71 |
- // TODO: switch back to x/sys/windows once go-ansiterm has switched |
|
| 72 |
- if emulateStdin {
|
|
| 73 |
- stdIn = windowsconsole.NewAnsiReader(syscall.STD_INPUT_HANDLE) |
|
| 74 |
- } else {
|
|
| 75 |
- stdIn = os.Stdin |
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- if emulateStdout {
|
|
| 79 |
- stdOut = windowsconsole.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE) |
|
| 80 |
- } else {
|
|
| 81 |
- stdOut = os.Stdout |
|
| 82 |
- } |
|
| 83 |
- |
|
| 84 |
- if emulateStderr {
|
|
| 85 |
- stdErr = windowsconsole.NewAnsiWriter(syscall.STD_ERROR_HANDLE) |
|
| 86 |
- } else {
|
|
| 87 |
- stdErr = os.Stderr |
|
| 88 |
- } |
|
| 89 |
- |
|
| 90 |
- return |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. |
|
| 94 |
-// Deprecated: use github.com/moby/term.GetFdInfo |
|
| 95 |
-func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
| 96 |
- return windowsconsole.GetHandleInfo(in) |
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 |
-// GetWinsize returns the window size based on the specified file descriptor. |
|
| 100 |
-// Deprecated: use github.com/moby/term.GetWinsize |
|
| 101 |
-func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
| 102 |
- info, err := winterm.GetConsoleScreenBufferInfo(fd) |
|
| 103 |
- if err != nil {
|
|
| 104 |
- return nil, err |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- winsize := &Winsize{
|
|
| 108 |
- Width: uint16(info.Window.Right - info.Window.Left + 1), |
|
| 109 |
- Height: uint16(info.Window.Bottom - info.Window.Top + 1), |
|
| 110 |
- } |
|
| 111 |
- |
|
| 112 |
- return winsize, nil |
|
| 113 |
-} |
|
| 114 |
- |
|
| 115 |
-// IsTerminal returns true if the given file descriptor is a terminal. |
|
| 116 |
-// Deprecated: use github.com/moby/term.IsTerminal |
|
| 117 |
-func IsTerminal(fd uintptr) bool {
|
|
| 118 |
- return windowsconsole.IsConsole(fd) |
|
| 119 |
-} |
|
| 120 |
- |
|
| 121 |
-// RestoreTerminal restores the terminal connected to the given file descriptor |
|
| 122 |
-// to a previous state. |
|
| 123 |
-// Deprecated: use github.com/moby/term.RestoreTerminal |
|
| 124 |
-func RestoreTerminal(fd uintptr, state *State) error {
|
|
| 125 |
- return winterm.SetConsoleMode(fd, state.mode) |
|
| 126 |
-} |
|
| 127 |
- |
|
| 128 |
-// SaveState saves the state of the terminal connected to the given file descriptor. |
|
| 129 |
-// Deprecated: use github.com/moby/term.SaveState |
|
| 130 |
-func SaveState(fd uintptr) (*State, error) {
|
|
| 131 |
- mode, e := winterm.GetConsoleMode(fd) |
|
| 132 |
- if e != nil {
|
|
| 133 |
- return nil, e |
|
| 134 |
- } |
|
| 135 |
- |
|
| 136 |
- return &State{mode: mode}, nil
|
|
| 137 |
-} |
|
| 138 |
- |
|
| 139 |
-// DisableEcho disables echo for the terminal connected to the given file descriptor. |
|
| 140 |
-// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx |
|
| 141 |
-// Deprecated: use github.com/moby/term.DisableEcho |
|
| 142 |
-func DisableEcho(fd uintptr, state *State) error {
|
|
| 143 |
- mode := state.mode |
|
| 144 |
- mode &^= winterm.ENABLE_ECHO_INPUT |
|
| 145 |
- mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT |
|
| 146 |
- err := winterm.SetConsoleMode(fd, mode) |
|
| 147 |
- if err != nil {
|
|
| 148 |
- return err |
|
| 149 |
- } |
|
| 150 |
- |
|
| 151 |
- // Register an interrupt handler to catch and restore prior state |
|
| 152 |
- restoreAtInterrupt(fd, state) |
|
| 153 |
- return nil |
|
| 154 |
-} |
|
| 155 |
- |
|
| 156 |
-// SetRawTerminal puts the terminal connected to the given file descriptor into |
|
| 157 |
-// raw mode and returns the previous state. On UNIX, this puts both the input |
|
| 158 |
-// and output into raw mode. On Windows, it only puts the input into raw mode. |
|
| 159 |
-// Deprecated: use github.com/moby/term.SetRawTerminal |
|
| 160 |
-func SetRawTerminal(fd uintptr) (*State, error) {
|
|
| 161 |
- state, err := MakeRaw(fd) |
|
| 162 |
- if err != nil {
|
|
| 163 |
- return nil, err |
|
| 164 |
- } |
|
| 165 |
- |
|
| 166 |
- // Register an interrupt handler to catch and restore prior state |
|
| 167 |
- restoreAtInterrupt(fd, state) |
|
| 168 |
- return state, err |
|
| 169 |
-} |
|
| 170 |
- |
|
| 171 |
-// SetRawTerminalOutput puts the output of terminal connected to the given file |
|
| 172 |
-// descriptor into raw mode. On UNIX, this does nothing and returns nil for the |
|
| 173 |
-// state. On Windows, it disables LF -> CRLF translation. |
|
| 174 |
-// Deprecated: use github.com/moby/term.SetRawTerminalOutput |
|
| 175 |
-func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
|
| 176 |
- state, err := SaveState(fd) |
|
| 177 |
- if err != nil {
|
|
| 178 |
- return nil, err |
|
| 179 |
- } |
|
| 180 |
- |
|
| 181 |
- // Ignore failures, since winterm.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this |
|
| 182 |
- // version of Windows. |
|
| 183 |
- winterm.SetConsoleMode(fd, state.mode|winterm.DISABLE_NEWLINE_AUTO_RETURN) |
|
| 184 |
- return state, err |
|
| 185 |
-} |
|
| 186 |
- |
|
| 187 |
-// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw |
|
| 188 |
-// mode and returns the previous state of the terminal so that it can be restored. |
|
| 189 |
-// Deprecated: use github.com/moby/term.MakeRaw |
|
| 190 |
-func MakeRaw(fd uintptr) (*State, error) {
|
|
| 191 |
- state, err := SaveState(fd) |
|
| 192 |
- if err != nil {
|
|
| 193 |
- return nil, err |
|
| 194 |
- } |
|
| 195 |
- |
|
| 196 |
- mode := state.mode |
|
| 197 |
- |
|
| 198 |
- // See |
|
| 199 |
- // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx |
|
| 200 |
- // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx |
|
| 201 |
- |
|
| 202 |
- // Disable these modes |
|
| 203 |
- mode &^= winterm.ENABLE_ECHO_INPUT |
|
| 204 |
- mode &^= winterm.ENABLE_LINE_INPUT |
|
| 205 |
- mode &^= winterm.ENABLE_MOUSE_INPUT |
|
| 206 |
- mode &^= winterm.ENABLE_WINDOW_INPUT |
|
| 207 |
- mode &^= winterm.ENABLE_PROCESSED_INPUT |
|
| 208 |
- |
|
| 209 |
- // Enable these modes |
|
| 210 |
- mode |= winterm.ENABLE_EXTENDED_FLAGS |
|
| 211 |
- mode |= winterm.ENABLE_INSERT_MODE |
|
| 212 |
- mode |= winterm.ENABLE_QUICK_EDIT_MODE |
|
| 213 |
- if vtInputSupported {
|
|
| 214 |
- mode |= winterm.ENABLE_VIRTUAL_TERMINAL_INPUT |
|
| 215 |
- } |
|
| 216 |
- |
|
| 217 |
- err = winterm.SetConsoleMode(fd, mode) |
|
| 218 |
- if err != nil {
|
|
| 219 |
- return nil, err |
|
| 220 |
- } |
|
| 221 |
- return state, nil |
|
| 222 |
-} |
|
| 223 |
- |
|
| 224 |
-func restoreAtInterrupt(fd uintptr, state *State) {
|
|
| 225 |
- sigchan := make(chan os.Signal, 1) |
|
| 226 |
- signal.Notify(sigchan, os.Interrupt) |
|
| 227 |
- |
|
| 228 |
- go func() {
|
|
| 229 |
- _ = <-sigchan |
|
| 230 |
- RestoreTerminal(fd, state) |
|
| 231 |
- os.Exit(0) |
|
| 232 |
- }() |
|
| 233 |
-} |
| 234 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,44 +0,0 @@ |
| 1 |
-// +build darwin freebsd openbsd netbsd |
|
| 2 |
- |
|
| 3 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "unsafe" |
|
| 7 |
- |
|
| 8 |
- "golang.org/x/sys/unix" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-const ( |
|
| 12 |
- getTermios = unix.TIOCGETA |
|
| 13 |
- setTermios = unix.TIOCSETA |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-// Termios is the Unix API for terminal I/O. |
|
| 17 |
-// Deprecated: use github.com/moby/term.Termios |
|
| 18 |
-type Termios unix.Termios |
|
| 19 |
- |
|
| 20 |
-// MakeRaw put the terminal connected to the given file descriptor into raw |
|
| 21 |
-// mode and returns the previous state of the terminal so that it can be |
|
| 22 |
-// restored. |
|
| 23 |
-// Deprecated: use github.com/moby/term.MakeRaw |
|
| 24 |
-func MakeRaw(fd uintptr) (*State, error) {
|
|
| 25 |
- var oldState State |
|
| 26 |
- if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
|
| 27 |
- return nil, err |
|
| 28 |
- } |
|
| 29 |
- |
|
| 30 |
- newState := oldState.termios |
|
| 31 |
- newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) |
|
| 32 |
- newState.Oflag &^= unix.OPOST |
|
| 33 |
- newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) |
|
| 34 |
- newState.Cflag &^= (unix.CSIZE | unix.PARENB) |
|
| 35 |
- newState.Cflag |= unix.CS8 |
|
| 36 |
- newState.Cc[unix.VMIN] = 1 |
|
| 37 |
- newState.Cc[unix.VTIME] = 0 |
|
| 38 |
- |
|
| 39 |
- if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
|
| 40 |
- return nil, err |
|
| 41 |
- } |
|
| 42 |
- |
|
| 43 |
- return &oldState, nil |
|
| 44 |
-} |
| 45 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,41 +0,0 @@ |
| 1 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "golang.org/x/sys/unix" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-const ( |
|
| 8 |
- getTermios = unix.TCGETS |
|
| 9 |
- setTermios = unix.TCSETS |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-// Termios is the Unix API for terminal I/O. |
|
| 13 |
-// Deprecated: use github.com/moby/term.Termios |
|
| 14 |
-type Termios unix.Termios |
|
| 15 |
- |
|
| 16 |
-// MakeRaw put the terminal connected to the given file descriptor into raw |
|
| 17 |
-// mode and returns the previous state of the terminal so that it can be |
|
| 18 |
-// restored. |
|
| 19 |
-// Deprecated: use github.com/moby/term.MakeRaw |
|
| 20 |
-func MakeRaw(fd uintptr) (*State, error) {
|
|
| 21 |
- termios, err := unix.IoctlGetTermios(int(fd), getTermios) |
|
| 22 |
- if err != nil {
|
|
| 23 |
- return nil, err |
|
| 24 |
- } |
|
| 25 |
- |
|
| 26 |
- var oldState State |
|
| 27 |
- oldState.termios = Termios(*termios) |
|
| 28 |
- |
|
| 29 |
- termios.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) |
|
| 30 |
- termios.Oflag &^= unix.OPOST |
|
| 31 |
- termios.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) |
|
| 32 |
- termios.Cflag &^= (unix.CSIZE | unix.PARENB) |
|
| 33 |
- termios.Cflag |= unix.CS8 |
|
| 34 |
- termios.Cc[unix.VMIN] = 1 |
|
| 35 |
- termios.Cc[unix.VTIME] = 0 |
|
| 36 |
- |
|
| 37 |
- if err := unix.IoctlSetTermios(int(fd), setTermios, termios); err != nil {
|
|
| 38 |
- return nil, err |
|
| 39 |
- } |
|
| 40 |
- return &oldState, nil |
|
| 41 |
-} |
| 42 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,264 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-package windowsconsole // import "github.com/docker/docker/pkg/term/windows" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "bytes" |
|
| 7 |
- "errors" |
|
| 8 |
- "fmt" |
|
| 9 |
- "io" |
|
| 10 |
- "os" |
|
| 11 |
- "strings" |
|
| 12 |
- "unsafe" |
|
| 13 |
- |
|
| 14 |
- ansiterm "github.com/Azure/go-ansiterm" |
|
| 15 |
- "github.com/Azure/go-ansiterm/winterm" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-const ( |
|
| 19 |
- escapeSequence = ansiterm.KEY_ESC_CSI |
|
| 20 |
-) |
|
| 21 |
- |
|
| 22 |
-// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation. |
|
| 23 |
-type ansiReader struct {
|
|
| 24 |
- file *os.File |
|
| 25 |
- fd uintptr |
|
| 26 |
- buffer []byte |
|
| 27 |
- cbBuffer int |
|
| 28 |
- command []byte |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a |
|
| 32 |
-// Windows console input handle. |
|
| 33 |
-// Deprecated: use github.com/moby/term/windows.NewAnsiReader |
|
| 34 |
-func NewAnsiReader(nFile int) io.ReadCloser {
|
|
| 35 |
- initLogger() |
|
| 36 |
- file, fd := winterm.GetStdFile(nFile) |
|
| 37 |
- return &ansiReader{
|
|
| 38 |
- file: file, |
|
| 39 |
- fd: fd, |
|
| 40 |
- command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), |
|
| 41 |
- buffer: make([]byte, 0), |
|
| 42 |
- } |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-// Close closes the wrapped file. |
|
| 46 |
-func (ar *ansiReader) Close() (err error) {
|
|
| 47 |
- return ar.file.Close() |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-// Fd returns the file descriptor of the wrapped file. |
|
| 51 |
-func (ar *ansiReader) Fd() uintptr {
|
|
| 52 |
- return ar.fd |
|
| 53 |
-} |
|
| 54 |
- |
|
| 55 |
-// Read reads up to len(p) bytes of translated input events into p. |
|
| 56 |
-func (ar *ansiReader) Read(p []byte) (int, error) {
|
|
| 57 |
- if len(p) == 0 {
|
|
| 58 |
- return 0, nil |
|
| 59 |
- } |
|
| 60 |
- |
|
| 61 |
- // Previously read bytes exist, read as much as we can and return |
|
| 62 |
- if len(ar.buffer) > 0 {
|
|
| 63 |
- logger.Debugf("Reading previously cached bytes")
|
|
| 64 |
- |
|
| 65 |
- originalLength := len(ar.buffer) |
|
| 66 |
- copiedLength := copy(p, ar.buffer) |
|
| 67 |
- |
|
| 68 |
- if copiedLength == originalLength {
|
|
| 69 |
- ar.buffer = make([]byte, 0, len(p)) |
|
| 70 |
- } else {
|
|
| 71 |
- ar.buffer = ar.buffer[copiedLength:] |
|
| 72 |
- } |
|
| 73 |
- |
|
| 74 |
- logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
|
|
| 75 |
- return copiedLength, nil |
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- // Read and translate key events |
|
| 79 |
- events, err := readInputEvents(ar.fd, len(p)) |
|
| 80 |
- if err != nil {
|
|
| 81 |
- return 0, err |
|
| 82 |
- } else if len(events) == 0 {
|
|
| 83 |
- logger.Debug("No input events detected")
|
|
| 84 |
- return 0, nil |
|
| 85 |
- } |
|
| 86 |
- |
|
| 87 |
- keyBytes := translateKeyEvents(events, []byte(escapeSequence)) |
|
| 88 |
- |
|
| 89 |
- // Save excess bytes and right-size keyBytes |
|
| 90 |
- if len(keyBytes) > len(p) {
|
|
| 91 |
- logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
|
|
| 92 |
- ar.buffer = keyBytes[len(p):] |
|
| 93 |
- keyBytes = keyBytes[:len(p)] |
|
| 94 |
- } else if len(keyBytes) == 0 {
|
|
| 95 |
- logger.Debug("No key bytes returned from the translator")
|
|
| 96 |
- return 0, nil |
|
| 97 |
- } |
|
| 98 |
- |
|
| 99 |
- copiedLength := copy(p, keyBytes) |
|
| 100 |
- if copiedLength != len(keyBytes) {
|
|
| 101 |
- return 0, errors.New("unexpected copy length encountered")
|
|
| 102 |
- } |
|
| 103 |
- |
|
| 104 |
- logger.Debugf("Read p[%d]: % x", copiedLength, p)
|
|
| 105 |
- logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
|
|
| 106 |
- return copiedLength, nil |
|
| 107 |
-} |
|
| 108 |
- |
|
| 109 |
-// readInputEvents polls until at least one event is available. |
|
| 110 |
-func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
|
|
| 111 |
- // Determine the maximum number of records to retrieve |
|
| 112 |
- // -- Cast around the type system to obtain the size of a single INPUT_RECORD. |
|
| 113 |
- // unsafe.Sizeof requires an expression vs. a type-reference; the casting |
|
| 114 |
- // tricks the type system into believing it has such an expression. |
|
| 115 |
- recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes))))) |
|
| 116 |
- countRecords := maxBytes / recordSize |
|
| 117 |
- if countRecords > ansiterm.MAX_INPUT_EVENTS {
|
|
| 118 |
- countRecords = ansiterm.MAX_INPUT_EVENTS |
|
| 119 |
- } else if countRecords == 0 {
|
|
| 120 |
- countRecords = 1 |
|
| 121 |
- } |
|
| 122 |
- logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)
|
|
| 123 |
- |
|
| 124 |
- // Wait for and read input events |
|
| 125 |
- events := make([]winterm.INPUT_RECORD, countRecords) |
|
| 126 |
- nEvents := uint32(0) |
|
| 127 |
- eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE) |
|
| 128 |
- if err != nil {
|
|
| 129 |
- return nil, err |
|
| 130 |
- } |
|
| 131 |
- |
|
| 132 |
- if eventsExist {
|
|
| 133 |
- err = winterm.ReadConsoleInput(fd, events, &nEvents) |
|
| 134 |
- if err != nil {
|
|
| 135 |
- return nil, err |
|
| 136 |
- } |
|
| 137 |
- } |
|
| 138 |
- |
|
| 139 |
- // Return a slice restricted to the number of returned records |
|
| 140 |
- logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
|
|
| 141 |
- return events[:nEvents], nil |
|
| 142 |
-} |
|
| 143 |
- |
|
| 144 |
-// KeyEvent Translation Helpers |
|
| 145 |
- |
|
| 146 |
-var arrowKeyMapPrefix = map[uint16]string{
|
|
| 147 |
- winterm.VK_UP: "%s%sA", |
|
| 148 |
- winterm.VK_DOWN: "%s%sB", |
|
| 149 |
- winterm.VK_RIGHT: "%s%sC", |
|
| 150 |
- winterm.VK_LEFT: "%s%sD", |
|
| 151 |
-} |
|
| 152 |
- |
|
| 153 |
-var keyMapPrefix = map[uint16]string{
|
|
| 154 |
- winterm.VK_UP: "\x1B[%sA", |
|
| 155 |
- winterm.VK_DOWN: "\x1B[%sB", |
|
| 156 |
- winterm.VK_RIGHT: "\x1B[%sC", |
|
| 157 |
- winterm.VK_LEFT: "\x1B[%sD", |
|
| 158 |
- winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1 |
|
| 159 |
- winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4 |
|
| 160 |
- winterm.VK_INSERT: "\x1B[2%s~", |
|
| 161 |
- winterm.VK_DELETE: "\x1B[3%s~", |
|
| 162 |
- winterm.VK_PRIOR: "\x1B[5%s~", |
|
| 163 |
- winterm.VK_NEXT: "\x1B[6%s~", |
|
| 164 |
- winterm.VK_F1: "", |
|
| 165 |
- winterm.VK_F2: "", |
|
| 166 |
- winterm.VK_F3: "\x1B[13%s~", |
|
| 167 |
- winterm.VK_F4: "\x1B[14%s~", |
|
| 168 |
- winterm.VK_F5: "\x1B[15%s~", |
|
| 169 |
- winterm.VK_F6: "\x1B[17%s~", |
|
| 170 |
- winterm.VK_F7: "\x1B[18%s~", |
|
| 171 |
- winterm.VK_F8: "\x1B[19%s~", |
|
| 172 |
- winterm.VK_F9: "\x1B[20%s~", |
|
| 173 |
- winterm.VK_F10: "\x1B[21%s~", |
|
| 174 |
- winterm.VK_F11: "\x1B[23%s~", |
|
| 175 |
- winterm.VK_F12: "\x1B[24%s~", |
|
| 176 |
-} |
|
| 177 |
- |
|
| 178 |
-// translateKeyEvents converts the input events into the appropriate ANSI string. |
|
| 179 |
-func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
|
|
| 180 |
- var buffer bytes.Buffer |
|
| 181 |
- for _, event := range events {
|
|
| 182 |
- if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
|
|
| 183 |
- buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence)) |
|
| 184 |
- } |
|
| 185 |
- } |
|
| 186 |
- |
|
| 187 |
- return buffer.Bytes() |
|
| 188 |
-} |
|
| 189 |
- |
|
| 190 |
-// keyToString maps the given input event record to the corresponding string. |
|
| 191 |
-func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
|
|
| 192 |
- if keyEvent.UnicodeChar == 0 {
|
|
| 193 |
- return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence) |
|
| 194 |
- } |
|
| 195 |
- |
|
| 196 |
- _, alt, control := getControlKeys(keyEvent.ControlKeyState) |
|
| 197 |
- if control {
|
|
| 198 |
- // TODO(azlinux): Implement following control sequences |
|
| 199 |
- // <Ctrl>-D Signals the end of input from the keyboard; also exits current shell. |
|
| 200 |
- // <Ctrl>-H Deletes the first character to the left of the cursor. Also called the ERASE key. |
|
| 201 |
- // <Ctrl>-Q Restarts printing after it has been stopped with <Ctrl>-s. |
|
| 202 |
- // <Ctrl>-S Suspends printing on the screen (does not stop the program). |
|
| 203 |
- // <Ctrl>-U Deletes all characters on the current line. Also called the KILL key. |
|
| 204 |
- // <Ctrl>-E Quits current command and creates a core |
|
| 205 |
- |
|
| 206 |
- } |
|
| 207 |
- |
|
| 208 |
- // <Alt>+Key generates ESC N Key |
|
| 209 |
- if !control && alt {
|
|
| 210 |
- return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar)) |
|
| 211 |
- } |
|
| 212 |
- |
|
| 213 |
- return string(keyEvent.UnicodeChar) |
|
| 214 |
-} |
|
| 215 |
- |
|
| 216 |
-// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string. |
|
| 217 |
-func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
|
|
| 218 |
- shift, alt, control := getControlKeys(controlState) |
|
| 219 |
- modifier := getControlKeysModifier(shift, alt, control) |
|
| 220 |
- |
|
| 221 |
- if format, ok := arrowKeyMapPrefix[key]; ok {
|
|
| 222 |
- return fmt.Sprintf(format, escapeSequence, modifier) |
|
| 223 |
- } |
|
| 224 |
- |
|
| 225 |
- if format, ok := keyMapPrefix[key]; ok {
|
|
| 226 |
- return fmt.Sprintf(format, modifier) |
|
| 227 |
- } |
|
| 228 |
- |
|
| 229 |
- return "" |
|
| 230 |
-} |
|
| 231 |
- |
|
| 232 |
-// getControlKeys extracts the shift, alt, and ctrl key states. |
|
| 233 |
-func getControlKeys(controlState uint32) (shift, alt, control bool) {
|
|
| 234 |
- shift = 0 != (controlState & winterm.SHIFT_PRESSED) |
|
| 235 |
- alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED)) |
|
| 236 |
- control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED)) |
|
| 237 |
- return shift, alt, control |
|
| 238 |
-} |
|
| 239 |
- |
|
| 240 |
-// getControlKeysModifier returns the ANSI modifier for the given combination of control keys. |
|
| 241 |
-func getControlKeysModifier(shift, alt, control bool) string {
|
|
| 242 |
- if shift && alt && control {
|
|
| 243 |
- return ansiterm.KEY_CONTROL_PARAM_8 |
|
| 244 |
- } |
|
| 245 |
- if alt && control {
|
|
| 246 |
- return ansiterm.KEY_CONTROL_PARAM_7 |
|
| 247 |
- } |
|
| 248 |
- if shift && control {
|
|
| 249 |
- return ansiterm.KEY_CONTROL_PARAM_6 |
|
| 250 |
- } |
|
| 251 |
- if control {
|
|
| 252 |
- return ansiterm.KEY_CONTROL_PARAM_5 |
|
| 253 |
- } |
|
| 254 |
- if shift && alt {
|
|
| 255 |
- return ansiterm.KEY_CONTROL_PARAM_4 |
|
| 256 |
- } |
|
| 257 |
- if alt {
|
|
| 258 |
- return ansiterm.KEY_CONTROL_PARAM_3 |
|
| 259 |
- } |
|
| 260 |
- if shift {
|
|
| 261 |
- return ansiterm.KEY_CONTROL_PARAM_2 |
|
| 262 |
- } |
|
| 263 |
- return "" |
|
| 264 |
-} |
| 265 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,65 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-package windowsconsole // import "github.com/docker/docker/pkg/term/windows" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "io" |
|
| 7 |
- "os" |
|
| 8 |
- |
|
| 9 |
- ansiterm "github.com/Azure/go-ansiterm" |
|
| 10 |
- "github.com/Azure/go-ansiterm/winterm" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation. |
|
| 14 |
-type ansiWriter struct {
|
|
| 15 |
- file *os.File |
|
| 16 |
- fd uintptr |
|
| 17 |
- infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO |
|
| 18 |
- command []byte |
|
| 19 |
- escapeSequence []byte |
|
| 20 |
- inAnsiSequence bool |
|
| 21 |
- parser *ansiterm.AnsiParser |
|
| 22 |
-} |
|
| 23 |
- |
|
| 24 |
-// NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a |
|
| 25 |
-// Windows console output handle. |
|
| 26 |
-// Deprecated: use github.com/moby/term/windows.NewAnsiWriter |
|
| 27 |
-func NewAnsiWriter(nFile int) io.Writer {
|
|
| 28 |
- initLogger() |
|
| 29 |
- file, fd := winterm.GetStdFile(nFile) |
|
| 30 |
- info, err := winterm.GetConsoleScreenBufferInfo(fd) |
|
| 31 |
- if err != nil {
|
|
| 32 |
- return nil |
|
| 33 |
- } |
|
| 34 |
- |
|
| 35 |
- parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file))
|
|
| 36 |
- logger.Infof("newAnsiWriter: parser %p", parser)
|
|
| 37 |
- |
|
| 38 |
- aw := &ansiWriter{
|
|
| 39 |
- file: file, |
|
| 40 |
- fd: fd, |
|
| 41 |
- infoReset: info, |
|
| 42 |
- command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), |
|
| 43 |
- escapeSequence: []byte(ansiterm.KEY_ESC_CSI), |
|
| 44 |
- parser: parser, |
|
| 45 |
- } |
|
| 46 |
- |
|
| 47 |
- logger.Infof("newAnsiWriter: aw.parser %p", aw.parser)
|
|
| 48 |
- logger.Infof("newAnsiWriter: %v", aw)
|
|
| 49 |
- return aw |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-func (aw *ansiWriter) Fd() uintptr {
|
|
| 53 |
- return aw.fd |
|
| 54 |
-} |
|
| 55 |
- |
|
| 56 |
-// Write writes len(p) bytes from p to the underlying data stream. |
|
| 57 |
-func (aw *ansiWriter) Write(p []byte) (total int, err error) {
|
|
| 58 |
- if len(p) == 0 {
|
|
| 59 |
- return 0, nil |
|
| 60 |
- } |
|
| 61 |
- |
|
| 62 |
- logger.Infof("Write: % x", p)
|
|
| 63 |
- logger.Infof("Write: %s", string(p))
|
|
| 64 |
- return aw.parser.Parse(p) |
|
| 65 |
-} |
| 66 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,37 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-package windowsconsole // import "github.com/docker/docker/pkg/term/windows" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "os" |
|
| 7 |
- |
|
| 8 |
- "github.com/Azure/go-ansiterm/winterm" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// GetHandleInfo returns file descriptor and bool indicating whether the file is a console. |
|
| 12 |
-// Deprecated: use github.com/moby/term/windows.GetHandleInfo |
|
| 13 |
-func GetHandleInfo(in interface{}) (uintptr, bool) {
|
|
| 14 |
- switch t := in.(type) {
|
|
| 15 |
- case *ansiReader: |
|
| 16 |
- return t.Fd(), true |
|
| 17 |
- case *ansiWriter: |
|
| 18 |
- return t.Fd(), true |
|
| 19 |
- } |
|
| 20 |
- |
|
| 21 |
- var inFd uintptr |
|
| 22 |
- var isTerminal bool |
|
| 23 |
- |
|
| 24 |
- if file, ok := in.(*os.File); ok {
|
|
| 25 |
- inFd = file.Fd() |
|
| 26 |
- isTerminal = IsConsole(inFd) |
|
| 27 |
- } |
|
| 28 |
- return inFd, isTerminal |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-// IsConsole returns true if the given file descriptor is a Windows Console. |
|
| 32 |
-// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. |
|
| 33 |
-// Deprecated: use github.com/moby/term/windows.IsConsole |
|
| 34 |
-func IsConsole(fd uintptr) bool {
|
|
| 35 |
- _, e := winterm.GetConsoleMode(fd) |
|
| 36 |
- return e == nil |
|
| 37 |
-} |
| 38 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+// Package windowsconsole implements ANSI-aware input and output streams for use |
|
| 3 |
+// by the Docker Windows client. When asked for the set of standard streams (e.g., |
|
| 4 |
+// stdin, stdout, stderr), the code will create and return pseudo-streams that |
|
| 5 |
+// convert ANSI sequences to / from Windows Console API calls. |
|
| 6 |
+// |
|
| 7 |
+// Deprecated: use github.com/moby/term/windows instead |
|
| 8 |
+package windowsconsole // import "github.com/docker/docker/pkg/term/windows" |
|
| 9 |
+ |
|
| 10 |
+import ( |
|
| 11 |
+ windowsconsole "github.com/moby/term/windows" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+var ( |
|
| 15 |
+ // GetHandleInfo returns file descriptor and bool indicating whether the file is a console. |
|
| 16 |
+ // Deprecated: use github.com/moby/term/windows.GetHandleInfo |
|
| 17 |
+ GetHandleInfo = windowsconsole.GetHandleInfo |
|
| 18 |
+ |
|
| 19 |
+ // IsConsole returns true if the given file descriptor is a Windows Console. |
|
| 20 |
+ // The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. |
|
| 21 |
+ // Deprecated: use github.com/moby/term/windows.IsConsole |
|
| 22 |
+ IsConsole = windowsconsole.IsConsole |
|
| 23 |
+ |
|
| 24 |
+ // NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a |
|
| 25 |
+ // Windows console input handle. |
|
| 26 |
+ // Deprecated: use github.com/moby/term/windows.NewAnsiReader |
|
| 27 |
+ NewAnsiReader = windowsconsole.NewAnsiReader |
|
| 28 |
+ |
|
| 29 |
+ // NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a |
|
| 30 |
+ // Windows console output handle. |
|
| 31 |
+ // Deprecated: use github.com/moby/term/windows.NewAnsiWriter |
|
| 32 |
+ NewAnsiWriter = windowsconsole.NewAnsiWriter |
|
| 33 |
+) |
| 0 | 34 |
deleted file mode 100644 |
| ... | ... |
@@ -1,37 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-// Package windowsconsole implements ANSI-aware input and output streams for use |
|
| 4 |
-// by the Docker Windows client. When asked for the set of standard streams (e.g., |
|
| 5 |
-// stdin, stdout, stderr), the code will create and return pseudo-streams that |
|
| 6 |
-// convert ANSI sequences to / from Windows Console API calls. |
|
| 7 |
-// |
|
| 8 |
-// Deprecated: use github.com/moby/term/windows instead |
|
| 9 |
-package windowsconsole // import "github.com/docker/docker/pkg/term/windows" |
|
| 10 |
- |
|
| 11 |
-import ( |
|
| 12 |
- "io/ioutil" |
|
| 13 |
- "os" |
|
| 14 |
- "sync" |
|
| 15 |
- |
|
| 16 |
- ansiterm "github.com/Azure/go-ansiterm" |
|
| 17 |
- "github.com/sirupsen/logrus" |
|
| 18 |
-) |
|
| 19 |
- |
|
| 20 |
-var logger *logrus.Logger |
|
| 21 |
-var initOnce sync.Once |
|
| 22 |
- |
|
| 23 |
-func initLogger() {
|
|
| 24 |
- initOnce.Do(func() {
|
|
| 25 |
- logFile := ioutil.Discard |
|
| 26 |
- |
|
| 27 |
- if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
|
| 28 |
- logFile, _ = os.Create("ansiReaderWriter.log")
|
|
| 29 |
- } |
|
| 30 |
- |
|
| 31 |
- logger = &logrus.Logger{
|
|
| 32 |
- Out: logFile, |
|
| 33 |
- Formatter: new(logrus.TextFormatter), |
|
| 34 |
- Level: logrus.DebugLevel, |
|
| 35 |
- } |
|
| 36 |
- }) |
|
| 37 |
-} |
| 38 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,22 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package term // import "github.com/docker/docker/pkg/term" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "golang.org/x/sys/unix" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// GetWinsize returns the window size based on the specified file descriptor. |
|
| 10 |
-// Deprecated: use github.com/moby/term.GetWinsize |
|
| 11 |
-func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
| 12 |
- uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ) |
|
| 13 |
- ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
|
|
| 14 |
- return ws, err |
|
| 15 |
-} |
|
| 16 |
- |
|
| 17 |
-// SetWinsize tries to set the specified window size for the specified file descriptor. |
|
| 18 |
-// Deprecated: use github.com/moby/term.GetWinsize |
|
| 19 |
-func SetWinsize(fd uintptr, ws *Winsize) error {
|
|
| 20 |
- uws := &unix.Winsize{Row: ws.Height, Col: ws.Width, Xpixel: ws.x, Ypixel: ws.y}
|
|
| 21 |
- return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, uws) |
|
| 22 |
-} |
| ... | ... |
@@ -6,6 +6,7 @@ github.com/golang/gddo 72a348e765d293ed6d1ded7b6995 |
| 6 | 6 |
github.com/google/uuid 0cd6bf5da1e1c83f8b45653022c74f71af0538a4 # v1.1.1 |
| 7 | 7 |
github.com/gorilla/mux 00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15 # v1.7.3 |
| 8 | 8 |
github.com/Microsoft/opengcs a10967154e143a36014584a6f664344e3bb0aa64 |
| 9 |
+github.com/moby/term 063f2cd0b49dbb0752774d1cb649998d91424fea |
|
| 9 | 10 |
|
| 10 | 11 |
github.com/creack/pty 3a6a957789163cacdfe0e291617a1c8e80612c11 # v1.1.9 |
| 11 | 12 |
github.com/konsorten/go-windows-terminal-sequences f55edac94c9bbba5d6182a4be46d86a2c9b5b50e # v1.0.2 |
| 12 | 13 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,191 @@ |
| 0 |
+ |
|
| 1 |
+ Apache License |
|
| 2 |
+ Version 2.0, January 2004 |
|
| 3 |
+ https://www.apache.org/licenses/ |
|
| 4 |
+ |
|
| 5 |
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|
| 6 |
+ |
|
| 7 |
+ 1. Definitions. |
|
| 8 |
+ |
|
| 9 |
+ "License" shall mean the terms and conditions for use, reproduction, |
|
| 10 |
+ and distribution as defined by Sections 1 through 9 of this document. |
|
| 11 |
+ |
|
| 12 |
+ "Licensor" shall mean the copyright owner or entity authorized by |
|
| 13 |
+ the copyright owner that is granting the License. |
|
| 14 |
+ |
|
| 15 |
+ "Legal Entity" shall mean the union of the acting entity and all |
|
| 16 |
+ other entities that control, are controlled by, or are under common |
|
| 17 |
+ control with that entity. For the purposes of this definition, |
|
| 18 |
+ "control" means (i) the power, direct or indirect, to cause the |
|
| 19 |
+ direction or management of such entity, whether by contract or |
|
| 20 |
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the |
|
| 21 |
+ outstanding shares, or (iii) beneficial ownership of such entity. |
|
| 22 |
+ |
|
| 23 |
+ "You" (or "Your") shall mean an individual or Legal Entity |
|
| 24 |
+ exercising permissions granted by this License. |
|
| 25 |
+ |
|
| 26 |
+ "Source" form shall mean the preferred form for making modifications, |
|
| 27 |
+ including but not limited to software source code, documentation |
|
| 28 |
+ source, and configuration files. |
|
| 29 |
+ |
|
| 30 |
+ "Object" form shall mean any form resulting from mechanical |
|
| 31 |
+ transformation or translation of a Source form, including but |
|
| 32 |
+ not limited to compiled object code, generated documentation, |
|
| 33 |
+ and conversions to other media types. |
|
| 34 |
+ |
|
| 35 |
+ "Work" shall mean the work of authorship, whether in Source or |
|
| 36 |
+ Object form, made available under the License, as indicated by a |
|
| 37 |
+ copyright notice that is included in or attached to the work |
|
| 38 |
+ (an example is provided in the Appendix below). |
|
| 39 |
+ |
|
| 40 |
+ "Derivative Works" shall mean any work, whether in Source or Object |
|
| 41 |
+ form, that is based on (or derived from) the Work and for which the |
|
| 42 |
+ editorial revisions, annotations, elaborations, or other modifications |
|
| 43 |
+ represent, as a whole, an original work of authorship. For the purposes |
|
| 44 |
+ of this License, Derivative Works shall not include works that remain |
|
| 45 |
+ separable from, or merely link (or bind by name) to the interfaces of, |
|
| 46 |
+ the Work and Derivative Works thereof. |
|
| 47 |
+ |
|
| 48 |
+ "Contribution" shall mean any work of authorship, including |
|
| 49 |
+ the original version of the Work and any modifications or additions |
|
| 50 |
+ to that Work or Derivative Works thereof, that is intentionally |
|
| 51 |
+ submitted to Licensor for inclusion in the Work by the copyright owner |
|
| 52 |
+ or by an individual or Legal Entity authorized to submit on behalf of |
|
| 53 |
+ the copyright owner. For the purposes of this definition, "submitted" |
|
| 54 |
+ means any form of electronic, verbal, or written communication sent |
|
| 55 |
+ to the Licensor or its representatives, including but not limited to |
|
| 56 |
+ communication on electronic mailing lists, source code control systems, |
|
| 57 |
+ and issue tracking systems that are managed by, or on behalf of, the |
|
| 58 |
+ Licensor for the purpose of discussing and improving the Work, but |
|
| 59 |
+ excluding communication that is conspicuously marked or otherwise |
|
| 60 |
+ designated in writing by the copyright owner as "Not a Contribution." |
|
| 61 |
+ |
|
| 62 |
+ "Contributor" shall mean Licensor and any individual or Legal Entity |
|
| 63 |
+ on behalf of whom a Contribution has been received by Licensor and |
|
| 64 |
+ subsequently incorporated within the Work. |
|
| 65 |
+ |
|
| 66 |
+ 2. Grant of Copyright License. Subject to the terms and conditions of |
|
| 67 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
| 68 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 69 |
+ copyright license to reproduce, prepare Derivative Works of, |
|
| 70 |
+ publicly display, publicly perform, sublicense, and distribute the |
|
| 71 |
+ Work and such Derivative Works in Source or Object form. |
|
| 72 |
+ |
|
| 73 |
+ 3. Grant of Patent License. Subject to the terms and conditions of |
|
| 74 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
| 75 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 76 |
+ (except as stated in this section) patent license to make, have made, |
|
| 77 |
+ use, offer to sell, sell, import, and otherwise transfer the Work, |
|
| 78 |
+ where such license applies only to those patent claims licensable |
|
| 79 |
+ by such Contributor that are necessarily infringed by their |
|
| 80 |
+ Contribution(s) alone or by combination of their Contribution(s) |
|
| 81 |
+ with the Work to which such Contribution(s) was submitted. If You |
|
| 82 |
+ institute patent litigation against any entity (including a |
|
| 83 |
+ cross-claim or counterclaim in a lawsuit) alleging that the Work |
|
| 84 |
+ or a Contribution incorporated within the Work constitutes direct |
|
| 85 |
+ or contributory patent infringement, then any patent licenses |
|
| 86 |
+ granted to You under this License for that Work shall terminate |
|
| 87 |
+ as of the date such litigation is filed. |
|
| 88 |
+ |
|
| 89 |
+ 4. Redistribution. You may reproduce and distribute copies of the |
|
| 90 |
+ Work or Derivative Works thereof in any medium, with or without |
|
| 91 |
+ modifications, and in Source or Object form, provided that You |
|
| 92 |
+ meet the following conditions: |
|
| 93 |
+ |
|
| 94 |
+ (a) You must give any other recipients of the Work or |
|
| 95 |
+ Derivative Works a copy of this License; and |
|
| 96 |
+ |
|
| 97 |
+ (b) You must cause any modified files to carry prominent notices |
|
| 98 |
+ stating that You changed the files; and |
|
| 99 |
+ |
|
| 100 |
+ (c) You must retain, in the Source form of any Derivative Works |
|
| 101 |
+ that You distribute, all copyright, patent, trademark, and |
|
| 102 |
+ attribution notices from the Source form of the Work, |
|
| 103 |
+ excluding those notices that do not pertain to any part of |
|
| 104 |
+ the Derivative Works; and |
|
| 105 |
+ |
|
| 106 |
+ (d) If the Work includes a "NOTICE" text file as part of its |
|
| 107 |
+ distribution, then any Derivative Works that You distribute must |
|
| 108 |
+ include a readable copy of the attribution notices contained |
|
| 109 |
+ within such NOTICE file, excluding those notices that do not |
|
| 110 |
+ pertain to any part of the Derivative Works, in at least one |
|
| 111 |
+ of the following places: within a NOTICE text file distributed |
|
| 112 |
+ as part of the Derivative Works; within the Source form or |
|
| 113 |
+ documentation, if provided along with the Derivative Works; or, |
|
| 114 |
+ within a display generated by the Derivative Works, if and |
|
| 115 |
+ wherever such third-party notices normally appear. The contents |
|
| 116 |
+ of the NOTICE file are for informational purposes only and |
|
| 117 |
+ do not modify the License. You may add Your own attribution |
|
| 118 |
+ notices within Derivative Works that You distribute, alongside |
|
| 119 |
+ or as an addendum to the NOTICE text from the Work, provided |
|
| 120 |
+ that such additional attribution notices cannot be construed |
|
| 121 |
+ as modifying the License. |
|
| 122 |
+ |
|
| 123 |
+ You may add Your own copyright statement to Your modifications and |
|
| 124 |
+ may provide additional or different license terms and conditions |
|
| 125 |
+ for use, reproduction, or distribution of Your modifications, or |
|
| 126 |
+ for any such Derivative Works as a whole, provided Your use, |
|
| 127 |
+ reproduction, and distribution of the Work otherwise complies with |
|
| 128 |
+ the conditions stated in this License. |
|
| 129 |
+ |
|
| 130 |
+ 5. Submission of Contributions. Unless You explicitly state otherwise, |
|
| 131 |
+ any Contribution intentionally submitted for inclusion in the Work |
|
| 132 |
+ by You to the Licensor shall be under the terms and conditions of |
|
| 133 |
+ this License, without any additional terms or conditions. |
|
| 134 |
+ Notwithstanding the above, nothing herein shall supersede or modify |
|
| 135 |
+ the terms of any separate license agreement you may have executed |
|
| 136 |
+ with Licensor regarding such Contributions. |
|
| 137 |
+ |
|
| 138 |
+ 6. Trademarks. This License does not grant permission to use the trade |
|
| 139 |
+ names, trademarks, service marks, or product names of the Licensor, |
|
| 140 |
+ except as required for reasonable and customary use in describing the |
|
| 141 |
+ origin of the Work and reproducing the content of the NOTICE file. |
|
| 142 |
+ |
|
| 143 |
+ 7. Disclaimer of Warranty. Unless required by applicable law or |
|
| 144 |
+ agreed to in writing, Licensor provides the Work (and each |
|
| 145 |
+ Contributor provides its Contributions) on an "AS IS" BASIS, |
|
| 146 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|
| 147 |
+ implied, including, without limitation, any warranties or conditions |
|
| 148 |
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
|
| 149 |
+ PARTICULAR PURPOSE. You are solely responsible for determining the |
|
| 150 |
+ appropriateness of using or redistributing the Work and assume any |
|
| 151 |
+ risks associated with Your exercise of permissions under this License. |
|
| 152 |
+ |
|
| 153 |
+ 8. Limitation of Liability. In no event and under no legal theory, |
|
| 154 |
+ whether in tort (including negligence), contract, or otherwise, |
|
| 155 |
+ unless required by applicable law (such as deliberate and grossly |
|
| 156 |
+ negligent acts) or agreed to in writing, shall any Contributor be |
|
| 157 |
+ liable to You for damages, including any direct, indirect, special, |
|
| 158 |
+ incidental, or consequential damages of any character arising as a |
|
| 159 |
+ result of this License or out of the use or inability to use the |
|
| 160 |
+ Work (including but not limited to damages for loss of goodwill, |
|
| 161 |
+ work stoppage, computer failure or malfunction, or any and all |
|
| 162 |
+ other commercial damages or losses), even if such Contributor |
|
| 163 |
+ has been advised of the possibility of such damages. |
|
| 164 |
+ |
|
| 165 |
+ 9. Accepting Warranty or Additional Liability. While redistributing |
|
| 166 |
+ the Work or Derivative Works thereof, You may choose to offer, |
|
| 167 |
+ and charge a fee for, acceptance of support, warranty, indemnity, |
|
| 168 |
+ or other liability obligations and/or rights consistent with this |
|
| 169 |
+ License. However, in accepting such obligations, You may act only |
|
| 170 |
+ on Your own behalf and on Your sole responsibility, not on behalf |
|
| 171 |
+ of any other Contributor, and only if You agree to indemnify, |
|
| 172 |
+ defend, and hold each Contributor harmless for any liability |
|
| 173 |
+ incurred by, or claims asserted against, such Contributor by reason |
|
| 174 |
+ of your accepting any such warranty or additional liability. |
|
| 175 |
+ |
|
| 176 |
+ END OF TERMS AND CONDITIONS |
|
| 177 |
+ |
|
| 178 |
+ Copyright 2013-2018 Docker, Inc. |
|
| 179 |
+ |
|
| 180 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 181 |
+ you may not use this file except in compliance with the License. |
|
| 182 |
+ You may obtain a copy of the License at |
|
| 183 |
+ |
|
| 184 |
+ https://www.apache.org/licenses/LICENSE-2.0 |
|
| 185 |
+ |
|
| 186 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 187 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 188 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 189 |
+ See the License for the specific language governing permissions and |
|
| 190 |
+ limitations under the License. |
| 0 | 191 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+# term - utilities for dealing with terminals |
|
| 1 |
+ |
|
| 2 |
+ [](https://godoc.org/github.com/moby/term) [](https://goreportcard.com/report/github.com/moby/term) |
|
| 3 |
+ |
|
| 4 |
+term provides structures and helper functions to work with terminal (state, sizes). |
|
| 5 |
+ |
|
| 6 |
+#### Using term |
|
| 7 |
+ |
|
| 8 |
+```go |
|
| 9 |
+package main |
|
| 10 |
+ |
|
| 11 |
+import ( |
|
| 12 |
+ "log" |
|
| 13 |
+ "os" |
|
| 14 |
+ |
|
| 15 |
+ "github.com/moby/term" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+func main() {
|
|
| 19 |
+ fd := os.Stdin.Fd() |
|
| 20 |
+ if term.IsTerminal(fd) {
|
|
| 21 |
+ ws, err := term.GetWinsize(fd) |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ log.Fatalf("term.GetWinsize: %s", err)
|
|
| 24 |
+ } |
|
| 25 |
+ log.Printf("%d:%d\n", ws.Height, ws.Width)
|
|
| 26 |
+ } |
|
| 27 |
+} |
|
| 28 |
+``` |
|
| 29 |
+ |
|
| 30 |
+## Contributing |
|
| 31 |
+ |
|
| 32 |
+Want to hack on term? [Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) apply. |
|
| 33 |
+ |
|
| 34 |
+## Copyright and license |
|
| 35 |
+Code and documentation copyright 2015 Docker, inc. Code released under the Apache 2.0 license. Docs released under Creative commons. |
| 0 | 36 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,66 @@ |
| 0 |
+package term |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// ASCII list the possible supported ASCII key sequence |
|
| 8 |
+var ASCII = []string{
|
|
| 9 |
+ "ctrl-@", |
|
| 10 |
+ "ctrl-a", |
|
| 11 |
+ "ctrl-b", |
|
| 12 |
+ "ctrl-c", |
|
| 13 |
+ "ctrl-d", |
|
| 14 |
+ "ctrl-e", |
|
| 15 |
+ "ctrl-f", |
|
| 16 |
+ "ctrl-g", |
|
| 17 |
+ "ctrl-h", |
|
| 18 |
+ "ctrl-i", |
|
| 19 |
+ "ctrl-j", |
|
| 20 |
+ "ctrl-k", |
|
| 21 |
+ "ctrl-l", |
|
| 22 |
+ "ctrl-m", |
|
| 23 |
+ "ctrl-n", |
|
| 24 |
+ "ctrl-o", |
|
| 25 |
+ "ctrl-p", |
|
| 26 |
+ "ctrl-q", |
|
| 27 |
+ "ctrl-r", |
|
| 28 |
+ "ctrl-s", |
|
| 29 |
+ "ctrl-t", |
|
| 30 |
+ "ctrl-u", |
|
| 31 |
+ "ctrl-v", |
|
| 32 |
+ "ctrl-w", |
|
| 33 |
+ "ctrl-x", |
|
| 34 |
+ "ctrl-y", |
|
| 35 |
+ "ctrl-z", |
|
| 36 |
+ "ctrl-[", |
|
| 37 |
+ "ctrl-\\", |
|
| 38 |
+ "ctrl-]", |
|
| 39 |
+ "ctrl-^", |
|
| 40 |
+ "ctrl-_", |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. |
|
| 44 |
+func ToBytes(keys string) ([]byte, error) {
|
|
| 45 |
+ codes := []byte{}
|
|
| 46 |
+next: |
|
| 47 |
+ for _, key := range strings.Split(keys, ",") {
|
|
| 48 |
+ if len(key) != 1 {
|
|
| 49 |
+ for code, ctrl := range ASCII {
|
|
| 50 |
+ if ctrl == key {
|
|
| 51 |
+ codes = append(codes, byte(code)) |
|
| 52 |
+ continue next |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ if key == "DEL" {
|
|
| 56 |
+ codes = append(codes, 127) |
|
| 57 |
+ } else {
|
|
| 58 |
+ return nil, fmt.Errorf("Unknown character: '%s'", key)
|
|
| 59 |
+ } |
|
| 60 |
+ } else {
|
|
| 61 |
+ codes = append(codes, key[0]) |
|
| 62 |
+ } |
|
| 63 |
+ } |
|
| 64 |
+ return codes, nil |
|
| 65 |
+} |
| 0 | 66 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+module github.com/moby/term |
|
| 1 |
+ |
|
| 2 |
+go 1.13 |
|
| 3 |
+ |
|
| 4 |
+require ( |
|
| 5 |
+ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 |
|
| 6 |
+ github.com/google/go-cmp v0.3.1 |
|
| 7 |
+ github.com/pkg/errors v0.9.1 // indirect |
|
| 8 |
+ github.com/sirupsen/logrus v1.4.2 |
|
| 9 |
+ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 |
|
| 10 |
+ gotest.tools v2.2.0+incompatible |
|
| 11 |
+ gotest.tools/v3 v3.0.2 // indirect |
|
| 12 |
+) |
| 0 | 13 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,88 @@ |
| 0 |
+package term |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// EscapeError is special error which returned by a TTY proxy reader's Read() |
|
| 7 |
+// method in case its detach escape sequence is read. |
|
| 8 |
+type EscapeError struct{}
|
|
| 9 |
+ |
|
| 10 |
+func (EscapeError) Error() string {
|
|
| 11 |
+ return "read escape sequence" |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// escapeProxy is used only for attaches with a TTY. It is used to proxy |
|
| 15 |
+// stdin keypresses from the underlying reader and look for the passed in |
|
| 16 |
+// escape key sequence to signal a detach. |
|
| 17 |
+type escapeProxy struct {
|
|
| 18 |
+ escapeKeys []byte |
|
| 19 |
+ escapeKeyPos int |
|
| 20 |
+ r io.Reader |
|
| 21 |
+ buf []byte |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader |
|
| 25 |
+// and detects when the specified escape keys are read, in which case the Read |
|
| 26 |
+// method will return an error of type EscapeError. |
|
| 27 |
+func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
|
|
| 28 |
+ return &escapeProxy{
|
|
| 29 |
+ escapeKeys: escapeKeys, |
|
| 30 |
+ r: r, |
|
| 31 |
+ } |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func (r *escapeProxy) Read(buf []byte) (n int, err error) {
|
|
| 35 |
+ if len(r.escapeKeys) > 0 && r.escapeKeyPos == len(r.escapeKeys) {
|
|
| 36 |
+ return 0, EscapeError{}
|
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ if len(r.buf) > 0 {
|
|
| 40 |
+ n = copy(buf, r.buf) |
|
| 41 |
+ r.buf = r.buf[n:] |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ nr, err := r.r.Read(buf[n:]) |
|
| 45 |
+ n += nr |
|
| 46 |
+ if len(r.escapeKeys) == 0 {
|
|
| 47 |
+ return n, err |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ for i := 0; i < n; i++ {
|
|
| 51 |
+ if buf[i] == r.escapeKeys[r.escapeKeyPos] {
|
|
| 52 |
+ r.escapeKeyPos++ |
|
| 53 |
+ |
|
| 54 |
+ // Check if the full escape sequence is matched. |
|
| 55 |
+ if r.escapeKeyPos == len(r.escapeKeys) {
|
|
| 56 |
+ n = i + 1 - r.escapeKeyPos |
|
| 57 |
+ if n < 0 {
|
|
| 58 |
+ n = 0 |
|
| 59 |
+ } |
|
| 60 |
+ return n, EscapeError{}
|
|
| 61 |
+ } |
|
| 62 |
+ continue |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ // If we need to prepend a partial escape sequence from the previous |
|
| 66 |
+ // read, make sure the new buffer size doesn't exceed len(buf). |
|
| 67 |
+ // Otherwise, preserve any extra data in a buffer for the next read. |
|
| 68 |
+ if i < r.escapeKeyPos {
|
|
| 69 |
+ preserve := make([]byte, 0, r.escapeKeyPos+n) |
|
| 70 |
+ preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...) |
|
| 71 |
+ preserve = append(preserve, buf[:n]...) |
|
| 72 |
+ n = copy(buf, preserve) |
|
| 73 |
+ i += r.escapeKeyPos |
|
| 74 |
+ r.buf = append(r.buf, preserve[n:]...) |
|
| 75 |
+ } |
|
| 76 |
+ r.escapeKeyPos = 0 |
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ // If we're in the middle of reading an escape sequence, make sure we don't |
|
| 80 |
+ // let the caller read it. If later on we find that this is not the escape |
|
| 81 |
+ // sequence, we'll prepend it back to buf. |
|
| 82 |
+ n -= r.escapeKeyPos |
|
| 83 |
+ if n < 0 {
|
|
| 84 |
+ n = 0 |
|
| 85 |
+ } |
|
| 86 |
+ return n, err |
|
| 87 |
+} |
| 0 | 88 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,20 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package term |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "syscall" |
|
| 6 |
+ "unsafe" |
|
| 7 |
+ |
|
| 8 |
+ "golang.org/x/sys/unix" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func tcget(fd uintptr, p *Termios) syscall.Errno {
|
|
| 12 |
+ _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p))) |
|
| 13 |
+ return err |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+func tcset(fd uintptr, p *Termios) syscall.Errno {
|
|
| 17 |
+ _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p))) |
|
| 18 |
+ return err |
|
| 19 |
+} |
| 0 | 20 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,124 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+// Package term provides structures and helper functions to work with |
|
| 3 |
+// terminal (state, sizes). |
|
| 4 |
+package term |
|
| 5 |
+ |
|
| 6 |
+import ( |
|
| 7 |
+ "errors" |
|
| 8 |
+ "fmt" |
|
| 9 |
+ "io" |
|
| 10 |
+ "os" |
|
| 11 |
+ "os/signal" |
|
| 12 |
+ |
|
| 13 |
+ "golang.org/x/sys/unix" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+var ( |
|
| 17 |
+ // ErrInvalidState is returned if the state of the terminal is invalid. |
|
| 18 |
+ ErrInvalidState = errors.New("Invalid terminal state")
|
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+// State represents the state of the terminal. |
|
| 22 |
+type State struct {
|
|
| 23 |
+ termios Termios |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// Winsize represents the size of the terminal window. |
|
| 27 |
+type Winsize struct {
|
|
| 28 |
+ Height uint16 |
|
| 29 |
+ Width uint16 |
|
| 30 |
+ x uint16 |
|
| 31 |
+ y uint16 |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// StdStreams returns the standard streams (stdin, stdout, stderr). |
|
| 35 |
+func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
| 36 |
+ return os.Stdin, os.Stdout, os.Stderr |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. |
|
| 40 |
+func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
| 41 |
+ var inFd uintptr |
|
| 42 |
+ var isTerminalIn bool |
|
| 43 |
+ if file, ok := in.(*os.File); ok {
|
|
| 44 |
+ inFd = file.Fd() |
|
| 45 |
+ isTerminalIn = IsTerminal(inFd) |
|
| 46 |
+ } |
|
| 47 |
+ return inFd, isTerminalIn |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// IsTerminal returns true if the given file descriptor is a terminal. |
|
| 51 |
+func IsTerminal(fd uintptr) bool {
|
|
| 52 |
+ var termios Termios |
|
| 53 |
+ return tcget(fd, &termios) == 0 |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 56 |
+// RestoreTerminal restores the terminal connected to the given file descriptor |
|
| 57 |
+// to a previous state. |
|
| 58 |
+func RestoreTerminal(fd uintptr, state *State) error {
|
|
| 59 |
+ if state == nil {
|
|
| 60 |
+ return ErrInvalidState |
|
| 61 |
+ } |
|
| 62 |
+ if err := tcset(fd, &state.termios); err != 0 {
|
|
| 63 |
+ return err |
|
| 64 |
+ } |
|
| 65 |
+ return nil |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+// SaveState saves the state of the terminal connected to the given file descriptor. |
|
| 69 |
+func SaveState(fd uintptr) (*State, error) {
|
|
| 70 |
+ var oldState State |
|
| 71 |
+ if err := tcget(fd, &oldState.termios); err != 0 {
|
|
| 72 |
+ return nil, err |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ return &oldState, nil |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+// DisableEcho applies the specified state to the terminal connected to the file |
|
| 79 |
+// descriptor, with echo disabled. |
|
| 80 |
+func DisableEcho(fd uintptr, state *State) error {
|
|
| 81 |
+ newState := state.termios |
|
| 82 |
+ newState.Lflag &^= unix.ECHO |
|
| 83 |
+ |
|
| 84 |
+ if err := tcset(fd, &newState); err != 0 {
|
|
| 85 |
+ return err |
|
| 86 |
+ } |
|
| 87 |
+ handleInterrupt(fd, state) |
|
| 88 |
+ return nil |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+// SetRawTerminal puts the terminal connected to the given file descriptor into |
|
| 92 |
+// raw mode and returns the previous state. On UNIX, this puts both the input |
|
| 93 |
+// and output into raw mode. On Windows, it only puts the input into raw mode. |
|
| 94 |
+func SetRawTerminal(fd uintptr) (*State, error) {
|
|
| 95 |
+ oldState, err := MakeRaw(fd) |
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ return nil, err |
|
| 98 |
+ } |
|
| 99 |
+ handleInterrupt(fd, oldState) |
|
| 100 |
+ return oldState, err |
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+// SetRawTerminalOutput puts the output of terminal connected to the given file |
|
| 104 |
+// descriptor into raw mode. On UNIX, this does nothing and returns nil for the |
|
| 105 |
+// state. On Windows, it disables LF -> CRLF translation. |
|
| 106 |
+func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
|
| 107 |
+ return nil, nil |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func handleInterrupt(fd uintptr, state *State) {
|
|
| 111 |
+ sigchan := make(chan os.Signal, 1) |
|
| 112 |
+ signal.Notify(sigchan, os.Interrupt) |
|
| 113 |
+ go func() {
|
|
| 114 |
+ for range sigchan {
|
|
| 115 |
+ // quit cleanly and the new terminal item is on a new line |
|
| 116 |
+ fmt.Println() |
|
| 117 |
+ signal.Stop(sigchan) |
|
| 118 |
+ close(sigchan) |
|
| 119 |
+ RestoreTerminal(fd, state) |
|
| 120 |
+ os.Exit(1) |
|
| 121 |
+ } |
|
| 122 |
+ }() |
|
| 123 |
+} |
| 0 | 124 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,221 @@ |
| 0 |
+package term |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "os" |
|
| 5 |
+ "os/signal" |
|
| 6 |
+ "syscall" // used for STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and STD_ERROR_HANDLE |
|
| 7 |
+ |
|
| 8 |
+ "github.com/Azure/go-ansiterm/winterm" |
|
| 9 |
+ windowsconsole "github.com/moby/term/windows" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// State holds the console mode for the terminal. |
|
| 13 |
+type State struct {
|
|
| 14 |
+ mode uint32 |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// Winsize is used for window size. |
|
| 18 |
+type Winsize struct {
|
|
| 19 |
+ Height uint16 |
|
| 20 |
+ Width uint16 |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// vtInputSupported is true if winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported by the console |
|
| 24 |
+var vtInputSupported bool |
|
| 25 |
+ |
|
| 26 |
+// StdStreams returns the standard streams (stdin, stdout, stderr). |
|
| 27 |
+func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
| 28 |
+ // Turn on VT handling on all std handles, if possible. This might |
|
| 29 |
+ // fail, in which case we will fall back to terminal emulation. |
|
| 30 |
+ var emulateStdin, emulateStdout, emulateStderr bool |
|
| 31 |
+ fd := os.Stdin.Fd() |
|
| 32 |
+ if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
| 33 |
+ // Validate that winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it. |
|
| 34 |
+ if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil {
|
|
| 35 |
+ emulateStdin = true |
|
| 36 |
+ } else {
|
|
| 37 |
+ vtInputSupported = true |
|
| 38 |
+ } |
|
| 39 |
+ // Unconditionally set the console mode back even on failure because SetConsoleMode |
|
| 40 |
+ // remembers invalid bits on input handles. |
|
| 41 |
+ winterm.SetConsoleMode(fd, mode) |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ fd = os.Stdout.Fd() |
|
| 45 |
+ if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
| 46 |
+ // Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it. |
|
| 47 |
+ if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING|winterm.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
|
|
| 48 |
+ emulateStdout = true |
|
| 49 |
+ } else {
|
|
| 50 |
+ winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
|
| 51 |
+ } |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ fd = os.Stderr.Fd() |
|
| 55 |
+ if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
| 56 |
+ // Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it. |
|
| 57 |
+ if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING|winterm.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
|
|
| 58 |
+ emulateStderr = true |
|
| 59 |
+ } else {
|
|
| 60 |
+ winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
|
| 61 |
+ } |
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ // Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and |
|
| 65 |
+ // STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as |
|
| 66 |
+ // go-ansiterm hasn't switch to x/sys/windows. |
|
| 67 |
+ // TODO: switch back to x/sys/windows once go-ansiterm has switched |
|
| 68 |
+ if emulateStdin {
|
|
| 69 |
+ stdIn = windowsconsole.NewAnsiReader(syscall.STD_INPUT_HANDLE) |
|
| 70 |
+ } else {
|
|
| 71 |
+ stdIn = os.Stdin |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ if emulateStdout {
|
|
| 75 |
+ stdOut = windowsconsole.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE) |
|
| 76 |
+ } else {
|
|
| 77 |
+ stdOut = os.Stdout |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ if emulateStderr {
|
|
| 81 |
+ stdErr = windowsconsole.NewAnsiWriter(syscall.STD_ERROR_HANDLE) |
|
| 82 |
+ } else {
|
|
| 83 |
+ stdErr = os.Stderr |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ return |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. |
|
| 90 |
+func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
| 91 |
+ return windowsconsole.GetHandleInfo(in) |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+// GetWinsize returns the window size based on the specified file descriptor. |
|
| 95 |
+func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
| 96 |
+ info, err := winterm.GetConsoleScreenBufferInfo(fd) |
|
| 97 |
+ if err != nil {
|
|
| 98 |
+ return nil, err |
|
| 99 |
+ } |
|
| 100 |
+ |
|
| 101 |
+ winsize := &Winsize{
|
|
| 102 |
+ Width: uint16(info.Window.Right - info.Window.Left + 1), |
|
| 103 |
+ Height: uint16(info.Window.Bottom - info.Window.Top + 1), |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ return winsize, nil |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+// IsTerminal returns true if the given file descriptor is a terminal. |
|
| 110 |
+func IsTerminal(fd uintptr) bool {
|
|
| 111 |
+ return windowsconsole.IsConsole(fd) |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// RestoreTerminal restores the terminal connected to the given file descriptor |
|
| 115 |
+// to a previous state. |
|
| 116 |
+func RestoreTerminal(fd uintptr, state *State) error {
|
|
| 117 |
+ return winterm.SetConsoleMode(fd, state.mode) |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 120 |
+// SaveState saves the state of the terminal connected to the given file descriptor. |
|
| 121 |
+func SaveState(fd uintptr) (*State, error) {
|
|
| 122 |
+ mode, e := winterm.GetConsoleMode(fd) |
|
| 123 |
+ if e != nil {
|
|
| 124 |
+ return nil, e |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ return &State{mode: mode}, nil
|
|
| 128 |
+} |
|
| 129 |
+ |
|
| 130 |
+// DisableEcho disables echo for the terminal connected to the given file descriptor. |
|
| 131 |
+// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx |
|
| 132 |
+func DisableEcho(fd uintptr, state *State) error {
|
|
| 133 |
+ mode := state.mode |
|
| 134 |
+ mode &^= winterm.ENABLE_ECHO_INPUT |
|
| 135 |
+ mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT |
|
| 136 |
+ err := winterm.SetConsoleMode(fd, mode) |
|
| 137 |
+ if err != nil {
|
|
| 138 |
+ return err |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ // Register an interrupt handler to catch and restore prior state |
|
| 142 |
+ restoreAtInterrupt(fd, state) |
|
| 143 |
+ return nil |
|
| 144 |
+} |
|
| 145 |
+ |
|
| 146 |
+// SetRawTerminal puts the terminal connected to the given file descriptor into |
|
| 147 |
+// raw mode and returns the previous state. On UNIX, this puts both the input |
|
| 148 |
+// and output into raw mode. On Windows, it only puts the input into raw mode. |
|
| 149 |
+func SetRawTerminal(fd uintptr) (*State, error) {
|
|
| 150 |
+ state, err := MakeRaw(fd) |
|
| 151 |
+ if err != nil {
|
|
| 152 |
+ return nil, err |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ // Register an interrupt handler to catch and restore prior state |
|
| 156 |
+ restoreAtInterrupt(fd, state) |
|
| 157 |
+ return state, err |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+// SetRawTerminalOutput puts the output of terminal connected to the given file |
|
| 161 |
+// descriptor into raw mode. On UNIX, this does nothing and returns nil for the |
|
| 162 |
+// state. On Windows, it disables LF -> CRLF translation. |
|
| 163 |
+func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
|
| 164 |
+ state, err := SaveState(fd) |
|
| 165 |
+ if err != nil {
|
|
| 166 |
+ return nil, err |
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ // Ignore failures, since winterm.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this |
|
| 170 |
+ // version of Windows. |
|
| 171 |
+ winterm.SetConsoleMode(fd, state.mode|winterm.DISABLE_NEWLINE_AUTO_RETURN) |
|
| 172 |
+ return state, err |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw |
|
| 176 |
+// mode and returns the previous state of the terminal so that it can be restored. |
|
| 177 |
+func MakeRaw(fd uintptr) (*State, error) {
|
|
| 178 |
+ state, err := SaveState(fd) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return nil, err |
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ mode := state.mode |
|
| 184 |
+ |
|
| 185 |
+ // See |
|
| 186 |
+ // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx |
|
| 187 |
+ // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx |
|
| 188 |
+ |
|
| 189 |
+ // Disable these modes |
|
| 190 |
+ mode &^= winterm.ENABLE_ECHO_INPUT |
|
| 191 |
+ mode &^= winterm.ENABLE_LINE_INPUT |
|
| 192 |
+ mode &^= winterm.ENABLE_MOUSE_INPUT |
|
| 193 |
+ mode &^= winterm.ENABLE_WINDOW_INPUT |
|
| 194 |
+ mode &^= winterm.ENABLE_PROCESSED_INPUT |
|
| 195 |
+ |
|
| 196 |
+ // Enable these modes |
|
| 197 |
+ mode |= winterm.ENABLE_EXTENDED_FLAGS |
|
| 198 |
+ mode |= winterm.ENABLE_INSERT_MODE |
|
| 199 |
+ mode |= winterm.ENABLE_QUICK_EDIT_MODE |
|
| 200 |
+ if vtInputSupported {
|
|
| 201 |
+ mode |= winterm.ENABLE_VIRTUAL_TERMINAL_INPUT |
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ err = winterm.SetConsoleMode(fd, mode) |
|
| 205 |
+ if err != nil {
|
|
| 206 |
+ return nil, err |
|
| 207 |
+ } |
|
| 208 |
+ return state, nil |
|
| 209 |
+} |
|
| 210 |
+ |
|
| 211 |
+func restoreAtInterrupt(fd uintptr, state *State) {
|
|
| 212 |
+ sigchan := make(chan os.Signal, 1) |
|
| 213 |
+ signal.Notify(sigchan, os.Interrupt) |
|
| 214 |
+ |
|
| 215 |
+ go func() {
|
|
| 216 |
+ _ = <-sigchan |
|
| 217 |
+ RestoreTerminal(fd, state) |
|
| 218 |
+ os.Exit(0) |
|
| 219 |
+ }() |
|
| 220 |
+} |
| 0 | 221 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,42 @@ |
| 0 |
+// +build darwin freebsd openbsd netbsd |
|
| 1 |
+ |
|
| 2 |
+package term |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "unsafe" |
|
| 6 |
+ |
|
| 7 |
+ "golang.org/x/sys/unix" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+const ( |
|
| 11 |
+ getTermios = unix.TIOCGETA |
|
| 12 |
+ setTermios = unix.TIOCSETA |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// Termios is the Unix API for terminal I/O. |
|
| 16 |
+type Termios unix.Termios |
|
| 17 |
+ |
|
| 18 |
+// MakeRaw put the terminal connected to the given file descriptor into raw |
|
| 19 |
+// mode and returns the previous state of the terminal so that it can be |
|
| 20 |
+// restored. |
|
| 21 |
+func MakeRaw(fd uintptr) (*State, error) {
|
|
| 22 |
+ var oldState State |
|
| 23 |
+ if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
|
| 24 |
+ return nil, err |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ newState := oldState.termios |
|
| 28 |
+ newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) |
|
| 29 |
+ newState.Oflag &^= unix.OPOST |
|
| 30 |
+ newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) |
|
| 31 |
+ newState.Cflag &^= (unix.CSIZE | unix.PARENB) |
|
| 32 |
+ newState.Cflag |= unix.CS8 |
|
| 33 |
+ newState.Cc[unix.VMIN] = 1 |
|
| 34 |
+ newState.Cc[unix.VTIME] = 0 |
|
| 35 |
+ |
|
| 36 |
+ if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
|
| 37 |
+ return nil, err |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ return &oldState, nil |
|
| 41 |
+} |
| 0 | 42 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+package term |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "golang.org/x/sys/unix" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+const ( |
|
| 7 |
+ getTermios = unix.TCGETS |
|
| 8 |
+ setTermios = unix.TCSETS |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// Termios is the Unix API for terminal I/O. |
|
| 12 |
+type Termios unix.Termios |
|
| 13 |
+ |
|
| 14 |
+// MakeRaw put the terminal connected to the given file descriptor into raw |
|
| 15 |
+// mode and returns the previous state of the terminal so that it can be |
|
| 16 |
+// restored. |
|
| 17 |
+func MakeRaw(fd uintptr) (*State, error) {
|
|
| 18 |
+ termios, err := unix.IoctlGetTermios(int(fd), getTermios) |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ return nil, err |
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ var oldState State |
|
| 24 |
+ oldState.termios = Termios(*termios) |
|
| 25 |
+ |
|
| 26 |
+ termios.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) |
|
| 27 |
+ termios.Oflag &^= unix.OPOST |
|
| 28 |
+ termios.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) |
|
| 29 |
+ termios.Cflag &^= (unix.CSIZE | unix.PARENB) |
|
| 30 |
+ termios.Cflag |= unix.CS8 |
|
| 31 |
+ termios.Cc[unix.VMIN] = 1 |
|
| 32 |
+ termios.Cc[unix.VTIME] = 0 |
|
| 33 |
+ |
|
| 34 |
+ if err := unix.IoctlSetTermios(int(fd), setTermios, termios); err != nil {
|
|
| 35 |
+ return nil, err |
|
| 36 |
+ } |
|
| 37 |
+ return &oldState, nil |
|
| 38 |
+} |
| 0 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,263 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+package windowsconsole // import "github.com/moby/term/windows" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "bytes" |
|
| 6 |
+ "errors" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 9 |
+ "os" |
|
| 10 |
+ "strings" |
|
| 11 |
+ "unsafe" |
|
| 12 |
+ |
|
| 13 |
+ ansiterm "github.com/Azure/go-ansiterm" |
|
| 14 |
+ "github.com/Azure/go-ansiterm/winterm" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+const ( |
|
| 18 |
+ escapeSequence = ansiterm.KEY_ESC_CSI |
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation. |
|
| 22 |
+type ansiReader struct {
|
|
| 23 |
+ file *os.File |
|
| 24 |
+ fd uintptr |
|
| 25 |
+ buffer []byte |
|
| 26 |
+ cbBuffer int |
|
| 27 |
+ command []byte |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a |
|
| 31 |
+// Windows console input handle. |
|
| 32 |
+func NewAnsiReader(nFile int) io.ReadCloser {
|
|
| 33 |
+ initLogger() |
|
| 34 |
+ file, fd := winterm.GetStdFile(nFile) |
|
| 35 |
+ return &ansiReader{
|
|
| 36 |
+ file: file, |
|
| 37 |
+ fd: fd, |
|
| 38 |
+ command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), |
|
| 39 |
+ buffer: make([]byte, 0), |
|
| 40 |
+ } |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// Close closes the wrapped file. |
|
| 44 |
+func (ar *ansiReader) Close() (err error) {
|
|
| 45 |
+ return ar.file.Close() |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+// Fd returns the file descriptor of the wrapped file. |
|
| 49 |
+func (ar *ansiReader) Fd() uintptr {
|
|
| 50 |
+ return ar.fd |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+// Read reads up to len(p) bytes of translated input events into p. |
|
| 54 |
+func (ar *ansiReader) Read(p []byte) (int, error) {
|
|
| 55 |
+ if len(p) == 0 {
|
|
| 56 |
+ return 0, nil |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ // Previously read bytes exist, read as much as we can and return |
|
| 60 |
+ if len(ar.buffer) > 0 {
|
|
| 61 |
+ logger.Debugf("Reading previously cached bytes")
|
|
| 62 |
+ |
|
| 63 |
+ originalLength := len(ar.buffer) |
|
| 64 |
+ copiedLength := copy(p, ar.buffer) |
|
| 65 |
+ |
|
| 66 |
+ if copiedLength == originalLength {
|
|
| 67 |
+ ar.buffer = make([]byte, 0, len(p)) |
|
| 68 |
+ } else {
|
|
| 69 |
+ ar.buffer = ar.buffer[copiedLength:] |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
|
|
| 73 |
+ return copiedLength, nil |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ // Read and translate key events |
|
| 77 |
+ events, err := readInputEvents(ar.fd, len(p)) |
|
| 78 |
+ if err != nil {
|
|
| 79 |
+ return 0, err |
|
| 80 |
+ } else if len(events) == 0 {
|
|
| 81 |
+ logger.Debug("No input events detected")
|
|
| 82 |
+ return 0, nil |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ keyBytes := translateKeyEvents(events, []byte(escapeSequence)) |
|
| 86 |
+ |
|
| 87 |
+ // Save excess bytes and right-size keyBytes |
|
| 88 |
+ if len(keyBytes) > len(p) {
|
|
| 89 |
+ logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
|
|
| 90 |
+ ar.buffer = keyBytes[len(p):] |
|
| 91 |
+ keyBytes = keyBytes[:len(p)] |
|
| 92 |
+ } else if len(keyBytes) == 0 {
|
|
| 93 |
+ logger.Debug("No key bytes returned from the translator")
|
|
| 94 |
+ return 0, nil |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ copiedLength := copy(p, keyBytes) |
|
| 98 |
+ if copiedLength != len(keyBytes) {
|
|
| 99 |
+ return 0, errors.New("unexpected copy length encountered")
|
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ logger.Debugf("Read p[%d]: % x", copiedLength, p)
|
|
| 103 |
+ logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
|
|
| 104 |
+ return copiedLength, nil |
|
| 105 |
+} |
|
| 106 |
+ |
|
| 107 |
+// readInputEvents polls until at least one event is available. |
|
| 108 |
+func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
|
|
| 109 |
+ // Determine the maximum number of records to retrieve |
|
| 110 |
+ // -- Cast around the type system to obtain the size of a single INPUT_RECORD. |
|
| 111 |
+ // unsafe.Sizeof requires an expression vs. a type-reference; the casting |
|
| 112 |
+ // tricks the type system into believing it has such an expression. |
|
| 113 |
+ recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes))))) |
|
| 114 |
+ countRecords := maxBytes / recordSize |
|
| 115 |
+ if countRecords > ansiterm.MAX_INPUT_EVENTS {
|
|
| 116 |
+ countRecords = ansiterm.MAX_INPUT_EVENTS |
|
| 117 |
+ } else if countRecords == 0 {
|
|
| 118 |
+ countRecords = 1 |
|
| 119 |
+ } |
|
| 120 |
+ logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)
|
|
| 121 |
+ |
|
| 122 |
+ // Wait for and read input events |
|
| 123 |
+ events := make([]winterm.INPUT_RECORD, countRecords) |
|
| 124 |
+ nEvents := uint32(0) |
|
| 125 |
+ eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE) |
|
| 126 |
+ if err != nil {
|
|
| 127 |
+ return nil, err |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ if eventsExist {
|
|
| 131 |
+ err = winterm.ReadConsoleInput(fd, events, &nEvents) |
|
| 132 |
+ if err != nil {
|
|
| 133 |
+ return nil, err |
|
| 134 |
+ } |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ // Return a slice restricted to the number of returned records |
|
| 138 |
+ logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
|
|
| 139 |
+ return events[:nEvents], nil |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+// KeyEvent Translation Helpers |
|
| 143 |
+ |
|
| 144 |
+var arrowKeyMapPrefix = map[uint16]string{
|
|
| 145 |
+ winterm.VK_UP: "%s%sA", |
|
| 146 |
+ winterm.VK_DOWN: "%s%sB", |
|
| 147 |
+ winterm.VK_RIGHT: "%s%sC", |
|
| 148 |
+ winterm.VK_LEFT: "%s%sD", |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+var keyMapPrefix = map[uint16]string{
|
|
| 152 |
+ winterm.VK_UP: "\x1B[%sA", |
|
| 153 |
+ winterm.VK_DOWN: "\x1B[%sB", |
|
| 154 |
+ winterm.VK_RIGHT: "\x1B[%sC", |
|
| 155 |
+ winterm.VK_LEFT: "\x1B[%sD", |
|
| 156 |
+ winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1 |
|
| 157 |
+ winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4 |
|
| 158 |
+ winterm.VK_INSERT: "\x1B[2%s~", |
|
| 159 |
+ winterm.VK_DELETE: "\x1B[3%s~", |
|
| 160 |
+ winterm.VK_PRIOR: "\x1B[5%s~", |
|
| 161 |
+ winterm.VK_NEXT: "\x1B[6%s~", |
|
| 162 |
+ winterm.VK_F1: "", |
|
| 163 |
+ winterm.VK_F2: "", |
|
| 164 |
+ winterm.VK_F3: "\x1B[13%s~", |
|
| 165 |
+ winterm.VK_F4: "\x1B[14%s~", |
|
| 166 |
+ winterm.VK_F5: "\x1B[15%s~", |
|
| 167 |
+ winterm.VK_F6: "\x1B[17%s~", |
|
| 168 |
+ winterm.VK_F7: "\x1B[18%s~", |
|
| 169 |
+ winterm.VK_F8: "\x1B[19%s~", |
|
| 170 |
+ winterm.VK_F9: "\x1B[20%s~", |
|
| 171 |
+ winterm.VK_F10: "\x1B[21%s~", |
|
| 172 |
+ winterm.VK_F11: "\x1B[23%s~", |
|
| 173 |
+ winterm.VK_F12: "\x1B[24%s~", |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+// translateKeyEvents converts the input events into the appropriate ANSI string. |
|
| 177 |
+func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
|
|
| 178 |
+ var buffer bytes.Buffer |
|
| 179 |
+ for _, event := range events {
|
|
| 180 |
+ if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
|
|
| 181 |
+ buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence)) |
|
| 182 |
+ } |
|
| 183 |
+ } |
|
| 184 |
+ |
|
| 185 |
+ return buffer.Bytes() |
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+// keyToString maps the given input event record to the corresponding string. |
|
| 189 |
+func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
|
|
| 190 |
+ if keyEvent.UnicodeChar == 0 {
|
|
| 191 |
+ return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence) |
|
| 192 |
+ } |
|
| 193 |
+ |
|
| 194 |
+ _, alt, control := getControlKeys(keyEvent.ControlKeyState) |
|
| 195 |
+ if control {
|
|
| 196 |
+ // TODO(azlinux): Implement following control sequences |
|
| 197 |
+ // <Ctrl>-D Signals the end of input from the keyboard; also exits current shell. |
|
| 198 |
+ // <Ctrl>-H Deletes the first character to the left of the cursor. Also called the ERASE key. |
|
| 199 |
+ // <Ctrl>-Q Restarts printing after it has been stopped with <Ctrl>-s. |
|
| 200 |
+ // <Ctrl>-S Suspends printing on the screen (does not stop the program). |
|
| 201 |
+ // <Ctrl>-U Deletes all characters on the current line. Also called the KILL key. |
|
| 202 |
+ // <Ctrl>-E Quits current command and creates a core |
|
| 203 |
+ |
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ // <Alt>+Key generates ESC N Key |
|
| 207 |
+ if !control && alt {
|
|
| 208 |
+ return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar)) |
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ return string(keyEvent.UnicodeChar) |
|
| 212 |
+} |
|
| 213 |
+ |
|
| 214 |
+// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string. |
|
| 215 |
+func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
|
|
| 216 |
+ shift, alt, control := getControlKeys(controlState) |
|
| 217 |
+ modifier := getControlKeysModifier(shift, alt, control) |
|
| 218 |
+ |
|
| 219 |
+ if format, ok := arrowKeyMapPrefix[key]; ok {
|
|
| 220 |
+ return fmt.Sprintf(format, escapeSequence, modifier) |
|
| 221 |
+ } |
|
| 222 |
+ |
|
| 223 |
+ if format, ok := keyMapPrefix[key]; ok {
|
|
| 224 |
+ return fmt.Sprintf(format, modifier) |
|
| 225 |
+ } |
|
| 226 |
+ |
|
| 227 |
+ return "" |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+// getControlKeys extracts the shift, alt, and ctrl key states. |
|
| 231 |
+func getControlKeys(controlState uint32) (shift, alt, control bool) {
|
|
| 232 |
+ shift = 0 != (controlState & winterm.SHIFT_PRESSED) |
|
| 233 |
+ alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED)) |
|
| 234 |
+ control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED)) |
|
| 235 |
+ return shift, alt, control |
|
| 236 |
+} |
|
| 237 |
+ |
|
| 238 |
+// getControlKeysModifier returns the ANSI modifier for the given combination of control keys. |
|
| 239 |
+func getControlKeysModifier(shift, alt, control bool) string {
|
|
| 240 |
+ if shift && alt && control {
|
|
| 241 |
+ return ansiterm.KEY_CONTROL_PARAM_8 |
|
| 242 |
+ } |
|
| 243 |
+ if alt && control {
|
|
| 244 |
+ return ansiterm.KEY_CONTROL_PARAM_7 |
|
| 245 |
+ } |
|
| 246 |
+ if shift && control {
|
|
| 247 |
+ return ansiterm.KEY_CONTROL_PARAM_6 |
|
| 248 |
+ } |
|
| 249 |
+ if control {
|
|
| 250 |
+ return ansiterm.KEY_CONTROL_PARAM_5 |
|
| 251 |
+ } |
|
| 252 |
+ if shift && alt {
|
|
| 253 |
+ return ansiterm.KEY_CONTROL_PARAM_4 |
|
| 254 |
+ } |
|
| 255 |
+ if alt {
|
|
| 256 |
+ return ansiterm.KEY_CONTROL_PARAM_3 |
|
| 257 |
+ } |
|
| 258 |
+ if shift {
|
|
| 259 |
+ return ansiterm.KEY_CONTROL_PARAM_2 |
|
| 260 |
+ } |
|
| 261 |
+ return "" |
|
| 262 |
+} |
| 0 | 263 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,64 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+package windowsconsole // import "github.com/moby/term/windows" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "io" |
|
| 6 |
+ "os" |
|
| 7 |
+ |
|
| 8 |
+ ansiterm "github.com/Azure/go-ansiterm" |
|
| 9 |
+ "github.com/Azure/go-ansiterm/winterm" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation. |
|
| 13 |
+type ansiWriter struct {
|
|
| 14 |
+ file *os.File |
|
| 15 |
+ fd uintptr |
|
| 16 |
+ infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO |
|
| 17 |
+ command []byte |
|
| 18 |
+ escapeSequence []byte |
|
| 19 |
+ inAnsiSequence bool |
|
| 20 |
+ parser *ansiterm.AnsiParser |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a |
|
| 24 |
+// Windows console output handle. |
|
| 25 |
+func NewAnsiWriter(nFile int) io.Writer {
|
|
| 26 |
+ initLogger() |
|
| 27 |
+ file, fd := winterm.GetStdFile(nFile) |
|
| 28 |
+ info, err := winterm.GetConsoleScreenBufferInfo(fd) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return nil |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file))
|
|
| 34 |
+ logger.Infof("newAnsiWriter: parser %p", parser)
|
|
| 35 |
+ |
|
| 36 |
+ aw := &ansiWriter{
|
|
| 37 |
+ file: file, |
|
| 38 |
+ fd: fd, |
|
| 39 |
+ infoReset: info, |
|
| 40 |
+ command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), |
|
| 41 |
+ escapeSequence: []byte(ansiterm.KEY_ESC_CSI), |
|
| 42 |
+ parser: parser, |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ logger.Infof("newAnsiWriter: aw.parser %p", aw.parser)
|
|
| 46 |
+ logger.Infof("newAnsiWriter: %v", aw)
|
|
| 47 |
+ return aw |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func (aw *ansiWriter) Fd() uintptr {
|
|
| 51 |
+ return aw.fd |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+// Write writes len(p) bytes from p to the underlying data stream. |
|
| 55 |
+func (aw *ansiWriter) Write(p []byte) (total int, err error) {
|
|
| 56 |
+ if len(p) == 0 {
|
|
| 57 |
+ return 0, nil |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ logger.Infof("Write: % x", p)
|
|
| 61 |
+ logger.Infof("Write: %s", string(p))
|
|
| 62 |
+ return aw.parser.Parse(p) |
|
| 63 |
+} |
| 0 | 64 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,35 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+package windowsconsole // import "github.com/moby/term/windows" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "os" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/Azure/go-ansiterm/winterm" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// GetHandleInfo returns file descriptor and bool indicating whether the file is a console. |
|
| 11 |
+func GetHandleInfo(in interface{}) (uintptr, bool) {
|
|
| 12 |
+ switch t := in.(type) {
|
|
| 13 |
+ case *ansiReader: |
|
| 14 |
+ return t.Fd(), true |
|
| 15 |
+ case *ansiWriter: |
|
| 16 |
+ return t.Fd(), true |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ var inFd uintptr |
|
| 20 |
+ var isTerminal bool |
|
| 21 |
+ |
|
| 22 |
+ if file, ok := in.(*os.File); ok {
|
|
| 23 |
+ inFd = file.Fd() |
|
| 24 |
+ isTerminal = IsConsole(inFd) |
|
| 25 |
+ } |
|
| 26 |
+ return inFd, isTerminal |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// IsConsole returns true if the given file descriptor is a Windows Console. |
|
| 30 |
+// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. |
|
| 31 |
+func IsConsole(fd uintptr) bool {
|
|
| 32 |
+ _, e := winterm.GetConsoleMode(fd) |
|
| 33 |
+ return e == nil |
|
| 34 |
+} |
| 0 | 35 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+// +build windows |
|
| 1 |
+// These files implement ANSI-aware input and output streams for use by the Docker Windows client. |
|
| 2 |
+// When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create |
|
| 3 |
+// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls. |
|
| 4 |
+ |
|
| 5 |
+package windowsconsole // import "github.com/moby/term/windows" |
|
| 6 |
+ |
|
| 7 |
+import ( |
|
| 8 |
+ "io/ioutil" |
|
| 9 |
+ "os" |
|
| 10 |
+ "sync" |
|
| 11 |
+ |
|
| 12 |
+ ansiterm "github.com/Azure/go-ansiterm" |
|
| 13 |
+ "github.com/sirupsen/logrus" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+var logger *logrus.Logger |
|
| 17 |
+var initOnce sync.Once |
|
| 18 |
+ |
|
| 19 |
+func initLogger() {
|
|
| 20 |
+ initOnce.Do(func() {
|
|
| 21 |
+ logFile := ioutil.Discard |
|
| 22 |
+ |
|
| 23 |
+ if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
|
| 24 |
+ logFile, _ = os.Create("ansiReaderWriter.log")
|
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ logger = &logrus.Logger{
|
|
| 28 |
+ Out: logFile, |
|
| 29 |
+ Formatter: new(logrus.TextFormatter), |
|
| 30 |
+ Level: logrus.DebugLevel, |
|
| 31 |
+ } |
|
| 32 |
+ }) |
|
| 33 |
+} |
| 0 | 34 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,20 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package term |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "golang.org/x/sys/unix" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// GetWinsize returns the window size based on the specified file descriptor. |
|
| 9 |
+func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
| 10 |
+ uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ) |
|
| 11 |
+ ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
|
|
| 12 |
+ return ws, err |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// SetWinsize tries to set the specified window size for the specified file descriptor. |
|
| 16 |
+func SetWinsize(fd uintptr, ws *Winsize) error {
|
|
| 17 |
+ uws := &unix.Winsize{Row: ws.Height, Col: ws.Width, Xpixel: ws.x, Ypixel: ws.y}
|
|
| 18 |
+ return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, uws) |
|
| 19 |
+} |