ソースを参照

Working tests, -race ok.

Next: cleanup code, there's lots of debug output with this.
Steve Thielemann 2 年 前
コミット
9a5ad0a854
6 ファイル変更511 行追加163 行削除
  1. 20 14
      door/door.go
  2. 86 0
      door/extended.go
  3. 34 142
      door/input.go
  4. 352 4
      door/input_linux.go
  5. 3 3
      door/input_test.go
  6. 16 0
      door/utilities.go

+ 20 - 14
door/door.go

@@ -105,23 +105,29 @@ type DropfileConfig struct {
 	Node           int    // BBS Node number
 	Node           int    // BBS Node number
 }
 }
 
 
+type ReaderData struct {
+	R   rune
+	Ex  Extended
+	Err error
+}
+
 type Door struct {
 type Door struct {
 	Config         DropfileConfig
 	Config         DropfileConfig
 	READFD         int
 	READFD         int
 	WRITEFD        int
 	WRITEFD        int
-	Disconnected   bool          // int32         // atomic bool      // Has User disconnected/Hung up?
-	TimeOut        time.Time     // Fixed point in time, when time expires
-	StartTime      time.Time     // Time when User started door
-	Pushback       FIFOBuffer    // Key buffer
-	LastColor      []int         // Track the last color sent for restore color
-	ReaderClosed   bool          // Reader close
-	readerChannel  chan rune     // Reading from the User
-	readerMutex    sync.Mutex    // Reader close mutex
-	readerFile     *os.File      // fd to File
-	runereader     *bufio.Reader // Rune Reader
-	ReaderCanClose bool          // We can close the reader (in tests)
-	WriterClosed   bool          // Writer closed
-	writerChannel  chan string   // Writing to the User
+	Disconnected   bool            // int32         // atomic bool      // Has User disconnected/Hung up?
+	TimeOut        time.Time       // Fixed point in time, when time expires
+	StartTime      time.Time       // Time when User started door
+	Pushback       FIFOBuffer      // Key buffer
+	LastColor      []int           // Track the last color sent for restore color
+	ReaderClosed   bool            // Reader close
+	readerChannel  chan ReaderData // Reading from the User
+	readerMutex    sync.Mutex      // Reader close mutex
+	readerFile     *os.File        // fd to File
+	runereader     *bufio.Reader   // Rune Reader
+	ReaderCanClose bool            // We can close the reader (in tests)
+	WriterClosed   bool            // Writer closed
+	writerChannel  chan string     // Writing to the User
 	writerMutex    sync.RWMutex
 	writerMutex    sync.RWMutex
 	LastMouse      []Mouse     // Store Mouse information
 	LastMouse      []Mouse     // Store Mouse information
 	LastCursor     []CursorPos // Store Cursor pos information
 	LastCursor     []CursorPos // Store Cursor pos information
@@ -385,7 +391,7 @@ func (d *Door) Init(doorname string) {
 	log.Printf("Loading dropfile %s\n", dropfile)
 	log.Printf("Loading dropfile %s\n", dropfile)
 	log.Printf("BBS %s, User %s / Handle %s / File %d\n", d.Config.BBSID, d.Config.Real_name, d.Config.Handle, d.Config.Comm_handle)
 	log.Printf("BBS %s, User %s / Handle %s / File %d\n", d.Config.BBSID, d.Config.Real_name, d.Config.Handle, d.Config.Comm_handle)
 
 
-	d.readerChannel = make(chan rune, 16) // was 8 ?
+	d.readerChannel = make(chan ReaderData, 16) // was 8 ?
 	/*
 	/*
 		Ok, here's the issue.  This blocks the go reader when this is full.
 		Ok, here's the issue.  This blocks the go reader when this is full.
 		It seems like it would be better to have a channel that receives
 		It seems like it would be better to have a channel that receives

+ 86 - 0
door/extended.go

@@ -0,0 +1,86 @@
+package door
+
+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
+	ALT_a
+	ALT_b
+	ALT_c
+	ALT_d
+	ALT_e
+	ALT_f
+	ALT_g
+	ALT_h
+	ALT_i
+	ALT_j
+	ALT_k
+	ALT_l
+	ALT_m
+	ALT_n
+	ALT_o
+	ALT_p
+	ALT_q
+	ALT_r
+	ALT_s
+	ALT_t
+	ALT_u
+	ALT_v
+	ALT_w
+	ALT_x
+	ALT_y
+	ALT_z
+	ALT_A
+	ALT_B
+	ALT_C
+	ALT_D
+	ALT_E
+	ALT_F
+	ALT_G
+	ALT_H
+	ALT_I
+	ALT_J
+	ALT_K
+	ALT_L
+	ALT_M
+	ALT_N
+	ALT_O
+	ALT_P
+	ALT_Q
+	ALT_R
+	ALT_S
+	ALT_T
+	ALT_U
+	ALT_V
+	ALT_W
+	ALT_X
+	ALT_Y
+	ALT_Z
+	UNKNOWN
+)
+
+//go:generate stringer -type=Extended

+ 34 - 142
door/input.go

@@ -9,135 +9,15 @@ import (
 	"unicode"
 	"unicode"
 )
 )
 
 
-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
-	ALT_a
-	ALT_b
-	ALT_c
-	ALT_d
-	ALT_e
-	ALT_f
-	ALT_g
-	ALT_h
-	ALT_i
-	ALT_j
-	ALT_k
-	ALT_l
-	ALT_m
-	ALT_n
-	ALT_o
-	ALT_p
-	ALT_q
-	ALT_r
-	ALT_s
-	ALT_t
-	ALT_u
-	ALT_v
-	ALT_w
-	ALT_x
-	ALT_y
-	ALT_z
-	ALT_A
-	ALT_B
-	ALT_C
-	ALT_D
-	ALT_E
-	ALT_F
-	ALT_G
-	ALT_H
-	ALT_I
-	ALT_J
-	ALT_K
-	ALT_L
-	ALT_M
-	ALT_N
-	ALT_O
-	ALT_P
-	ALT_Q
-	ALT_R
-	ALT_S
-	ALT_T
-	ALT_U
-	ALT_V
-	ALT_W
-	ALT_X
-	ALT_Y
-	ALT_Z
-	UNKNOWN
-)
-
-//go:generate stringer -type=Extended
-
 const DEBUG_INPUT = true
 const DEBUG_INPUT = true
 
 
-func extended_output(buffer []rune) string {
-	var output string = string(buffer)
-	output = strings.Replace(output, "\x1b", "^[", -1)
-	return output
-}
-
 var ErrInactivity error = fmt.Errorf("Inactivity")
 var ErrInactivity error = fmt.Errorf("Inactivity")
 var ErrTimeout error = fmt.Errorf("Timeout")
 var ErrTimeout error = fmt.Errorf("Timeout")
 var ErrDisconnected error = fmt.Errorf("Disconnected")
 var ErrDisconnected error = fmt.Errorf("Disconnected")
 
 
 var DefaultTimeout time.Duration = time.Duration(60) * time.Second
 var DefaultTimeout time.Duration = time.Duration(60) * time.Second
 
 
-var 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
 // ReadRune fails on IAC AYT => unicode.ReplacementChar X 2
 
 
 // getch -> ReadRune  Low Level
 // getch -> ReadRune  Low Level
@@ -399,33 +279,44 @@ func (d *Door) GetKey() (rune, Extended, error) {
 		return rune(0), UNKNOWN, nil
 		return rune(0), UNKNOWN, nil
 	}
 	}
 
 
-	// AYT (Telnet Are You There?)
+	return r, ex, nil
+}
+
+*/
+// "[1":   XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?
+
+func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
 	/*
 	/*
-		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()
-			}
+		d.readerMutex.Lock()
+		if d.ReaderClosed {
+			d.readerMutex.Unlock()
+			return 0, NOP, ErrDisconnected
 		}
 		}
+		d.readerMutex.Unlock()
 	*/
 	*/
 
 
-	return r, ex, nil
-}
+	// Probably faster to just read from closed channel and get ok = false.
 
 
-// "[1":   XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?
+	select {
+	case r, ok := <-d.readerChannel:
+		if ok {
+			if DEBUG_INPUT {
+				log.Println("WaitKey:", r)
+			}
+			// return bio.GetKey()
+			return r.R, r.Ex, r.Err
+		} else {
+			log.Println("WaitKey: Disconnected")
+			// Reader has closed.
+			// Disconnected = true
+			return 0, NOP, ErrDisconnected
+		}
+	case <-time.After(timeout):
+		return 0, NOP, ErrTimeout
+	}
+}
 
 
+/*
 // port over WaitKey
 // port over WaitKey
 func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
 func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
 	// disconnected test
 	// disconnected test
@@ -472,6 +363,7 @@ func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
 		return rune(0), Extended(0), ErrTimeout
 		return rune(0), Extended(0), ErrTimeout
 	}
 	}
 }
 }
+*/
 
 
 // Outputs spaces and backspaces
 // Outputs spaces and backspaces
 // If you have set a background color, this shows the input area.
 // If you have set a background color, this shows the input area.

+ 352 - 4
door/input_linux.go

@@ -5,6 +5,7 @@ import (
 	"bytes"
 	"bytes"
 	"io"
 	"io"
 	"log"
 	"log"
+	"strings"
 	"syscall"
 	"syscall"
 	"time"
 	"time"
 	"unicode"
 	"unicode"
@@ -13,6 +14,13 @@ import (
 var ReaderInterval = time.Duration(200) * time.Millisecond
 var ReaderInterval = time.Duration(200) * time.Millisecond
 var ReaderTimeval syscall.Timeval = syscall.Timeval{0, 200}
 var ReaderTimeval syscall.Timeval = syscall.Timeval{0, 200}
 
 
+// Output a nice string representation of the rune buffer.
+func extended_output(buffer []rune) string {
+	var output string = string(buffer)
+	output = strings.Replace(output, "\x1b", "^[", -1)
+	return output
+}
+
 // syscall.FdSet clear all
 // syscall.FdSet clear all
 func clearAll(fdSetPtr *syscall.FdSet) {
 func clearAll(fdSetPtr *syscall.FdSet) {
 	for index := range (*fdSetPtr).Bits {
 	for index := range (*fdSetPtr).Bits {
@@ -25,13 +33,346 @@ func set(fdSetPtr *syscall.FdSet, fd int) {
 	(*fdSetPtr).Bits[fd/64] |= 1 << uint64(fd%64)
 	(*fdSetPtr).Bits[fd/64] |= 1 << uint64(fd%64)
 }
 }
 
 
+func RuneToInt8(r rune) int8 {
+	return int8(r)
+}
+
 const READ_SIZE = 16 // Size of read buffer
 const READ_SIZE = 16 // Size of read buffer
 
 
+var readerBuffer []rune
+
+// Translate these extended codes into these Extended values.
+var 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
+}
+
+func process(d *Door, newRune bool) {
+	// This is like GetKey
+	var rlen int
+	var r, r2 rune
+	var has2 bool
+	var done bool = false
+
+	for !done {
+		rlen = len(readerBuffer)
+		if rlen == 0 {
+			// Nothing to do here
+			return
+		}
+
+		log.Println("rlen:", rlen, "readerBuffer:", readerBuffer, "newRune:", newRune)
+
+		r = readerBuffer[0]
+		if rlen >= 2 {
+			r2 = readerBuffer[1]
+			has2 = true
+		} else {
+			r2 = unicode.ReplacementChar
+			has2 = false
+		}
+
+		// if !has2 and !newRune, then we're "done" (received everything,
+		// and there's nothing else coming...)
+
+		// fyneterm CR
+		if r == '\x0a' {
+			if !has2 && !newRune {
+				ArrayPop(&readerBuffer, 1)
+				d.readerChannel <- ReaderData{R: '\x0d', Ex: NOP, Err: nil}
+				return
+			}
+
+			if has2 {
+				ArrayPop(&readerBuffer, 1)
+				if r2 == '\x00' || r2 == '\x0a' {
+					ArrayPop(&readerBuffer, 1)
+				}
+				d.readerChannel <- ReaderData{R: '\x0d', Ex: NOP, Err: nil}
+			} else {
+				// We don't have a 2nd rune, and we haven't timed out.
+				return
+			}
+
+			continue
+		}
+
+		// We get 0x0d, 0x00, or 0x0d 0x0a from syncterm
+		if r == '\x0d' {
+			if !has2 && !newRune {
+				ArrayPop(&readerBuffer, 1)
+				d.readerChannel <- ReaderData{R: r, Ex: NOP, Err: nil}
+				return
+			}
+
+			if has2 {
+				ArrayPop(&readerBuffer, 1)
+				if r2 == '\x00' || r2 == '\x0a' {
+					ArrayPop(&readerBuffer, 1)
+				}
+				d.readerChannel <- ReaderData{R: r, Ex: NOP, Err: nil}
+			} else {
+				return
+			}
+
+			continue
+		}
+
+		if r == '\x00' {
+			// Possibly doorway mode - deprecated?
+			// syncterm does support this.
+
+			if !has2 && !newRune {
+				// timeout
+				ArrayPop(&readerBuffer, 1)
+				d.readerChannel <- ReaderData{R: r, Ex: NOP, Err: nil}
+				return
+			}
+
+			if has2 {
+				ArrayPop(&readerBuffer, 2)
+				switch r2 {
+				case '\x50':
+					d.readerChannel <- ReaderData{0, DOWN_ARROW, nil}
+				case '\x48':
+					d.readerChannel <- ReaderData{0, UP_ARROW, nil}
+				case '\x4b':
+					d.readerChannel <- ReaderData{0, LEFT_ARROW, nil}
+				case 0x4d:
+					d.readerChannel <- ReaderData{0, RIGHT_ARROW, nil}
+				case 0x47:
+					d.readerChannel <- ReaderData{0, HOME, nil}
+				case 0x4f:
+					d.readerChannel <- ReaderData{0, END, nil}
+				case 0x49:
+					d.readerChannel <- ReaderData{0, PAGE_UP, nil}
+				case 0x51:
+					d.readerChannel <- ReaderData{0, PAGE_DOWN, nil}
+				case 0x3b:
+					d.readerChannel <- ReaderData{0, F1, nil}
+				case 0x3c:
+					d.readerChannel <- ReaderData{0, F2, nil}
+				case 0x3d:
+					d.readerChannel <- ReaderData{0, F3, nil}
+				case 0x3e:
+					d.readerChannel <- ReaderData{0, F4, nil}
+				case 0x3f:
+					d.readerChannel <- ReaderData{0, F5, nil}
+				case 0x40:
+					d.readerChannel <- ReaderData{0, F6, nil}
+				case 0x41:
+					d.readerChannel <- ReaderData{0, F7, nil}
+				case 0x42:
+					d.readerChannel <- ReaderData{0, F8, nil}
+				case 0x43:
+					d.readerChannel <- ReaderData{0, F9, nil}
+				case 0x44:
+					d.readerChannel <- ReaderData{0, F10, nil}
+
+				case 0x45:
+					d.readerChannel <- ReaderData{0, F11, nil}
+				case 0x46:
+					d.readerChannel <- ReaderData{0, F12, nil}
+
+				case 0x52:
+					d.readerChannel <- ReaderData{0, INSERT, nil}
+				case 0x53:
+					d.readerChannel <- ReaderData{0, DELETE, nil}
+				default:
+					log.Printf("ERROR Doorway mode: 0x00 %x\n", r2)
+					d.readerChannel <- ReaderData{0, UNKNOWN, nil}
+
+				}
+			} else {
+				return
+			}
+			continue
+		} // end doorway mode
+
+		if r == '\x1b' {
+			// Escape key, or ?
+			// This is a little harder, since we don't know how many we need.
+			if !has2 && !newRune {
+				ArrayPop(&readerBuffer, 1)
+				d.readerChannel <- ReaderData{r, NOP, nil}
+				return
+			}
+
+			if has2 {
+				// We at least have the 2nd one...
+				if r2 == '\x1b' {
+					ArrayPop(&readerBuffer, 1)
+					d.readerChannel <- ReaderData{r, NOP, nil}
+					continue
+				}
+
+				// Can't distinguish between \x1bO and \x1bOP (F1)
+				/*
+					if unicode.IsLetter(r2) {
+						// ALT-KEY
+						if unicode.IsLower(r2) {
+							ArrayPop(&readerBuffer, 2)
+							ex := Extended(int(ALT_a) + int(r2-'a'))
+							d.readerChannel <- ReaderData{0, ex, nil}
+						} else {
+							// Must be upper
+							ArrayPop(&readerBuffer, 2)
+							ex := Extended(int(ALT_A) + int(r2-'A'))
+							d.readerChannel <- ReaderData{0, ex, nil}
+						}
+						continue
+					}
+				*/
+
+				var extended []rune = make([]rune, 0, 10)
+				extended = append(extended, r2)
+				var extlen = 2 // Length of codes to remove on successful match.
+				var pos = 2    // Where we get the next rune from
+				var isMouse bool = false
+				var found bool = false
+
+				log.Println(pos, readerBuffer)
+
+				for pos < rlen {
+					r2 = readerBuffer[pos]
+
+					pos++
+					extlen++
+
+					extended = append(extended, r2)
+
+					log.Println("0x1b LOOP pos:", pos, "extlen:", extlen, "extended:", string(extended))
+
+					ext, has := extendedKeys[string(extended)]
+					if has {
+						// Found it!
+						log.Println("Found Extended Match:", ext.String())
+						ArrayPop(&readerBuffer, extlen)
+						d.readerChannel <- ReaderData{0, ext, nil}
+						found = true
+						break
+					}
+
+					// Mouse codes can also contain letters.
+					if !isMouse && unicode.IsLetter(r2) {
+						// end of extended code, unless mouse!
+						log.Println("not mouse, is letter...")
+						if string(extended) == "[M" {
+							isMouse = true
+						} else {
+							break
+						}
+					}
+
+					if isMouse && len(extended) == 5 {
+						break
+					}
+				}
+
+				if found {
+					continue
+				}
+
+				log.Println("POS:", pos, "EXLEN:", extlen, "Extended:", extended, "readerBuffer:", readerBuffer)
+
+				log.Printf("(possible) Extended Code: [%s]", extended_output(extended))
+
+				var exString string = string(extended)
+				if strings.HasPrefix(exString, "[M") && len(extended) == 5 {
+					// Yes, "valid"
+					ArrayPop(&readerBuffer, extlen)
+					// 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()
+					d.LastMouse = append(d.LastMouse, mouse)
+					d.mcMutex.Unlock()
+					log.Println("Mouse:", mouse)
+					d.readerChannel <- ReaderData{0, MOUSE, nil}
+					continue
+				}
+
+				if strings.HasSuffix(exString, "R") {
+					// yes, "valid"
+					ArrayPop(&readerBuffer, extlen)
+					// Cursor Position information (or Shift-F3)
+					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()
+						d.LastCursor = append(d.LastCursor, cursor)
+						d.mcMutex.Unlock()
+						log.Println("Cursor Pos:", cursor)
+						d.readerChannel <- ReaderData{0, CURSOR, nil}
+						continue
+					} else {
+						log.Println("ERROR Cursor Pos:", extended)
+						d.readerChannel <- ReaderData{0, UNKNOWN, nil}
+						continue
+					}
+				}
+
+				// Ok, this LOOKS like something invalid...
+				if !newRune {
+					// Yes, this is invalid.
+					ArrayPop(&readerBuffer, extlen)
+					log.Println("ERROR Extended:", extended)
+					d.readerChannel <- ReaderData{0, UNKNOWN, nil}
+				} else {
+					log.Println("(Possibly) invalid extended:", extended)
+					return
+				}
+
+			} else {
+				return
+			}
+			continue
+		}
+
+		ArrayPop(&readerBuffer, 1)
+		d.readerChannel <- ReaderData{r, NOP, nil}
+		continue
+	}
+}
+
 // go routine Reader for input
 // go routine Reader for input
 // This "times out" every ReaderTimeval
 // This "times out" every ReaderTimeval
 func Reader(d *Door) {
 func Reader(d *Door) {
 	// I need non-blocking reads here.
 	// I need non-blocking reads here.
 
 
+	readerBuffer = make([]rune, 0, READ_SIZE*2)
+
 	defer func() {
 	defer func() {
 		log.Printf("~Reader\n")
 		log.Printf("~Reader\n")
 		if d.ReaderCanClose {
 		if d.ReaderCanClose {
@@ -73,6 +414,7 @@ func Reader(d *Door) {
 
 
 		if v == 0 {
 		if v == 0 {
 			// timeout
 			// timeout
+			process(d, false)
 			continue
 			continue
 		}
 		}
 
 
@@ -118,12 +460,12 @@ func Reader(d *Door) {
 			input, _, err = runeread.ReadRune()
 			input, _, err = runeread.ReadRune()
 
 
 			if err == io.EOF {
 			if err == io.EOF {
-				continue
+				break // continue
 			}
 			}
 			if err != nil {
 			if err != nil {
 				log.Printf("ReadRune: %#v\n", err)
 				log.Printf("ReadRune: %#v\n", err)
 				// errors EOF
 				// errors EOF
-				continue // break for loop
+				break // continue // break for loop
 			}
 			}
 			if input == unicode.ReplacementChar {
 			if input == unicode.ReplacementChar {
 				runeread.UnreadRune()
 				runeread.UnreadRune()
@@ -131,15 +473,21 @@ func Reader(d *Door) {
 				if DEBUG_INPUT {
 				if DEBUG_INPUT {
 					log.Printf("Reader (byte) >> %x\n", b)
 					log.Printf("Reader (byte) >> %x\n", b)
 				}
 				}
-				d.readerChannel <- rune(b)
+				readerBuffer = append(readerBuffer, rune(b))
+				// d.readerChannel <- rune(b)
 			} else {
 			} else {
 				if DEBUG_INPUT {
 				if DEBUG_INPUT {
 					log.Printf("Reader >> %x\n", input)
 					log.Printf("Reader >> %x\n", input)
 				}
 				}
-				d.readerChannel <- input
+				readerBuffer = append(readerBuffer, input)
+				// d.readerChannel <- input
 			}
 			}
+			// process(d, true)
+
 		} // goto RuneRead
 		} // goto RuneRead
 
 
+		process(d, true)
+
 		// buffer = append(buffer, readone[0])
 		// buffer = append(buffer, readone[0])
 
 
 		/*
 		/*

+ 3 - 3
door/input_test.go

@@ -379,7 +379,7 @@ func TestDoorInputConnection(t *testing.T) {
 		var result string = string(buffer[:r])
 		var result string = string(buffer[:r])
 	*/
 	*/
 
 
-	expected := "     \x08\x08\x08\x08\x0812345\x07\x07\x07\x07\x07"
+	expected := "     \x08\x08\x08\x08\x0812345\x07\x07\x07\x07"
 	if result != expected {
 	if result != expected {
 		t.Errorf("Buffer Input(5): Expected %#v, got %#v\n", expected, result)
 		t.Errorf("Buffer Input(5): Expected %#v, got %#v\n", expected, result)
 	}
 	}
@@ -436,7 +436,7 @@ func TestDoorInputConnection(t *testing.T) {
 		}
 		}
 	*/
 	*/
 
 
-	_, err = d.ReadRune() // GetKey()
+	_, _, err = d.WaitKey(time.Millisecond) // GetKey()
 
 
 	if err != ErrDisconnected {
 	if err != ErrDisconnected {
 		t.Errorf("Expected ErrDisconnected, got %#v", err)
 		t.Errorf("Expected ErrDisconnected, got %#v", err)
@@ -453,7 +453,7 @@ func TestDoorInputConnection(t *testing.T) {
 		t.Errorf("Input should return blank (hangup).")
 		t.Errorf("Input should return blank (hangup).")
 	}
 	}
 
 
-	_, err = d.ReadRune() // getch()
+	_, _, err = d.WaitKey(time.Millisecond) // getch()
 
 
 	if err != ErrDisconnected {
 	if err != ErrDisconnected {
 		t.Errorf("Expected ErrDisconnected, got %#v", err)
 		t.Errorf("Expected ErrDisconnected, got %#v", err)

+ 16 - 0
door/utilities.go

@@ -33,6 +33,22 @@ func ArrayDelete[T any](stack *[]T, pos int) (T, bool) {
 	return result, true
 	return result, true
 }
 }
 
 
+func ArrayPop[T any](stack *[]T, count int) bool {
+	/*
+	   https://stackoverflow.com/questions/33834742/remove-and-adding-elements-to-array-in-go-lang
+	   https://github.com/golang/go/wiki/SliceTricks
+	*/
+	if count < 0 || count > len(*stack) {
+		return false
+	}
+
+	copy((*stack)[0:], (*stack)[count:])
+	// var temp T
+	// (*stack)[len(*stack)-1] = temp
+	*stack = (*stack)[:len(*stack)-count]
+	return true
+}
+
 func SplitToInt(input string, sep string) []int {
 func SplitToInt(input string, sep string) []int {
 	var result []int
 	var result []int
 	for _, number := range strings.Split(input, sep) {
 	for _, number := range strings.Split(input, sep) {