|
5 | 5 | package term
|
6 | 6 |
|
7 | 7 | import (
|
| 8 | + "context" |
8 | 9 | "os"
|
9 | 10 |
|
10 | 11 | "golang.org/x/sys/windows"
|
@@ -77,3 +78,47 @@ func readPassword(fd int) ([]byte, error) {
|
77 | 78 | defer f.Close()
|
78 | 79 | return readPasswordLine(f)
|
79 | 80 | }
|
| 81 | + |
| 82 | +func readPasswordWithContext(fd int, ctx context.Context) ([]byte, error) { |
| 83 | + var st uint32 |
| 84 | + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { |
| 85 | + return nil, err |
| 86 | + } |
| 87 | + old := st |
| 88 | + |
| 89 | + st &^= (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT) |
| 90 | + st |= (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_PROCESSED_INPUT) |
| 91 | + if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { |
| 92 | + return nil, err |
| 93 | + } |
| 94 | + |
| 95 | + defer windows.SetConsoleMode(windows.Handle(fd), old) |
| 96 | + |
| 97 | + var h windows.Handle |
| 98 | + p, _ := windows.GetCurrentProcess() |
| 99 | + if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil { |
| 100 | + return nil, err |
| 101 | + } |
| 102 | + |
| 103 | + f := os.NewFile(uintptr(h), "stdin") |
| 104 | + defer f.Close() |
| 105 | + |
| 106 | + // Buffer for reading in separate goroutine |
| 107 | + type Buffer struct { |
| 108 | + line []byte |
| 109 | + err error |
| 110 | + } |
| 111 | + doneChannel := make(chan Buffer, 1) |
| 112 | + go func() { |
| 113 | + // The following blocks and cannot be unblocked |
| 114 | + ret, err := readPasswordLine(f) |
| 115 | + doneChannel <- Buffer{line: ret, err: err} |
| 116 | + }() |
| 117 | + select { |
| 118 | + case <-ctx.Done(): |
| 119 | + f.Close() // Blocks until terminal receives a return key :-( |
| 120 | + return make([]byte, 0), ctx.Err() |
| 121 | + case buf := <-doneChannel: |
| 122 | + return buf.line, buf.err |
| 123 | + } |
| 124 | +} |
0 commit comments