From 8a1389155f133761e8e73b585f47ed27a78b74d5 Mon Sep 17 00:00:00 2001 From: Gereon Frey Date: Mon, 13 Mar 2017 00:57:45 +0100 Subject: [PATCH] Solaris support (#110) * Add support for solaris * Change state to handle system dependent termios type. * Move syscalls to get and set termios to functions in files only built for respective platforms. * Create `term_unix.go` go file built on all supported unices except solaris with types and functions valid for all of them. * Change `MakeRaw` to set VMIN and VTIME to default values. Fixes #95. * Fix error handling Doing the string comparison could be improved, but at least we should return an error, if it is not "errno 0". --- README.md | 2 +- term.go | 49 +++++++++++++++++++------------------------------ term_bsd.go | 23 ++++++++++++++++++++--- term_linux.go | 22 ++++++++++++++++++++++ term_solaris.go | 32 ++++++++++++++++++++++++++++++++ term_unix.go | 24 ++++++++++++++++++++++++ utils.go | 4 +++- utils_unix.go | 15 ++++----------- 8 files changed, 125 insertions(+), 46 deletions(-) create mode 100644 term_solaris.go create mode 100644 term_unix.go diff --git a/README.md b/README.md index d73fd29..fab974b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

-A powerful readline library in `Linux` `macOS` `Windows` +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` ## Guide diff --git a/term.go b/term.go index 87ef8f7..133993c 100644 --- a/term.go +++ b/term.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. @@ -19,19 +19,17 @@ package readline import ( "io" "syscall" - "unsafe" ) // State contains the state of a terminal. type State struct { - termios syscall.Termios + termios Termios } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd int) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 + _, err := getTermios(fd) + return err == nil } // MakeRaw put the terminal connected to the given file descriptor into raw @@ -39,8 +37,11 @@ func IsTerminal(fd int) bool { // restored. func MakeRaw(fd int) (*State, error) { var oldState State - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + + if termios, err := getTermios(fd); err != nil { return nil, err + } else { + oldState.termios = *termios } newState := oldState.termios @@ -52,47 +53,35 @@ func MakeRaw(fd int) (*State, error) { newState.Cflag &^= syscall.CSIZE | syscall.PARENB newState.Cflag |= syscall.CS8 - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { - return nil, err - } + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 - return &oldState, nil + return &oldState, setTermios(fd, &newState) } // GetState returns the current state of a terminal which may be useful to // restore the terminal after a signal. func GetState(fd int) (*State, error) { - var oldState State - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + termios, err := getTermios(fd) + if err != nil { return nil, err } - return &oldState, nil + return &State{termios: *termios}, nil } // Restore restores the terminal connected to the given file descriptor to a // previous state. func restoreTerm(fd int, state *State) error { - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) - return err -} - -// GetSize returns the dimensions of the given terminal. -func GetSize(fd int) (width, height int, err error) { - var dimensions [4]uint16 - - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 { - return -1, -1, err - } - return int(dimensions[1]), int(dimensions[0]), nil + return setTermios(fd, &state.termios) } // ReadPassword reads a line of input from a terminal without local echo. This // is commonly used for inputting passwords and other sensitive data. The slice // returned does not include the \n. func ReadPassword(fd int) ([]byte, error) { - var oldState syscall.Termios - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { + oldState, err := getTermios(fd) + if err != nil { return nil, err } @@ -100,12 +89,12 @@ func ReadPassword(fd int) ([]byte, error) { newState.Lflag &^= syscall.ECHO newState.Lflag |= syscall.ICANON | syscall.ISIG newState.Iflag |= syscall.ICRNL - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + if err := setTermios(fd, newState); err != nil { return nil, err } defer func() { - syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) + setTermios(fd, oldState) }() var buf [16]byte diff --git a/term_bsd.go b/term_bsd.go index 69682cd..68b56ea 100644 --- a/term_bsd.go +++ b/term_bsd.go @@ -6,7 +6,24 @@ package readline -import "syscall" +import ( + "syscall" + "unsafe" +) -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/term_linux.go b/term_linux.go index 8918008..e3392b4 100644 --- a/term_linux.go +++ b/term_linux.go @@ -4,8 +4,30 @@ package readline +import ( + "syscall" + "unsafe" +) + // These constants are declared here, rather than importing // them from the syscall package as some syscall packages, even // on linux, for example gccgo, do not declare them. const ioctlReadTermios = 0x5401 // syscall.TCGETS const ioctlWriteTermios = 0x5402 // syscall.TCSETS + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/term_solaris.go b/term_solaris.go new file mode 100644 index 0000000..4c27273 --- /dev/null +++ b/term_solaris.go @@ -0,0 +1,32 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package readline + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} + +type Termios unix.Termios + +func getTermios(fd int) (*Termios, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + return (*Termios)(termios), nil +} + +func setTermios(fd int, termios *Termios) error { + return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios)) +} diff --git a/term_unix.go b/term_unix.go new file mode 100644 index 0000000..d3ea242 --- /dev/null +++ b/term_unix.go @@ -0,0 +1,24 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +type Termios syscall.Termios + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + var dimensions [4]uint16 + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0) + if err != 0 { + return 0, 0, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} diff --git a/utils.go b/utils.go index 96518f1..670736b 100644 --- a/utils.go +++ b/utils.go @@ -82,7 +82,9 @@ func Restore(fd int, state *State) error { if err != nil { // errno 0 means everything is ok :) if err.Error() == "errno 0" { - err = nil + return nil + } else { + return err } } return nil diff --git a/utils_unix.go b/utils_unix.go index 39c32a1..f88dac9 100644 --- a/utils_unix.go +++ b/utils_unix.go @@ -1,4 +1,4 @@ -// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris package readline @@ -8,7 +8,6 @@ import ( "os/signal" "sync" "syscall" - "unsafe" ) type winsize struct { @@ -30,17 +29,11 @@ func SuspendMe() { // get width of the terminal func getWidth(stdoutFd int) int { - ws := &winsize{} - retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, - uintptr(stdoutFd), - uintptr(syscall.TIOCGWINSZ), - uintptr(unsafe.Pointer(ws))) - - if int(retCode) == -1 { - _ = errno + cols, _, err := GetSize(stdoutFd) + if err != nil { return -1 } - return int(ws.Col) + return cols } func GetScreenWidth() int {