tty_windows.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // +build windows
  2. package tty
  3. import (
  4. "os"
  5. "syscall"
  6. "unsafe"
  7. "github.com/mattn/go-isatty"
  8. )
  9. const (
  10. rightAltPressed = 1
  11. leftAltPressed = 2
  12. rightCtrlPressed = 4
  13. leftCtrlPressed = 8
  14. shiftPressed = 0x0010
  15. ctrlPressed = rightCtrlPressed | leftCtrlPressed
  16. altPressed = rightAltPressed | leftAltPressed
  17. )
  18. const (
  19. enableProcessedInput = 0x1
  20. enableLineInput = 0x2
  21. enableEchoInput = 0x4
  22. enableWindowInput = 0x8
  23. enableMouseInput = 0x10
  24. enableInsertMode = 0x20
  25. enableQuickEditMode = 0x40
  26. enableExtendedFlag = 0x80
  27. enableProcessedOutput = 1
  28. enableWrapAtEolOutput = 2
  29. keyEvent = 0x1
  30. mouseEvent = 0x2
  31. windowBufferSizeEvent = 0x4
  32. )
  33. var kernel32 = syscall.NewLazyDLL("kernel32.dll")
  34. var (
  35. procAllocConsole = kernel32.NewProc("AllocConsole")
  36. procSetStdHandle = kernel32.NewProc("SetStdHandle")
  37. procGetStdHandle = kernel32.NewProc("GetStdHandle")
  38. procSetConsoleScreenBufferSize = kernel32.NewProc("SetConsoleScreenBufferSize")
  39. procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
  40. procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
  41. procWriteConsoleOutputCharacter = kernel32.NewProc("WriteConsoleOutputCharacterW")
  42. procWriteConsoleOutputAttribute = kernel32.NewProc("WriteConsoleOutputAttribute")
  43. procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
  44. procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
  45. procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
  46. procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
  47. procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
  48. procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
  49. procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
  50. procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
  51. procScrollConsoleScreenBuffer = kernel32.NewProc("ScrollConsoleScreenBufferW")
  52. )
  53. type wchar uint16
  54. type short int16
  55. type dword uint32
  56. type word uint16
  57. type coord struct {
  58. x short
  59. y short
  60. }
  61. type smallRect struct {
  62. left short
  63. top short
  64. right short
  65. bottom short
  66. }
  67. type consoleScreenBufferInfo struct {
  68. size coord
  69. cursorPosition coord
  70. attributes word
  71. window smallRect
  72. maximumWindowSize coord
  73. }
  74. type consoleCursorInfo struct {
  75. size dword
  76. visible int32
  77. }
  78. type inputRecord struct {
  79. eventType word
  80. _ [2]byte
  81. event [16]byte
  82. }
  83. type keyEventRecord struct {
  84. keyDown int32
  85. repeatCount word
  86. virtualKeyCode word
  87. virtualScanCode word
  88. unicodeChar wchar
  89. controlKeyState dword
  90. }
  91. type windowBufferSizeRecord struct {
  92. size coord
  93. }
  94. type mouseEventRecord struct {
  95. mousePos coord
  96. buttonState dword
  97. controlKeyState dword
  98. eventFlags dword
  99. }
  100. type charInfo struct {
  101. unicodeChar wchar
  102. attributes word
  103. }
  104. type TTY struct {
  105. in *os.File
  106. out *os.File
  107. st uint32
  108. rs []rune
  109. ws chan WINSIZE
  110. }
  111. func readConsoleInput(fd uintptr, record *inputRecord) (err error) {
  112. var w uint32
  113. r1, _, err := procReadConsoleInput.Call(fd, uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&w)))
  114. if r1 == 0 {
  115. return err
  116. }
  117. return nil
  118. }
  119. func open() (*TTY, error) {
  120. tty := new(TTY)
  121. if false && isatty.IsTerminal(os.Stdin.Fd()) {
  122. tty.in = os.Stdin
  123. } else {
  124. in, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
  125. if err != nil {
  126. return nil, err
  127. }
  128. tty.in = os.NewFile(uintptr(in), "/dev/tty")
  129. }
  130. if isatty.IsTerminal(os.Stdout.Fd()) {
  131. tty.out = os.Stdout
  132. } else {
  133. procAllocConsole.Call()
  134. out, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
  135. if err != nil {
  136. return nil, err
  137. }
  138. tty.out = os.NewFile(uintptr(out), "/dev/tty")
  139. }
  140. h := tty.in.Fd()
  141. var st uint32
  142. r1, _, err := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&st)))
  143. if r1 == 0 {
  144. return nil, err
  145. }
  146. tty.st = st
  147. st &^= enableEchoInput
  148. st &^= enableInsertMode
  149. st &^= enableLineInput
  150. st &^= enableMouseInput
  151. st &^= enableWindowInput
  152. st &^= enableExtendedFlag
  153. st &^= enableQuickEditMode
  154. st &^= enableProcessedInput
  155. // ignore error
  156. procSetConsoleMode.Call(h, uintptr(st))
  157. tty.ws = make(chan WINSIZE)
  158. return tty, nil
  159. }
  160. func (tty *TTY) buffered() bool {
  161. return len(tty.rs) > 0
  162. }
  163. func (tty *TTY) readRune() (rune, error) {
  164. if len(tty.rs) > 0 {
  165. r := tty.rs[0]
  166. tty.rs = tty.rs[1:]
  167. return r, nil
  168. }
  169. var ir inputRecord
  170. err := readConsoleInput(tty.in.Fd(), &ir)
  171. if err != nil {
  172. return 0, err
  173. }
  174. switch ir.eventType {
  175. case windowBufferSizeEvent:
  176. wr := (*windowBufferSizeRecord)(unsafe.Pointer(&ir.event))
  177. tty.ws <- WINSIZE{
  178. W: int(wr.size.x),
  179. H: int(wr.size.y),
  180. }
  181. case keyEvent:
  182. kr := (*keyEventRecord)(unsafe.Pointer(&ir.event))
  183. if kr.keyDown != 0 {
  184. if kr.controlKeyState&altPressed != 0 && kr.unicodeChar > 0 {
  185. tty.rs = []rune{rune(kr.unicodeChar)}
  186. return rune(0x1b), nil
  187. }
  188. if kr.unicodeChar > 0 {
  189. if kr.controlKeyState&shiftPressed != 0 {
  190. switch kr.unicodeChar {
  191. case 0x09:
  192. tty.rs = []rune{0x5b, 0x5a}
  193. return rune(0x1b), nil
  194. }
  195. }
  196. return rune(kr.unicodeChar), nil
  197. }
  198. vk := kr.virtualKeyCode
  199. switch vk {
  200. case 0x21: // page-up
  201. tty.rs = []rune{0x5b, 0x35, 0x7e}
  202. return rune(0x1b), nil
  203. case 0x22: // page-down
  204. tty.rs = []rune{0x5b, 0x36, 0x7e}
  205. return rune(0x1b), nil
  206. case 0x23: // end
  207. tty.rs = []rune{0x5b, 0x46}
  208. return rune(0x1b), nil
  209. case 0x24: // home
  210. tty.rs = []rune{0x5b, 0x48}
  211. return rune(0x1b), nil
  212. case 0x25: // left
  213. tty.rs = []rune{0x5b, 0x44}
  214. return rune(0x1b), nil
  215. case 0x26: // up
  216. tty.rs = []rune{0x5b, 0x41}
  217. return rune(0x1b), nil
  218. case 0x27: // right
  219. tty.rs = []rune{0x5b, 0x43}
  220. return rune(0x1b), nil
  221. case 0x28: // down
  222. tty.rs = []rune{0x5b, 0x42}
  223. return rune(0x1b), nil
  224. case 0x2e: // delete
  225. tty.rs = []rune{0x5b, 0x33, 0x7e}
  226. return rune(0x1b), nil
  227. case 0x70, 0x71, 0x72, 0x73: // F1,F2,F3,F4
  228. tty.rs = []rune{0x5b, 0x4f, rune(vk) - 0x20}
  229. return rune(0x1b), nil
  230. case 0x074, 0x75, 0x76, 0x77: // F5,F6,F7,F8
  231. tty.rs = []rune{0x5b, 0x31, rune(vk) - 0x3f, 0x7e}
  232. return rune(0x1b), nil
  233. case 0x78, 0x79: // F9,F10
  234. tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x48, 0x7e}
  235. return rune(0x1b), nil
  236. case 0x7a, 0x7b: // F11,F12
  237. tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x47, 0x7e}
  238. return rune(0x1b), nil
  239. }
  240. return 0, nil
  241. }
  242. }
  243. return 0, nil
  244. }
  245. func (tty *TTY) close() error {
  246. close(tty.ws)
  247. procSetConsoleMode.Call(tty.in.Fd(), uintptr(tty.st))
  248. return nil
  249. }
  250. func (tty *TTY) size() (int, int, error) {
  251. var csbi consoleScreenBufferInfo
  252. r1, _, err := procGetConsoleScreenBufferInfo.Call(tty.out.Fd(), uintptr(unsafe.Pointer(&csbi)))
  253. if r1 == 0 {
  254. return 0, 0, err
  255. }
  256. return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
  257. }
  258. func (tty *TTY) input() *os.File {
  259. return tty.in
  260. }
  261. func (tty *TTY) output() *os.File {
  262. return tty.out
  263. }
  264. func (tty *TTY) raw() (func() error, error) {
  265. var st uint32
  266. r1, _, err := procGetConsoleMode.Call(tty.in.Fd(), uintptr(unsafe.Pointer(&st)))
  267. if r1 == 0 {
  268. return nil, err
  269. }
  270. mode := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
  271. r1, _, err = procSetConsoleMode.Call(tty.in.Fd(), uintptr(mode))
  272. if r1 == 0 {
  273. return nil, err
  274. }
  275. return func() error {
  276. r1, _, err := procSetConsoleMode.Call(tty.in.Fd(), uintptr(st))
  277. if r1 == 0 {
  278. return err
  279. }
  280. return nil
  281. }, nil
  282. }
  283. func (tty *TTY) sigwinch() chan WINSIZE {
  284. return tty.ws
  285. }