208 lines
4.1 KiB
Go
208 lines
4.1 KiB
Go
|
// +build !windows
|
||
|
|
||
|
package keyboard
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"golang.org/x/sys/unix"
|
||
|
"os"
|
||
|
"os/signal"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"syscall"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
input_event struct {
|
||
|
data []byte
|
||
|
err error
|
||
|
}
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
out *os.File
|
||
|
in int
|
||
|
|
||
|
// term specific keys
|
||
|
keys []string
|
||
|
|
||
|
// termbox inner state
|
||
|
orig_tios unix.Termios
|
||
|
|
||
|
sigio = make(chan os.Signal, 1)
|
||
|
quitEvProd = make(chan bool)
|
||
|
quitConsole = make(chan bool)
|
||
|
inbuf = make([]byte, 0, 128)
|
||
|
input_buf = make(chan input_event)
|
||
|
)
|
||
|
|
||
|
func parse_escape_sequence(buf []byte) (size int, event KeyEvent) {
|
||
|
bufstr := string(buf)
|
||
|
for i, key := range keys {
|
||
|
if strings.HasPrefix(bufstr, key) {
|
||
|
event.Rune = 0
|
||
|
event.Key = Key(0xFFFF - i)
|
||
|
size = len(key)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
return 0, event
|
||
|
}
|
||
|
|
||
|
func extract_event(inbuf []byte) (int, KeyEvent) {
|
||
|
if len(inbuf) == 0 {
|
||
|
return 0, KeyEvent{}
|
||
|
}
|
||
|
|
||
|
if inbuf[0] == '\033' {
|
||
|
if len(inbuf) == 1 {
|
||
|
return 1, KeyEvent{Key: KeyEsc}
|
||
|
}
|
||
|
// possible escape sequence
|
||
|
if size, event := parse_escape_sequence(inbuf); size != 0 {
|
||
|
return size, event
|
||
|
} else {
|
||
|
// it's not a recognized escape sequence, return error
|
||
|
i := 1 // check for multiple sequences in the buffer
|
||
|
for ; i < len(inbuf) && inbuf[i] != '\033'; i++ {
|
||
|
}
|
||
|
return i, KeyEvent{Key: KeyEsc, Err: errors.New("Unrecognized escape sequence")}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we're here, this is not an escape sequence and not an alt sequence
|
||
|
// so, it's a FUNCTIONAL KEY or a UNICODE character
|
||
|
|
||
|
// first of all check if it's a functional key
|
||
|
if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
|
||
|
return 1, KeyEvent{Key: Key(inbuf[0])}
|
||
|
}
|
||
|
|
||
|
// the only possible option is utf8 rune
|
||
|
if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
|
||
|
return n, KeyEvent{Rune: r}
|
||
|
}
|
||
|
|
||
|
return 0, KeyEvent{}
|
||
|
}
|
||
|
|
||
|
// Wait for an event and return it. This is a blocking function call.
|
||
|
func inputEventsProducer() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-quitEvProd:
|
||
|
return
|
||
|
case ev := <-input_buf:
|
||
|
if ev.err != nil {
|
||
|
select {
|
||
|
case <-quitEvProd:
|
||
|
return
|
||
|
case inputComm <- KeyEvent{Err: ev.err}:
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
inbuf = append(inbuf, ev.data...)
|
||
|
for {
|
||
|
size, event := extract_event(inbuf)
|
||
|
if size > 0 {
|
||
|
select {
|
||
|
case <-quitEvProd:
|
||
|
return
|
||
|
case inputComm <- event:
|
||
|
}
|
||
|
copy(inbuf, inbuf[size:])
|
||
|
inbuf = inbuf[:len(inbuf)-size]
|
||
|
}
|
||
|
if size == 0 || len(inbuf) == 0 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func initConsole() (err error) {
|
||
|
out, err = os.OpenFile("/dev/tty", unix.O_WRONLY, 0)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
in, err = syscall.Open("/dev/tty", unix.O_RDONLY, 0)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
err = setup_term()
|
||
|
if err != nil {
|
||
|
return errors.New("Error while reading terminfo data:" + err.Error())
|
||
|
}
|
||
|
|
||
|
signal.Notify(sigio, unix.SIGIO)
|
||
|
|
||
|
if _, err = unix.FcntlInt(uintptr(in), unix.F_SETFL, unix.O_ASYNC|unix.O_NONBLOCK); err != nil {
|
||
|
return
|
||
|
}
|
||
|
_, err = unix.FcntlInt(uintptr(in), unix.F_SETOWN, unix.Getpid())
|
||
|
if runtime.GOOS != "darwin" && err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = unix.IoctlSetTermios(int(out.Fd()), ioctl_GETATTR, &orig_tios); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
tios := orig_tios
|
||
|
tios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK |
|
||
|
unix.ISTRIP | unix.INLCR | unix.IGNCR |
|
||
|
unix.ICRNL | unix.IXON
|
||
|
tios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON |
|
||
|
unix.ISIG | unix.IEXTEN
|
||
|
tios.Cflag &^= unix.CSIZE | unix.PARENB
|
||
|
tios.Cflag |= unix.CS8
|
||
|
tios.Cc[unix.VMIN] = 1
|
||
|
tios.Cc[unix.VTIME] = 0
|
||
|
|
||
|
if err = unix.IoctlSetTermios(int(out.Fd()), ioctl_SETATTR, &tios); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
go func() {
|
||
|
buf := make([]byte, 128)
|
||
|
for {
|
||
|
select {
|
||
|
case <-quitConsole:
|
||
|
return
|
||
|
case <-sigio:
|
||
|
for {
|
||
|
bytesRead, err := syscall.Read(in, buf)
|
||
|
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
|
||
|
break
|
||
|
}
|
||
|
if err != nil {
|
||
|
bytesRead = 0
|
||
|
}
|
||
|
data := make([]byte, bytesRead)
|
||
|
copy(data, buf)
|
||
|
select {
|
||
|
case <-quitConsole:
|
||
|
return
|
||
|
case input_buf <- input_event{data, err}:
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
go inputEventsProducer()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func releaseConsole() {
|
||
|
quitConsole <- true
|
||
|
quitEvProd <- true
|
||
|
unix.IoctlSetTermios(int(out.Fd()), ioctl_SETATTR, &orig_tios)
|
||
|
out.Close()
|
||
|
unix.Close(in)
|
||
|
}
|