package door import ( "fmt" "strconv" "strings" "syscall" ) // 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_UNKNOWN = 0x1111 ) // from: https://github.com/yubo/dea_ng // https://github.com/yubo/dea_ng/blob/master/go/src/directoryserver/streaming.go func set(fdSetPtr *syscall.FdSet, fd int) { (*fdSetPtr).Bits[fd/64] |= 1 << uint64(fd%64) } func isSet(fdSetPtr *syscall.FdSet, fd int) bool { return ((*fdSetPtr).Bits[fd/64] & (1 << uint64(fd%64))) != 0 } func clearAll(fdSetPtr *syscall.FdSet) { for index, _ := range (*fdSetPtr).Bits { (*fdSetPtr).Bits[index] = 0 } } // Is there a key waiting? // Returns true on key, or if hungup/time out func (d *Door) HasKey() bool { if d.Disconnected { return true } if d.TimeLeft() < 0 { return true } var fdsetRead = syscall.FdSet{} clearAll(&fdsetRead) set(&fdsetRead, d.READFD) timeout := syscall.Timeval{Sec: 0, Usec: 1} v, _ := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout) if v == -1 { return false } if v == 0 { return false } return true } // 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 { var fdsetRead syscall.FdSet clearAll(&fdsetRead) set(&fdsetRead, d.READFD) // 100 Usec seems like a good value, works with QModem in dosbox. timeout := syscall.Timeval{Sec: 0, Usec: 100} v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout) if v == -1 { // hangup return -2 } if v == 0 { // timeout return -1 } buffer := make([]byte, 1) r, err := syscall.Read(d.READFD, buffer) if r != 1 { // I'm getting write error here... (when disconnected) fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err) // hangup d.Disconnected = true return -2 } return int(buffer[0]) } func (d *Door) getkey_or_pushback() int { if d.pushback.Len() != 0 { e := d.pushback.Front() d.pushback.Remove(e) return e.Value.(int) } 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.Disconnected { 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.PushFront(c2) } } return c } if c == 0 { // possibly doorway mode tries := 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: fmt.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 string = string(c2) c2 = d.getkey_or_pushback() for c2 > 0 { if c2 == 0x1b { d.pushback.PushBack(c2) break } extended += string(c2) c2 = d.getkey_or_pushback() } switch extended { case "[A": return XKEY_UP_ARROW case "[B": return XKEY_DOWN_ARROW case "[C": return XKEY_RIGHT_ARROW case "[D": return XKEY_LEFT_ARROW case "[H": return XKEY_HOME case "[F": return XKEY_END // terminal case "[K": return XKEY_END case "[V": return XKEY_PGUP case "[U": return XKEY_PGDN case "[@": return XKEY_INSERT case "[1": // Syncterm is lost, could be F1..F5? fmt.Printf("ERROR (Syncterm) Extended %#v\n", extended) return XKEY_UNKNOWN case "[2~": return XKEY_INSERT // terminal case "[3~": return XKEY_DELETE // terminal case "[5~": return XKEY_PGUP // terminal case "[6~": return XKEY_PGDN // terminal case "[15~": return XKEY_F5 // terminal case "[17~": return XKEY_F6 // terminal case "[18~": return XKEY_F7 // terminal case "[19~": return XKEY_F8 // terminal case "[20~": return XKEY_F9 // terminal case "[21~": return XKEY_F10 // terminal case "[23~": return XKEY_F11 case "[24~": return XKEY_F12 // terminal case "OP": return XKEY_F1 case "OQ": return XKEY_F2 case "OR": return XKEY_F3 case "OS": return XKEY_F4 case "Ot": return XKEY_F5 // syncterm default: fmt.Printf("ERROR Extended %#v\n", extended) return XKEY_UNKNOWN } } return c } func (d *Door) WaitKey(secs int64, usecs int64) int { if d.Disconnected { return -2 } // var fdsetRead, fdsetWrite, fdsete syscall.FdSet var fdsetRead syscall.FdSet // fdsetWrite := syscall.FdSet clearAll(&fdsetRead) // clearAll(&fdsetWrite) // clearAll(&fdsete) set(&fdsetRead, d.READFD) // timeout := syscall.Timeval{Sec: 0, Usec: 100} timeout := syscall.Timeval{Sec: secs, Usec: usecs} v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout) if v == -1 { errno, ok := err.(syscall.Errno) if ok && errno == syscall.EINTR { return -1 } // Interrupted system call ??? What does this mean? // This isn't hangup. I'm still connected. // -1 : interrupted system call fmt.Println("-1 : ", err) // hangup ?! return -2 } if v == 0 { // timeout return -1 } return d.GetKey() // var buffer []byte -- 0 byte buffer. doh! buffer := make([]byte, 1) r, err := syscall.Read(d.READFD, buffer) if r != 1 { // I'm getting write error here... (when disconnected) fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err) // hangup d.Disconnected = true return -2 } return int(buffer[0]) } // 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(strings.Repeat(" ", max) + strings.Repeat("\x08", max)) var c int for true { 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(c)) line += string(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 } } } // this is never reached return line }