324 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
 | 
						|
// Use of this source code is governed by a MIT license that can
 | 
						|
// be found in the LICENSE file.
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
}
 | 
						|
// Remove all existing defined Handlers from the map
 | 
						|
func (es *EvtStream) ResetHandlers() {
 | 
						|
	for Path, _ := range es.Handlers {
 | 
						|
		delete(es.Handlers, Path)
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |