Browse code

Implement configurable detach key

Implement configurable detach keys (for `attach`, exec`, `run` and
`start`) using the client-side configuration

- Adds a `--detach-keys` flag to `attach`, `exec`, `run` and `start`
commands.
- Adds a new configuration field (in `~/.docker/config.json`) to
configure the default escape keys for docker client.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2016/01/04 07:03:39
Showing 27 changed files
... ...
@@ -18,6 +18,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
18 18
 	cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
19 19
 	noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN")
20 20
 	proxy := cmd.Bool([]string{"-sig-proxy"}, true, "Proxy all received signals to the process")
21
+	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
21 22
 
22 23
 	cmd.Require(flag.Exact, 1)
23 24
 
... ...
@@ -46,12 +47,17 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
46 46
 		}
47 47
 	}
48 48
 
49
+	if *detachKeys != "" {
50
+		cli.configFile.DetachKeys = *detachKeys
51
+	}
52
+
49 53
 	options := types.ContainerAttachOptions{
50 54
 		ContainerID: cmd.Arg(0),
51 55
 		Stream:      true,
52 56
 		Stdin:       !*noStdin && c.Config.OpenStdin,
53 57
 		Stdout:      true,
54 58
 		Stderr:      true,
59
+		DetachKeys:  cli.configFile.DetachKeys,
55 60
 	}
56 61
 
57 62
 	var in io.ReadCloser
... ...
@@ -16,6 +16,7 @@ import (
16 16
 // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
17 17
 func (cli *DockerCli) CmdExec(args ...string) error {
18 18
 	cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
19
+	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
19 20
 
20 21
 	execConfig, err := runconfig.ParseExec(cmd, args)
21 22
 	// just in case the ParseExec does not exit
... ...
@@ -23,6 +24,13 @@ func (cli *DockerCli) CmdExec(args ...string) error {
23 23
 		return Cli.StatusError{StatusCode: 1}
24 24
 	}
25 25
 
26
+	if *detachKeys != "" {
27
+		cli.configFile.DetachKeys = *detachKeys
28
+	}
29
+
30
+	// Send client escape keys
31
+	execConfig.DetachKeys = cli.configFile.DetachKeys
32
+
26 33
 	response, err := cli.client.ContainerExecCreate(*execConfig)
27 34
 	if err != nil {
28 35
 		return err
... ...
@@ -24,6 +24,9 @@ func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types.
24 24
 	if options.Stderr {
25 25
 		query.Set("stderr", "1")
26 26
 	}
27
+	if options.DetachKeys != "" {
28
+		query.Set("detachKeys", options.DetachKeys)
29
+	}
27 30
 
28 31
 	headers := map[string][]string{"Content-Type": {"text/plain"}}
29 32
 	return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers)
... ...
@@ -74,6 +74,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
74 74
 		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
75 75
 		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
76 76
 		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
77
+		flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
77 78
 		flAttach     *opts.ListOpts
78 79
 
79 80
 		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
... ...
@@ -188,12 +189,17 @@ func (cli *DockerCli) CmdRun(args ...string) error {
188 188
 			}
189 189
 		}
190 190
 
191
+		if *flDetachKeys != "" {
192
+			cli.configFile.DetachKeys = *flDetachKeys
193
+		}
194
+
191 195
 		options := types.ContainerAttachOptions{
192 196
 			ContainerID: createResponse.ID,
193 197
 			Stream:      true,
194 198
 			Stdin:       config.AttachStdin,
195 199
 			Stdout:      config.AttachStdout,
196 200
 			Stderr:      config.AttachStderr,
201
+			DetachKeys:  cli.configFile.DetachKeys,
197 202
 		}
198 203
 
199 204
 		resp, err := cli.client.ContainerAttach(options)
... ...
@@ -49,6 +49,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
49 49
 	cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
50 50
 	attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
51 51
 	openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
52
+	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
52 53
 	cmd.Require(flag.Min, 1)
53 54
 
54 55
 	cmd.ParseFlags(args, true)
... ...
@@ -72,12 +73,17 @@ func (cli *DockerCli) CmdStart(args ...string) error {
72 72
 			defer signal.StopCatch(sigc)
73 73
 		}
74 74
 
75
+		if *detachKeys != "" {
76
+			cli.configFile.DetachKeys = *detachKeys
77
+		}
78
+
75 79
 		options := types.ContainerAttachOptions{
76 80
 			ContainerID: containerID,
77 81
 			Stream:      true,
78 82
 			Stdin:       *openStdin && c.Config.OpenStdin,
79 83
 			Stdout:      true,
80 84
 			Stderr:      true,
85
+			DetachKeys:  cli.configFile.DetachKeys,
81 86
 		}
82 87
 
83 88
 		var in io.ReadCloser
... ...
@@ -19,6 +19,7 @@ import (
19 19
 	derr "github.com/docker/docker/errors"
20 20
 	"github.com/docker/docker/pkg/ioutils"
21 21
 	"github.com/docker/docker/pkg/signal"
22
+	"github.com/docker/docker/pkg/term"
22 23
 	"github.com/docker/docker/runconfig"
23 24
 	"github.com/docker/docker/utils"
24 25
 	"golang.org/x/net/context"
... ...
@@ -420,21 +421,32 @@ func (s *containerRouter) postContainersResize(ctx context.Context, w http.Respo
420 420
 }
421 421
 
422 422
 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
423
-	if err := httputils.ParseForm(r); err != nil {
423
+	err := httputils.ParseForm(r)
424
+	if err != nil {
424 425
 		return err
425 426
 	}
426 427
 	containerName := vars["name"]
427 428
 
428 429
 	_, upgrade := r.Header["Upgrade"]
429 430
 
431
+	keys := []byte{}
432
+	detachKeys := r.FormValue("detachKeys")
433
+	if detachKeys != "" {
434
+		keys, err = term.ToBytes(detachKeys)
435
+		if err != nil {
436
+			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
437
+		}
438
+	}
439
+
430 440
 	attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
431
-		Hijacker:  w.(http.Hijacker),
432
-		Upgrade:   upgrade,
433
-		UseStdin:  httputils.BoolValue(r, "stdin"),
434
-		UseStdout: httputils.BoolValue(r, "stdout"),
435
-		UseStderr: httputils.BoolValue(r, "stderr"),
436
-		Logs:      httputils.BoolValue(r, "logs"),
437
-		Stream:    httputils.BoolValue(r, "stream"),
441
+		Hijacker:   w.(http.Hijacker),
442
+		Upgrade:    upgrade,
443
+		UseStdin:   httputils.BoolValue(r, "stdin"),
444
+		UseStdout:  httputils.BoolValue(r, "stdout"),
445
+		UseStderr:  httputils.BoolValue(r, "stderr"),
446
+		Logs:       httputils.BoolValue(r, "logs"),
447
+		Stream:     httputils.BoolValue(r, "stream"),
448
+		DetachKeys: keys,
438 449
 	}
439 450
 
440 451
 	return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig)
... ...
@@ -450,15 +462,26 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons
450 450
 		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
451 451
 	}
452 452
 
453
+	var keys []byte
454
+	var err error
455
+	detachKeys := r.FormValue("detachKeys")
456
+	if detachKeys != "" {
457
+		keys, err = term.ToBytes(detachKeys)
458
+		if err != nil {
459
+			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
460
+		}
461
+	}
462
+
453 463
 	h := websocket.Handler(func(ws *websocket.Conn) {
454 464
 		defer ws.Close()
455 465
 
456 466
 		wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
457
-			InStream:  ws,
458
-			OutStream: ws,
459
-			ErrStream: ws,
460
-			Logs:      httputils.BoolValue(r, "logs"),
461
-			Stream:    httputils.BoolValue(r, "stream"),
467
+			InStream:   ws,
468
+			OutStream:  ws,
469
+			ErrStream:  ws,
470
+			Logs:       httputils.BoolValue(r, "logs"),
471
+			Stream:     httputils.BoolValue(r, "stream"),
472
+			DetachKeys: keys,
462 473
 		}
463 474
 
464 475
 		if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
... ...
@@ -17,6 +17,7 @@ type ContainerAttachOptions struct {
17 17
 	Stdin       bool
18 18
 	Stdout      bool
19 19
 	Stderr      bool
20
+	DetachKeys  string
20 21
 }
21 22
 
22 23
 // ContainerCommitOptions holds parameters to commit changes into a container.
... ...
@@ -45,5 +45,6 @@ type ExecConfig struct {
45 45
 	AttachStderr bool     // Attach the standard output
46 46
 	AttachStdout bool     // Attach the standard error
47 47
 	Detach       bool     // Execute in detach mode
48
+	DetachKeys   string   // Escape keys for detach
48 49
 	Cmd          []string // Execution commands and args
49 50
 }
... ...
@@ -51,6 +51,7 @@ type ConfigFile struct {
51 51
 	HTTPHeaders  map[string]string           `json:"HttpHeaders,omitempty"`
52 52
 	PsFormat     string                      `json:"psFormat,omitempty"`
53 53
 	ImagesFormat string                      `json:"imagesFormat,omitempty"`
54
+	DetachKeys   string                      `json:"detachKeys,omitempty"`
54 55
 	filename     string                      // Note: not serialized - for internal use only
55 56
 }
56 57
 
... ...
@@ -329,13 +329,13 @@ func (container *Container) GetExecIDs() []string {
329 329
 
330 330
 // Attach connects to the container's TTY, delegating to standard
331 331
 // streams or websockets depending on the configuration.
332
-func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
333
-	return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr)
332
+func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
333
+	return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys)
334 334
 }
335 335
 
336 336
 // AttachStreams connects streams to a TTY.
337 337
 // Used by exec too. Should this move somewhere else?
338
-func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
338
+func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
339 339
 	var (
340 340
 		cStdout, cStderr io.ReadCloser
341 341
 		cStdin           io.WriteCloser
... ...
@@ -382,7 +382,7 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t
382 382
 
383 383
 		var err error
384 384
 		if tty {
385
-			_, err = copyEscapable(cStdin, stdin)
385
+			_, err = copyEscapable(cStdin, stdin, keys)
386 386
 		} else {
387 387
 			_, err = io.Copy(cStdin, stdin)
388 388
 
... ...
@@ -438,22 +438,27 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t
438 438
 }
439 439
 
440 440
 // Code c/c from io.Copy() modified to handle escape sequence
441
-func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
441
+func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
442
+	if len(keys) == 0 {
443
+		// Default keys : ctrl-p ctrl-q
444
+		keys = []byte{16, 17}
445
+	}
442 446
 	buf := make([]byte, 32*1024)
443 447
 	for {
444 448
 		nr, er := src.Read(buf)
445 449
 		if nr > 0 {
446 450
 			// ---- Docker addition
447
-			// char 16 is C-p
448
-			if nr == 1 && buf[0] == 16 {
449
-				nr, er = src.Read(buf)
450
-				// char 17 is C-q
451
-				if nr == 1 && buf[0] == 17 {
451
+			for i, key := range keys {
452
+				if nr != 1 || buf[0] != key {
453
+					break
454
+				}
455
+				if i == len(keys)-1 {
452 456
 					if err := src.Close(); err != nil {
453 457
 						return 0, err
454 458
 					}
455 459
 					return 0, nil
456 460
 				}
461
+				nr, er = src.Read(buf)
457 462
 			}
458 463
 			// ---- End of docker
459 464
 			nw, ew := dst.Write(buf[0:nr])
... ...
@@ -15,13 +15,14 @@ import (
15 15
 
16 16
 // ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs.
17 17
 type ContainerAttachWithLogsConfig struct {
18
-	Hijacker  http.Hijacker
19
-	Upgrade   bool
20
-	UseStdin  bool
21
-	UseStdout bool
22
-	UseStderr bool
23
-	Logs      bool
24
-	Stream    bool
18
+	Hijacker   http.Hijacker
19
+	Upgrade    bool
20
+	UseStdin   bool
21
+	UseStdout  bool
22
+	UseStderr  bool
23
+	Logs       bool
24
+	Stream     bool
25
+	DetachKeys []byte
25 26
 }
26 27
 
27 28
 // ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig.
... ...
@@ -75,7 +76,7 @@ func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *ContainerA
75 75
 		stderr = errStream
76 76
 	}
77 77
 
78
-	if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream); err != nil {
78
+	if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil {
79 79
 		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
80 80
 	}
81 81
 	return nil
... ...
@@ -87,6 +88,7 @@ type ContainerWsAttachWithLogsConfig struct {
87 87
 	InStream             io.ReadCloser
88 88
 	OutStream, ErrStream io.Writer
89 89
 	Logs, Stream         bool
90
+	DetachKeys           []byte
90 91
 }
91 92
 
92 93
 // ContainerWsAttachWithLogs websocket connection
... ...
@@ -95,10 +97,10 @@ func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *Containe
95 95
 	if err != nil {
96 96
 		return err
97 97
 	}
98
-	return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream)
98
+	return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream, c.DetachKeys)
99 99
 }
100 100
 
101
-func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error {
101
+func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
102 102
 	if logs {
103 103
 		logDriver, err := daemon.getLogger(container)
104 104
 		if err != nil {
... ...
@@ -144,7 +146,7 @@ func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.Re
144 144
 			}()
145 145
 			stdinPipe = r
146 146
 		}
147
-		<-container.Attach(stdinPipe, stdout, stderr)
147
+		<-container.Attach(stdinPipe, stdout, stderr, keys)
148 148
 		// If we are in stdinonce mode, wait for the process to end
149 149
 		// otherwise, simply return
150 150
 		if container.Config.StdinOnce && !container.Config.Tty {
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	derr "github.com/docker/docker/errors"
15 15
 	"github.com/docker/docker/pkg/pools"
16 16
 	"github.com/docker/docker/pkg/promise"
17
+	"github.com/docker/docker/pkg/term"
17 18
 )
18 19
 
19 20
 func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) {
... ...
@@ -88,6 +89,14 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
88 88
 	cmd := strslice.New(config.Cmd...)
89 89
 	entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd)
90 90
 
91
+	keys := []byte{}
92
+	if config.DetachKeys != "" {
93
+		keys, err = term.ToBytes(config.DetachKeys)
94
+		if err != nil {
95
+			logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error())
96
+		}
97
+	}
98
+
91 99
 	processConfig := &execdriver.ProcessConfig{
92 100
 		CommonProcessConfig: execdriver.CommonProcessConfig{
93 101
 			Tty:        config.Tty,
... ...
@@ -103,6 +112,7 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
103 103
 	execConfig.OpenStderr = config.AttachStderr
104 104
 	execConfig.ProcessConfig = processConfig
105 105
 	execConfig.ContainerID = container.ID
106
+	execConfig.DetachKeys = keys
106 107
 
107 108
 	d.registerExecCommand(container, execConfig)
108 109
 
... ...
@@ -158,7 +168,8 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
158 158
 		ec.NewNopInputPipe()
159 159
 	}
160 160
 
161
-	attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr)
161
+	attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
162
+
162 163
 	execErr := make(chan error)
163 164
 
164 165
 	// Note, the ExecConfig data will be removed when the container
... ...
@@ -25,6 +25,7 @@ type Config struct {
25 25
 	OpenStdout    bool
26 26
 	CanRemove     bool
27 27
 	ContainerID   string
28
+	DetachKeys    []byte
28 29
 
29 30
 	// waitStart will be closed immediately after the exec is really started.
30 31
 	waitStart chan struct{}
... ...
@@ -862,10 +862,9 @@ This endpoint returns a live stream of a container's resource usage statistics.
862 862
                "total_usage" : 36488948,
863 863
                "usage_in_kernelmode" : 20000000
864 864
             },
865
-            "system_cpu_usage" : 20091722000000000,
865
+            "system_cpu_usage" : 20091722000000000,
866 866
             "throttling_data" : {}
867
-         }
868
-      }
867
+         }      }
869 868
 
870 869
 Query Parameters:
871 870
 
... ...
@@ -922,6 +921,12 @@ Start the container `id`
922 922
 
923 923
     HTTP/1.1 204 No Content
924 924
 
925
+Query Parameters:
926
+
927
+-   **detacheys** – Override the key sequence for detaching a
928
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
929
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
930
+
925 931
 Status Codes:
926 932
 
927 933
 -   **204** – no error
... ...
@@ -1133,6 +1138,9 @@ Attach to the container `id`
1133 1133
 
1134 1134
 Query Parameters:
1135 1135
 
1136
+-   **detacheys** – Override the key sequence for detaching a
1137
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
1138
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
1136 1139
 -   **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
1137 1140
 -   **stream** – 1/True/true or 0/False/false, return stream.
1138 1141
         Default `false`.
... ...
@@ -1213,6 +1221,9 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1213 1213
 
1214 1214
 Query Parameters:
1215 1215
 
1216
+-   **detacheys** – Override the key sequence for detaching a
1217
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
1218
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
1216 1219
 -   **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
1217 1220
 -   **stream** – 1/True/true or 0/False/false, return stream.
1218 1221
         Default `false`.
... ...
@@ -2420,6 +2431,7 @@ Sets up an exec instance in a running container `id`
2420 2420
        "AttachStdin": false,
2421 2421
        "AttachStdout": true,
2422 2422
        "AttachStderr": true,
2423
+       "DetachKeys": "ctrl-p,ctrl-q",
2423 2424
        "Tty": false,
2424 2425
        "Cmd": [
2425 2426
                      "date"
... ...
@@ -2441,6 +2453,9 @@ Json Parameters:
2441 2441
 -   **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command.
2442 2442
 -   **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command.
2443 2443
 -   **AttachStderr** - Boolean value, attaches to `stderr` of the `exec` command.
2444
+-   **Detacheys** – Override the key sequence for detaching a
2445
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
2446
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
2444 2447
 -   **Tty** - Boolean value to allocate a pseudo-TTY.
2445 2448
 -   **Cmd** - Command to run specified as a string or an array of strings.
2446 2449
 
... ...
@@ -14,9 +14,10 @@ parent = "smn_cli"
14 14
 
15 15
     Attach to a running container
16 16
 
17
-      --help              Print usage
18
-      --no-stdin          Do not attach STDIN
19
-      --sig-proxy=true    Proxy all received signals to the process
17
+      --detach-keys="<sequence>"       Set up escape key sequence
18
+      --help                           Print usage
19
+      --no-stdin                       Do not attach STDIN
20
+      --sig-proxy=true                 Proxy all received signals to the process
20 21
 
21 22
 The `docker attach` command allows you to attach to a running container using
22 23
 the container's ID or name, either to view its ongoing output or to control it
... ...
@@ -24,11 +25,10 @@ interactively. You can attach to the same contained process multiple times
24 24
 simultaneously, screen sharing style, or quickly view the progress of your
25 25
 detached  process.
26 26
 
27
-You can detach from the container and leave it running with `CTRL-p CTRL-q`
28
-(for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false.
29
-
30
-If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to the
31
-container.
27
+To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
28
+container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to
29
+the container. You can detach from a container and leave it running using the
30
+using `CTRL-p CTRL-q` key sequence.
32 31
 
33 32
 > **Note:**
34 33
 > A process running as PID 1 inside a container is treated specially by
... ...
@@ -39,6 +39,31 @@ container.
39 39
 It is forbidden to redirect the standard input of a `docker attach` command
40 40
 while attaching to a tty-enabled container (i.e.: launched with `-t`).
41 41
 
42
+
43
+## Override the detach sequence
44
+
45
+If you want, you can configure a override the Docker key sequence for detach.
46
+This is is useful if the Docker default sequence conflicts with key squence you
47
+use for other applications. There are two ways to defines a your own detach key
48
+sequence, as a per-container override or as a configuration property on  your
49
+entire configuration.
50
+
51
+To override the sequence for an individual container, use the
52
+`--detach-keys="<sequence>"` flag with the `docker attach` command. The format of
53
+the `<sequence>` is either a letter [a-Z], or the `ctrl-` combined with any of
54
+the following:
55
+
56
+* `a-z` (a single lowercase alpha character )
57
+* `@` (ampersand)
58
+* `[` (left bracket)
59
+* `\\` (two backward slashes)
60
+*  `_` (underscore)
61
+* `^` (caret)
62
+
63
+These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key
64
+sequences. To configure a different configuration default key sequence for all
65
+containers, see [**Configuration file** section](cli.md#configuration-files).
66
+
42 67
 #### Examples
43 68
 
44 69
     $ docker run -d --name topdemo ubuntu /usr/bin/top -b
... ...
@@ -101,7 +101,26 @@ The property `psFormat` specifies the default format for `docker ps` output.
101 101
 When the `--format` flag is not provided with the `docker ps` command,
102 102
 Docker's client uses this property. If this property is not set, the client
103 103
 falls back to the default table format. For a list of supported formatting
104
-directives, see the [**Formatting** section in the `docker ps` documentation](ps.md)
104
+directives, see the
105
+[**Formatting** section in the `docker ps` documentation](ps.md)
106
+
107
+Once attached to a container, users detach from it and leave it running using
108
+the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable
109
+using the `detachKeys` property. Specify a `<sequence>` value for the
110
+property. The format of the `<sequence>` is either a letter [a-Z], or the `ctrl-`
111
+combined with any of the following:
112
+
113
+* `a-z` (a single lowercase alpha character )
114
+* `@` (ampersand)
115
+* `[` (left bracket)
116
+* `\\` (two backward slashes)
117
+*  `_` (underscore)
118
+* `^` (caret)
119
+
120
+Your customization applies to all containers started in with your Docker client.
121
+Users can override your custom or the default key sequence on a per-container
122
+basis. To do this, the user specifies the `--detach-keys` flag with the `docker
123
+attach`, `docker exec`, `docker run` or `docker start` command.
105 124
 
106 125
 The property `imagesFormat` specifies the default format for `docker images` output.
107 126
 When the `--format` flag is not provided with the `docker images` command,
... ...
@@ -115,8 +134,9 @@ Following is a sample `config.json` file:
115 115
       "HttpHeaders": {
116 116
         "MyHeader": "MyValue"
117 117
       },
118
-      "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}"
119
-      "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}"
118
+      "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
119
+      "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
120
+      "detachKeys": "ctrl-e,e"
120 121
     }
121 122
 
122 123
 ### Notary
... ...
@@ -15,6 +15,7 @@ parent = "smn_cli"
15 15
     Run a command in a running container
16 16
 
17 17
       -d, --detach               Detached mode: run command in the background
18
+      --detach-keys              Specify the escape key sequence used to detach a container
18 19
       --help                     Print usage
19 20
       -i, --interactive          Keep STDIN open even if not attached
20 21
       --privileged               Give extended Linux capabilities to the command
... ...
@@ -28,6 +28,7 @@ parent = "smn_cli"
28 28
       --cpuset-cpus=""              CPUs in which to allow execution (0-3, 0,1)
29 29
       --cpuset-mems=""              Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
30 30
       -d, --detach                  Run container in background and print container ID
31
+      --detach-keys                 Specify the escape key sequence used to detach a container
31 32
       --device=[]                   Add a host device to the container
32 33
       --device-read-bps=[]          Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb)
33 34
       --device-read-iops=[]         Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000)
... ...
@@ -15,5 +15,6 @@ parent = "smn_cli"
15 15
     Start one or more containers
16 16
 
17 17
       -a, --attach               Attach STDOUT/STDERR and forward signals
18
+      --detach-keys              Specify the escape key sequence used to detach a container
18 19
       --help                     Print usage
19 20
       -i, --interactive          Attach container's STDIN
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"strings"
15 15
 	"time"
16 16
 
17
+	"github.com/docker/docker/pkg/homedir"
17 18
 	"github.com/docker/docker/pkg/integration/checker"
18 19
 	"github.com/docker/docker/pkg/mount"
19 20
 	"github.com/docker/docker/pkg/parsers"
... ...
@@ -87,10 +88,13 @@ func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) {
87 87
 	c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "seq", check.Commentf("expected output /dev/othersnd/seq"))
88 88
 }
89 89
 
90
-// TestRunDetach checks attaching and detaching with the escape sequence.
90
+// TestRunDetach checks attaching and detaching with the default escape sequence.
91 91
 func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
92 92
 	name := "attach-detach"
93
-	cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat")
93
+
94
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
95
+
96
+	cmd := exec.Command(dockerBinary, "attach", name)
94 97
 	stdout, err := cmd.StdoutPipe()
95 98
 	c.Assert(err, checker.IsNil)
96 99
 	cpty, tty, err := pty.Open()
... ...
@@ -120,21 +124,248 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
120 120
 		ch <- struct{}{}
121 121
 	}()
122 122
 
123
+	select {
124
+	case <-ch:
125
+	case <-time.After(10 * time.Second):
126
+		c.Fatal("timed out waiting for container to exit")
127
+	}
128
+
123 129
 	running, err := inspectField(name, "State.Running")
124 130
 	c.Assert(err, checker.IsNil)
125 131
 	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
132
+}
133
+
134
+// TestRunDetach checks attaching and detaching with the escape sequence specified via flags.
135
+func (s *DockerSuite) TestRunAttachDetachFromFlag(c *check.C) {
136
+	name := "attach-detach"
137
+	keyCtrlA := []byte{1}
138
+	keyA := []byte{97}
139
+
140
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
141
+
142
+	cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name)
143
+	stdout, err := cmd.StdoutPipe()
144
+	if err != nil {
145
+		c.Fatal(err)
146
+	}
147
+	cpty, tty, err := pty.Open()
148
+	if err != nil {
149
+		c.Fatal(err)
150
+	}
151
+	defer cpty.Close()
152
+	cmd.Stdin = tty
153
+	if err := cmd.Start(); err != nil {
154
+		c.Fatal(err)
155
+	}
156
+	c.Assert(waitRun(name), check.IsNil)
157
+
158
+	if _, err := cpty.Write([]byte("hello\n")); err != nil {
159
+		c.Fatal(err)
160
+	}
161
+
162
+	out, err := bufio.NewReader(stdout).ReadString('\n')
163
+	if err != nil {
164
+		c.Fatal(err)
165
+	}
166
+	if strings.TrimSpace(out) != "hello" {
167
+		c.Fatalf("expected 'hello', got %q", out)
168
+	}
169
+
170
+	// escape sequence
171
+	if _, err := cpty.Write(keyCtrlA); err != nil {
172
+		c.Fatal(err)
173
+	}
174
+	time.Sleep(100 * time.Millisecond)
175
+	if _, err := cpty.Write(keyA); err != nil {
176
+		c.Fatal(err)
177
+	}
126 178
 
179
+	ch := make(chan struct{})
127 180
 	go func() {
128
-		exec.Command(dockerBinary, "kill", name).Run()
181
+		cmd.Wait()
182
+		ch <- struct{}{}
129 183
 	}()
130 184
 
131 185
 	select {
132 186
 	case <-ch:
133
-	case <-time.After(10 * time.Millisecond):
187
+	case <-time.After(10 * time.Second):
134 188
 		c.Fatal("timed out waiting for container to exit")
135 189
 	}
190
+
191
+	running, err := inspectField(name, "State.Running")
192
+	c.Assert(err, checker.IsNil)
193
+	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
136 194
 }
137 195
 
196
+// TestRunDetach checks attaching and detaching with the escape sequence specified via config file.
197
+func (s *DockerSuite) TestRunAttachDetachFromConfig(c *check.C) {
198
+	keyCtrlA := []byte{1}
199
+	keyA := []byte{97}
200
+
201
+	// Setup config
202
+	homeKey := homedir.Key()
203
+	homeVal := homedir.Get()
204
+	tmpDir, err := ioutil.TempDir("", "fake-home")
205
+	c.Assert(err, checker.IsNil)
206
+	defer os.RemoveAll(tmpDir)
207
+
208
+	dotDocker := filepath.Join(tmpDir, ".docker")
209
+	os.Mkdir(dotDocker, 0600)
210
+	tmpCfg := filepath.Join(dotDocker, "config.json")
211
+
212
+	defer func() { os.Setenv(homeKey, homeVal) }()
213
+	os.Setenv(homeKey, tmpDir)
214
+
215
+	data := `{
216
+		"detachKeys": "ctrl-a,a"
217
+	}`
218
+
219
+	err = ioutil.WriteFile(tmpCfg, []byte(data), 0600)
220
+	c.Assert(err, checker.IsNil)
221
+
222
+	// Then do the work
223
+	name := "attach-detach"
224
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
225
+
226
+	cmd := exec.Command(dockerBinary, "attach", name)
227
+	stdout, err := cmd.StdoutPipe()
228
+	if err != nil {
229
+		c.Fatal(err)
230
+	}
231
+	cpty, tty, err := pty.Open()
232
+	if err != nil {
233
+		c.Fatal(err)
234
+	}
235
+	defer cpty.Close()
236
+	cmd.Stdin = tty
237
+	if err := cmd.Start(); err != nil {
238
+		c.Fatal(err)
239
+	}
240
+	c.Assert(waitRun(name), check.IsNil)
241
+
242
+	if _, err := cpty.Write([]byte("hello\n")); err != nil {
243
+		c.Fatal(err)
244
+	}
245
+
246
+	out, err := bufio.NewReader(stdout).ReadString('\n')
247
+	if err != nil {
248
+		c.Fatal(err)
249
+	}
250
+	if strings.TrimSpace(out) != "hello" {
251
+		c.Fatalf("expected 'hello', got %q", out)
252
+	}
253
+
254
+	// escape sequence
255
+	if _, err := cpty.Write(keyCtrlA); err != nil {
256
+		c.Fatal(err)
257
+	}
258
+	time.Sleep(100 * time.Millisecond)
259
+	if _, err := cpty.Write(keyA); err != nil {
260
+		c.Fatal(err)
261
+	}
262
+
263
+	ch := make(chan struct{})
264
+	go func() {
265
+		cmd.Wait()
266
+		ch <- struct{}{}
267
+	}()
268
+
269
+	select {
270
+	case <-ch:
271
+	case <-time.After(10 * time.Second):
272
+		c.Fatal("timed out waiting for container to exit")
273
+	}
274
+
275
+	running, err := inspectField(name, "State.Running")
276
+	c.Assert(err, checker.IsNil)
277
+	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
278
+}
279
+
280
+// TestRunDetach checks attaching and detaching with the detach flags, making sure it overrides config file
281
+func (s *DockerSuite) TestRunAttachDetachKeysOverrideConfig(c *check.C) {
282
+	keyCtrlA := []byte{1}
283
+	keyA := []byte{97}
284
+
285
+	// Setup config
286
+	homeKey := homedir.Key()
287
+	homeVal := homedir.Get()
288
+	tmpDir, err := ioutil.TempDir("", "fake-home")
289
+	c.Assert(err, checker.IsNil)
290
+	defer os.RemoveAll(tmpDir)
291
+
292
+	dotDocker := filepath.Join(tmpDir, ".docker")
293
+	os.Mkdir(dotDocker, 0600)
294
+	tmpCfg := filepath.Join(dotDocker, "config.json")
295
+
296
+	defer func() { os.Setenv(homeKey, homeVal) }()
297
+	os.Setenv(homeKey, tmpDir)
298
+
299
+	data := `{
300
+		"detachKeys": "ctrl-e,e"
301
+	}`
302
+
303
+	err = ioutil.WriteFile(tmpCfg, []byte(data), 0600)
304
+	c.Assert(err, checker.IsNil)
305
+
306
+	// Then do the work
307
+	name := "attach-detach"
308
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
309
+
310
+	cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name)
311
+	stdout, err := cmd.StdoutPipe()
312
+	if err != nil {
313
+		c.Fatal(err)
314
+	}
315
+	cpty, tty, err := pty.Open()
316
+	if err != nil {
317
+		c.Fatal(err)
318
+	}
319
+	defer cpty.Close()
320
+	cmd.Stdin = tty
321
+	if err := cmd.Start(); err != nil {
322
+		c.Fatal(err)
323
+	}
324
+	c.Assert(waitRun(name), check.IsNil)
325
+
326
+	if _, err := cpty.Write([]byte("hello\n")); err != nil {
327
+		c.Fatal(err)
328
+	}
329
+
330
+	out, err := bufio.NewReader(stdout).ReadString('\n')
331
+	if err != nil {
332
+		c.Fatal(err)
333
+	}
334
+	if strings.TrimSpace(out) != "hello" {
335
+		c.Fatalf("expected 'hello', got %q", out)
336
+	}
337
+
338
+	// escape sequence
339
+	if _, err := cpty.Write(keyCtrlA); err != nil {
340
+		c.Fatal(err)
341
+	}
342
+	time.Sleep(100 * time.Millisecond)
343
+	if _, err := cpty.Write(keyA); err != nil {
344
+		c.Fatal(err)
345
+	}
346
+
347
+	ch := make(chan struct{})
348
+	go func() {
349
+		cmd.Wait()
350
+		ch <- struct{}{}
351
+	}()
352
+
353
+	select {
354
+	case <-ch:
355
+	case <-time.After(10 * time.Second):
356
+		c.Fatal("timed out waiting for container to exit")
357
+	}
358
+
359
+	running, err := inspectField(name, "State.Running")
360
+	c.Assert(err, checker.IsNil)
361
+	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
362
+}
363
+
364
+// "test" should be printed
138 365
 func (s *DockerSuite) TestRunWithCPUQuota(c *check.C) {
139 366
 	testRequires(c, cpuCfsQuota)
140 367
 
... ...
@@ -6,6 +6,7 @@ docker-attach - Attach to a running container
6 6
 
7 7
 # SYNOPSIS
8 8
 **docker attach**
9
+[**--detach-keys**[=*[]*]]
9 10
 [**--help**]
10 11
 [**--no-stdin**]
11 12
 [**--sig-proxy**[=*true*]]
... ...
@@ -18,15 +19,19 @@ interactively.  You can attach to the same contained process multiple times
18 18
 simultaneously, screen sharing style, or quickly view the progress of your
19 19
 detached process.
20 20
 
21
-You can detach from the container (and leave it running) with `CTRL-p CTRL-q`
22
-(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container.
23
-When you are attached to a container, and exit its main process, the process's
24
-exit code will be returned to the client.
21
+To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
22
+container. You can detach from the container (and leave it running) using a
23
+configurable key sequence. The default sequence is `CTRL-p CTRL-q`. You
24
+configure the key sequence using the **--detach-keys** option or a configuration
25
+file. See **config-json(5)** for documentation on using a configuration file.
25 26
 
26 27
 It is forbidden to redirect the standard input of a `docker attach` command while
27 28
 attaching to a tty-enabled container (i.e.: launched with `-t`).
28 29
 
29 30
 # OPTIONS
31
+**--detach-keys**=""
32
+    Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
33
+
30 34
 **--help**
31 35
   Print usage statement
32 36
 
... ...
@@ -36,6 +41,30 @@ attaching to a tty-enabled container (i.e.: launched with `-t`).
36 36
 **--sig-proxy**=*true*|*false*
37 37
    Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied. The default is *true*.
38 38
 
39
+# Override the detach sequence
40
+
41
+If you want, you can configure a override the Docker key sequence for detach.
42
+This is is useful if the Docker default sequence conflicts with key squence you
43
+use for other applications. There are two ways to defines a your own detach key
44
+sequence, as a per-container override or as a configuration property on  your
45
+entire configuration.
46
+
47
+To override the sequence for an individual container, use the
48
+`--detach-keys="<sequence>"` flag with the `docker attach` command. The format of
49
+the `<sequence>` is either a letter [a-Z], or the `ctrl-` combined with any of
50
+the following:
51
+
52
+* `a-z` (a single lowercase alpha character )
53
+* `@` (ampersand)
54
+* `[` (left bracket)
55
+* `\\` (two backward slashes)
56
+*  `_` (underscore)
57
+* `^` (caret)
58
+
59
+These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key
60
+sequences. To configure a different configuration default key sequence for all
61
+containers, see **docker(1)**.
62
+
39 63
 # EXAMPLES
40 64
 
41 65
 ## Attaching to a container
... ...
@@ -7,6 +7,7 @@ docker-exec - Run a command in a running container
7 7
 # SYNOPSIS
8 8
 **docker exec**
9 9
 [**-d**|**--detach**]
10
+[**--detach-keys**[=*[]*]]
10 11
 [**--help**]
11 12
 [**-i**|**--interactive**]
12 13
 [**--privileged**]
... ...
@@ -26,7 +27,10 @@ container is unpaused, and then run
26 26
 
27 27
 # OPTIONS
28 28
 **-d**, **--detach**=*true*|*false*
29
-   Detached mode: run command in the background. The default is *false*.
29
+    Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
30
+
31
+**--detach-keys**=""
32
+  Define the key sequence which detaches the container.
30 33
 
31 34
 **--help**
32 35
   Print usage statement
... ...
@@ -20,6 +20,7 @@ docker-run - Run a command in a new container
20 20
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
21 21
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
22 22
 [**-d**|**--detach**]
23
+[**--detach-keys**[=*[]*]]
23 24
 [**--device**[=*[]*]]
24 25
 [**--device-read-bps**[=*[]*]]
25 26
 [**--device-read-iops**[=*[]*]]
... ...
@@ -190,8 +191,13 @@ the other shell to view a list of the running containers. You can reattach to a
190 190
 detached container with **docker attach**. If you choose to run a container in
191 191
 the detached mode, then you cannot use the **-rm** option.
192 192
 
193
-   When attached in the tty mode, you can detach from a running container without
194
-stopping the process by pressing the keys CTRL-P CTRL-Q.
193
+   When attached in the tty mode, you can detach from the container (and leave it
194
+running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
195
+You configure the key sequence using the **--detach-keys** option or a configuration file.
196
+See **config-json(5)** for documentation on using a configuration file.
197
+
198
+**--detach-keys**=""
199
+   Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
195 200
 
196 201
 **--device**=[]
197 202
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
... ...
@@ -7,6 +7,7 @@ docker-start - Start one or more containers
7 7
 # SYNOPSIS
8 8
 **docker start**
9 9
 [**-a**|**--attach**]
10
+[**--detach-keys**[=*[]*]]
10 11
 [**--help**]
11 12
 [**-i**|**--interactive**]
12 13
 CONTAINER [CONTAINER...]
... ...
@@ -17,7 +18,11 @@ Start one or more containers.
17 17
 
18 18
 # OPTIONS
19 19
 **-a**, **--attach**=*true*|*false*
20
-   Attach container's STDOUT and STDERR and forward all signals to the process. The default is *false*.
20
+   Attach container's STDOUT and STDERR and forward all signals to the
21
+   process. The default is *false*.
22
+
23
+**--detach-keys**=""
24
+   Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
21 25
 
22 26
 **--help**
23 27
   Print usage statement
... ...
@@ -223,6 +223,7 @@ inside it)
223 223
   Block until a container stops, then print its exit code
224 224
   See **docker-wait(1)** for full documentation on the **wait** command.
225 225
 
226
+
226 227
 # EXEC DRIVER OPTIONS
227 228
 
228 229
 Use the **--exec-opt** flags to specify options to the execution driver. The only
229 230
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, byte(key[0]))
62
+		}
63
+	}
64
+	return codes, nil
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package term
1
+
2
+import "testing"
3
+
4
+func TestToBytes(t *testing.T) {
5
+	codes, err := ToBytes("ctrl-a,a")
6
+	if err != nil {
7
+		t.Fatal(err)
8
+	}
9
+	if len(codes) != 2 {
10
+		t.Fatalf("Expected 2 codes, got %d", len(codes))
11
+	}
12
+	if codes[0] != 1 || codes[1] != 97 {
13
+		t.Fatalf("Expected '1' '97', got '%d' '%d'", codes[0], codes[1])
14
+	}
15
+
16
+	codes, err = ToBytes("shift-z")
17
+	if err == nil {
18
+		t.Fatalf("Expected error, got none")
19
+	}
20
+
21
+	codes, err = ToBytes("ctrl-@,ctrl-[,~,ctrl-o")
22
+	if err != nil {
23
+		t.Fatal(err)
24
+	}
25
+	if len(codes) != 4 {
26
+		t.Fatalf("Expected 4 codes, got %d", len(codes))
27
+	}
28
+	if codes[0] != 0 || codes[1] != 27 || codes[2] != 126 || codes[3] != 15 {
29
+		t.Fatalf("Expected '0' '27' '126', '15', got '%d' '%d' '%d' '%d'", codes[0], codes[1], codes[2], codes[3])
30
+	}
31
+
32
+	codes, err = ToBytes("DEL,+")
33
+	if err != nil {
34
+		t.Fatal(err)
35
+	}
36
+	if len(codes) != 2 {
37
+		t.Fatalf("Expected 2 codes, got %d", len(codes))
38
+	}
39
+	if codes[0] != 127 || codes[1] != 43 {
40
+		t.Fatalf("Expected '127 '43'', got '%d' '%d'", codes[0], codes[1])
41
+	}
42
+}