| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 | package doorimport (	"fmt"	"log"	"strconv"	"strings"	"time"	"unicode")type Extended int8const (	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)//go:generate stringer -type=Extendedconst DEBUG_INPUT = truevar ErrInactivity error = fmt.Errorf("Inactivity")var ErrTimeout error = fmt.Errorf("Timeout")var ErrDisconnected error = fmt.Errorf("Disconnected")var DefaultTimeout time.Duration = time.Duration(60) * time.Secondvar extendedKeys map[string]Extended = map[string]Extended{	"[A":   UP_ARROW,	"[B":   DOWN_ARROW,	"[C":   RIGHT_ARROW,	"[D":   LEFT_ARROW,	"[H":   HOME,	"[F":   END, // terminal	"[K":   END,	"[V":   PAGE_UP,	"[U":   PAGE_DOWN,	"[@":   INSERT,	"[2~":  INSERT,    // terminal	"[3~":  DELETE,    // terminal	"[5~":  PAGE_UP,   // terminal	"[6~":  PAGE_DOWN, // terminal	"[15~": F5,        // terminal	"[17~": F6,        // terminal	"[18~": F7,        // terminal	"[19~": F8,        // terminal	"[20~": F9,        // terminal	"[21~": F10,       // terminal	"[23~": F11,	"[24~": F12, // terminal	"OP":   F1,	"OQ":   F2,	"OR":   F3,	"OS":   F4, // syncterm "[1" = F1,F2,F3, and F4)	"Ot":   F5, // syncterm}// ReadRune fails on IAC AYT => unicode.ReplacementChar X 2// getch -> ReadRune  Low Levelfunc (d *Door) ReadRune() (rune, error) {	var r rune	if d.ReaderClosed {		return r, ErrDisconnected	}	for {		select {		case r = <-d.readerChannel:			return r, nil		case <-time.After(ReaderInterval):			return r, ErrTimeout		}	}}func (d *Door) RunePushback(r rune) {	d.Pushback.Push(r)}// getkey_or_pushback -> ReadRunePushback()func (d *Door) ReadRunePushback() (rune, error) {	if !d.Pushback.Empty() {		if DEBUG_INPUT {			log.Println("Read From PushBack")		}		return d.Pushback.Pop(), nil	}	return d.ReadRune()}func RuneToInt8(r rune) int8 {	return int8(r)}// Confusion - It's possible to return a null rune// that we received.  So it's a little sloppy having// to check Extended == NOP.  :(// High Level function - returns rune or extendedfunc (d *Door) GetKey() (rune, Extended, error) {	var r, r2 rune	var err, err2 error	var ex Extended	if d.ReaderClosed {		return r, ex, ErrDisconnected	}	// if bio.Disconnected() {	//	return r, ex, DisconnectedError	// }	r, err = d.ReadRunePushback()	if err != nil {		return r, ex, err	}	// fyneterm CR	if r == '\x0a' {		r2, err2 = d.ReadRunePushback()		if err2 == nil {			// Not an error			if r2 != '\x00' && r2 != '\x0a' {				// Wasn't 0x00 or 0x0a				d.RunePushback(r2)			}		}		return '\x0d', ex, nil	}	// We get 0x0d, 0x00, or 0x0d 0x0a from syncterm	if r == '\x0d' {		r2, err2 = d.ReadRunePushback()		if err2 == nil {			// Not an error			if r2 != '\x00' && r2 != '\x0a' {				// Wasn't 0x00 or 0x0a				d.RunePushback(r2)			}		}		return r, ex, nil	}	if r == '\x00' {		// Possibly doorway mode - deprecated?		// syncterm does support this, so it isn't entirely dead (NNY!)		r2, _ = d.ReadRunePushback()		r = rune(0)		switch r2 {		case '\x50':			return r, DOWN_ARROW, nil		case '\x48':			return r, UP_ARROW, nil		case '\x4b':			return r, LEFT_ARROW, nil		case 0x4d:			return r, RIGHT_ARROW, nil		case 0x47:			return r, HOME, nil		case 0x4f:			return r, END, nil		case 0x49:			return r, PAGE_UP, nil		case 0x51:			return r, PAGE_DOWN, nil		case 0x3b:			return r, F1, nil		case 0x3c:			return r, F2, nil		case 0x3d:			return r, F3, nil		case 0x3e:			return r, F4, nil		case 0x3f:			return r, F5, nil		case 0x40:			return r, F6, nil		case 0x41:			return r, F7, nil		case 0x42:			return r, F8, nil		case 0x43:			return r, F9, nil		case 0x44:			return r, F10, nil		/*			case 0x45:				return F11			case 0x46:				return F12		*/		case 0x52:			return r, INSERT, nil		case 0x53:			return r, DELETE, nil		default:			log.Printf("ERROR Doorway mode: 0x00 %x\n", r2)			return r, UNKNOWN, nil		}	} // End doorway	if r == '\x1b' {		// Escape key?		r2, err2 = d.ReadRunePushback()		if err2 != nil {			// Just escape key			return r, ex, nil		}		if r2 == '\x1b' {			d.RunePushback(r2)			return r, ex, nil		}		var extended []rune = make([]rune, 1)		extended[0] = r2		r2, err2 = d.ReadRunePushback()		for err2 == nil {			if r2 == '\x1b' {				// This is the end of the extended code.				d.RunePushback(r2)				break			}			extended = append(extended, r2)			ext, has := extendedKeys[string(extended)]			if has {				return rune(0), ext, nil			}			if unicode.IsLetter(r2) {				// The end of the extended code				break			}			r2, err2 = d.ReadRunePushback()		}		var exString string = string(extended)		if strings.HasPrefix(exString, "[M") && len(extended) == 5 {			// Mouse Extended - input zero based (I add +1 to X, Y, and button)			var mouse Mouse = Mouse{Button: RuneToInt8(extended[2]) - ' ' + 1,				X: RuneToInt8(extended[3]) - '!' + 1,				Y: RuneToInt8(extended[4]) - '!' + 1}			d.mcMutex.Lock()			defer d.mcMutex.Unlock()			d.LastMouse = append(d.LastMouse, mouse)			log.Println("Mouse:", mouse)			return rune(0), MOUSE, nil		}		if strings.HasSuffix(exString, "R") {			// Cursor Position information			var cursor CursorPos			// ^[[1;1R^[[2;3R^[[41;173R			// Y;X			exString = exString[1 : len(exString)-1] // Remove [ and R			pos := SplitToInt(exString, ";")			if len(pos) == 2 {				cursor.X = pos[1]				cursor.Y = pos[0]				d.mcMutex.Lock()				defer d.mcMutex.Unlock()				d.LastCursor = append(d.LastCursor, cursor)				log.Println("Cursor Pos:", cursor)				return rune(0), CURSOR, nil			} else {				log.Println("ERROR Cursor:", extended)				return rune(0), UNKNOWN, nil			}		}		if exString == "\x1b" {			return extended[0], Extended(0), nil		}		log.Println("ERROR Extended:", extended)		return rune(0), UNKNOWN, nil	}	// AYT (Telnet Are You There?)	/*		if r == '\xff' {			log.Printf("IAC")			r2, err2 = bio.ReadRunePushback()			if err2 != nil {				// Return value				return r, ex, nil			}			if r2 == '\xf6' {				// Are You There?				log.Println("AYT? - Yes")				bio.Write("Yes?")				// bio.Write(string([]byte{0x00})) // 0xff, 0xf2}))				// Eat these keys, and re-call ourselves...				// Or goto very top?				return bio.GetKey()			}		}	*/	return r, ex, nil}// "[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 timefunc (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 DOWN_ARROW		case 0x48:			return UP_ARROW		case 0x4b:			return LEFT_ARROW		case 0x4d:			return RIGHT_ARROW		case 0x47:			return HOME		case 0x4f:			return END		case 0x49:			return PAGE_UP		case 0x51:			return PAGE_DOWN		case 0x3b:			return F1		case 0x3c:			return F2		case 0x3d:			return F3		case 0x3e:			return F4		case 0x3f:			return F5		case 0x40:			return F6		case 0x41:			return F7		case 0x42:			return F8		case 0x43:			return F9		case 0x44:			return F10		case 0x52:			return INSERT		case 0x53:			return 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 = Microsecondsfunc (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	}}*/// port over WaitKeyfunc (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {	// disconnected test	d.readerMutex.Lock()	if d.ReaderClosed {		d.readerMutex.Unlock()		return rune(0), Extended(0), ErrDisconnected	}	d.readerMutex.Unlock()	if !d.Pushback.Empty() {		log.Println("WaitKey: Pushback ! empty.")		r, ex, e := d.GetKey()		if DEBUG_INPUT {			log.Println("WaitKey:", r, ex, e)		}		// return bio.GetKey()		return r, ex, e	}	// expiration := time.NewTicker(timeout)	// defer expiration.Stop()	select {	case r, ok := <-d.readerChannel:		if ok {			// Is this blocking? Ignoring expiration?			d.RunePushback(r)			log.Printf("readerChannel %#v ...\n", r)			r, ex, e := d.GetKey()			if DEBUG_INPUT {				log.Println("WaitKey:", r, ex, e)			}			// return bio.GetKey()			return r, ex, e		} else {			log.Println("WaitKey: Disconnected")			// Reader has closed.			// Disconnected = true			return rune(0), Extended(0), ErrDisconnected		}	case <-time.After(timeout):		return rune(0), Extended(0), ErrTimeout	}}// 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 r rune	var ex Extended	var err error	for {		r, ex, err = d.WaitKey(DefaultTimeout)		if err != nil {			// timeout/hangup			return ""		}		if ex != NOP {			continue		}		if strconv.IsPrint(r) {			if len(line) < max {				d.Write(string(r))				line += string(r)			} else {				d.Write("\x07")			}		} else {			// Non-print			switch r {			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) rune {	var r rune	var err error	for {		r, _, err = d.WaitKey(DefaultTimeout)		if err != nil {			return rune(0)		}		r := unicode.ToUpper(r)		if strings.ContainsRune(possible, r) {			// return upper case rune			return r		}		/*			c = strings.IndexRune(possible, r)			if c != -1 {				return c			}		*/	}}
 |