input.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. package door
  2. import (
  3. "log"
  4. "strconv"
  5. "strings"
  6. "time"
  7. "unicode"
  8. )
  9. // This is the current list of Extended keys we support:
  10. const (
  11. XKEY_UP_ARROW = 0x1001
  12. XKEY_DOWN_ARROW = 0x1002
  13. XKEY_RIGHT_ARROW = 0x1003
  14. XKEY_LEFT_ARROW = 0x1004
  15. XKEY_HOME = 0x1010
  16. XKEY_END = 0x1011
  17. XKEY_PGUP = 0x1012
  18. XKEY_PGDN = 0x1023
  19. XKEY_INSERT = 0x1024
  20. XKEY_DELETE = 0x7f
  21. XKEY_F1 = 0x1021
  22. XKEY_F2 = 0x1022
  23. XKEY_F3 = 0x1023
  24. XKEY_F4 = 0x1024
  25. XKEY_F5 = 0x1025
  26. XKEY_F6 = 0x1026
  27. XKEY_F7 = 0x1027
  28. XKEY_F8 = 0x1028
  29. XKEY_F9 = 0x1029
  30. XKEY_F10 = 0x102a
  31. XKEY_F11 = 0x102b
  32. XKEY_F12 = 0x102c
  33. XKEY_MOUSE = 0x1100
  34. XKEY_UNKNOWN = 0x1111
  35. )
  36. var extendedKeys map[string]int = map[string]int{
  37. "[A": XKEY_UP_ARROW,
  38. "[B": XKEY_DOWN_ARROW,
  39. "[C": XKEY_RIGHT_ARROW,
  40. "[D": XKEY_LEFT_ARROW,
  41. "[H": XKEY_HOME,
  42. "[F": XKEY_END, // terminal
  43. "[K": XKEY_END,
  44. "[V": XKEY_PGUP,
  45. "[U": XKEY_PGDN,
  46. "[@": XKEY_INSERT,
  47. "[2~": XKEY_INSERT, // terminal
  48. "[3~": XKEY_DELETE, // terminal
  49. "[5~": XKEY_PGUP, // terminal
  50. "[6~": XKEY_PGDN, // terminal
  51. "[15~": XKEY_F5, // terminal
  52. "[17~": XKEY_F6, // terminal
  53. "[18~": XKEY_F7, // terminal
  54. "[19~": XKEY_F8, // terminal
  55. "[20~": XKEY_F9, // terminal
  56. "[21~": XKEY_F10, // terminal
  57. "[23~": XKEY_F11,
  58. "[24~": XKEY_F12, // terminal
  59. "OP": XKEY_F1,
  60. "OQ": XKEY_F2,
  61. "OR": XKEY_F3,
  62. "OS": XKEY_F4,
  63. "Ot": XKEY_F5, // syncterm
  64. }
  65. // "[1": XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?
  66. // Low level read key function.
  67. // This gets the raw keys from the client, it doesn't handle extended keys,
  68. // functions, arrows.
  69. // Return key, or -1 (Timeout/No key available), -2 hangup
  70. func (d *Door) getch() int {
  71. select {
  72. case res, ok := <-d.readerChannel:
  73. if ok {
  74. return int(res)
  75. } else {
  76. d.Disconnected = true
  77. // atomic.StoreInt32(&d.Disconnected, 1)
  78. return -2
  79. }
  80. case <-time.After(time.Duration(100) * time.Millisecond):
  81. return -1
  82. }
  83. }
  84. func (d *Door) getkey_or_pushback() int {
  85. if !d.Pushback.Empty() {
  86. return d.Pushback.Pop()
  87. }
  88. if false {
  89. var key int = d.getch()
  90. log.Printf("%d / %X\n", key, key)
  91. return key
  92. } else {
  93. return d.getch()
  94. }
  95. }
  96. // Return key received, or XKEY_* values.
  97. // -1 timeout/no key
  98. // -2 hangup
  99. // -3 out of time
  100. func (d *Door) GetKey() int {
  101. var c, c2 int
  102. if d.Disconnect() {
  103. return -2
  104. }
  105. if d.TimeLeft() < 0 {
  106. return -3
  107. }
  108. c = d.getkey_or_pushback()
  109. if c < 0 {
  110. return c
  111. }
  112. // We get 0x0d 0x00, or 0x0d 0x0a from syncterm.
  113. if c == 0x0d {
  114. c2 = d.getkey_or_pushback()
  115. if c2 > 0 {
  116. // wasn't an error
  117. if c2 != 0x00 && c2 != 0x0a {
  118. // wasn't 0x00 or 0x0a
  119. d.Pushback.Push(c2)
  120. // log.Printf("Push 0x0d trailer %d / %x\n", c2, c2)
  121. }
  122. }
  123. return c
  124. }
  125. if c == 0 {
  126. // possibly doorway mode
  127. var tries int = 0
  128. c2 = d.getkey_or_pushback()
  129. for c2 < 0 {
  130. if tries > 7 {
  131. return c
  132. }
  133. c2 = d.getkey_or_pushback()
  134. tries++
  135. }
  136. switch c2 {
  137. case 0x50:
  138. return XKEY_DOWN_ARROW
  139. case 0x48:
  140. return XKEY_UP_ARROW
  141. case 0x4b:
  142. return XKEY_LEFT_ARROW
  143. case 0x4d:
  144. return XKEY_RIGHT_ARROW
  145. case 0x47:
  146. return XKEY_HOME
  147. case 0x4f:
  148. return XKEY_END
  149. case 0x49:
  150. return XKEY_PGUP
  151. case 0x51:
  152. return XKEY_PGDN
  153. case 0x3b:
  154. return XKEY_F1
  155. case 0x3c:
  156. return XKEY_F2
  157. case 0x3d:
  158. return XKEY_F3
  159. case 0x3e:
  160. return XKEY_F4
  161. case 0x3f:
  162. return XKEY_F5
  163. case 0x40:
  164. return XKEY_F6
  165. case 0x41:
  166. return XKEY_F7
  167. case 0x42:
  168. return XKEY_F8
  169. case 0x43:
  170. return XKEY_F9
  171. case 0x44:
  172. return XKEY_F10
  173. /*
  174. case 0x45:
  175. return XKEY_F11
  176. case 0x46:
  177. return XKEY_F12
  178. */
  179. case 0x52:
  180. return XKEY_INSERT
  181. case 0x53:
  182. return XKEY_DELETE
  183. default:
  184. log.Printf("ERROR Doorway mode: 0x00 %x\n", c2)
  185. return XKEY_UNKNOWN
  186. }
  187. }
  188. if c == 0x1b {
  189. // Escape key?
  190. c2 = d.getkey_or_pushback()
  191. if c2 < 0 {
  192. // Just escape key
  193. return c
  194. }
  195. var extended []byte = make([]byte, 1)
  196. extended[0] = byte(c2) // string = string(byte(c2))
  197. c2 = d.getkey_or_pushback()
  198. for c2 > 0 {
  199. if c2 == 0x1b {
  200. d.Pushback.Push(c2)
  201. break
  202. }
  203. extended = append(extended, byte(c2)) // += string(byte(c2))
  204. var has bool
  205. c2, has = extendedKeys[string(extended)]
  206. if has {
  207. // break out here if \x1b[ + letter or @
  208. // break out if \x1b[ + digits + ~
  209. // break out if \x1bO + letter
  210. return c2
  211. }
  212. c2 = d.getkey_or_pushback()
  213. }
  214. if strings.HasPrefix(string(extended), "[M") && len(extended) == 5 {
  215. // log.Printf("MOUSE Extended %#v\n", extended)
  216. Mouse.Button = int(extended[2]) - 0x20 + 1
  217. Mouse.X = int(extended[3]) - 0x21 + 1
  218. Mouse.Y = int(extended[4]) - 0x21 + 1
  219. return XKEY_MOUSE
  220. // log.Printf("MOUSE %d (%d,%d)\n", mouse_button, mouse_x, mouse_y)
  221. }
  222. log.Printf("ERROR Extended %#v\n", extended)
  223. return XKEY_UNKNOWN
  224. }
  225. return c
  226. }
  227. func (d *Door) Key() int {
  228. return d.WaitKey(Inactivity, 0)
  229. }
  230. // usecs = Microseconds
  231. func (d *Door) WaitKey(secs int64, usecs int64) int {
  232. if d.Disconnect() {
  233. return -2
  234. }
  235. if !d.Pushback.Empty() {
  236. return d.GetKey()
  237. }
  238. var timeout time.Duration = time.Duration(secs)*time.Second + time.Duration(usecs)*time.Microsecond
  239. select {
  240. case res, ok := <-d.readerChannel:
  241. if ok {
  242. d.Pushback.Push(int(res))
  243. return d.GetKey()
  244. } else {
  245. // Reader Closed
  246. d.Disconnected = true
  247. // If I wrap this with if !d.WriterClosed .. races ?
  248. // Why can't I do this? This isn't a go routine...
  249. if !d.WriterClosed {
  250. d.writerChannel <- ""
  251. }
  252. // d.closeChannel <- struct{}{}
  253. // atomic.StoreInt32(&d.Disconnected, 1)
  254. return -2
  255. }
  256. case <-time.After(timeout):
  257. return -1
  258. }
  259. }
  260. // Outputs spaces and backspaces
  261. // If you have set a background color, this shows the input area.
  262. func DisplayInput(max int) string {
  263. return strings.Repeat(" ", max) + strings.Repeat("\x08", max)
  264. }
  265. // Input a string of max length.
  266. // This displays the input area if a bg color was set.
  267. // This handles timeout, input, backspace, and enter.
  268. func (d *Door) Input(max int) string {
  269. var line string
  270. // draw input area
  271. d.Write(DisplayInput(max))
  272. var c int
  273. for {
  274. c = d.WaitKey(Inactivity, 0)
  275. if c < 0 {
  276. // timeout/hangup
  277. return ""
  278. }
  279. if c > 1000 {
  280. continue
  281. }
  282. if strconv.IsPrint(rune(c)) {
  283. if len(line) < max {
  284. d.Write(string(byte(c)))
  285. line += string(byte(c))
  286. } else {
  287. d.Write("\x07")
  288. }
  289. } else {
  290. // Non-print
  291. switch c {
  292. case 0x7f, 0x08:
  293. if len(line) > 0 {
  294. d.Write("\x08 \x08")
  295. line = line[:len(line)-1]
  296. }
  297. case 0x0d:
  298. return line
  299. }
  300. }
  301. }
  302. }
  303. func (d *Door) GetOneOf(possible string) int {
  304. var c int
  305. for {
  306. c = d.WaitKey(Inactivity, 0)
  307. if c < 0 {
  308. return c
  309. }
  310. r := unicode.ToUpper(rune(c))
  311. if strings.ContainsRune(possible, r) {
  312. // return upper case rune
  313. return int(r)
  314. }
  315. /*
  316. c = strings.IndexRune(possible, r)
  317. if c != -1 {
  318. return c
  319. }
  320. */
  321. }
  322. }