diff --git a/src/device/input/.keyboard_integration.d/main.go b/src/device/input/.keyboard_integration.d/main.go new file mode 100644 index 0000000..a950c11 --- /dev/null +++ b/src/device/input/.keyboard_integration.d/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "mayhem-party/src/device/input" +) + +func main() { + k := input.NewBuffered(input.NewKeyboard()) + defer func() { + recover() + k.Close() + }() + + log.Printf("try the keyboard") + n := 5 + for n > 0 { + result := k.Read() + n -= len(result) + for j := range result { + log.Printf("[%d][%d] %c|%v", n, j, result[j].Char, result[j].Down) + } + } +} diff --git a/src/device/input/buffered.go b/src/device/input/buffered.go new file mode 100644 index 0000000..2c35d3d --- /dev/null +++ b/src/device/input/buffered.go @@ -0,0 +1,80 @@ +package input + +import ( + "context" + "sync" + "time" +) + +type Buffered struct { + ctx context.Context + can context.CancelFunc + lock sync.Mutex + keys map[byte]int64 + input Input + listenInterval time.Duration + expirationInterval time.Duration +} + +func NewBuffered(input Input) *Buffered { + ctx, can := context.WithCancel(context.Background()) + result := &Buffered{ + input: input, + ctx: ctx, + can: can, + lock: sync.Mutex{}, + keys: map[byte]int64{}, + listenInterval: time.Millisecond * 20, + expirationInterval: time.Millisecond * 100, + } + go result.listen() + return result +} + +func (b *Buffered) listen() { + for b.ctx.Err() == nil { + buttons := b.input.Read() + + b.lock.Lock() + for i := range buttons { + if buttons[i].Down { + b.keys[buttons[i].Char] = time.Now().UnixNano() + } + } + b.lock.Unlock() + select { + case <-b.ctx.Done(): + case <-time.After(b.listenInterval): + } + } +} + +func (b *Buffered) Close() { + b.input.Close() + b.can() +} + +func (b *Buffered) Read() []Button { + b.lock.Lock() + defer b.lock.Unlock() + + result := make([]Button, 0, len(b.keys)) + for k, v := range b.keys { + isFresh := v > 0 + isStale := v < 0 && time.Since(time.Unix(0, -1*v)) > b.expirationInterval + if isFresh || isStale { + result = append(result, Button{Char: k, Down: isFresh}) + } + if isFresh { + b.keys[k] = -1 * v + } + } + + for i := range result { + if !result[i].Down { + delete(b.keys, result[i].Char) + } + } + + return result +} diff --git a/src/device/input/input.go b/src/device/input/input.go index c9a2ed2..ddf120d 100644 --- a/src/device/input/input.go +++ b/src/device/input/input.go @@ -4,6 +4,7 @@ import "os" type Input interface { Read() []Button + Close() } func New() Input { diff --git a/src/device/input/input_test.go b/src/device/input/input_test.go index 3537a11..ff32d7f 100644 --- a/src/device/input/input_test.go +++ b/src/device/input/input_test.go @@ -4,4 +4,6 @@ import "testing" func TestInput(t *testing.T) { var _ Input = &Random{} + var _ Input = Keyboard{} + var _ Input = &Buffered{} } diff --git a/src/device/input/keyboard.go b/src/device/input/keyboard.go new file mode 100644 index 0000000..ea5e2ec --- /dev/null +++ b/src/device/input/keyboard.go @@ -0,0 +1,58 @@ +package input + +import ( + "io" + "os" + "os/exec" + "runtime" +) + +type Keyboard struct { +} + +func NewKeyboard() Keyboard { + switch runtime.GOOS { + case "linux": + if err := exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run(); err != nil { + panic(err) + } else if err := exec.Command("stty", "-F", "/dev/tty", "-echo").Run(); err != nil { + panic(err) + } + case "darwin": + if err := exec.Command("stty", "-f", "/dev/tty", "cbreak", "min", "1").Run(); err != nil { + panic(err) + } else if err := exec.Command("stty", "-f", "/dev/tty", "-echo").Run(); err != nil { + panic(err) + } + default: + panic(runtime.GOOS) + } + return Keyboard{} +} + +func (kb Keyboard) Close() { + switch runtime.GOOS { + case "linux": + if err := exec.Command("stty", "-F", "/dev/tty", "echo").Run(); err != nil { + panic(err) + } + case "darwin": + if err := exec.Command("stty", "-f", "/dev/tty", "echo").Run(); err != nil { + panic(err) + } + } +} + +func (kb Keyboard) Read() []Button { + b := make([]byte, 5) + n, err := os.Stdin.Read(b) + if err != nil && err != io.EOF { + panic(err) + } + + result := make([]Button, n) + for i := range result { + result[i] = Button{Char: b[i], Down: true} + } + return result +} diff --git a/src/device/input/random.go b/src/device/input/random.go index dbfafa7..c487002 100644 --- a/src/device/input/random.go +++ b/src/device/input/random.go @@ -26,6 +26,9 @@ func NewRandom(generator func() byte) *Random { return &Random{generator: generator} } +func (r *Random) Close() { +} + func (r *Random) Read() []Button { if len(r.down) > 0 && rand.Int()%2 == 0 { was := r.down