package door import ( "log" "strconv" "strings" "time" "unicode" ) // This is neat, would be neater if it WORKED! go:generate stringer -type=Extended type Extended int8 const ( NOP Extended = iota UP_ARROW DOWN_ARROW RIGHT_ARROW LEFT_ARROW HOME END PAGE_UP PAGE_DOWN INSERT DELETE F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 MOUSE CURSOR UNKNOWN ) // To rebuild this, copy the above into it's own file and generate. // Then, copy the generated code to below: const _Extended_name = "NOPUP_ARROWDOWN_ARROWRIGHT_ARROWLEFT_ARROWHOMEENDPAGE_UPPAGE_DOWNINSERTDELETEF1F2F3F4F5F6F7F8F9F10F11F12MOUSECURSORUNKNOWN" 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} func (i Extended) String() string { if i < 0 || i >= Extended(len(_Extended_index)-1) { return "Extended(" + strconv.FormatInt(int64(i), 10) + ")" } return _Extended_name[_Extended_index[i]:_Extended_index[i+1]] } // This is the current list of Extended keys we support: const ( XKEY_UP_ARROW = 0x1001 XKEY_DOWN_ARROW = 0x1002 XKEY_RIGHT_ARROW = 0x1003 XKEY_LEFT_ARROW = 0x1004 XKEY_HOME = 0x1010 XKEY_END = 0x1011 XKEY_PGUP = 0x1012 XKEY_PGDN = 0x1023 XKEY_INSERT = 0x1024 XKEY_DELETE = 0x7f XKEY_F1 = 0x1021 XKEY_F2 = 0x1022 XKEY_F3 = 0x1023 XKEY_F4 = 0x1024 XKEY_F5 = 0x1025 XKEY_F6 = 0x1026 XKEY_F7 = 0x1027 XKEY_F8 = 0x1028 XKEY_F9 = 0x1029 XKEY_F10 = 0x102a XKEY_F11 = 0x102b XKEY_F12 = 0x102c XKEY_MOUSE = 0x1100 XKEY_UNKNOWN = 0x1111 ) var extendedKeys map[string]int = map[string]int{ "[A": XKEY_UP_ARROW, "[B": XKEY_DOWN_ARROW, "[C": XKEY_RIGHT_ARROW, "[D": XKEY_LEFT_ARROW, "[H": XKEY_HOME, "[F": XKEY_END, // terminal "[K": XKEY_END, "[V": XKEY_PGUP, "[U": XKEY_PGDN, "[@": XKEY_INSERT, "[2~": XKEY_INSERT, // terminal "[3~": XKEY_DELETE, // terminal "[5~": XKEY_PGUP, // terminal "[6~": XKEY_PGDN, // terminal "[15~": XKEY_F5, // terminal "[17~": XKEY_F6, // terminal "[18~": XKEY_F7, // terminal "[19~": XKEY_F8, // terminal "[20~": XKEY_F9, // terminal "[21~": XKEY_F10, // terminal "[23~": XKEY_F11, "[24~": XKEY_F12, // terminal "OP": XKEY_F1, "OQ": XKEY_F2, "OR": XKEY_F3, "OS": XKEY_F4, "Ot": XKEY_F5, // syncterm } // "[1": XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5? // Low level read key function. // This gets the raw keys from the client, it doesn't handle extended keys, // functions, arrows. // Return key, or -1 (Timeout/No key available), -2 hangup func (d *Door) getch() int { select { case res, ok := <-d.readerChannel: if ok { return int(res) } else { d.Disconnected = true // atomic.StoreInt32(&d.Disconnected, 1) return -2 } case <-time.After(time.Duration(100) * time.Millisecond): return -1 } } func (d *Door) getkey_or_pushback() int { if !d.Pushback.Empty() { return d.Pushback.Pop() } if false { var key int = d.getch() log.Printf("%d / %X\n", key, key) return key } else { return d.getch() } } // Return key received, or XKEY_* values. // -1 timeout/no key // -2 hangup // -3 out of time func (d *Door) GetKey() int { var c, c2 int if d.Disconnect() { return -2 } if d.TimeLeft() < 0 { return -3 } c = d.getkey_or_pushback() if c < 0 { return c } // We get 0x0d 0x00, or 0x0d 0x0a from syncterm. if c == 0x0d { c2 = d.getkey_or_pushback() if c2 > 0 { // wasn't an error if c2 != 0x00 && c2 != 0x0a { // wasn't 0x00 or 0x0a d.Pushback.Push(c2) // log.Printf("Push 0x0d trailer %d / %x\n", c2, c2) } } return c } if c == 0 { // possibly doorway mode var tries int = 0 c2 = d.getkey_or_pushback() for c2 < 0 { if tries > 7 { return c } c2 = d.getkey_or_pushback() tries++ } switch c2 { case 0x50: return XKEY_DOWN_ARROW case 0x48: return XKEY_UP_ARROW case 0x4b: return XKEY_LEFT_ARROW case 0x4d: return XKEY_RIGHT_ARROW case 0x47: return XKEY_HOME case 0x4f: return XKEY_END case 0x49: return XKEY_PGUP case 0x51: return XKEY_PGDN case 0x3b: return XKEY_F1 case 0x3c: return XKEY_F2 case 0x3d: return XKEY_F3 case 0x3e: return XKEY_F4 case 0x3f: return XKEY_F5 case 0x40: return XKEY_F6 case 0x41: return XKEY_F7 case 0x42: return XKEY_F8 case 0x43: return XKEY_F9 case 0x44: return XKEY_F10 /* case 0x45: return XKEY_F11 case 0x46: return XKEY_F12 */ case 0x52: return XKEY_INSERT case 0x53: return XKEY_DELETE default: log.Printf("ERROR Doorway mode: 0x00 %x\n", c2) return XKEY_UNKNOWN } } if c == 0x1b { // Escape key? c2 = d.getkey_or_pushback() if c2 < 0 { // Just escape key return c } var extended []byte = make([]byte, 1) extended[0] = byte(c2) // string = string(byte(c2)) c2 = d.getkey_or_pushback() for c2 > 0 { if c2 == 0x1b { d.Pushback.Push(c2) break } extended = append(extended, byte(c2)) // += string(byte(c2)) var has bool c2, has = extendedKeys[string(extended)] if has { // break out here if \x1b[ + letter or @ // break out if \x1b[ + digits + ~ // break out if \x1bO + letter return c2 } c2 = d.getkey_or_pushback() } if strings.HasPrefix(string(extended), "[M") && len(extended) == 5 { // log.Printf("MOUSE Extended %#v\n", extended) var mouse Mouse = Mouse{Button: int8(extended[2]) - ' ' + 1, X: int8(extended[3]) - '!' + 1, Y: int8(extended[4]) - '!' + 1} d.AddMouse(mouse) log.Printf("MOUSE %d (%d,%d)\n", mouse.Button, mouse.X, mouse.Y) return XKEY_MOUSE } log.Printf("ERROR Extended %#v\n", extended) return XKEY_UNKNOWN } return c } func (d *Door) Key() int { return d.WaitKey(Inactivity, 0) } // usecs = Microseconds func (d *Door) WaitKey(secs int64, usecs int64) int { if d.Disconnect() { return -2 } if !d.Pushback.Empty() { return d.GetKey() } var timeout time.Duration = time.Duration(secs)*time.Second + time.Duration(usecs)*time.Microsecond select { case res, ok := <-d.readerChannel: if ok { d.Pushback.Push(int(res)) return d.GetKey() } else { // Reader Closed d.Disconnected = true // If I wrap this with if !d.WriterClosed .. races ? // Why can't I do this? This isn't a go routine... if !d.WriterClosed { d.writerChannel <- "" } // d.closeChannel <- struct{}{} // atomic.StoreInt32(&d.Disconnected, 1) return -2 } case <-time.After(timeout): return -1 } } // Outputs spaces and backspaces // If you have set a background color, this shows the input area. func DisplayInput(max int) string { return strings.Repeat(" ", max) + strings.Repeat("\x08", max) } // Input a string of max length. // This displays the input area if a bg color was set. // This handles timeout, input, backspace, and enter. func (d *Door) Input(max int) string { var line string // draw input area d.Write(DisplayInput(max)) var c int for { c = d.WaitKey(Inactivity, 0) if c < 0 { // timeout/hangup return "" } if c > 1000 { continue } if strconv.IsPrint(rune(c)) { if len(line) < max { d.Write(string(byte(c))) line += string(byte(c)) } else { d.Write("\x07") } } else { // Non-print switch c { case 0x7f, 0x08: if len(line) > 0 { d.Write("\x08 \x08") line = line[:len(line)-1] } case 0x0d: return line } } } } func (d *Door) GetOneOf(possible string) int { var c int for { c = d.WaitKey(Inactivity, 0) if c < 0 { return c } r := unicode.ToUpper(rune(c)) if strings.ContainsRune(possible, r) { // return upper case rune return int(r) } /* c = strings.IndexRune(possible, r) if c != -1 { return c } */ } }