You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

214 lines
4.8 KiB

// +build !windows
package keyboard
import (
"os"
"os/signal"
"syscall"
"unicode/utf8"
"strings"
"fmt"
"runtime"
"unsafe"
"golang.org/x/sys/unix"
)
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)
quit = make(chan int)
inbuf = make([]byte, 0, 128)
input_buf = make(chan input_event)
)
func fcntl(cmd int, arg int) error {
_, _, e := syscall.Syscall(unix.SYS_FCNTL, uintptr(in), uintptr(cmd), uintptr(arg))
if e != 0 {
return e
}
return nil
}
func ioctl(cmd int, termios *unix.Termios) error {
r, _, e := syscall.Syscall(unix.SYS_IOCTL, out.Fd(), uintptr(cmd), uintptr(unsafe.Pointer(termios)))
if r != 0 {
return os.NewSyscallError("SYS_IOCTL", e)
}
return nil
}
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 {
if len(inbuf) == 0 {
return 0
}
if inbuf[0] == '\033' {
// possible escape sequence
if size, event := parse_escape_sequence(inbuf); size != 0 {
input_comm <- event
return size
}
// it's not a recognized escape sequence, then return Esc
input_comm <- keyEvent{key: KeyEsc}
return len(inbuf)
}
// 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 {
input_comm <- keyEvent{key: Key(inbuf[0])}
return 1
}
// the only possible option is utf8 rune
if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
input_comm <- keyEvent{rune: r}
return n
}
return 0
}
// Wait for an event and return it. This is a blocking function call.
func inputEventsProducer() {
// try to extract event from input buffer, return on success
size := extract_event(inbuf)
if size != 0 {
copy(inbuf, inbuf[size:])
inbuf = inbuf[:len(inbuf)-size]
}
for {
select {
case ev := <-input_buf:
if ev.err != nil {
input_comm <- keyEvent{err: ev.err}
return
}
inbuf = append(inbuf, ev.data...)
size = extract_event(inbuf)
if size != 0 {
copy(inbuf, inbuf[size:])
inbuf = inbuf[:len(inbuf)-size]
}
case <-quit:
return
}
}
}
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 fmt.Errorf("Error while reading terminfo data: %v", err)
}
signal.Notify(sigio, unix.SIGIO)
err = fcntl(unix.F_SETFL, unix.O_ASYNC|unix.O_NONBLOCK)
if err != nil {
return
}
err = fcntl(unix.F_SETOWN, unix.Getpid())
if runtime.GOOS != "darwin" && err != nil {
return
}
err = ioctl(ioctl_GETATTR, &orig_tios)
if 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
err = ioctl(ioctl_SETATTR, &tios)
if err != nil {
return err
}
go func() {
buf := make([]byte, 128)
for {
select {
case <-sigio:
for {
n, err := syscall.Read(in, buf)
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
break
}
select {
case input_buf <- input_event{buf[:n], err}:
continue
case <-quit:
return
}
}
case <-quit:
return
}
}
}()
go inputEventsProducer()
return
}
func releaseConsole() {
quit <- 1
ioctl(ioctl_SETATTR, &orig_tios)
out.Close()
unix.Close(in)
}