Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Adds support for windows #79

Merged
merged 3 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Abstract cross-platform terminal syscalls
Change-Id: I409e93a6298d6c45ee96d47e65e4abef6e4fe503
  • Loading branch information
cmoog committed Jul 28, 2020
commit 6fc2c8ccaa66e128a0bc5932cb965643724b2ade
39 changes: 0 additions & 39 deletions cmd/coder/resize_unix.go

This file was deleted.

46 changes: 0 additions & 46 deletions cmd/coder/resize_windows.go

This file was deleted.

84 changes: 40 additions & 44 deletions cmd/coder/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"go.coder.com/flog"

"cdr.dev/coder-cli/internal/activity"
"cdr.dev/coder-cli/internal/xterminal"
"cdr.dev/wsep"
)

Expand All @@ -31,49 +32,10 @@ func (cmd *shellCmd) Spec() cli.CommandSpec {
}
}

func enableTerminal(fd int) (restore func(), err error) {
state, err := terminal.MakeRaw(fd)
if err != nil {
return restore, xerrors.Errorf("make raw term: %w", err)
}
return func() {
err := terminal.Restore(fd, state)
if err != nil {
flog.Error("restore term state: %v", err)
}
}, nil
}

type resizeEvent struct {
height, width uint16
}

func (s resizeEvent) equal(s2 *resizeEvent) bool {
if s2 == nil {
return false
}
return s.height == s2.height && s.width == s2.width
}

func sendResizeEvents(ctx context.Context, termfd int, process wsep.Process) {
events := resizeEvents(ctx, termfd)

// Limit the frequency of resizes to prevent a stuttering effect.
resizeLimiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 1)
for {
select {
case newsize := <-events:
err := process.Resize(ctx, newsize.height, newsize.width)
if err != nil {
return
}
_ = resizeLimiter.Wait(ctx)
case <-ctx.Done():
return
}
}
}

func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
if len(fl.Args()) < 1 {
exitUsage(fl)
Expand Down Expand Up @@ -101,21 +63,46 @@ func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
}
}

func sendResizeEvents(ctx context.Context, termfd uintptr, process wsep.Process) {
events := xterminal.ResizeEvents(ctx, termfd)

// Limit the frequency of resizes to prevent a stuttering effect.
resizeLimiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 1)
for {
select {
case newsize := <-events:
err := process.Resize(ctx, newsize.Height, newsize.Width)
if err != nil {
return
}
_ = resizeLimiter.Wait(ctx)
case <-ctx.Done():
return
}
}
}

func runCommand(ctx context.Context, envName string, command string, args []string) error {
var (
entClient = requireAuth()
env = findEnv(entClient, envName)
)

termfd := int(os.Stdin.Fd())
termfd := os.Stdout.Fd()

tty := terminal.IsTerminal(termfd)
tty := terminal.IsTerminal(int(termfd))
if tty {
restore, err := enableTerminal(termfd)
stdinState, err := xterminal.MakeRaw(os.Stdin.Fd())
if err != nil {
return err
}
defer xterminal.Restore(os.Stdin.Fd(), stdinState)

stdoutState, err := xterminal.MakeOutputRaw(os.Stdout.Fd())
if err != nil {
return err
}
defer restore()
defer xterminal.Restore(os.Stdout.Fd(), stdoutState)
}

ctx, cancel := context.WithCancel(ctx)
Expand All @@ -127,13 +114,22 @@ func runCommand(ctx context.Context, envName string, command string, args []stri
}
go heartbeat(ctx, conn, 15*time.Second)

var cmdEnv []string
if tty {
term := os.Getenv("TERM")
if term == "" {
term = "xterm"
}
cmdEnv = append(cmdEnv, "TERM="+term)
}

execer := wsep.RemoteExecer(conn)
process, err := execer.Start(ctx, wsep.Command{
Command: command,
Args: args,
TTY: tty,
Stdin: true,
Env: []string{"TERM=" + os.Getenv("TERM")},
Env: cmdEnv,
})
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module cdr.dev/coder-cli
go 1.14

require (
cdr.dev/wsep v0.0.0-20200727194627-13ef1f8df965
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f
github.com/fatih/color v1.9.0 // indirect
github.com/gorilla/websocket v1.4.1
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ=
cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
cdr.dev/wsep v0.0.0-20200727194627-13ef1f8df965 h1:zFpraxgmcgX60oWFs8r1moWFMJ0x945t4moxNb6fjJ8=
cdr.dev/wsep v0.0.0-20200727194627-13ef1f8df965/go.mod h1:N20HJdMn6q9NG7sjxL4uYdBQBGOf8/6psfdMyuJnYw8=
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f h1:WnTUINBwXE11xjp5nTVt+H2qB2/KEymos1jKMcppG9U=
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f/go.mod h1:2VKClUml3gfmLez0gBxTJIjSKszpQotc2ZqPdApfK/Y=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
Expand Down Expand Up @@ -107,8 +107,6 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/iamacarpet/go-winpty v1.0.2 h1:jwPVTYrjAHZx6Mcm6K5i9G4opMp5TblEHH5EQCl/Gzw=
github.com/iamacarpet/go-winpty v1.0.2/go.mod h1:/GHKJicG/EVRQIK1IQikMYBakBkhj/3hTjLgdzYsmpI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down
13 changes: 13 additions & 0 deletions internal/xterminal/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Package xterminal provides functions to change termios or console attributes
// and restore them later on. It supports Unix and Windows.
//
// This does the same thing as x/crypto/ssh/terminal on Linux. On Windows, it
// sets the same console modes as the terminal package but also sets
// `ENABLE_VIRTUAL_TERMINAL_INPUT` and `ENABLE_VIRTUAL_TERMINAL_PROCESSING` to
// allow for VT100 sequences in the console. This is important, otherwise Linux
// apps (with colors or ncurses) that are run through SSH or wsep get
// garbled in a Windows console.
//
// More details can be found out about Windows console modes here:
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
package xterminal
71 changes: 71 additions & 0 deletions internal/xterminal/terminal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// +build !windows

package xterminal

import (
"context"
"os"
"os/signal"

"golang.org/x/crypto/ssh/terminal"
"golang.org/x/sys/unix"
)

// State differs per-platform.
type State struct {
s *terminal.State
}

// MakeRaw sets the terminal to raw.
func MakeRaw(fd uintptr) (*State, error) {
s, err := terminal.MakeRaw(int(fd))
return &State{s}, err
}

// MakeOutputRaw does nothing on non-Windows platforms.
func MakeOutputRaw(fd uintptr) (*State, error) {
return nil, nil
}

// Restore terminal back to original state.
func Restore(fd uintptr, state *State) error {
if state == nil {
return nil
}

return terminal.Restore(int(fd), state.s)
}

// ColorEnabled returns true on Linux if handle is a terminal.
func ColorEnabled(fd uintptr) (bool, error) {
return terminal.IsTerminal(int(fd)), nil
}

type ResizeEvent struct {
Height, Width uint16
}

// ResizeEvents sends terminal resize events
func ResizeEvents(ctx context.Context, termfd uintptr) chan ResizeEvent {
sigs := make(chan os.Signal, 16)
signal.Notify(sigs, unix.SIGWINCH)

events := make(chan ResizeEvent)

go func() {
for ctx.Err() == nil {
width, height, err := terminal.GetSize(int(termfd))
if err != nil {
return
}
events <- ResizeEvent{
Height: uint16(height),
Width: uint16(width),
}

<-sigs
}
}()

return events
}
Loading