mirror of https://github.com/docker/docs.git
Merge pull request #710 from dotcloud/tty_resize
+ Tty: Handle terminal size and resize in tty mode
This commit is contained in:
commit
a05bfb246f
24
api.go
24
api.go
|
@ -48,6 +48,7 @@ func writeJson(w http.ResponseWriter, b []byte) {
|
||||||
w.Write(b)
|
w.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Use stvconv.ParseBool() instead?
|
||||||
func getBoolParam(value string) (bool, error) {
|
func getBoolParam(value string) (bool, error) {
|
||||||
if value == "1" || strings.ToLower(value) == "true" {
|
if value == "1" || strings.ToLower(value) == "true" {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -485,6 +486,28 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := parseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
width, err := strconv.Atoi(r.Form.Get("w"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vars == nil {
|
||||||
|
return fmt.Errorf("Missing parameter")
|
||||||
|
}
|
||||||
|
name := vars["name"]
|
||||||
|
if err := srv.ContainerResize(name, height, width); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := parseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -620,6 +643,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
|
||||||
"/containers/{name:.*}/start": postContainersStart,
|
"/containers/{name:.*}/start": postContainersStart,
|
||||||
"/containers/{name:.*}/stop": postContainersStop,
|
"/containers/{name:.*}/stop": postContainersStop,
|
||||||
"/containers/{name:.*}/wait": postContainersWait,
|
"/containers/{name:.*}/wait": postContainersWait,
|
||||||
|
"/containers/{name:.*}/resize": postContainersResize,
|
||||||
"/containers/{name:.*}/attach": postContainersAttach,
|
"/containers/{name:.*}/attach": postContainersAttach,
|
||||||
},
|
},
|
||||||
"DELETE": {
|
"DELETE": {
|
||||||
|
|
41
commands.go
41
commands.go
|
@ -15,10 +15,12 @@ import (
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
@ -69,7 +71,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.addr, cli.port)
|
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
|
||||||
for cmd, description := range map[string]string{
|
for cmd, description := range map[string]string{
|
||||||
"attach": "Attach to a running container",
|
"attach": "Attach to a running container",
|
||||||
"build": "Build a container from Dockerfile or via stdin",
|
"build": "Build a container from Dockerfile or via stdin",
|
||||||
|
@ -956,6 +958,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
v.Set("stdin", "1")
|
v.Set("stdin", "1")
|
||||||
|
|
||||||
|
cli.monitorTtySize(cmd.Arg(0))
|
||||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
|
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1143,6 +1146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||||
|
cli.monitorTtySize(out.Id)
|
||||||
if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
|
if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1197,7 +1201,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
||||||
params = bytes.NewBuffer(buf)
|
params = bytes.NewBuffer(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.addr, cli.port, API_VERSION, path), params)
|
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
|
@ -1229,7 +1233,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||||
if (method == "POST" || method == "PUT") && in == nil {
|
if (method == "POST" || method == "PUT") && in == nil {
|
||||||
in = bytes.NewReader([]byte{})
|
in = bytes.NewReader([]byte{})
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.addr, cli.port, API_VERSION, path), in)
|
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1265,7 +1269,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "plain/text")
|
req.Header.Set("Content-Type", "plain/text")
|
||||||
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.addr, cli.port))
|
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1310,6 +1314,33 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) resizeTty(id string) {
|
||||||
|
ws, err := term.GetWinsize(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
utils.Debugf("Error getting size: %s", err)
|
||||||
|
}
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("h", strconv.Itoa(int(ws.Height)))
|
||||||
|
v.Set("w", strconv.Itoa(int(ws.Width)))
|
||||||
|
if _, _, err := cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil); err != nil {
|
||||||
|
utils.Debugf("Error resize: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) monitorTtySize(id string) {
|
||||||
|
cli.resizeTty(id)
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGWINCH)
|
||||||
|
go func() {
|
||||||
|
for sig := range c {
|
||||||
|
if sig == syscall.SIGWINCH {
|
||||||
|
cli.resizeTty(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func Subcmd(name, signature, description string) *flag.FlagSet {
|
func Subcmd(name, signature, description string) *flag.FlagSet {
|
||||||
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||||
flags.Usage = func() {
|
flags.Usage = func() {
|
||||||
|
@ -1324,6 +1355,6 @@ func NewDockerCli(addr string, port int) *DockerCli {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockerCli struct {
|
type DockerCli struct {
|
||||||
addr string
|
host string
|
||||||
port int
|
port int
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/dotcloud/docker/term"
|
||||||
"github.com/dotcloud/docker/utils"
|
"github.com/dotcloud/docker/utils"
|
||||||
"github.com/kr/pty"
|
"github.com/kr/pty"
|
||||||
"io"
|
"io"
|
||||||
|
@ -754,6 +755,14 @@ func (container *Container) Wait() int {
|
||||||
return container.State.ExitCode
|
return container.State.ExitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (container *Container) Resize(h, w int) error {
|
||||||
|
pty, ok := container.ptyMaster.(*os.File)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("ptyMaster does not have Fd() method")
|
||||||
|
}
|
||||||
|
return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
|
||||||
|
}
|
||||||
|
|
||||||
func (container *Container) ExportRw() (Archive, error) {
|
func (container *Container) ExportRw() (Archive, error) {
|
||||||
return Tar(container.rwPath(), Uncompressed)
|
return Tar(container.rwPath(), Uncompressed)
|
||||||
}
|
}
|
||||||
|
|
|
@ -776,6 +776,13 @@ func (srv *Server) ContainerWait(name string) (int, error) {
|
||||||
return 0, fmt.Errorf("No such container: %s", name)
|
return 0, fmt.Errorf("No such container: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srv *Server) ContainerResize(name string, h, w int) error {
|
||||||
|
if container := srv.runtime.Get(name); container != nil {
|
||||||
|
return container.Resize(h, w)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("No such container: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error {
|
func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error {
|
||||||
container := srv.runtime.Get(name)
|
container := srv.runtime.Get(name)
|
||||||
if container == nil {
|
if container == nil {
|
||||||
|
|
22
term/term.go
22
term/term.go
|
@ -109,17 +109,35 @@ type State struct {
|
||||||
termios Termios
|
termios Termios
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Winsize struct {
|
||||||
|
Width uint16
|
||||||
|
Height uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
ws := &Winsize{}
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
func IsTerminal(fd int) bool {
|
func IsTerminal(fd int) bool {
|
||||||
var termios Termios
|
var termios Termios
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
|
||||||
return err == 0
|
return err == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
// previous state.
|
// previous state.
|
||||||
func Restore(fd int, state *State) error {
|
func Restore(fd int, state *State) error {
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue