input.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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. ALT_a
  38. ALT_b
  39. ALT_c
  40. ALT_d
  41. ALT_e
  42. ALT_f
  43. ALT_g
  44. ALT_h
  45. ALT_i
  46. ALT_j
  47. ALT_k
  48. ALT_l
  49. ALT_m
  50. ALT_n
  51. ALT_o
  52. ALT_p
  53. ALT_q
  54. ALT_r
  55. ALT_s
  56. ALT_t
  57. ALT_u
  58. ALT_v
  59. ALT_w
  60. ALT_x
  61. ALT_y
  62. ALT_z
  63. ALT_A
  64. ALT_B
  65. ALT_C
  66. ALT_D
  67. ALT_E
  68. ALT_F
  69. ALT_G
  70. ALT_H
  71. ALT_I
  72. ALT_J
  73. ALT_K
  74. ALT_L
  75. ALT_M
  76. ALT_N
  77. ALT_O
  78. ALT_P
  79. ALT_Q
  80. ALT_R
  81. ALT_S
  82. ALT_T
  83. ALT_U
  84. ALT_V
  85. ALT_W
  86. ALT_X
  87. ALT_Y
  88. ALT_Z
  89. UNKNOWN
  90. )
  91. //go:generate stringer -type=Extended
  92. const DEBUG_INPUT = true
  93. func extended_output(buffer []rune) string {
  94. var output string = string(buffer)
  95. output = strings.Replace(output, "\x1b", "^[", -1)
  96. return output
  97. }
  98. var ErrInactivity error = fmt.Errorf("Inactivity")
  99. var ErrTimeout error = fmt.Errorf("Timeout")
  100. var ErrDisconnected error = fmt.Errorf("Disconnected")
  101. var DefaultTimeout time.Duration = time.Duration(60) * time.Second
  102. var extendedKeys map[string]Extended = map[string]Extended{
  103. "[A": UP_ARROW,
  104. "[B": DOWN_ARROW,
  105. "[C": RIGHT_ARROW,
  106. "[D": LEFT_ARROW,
  107. "[H": HOME,
  108. "[F": END, // terminal
  109. "[K": END,
  110. "[V": PAGE_UP,
  111. "[U": PAGE_DOWN,
  112. "[@": INSERT,
  113. "[2~": INSERT, // terminal
  114. "[3~": DELETE, // terminal
  115. "[5~": PAGE_UP, // terminal
  116. "[6~": PAGE_DOWN, // terminal
  117. "[15~": F5, // terminal
  118. "[17~": F6, // terminal
  119. "[18~": F7, // terminal
  120. "[19~": F8, // terminal
  121. "[20~": F9, // terminal
  122. "[21~": F10, // terminal
  123. "[23~": F11,
  124. "[24~": F12, // terminal
  125. "OP": F1,
  126. "OQ": F2,
  127. "OR": F3,
  128. "OS": F4, // syncterm "[1" = F1,F2,F3, and F4)
  129. "Ot": F5, // syncterm
  130. }
  131. // ReadRune fails on IAC AYT => unicode.ReplacementChar X 2
  132. // getch -> ReadRune Low Level
  133. func (d *Door) ReadRune() (rune, error) {
  134. var r rune
  135. if d.ReaderClosed {
  136. return r, ErrDisconnected
  137. }
  138. for {
  139. select {
  140. case r = <-d.readerChannel:
  141. return r, nil
  142. case <-time.After(ReaderInterval):
  143. return r, ErrTimeout
  144. }
  145. }
  146. }
  147. func (d *Door) RunePushback(r rune) {
  148. d.Pushback.Push(r)
  149. }
  150. // getkey_or_pushback -> ReadRunePushback()
  151. func (d *Door) ReadRunePushback() (rune, error) {
  152. if !d.Pushback.Empty() {
  153. if DEBUG_INPUT {
  154. log.Println("Read From PushBack")
  155. }
  156. return d.Pushback.Pop(), nil
  157. }
  158. return d.ReadRune()
  159. }
  160. func RuneToInt8(r rune) int8 {
  161. return int8(r)
  162. }
  163. // Confusion - It's possible to return a null rune
  164. // that we received. So it's a little sloppy having
  165. // to check Extended == NOP. :(
  166. // High Level function - returns rune or extended
  167. func (d *Door) GetKey() (rune, Extended, error) {
  168. var r, r2 rune
  169. var err, err2 error
  170. var ex Extended
  171. if d.ReaderClosed {
  172. return r, ex, ErrDisconnected
  173. }
  174. // if bio.Disconnected() {
  175. // return r, ex, DisconnectedError
  176. // }
  177. r, err = d.ReadRunePushback()
  178. if err != nil {
  179. return r, ex, err
  180. }
  181. // fyneterm CR
  182. if r == '\x0a' {
  183. r2, err2 = d.ReadRunePushback()
  184. if err2 == nil {
  185. // Not an error
  186. if r2 != '\x00' && r2 != '\x0a' {
  187. // Wasn't 0x00 or 0x0a
  188. d.RunePushback(r2)
  189. }
  190. }
  191. return '\x0d', ex, nil
  192. }
  193. // We get 0x0d, 0x00, or 0x0d 0x0a from syncterm
  194. if r == '\x0d' {
  195. r2, err2 = d.ReadRunePushback()
  196. if err2 == nil {
  197. // Not an error
  198. if r2 != '\x00' && r2 != '\x0a' {
  199. // Wasn't 0x00 or 0x0a
  200. d.RunePushback(r2)
  201. }
  202. }
  203. return r, ex, nil
  204. }
  205. if r == '\x00' {
  206. // Possibly doorway mode - deprecated?
  207. // syncterm does support this, so it isn't entirely dead (NNY!)
  208. r2, _ = d.ReadRunePushback()
  209. r = rune(0)
  210. switch r2 {
  211. case '\x50':
  212. return r, DOWN_ARROW, nil
  213. case '\x48':
  214. return r, UP_ARROW, nil
  215. case '\x4b':
  216. return r, LEFT_ARROW, nil
  217. case 0x4d:
  218. return r, RIGHT_ARROW, nil
  219. case 0x47:
  220. return r, HOME, nil
  221. case 0x4f:
  222. return r, END, nil
  223. case 0x49:
  224. return r, PAGE_UP, nil
  225. case 0x51:
  226. return r, PAGE_DOWN, nil
  227. case 0x3b:
  228. return r, F1, nil
  229. case 0x3c:
  230. return r, F2, nil
  231. case 0x3d:
  232. return r, F3, nil
  233. case 0x3e:
  234. return r, F4, nil
  235. case 0x3f:
  236. return r, F5, nil
  237. case 0x40:
  238. return r, F6, nil
  239. case 0x41:
  240. return r, F7, nil
  241. case 0x42:
  242. return r, F8, nil
  243. case 0x43:
  244. return r, F9, nil
  245. case 0x44:
  246. return r, F10, nil
  247. case 0x45:
  248. return r, F11, nil
  249. case 0x46:
  250. return r, F12, nil
  251. case 0x52:
  252. return r, INSERT, nil
  253. case 0x53:
  254. return r, DELETE, nil
  255. default:
  256. log.Printf("ERROR Doorway mode: 0x00 %x\n", r2)
  257. return r, UNKNOWN, nil
  258. }
  259. } // End doorway
  260. if r == '\x1b' {
  261. // Escape key?
  262. r2, err2 = d.ReadRunePushback()
  263. if err2 != nil {
  264. // Just escape key
  265. return r, ex, nil
  266. }
  267. if r2 == '\x1b' {
  268. d.RunePushback(r2)
  269. return r, ex, nil
  270. }
  271. if unicode.IsLetter(r2) {
  272. // ALT-KEY
  273. if unicode.IsLower(r2) {
  274. // Lower case
  275. ex = Extended(int(ALT_a) + int(r2-'a'))
  276. return r, ex, nil
  277. } else {
  278. // Upper case
  279. ex = Extended(int(ALT_A) + int(r2-'A'))
  280. return r, ex, nil
  281. }
  282. }
  283. var extended []rune = make([]rune, 0, 10)
  284. extended = append(extended, r2)
  285. // extended[0] = r2
  286. var IsMouse bool = false
  287. r2, err2 = d.ReadRunePushback()
  288. for err2 == nil {
  289. if r2 == '\x1b' {
  290. // This is the end of the extended code.
  291. d.RunePushback(r2)
  292. break
  293. }
  294. extended = append(extended, r2)
  295. ext, has := extendedKeys[string(extended)]
  296. if has {
  297. // Found it! Return extended code.
  298. return rune(0), ext, nil
  299. }
  300. // Mouse codes can also contain letters.
  301. if !IsMouse && unicode.IsLetter(r2) {
  302. // The end of the extended code
  303. // Unless this is Mouse!
  304. if string(extended) == "[M" {
  305. IsMouse = true
  306. } else {
  307. break
  308. }
  309. }
  310. if IsMouse && len(extended) == 5 {
  311. break
  312. }
  313. r2, err2 = d.ReadRunePushback()
  314. }
  315. log.Printf("Extended Code: [%s]", extended_output(extended))
  316. var exString string = string(extended)
  317. if strings.HasPrefix(exString, "[M") && len(extended) == 5 {
  318. // Mouse Extended - input zero based (I add +1 to X, Y, and button)
  319. var mouse Mouse = Mouse{Button: RuneToInt8(extended[2]) - ' ' + 1,
  320. X: RuneToInt8(extended[3]) - '!' + 1,
  321. Y: RuneToInt8(extended[4]) - '!' + 1}
  322. d.mcMutex.Lock()
  323. defer d.mcMutex.Unlock()
  324. d.LastMouse = append(d.LastMouse, mouse)
  325. log.Println("Mouse:", mouse)
  326. return rune(0), MOUSE, nil
  327. }
  328. if strings.HasSuffix(exString, "R") {
  329. // Cursor Position information
  330. var cursor CursorPos
  331. // ^[[1;1R^[[2;3R^[[41;173R
  332. // Y;X
  333. exString = exString[1 : len(exString)-1] // Remove [ and R
  334. pos := SplitToInt(exString, ";")
  335. if len(pos) == 2 {
  336. cursor.X = pos[1]
  337. cursor.Y = pos[0]
  338. d.mcMutex.Lock()
  339. defer d.mcMutex.Unlock()
  340. d.LastCursor = append(d.LastCursor, cursor)
  341. log.Println("Cursor Pos:", cursor)
  342. return rune(0), CURSOR, nil
  343. } else {
  344. log.Println("ERROR Cursor:", extended)
  345. return rune(0), UNKNOWN, nil
  346. }
  347. }
  348. if exString == "\x1b" {
  349. return extended[0], Extended(0), nil
  350. }
  351. log.Println("ERROR Extended:", extended)
  352. return rune(0), UNKNOWN, nil
  353. }
  354. // AYT (Telnet Are You There?)
  355. /*
  356. if r == '\xff' {
  357. log.Printf("IAC")
  358. r2, err2 = bio.ReadRunePushback()
  359. if err2 != nil {
  360. // Return value
  361. return r, ex, nil
  362. }
  363. if r2 == '\xf6' {
  364. // Are You There?
  365. log.Println("AYT? - Yes")
  366. bio.Write("Yes?")
  367. // bio.Write(string([]byte{0x00})) // 0xff, 0xf2}))
  368. // Eat these keys, and re-call ourselves...
  369. // Or goto very top?
  370. return bio.GetKey()
  371. }
  372. }
  373. */
  374. return r, ex, nil
  375. }
  376. // "[1": XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?
  377. // port over WaitKey
  378. func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
  379. // disconnected test
  380. d.readerMutex.Lock()
  381. if d.ReaderClosed {
  382. d.readerMutex.Unlock()
  383. return rune(0), Extended(0), ErrDisconnected
  384. }
  385. d.readerMutex.Unlock()
  386. if !d.Pushback.Empty() {
  387. log.Println("WaitKey: Pushback ! empty.")
  388. r, ex, e := d.GetKey()
  389. if DEBUG_INPUT {
  390. log.Println("WaitKey:", r, ex, e)
  391. }
  392. // return bio.GetKey()
  393. return r, ex, e
  394. }
  395. // expiration := time.NewTicker(timeout)
  396. // defer expiration.Stop()
  397. select {
  398. case r, ok := <-d.readerChannel:
  399. if ok {
  400. // Is this blocking? Ignoring expiration?
  401. d.RunePushback(r)
  402. log.Printf("readerChannel %#v ...\n", r)
  403. r, ex, e := d.GetKey()
  404. if DEBUG_INPUT {
  405. log.Println("WaitKey:", r, ex, e)
  406. }
  407. // return bio.GetKey()
  408. return r, ex, e
  409. } else {
  410. log.Println("WaitKey: Disconnected")
  411. // Reader has closed.
  412. // Disconnected = true
  413. return rune(0), Extended(0), ErrDisconnected
  414. }
  415. case <-time.After(timeout):
  416. return rune(0), Extended(0), ErrTimeout
  417. }
  418. }
  419. // Outputs spaces and backspaces
  420. // If you have set a background color, this shows the input area.
  421. func DisplayInput(max int) string {
  422. return strings.Repeat(" ", max) + strings.Repeat("\x08", max)
  423. }
  424. // Input a string of max length.
  425. // This displays the input area if a bg color was set.
  426. // This handles timeout, input, backspace, and enter.
  427. func (d *Door) Input(max int) string {
  428. var line []rune = make([]rune, 0, max)
  429. var length int
  430. // draw input area
  431. d.Write(DisplayInput(max))
  432. var r rune
  433. var ex Extended
  434. var err error
  435. for {
  436. r, ex, err = d.WaitKey(DefaultTimeout)
  437. if err != nil {
  438. // timeout/hangup
  439. return ""
  440. }
  441. if ex != NOP {
  442. continue
  443. }
  444. uw := UnicodeWidth(r)
  445. if strconv.IsPrint(r) {
  446. if length+uw <= max {
  447. d.Write(string(r))
  448. line = append(line, r)
  449. length += uw
  450. } else {
  451. d.Write("\x07")
  452. }
  453. } else {
  454. // Non-print
  455. switch r {
  456. case 0x7f, 0x08:
  457. if len(line) > 0 {
  458. d.Write("\x08 \x08")
  459. rlen := len(line)
  460. if UnicodeWidth(line[rlen-1]) == 2 {
  461. d.Write("\x08 \x08")
  462. length -= 2
  463. } else {
  464. length--
  465. }
  466. line = line[0 : rlen-1]
  467. }
  468. case 0x0d:
  469. return string(line)
  470. }
  471. }
  472. }
  473. }
  474. func (d *Door) GetOneOf(possible string) rune {
  475. var r rune
  476. var err error
  477. for {
  478. r, _, err = d.WaitKey(DefaultTimeout)
  479. if err != nil {
  480. return rune(0)
  481. }
  482. r := unicode.ToUpper(r)
  483. if strings.ContainsRune(possible, r) {
  484. // return upper case rune
  485. return r
  486. }
  487. /*
  488. c = strings.IndexRune(possible, r)
  489. if c != -1 {
  490. return c
  491. }
  492. */
  493. }
  494. }