input.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. package door
  2. import (
  3. "fmt"
  4. "log"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "unicode"
  9. )
  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. //go:generate stringer -type=Extended
  40. const DEBUG_INPUT = true
  41. var ErrInactivity error = fmt.Errorf("Inactivity")
  42. var ErrTimeout error = fmt.Errorf("Timeout")
  43. var ErrDisconnected error = fmt.Errorf("Disconnected")
  44. var DefaultTimeout time.Duration = time.Duration(60) * time.Second
  45. var extendedKeys map[string]Extended = map[string]Extended{
  46. "[A": UP_ARROW,
  47. "[B": DOWN_ARROW,
  48. "[C": RIGHT_ARROW,
  49. "[D": LEFT_ARROW,
  50. "[H": HOME,
  51. "[F": END, // terminal
  52. "[K": END,
  53. "[V": PAGE_UP,
  54. "[U": PAGE_DOWN,
  55. "[@": INSERT,
  56. "[2~": INSERT, // terminal
  57. "[3~": DELETE, // terminal
  58. "[5~": PAGE_UP, // terminal
  59. "[6~": PAGE_DOWN, // terminal
  60. "[15~": F5, // terminal
  61. "[17~": F6, // terminal
  62. "[18~": F7, // terminal
  63. "[19~": F8, // terminal
  64. "[20~": F9, // terminal
  65. "[21~": F10, // terminal
  66. "[23~": F11,
  67. "[24~": F12, // terminal
  68. "OP": F1,
  69. "OQ": F2,
  70. "OR": F3,
  71. "OS": F4, // syncterm "[1" = F1,F2,F3, and F4)
  72. "Ot": F5, // syncterm
  73. }
  74. // ReadRune fails on IAC AYT => unicode.ReplacementChar X 2
  75. // getch -> ReadRune Low Level
  76. func (d *Door) ReadRune() (rune, error) {
  77. var r rune
  78. if d.ReaderClosed {
  79. return r, ErrDisconnected
  80. }
  81. for {
  82. select {
  83. case r = <-d.readerChannel:
  84. return r, nil
  85. case <-time.After(ReaderInterval):
  86. return r, ErrTimeout
  87. }
  88. }
  89. }
  90. func (d *Door) RunePushback(r rune) {
  91. d.Pushback.Push(r)
  92. }
  93. // getkey_or_pushback -> ReadRunePushback()
  94. func (d *Door) ReadRunePushback() (rune, error) {
  95. if !d.Pushback.Empty() {
  96. if DEBUG_INPUT {
  97. log.Println("Read From PushBack")
  98. }
  99. return d.Pushback.Pop(), nil
  100. }
  101. return d.ReadRune()
  102. }
  103. func RuneToInt8(r rune) int8 {
  104. return int8(r)
  105. }
  106. // Confusion - It's possible to return a null rune
  107. // that we received. So it's a little sloppy having
  108. // to check Extended == NOP. :(
  109. // High Level function - returns rune or extended
  110. func (d *Door) GetKey() (rune, Extended, error) {
  111. var r, r2 rune
  112. var err, err2 error
  113. var ex Extended
  114. if d.ReaderClosed {
  115. return r, ex, ErrDisconnected
  116. }
  117. // if bio.Disconnected() {
  118. // return r, ex, DisconnectedError
  119. // }
  120. r, err = d.ReadRunePushback()
  121. if err != nil {
  122. return r, ex, err
  123. }
  124. // fyneterm CR
  125. if r == '\x0a' {
  126. r2, err2 = d.ReadRunePushback()
  127. if err2 == nil {
  128. // Not an error
  129. if r2 != '\x00' && r2 != '\x0a' {
  130. // Wasn't 0x00 or 0x0a
  131. d.RunePushback(r2)
  132. }
  133. }
  134. return '\x0d', ex, nil
  135. }
  136. // We get 0x0d, 0x00, or 0x0d 0x0a from syncterm
  137. if r == '\x0d' {
  138. r2, err2 = d.ReadRunePushback()
  139. if err2 == nil {
  140. // Not an error
  141. if r2 != '\x00' && r2 != '\x0a' {
  142. // Wasn't 0x00 or 0x0a
  143. d.RunePushback(r2)
  144. }
  145. }
  146. return r, ex, nil
  147. }
  148. if r == '\x00' {
  149. // Possibly doorway mode - deprecated?
  150. // syncterm does support this, so it isn't entirely dead (NNY!)
  151. r2, _ = d.ReadRunePushback()
  152. r = rune(0)
  153. switch r2 {
  154. case '\x50':
  155. return r, DOWN_ARROW, nil
  156. case '\x48':
  157. return r, UP_ARROW, nil
  158. case '\x4b':
  159. return r, LEFT_ARROW, nil
  160. case 0x4d:
  161. return r, RIGHT_ARROW, nil
  162. case 0x47:
  163. return r, HOME, nil
  164. case 0x4f:
  165. return r, END, nil
  166. case 0x49:
  167. return r, PAGE_UP, nil
  168. case 0x51:
  169. return r, PAGE_DOWN, nil
  170. case 0x3b:
  171. return r, F1, nil
  172. case 0x3c:
  173. return r, F2, nil
  174. case 0x3d:
  175. return r, F3, nil
  176. case 0x3e:
  177. return r, F4, nil
  178. case 0x3f:
  179. return r, F5, nil
  180. case 0x40:
  181. return r, F6, nil
  182. case 0x41:
  183. return r, F7, nil
  184. case 0x42:
  185. return r, F8, nil
  186. case 0x43:
  187. return r, F9, nil
  188. case 0x44:
  189. return r, F10, nil
  190. /*
  191. case 0x45:
  192. return F11
  193. case 0x46:
  194. return F12
  195. */
  196. case 0x52:
  197. return r, INSERT, nil
  198. case 0x53:
  199. return r, DELETE, nil
  200. default:
  201. log.Printf("ERROR Doorway mode: 0x00 %x\n", r2)
  202. return r, UNKNOWN, nil
  203. }
  204. } // End doorway
  205. if r == '\x1b' {
  206. // Escape key?
  207. r2, err2 = d.ReadRunePushback()
  208. if err2 != nil {
  209. // Just escape key
  210. return r, ex, nil
  211. }
  212. if r2 == '\x1b' {
  213. d.RunePushback(r2)
  214. return r, ex, nil
  215. }
  216. var extended []rune = make([]rune, 1)
  217. extended[0] = r2
  218. r2, err2 = d.ReadRunePushback()
  219. for err2 == nil {
  220. if r2 == '\x1b' {
  221. // This is the end of the extended code.
  222. d.RunePushback(r2)
  223. break
  224. }
  225. extended = append(extended, r2)
  226. ext, has := extendedKeys[string(extended)]
  227. if has {
  228. return rune(0), ext, nil
  229. }
  230. if unicode.IsLetter(r2) {
  231. // The end of the extended code
  232. break
  233. }
  234. r2, err2 = d.ReadRunePushback()
  235. }
  236. var exString string = string(extended)
  237. if strings.HasPrefix(exString, "[M") && len(extended) == 5 {
  238. // Mouse Extended - input zero based (I add +1 to X, Y, and button)
  239. var mouse Mouse = Mouse{Button: RuneToInt8(extended[2]) - ' ' + 1,
  240. X: RuneToInt8(extended[3]) - '!' + 1,
  241. Y: RuneToInt8(extended[4]) - '!' + 1}
  242. d.mcMutex.Lock()
  243. defer d.mcMutex.Unlock()
  244. d.LastMouse = append(d.LastMouse, mouse)
  245. log.Println("Mouse:", mouse)
  246. return rune(0), MOUSE, nil
  247. }
  248. if strings.HasSuffix(exString, "R") {
  249. // Cursor Position information
  250. var cursor CursorPos
  251. // ^[[1;1R^[[2;3R^[[41;173R
  252. // Y;X
  253. exString = exString[1 : len(exString)-1] // Remove [ and R
  254. pos := SplitToInt(exString, ";")
  255. if len(pos) == 2 {
  256. cursor.X = pos[1]
  257. cursor.Y = pos[0]
  258. d.mcMutex.Lock()
  259. defer d.mcMutex.Unlock()
  260. d.LastCursor = append(d.LastCursor, cursor)
  261. log.Println("Cursor Pos:", cursor)
  262. return rune(0), CURSOR, nil
  263. } else {
  264. log.Println("ERROR Cursor:", extended)
  265. return rune(0), UNKNOWN, nil
  266. }
  267. }
  268. if exString == "\x1b" {
  269. return extended[0], Extended(0), nil
  270. }
  271. log.Println("ERROR Extended:", extended)
  272. return rune(0), UNKNOWN, nil
  273. }
  274. // AYT (Telnet Are You There?)
  275. /*
  276. if r == '\xff' {
  277. log.Printf("IAC")
  278. r2, err2 = bio.ReadRunePushback()
  279. if err2 != nil {
  280. // Return value
  281. return r, ex, nil
  282. }
  283. if r2 == '\xf6' {
  284. // Are You There?
  285. log.Println("AYT? - Yes")
  286. bio.Write("Yes?")
  287. // bio.Write(string([]byte{0x00})) // 0xff, 0xf2}))
  288. // Eat these keys, and re-call ourselves...
  289. // Or goto very top?
  290. return bio.GetKey()
  291. }
  292. }
  293. */
  294. return r, ex, nil
  295. }
  296. // "[1": XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?
  297. // Low level read key function.
  298. // This gets the raw keys from the client, it doesn't handle extended keys,
  299. // functions, arrows.
  300. // Return key, or -1 (Timeout/No key available), -2 hangup
  301. /*
  302. func (d *Door) getch() int {
  303. select {
  304. case res, ok := <-d.readerChannel:
  305. if ok {
  306. return int(res)
  307. } else {
  308. d.Disconnected = true
  309. // atomic.StoreInt32(&d.Disconnected, 1)
  310. return -2
  311. }
  312. case <-time.After(time.Duration(100) * time.Millisecond):
  313. return -1
  314. }
  315. }
  316. func (d *Door) getkey_or_pushback() int {
  317. if !d.Pushback.Empty() {
  318. return d.Pushback.Pop()
  319. }
  320. if false {
  321. var key int = d.getch()
  322. log.Printf("%d / %X\n", key, key)
  323. return key
  324. } else {
  325. return d.getch()
  326. }
  327. }
  328. // Return key received, or XKEY_* values.
  329. // -1 timeout/no key
  330. // -2 hangup
  331. // -3 out of time
  332. func (d *Door) GetKey() int {
  333. var c, c2 int
  334. if d.Disconnect() {
  335. return -2
  336. }
  337. if d.TimeLeft() < 0 {
  338. return -3
  339. }
  340. c = d.getkey_or_pushback()
  341. if c < 0 {
  342. return c
  343. }
  344. // We get 0x0d 0x00, or 0x0d 0x0a from syncterm.
  345. if c == 0x0d {
  346. c2 = d.getkey_or_pushback()
  347. if c2 > 0 {
  348. // wasn't an error
  349. if c2 != 0x00 && c2 != 0x0a {
  350. // wasn't 0x00 or 0x0a
  351. d.Pushback.Push(c2)
  352. // log.Printf("Push 0x0d trailer %d / %x\n", c2, c2)
  353. }
  354. }
  355. return c
  356. }
  357. if c == 0 {
  358. // possibly doorway mode
  359. var tries int = 0
  360. c2 = d.getkey_or_pushback()
  361. for c2 < 0 {
  362. if tries > 7 {
  363. return c
  364. }
  365. c2 = d.getkey_or_pushback()
  366. tries++
  367. }
  368. switch c2 {
  369. case 0x50:
  370. return DOWN_ARROW
  371. case 0x48:
  372. return UP_ARROW
  373. case 0x4b:
  374. return LEFT_ARROW
  375. case 0x4d:
  376. return RIGHT_ARROW
  377. case 0x47:
  378. return HOME
  379. case 0x4f:
  380. return END
  381. case 0x49:
  382. return PAGE_UP
  383. case 0x51:
  384. return PAGE_DOWN
  385. case 0x3b:
  386. return F1
  387. case 0x3c:
  388. return F2
  389. case 0x3d:
  390. return F3
  391. case 0x3e:
  392. return F4
  393. case 0x3f:
  394. return F5
  395. case 0x40:
  396. return F6
  397. case 0x41:
  398. return F7
  399. case 0x42:
  400. return F8
  401. case 0x43:
  402. return F9
  403. case 0x44:
  404. return F10
  405. case 0x52:
  406. return INSERT
  407. case 0x53:
  408. return DELETE
  409. default:
  410. log.Printf("ERROR Doorway mode: 0x00 %x\n", c2)
  411. return XKEY_UNKNOWN
  412. }
  413. }
  414. if c == 0x1b {
  415. // Escape key?
  416. c2 = d.getkey_or_pushback()
  417. if c2 < 0 {
  418. // Just escape key
  419. return c
  420. }
  421. var extended []byte = make([]byte, 1)
  422. extended[0] = byte(c2) // string = string(byte(c2))
  423. c2 = d.getkey_or_pushback()
  424. for c2 > 0 {
  425. if c2 == 0x1b {
  426. d.Pushback.Push(c2)
  427. break
  428. }
  429. extended = append(extended, byte(c2)) // += string(byte(c2))
  430. var has bool
  431. c2, has = extendedKeys[string(extended)]
  432. if has {
  433. // break out here if \x1b[ + letter or @
  434. // break out if \x1b[ + digits + ~
  435. // break out if \x1bO + letter
  436. return c2
  437. }
  438. c2 = d.getkey_or_pushback()
  439. }
  440. if strings.HasPrefix(string(extended), "[M") && len(extended) == 5 {
  441. // log.Printf("MOUSE Extended %#v\n", extended)
  442. var mouse Mouse = Mouse{Button: int8(extended[2]) - ' ' + 1,
  443. X: int8(extended[3]) - '!' + 1,
  444. Y: int8(extended[4]) - '!' + 1}
  445. d.AddMouse(mouse)
  446. log.Printf("MOUSE %d (%d,%d)\n", mouse.Button, mouse.X, mouse.Y)
  447. return XKEY_MOUSE
  448. }
  449. log.Printf("ERROR Extended %#v\n", extended)
  450. return XKEY_UNKNOWN
  451. }
  452. return c
  453. }
  454. func (d *Door) Key() int {
  455. return d.WaitKey(Inactivity, 0)
  456. }
  457. // usecs = Microseconds
  458. func (d *Door) WaitKey(secs int64, usecs int64) int {
  459. if d.Disconnect() {
  460. return -2
  461. }
  462. if !d.Pushback.Empty() {
  463. return d.GetKey()
  464. }
  465. var timeout time.Duration = time.Duration(secs)*time.Second + time.Duration(usecs)*time.Microsecond
  466. select {
  467. case res, ok := <-d.readerChannel:
  468. if ok {
  469. d.Pushback.Push(int(res))
  470. return d.GetKey()
  471. } else {
  472. // Reader Closed
  473. d.Disconnected = true
  474. // If I wrap this with if !d.WriterClosed .. races ?
  475. // Why can't I do this? This isn't a go routine...
  476. if !d.WriterClosed {
  477. d.writerChannel <- ""
  478. }
  479. // d.closeChannel <- struct{}{}
  480. // atomic.StoreInt32(&d.Disconnected, 1)
  481. return -2
  482. }
  483. case <-time.After(timeout):
  484. return -1
  485. }
  486. }
  487. */
  488. // port over WaitKey
  489. func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
  490. // disconnected test
  491. d.readerMutex.Lock()
  492. if d.ReaderClosed {
  493. d.readerMutex.Unlock()
  494. return rune(0), Extended(0), ErrDisconnected
  495. }
  496. d.readerMutex.Unlock()
  497. if !d.Pushback.Empty() {
  498. log.Println("WaitKey: Pushback ! empty.")
  499. r, ex, e := d.GetKey()
  500. if DEBUG_INPUT {
  501. log.Println("WaitKey:", r, ex, e)
  502. }
  503. // return bio.GetKey()
  504. return r, ex, e
  505. }
  506. // expiration := time.NewTicker(timeout)
  507. // defer expiration.Stop()
  508. select {
  509. case r, ok := <-d.readerChannel:
  510. if ok {
  511. // Is this blocking? Ignoring expiration?
  512. d.RunePushback(r)
  513. log.Printf("readerChannel %#v ...\n", r)
  514. r, ex, e := d.GetKey()
  515. if DEBUG_INPUT {
  516. log.Println("WaitKey:", r, ex, e)
  517. }
  518. // return bio.GetKey()
  519. return r, ex, e
  520. } else {
  521. log.Println("WaitKey: Disconnected")
  522. // Reader has closed.
  523. // Disconnected = true
  524. return rune(0), Extended(0), ErrDisconnected
  525. }
  526. case <-time.After(timeout):
  527. return rune(0), Extended(0), ErrTimeout
  528. }
  529. }
  530. // Outputs spaces and backspaces
  531. // If you have set a background color, this shows the input area.
  532. func DisplayInput(max int) string {
  533. return strings.Repeat(" ", max) + strings.Repeat("\x08", max)
  534. }
  535. // Input a string of max length.
  536. // This displays the input area if a bg color was set.
  537. // This handles timeout, input, backspace, and enter.
  538. func (d *Door) Input(max int) string {
  539. var line string
  540. // draw input area
  541. d.Write(DisplayInput(max))
  542. var r rune
  543. var ex Extended
  544. var err error
  545. for {
  546. r, ex, err = d.WaitKey(DefaultTimeout)
  547. if err != nil {
  548. // timeout/hangup
  549. return ""
  550. }
  551. if ex != NOP {
  552. continue
  553. }
  554. if strconv.IsPrint(r) {
  555. if len(line) < max {
  556. d.Write(string(r))
  557. line += string(r)
  558. } else {
  559. d.Write("\x07")
  560. }
  561. } else {
  562. // Non-print
  563. switch r {
  564. case 0x7f, 0x08:
  565. if len(line) > 0 {
  566. d.Write("\x08 \x08")
  567. line = line[:len(line)-1]
  568. }
  569. case 0x0d:
  570. return line
  571. }
  572. }
  573. }
  574. }
  575. func (d *Door) GetOneOf(possible string) rune {
  576. var r rune
  577. var err error
  578. for {
  579. r, _, err = d.WaitKey(DefaultTimeout)
  580. if err != nil {
  581. return rune(0)
  582. }
  583. r := unicode.ToUpper(r)
  584. if strings.ContainsRune(possible, r) {
  585. // return upper case rune
  586. return r
  587. }
  588. /*
  589. c = strings.IndexRune(possible, r)
  590. if c != -1 {
  591. return c
  592. }
  593. */
  594. }
  595. }