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. case 0x45:
  191. return r, F11, nil
  192. case 0x46:
  193. return r, F12, nil
  194. case 0x52:
  195. return r, INSERT, nil
  196. case 0x53:
  197. return r, DELETE, nil
  198. default:
  199. log.Printf("ERROR Doorway mode: 0x00 %x\n", r2)
  200. return r, UNKNOWN, nil
  201. }
  202. } // End doorway
  203. if r == '\x1b' {
  204. // Escape key?
  205. r2, err2 = d.ReadRunePushback()
  206. if err2 != nil {
  207. // Just escape key
  208. return r, ex, nil
  209. }
  210. if r2 == '\x1b' {
  211. d.RunePushback(r2)
  212. return r, ex, nil
  213. }
  214. var extended []rune = make([]rune, 1)
  215. extended[0] = r2
  216. r2, err2 = d.ReadRunePushback()
  217. for err2 == nil {
  218. if r2 == '\x1b' {
  219. // This is the end of the extended code.
  220. d.RunePushback(r2)
  221. break
  222. }
  223. extended = append(extended, r2)
  224. ext, has := extendedKeys[string(extended)]
  225. if has {
  226. return rune(0), ext, nil
  227. }
  228. if unicode.IsLetter(r2) {
  229. // The end of the extended code
  230. break
  231. }
  232. r2, err2 = d.ReadRunePushback()
  233. }
  234. var exString string = string(extended)
  235. if strings.HasPrefix(exString, "[M") && len(extended) == 5 {
  236. // Mouse Extended - input zero based (I add +1 to X, Y, and button)
  237. var mouse Mouse = Mouse{Button: RuneToInt8(extended[2]) - ' ' + 1,
  238. X: RuneToInt8(extended[3]) - '!' + 1,
  239. Y: RuneToInt8(extended[4]) - '!' + 1}
  240. d.mcMutex.Lock()
  241. defer d.mcMutex.Unlock()
  242. d.LastMouse = append(d.LastMouse, mouse)
  243. log.Println("Mouse:", mouse)
  244. return rune(0), MOUSE, nil
  245. }
  246. if strings.HasSuffix(exString, "R") {
  247. // Cursor Position information
  248. var cursor CursorPos
  249. // ^[[1;1R^[[2;3R^[[41;173R
  250. // Y;X
  251. exString = exString[1 : len(exString)-1] // Remove [ and R
  252. pos := SplitToInt(exString, ";")
  253. if len(pos) == 2 {
  254. cursor.X = pos[1]
  255. cursor.Y = pos[0]
  256. d.mcMutex.Lock()
  257. defer d.mcMutex.Unlock()
  258. d.LastCursor = append(d.LastCursor, cursor)
  259. log.Println("Cursor Pos:", cursor)
  260. return rune(0), CURSOR, nil
  261. } else {
  262. log.Println("ERROR Cursor:", extended)
  263. return rune(0), UNKNOWN, nil
  264. }
  265. }
  266. if exString == "\x1b" {
  267. return extended[0], Extended(0), nil
  268. }
  269. log.Println("ERROR Extended:", extended)
  270. return rune(0), UNKNOWN, nil
  271. }
  272. // AYT (Telnet Are You There?)
  273. /*
  274. if r == '\xff' {
  275. log.Printf("IAC")
  276. r2, err2 = bio.ReadRunePushback()
  277. if err2 != nil {
  278. // Return value
  279. return r, ex, nil
  280. }
  281. if r2 == '\xf6' {
  282. // Are You There?
  283. log.Println("AYT? - Yes")
  284. bio.Write("Yes?")
  285. // bio.Write(string([]byte{0x00})) // 0xff, 0xf2}))
  286. // Eat these keys, and re-call ourselves...
  287. // Or goto very top?
  288. return bio.GetKey()
  289. }
  290. }
  291. */
  292. return r, ex, nil
  293. }
  294. // "[1": XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?
  295. // Low level read key function.
  296. // This gets the raw keys from the client, it doesn't handle extended keys,
  297. // functions, arrows.
  298. // Return key, or -1 (Timeout/No key available), -2 hangup
  299. /*
  300. func (d *Door) getch() int {
  301. select {
  302. case res, ok := <-d.readerChannel:
  303. if ok {
  304. return int(res)
  305. } else {
  306. d.Disconnected = true
  307. // atomic.StoreInt32(&d.Disconnected, 1)
  308. return -2
  309. }
  310. case <-time.After(time.Duration(100) * time.Millisecond):
  311. return -1
  312. }
  313. }
  314. func (d *Door) getkey_or_pushback() int {
  315. if !d.Pushback.Empty() {
  316. return d.Pushback.Pop()
  317. }
  318. if false {
  319. var key int = d.getch()
  320. log.Printf("%d / %X\n", key, key)
  321. return key
  322. } else {
  323. return d.getch()
  324. }
  325. }
  326. // Return key received, or XKEY_* values.
  327. // -1 timeout/no key
  328. // -2 hangup
  329. // -3 out of time
  330. func (d *Door) GetKey() int {
  331. var c, c2 int
  332. if d.Disconnect() {
  333. return -2
  334. }
  335. if d.TimeLeft() < 0 {
  336. return -3
  337. }
  338. c = d.getkey_or_pushback()
  339. if c < 0 {
  340. return c
  341. }
  342. // We get 0x0d 0x00, or 0x0d 0x0a from syncterm.
  343. if c == 0x0d {
  344. c2 = d.getkey_or_pushback()
  345. if c2 > 0 {
  346. // wasn't an error
  347. if c2 != 0x00 && c2 != 0x0a {
  348. // wasn't 0x00 or 0x0a
  349. d.Pushback.Push(c2)
  350. // log.Printf("Push 0x0d trailer %d / %x\n", c2, c2)
  351. }
  352. }
  353. return c
  354. }
  355. if c == 0 {
  356. // possibly doorway mode
  357. var tries int = 0
  358. c2 = d.getkey_or_pushback()
  359. for c2 < 0 {
  360. if tries > 7 {
  361. return c
  362. }
  363. c2 = d.getkey_or_pushback()
  364. tries++
  365. }
  366. switch c2 {
  367. case 0x50:
  368. return DOWN_ARROW
  369. case 0x48:
  370. return UP_ARROW
  371. case 0x4b:
  372. return LEFT_ARROW
  373. case 0x4d:
  374. return RIGHT_ARROW
  375. case 0x47:
  376. return HOME
  377. case 0x4f:
  378. return END
  379. case 0x49:
  380. return PAGE_UP
  381. case 0x51:
  382. return PAGE_DOWN
  383. case 0x3b:
  384. return F1
  385. case 0x3c:
  386. return F2
  387. case 0x3d:
  388. return F3
  389. case 0x3e:
  390. return F4
  391. case 0x3f:
  392. return F5
  393. case 0x40:
  394. return F6
  395. case 0x41:
  396. return F7
  397. case 0x42:
  398. return F8
  399. case 0x43:
  400. return F9
  401. case 0x44:
  402. return F10
  403. case 0x52:
  404. return INSERT
  405. case 0x53:
  406. return DELETE
  407. default:
  408. log.Printf("ERROR Doorway mode: 0x00 %x\n", c2)
  409. return XKEY_UNKNOWN
  410. }
  411. }
  412. if c == 0x1b {
  413. // Escape key?
  414. c2 = d.getkey_or_pushback()
  415. if c2 < 0 {
  416. // Just escape key
  417. return c
  418. }
  419. var extended []byte = make([]byte, 1)
  420. extended[0] = byte(c2) // string = string(byte(c2))
  421. c2 = d.getkey_or_pushback()
  422. for c2 > 0 {
  423. if c2 == 0x1b {
  424. d.Pushback.Push(c2)
  425. break
  426. }
  427. extended = append(extended, byte(c2)) // += string(byte(c2))
  428. var has bool
  429. c2, has = extendedKeys[string(extended)]
  430. if has {
  431. // break out here if \x1b[ + letter or @
  432. // break out if \x1b[ + digits + ~
  433. // break out if \x1bO + letter
  434. return c2
  435. }
  436. c2 = d.getkey_or_pushback()
  437. }
  438. if strings.HasPrefix(string(extended), "[M") && len(extended) == 5 {
  439. // log.Printf("MOUSE Extended %#v\n", extended)
  440. var mouse Mouse = Mouse{Button: int8(extended[2]) - ' ' + 1,
  441. X: int8(extended[3]) - '!' + 1,
  442. Y: int8(extended[4]) - '!' + 1}
  443. d.AddMouse(mouse)
  444. log.Printf("MOUSE %d (%d,%d)\n", mouse.Button, mouse.X, mouse.Y)
  445. return XKEY_MOUSE
  446. }
  447. log.Printf("ERROR Extended %#v\n", extended)
  448. return XKEY_UNKNOWN
  449. }
  450. return c
  451. }
  452. func (d *Door) Key() int {
  453. return d.WaitKey(Inactivity, 0)
  454. }
  455. // usecs = Microseconds
  456. func (d *Door) WaitKey(secs int64, usecs int64) int {
  457. if d.Disconnect() {
  458. return -2
  459. }
  460. if !d.Pushback.Empty() {
  461. return d.GetKey()
  462. }
  463. var timeout time.Duration = time.Duration(secs)*time.Second + time.Duration(usecs)*time.Microsecond
  464. select {
  465. case res, ok := <-d.readerChannel:
  466. if ok {
  467. d.Pushback.Push(int(res))
  468. return d.GetKey()
  469. } else {
  470. // Reader Closed
  471. d.Disconnected = true
  472. // If I wrap this with if !d.WriterClosed .. races ?
  473. // Why can't I do this? This isn't a go routine...
  474. if !d.WriterClosed {
  475. d.writerChannel <- ""
  476. }
  477. // d.closeChannel <- struct{}{}
  478. // atomic.StoreInt32(&d.Disconnected, 1)
  479. return -2
  480. }
  481. case <-time.After(timeout):
  482. return -1
  483. }
  484. }
  485. */
  486. // port over WaitKey
  487. func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
  488. // disconnected test
  489. d.readerMutex.Lock()
  490. if d.ReaderClosed {
  491. d.readerMutex.Unlock()
  492. return rune(0), Extended(0), ErrDisconnected
  493. }
  494. d.readerMutex.Unlock()
  495. if !d.Pushback.Empty() {
  496. log.Println("WaitKey: Pushback ! empty.")
  497. r, ex, e := d.GetKey()
  498. if DEBUG_INPUT {
  499. log.Println("WaitKey:", r, ex, e)
  500. }
  501. // return bio.GetKey()
  502. return r, ex, e
  503. }
  504. // expiration := time.NewTicker(timeout)
  505. // defer expiration.Stop()
  506. select {
  507. case r, ok := <-d.readerChannel:
  508. if ok {
  509. // Is this blocking? Ignoring expiration?
  510. d.RunePushback(r)
  511. log.Printf("readerChannel %#v ...\n", r)
  512. r, ex, e := d.GetKey()
  513. if DEBUG_INPUT {
  514. log.Println("WaitKey:", r, ex, e)
  515. }
  516. // return bio.GetKey()
  517. return r, ex, e
  518. } else {
  519. log.Println("WaitKey: Disconnected")
  520. // Reader has closed.
  521. // Disconnected = true
  522. return rune(0), Extended(0), ErrDisconnected
  523. }
  524. case <-time.After(timeout):
  525. return rune(0), Extended(0), ErrTimeout
  526. }
  527. }
  528. // Outputs spaces and backspaces
  529. // If you have set a background color, this shows the input area.
  530. func DisplayInput(max int) string {
  531. return strings.Repeat(" ", max) + strings.Repeat("\x08", max)
  532. }
  533. // Input a string of max length.
  534. // This displays the input area if a bg color was set.
  535. // This handles timeout, input, backspace, and enter.
  536. func (d *Door) Input(max int) string {
  537. var line string
  538. // draw input area
  539. d.Write(DisplayInput(max))
  540. var r rune
  541. var ex Extended
  542. var err error
  543. for {
  544. r, ex, err = d.WaitKey(DefaultTimeout)
  545. if err != nil {
  546. // timeout/hangup
  547. return ""
  548. }
  549. if ex != NOP {
  550. continue
  551. }
  552. if strconv.IsPrint(r) {
  553. if len(line) < max {
  554. d.Write(string(r))
  555. line += string(r)
  556. } else {
  557. d.Write("\x07")
  558. }
  559. } else {
  560. // Non-print
  561. switch r {
  562. case 0x7f, 0x08:
  563. if len(line) > 0 {
  564. d.Write("\x08 \x08")
  565. line = line[:len(line)-1]
  566. }
  567. case 0x0d:
  568. return line
  569. }
  570. }
  571. }
  572. }
  573. func (d *Door) GetOneOf(possible string) rune {
  574. var r rune
  575. var err error
  576. for {
  577. r, _, err = d.WaitKey(DefaultTimeout)
  578. if err != nil {
  579. return rune(0)
  580. }
  581. r := unicode.ToUpper(r)
  582. if strings.ContainsRune(possible, r) {
  583. // return upper case rune
  584. return r
  585. }
  586. /*
  587. c = strings.IndexRune(possible, r)
  588. if c != -1 {
  589. return c
  590. }
  591. */
  592. }
  593. }