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

Commit 1963400

Browse files
committed
Attempt to add resizing
1 parent 786cd0b commit 1963400

File tree

5 files changed

+86
-24
lines changed

5 files changed

+86
-24
lines changed

Diff for: cmd/coder/shell.go

+35-9
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import (
44
"context"
55
"io"
66
"os"
7-
"os/exec"
7+
"os/signal"
88

9-
"github.com/mattn/go-isatty"
109
"github.com/spf13/pflag"
1110
"go.coder.com/cli"
1211
"go.coder.com/flog"
12+
"golang.org/x/crypto/ssh/terminal"
13+
"golang.org/x/sys/unix"
1314

1415
client "cdr.dev/coder-cli/internal/entclient"
1516
"cdr.dev/coder-cli/wush"
@@ -27,15 +28,34 @@ func (cmd *shellCmd) Spec() cli.CommandSpec {
2728
}
2829
}
2930

30-
func enableTerminal() {
31-
out, err := exec.Command("stty", "-f", "/dev/tty",
32-
"raw",
33-
).CombinedOutput()
31+
func enableTerminal(fd int) {
32+
_, err := terminal.MakeRaw(fd)
3433
if err != nil {
35-
flog.Fatal("configure tty: %v %q", err, out)
34+
flog.Fatal("make raw term: %v", err)
3635
}
3736
}
3837

38+
func (cmd *shellCmd) sendResizeEvents(termfd int, client *wush.Client) {
39+
sigs := make(chan os.Signal, 16)
40+
signal.Notify(sigs, unix.SIGWINCH)
41+
42+
for {
43+
width, height, err := terminal.GetSize(termfd)
44+
if err != nil {
45+
flog.Error("get term size: %v", err)
46+
return
47+
}
48+
49+
err = client.Resize(width, height)
50+
if err != nil {
51+
flog.Error("get term size: %v", err)
52+
return
53+
}
54+
// Do this last so the first resize is sent.
55+
<-sigs
56+
}
57+
}
58+
3959
func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
4060
if len(fl.Args()) < 2 {
4161
exitUsage(fl)
@@ -51,9 +71,11 @@ func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
5171
env = findEnv(entClient, envName)
5272
)
5373

54-
tty := isatty.IsTerminal(os.Stdout.Fd())
74+
termfd := int(os.Stdout.Fd())
75+
76+
tty := terminal.IsTerminal(termfd)
5577
if tty {
56-
enableTerminal()
78+
enableTerminal(termfd)
5779
}
5880

5981
conn, err := entClient.DialWush(
@@ -68,6 +90,10 @@ func (cmd *shellCmd) Run(fl *pflag.FlagSet) {
6890
ctx := context.Background()
6991

7092
wc := wush.NewClient(ctx, conn)
93+
if tty {
94+
go cmd.sendResizeEvents(termfd, wc)
95+
}
96+
7197
go io.Copy(wc.Stdin, os.Stdin)
7298
go io.Copy(os.Stdout, wc.Stdout)
7399
go io.Copy(os.Stderr, wc.Stderr)

Diff for: go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ require (
1313
github.com/spf13/pflag v1.0.5
1414
go.coder.com/cli v0.4.0
1515
go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512
16+
golang.org/x/crypto v0.0.0-20200422194213-44a606286825
1617
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
17-
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
18+
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4
1819
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
1920
nhooyr.io/websocket v1.8.5
2021
)

Diff for: go.sum

+7
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,22 @@ go.coder.com/cli v0.4.0 h1:PruDGwm/CPFndyK/eMowZG3vzg5CgohRWeXWCTr3zi8=
4343
go.coder.com/cli v0.4.0/go.mod h1:hRTOURCR3LJF1FRW9arecgrzX+AHG7mfYMwThPIgq+w=
4444
go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512 h1:DjCS6dRQh+1PlfiBmnabxfdrzenb0tAwJqFxDEH/s9g=
4545
go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512/go.mod h1:83JsYgXYv0EOaXjIMnaZ1Fl6ddNB3fJnDZ/8845mUJ8=
46+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
47+
golang.org/x/crypto v0.0.0-20200422194213-44a606286825 h1:dSChiwOTvzwbHFTMq2l6uRardHH7/E6SqEkqccinS/o=
48+
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
49+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4650
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
4751
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
4852
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
53+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
4954
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
55+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5056
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5157
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5258
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5359
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
5460
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
61+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
5562
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
5663
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
5764
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

Diff for: wush/client.go

+23-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/base64"
66
"fmt"
77
"io"
8-
"sync"
98

109
"golang.org/x/sync/errgroup"
1110
"golang.org/x/xerrors"
@@ -15,20 +14,20 @@ import (
1514

1615
// Client converts a Wush connection into OS streams.
1716
type Client struct {
18-
done <-chan struct{}
19-
statusMu sync.Mutex
17+
statusPromise promise
2018
exitCode uint8
2119
err error
2220

21+
conn *websocket.Conn
22+
ctx context.Context
23+
2324
Stdin io.WriteCloser
2425
Stdout io.Reader
2526
Stderr io.Reader
2627
}
2728

2829
type stdinWriter struct {
29-
conn *websocket.Conn
3030
*Client
31-
ctx context.Context
3231
}
3332

3433
func (w *stdinWriter) writeChunk(p []byte) (int, error) {
@@ -69,6 +68,15 @@ func (w *stdinWriter) Close() error {
6968
})
7069
}
7170

71+
func (c *Client) Resize(width, height int) error {
72+
err := wsjson.Write(c.ctx, c.conn, &ClientMessage{
73+
Type: Stdin,
74+
Height: height,
75+
Width: width,
76+
})
77+
return err
78+
}
79+
7280
// NewClient begins multiplexing the Wush connection
7381
// into independent streams.
7482
// It will cancel all goroutines when the provided context cancels.
@@ -77,19 +85,21 @@ func NewClient(ctx context.Context, conn *websocket.Conn) *Client {
7785
stdoutReader, stdoutWriter = io.Pipe()
7886
stderrReader, stderrWriter = io.Pipe()
7987
)
80-
done := make(chan struct{})
88+
89+
eg, ctx := errgroup.WithContext(ctx)
90+
8191
c := &Client{
8292
Stdout: stdoutReader,
8393
Stderr: stderrReader,
84-
done: done,
94+
conn: conn,
95+
ctx: ctx,
96+
statusPromise: newPromise(),
8597
}
86-
eg, ctx := errgroup.WithContext(ctx)
8798
c.Stdin = &stdinWriter{
8899
Client: c,
89-
conn: conn,
90-
ctx: ctx,
91100
}
92101

102+
93103
// We expect massive reads from some commands. Because we're streaming it's no big deal.
94104
conn.SetReadLimit(1 << 40)
95105

@@ -137,16 +147,15 @@ func NewClient(ctx context.Context, conn *websocket.Conn) *Client {
137147
})
138148
// Cleanup routine
139149
go func() {
150+
defer c.statusPromise.Release()
151+
140152
err := eg.Wait()
141-
c.statusMu.Lock()
142-
defer c.statusMu.Unlock()
143153
// If the command failed before exit code, don't block.
144154
select {
145155
case c.exitCode = <-exitCode:
146156
default:
147157
}
148158
c.err = err
149-
close(done)
150159
}()
151160

152161
return c
@@ -155,7 +164,7 @@ func NewClient(ctx context.Context, conn *websocket.Conn) *Client {
155164
// Wait returns the status code of the command, along
156165
// with any error.
157166
func (c *Client) Wait() (uint8, error) {
158-
<-c.done
167+
c.statusPromise.Wait()
159168
// There is guaranteed to be no writers after the channel is closed.
160169
return c.exitCode, c.err
161170
}

Diff for: wush/promise.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package wush
2+
3+
type promise struct {
4+
wait chan struct{}
5+
}
6+
7+
func newPromise() promise {
8+
return promise{wait: make(chan struct{})}
9+
}
10+
11+
// Wait may be called any number of times by value recievers.
12+
func (o promise) Wait() {
13+
<-o.wait
14+
}
15+
16+
// Release may only be called once from the value setter.
17+
func (o promise) Release() {
18+
close(o.wait)
19+
}

0 commit comments

Comments
 (0)