-
Notifications
You must be signed in to change notification settings - Fork 799
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support windows console input buffer
This adds support to the Windows Console Input Buffer API which access the console API directly without the need for virtual terminal input (i.e. the current mode that emulates unix inputs). Since this uses the console input api, we can finally read window size events. This is mearly based on the awesome work of @erikgeiser in #140. Fixes: #538 Fixes: #121
- Loading branch information
1 parent
bc1c475
commit f7f0e43
Showing
12 changed files
with
433 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
//go:build !windows | ||
Check failure on line 1 in inputreader_other.go GitHub Actions / lint
|
||
// +build !windows | ||
|
||
package tea | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/muesli/cancelreader" | ||
) | ||
|
||
func newInputReader(r io.Reader) (cancelreader.CancelReader, bool, error) { | ||
c, err := cancelreader.NewReader(r) | ||
return c, false, err | ||
Check failure on line 14 in inputreader_other.go GitHub Actions / lint-soft
Check failure on line 14 in inputreader_other.go GitHub Actions / lint-soft
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
//go:build windows | ||
// +build windows | ||
|
||
package tea | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"sync" | ||
|
||
"github.com/erikgeiser/coninput" | ||
"github.com/muesli/cancelreader" | ||
"golang.org/x/sys/windows" | ||
) | ||
|
||
type conInputReader struct { | ||
cancelMixin | ||
|
||
conin windows.Handle | ||
cancelEvent windows.Handle | ||
|
||
originalMode uint32 | ||
|
||
// inputEvent holds the input event that was read in order to avoid | ||
// unneccessary allocations. This re-use is possible because | ||
// InputRecord.Unwarp which is called inparseInputMsgFromInputRecord | ||
// returns an data structure that is independent of the passed InputRecord. | ||
inputEvent []coninput.InputRecord | ||
} | ||
|
||
var _ cancelreader.CancelReader = &conInputReader{} | ||
|
||
func newInputReader(r io.Reader) (cancelreader.CancelReader, bool, error) { | ||
fallback := func(io.Reader) (cancelreader.CancelReader, bool, error) { | ||
c, err := cancelreader.NewReader(r) | ||
return c, false, err | ||
} | ||
if f, ok := r.(*os.File); !ok || f.Fd() != os.Stdin.Fd() { | ||
return fallback(r) | ||
} | ||
|
||
conin, err := coninput.NewStdinHandle() | ||
if err != nil { | ||
return fallback(r) | ||
} | ||
|
||
originalMode, err := prepareConsole(conin, | ||
windows.ENABLE_MOUSE_INPUT, | ||
windows.ENABLE_WINDOW_INPUT, | ||
windows.ENABLE_PROCESSED_INPUT, | ||
windows.ENABLE_EXTENDED_FLAGS, | ||
) | ||
if err != nil { | ||
return nil, false, fmt.Errorf("failed to prepare console input: %w", err) | ||
} | ||
|
||
cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil) | ||
if err != nil { | ||
return nil, false, fmt.Errorf("create stop event: %w", err) | ||
} | ||
|
||
return &conInputReader{ | ||
conin: conin, | ||
cancelEvent: cancelEvent, | ||
originalMode: originalMode, | ||
}, true, nil | ||
} | ||
|
||
// Cancel implements cancelreader.CancelReader. | ||
func (r *conInputReader) Cancel() bool { | ||
r.setCanceled() | ||
|
||
err := windows.SetEvent(r.cancelEvent) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
// Close implements cancelreader.CancelReader. | ||
func (r *conInputReader) Close() error { | ||
err := windows.CloseHandle(r.cancelEvent) | ||
if err != nil { | ||
return fmt.Errorf("closing cancel event handle: %w", err) | ||
} | ||
|
||
if r.originalMode != 0 { | ||
err := windows.SetConsoleMode(r.conin, r.originalMode) | ||
if err != nil { | ||
return fmt.Errorf("reset console mode: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Read implements cancelreader.CancelReader. | ||
func (*conInputReader) Read(_ []byte) (n int, err error) { | ||
return 0, nil | ||
} | ||
|
||
func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, err error) { | ||
err = windows.GetConsoleMode(input, &originalMode) | ||
if err != nil { | ||
return 0, fmt.Errorf("get console mode: %w", err) | ||
} | ||
|
||
newMode := coninput.AddInputModes(0, modes...) | ||
|
||
err = windows.SetConsoleMode(input, newMode) | ||
if err != nil { | ||
return 0, fmt.Errorf("set console mode: %w", err) | ||
} | ||
|
||
return originalMode, nil | ||
} | ||
|
||
func waitForInput(conin, cancel windows.Handle) error { | ||
event, err := windows.WaitForMultipleObjects([]windows.Handle{conin, cancel}, false, windows.INFINITE) | ||
switch { | ||
case windows.WAIT_OBJECT_0 <= event && event < windows.WAIT_OBJECT_0+2: | ||
if event == windows.WAIT_OBJECT_0+1 { | ||
return cancelreader.ErrCanceled | ||
} | ||
|
||
if event == windows.WAIT_OBJECT_0 { | ||
return nil | ||
} | ||
|
||
return fmt.Errorf("unexpected wait object is ready: %d", event-windows.WAIT_OBJECT_0) | ||
case windows.WAIT_ABANDONED <= event && event < windows.WAIT_ABANDONED+2: | ||
return fmt.Errorf("abandoned") | ||
case event == uint32(windows.WAIT_TIMEOUT): | ||
return fmt.Errorf("timeout") | ||
case event == windows.WAIT_FAILED: | ||
return fmt.Errorf("failed") | ||
default: | ||
return fmt.Errorf("unexpected error: %w", error(err)) | ||
} | ||
} | ||
|
||
// cancelMixin represents a goroutine-safe cancelation status. | ||
type cancelMixin struct { | ||
unsafeCanceled bool | ||
lock sync.Mutex | ||
} | ||
|
||
func (c *cancelMixin) isCanceled() bool { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
return c.unsafeCanceled | ||
} | ||
|
||
func (c *cancelMixin) setCanceled() { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
c.unsafeCanceled = true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
//go:build !windows | ||
Check failure on line 1 in key_other.go GitHub Actions / lint
|
||
// +build !windows | ||
|
||
package tea | ||
|
||
import ( | ||
"context" | ||
"io" | ||
) | ||
|
||
func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error { | ||
return readAnsiInputs(ctx, msgs, input) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.