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

Commit e775a2b

Browse files
authored
Merge pull request #79 from cdr/windows-tty
Adds support for windows
2 parents 27020b7 + 5a6b14e commit e775a2b

File tree

9 files changed

+252
-53
lines changed

9 files changed

+252
-53
lines changed

.github/workflows/test.yaml

Whitespace-only changes.

ci/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ build(){
3030
if [[ "$(uname)" == "Darwin" ]]; then
3131
GOOS=linux build
3232
CGO_ENABLED=1 GOOS=darwin build
33+
GOOS=windows GOARCH=386 build
3334
exit 0
3435
fi
3536

cmd/coder/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import (
66
_ "net/http/pprof"
77
"os"
88

9+
"cdr.dev/coder-cli/internal/xterminal"
910
"github.com/spf13/pflag"
1011

12+
"go.coder.com/flog"
13+
1114
"go.coder.com/cli"
1215
)
1316

@@ -48,5 +51,12 @@ func main() {
4851
log.Println(http.ListenAndServe("localhost:6060", nil))
4952
}()
5053
}
54+
55+
stdoutState, err := xterminal.MakeOutputRaw(os.Stdout.Fd())
56+
if err != nil {
57+
flog.Fatal("failed to set output to raw: %v", err)
58+
}
59+
defer xterminal.Restore(os.Stdout.Fd(), stdoutState)
60+
5161
cli.RunRoot(&rootCmd{})
5262
}

cmd/coder/shell.go

Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ import (
44
"context"
55
"io"
66
"os"
7-
"os/signal"
87
"strings"
98
"time"
109

1110
"github.com/spf13/pflag"
1211
"golang.org/x/crypto/ssh/terminal"
13-
"golang.org/x/sys/unix"
1412
"golang.org/x/time/rate"
1513
"golang.org/x/xerrors"
1614
"nhooyr.io/websocket"
@@ -19,6 +17,7 @@ import (
1917
"go.coder.com/flog"
2018

2119
"cdr.dev/coder-cli/internal/activity"
20+
"cdr.dev/coder-cli/internal/xterminal"
2221
"cdr.dev/wsep"
2322
)
2423

@@ -33,45 +32,8 @@ func (cmd *shellCmd) Spec() cli.CommandSpec {
3332
}
3433
}
3534

36-
func enableTerminal(fd int) (restore func(), err error) {
37-
state, err := terminal.MakeRaw(fd)
38-
if err != nil {
39-
return restore, xerrors.Errorf("make raw term: %w", err)
40-
}
41-
return func() {
42-
err := terminal.Restore(fd, state)
43-
if err != nil {
44-
flog.Error("restore term state: %v", err)
45-
}
46-
}, nil
47-
}
48-
49-
func sendResizeEvents(ctx context.Context, termfd int, process wsep.Process) {
50-
sigs := make(chan os.Signal, 16)
51-
signal.Notify(sigs, unix.SIGWINCH)
52-
53-
// Limit the frequency of resizes to prevent a stuttering effect.
54-
resizeLimiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 1)
55-
56-
for ctx.Err() == nil {
57-
if ctx.Err() != nil {
58-
return
59-
}
60-
width, height, err := terminal.GetSize(termfd)
61-
if err != nil {
62-
flog.Error("get term size: %v", err)
63-
return
64-
}
65-
66-
err = process.Resize(ctx, uint16(height), uint16(width))
67-
if err != nil {
68-
return
69-
}
70-
71-
// Do this last so the first resize is sent.
72-
<-sigs
73-
resizeLimiter.Wait(ctx)
74-
}
35+
type resizeEvent struct {
36+
height, width uint16
7537
}
7638

7739
func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
@@ -101,21 +63,40 @@ func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
10163
}
10264
}
10365

