Skip to content

Commit d5c83ad

Browse files
authored
feature: Added raw terminal support in monitor command (#2291)
* Removed useless tty abstraction * Print 'Connected to...' message immediatly after connection * Added terminal raw mode * Make local the argument/flags of 'monitor' command * Applied code suggestion
1 parent 0054738 commit d5c83ad

File tree

3 files changed

+77
-74
lines changed

3 files changed

+77
-74
lines changed

Diff for: internal/cli/feedback/terminal.go

+22
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ func InteractiveStreams() (io.Reader, io.Writer, error) {
4141
return os.Stdin, stdOut, nil
4242
}
4343

44+
var oldStateStdin *term.State
45+
46+
// SetRawModeStdin sets the stdin stream in RAW mode (no buffering, echo disabled,
47+
// no terminal escape codes nor signals interpreted)
48+
func SetRawModeStdin() {
49+
if oldStateStdin != nil {
50+
panic("terminal already in RAW mode")
51+
}
52+
oldStateStdin, _ = term.MakeRaw(int(os.Stdin.Fd()))
53+
}
54+
55+
// RestoreModeStdin restore the terminal settings to the normal non-RAW state. This
56+
// function must be called after SetRawModeStdin to not leave the terminal in an
57+
// undefined state.
58+
func RestoreModeStdin() {
59+
if oldStateStdin == nil {
60+
return
61+
}
62+
_ = term.Restore(int(os.Stdin.Fd()), oldStateStdin)
63+
oldStateStdin = nil
64+
}
65+
4466
func isTerminal() bool {
4567
return term.IsTerminal(int(os.Stdin.Fd()))
4668
}

Diff for: internal/cli/monitor/monitor.go

+55-23
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package monitor
1717

1818
import (
19+
"bytes"
1920
"context"
2021
"errors"
2122
"fmt"
@@ -35,29 +36,34 @@ import (
3536
"github.com/fatih/color"
3637
"github.com/sirupsen/logrus"
3738
"github.com/spf13/cobra"
39+
"go.bug.st/cleanup"
3840
)
3941

40-
var (
41-
portArgs arguments.Port
42-
describe bool
43-
configs []string
44-
quiet bool
45-
fqbn arguments.Fqbn
46-
tr = i18n.Tr
47-
)
42+
var tr = i18n.Tr
4843

4944
// NewCommand created a new `monitor` command
5045
func NewCommand() *cobra.Command {
46+
var (
47+
raw bool
48+
portArgs arguments.Port
49+
describe bool
50+
configs []string
51+
quiet bool
52+
fqbn arguments.Fqbn
53+
)
5154
monitorCommand := &cobra.Command{
5255
Use: "monitor",
5356
Short: tr("Open a communication port with a board."),
5457
Long: tr("Open a communication port with a board."),
5558
Example: "" +
5659
" " + os.Args[0] + " monitor -p /dev/ttyACM0\n" +
5760
" " + os.Args[0] + " monitor -p /dev/ttyACM0 --describe",
58-
Run: runMonitorCmd,
61+
Run: func(cmd *cobra.Command, args []string) {
62+
runMonitorCmd(&portArgs, &fqbn, configs, describe, quiet, raw)
63+
},
5964
}
6065
portArgs.AddToCommand(monitorCommand)
66+
monitorCommand.Flags().BoolVar(&raw, "raw", false, tr("Set terminal in raw mode (unbuffered)."))
6167
monitorCommand.Flags().BoolVar(&describe, "describe", false, tr("Show all the settings of the communication port."))
6268
monitorCommand.Flags().StringSliceVarP(&configs, "config", "c", []string{}, tr("Configure communication port settings. The format is <ID>=<value>[,<ID>=<value>]..."))
6369
monitorCommand.Flags().BoolVarP(&quiet, "quiet", "q", false, tr("Run in silent mode, show only monitor input and output."))
@@ -66,7 +72,7 @@ func NewCommand() *cobra.Command {
6672
return monitorCommand
6773
}
6874

69-
func runMonitorCmd(cmd *cobra.Command, args []string) {
75+
func runMonitorCmd(portArgs *arguments.Port, fqbn *arguments.Fqbn, configs []string, describe, quiet, raw bool) {
7076
instance := instance.CreateAndInit()
7177
logrus.Info("Executing `arduino-cli monitor`")
7278

@@ -93,12 +99,6 @@ func runMonitorCmd(cmd *cobra.Command, args []string) {
9399
return
94100
}
95101

96-
tty, err := newStdInOutTerminal()
97-
if err != nil {
98-
feedback.FatalError(err, feedback.ErrGeneric)
99-
}
100-
defer tty.Close()
101-
102102
configuration := &rpc.MonitorPortConfiguration{}
103103
if len(configs) > 0 {
104104
for _, config := range configs {
@@ -151,9 +151,33 @@ func runMonitorCmd(cmd *cobra.Command, args []string) {
151151
}
152152
defer portProxy.Close()
153153

154-
ctx, cancel := context.WithCancel(context.Background())
154+
if !quiet {
155+
feedback.Print(tr("Connected to %s! Press CTRL-C to exit.", portAddress))
156+
}
157+
158+
ttyIn, ttyOut, err := feedback.InteractiveStreams()
159+
if err != nil {
160+
feedback.FatalError(err, feedback.ErrGeneric)
161+
}
162+
163+
ctx, cancel := cleanup.InterruptableContext(context.Background())
164+
if raw {
165+
feedback.SetRawModeStdin()
166+
defer func() {
167+
feedback.RestoreModeStdin()
168+
}()
169+
170+
// In RAW mode CTRL-C is not converted into an Interrupt by
171+
// the terminal, we must intercept ASCII 3 (CTRL-C) on our own...
172+
ctrlCDetector := &charDetectorWriter{
173+
callback: cancel,
174+
detectedChar: 3, // CTRL-C
175+
}
176+
ttyIn = io.TeeReader(ttyIn, ctrlCDetector)
177+
}
178+
155179
go func() {
156-
_, err := io.Copy(tty, portProxy)
180+
_, err := io.Copy(ttyOut, portProxy)
157181
if err != nil && !errors.Is(err, io.EOF) {
158182
if !quiet {
159183
feedback.Print(tr("Port closed: %v", err))
@@ -162,7 +186,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) {
162186
cancel()
163187
}()
164188
go func() {
165-
_, err := io.Copy(portProxy, tty)
189+
_, err := io.Copy(portProxy, ttyIn)
166190
if err != nil && !errors.Is(err, io.EOF) {
167191
if !quiet {
168192
feedback.Print(tr("Port closed: %v", err))
@@ -171,14 +195,22 @@ func runMonitorCmd(cmd *cobra.Command, args []string) {
171195
cancel()
172196
}()
173197

174-
if !quiet {
175-
feedback.Print(tr("Connected to %s! Press CTRL-C to exit.", portAddress))
176-
}
177-
178198
// Wait for port closed
179199
<-ctx.Done()
180200
}
181201

202+
type charDetectorWriter struct {
203+
callback func()
204+
detectedChar byte
205+
}
206+
207+
func (cd *charDetectorWriter) Write(buf []byte) (int, error) {
208+
if bytes.IndexByte(buf, cd.detectedChar) != -1 {
209+
cd.callback()
210+
}
211+
return len(buf), nil
212+
}
213+
182214
type detailsResult struct {
183215
Settings []*rpc.MonitorPortSettingDescriptor `json:"settings"`
184216
}

Diff for: internal/cli/monitor/term.go

-51
This file was deleted.

0 commit comments

Comments
 (0)