diff --git a/experimental/libbox/native_shell_session.go b/experimental/libbox/native_shell_session.go index 82fec5f3..d9bd5d29 100644 --- a/experimental/libbox/native_shell_session.go +++ b/experimental/libbox/native_shell_session.go @@ -25,7 +25,7 @@ func OpenNativeShellSession( cwd, int(uid), int(gid), common.Map(iteratorToArray[int32](groups), func(g int32) int { return int(g) }), - uint16(rows), uint16(cols), + uint16(rows), uint16(cols), 0, 0, ) if err != nil { return nil, err @@ -58,7 +58,7 @@ func (s *nativeShellSession) MasterFD() int32 { } func (s *nativeShellSession) Resize(rows, cols int32) error { - return s.shell.Resize(uint16(rows), uint16(cols)) + return s.shell.Resize(uint16(rows), uint16(cols), 0, 0) } func (s *nativeShellSession) Signal(sig int32) error { diff --git a/protocol/tailscale/tailssh/server.go b/protocol/tailscale/tailssh/server.go index 27dc9d25..476f06b7 100644 --- a/protocol/tailscale/tailssh/server.go +++ b/protocol/tailscale/tailssh/server.go @@ -582,11 +582,13 @@ func (s *Server) handleSession(session gliderssh.Session) { session.DisablePTYEmulation() command := session.RawCommand() var term string - var rows, cols uint16 + var rows, cols, widthPixels, heightPixels uint16 if isPty { term = ptyReq.Term rows = clampWindowDimension(ptyReq.Window.Height) cols = clampWindowDimension(ptyReq.Window.Width) + widthPixels = clampWindowDimension(ptyReq.Window.WidthPixels) + heightPixels = clampWindowDimension(ptyReq.Window.HeightPixels) } var rec *recording recorderList, onFailure := recorders(connInfo) @@ -611,12 +613,14 @@ func (s *Server) handleSession(session gliderssh.Session) { } } shellSession, err := s.backend.OpenSession(shellRequest{ - User: localUser, - Command: command, - Env: env, - Term: term, - Rows: rows, - Cols: cols, + User: localUser, + Command: command, + Env: env, + Term: term, + Rows: rows, + Cols: cols, + WidthPixels: widthPixels, + HeightPixels: heightPixels, }) if err != nil { s.logger.Error("failed to open shell session: ", err) @@ -661,7 +665,12 @@ func (s *Server) handleSession(session gliderssh.Session) { for win := range winCh { shellAccess.Lock() if shellAlive { - shellSession.Resize(clampWindowDimension(win.Height), clampWindowDimension(win.Width)) + shellSession.Resize( + clampWindowDimension(win.Height), + clampWindowDimension(win.Width), + clampWindowDimension(win.WidthPixels), + clampWindowDimension(win.HeightPixels), + ) } shellAccess.Unlock() } diff --git a/protocol/tailscale/tailssh/session.go b/protocol/tailscale/tailssh/session.go index c57d8467..9afe45f2 100644 --- a/protocol/tailscale/tailssh/session.go +++ b/protocol/tailscale/tailssh/session.go @@ -14,12 +14,14 @@ type shellBackend interface { } type shellRequest struct { - User *adapter.PlatformUser - Command string - Env []string - Term string - Rows uint16 - Cols uint16 + User *adapter.PlatformUser + Command string + Env []string + Term string + Rows uint16 + Cols uint16 + WidthPixels uint16 + HeightPixels uint16 } type shellSession interface { @@ -27,7 +29,7 @@ type shellSession interface { // CloseWrite signals EOF on the child's stdin without tearing down the // session, so programs that read stdin to EOF can finish normally. CloseWrite() error - Resize(rows, cols uint16) error + Resize(rows, cols, widthPixels, heightPixels uint16) error Signal(sig int) error Wait() (exitStatus uint32, err error) } diff --git a/protocol/tailscale/tailssh/session_platform.go b/protocol/tailscale/tailssh/session_platform.go index 5d5c5b77..b613362c 100644 --- a/protocol/tailscale/tailssh/session_platform.go +++ b/protocol/tailscale/tailssh/session_platform.go @@ -25,11 +25,15 @@ func (b *platformShellBackend) OpenSession(request shellRequest) (shellSession, return nil, err } master := os.NewFile(uintptr(dupFd), "pty-master") - return &platformShellSession{ + shellSession := &platformShellSession{ session: session, master: master, isPty: request.Term != "", - }, nil + } + if shellSession.isPty && (request.WidthPixels > 0 || request.HeightPixels > 0) { + _ = SetWinsize(int(master.Fd()), request.Rows, request.Cols, request.WidthPixels, request.HeightPixels) + } + return shellSession, nil } func (b *platformShellBackend) Close() error { @@ -61,8 +65,17 @@ func (s *platformShellSession) CloseWrite() error { return syscall.Shutdown(int(s.master.Fd()), syscall.SHUT_WR) } -func (s *platformShellSession) Resize(rows, cols uint16) error { - return s.session.Resize(int32(rows), int32(cols)) +func (s *platformShellSession) Resize(rows, cols, widthPixels, heightPixels uint16) error { + err := s.session.Resize(int32(rows), int32(cols)) + if err != nil { + return err + } + // The platform interface carries no pixel dimensions; set them directly + // on the duplicated pty master. + if s.isPty && (widthPixels > 0 || heightPixels > 0) { + return SetWinsize(int(s.master.Fd()), rows, cols, widthPixels, heightPixels) + } + return nil } func (s *platformShellSession) Signal(sig int) error { diff --git a/protocol/tailscale/tailssh/session_unix.go b/protocol/tailscale/tailssh/session_unix.go index 7b98f58e..dc9d5be7 100644 --- a/protocol/tailscale/tailssh/session_unix.go +++ b/protocol/tailscale/tailssh/session_unix.go @@ -45,7 +45,7 @@ func (b *directShellBackend) OpenSession(request shellRequest) (shellSession, er args = []string{"-" + filepath.Base(shell)} } if request.Term != "" { - return OpenPtyShell(shell, args, request.Env, request.User.HomeDir, request.User.Uid, request.User.Gid, request.User.Groups, request.Rows, request.Cols) + return OpenPtyShell(shell, args, request.Env, request.User.HomeDir, request.User.Uid, request.User.Gid, request.User.Groups, request.Rows, request.Cols, request.WidthPixels, request.HeightPixels) } return OpenSocketpairShell(shell, args, request.Env, request.User.HomeDir, request.User.Uid, request.User.Gid, request.User.Groups) } diff --git a/protocol/tailscale/tailssh/session_windows.go b/protocol/tailscale/tailssh/session_windows.go index 0b6db19c..4d07f729 100644 --- a/protocol/tailscale/tailssh/session_windows.go +++ b/protocol/tailscale/tailssh/session_windows.go @@ -325,7 +325,7 @@ func (s *conptyShellSession) Write(p []byte) (int, error) { return s.input.Write(p) } -func (s *conptyShellSession) Resize(rows, cols uint16) error { +func (s *conptyShellSession) Resize(rows, cols, _, _ uint16) error { return s.console.Resize(windows.Coord{X: clampConsoleDimension(cols), Y: clampConsoleDimension(rows)}) } @@ -451,7 +451,7 @@ func (s *pipeShellSession) Write(p []byte) (int, error) { return s.stdin.Write(p) } -func (s *pipeShellSession) Resize(_, _ uint16) error { +func (s *pipeShellSession) Resize(_, _, _, _ uint16) error { return nil } diff --git a/protocol/tailscale/tailssh/shell_unix.go b/protocol/tailscale/tailssh/shell_unix.go index 254786f9..774f2dec 100644 --- a/protocol/tailscale/tailssh/shell_unix.go +++ b/protocol/tailscale/tailssh/shell_unix.go @@ -13,8 +13,8 @@ type Shell struct { isPty bool } -func OpenPtyShell(shell string, args, env []string, dir string, uid, gid int, groups []int, rows, cols uint16) (*Shell, error) { - master, process, err := StartPtyProcess(shell, args, env, dir, uid, gid, groups, rows, cols) +func OpenPtyShell(shell string, args, env []string, dir string, uid, gid int, groups []int, rows, cols, widthPixels, heightPixels uint16) (*Shell, error) { + master, process, err := StartPtyProcess(shell, args, env, dir, uid, gid, groups, rows, cols, widthPixels, heightPixels) if err != nil { return nil, err } @@ -52,11 +52,11 @@ func (s *Shell) Write(p []byte) (int, error) { return s.master.Write(p) } -func (s *Shell) Resize(rows, cols uint16) error { +func (s *Shell) Resize(rows, cols, widthPixels, heightPixels uint16) error { if !s.isPty { return nil } - return SetWinsize(int(s.master.Fd()), rows, cols) + return SetWinsize(int(s.master.Fd()), rows, cols, widthPixels, heightPixels) } func (s *Shell) Signal(sig int) error { diff --git a/protocol/tailscale/tailssh/subprocess_unix.go b/protocol/tailscale/tailssh/subprocess_unix.go index 09e6b6bd..6b102975 100644 --- a/protocol/tailscale/tailssh/subprocess_unix.go +++ b/protocol/tailscale/tailssh/subprocess_unix.go @@ -14,7 +14,7 @@ import ( "golang.org/x/sys/unix" ) -func StartPtyProcess(shell string, args, env []string, dir string, uid, gid int, groups []int, rows, cols uint16) (*os.File, *os.Process, error) { +func StartPtyProcess(shell string, args, env []string, dir string, uid, gid int, groups []int, rows, cols, widthPixels, heightPixels uint16) (*os.File, *os.Process, error) { cmd := exec.Command(shell) cmd.Args = args cmd.Dir = dir @@ -27,7 +27,7 @@ func StartPtyProcess(shell string, args, env []string, dir string, uid, gid int, setCredential(attrs, uid, gid, groups) var size *pty.Winsize if rows > 0 && cols > 0 { - size = &pty.Winsize{Rows: rows, Cols: cols} + size = &pty.Winsize{Rows: rows, Cols: cols, X: widthPixels, Y: heightPixels} } master, err := pty.StartWithAttrs(cmd, size, attrs) if err != nil { @@ -94,6 +94,11 @@ func setCredential(attr *syscall.SysProcAttr, uid, gid int, groups []int) { attr.Credential = cred } -func SetWinsize(fd int, rows, cols uint16) error { - return unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Row: rows, Col: cols}) +func SetWinsize(fd int, rows, cols, widthPixels, heightPixels uint16) error { + return unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{ + Row: rows, + Col: cols, + Xpixel: widthPixels, + Ypixel: heightPixels, + }) }