66+
func sendResizeEvents(ctx context.Context, termfd uintptr, process wsep.Process) {
67+
events := xterminal.ResizeEvents(ctx, termfd)
68+
69+
// Limit the frequency of resizes to prevent a stuttering effect.
70+
resizeLimiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 1)
71+
for {
72+
select {
73+
case newsize := <-events:
74+
err := process.Resize(ctx, newsize.Height, newsize.Width)
75+
if err != nil {
76+
return
77+
}
78+
_ = resizeLimiter.Wait(ctx)
79+
case <-ctx.Done():
80+
return
81+
}
82+
}
83+
}
84+
10485
func runCommand(ctx context.Context, envName string, command string, args []string) error {
10586
var (
10687
entClient = requireAuth()
10788
env = findEnv(entClient, envName)
10889
)
10990

110-
termfd := int(os.Stdin.Fd())
91+
termfd := os.Stdout.Fd()
11192

112-
tty := terminal.IsTerminal(termfd)
93+
tty := terminal.IsTerminal(int(termfd))
11394
if tty {
114-
restore, err := enableTerminal(termfd)
95+
stdinState, err := xterminal.MakeRaw(os.Stdin.Fd())
11596
if err != nil {
11697
return err
11798
}
118-
defer restore()
99+
defer xterminal.Restore(os.Stdin.Fd(), stdinState)
119100
}
120101

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

111+
var cmdEnv []string
112+
if tty {
113+
term := os.Getenv("TERM")
114+
if term == "" {
115+
term = "xterm"
116+
}
117+
cmdEnv = append(cmdEnv, "TERM="+term)
118+
}
119+
130120
execer := wsep.RemoteExecer(conn)
131121
process, err := execer.Start(ctx, wsep.Command{
132122
Command: command,
133123
Args: args,
134124
TTY: tty,
135125
Stdin: true,
136-
Env: []string{"TERM=" + os.Getenv("TERM")},
126+
Env: cmdEnv,
137127
})
138128
if err != nil {
139129
return err

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module cdr.dev/coder-cli
33
go 1.14
44

55
require (
6-
cdr.dev/wsep v0.0.0-20200616212001-0613cfe9a4ac
6+
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f
77
github.com/fatih/color v1.9.0 // indirect
88
github.com/gorilla/websocket v1.4.1
99
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f

go.sum

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ=
22
cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
3-
cdr.dev/wsep v0.0.0-20200615020153-e2b1c576fc40 h1:f369880iSAZ3cXwvbdc9WIyy3FZ4yanusYZjaVHeis4=
4-
cdr.dev/wsep v0.0.0-20200615020153-e2b1c576fc40/go.mod h1:2VKClUml3gfmLez0gBxTJIjSKszpQotc2ZqPdApfK/Y=
5-
cdr.dev/wsep v0.0.0-20200616212001-0613cfe9a4ac h1:rl4O0qfxgNRWBUe5gQu4of2cdsclcpjGYmLQhSCHX7c=
6-
cdr.dev/wsep v0.0.0-20200616212001-0613cfe9a4ac/go.mod h1:2VKClUml3gfmLez0gBxTJIjSKszpQotc2ZqPdApfK/Y=
3+
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f h1:WnTUINBwXE11xjp5nTVt+H2qB2/KEymos1jKMcppG9U=
4+
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f/go.mod h1:2VKClUml3gfmLez0gBxTJIjSKszpQotc2ZqPdApfK/Y=
75
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
86
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
97
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -240,8 +238,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
240238
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
241239
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
242240
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
243-
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
244-
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
245241
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
246242
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
247243
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

internal/xterminal/doc.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Package xterminal provides functions to change termios or console attributes
2+
// and restore them later on. It supports Unix and Windows.
3+
//
4+
// This does the same thing as x/crypto/ssh/terminal on Linux. On Windows, it
5+
// sets the same console modes as the terminal package but also sets
6+
// `ENABLE_VIRTUAL_TERMINAL_INPUT` and `ENABLE_VIRTUAL_TERMINAL_PROCESSING` to
7+
// allow for VT100 sequences in the console. This is important, otherwise Linux
8+
// apps (with colors or ncurses) that are run through SSH or wsep get
9+
// garbled in a Windows console.
10+
//
11+
// More details can be found out about Windows console modes here:
12+
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
13+
package xterminal

internal/xterminal/terminal.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// +build !windows
2+
3+
package xterminal
4+
5+
import (
6+
"context"
7+
"os"
8+
"os/signal"
9+
10+
"golang.org/x/crypto/ssh/terminal"
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
// State differs per-platform.
15+
type State struct {
16+
s *terminal.State
17+
}
18+
19+
// MakeRaw sets the terminal to raw.
20+
func MakeRaw(fd uintptr) (*State, error) {
21+
s, err := terminal.MakeRaw(int(fd))
22+
return &State{s}, err
23+
}
24+
25+
// MakeOutputRaw does nothing on non-Windows platforms.
26+
func MakeOutputRaw(fd uintptr) (*State, error) {
27+
return nil, nil
28+
}
29+
30+
// Restore terminal back to original state.
31+
func Restore(fd uintptr, state *State) error {
32+
if state == nil {
33+
return nil
34+
}
35+
36+
return terminal.Restore(int(fd), state.s)
37+
}
38+
39+
// ColorEnabled returns true on Linux if handle is a terminal.
40+
func ColorEnabled(fd uintptr) (bool, error) {
41+
return terminal.IsTerminal(int(fd)), nil
42+
}
43+
44+
type ResizeEvent struct {
45+
Height, Width uint16
46+
}
47+
48+
// ResizeEvents sends terminal resize events
49+
func ResizeEvents(ctx context.Context, termfd uintptr) chan ResizeEvent {
50+
sigs := make(chan os.Signal, 16)
51+
signal.Notify(sigs, unix.SIGWINCH)
52+
53+
events := make(chan ResizeEvent)
54+
55+
go func() {
56+
for ctx.Err() == nil {
57+
width, height, err := terminal.GetSize(int(termfd))
58+
if err != nil {
59+
return
60+
}
61+
events <- ResizeEvent{
62+
Height: uint16(height),
63+
Width: uint16(width),
64+
}
65+
66+
<-sigs
67+
}
68+
}()
69+
70+
return events
71+
}

0 commit comments

Comments
 (0)