inputreader_windows.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. //go:build windows
  2. // +build windows
  3. package tea
  4. import (
  5. "fmt"
  6. "io"
  7. "os"
  8. "sync"
  9. "github.com/charmbracelet/x/term"
  10. "github.com/erikgeiser/coninput"
  11. "github.com/muesli/cancelreader"
  12. "golang.org/x/sys/windows"
  13. )
  14. type conInputReader struct {
  15. cancelMixin
  16. conin windows.Handle
  17. originalMode uint32
  18. }
  19. var _ cancelreader.CancelReader = &conInputReader{}
  20. func newInputReader(r io.Reader) (cancelreader.CancelReader, error) {
  21. fallback := func(io.Reader) (cancelreader.CancelReader, error) {
  22. return cancelreader.NewReader(r)
  23. }
  24. if f, ok := r.(term.File); !ok || f.Fd() != os.Stdin.Fd() {
  25. return fallback(r)
  26. }
  27. conin, err := coninput.NewStdinHandle()
  28. if err != nil {
  29. return fallback(r)
  30. }
  31. originalMode, err := prepareConsole(conin,
  32. windows.ENABLE_MOUSE_INPUT,
  33. windows.ENABLE_WINDOW_INPUT,
  34. windows.ENABLE_EXTENDED_FLAGS,
  35. )
  36. if err != nil {
  37. return nil, fmt.Errorf("failed to prepare console input: %w", err)
  38. }
  39. return &conInputReader{
  40. conin: conin,
  41. originalMode: originalMode,
  42. }, nil
  43. }
  44. // Cancel implements cancelreader.CancelReader.
  45. func (r *conInputReader) Cancel() bool {
  46. r.setCanceled()
  47. return windows.CancelIoEx(r.conin, nil) == nil || windows.CancelIo(r.conin) == nil
  48. }
  49. // Close implements cancelreader.CancelReader.
  50. func (r *conInputReader) Close() error {
  51. if r.originalMode != 0 {
  52. err := windows.SetConsoleMode(r.conin, r.originalMode)
  53. if err != nil {
  54. return fmt.Errorf("reset console mode: %w", err)
  55. }
  56. }
  57. return nil
  58. }
  59. // Read implements cancelreader.CancelReader.
  60. func (r *conInputReader) Read(_ []byte) (n int, err error) {
  61. if r.isCanceled() {
  62. err = cancelreader.ErrCanceled
  63. }
  64. return
  65. }
  66. func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, err error) {
  67. err = windows.GetConsoleMode(input, &originalMode)
  68. if err != nil {
  69. return 0, fmt.Errorf("get console mode: %w", err)
  70. }
  71. newMode := coninput.AddInputModes(0, modes...)
  72. err = windows.SetConsoleMode(input, newMode)
  73. if err != nil {
  74. return 0, fmt.Errorf("set console mode: %w", err)
  75. }
  76. return originalMode, nil
  77. }
  78. // cancelMixin represents a goroutine-safe cancelation status.
  79. type cancelMixin struct {
  80. unsafeCanceled bool
  81. lock sync.Mutex
  82. }
  83. func (c *cancelMixin) setCanceled() {
  84. c.lock.Lock()
  85. defer c.lock.Unlock()
  86. c.unsafeCanceled = true
  87. }
  88. func (c *cancelMixin) isCanceled() bool {
  89. c.lock.Lock()
  90. defer c.lock.Unlock()
  91. return c.unsafeCanceled
  92. }