package tui import ( "os" "os/signal" "zgo.at/termfo" "zgo.at/termfo/keys" "strings" "strconv" "bufio" "syscall" "sync" ) type Event struct { TextInput rune Key keys.Key Mouse MouseEvent Err error Resize bool } type MouseEvent struct { Button int Pressed bool Released bool ReleasedAnywhere bool X, Y int Scroll int } var ( evChan chan Event = make(chan Event, 1) lastEvent chan Event = make(chan Event, 1) once sync.Once ) func Events() <-chan Event { once.Do(func() { ch := terminfo.FindKeys(bufio.NewReader(os.Stdin)) go readKeys(ch, evChan) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGWINCH) go func() { for _ = range sigs { sendEvent(Event {Resize: true}) } }() }) return evChan } func sendEvent(ev Event) { select { case lastEvent <- ev: default: } evChan <- ev } func getLastEvent() Event { var ev Event select { case ev = <-lastEvent: default: } return ev } func readKeys(ch <-chan termfo.Event, evChan chan<- Event) { var pasting bool for kev := range ch { var ev Event if kev.Err != nil { ev.Err = kev.Err sendEvent(ev) return } noMods := kev.Key & (keys.Ctrl | keys.Alt) == 0 notSpecial := kev.Key.WithoutMods() < (1 << 32) if noMods && notSpecial { r := rune(kev.Key.WithoutMods()) if kev.Key & keys.Shift != 0 && r >= 'a' && r <= 'z' { r -= 0x20 } ev.TextInput = r } switch kev.Key { case keys.Mouse: if err := parseMouse(&ev, ch); err != nil { ev.Err = err sendEvent(ev) return } default: ev.Key = kev.Key } if string(kev.Seq) == "\033[200~" { pasting = true } if pasting { switch kev.Seq[0] { case '\n', '\t': ev.TextInput = rune(kev.Seq[0]) } ev.Key = 0 } if string(kev.Seq) == "\033[201~" { pasting = false } sendEvent(ev) } } func parseMouse(ev *Event, ch <-chan termfo.Event) error { var ( arg strings.Builder args []int down bool ) parse: for kev := range ch { if kev.Err != nil { return kev.Err } switch { case kev.Key >= '0' && kev.Key <= '9': arg.WriteRune(rune(kev.Key)) case kev.Key == ';': i, _ := strconv.Atoi(arg.String()) args = append(args, i) arg.Reset() case kev.Key == 'm' | keys.Shift: down = true fallthrough default: i, _ := strconv.Atoi(arg.String()) args = append(args, i) break parse } } if len(args) != 3 { return nil } ev.Mouse.X = args[1] - 1 ev.Mouse.Y = args[2] - 1 switch args[0] { case 64: ev.Mouse.Scroll = -1 case 65: ev.Mouse.Scroll = 1 default: ev.Mouse.Button = args[0] ev.Mouse.Pressed = down ev.Mouse.Released = !down ev.Mouse.ReleasedAnywhere = !down } return nil }