From 6d90e6b4c03b26ca7126c18f965441c4b55deeb6 Mon Sep 17 00:00:00 2001 From: Hannu Hartikainen Date: Sat, 23 May 2020 11:49:30 +0300 Subject: [PATCH] Replace calls to `stty` with syscalls - Move termios-related functionality to its own package - Reimplement stty calls using ioctl calls - Add per-platform constants for linux and non-linux because the ioctl enums are different. The enums in Darwin seem to come from BSD and so I'm assuming they might also work for other operating systems. If not, we'll need to add other build-constrained const files. This code has been tested on Darwin and will be tested on Linux before merging. --- client.go | 20 ++----------- cui/cui.go | 37 ++++++----------------- termios/consts_linux.go | 10 +++++++ termios/consts_nonlinux.go | 10 +++++++ termios/termios.go | 60 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 termios/consts_linux.go create mode 100644 termios/consts_nonlinux.go create mode 100644 termios/termios.go diff --git a/client.go b/client.go index 5322c08..fdf44ce 100644 --- a/client.go +++ b/client.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "regexp" "strconv" @@ -19,6 +18,7 @@ import ( "tildegit.org/sloum/bombadillo/http" "tildegit.org/sloum/bombadillo/local" "tildegit.org/sloum/bombadillo/telnet" + "tildegit.org/sloum/bombadillo/termios" ) //------------------------------------------------\\ @@ -43,14 +43,7 @@ type client struct { //--------------------------------------------------\\ func (c *client) GetSizeOnce() { - cmd := exec.Command("stty", "size") - cmd.Stdin = os.Stdin - out, err := cmd.Output() - if err != nil { - cui.Exit(5, "Fatal error: Unable to retrieve terminal size") - } - var h, w int - _, _ = fmt.Sscan(string(out), &h, &w) + var w, h = termios.GetWindowSize() c.Height = h c.Width = w } @@ -61,14 +54,7 @@ func (c *client) GetSize() { c.Draw() for { - cmd := exec.Command("stty", "size") - cmd.Stdin = os.Stdin - out, err := cmd.Output() - if err != nil { - cui.Exit(5, "Fatal error: Unable to retrieve terminal size") - } - var h, w int - _, _ = fmt.Sscan(string(out), &h, &w) + var w, h = termios.GetWindowSize() if h != c.Height || w != c.Width { c.Height = h c.Width = w diff --git a/cui/cui.go b/cui/cui.go index 676fcc6..c3ee14a 100644 --- a/cui/cui.go +++ b/cui/cui.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "os/exec" + + "tildegit.org/sloum/bombadillo/termios" ) var Shapes = map[string]string{ @@ -55,16 +57,17 @@ func Exit(exitCode int, msg string) { // InitTerm sets the terminal modes appropriate for Bombadillo func InitTerm() { - SetCharMode() - Tput("smcup") // use alternate screen - Tput("rmam") // turn off line wrapping + termios.SetCharMode() + Tput("smcup") // use alternate screen + Tput("rmam") // turn off line wrapping + fmt.Print("\033[?25l") // hide cursor } // CleanupTerm reverts changs to terminal mode made by InitTerm func CleanupTerm() { moveCursorToward("down", 500) moveCursorToward("right", 500) - SetLineMode() + termios.SetLineMode() fmt.Print("\n") fmt.Print("\033[?25h") // reenables cursor blinking @@ -98,7 +101,7 @@ func Getch() rune { } func GetLine(prefix string) (string, error) { - SetLineMode() + termios.SetLineMode() reader := bufio.NewReader(os.Stdin) fmt.Print(prefix) @@ -107,32 +110,10 @@ func GetLine(prefix string) (string, error) { return "", err } - SetCharMode() + termios.SetCharMode() return text[:len(text)-1], nil } -func SetCharMode() { - cmd := exec.Command("stty", "cbreak", "-echo") - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - err := cmd.Run() - if err != nil { - panic(err) - } - - fmt.Print("\033[?25l") -} - -func SetLineMode() { - cmd := exec.Command("stty", "-cbreak", "echo") - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - err := cmd.Run() - if err != nil { - panic(err) - } -} - func Tput(opt string) { cmd := exec.Command("tput", opt) cmd.Stdin = os.Stdin diff --git a/termios/consts_linux.go b/termios/consts_linux.go new file mode 100644 index 0000000..ae6e076 --- /dev/null +++ b/termios/consts_linux.go @@ -0,0 +1,10 @@ +// +build linux + +package termios + +import "syscall" + +const ( + getTermiosIoctl = syscall.TCGETS + setTermiosIoctl = syscall.TCSETS +) diff --git a/termios/consts_nonlinux.go b/termios/consts_nonlinux.go new file mode 100644 index 0000000..ca0daf7 --- /dev/null +++ b/termios/consts_nonlinux.go @@ -0,0 +1,10 @@ +// +build !linux + +package termios + +import "syscall" + +const ( + getTermiosIoctl = syscall.TIOCGETA + setTermiosIoctl = syscall.TIOCSETAF +) diff --git a/termios/termios.go b/termios/termios.go new file mode 100644 index 0000000..9e0a5e7 --- /dev/null +++ b/termios/termios.go @@ -0,0 +1,60 @@ +package termios + +import ( + "os" + "runtime" + "syscall" + "unsafe" +) + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +var fd = os.Stdin.Fd() + +func ioctl(fd, request, argp uintptr) error { + if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, request, argp); e != 0 { + return e + } + return nil +} + +func GetWindowSize() (int, int) { + var value winsize + ioctl(fd, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&value))) + return int(value.Col), int(value.Row) +} + +func getTermios() syscall.Termios { + var value syscall.Termios + err := ioctl(fd, getTermiosIoctl, uintptr(unsafe.Pointer(&value))) + if err != nil { + panic(err) + } + return value +} + +func setTermios(termios syscall.Termios) { + err := ioctl(fd, setTermiosIoctl, uintptr(unsafe.Pointer(&termios))) + if err != nil { + panic(err) + } + runtime.KeepAlive(termios) +} + +func SetCharMode() { + t := getTermios() + t.Lflag = t.Lflag ^ syscall.ICANON + t.Lflag = t.Lflag ^ syscall.ECHO + setTermios(t) +} + +func SetLineMode() { + var t = getTermios() + t.Lflag = t.Lflag | (syscall.ICANON | syscall.ECHO) + setTermios(t) +}