inputreader_windows.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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.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 (*conInputReader) Read(_ []byte) (n int, err error) {
  61. return 0, nil
  62. }
  63. func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, err error) {
  64. err = windows.GetConsoleMode(input, &originalMode)
  65. if err != nil {
  66. return 0, fmt.Errorf("get console mode: %w", err)
  67. }
  68. newMode := coninput.AddInputModes(0, modes...)
  69. err = windows.SetConsoleMode(input, newMode)
  70. if err != nil {
  71. return 0, fmt.Errorf("set console mode: %w", err)
  72. }
  73. return originalMode, nil
  74. }
  75. // cancelMixin represents a goroutine-safe cancelation status.
  76. type cancelMixin struct {
  77. unsafeCanceled bool
  78. lock sync.Mutex
  79. }
  80. func (c *cancelMixin) setCanceled() {
  81. c.lock.Lock()
  82. defer c.lock.Unlock()
  83. c.unsafeCanceled = true
  84. }