From cec3153dc63f902e4ee7102c526fc23e10e323dc Mon Sep 17 00:00:00 2001 From: Jakub Sobon Date: Thu, 5 Apr 2018 19:35:02 +0200 Subject: [PATCH] Hooking up events from termbox. --- experimental/boxes/boxes.go | 37 +++++++++++++++++++++++++++++++- terminal/termbox/event.go | 4 ++-- terminal/termbox/event_test.go | 26 +++++++++++------------ terminal/termbox/termbox.go | 39 ++++++++++++++++++++++++++++++++-- terminalapi/event.go | 15 +++++++++++++ 5 files changed, 103 insertions(+), 18 deletions(-) diff --git a/experimental/boxes/boxes.go b/experimental/boxes/boxes.go index 1397899..ef63749 100644 --- a/experimental/boxes/boxes.go +++ b/experimental/boxes/boxes.go @@ -1,14 +1,37 @@ // Binary boxes just creates containers with borders. +// Runs as long as there is at least one input (keyboard, mouse or terminal resize) event every 10 seconds. package main import ( + "context" + "log" "time" "github.com/mum4k/termdash/container" "github.com/mum4k/termdash/draw" "github.com/mum4k/termdash/terminal/termbox" + "github.com/mum4k/termdash/terminalapi" ) +func events(t terminalapi.Terminal, ctx context.Context) <-chan terminalapi.Event { + ch := make(chan terminalapi.Event) + go func() { + for { + ctx, cancel := context.WithTimeout(ctx, 3*time.Second) + ev := t.Event(ctx) + switch ev.(type) { + case *terminalapi.Error: + log.Print(ev) + default: + ch <- ev + } + + cancel() + } + }() + return ch +} + func main() { t, err := termbox.New() if err != nil { @@ -49,5 +72,17 @@ func main() { if err := t.Flush(); err != nil { panic(err) } - time.Sleep(3 * time.Second) + + ev := events(t, context.Background()) + for { + timer := time.NewTicker(10 * time.Second) + defer timer.Stop() + select { + case e := <-ev: + log.Printf("Event: %v", e) + case <-timer.C: + log.Printf("Exiting...") + return + } + } } diff --git a/terminal/termbox/event.go b/terminal/termbox/event.go index 24469ef..026c4a4 100644 --- a/terminal/termbox/event.go +++ b/terminal/termbox/event.go @@ -192,8 +192,8 @@ func convResize(tbxEv tbx.Event) terminalapi.Event { } } -// toTermdashEvent converts a termbox event to the termdash event format. -func toTermdashEvent(tbxEv tbx.Event) []terminalapi.Event { +// toTermdashEvents converts a termbox event to the termdash event format. +func toTermdashEvents(tbxEv tbx.Event) []terminalapi.Event { switch t := tbxEv.Type; t { case tbx.EventInterrupt: return []terminalapi.Event{ diff --git a/terminal/termbox/event_test.go b/terminal/termbox/event_test.go index 802420a..1f8bca2 100644 --- a/terminal/termbox/event_test.go +++ b/terminal/termbox/event_test.go @@ -14,7 +14,7 @@ import ( tbx "github.com/nsf/termbox-go" ) -func TestToTermdashEvent(t *testing.T) { +func TestToTermdashEvents(t *testing.T) { tests := []struct { desc string event tbx.Event @@ -121,9 +121,9 @@ func TestToTermdashEvent(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - got := toTermdashEvent(tc.event) + got := toTermdashEvents(tc.event) if diff := pretty.Compare(tc.want, got); diff != "" { - t.Errorf("toTermdashEvent => unexpected diff (-want, +got):\n%s", diff) + t.Errorf("toTermdashEvents => unexpected diff (-want, +got):\n%s", diff) } }) } @@ -149,14 +149,14 @@ func TestMouseButtons(t *testing.T) { for _, tc := range tests { t.Run(fmt.Sprintf("key:%v want:%v", tc.key, tc.want), func(t *testing.T) { - evs := toTermdashEvent(tbx.Event{Type: tbx.EventMouse, Key: tc.key}) + evs := toTermdashEvents(tbx.Event{Type: tbx.EventMouse, Key: tc.key}) if got, want := len(evs), 1; got != want { - t.Fatalf("toTermdashEvent => got %d events, want %d", got, want) + t.Fatalf("toTermdashEvents => got %d events, want %d", got, want) } ev := evs[0] if err, ok := ev.(*terminalapi.Error); ok != tc.wantErr { - t.Fatalf("toTermdashEvent => unexpected error:%v, wantErr: %v", err, tc.wantErr) + t.Fatalf("toTermdashEvents => unexpected error:%v, wantErr: %v", err, tc.wantErr) } if _, ok := ev.(*terminalapi.Error); ok { return @@ -165,11 +165,11 @@ func TestMouseButtons(t *testing.T) { switch e := ev.(type) { case *terminalapi.Mouse: if got := e.Button; got != tc.want { - t.Errorf("toTermdashEvent => got %v, want %v", got, tc.want) + t.Errorf("toTermdashEvents => got %v, want %v", got, tc.want) } default: - t.Fatalf("toTermdashEvent => unexpected event type %T", e) + t.Fatalf("toTermdashEvents => unexpected event type %T", e) } }) } @@ -263,7 +263,7 @@ func TestKeyboardKeys(t *testing.T) { for _, tc := range tests { t.Run(fmt.Sprintf("key:%v and ch:%v want:%v", tc.key, tc.ch, tc.want), func(t *testing.T) { - evs := toTermdashEvent(tbx.Event{ + evs := toTermdashEvents(tbx.Event{ Type: tbx.EventKey, Key: tc.key, Ch: tc.ch, @@ -278,12 +278,12 @@ func TestKeyboardKeys(t *testing.T) { } if gotCount != wantCount { - t.Fatalf("toTermdashEvent => got %d events, want %d, events were:\n%v", gotCount, wantCount, pretty.Sprint(evs)) + t.Fatalf("toTermdashEvents => got %d events, want %d, events were:\n%v", gotCount, wantCount, pretty.Sprint(evs)) } for i, ev := range evs { if err, ok := ev.(*terminalapi.Error); ok != tc.wantErr { - t.Fatalf("toTermdashEvent => unexpected error:%v, wantErr: %v", err, tc.wantErr) + t.Fatalf("toTermdashEvents => unexpected error:%v, wantErr: %v", err, tc.wantErr) } if _, ok := ev.(*terminalapi.Error); ok { return @@ -292,11 +292,11 @@ func TestKeyboardKeys(t *testing.T) { switch e := ev.(type) { case *terminalapi.Keyboard: if got, want := e.Key, tc.want[i]; got != want { - t.Errorf("toTermdashEvent => got key[%d] %v, want %v", got, i, want) + t.Errorf("toTermdashEvents => got key[%d] %v, want %v", got, i, want) } default: - t.Fatalf("toTermdashEvent => unexpected event type %T", e) + t.Fatalf("toTermdashEvents => unexpected event type %T", e) } } }) diff --git a/terminal/termbox/termbox.go b/terminal/termbox/termbox.go index d3415ac..9fb75ce 100644 --- a/terminal/termbox/termbox.go +++ b/terminal/termbox/termbox.go @@ -6,6 +6,7 @@ import ( "image" "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/eventqueue" "github.com/mum4k/termdash/terminalapi" tbx "github.com/nsf/termbox-go" ) @@ -37,6 +38,13 @@ func ColorMode(cm terminalapi.ColorMode) Option { // supported, because termbox uses global state. // Implements terminalapi.Terminal. type Terminal struct { + // events is a queue of input events. + events *eventqueue.Unbound + + // done gets closed when Close() is called. + done chan struct{} + + // Options. colorMode terminalapi.ColorMode } @@ -46,8 +54,12 @@ func New(opts ...Option) (*Terminal, error) { if err := tbx.Init(); err != nil { return nil, err } + tbx.SetInputMode(tbx.InputEsc | tbx.InputMouse) - t := &Terminal{} + t := &Terminal{ + events: eventqueue.New(), + done: make(chan struct{}), + } for _, opt := range opts { opt.set(t) } @@ -57,6 +69,8 @@ func New(opts ...Option) (*Terminal, error) { return nil, err } tbx.SetOutputMode(om) + + go t.pollEvents() // Stops when Close() is called. return t, nil } @@ -112,13 +126,34 @@ func (t *Terminal) SetCell(p image.Point, r rune, opts ...cell.Option) error { return nil } +// pollEvents polls and enqueues the input events. +func (t *Terminal) pollEvents() { + for { + select { + case <-t.done: + return + default: + } + + events := toTermdashEvents(tbx.PollEvent()) + for _, ev := range events { + t.events.Push(ev) + } + } +} + // Implements terminalapi.Terminal.Event. func (t *Terminal) Event(ctx context.Context) terminalapi.Event { - return nil + ev, err := t.events.Pull(ctx) + if err != nil { + return terminalapi.NewErrorf("unable to pull the next event: %v", err) + } + return ev } // Closes the terminal, should be called when the terminal isn't required // anymore to return the screen to a sane state. func (t *Terminal) Close() { + close(t.done) tbx.Close() } diff --git a/terminalapi/event.go b/terminalapi/event.go index 2f01606..c25f9ca 100644 --- a/terminalapi/event.go +++ b/terminalapi/event.go @@ -25,6 +25,11 @@ type Keyboard struct { func (*Keyboard) isEvent() {} +// String implements fmt.Stringer. +func (k Keyboard) String() string { + return fmt.Sprintf("Keyboard{Key: %v}", k.Key) +} + // Resize is the event used when the terminal was resized. // Implements terminalapi.Event. type Resize struct { @@ -34,6 +39,11 @@ type Resize struct { func (*Resize) isEvent() {} +// String implements fmt.Stringer. +func (r Resize) String() string { + return fmt.Sprintf("Resize{Size: %v}", r.Size) +} + // Mouse is the event used when the mouse is moved or a mouse button is // pressed. // Implements terminalapi.Event. @@ -46,6 +56,11 @@ type Mouse struct { func (*Mouse) isEvent() {} +// String implements fmt.Stringer. +func (m Mouse) String() string { + return fmt.Sprintf("Mouse{Position: %v, Button: %v}", m.Position, m.Button) +} + // Error is an event indicating an error while processing input. type Error string