From e3e2afc4fcd6bdac096ef33c20bc165570599bbe 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 9c13b45..8640e31 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) +}