321 lines
5.8 KiB
Go
321 lines
5.8 KiB
Go
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
|
// Use of this source code is governed by a MIT license that can
|
|
// be found in the LICENSE file.
|
|
//
|
|
// Portions of this file uses [termbox-go](https://github.com/nsf/termbox-go/blob/54b74d087b7c397c402d0e3b66d2ccb6eaf5c2b4/api_common.go)
|
|
// by [authors](https://github.com/nsf/termbox-go/blob/master/AUTHORS)
|
|
// under [license](https://github.com/nsf/termbox-go/blob/master/LICENSE)
|
|
|
|
package termui
|
|
|
|
import (
|
|
"path"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/nsf/termbox-go"
|
|
)
|
|
|
|
type Event struct {
|
|
Type string
|
|
Path string
|
|
From string
|
|
To string
|
|
Data interface{}
|
|
Time int64
|
|
}
|
|
|
|
var sysEvtChs []chan Event
|
|
|
|
type EvtKbd struct {
|
|
KeyStr string
|
|
}
|
|
|
|
func evtKbd(e termbox.Event) EvtKbd {
|
|
ek := EvtKbd{}
|
|
|
|
k := string(e.Ch)
|
|
pre := ""
|
|
mod := ""
|
|
|
|
if e.Mod == termbox.ModAlt {
|
|
mod = "M-"
|
|
}
|
|
if e.Ch == 0 {
|
|
if e.Key > 0xFFFF-12 {
|
|
k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
|
|
} else if e.Key > 0xFFFF-25 {
|
|
ks := []string{"<insert>", "<delete>", "<home>", "<end>", "<previous>", "<next>", "<up>", "<down>", "<left>", "<right>"}
|
|
k = ks[0xFFFF-int(e.Key)-12]
|
|
}
|
|
|
|
if e.Key <= 0x7F {
|
|
pre = "C-"
|
|
k = string('a' - 1 + int(e.Key))
|
|
kmap := map[termbox.Key][2]string{
|
|
termbox.KeyCtrlSpace: {"C-", "<space>"},
|
|
termbox.KeyBackspace: {"", "<backspace>"},
|
|
termbox.KeyTab: {"", "<tab>"},
|
|
termbox.KeyEnter: {"", "<enter>"},
|
|
termbox.KeyEsc: {"", "<escape>"},
|
|
termbox.KeyCtrlBackslash: {"C-", "\\"},
|
|
termbox.KeyCtrlSlash: {"C-", "/"},
|
|
termbox.KeySpace: {"", "<space>"},
|
|
termbox.KeyCtrl8: {"C-", "8"},
|
|
}
|
|
if sk, ok := kmap[e.Key]; ok {
|
|
pre = sk[0]
|
|
k = sk[1]
|
|
}
|
|
}
|
|
}
|
|
|
|
ek.KeyStr = pre + mod + k
|
|
return ek
|
|
}
|
|
|
|
func crtTermboxEvt(e termbox.Event) Event {
|
|
systypemap := map[termbox.EventType]string{
|
|
termbox.EventKey: "keyboard",
|
|
termbox.EventResize: "window",
|
|
termbox.EventMouse: "mouse",
|
|
termbox.EventError: "error",
|
|
termbox.EventInterrupt: "interrupt",
|
|
}
|
|
ne := Event{From: "/sys", Time: time.Now().Unix()}
|
|
typ := e.Type
|
|
ne.Type = systypemap[typ]
|
|
|
|
switch typ {
|
|
case termbox.EventKey:
|
|
kbd := evtKbd(e)
|
|
ne.Path = "/sys/kbd/" + kbd.KeyStr
|
|
ne.Data = kbd
|
|
case termbox.EventResize:
|
|
wnd := EvtWnd{}
|
|
wnd.Width = e.Width
|
|
wnd.Height = e.Height
|
|
ne.Path = "/sys/wnd/resize"
|
|
ne.Data = wnd
|
|
case termbox.EventError:
|
|
err := EvtErr(e.Err)
|
|
ne.Path = "/sys/err"
|
|
ne.Data = err
|
|
case termbox.EventMouse:
|
|
m := EvtMouse{}
|
|
m.X = e.MouseX
|
|
m.Y = e.MouseY
|
|
ne.Path = "/sys/mouse"
|
|
ne.Data = m
|
|
}
|
|
return ne
|
|
}
|
|
|
|
type EvtWnd struct {
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
type EvtMouse struct {
|
|
X int
|
|
Y int
|
|
Press string
|
|
}
|
|
|
|
type EvtErr error
|
|
|
|
func hookTermboxEvt() {
|
|
for {
|
|
e := termbox.PollEvent()
|
|
|
|
for _, c := range sysEvtChs {
|
|
go func(ch chan Event) {
|
|
ch <- crtTermboxEvt(e)
|
|
}(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func NewSysEvtCh() chan Event {
|
|
ec := make(chan Event)
|
|
sysEvtChs = append(sysEvtChs, ec)
|
|
return ec
|
|
}
|
|
|
|
var DefaultEvtStream = NewEvtStream()
|
|
|
|
type EvtStream struct {
|
|
sync.RWMutex
|
|
srcMap map[string]chan Event
|
|
stream chan Event
|
|
wg sync.WaitGroup
|
|
sigStopLoop chan Event
|
|
Handlers map[string]func(Event)
|
|
hook func(Event)
|
|
}
|
|
|
|
func NewEvtStream() *EvtStream {
|
|
return &EvtStream{
|
|
srcMap: make(map[string]chan Event),
|
|
stream: make(chan Event),
|
|
Handlers: make(map[string]func(Event)),
|
|
sigStopLoop: make(chan Event),
|
|
}
|
|
}
|
|
|
|
func (es *EvtStream) Init() {
|
|
es.Merge("internal", es.sigStopLoop)
|
|
go func() {
|
|
es.wg.Wait()
|
|
close(es.stream)
|
|
}()
|
|
}
|
|
|
|
func cleanPath(p string) string {
|
|
if p == "" {
|
|
return "/"
|
|
}
|
|
if p[0] != '/' {
|
|
p = "/" + p
|
|
}
|
|
return path.Clean(p)
|
|
}
|
|
|
|
func isPathMatch(pattern, path string) bool {
|
|
if len(pattern) == 0 {
|
|
return false
|
|
}
|
|
n := len(pattern)
|
|
return len(path) >= n && path[0:n] == pattern
|
|
}
|
|
|
|
func (es *EvtStream) Merge(name string, ec chan Event) {
|
|
es.Lock()
|
|
defer es.Unlock()
|
|
|
|
es.wg.Add(1)
|
|
es.srcMap[name] = ec
|
|
|
|
go func(a chan Event) {
|
|
for n := range a {
|
|
n.From = name
|
|
es.stream <- n
|
|
}
|
|
es.wg.Done()
|
|
}(ec)
|
|
}
|
|
|
|
func (es *EvtStream) Handle(path string, handler func(Event)) {
|
|
es.Handlers[cleanPath(path)] = handler
|
|
}
|
|
|
|
func findMatch(mux map[string]func(Event), path string) string {
|
|
n := -1
|
|
pattern := ""
|
|
for m := range mux {
|
|
if !isPathMatch(m, path) {
|
|
continue
|
|
}
|
|
if len(m) > n {
|
|
pattern = m
|
|
n = len(m)
|
|
}
|
|
}
|
|
return pattern
|
|
|
|
}
|
|
|
|
func (es *EvtStream) match(path string) string {
|
|
return findMatch(es.Handlers, path)
|
|
}
|
|
|
|
func (es *EvtStream) Hook(f func(Event)) {
|
|
es.hook = f
|
|
}
|
|
|
|
func (es *EvtStream) Loop() {
|
|
for e := range es.stream {
|
|
switch e.Path {
|
|
case "/sig/stoploop":
|
|
return
|
|
}
|
|
go func(a Event) {
|
|
es.RLock()
|
|
defer es.RUnlock()
|
|
if pattern := es.match(a.Path); pattern != "" {
|
|
es.Handlers[pattern](a)
|
|
}
|
|
}(e)
|
|
if es.hook != nil {
|
|
es.hook(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (es *EvtStream) StopLoop() {
|
|
go func() {
|
|
e := Event{
|
|
Path: "/sig/stoploop",
|
|
}
|
|
es.sigStopLoop <- e
|
|
}()
|
|
}
|
|
|
|
func Merge(name string, ec chan Event) {
|
|
DefaultEvtStream.Merge(name, ec)
|
|
}
|
|
|
|
func Handle(path string, handler func(Event)) {
|
|
DefaultEvtStream.Handle(path, handler)
|
|
}
|
|
|
|
func Loop() {
|
|
DefaultEvtStream.Loop()
|
|
}
|
|
|
|
func StopLoop() {
|
|
DefaultEvtStream.StopLoop()
|
|
}
|
|
|
|
type EvtTimer struct {
|
|
Duration time.Duration
|
|
Count uint64
|
|
}
|
|
|
|
func NewTimerCh(du time.Duration) chan Event {
|
|
t := make(chan Event)
|
|
|
|
go func(a chan Event) {
|
|
n := uint64(0)
|
|
for {
|
|
n++
|
|
time.Sleep(du)
|
|
e := Event{}
|
|
e.Type = "timer"
|
|
e.Path = "/timer/" + du.String()
|
|
e.Time = time.Now().Unix()
|
|
e.Data = EvtTimer{
|
|
Duration: du,
|
|
Count: n,
|
|
}
|
|
t <- e
|
|
|
|
}
|
|
}(t)
|
|
return t
|
|
}
|
|
|
|
var DefualtHandler = func(e Event) {
|
|
}
|
|
|
|
var usrEvtCh = make(chan Event)
|
|
|
|
func SendCustomEvt(path string, data interface{}) {
|
|
e := Event{}
|
|
e.Path = path
|
|
e.Data = data
|
|
e.Time = time.Now().Unix()
|
|
usrEvtCh <- e
|
|
}
|