Skip to content

Commit 0a63003

Browse files
authored
fix: Windows: first character input lost on successive programs (#1368)
* Fix: Windows: first character input lost on successive programs * lint G115 * intToUint32OrDie * avoid max check math.MaxUint32 (untyped int constant 4294967295) overflows int
1 parent ca9473b commit 0a63003

File tree

2 files changed

+49
-6
lines changed

2 files changed

+49
-6
lines changed

inputreader_windows.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ func newInputReader(r io.Reader, enableMouse bool) (cancelreader.CancelReader, e
6767
func (r *conInputReader) Cancel() bool {
6868
r.setCanceled()
6969

70+
// Warning: These cancel methods do not reliably work on console input
71+
// and should not be counted on.
7072
return windows.CancelIoEx(r.conin, nil) == nil || windows.CancelIo(r.conin) == nil
7173
}
7274

key_windows.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99
"io"
10+
"time"
1011

1112
"github.com/erikgeiser/coninput"
1213
localereader "github.com/mattn/go-localereader"
@@ -25,14 +26,10 @@ func readConInputs(ctx context.Context, msgsch chan<- Msg, con *conInputReader)
2526
var ps coninput.ButtonState // keep track of previous mouse state
2627
var ws coninput.WindowBufferSizeEventRecord // keep track of the last window size event
2728
for {
28-
events, err := coninput.ReadNConsoleInputs(con.conin, 16)
29+
events, err := peekAndReadConsInput(con)
2930
if err != nil {
30-
if con.isCanceled() {
31-
return cancelreader.ErrCanceled
32-
}
33-
return fmt.Errorf("read coninput events: %w", err)
31+
return err
3432
}
35-
3633
for _, event := range events {
3734
var msgs []Msg
3835
switch e := event.Unwrap().(type) {
@@ -94,6 +91,50 @@ func readConInputs(ctx context.Context, msgsch chan<- Msg, con *conInputReader)
9491
}
9592
}
9693

94+
// Peek for new input in a tight loop and then read the input.
95+
// windows.CancelIo* does not work reliably so peek first and only use the data if
96+
// the console input is not cancelled.
97+
func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
98+
events, err := peekConsInput(con)
99+
if err != nil {
100+
return events, err
101+
}
102+
events, err = coninput.ReadNConsoleInputs(con.conin, intToUint32OrDie(len(events)))
103+
if con.isCanceled() {
104+
return events, cancelreader.ErrCanceled
105+
}
106+
if err != nil {
107+
return events, fmt.Errorf("read coninput events: %w", err)
108+
}
109+
return events, nil
110+
}
111+
112+
// Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115.
113+
func intToUint32OrDie(i int) uint32 {
114+
if i < 0 {
115+
panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32")
116+
}
117+
return uint32(i)
118+
}
119+
120+
// Keeps peeking until there is data or the input is cancelled.
121+
func peekConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
122+
for {
123+
events, err := coninput.PeekNConsoleInputs(con.conin, 16)
124+
if con.isCanceled() {
125+
return events, cancelreader.ErrCanceled
126+
}
127+
if err != nil {
128+
return events, fmt.Errorf("peek coninput events: %w", err)
129+
}
130+
if len(events) > 0 {
131+
return events, nil
132+
}
133+
// Sleep for a bit to avoid busy waiting.
134+
time.Sleep(16 * time.Millisecond)
135+
}
136+
}
137+
97138
func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action MouseAction) {
98139
btn := p ^ s
99140
action = MouseActionPress

0 commit comments

Comments
 (0)