input.go 7.8 KB

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