Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

‎internal/cli/feedback/terminal.go‎

Lines changed: 22 additions & 0 deletions
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
}

‎internal/cli/monitor/monitor.go‎

Lines changed: 55 additions & 23 deletions
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, rawbool) {
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
}

‎internal/cli/monitor/term.go‎

Lines changed: 0 additions & 51 deletions
This file was deleted.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /