55package main
66
77import (
8- "bufio"
98 "context"
109 "errors"
1110 "fmt"
@@ -17,6 +16,7 @@ import (
1716
1817 "connectrpc.com/connect"
1918 "github.com/BlindspotSoftware/dutctl/internal/output"
19+ "golang.org/x/sys/unix"
2020
2121 pb "github.com/BlindspotSoftware/dutctl/protobuf/gen/dutctl/v1"
2222)
@@ -92,10 +92,44 @@ func (app *application) detailsRPC(device, command, keyword string) error {
9292 return nil
9393}
9494
95+ // setRawInput puts the terminal into raw input mode: disables local echo and
96+ // canonical (line-buffered) mode so each keystroke is available immediately.
97+ // It returns a restore function, or nil if the fd is not a terminal.
98+ func setRawInput (fd int ) func () {
99+ termios , err := unix .IoctlGetTermios (fd , unix .TCGETS )
100+ if err != nil {
101+ return nil
102+ }
103+
104+ old := * termios
105+
106+ termios .Iflag &^= unix .ICRNL
107+ termios .Lflag &^= unix .ECHO | unix .ICANON
108+ termios .Cc [unix .VMIN ] = 1
109+ termios .Cc [unix .VTIME ] = 0
110+
111+ if err := unix .IoctlSetTermios (fd , unix .TCSETS , termios ); err != nil {
112+ return nil
113+ }
114+
115+ return func () {
116+ _ = unix .IoctlSetTermios (fd , unix .TCSETS , & old )
117+ }
118+ }
119+
95120//nolint:funlen,cyclop,gocognit
96121func (app * application ) runRPC (device , command string , cmdArgs []string ) error {
97122 const numWorkers = 2 // The send and receive worker goroutines
98123
124+ // Set raw input mode: disable local echo and canonical mode so each
125+ // keystroke is sent immediately. Interactive modules like serial rely
126+ // on the remote side to echo input.
127+ if f , ok := app .stdin .(* os.File ); ok {
128+ if restore := setRawInput (int (f .Fd ())); restore != nil {
129+ defer restore ()
130+ }
131+ }
132+
99133 runCtx , cancel := context .WithCancel (context .Background ())
100134 defer cancel ()
101135
@@ -230,7 +264,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
230264 go func () {
231265 defer cancel ()
232266
233- reader := bufio . NewReader ( app . stdin )
267+ buf := make ([] byte , 256 )
234268
235269 for {
236270 select {
@@ -241,7 +275,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
241275 default :
242276 }
243277
244- text , err := reader . ReadString ( '\n' )
278+ n , err := app . stdin . Read ( buf )
245279 if err != nil {
246280 if ! errors .Is (err , io .EOF ) {
247281 errChan <- fmt .Errorf ("reading stdin: %w" , err )
@@ -254,7 +288,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
254288 Msg : & pb.RunRequest_Console {
255289 Console : & pb.Console {
256290 Data : & pb.Console_Stdin {
257- Stdin : [] byte ( text ) ,
291+ Stdin : buf [: n ] ,
258292 },
259293 },
260294 },
0 commit comments