From dcc8a3a4f93eb5b7dcc965e63ad2ea6057a8ee6b Mon Sep 17 00:00:00 2001 From: citrons Date: Wed, 29 Jan 2025 21:57:36 -0600 Subject: keyboard and mouse handling --- tui/event.go | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tui/input.go | 57 ------------------------- 2 files changed, 136 insertions(+), 57 deletions(-) create mode 100644 tui/event.go delete mode 100644 tui/input.go (limited to 'tui') diff --git a/tui/event.go b/tui/event.go new file mode 100644 index 0000000..4d0a535 --- /dev/null +++ b/tui/event.go @@ -0,0 +1,136 @@ +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 struct { + Button int + Pressed bool + Released bool + X, Y int + Scroll int + } + Err error + Resize bool +} + +var evChan chan Event +var once sync.Once +func Events() <-chan Event { + once.Do(func() { + evChan = make(chan Event, 2) + + // buffer FindKeys + ch := make(chan termfo.Event, 16) + go func() { + r := bufio.NewReader(os.Stdin) + for e := range terminfo.FindKeys(r) { + ch <- e + if e.Err != nil { + return + } + } + }() + + go func() { + for kev := range ch { + var ev Event + if kev.Err != nil { + ev.Err = kev.Err + evChan <- 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 + evChan <- ev + return + } + default: + ev.Key = kev.Key + } + + evChan <- ev + } + }() + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGWINCH) + go func() { + for _ = range sigs { + evChan <- Event {Resize: true} + } + }() + }) + + return evChan +} + +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 + } + + return nil +} diff --git a/tui/input.go b/tui/input.go deleted file mode 100644 index a200e53..0000000 --- a/tui/input.go +++ /dev/null @@ -1,57 +0,0 @@ -package tui - -import ( - "os" - "os/signal" - "golang.org/x/term" - "unicode" - "syscall" - "bufio" -) - -type Event interface {} - -type TextInput rune -type Paste string -type Resize Dims - -var evChan chan Event -func Events() <-chan Event { - if evChan != nil { - return evChan - } - evChan = make(chan Event, 1) - - go func() { - rd := bufio.NewReader(os.Stdin) - for { - r, _, err := rd.ReadRune() - if err != nil { - evChan <- err - return - } - switch { - case r == '\033': - // todo - case unicode.IsControl(r): - default: - evChan <- TextInput(r) - } - } - }() - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGWINCH) - go func() { - for _ = range sigs { - w, h, err := term.GetSize(0) - if err == nil { - evChan <- Resize {w, h} - } else { - evChan <- err - } - } - }() - - return evChan -} \ No newline at end of file -- cgit v1.2.3