tty: remove remaining usages of docker/pkg/term
This removes usages of docker/pkg/term to set raw terminal, handle interrupt and restore the terminal, and instead use containerd/console and handle interrupt ourselves. Signed-off-by: Daniel Dao <dqminh89@gmail.com>
This commit is contained in:
parent
1439022b60
commit
6cfb498d2c
28
tty.go
28
tty.go
|
@ -6,10 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ import (
|
||||||
type tty struct {
|
type tty struct {
|
||||||
epoller *console.Epoller
|
epoller *console.Epoller
|
||||||
console *console.EpollConsole
|
console *console.EpollConsole
|
||||||
state *term.State
|
stdin console.Console
|
||||||
closers []io.Closer
|
closers []io.Closer
|
||||||
postStart []io.Closer
|
postStart []io.Closer
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
@ -93,19 +93,31 @@ func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) error {
|
||||||
t.wg.Add(1)
|
t.wg.Add(1)
|
||||||
go t.copyIO(os.Stdout, epollConsole)
|
go t.copyIO(os.Stdout, epollConsole)
|
||||||
|
|
||||||
// TODO: perhaps migrate to console.SetRaw later. Need to handle interrupt
|
// set raw mode to stdin and also handle interrupt
|
||||||
// ourselves though
|
stdin, err := console.ConsoleFromFile(os.Stdin)
|
||||||
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := stdin.SetRaw(); err != nil {
|
||||||
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
||||||
}
|
}
|
||||||
|
go handleInterrupt(stdin)
|
||||||
|
|
||||||
t.epoller = epoller
|
t.epoller = epoller
|
||||||
t.state = state
|
t.stdin = stdin
|
||||||
t.console = epollConsole
|
t.console = epollConsole
|
||||||
t.closers = []io.Closer{epollConsole}
|
t.closers = []io.Closer{epollConsole}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleInterrupt(c console.Console) {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigchan, os.Interrupt)
|
||||||
|
<-sigchan
|
||||||
|
c.Reset()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tty) waitConsole() error {
|
func (t *tty) waitConsole() error {
|
||||||
if t.consoleC != nil {
|
if t.consoleC != nil {
|
||||||
return <-t.consoleC
|
return <-t.consoleC
|
||||||
|
@ -138,8 +150,8 @@ func (t *tty) Close() error {
|
||||||
for _, c := range t.closers {
|
for _, c := range t.closers {
|
||||||
c.Close()
|
c.Close()
|
||||||
}
|
}
|
||||||
if t.state != nil {
|
if t.stdin != nil {
|
||||||
term.RestoreTerminal(os.Stdin.Fd(), t.state)
|
t.stdin.Reset()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
// +build linux,cgo
|
|
||||||
|
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// #include <termios.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
type Termios syscall.Termios
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd uintptr) (*State, error) {
|
|
||||||
var oldState State
|
|
||||||
if err := tcget(fd, &oldState.termios); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := oldState.termios
|
|
||||||
|
|
||||||
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState)))
|
|
||||||
newState.Oflag = newState.Oflag | C.OPOST
|
|
||||||
if err := tcset(fd, &newState); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
|
||||||
ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p)))
|
|
||||||
if ret != 0 {
|
|
||||||
return err.(syscall.Errno)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
|
||||||
ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p)))
|
|
||||||
if ret != 0 {
|
|
||||||
return err.(syscall.Errno)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
// +build !linux !cgo
|
|
||||||
|
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
|
||||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p)))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
|
||||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p)))
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidState = errors.New("Invalid terminal state")
|
|
||||||
)
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
termios Termios
|
|
||||||
}
|
|
||||||
|
|
||||||
type Winsize struct {
|
|
||||||
Height uint16
|
|
||||||
Width uint16
|
|
||||||
x uint16
|
|
||||||
y uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
||||||
return os.Stdin, os.Stdout, os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
||||||
var inFd uintptr
|
|
||||||
var isTerminalIn bool
|
|
||||||
if file, ok := in.(*os.File); ok {
|
|
||||||
inFd = file.Fd()
|
|
||||||
isTerminalIn = IsTerminal(inFd)
|
|
||||||
}
|
|
||||||
return inFd, isTerminalIn
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
||||||
ws := &Winsize{}
|
|
||||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
|
||||||
// Skipp errno = 0
|
|
||||||
if err == 0 {
|
|
||||||
return ws, nil
|
|
||||||
}
|
|
||||||
return ws, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetWinsize(fd uintptr, ws *Winsize) error {
|
|
||||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
|
||||||
// Skipp errno = 0
|
|
||||||
if err == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd uintptr) bool {
|
|
||||||
var termios Termios
|
|
||||||
return tcget(fd, &termios) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func RestoreTerminal(fd uintptr, state *State) error {
|
|
||||||
if state == nil {
|
|
||||||
return ErrInvalidState
|
|
||||||
}
|
|
||||||
if err := tcset(fd, &state.termios); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveState(fd uintptr) (*State, error) {
|
|
||||||
var oldState State
|
|
||||||
if err := tcget(fd, &oldState.termios); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DisableEcho(fd uintptr, state *State) error {
|
|
||||||
newState := state.termios
|
|
||||||
newState.Lflag &^= syscall.ECHO
|
|
||||||
|
|
||||||
if err := tcset(fd, &newState); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
handleInterrupt(fd, state)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
|
||||||
oldState, err := MakeRaw(fd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
handleInterrupt(fd, oldState)
|
|
||||||
return oldState, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleInterrupt(fd uintptr, state *State) {
|
|
||||||
sigchan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigchan, os.Interrupt)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_ = <-sigchan
|
|
||||||
RestoreTerminal(fd, state)
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term/winconsole"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// State holds the console mode for the terminal.
|
|
||||||
type State struct {
|
|
||||||
mode uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Winsize is used for window size.
|
|
||||||
type Winsize struct {
|
|
||||||
Height uint16
|
|
||||||
Width uint16
|
|
||||||
x uint16
|
|
||||||
y uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
||||||
switch {
|
|
||||||
case os.Getenv("ConEmuANSI") == "ON":
|
|
||||||
// The ConEmu shell emulates ANSI well by default.
|
|
||||||
return os.Stdin, os.Stdout, os.Stderr
|
|
||||||
case os.Getenv("MSYSTEM") != "":
|
|
||||||
// MSYS (mingw) does not emulate ANSI well.
|
|
||||||
return winconsole.WinConsoleStreams()
|
|
||||||
default:
|
|
||||||
return winconsole.WinConsoleStreams()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal.
|
|
||||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
||||||
return winconsole.GetHandleInfo(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor.
|
|
||||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
||||||
info, err := winconsole.GetConsoleScreenBufferInfo(fd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
|
|
||||||
return &Winsize{
|
|
||||||
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
|
||||||
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
|
||||||
x: 0,
|
|
||||||
y: 0}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWinsize sets the size of the given terminal connected to the passed file descriptor.
|
|
||||||
func SetWinsize(fd uintptr, ws *Winsize) error {
|
|
||||||
// TODO(azlinux): Implement SetWinsize
|
|
||||||
logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd uintptr) bool {
|
|
||||||
return winconsole.IsConsole(fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestoreTerminal restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func RestoreTerminal(fd uintptr, state *State) error {
|
|
||||||
return winconsole.SetConsoleMode(fd, state.mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveState saves the state of the terminal connected to the given file descriptor.
|
|
||||||
func SaveState(fd uintptr) (*State, error) {
|
|
||||||
mode, e := winconsole.GetConsoleMode(fd)
|
|
||||||
if e != nil {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
return &State{mode}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
|
||||||
// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
|
||||||
func DisableEcho(fd uintptr, state *State) error {
|
|
||||||
mode := state.mode
|
|
||||||
mode &^= winconsole.ENABLE_ECHO_INPUT
|
|
||||||
mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
|
|
||||||
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
|
||||||
return winconsole.SetConsoleMode(fd, mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRawTerminal puts the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
|
||||||
state, err := MakeRaw(fd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
|
||||||
return state, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd uintptr) (*State, error) {
|
|
||||||
state, err := SaveState(fd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// See
|
|
||||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
|
||||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
|
||||||
mode := state.mode
|
|
||||||
|
|
||||||
// Disable these modes
|
|
||||||
mode &^= winconsole.ENABLE_ECHO_INPUT
|
|
||||||
mode &^= winconsole.ENABLE_LINE_INPUT
|
|
||||||
mode &^= winconsole.ENABLE_MOUSE_INPUT
|
|
||||||
mode &^= winconsole.ENABLE_WINDOW_INPUT
|
|
||||||
mode &^= winconsole.ENABLE_PROCESSED_INPUT
|
|
||||||
|
|
||||||
// Enable these modes
|
|
||||||
mode |= winconsole.ENABLE_EXTENDED_FLAGS
|
|
||||||
mode |= winconsole.ENABLE_INSERT_MODE
|
|
||||||
mode |= winconsole.ENABLE_QUICK_EDIT_MODE
|
|
||||||
|
|
||||||
err = winconsole.SetConsoleMode(fd, mode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return state, nil
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
getTermios = syscall.TIOCGETA
|
|
||||||
setTermios = syscall.TIOCSETA
|
|
||||||
|
|
||||||
IGNBRK = syscall.IGNBRK
|
|
||||||
PARMRK = syscall.PARMRK
|
|
||||||
INLCR = syscall.INLCR
|
|
||||||
IGNCR = syscall.IGNCR
|
|
||||||
ECHONL = syscall.ECHONL
|
|
||||||
CSIZE = syscall.CSIZE
|
|
||||||
ICRNL = syscall.ICRNL
|
|
||||||
ISTRIP = syscall.ISTRIP
|
|
||||||
PARENB = syscall.PARENB
|
|
||||||
ECHO = syscall.ECHO
|
|
||||||
ICANON = syscall.ICANON
|
|
||||||
ISIG = syscall.ISIG
|
|
||||||
IXON = syscall.IXON
|
|
||||||
BRKINT = syscall.BRKINT
|
|
||||||
INPCK = syscall.INPCK
|
|
||||||
OPOST = syscall.OPOST
|
|
||||||
CS8 = syscall.CS8
|
|
||||||
IEXTEN = syscall.IEXTEN
|
|
||||||
)
|
|
||||||
|
|
||||||
type Termios struct {
|
|
||||||
Iflag uint64
|
|
||||||
Oflag uint64
|
|
||||||
Cflag uint64
|
|
||||||
Lflag uint64
|
|
||||||
Cc [20]byte
|
|
||||||
Ispeed uint64
|
|
||||||
Ospeed uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd uintptr) (*State, error) {
|
|
||||||
var oldState State
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := oldState.termios
|
|
||||||
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
|
||||||
newState.Oflag &^= OPOST
|
|
||||||
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
|
||||||
newState.Cflag &^= (CSIZE | PARENB)
|
|
||||||
newState.Cflag |= CS8
|
|
||||||
newState.Cc[syscall.VMIN] = 1
|
|
||||||
newState.Cc[syscall.VTIME] = 0
|
|
||||||
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
getTermios = syscall.TIOCGETA
|
|
||||||
setTermios = syscall.TIOCSETA
|
|
||||||
|
|
||||||
IGNBRK = syscall.IGNBRK
|
|
||||||
PARMRK = syscall.PARMRK
|
|
||||||
INLCR = syscall.INLCR
|
|
||||||
IGNCR = syscall.IGNCR
|
|
||||||
ECHONL = syscall.ECHONL
|
|
||||||
CSIZE = syscall.CSIZE
|
|
||||||
ICRNL = syscall.ICRNL
|
|
||||||
ISTRIP = syscall.ISTRIP
|
|
||||||
PARENB = syscall.PARENB
|
|
||||||
ECHO = syscall.ECHO
|
|
||||||
ICANON = syscall.ICANON
|
|
||||||
ISIG = syscall.ISIG
|
|
||||||
IXON = syscall.IXON
|
|
||||||
BRKINT = syscall.BRKINT
|
|
||||||
INPCK = syscall.INPCK
|
|
||||||
OPOST = syscall.OPOST
|
|
||||||
CS8 = syscall.CS8
|
|
||||||
IEXTEN = syscall.IEXTEN
|
|
||||||
)
|
|
||||||
|
|
||||||
type Termios struct {
|
|
||||||
Iflag uint32
|
|
||||||
Oflag uint32
|
|
||||||
Cflag uint32
|
|
||||||
Lflag uint32
|
|
||||||
Cc [20]byte
|
|
||||||
Ispeed uint32
|
|
||||||
Ospeed uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd uintptr) (*State, error) {
|
|
||||||
var oldState State
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := oldState.termios
|
|
||||||
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
|
||||||
newState.Oflag &^= OPOST
|
|
||||||
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
|
||||||
newState.Cflag &^= (CSIZE | PARENB)
|
|
||||||
newState.Cflag |= CS8
|
|
||||||
newState.Cc[syscall.VMIN] = 1
|
|
||||||
newState.Cc[syscall.VTIME] = 0
|
|
||||||
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
// +build !cgo
|
|
||||||
|
|
||||||
package term
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
getTermios = syscall.TCGETS
|
|
||||||
setTermios = syscall.TCSETS
|
|
||||||
)
|
|
||||||
|
|
||||||
type Termios struct {
|
|
||||||
Iflag uint32
|
|
||||||
Oflag uint32
|
|
||||||
Cflag uint32
|
|
||||||
Lflag uint32
|
|
||||||
Cc [20]byte
|
|
||||||
Ispeed uint32
|
|
||||||
Ospeed uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd uintptr) (*State, error) {
|
|
||||||
var oldState State
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := oldState.termios
|
|
||||||
|
|
||||||
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
|
|
||||||
newState.Oflag &^= syscall.OPOST
|
|
||||||
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
|
|
||||||
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
|
|
||||||
newState.Cflag |= syscall.CS8
|
|
||||||
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,234 +0,0 @@
|
||||||
package winconsole
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
|
||||||
const (
|
|
||||||
ANSI_ESCAPE_PRIMARY = 0x1B
|
|
||||||
ANSI_ESCAPE_SECONDARY = 0x5B
|
|
||||||
ANSI_COMMAND_FIRST = 0x40
|
|
||||||
ANSI_COMMAND_LAST = 0x7E
|
|
||||||
ANSI_PARAMETER_SEP = ";"
|
|
||||||
ANSI_CMD_G0 = '('
|
|
||||||
ANSI_CMD_G1 = ')'
|
|
||||||
ANSI_CMD_G2 = '*'
|
|
||||||
ANSI_CMD_G3 = '+'
|
|
||||||
ANSI_CMD_DECPNM = '>'
|
|
||||||
ANSI_CMD_DECPAM = '='
|
|
||||||
ANSI_CMD_OSC = ']'
|
|
||||||
ANSI_CMD_STR_TERM = '\\'
|
|
||||||
ANSI_BEL = 0x07
|
|
||||||
KEY_EVENT = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// Interface that implements terminal handling
|
|
||||||
type terminalEmulator interface {
|
|
||||||
HandleOutputCommand(fd uintptr, command []byte) (n int, err error)
|
|
||||||
HandleInputSequence(fd uintptr, command []byte) (n int, err error)
|
|
||||||
WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error)
|
|
||||||
ReadChars(fd uintptr, w io.Reader, p []byte) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type terminalWriter struct {
|
|
||||||
wrappedWriter io.Writer
|
|
||||||
emulator terminalEmulator
|
|
||||||
command []byte
|
|
||||||
inSequence bool
|
|
||||||
fd uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
type terminalReader struct {
|
|
||||||
wrappedReader io.ReadCloser
|
|
||||||
emulator terminalEmulator
|
|
||||||
command []byte
|
|
||||||
inSequence bool
|
|
||||||
fd uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
|
||||||
func isAnsiCommandChar(b byte) bool {
|
|
||||||
switch {
|
|
||||||
case ANSI_COMMAND_FIRST <= b && b <= ANSI_COMMAND_LAST && b != ANSI_ESCAPE_SECONDARY:
|
|
||||||
return true
|
|
||||||
case b == ANSI_CMD_G1 || b == ANSI_CMD_OSC || b == ANSI_CMD_DECPAM || b == ANSI_CMD_DECPNM:
|
|
||||||
// non-CSI escape sequence terminator
|
|
||||||
return true
|
|
||||||
case b == ANSI_CMD_STR_TERM || b == ANSI_BEL:
|
|
||||||
// String escape sequence terminator
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCharacterSelectionCmdChar(b byte) bool {
|
|
||||||
return (b == ANSI_CMD_G0 || b == ANSI_CMD_G1 || b == ANSI_CMD_G2 || b == ANSI_CMD_G3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isXtermOscSequence(command []byte, current byte) bool {
|
|
||||||
return (len(command) >= 2 && command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_CMD_OSC && current != ANSI_BEL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes len(p) bytes from p to the underlying data stream.
|
|
||||||
// http://golang.org/pkg/io/#Writer
|
|
||||||
func (tw *terminalWriter) Write(p []byte) (n int, err error) {
|
|
||||||
if len(p) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if tw.emulator == nil {
|
|
||||||
return tw.wrappedWriter.Write(p)
|
|
||||||
}
|
|
||||||
// Emulate terminal by extracting commands and executing them
|
|
||||||
totalWritten := 0
|
|
||||||
start := 0 // indicates start of the next chunk
|
|
||||||
end := len(p)
|
|
||||||
for current := 0; current < end; current++ {
|
|
||||||
if tw.inSequence {
|
|
||||||
// inside escape sequence
|
|
||||||
tw.command = append(tw.command, p[current])
|
|
||||||
if isAnsiCommandChar(p[current]) {
|
|
||||||
if !isXtermOscSequence(tw.command, p[current]) {
|
|
||||||
// found the last command character.
|
|
||||||
// Now we have a complete command.
|
|
||||||
nchar, err := tw.emulator.HandleOutputCommand(tw.fd, tw.command)
|
|
||||||
totalWritten += nchar
|
|
||||||
if err != nil {
|
|
||||||
return totalWritten, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear the command
|
|
||||||
// don't include current character again
|
|
||||||
tw.command = tw.command[:0]
|
|
||||||
start = current + 1
|
|
||||||
tw.inSequence = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if p[current] == ANSI_ESCAPE_PRIMARY {
|
|
||||||
// entering escape sequnce
|
|
||||||
tw.inSequence = true
|
|
||||||
// indicates end of "normal sequence", write whatever you have so far
|
|
||||||
if len(p[start:current]) > 0 {
|
|
||||||
nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:current])
|
|
||||||
totalWritten += nw
|
|
||||||
if err != nil {
|
|
||||||
return totalWritten, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// include the current character as part of the next sequence
|
|
||||||
tw.command = append(tw.command, p[current])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// note that so far, start of the escape sequence triggers writing out of bytes to console.
|
|
||||||
// For the part _after_ the end of last escape sequence, it is not written out yet. So write it out
|
|
||||||
if !tw.inSequence {
|
|
||||||
// assumption is that we can't be inside sequence and therefore command should be empty
|
|
||||||
if len(p[start:]) > 0 {
|
|
||||||
nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:])
|
|
||||||
totalWritten += nw
|
|
||||||
if err != nil {
|
|
||||||
return totalWritten, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return totalWritten, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads up to len(p) bytes into p.
|
|
||||||
// http://golang.org/pkg/io/#Reader
|
|
||||||
func (tr *terminalReader) Read(p []byte) (n int, err error) {
|
|
||||||
//Implementations of Read are discouraged from returning a zero byte count
|
|
||||||
// with a nil error, except when len(p) == 0.
|
|
||||||
if len(p) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if nil == tr.emulator {
|
|
||||||
return tr.readFromWrappedReader(p)
|
|
||||||
}
|
|
||||||
return tr.emulator.ReadChars(tr.fd, tr.wrappedReader, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the underlying stream
|
|
||||||
func (tr *terminalReader) Close() (err error) {
|
|
||||||
return tr.wrappedReader.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *terminalReader) readFromWrappedReader(p []byte) (n int, err error) {
|
|
||||||
return tr.wrappedReader.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ansiCommand struct {
|
|
||||||
CommandBytes []byte
|
|
||||||
Command string
|
|
||||||
Parameters []string
|
|
||||||
IsSpecial bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAnsiCommand(command []byte) *ansiCommand {
|
|
||||||
if isCharacterSelectionCmdChar(command[1]) {
|
|
||||||
// Is Character Set Selection commands
|
|
||||||
return &ansiCommand{
|
|
||||||
CommandBytes: command,
|
|
||||||
Command: string(command),
|
|
||||||
IsSpecial: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// last char is command character
|
|
||||||
lastCharIndex := len(command) - 1
|
|
||||||
|
|
||||||
retValue := &ansiCommand{
|
|
||||||
CommandBytes: command,
|
|
||||||
Command: string(command[lastCharIndex]),
|
|
||||||
IsSpecial: false,
|
|
||||||
}
|
|
||||||
// more than a single escape
|
|
||||||
if lastCharIndex != 0 {
|
|
||||||
start := 1
|
|
||||||
// skip if double char escape sequence
|
|
||||||
if command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_ESCAPE_SECONDARY {
|
|
||||||
start++
|
|
||||||
}
|
|
||||||
// convert this to GetNextParam method
|
|
||||||
retValue.Parameters = strings.Split(string(command[start:lastCharIndex]), ANSI_PARAMETER_SEP)
|
|
||||||
}
|
|
||||||
return retValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ansiCommand) getParam(index int) string {
|
|
||||||
if len(c.Parameters) > index {
|
|
||||||
return c.Parameters[index]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *ansiCommand) String() string {
|
|
||||||
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
|
||||||
bytesToHex(ac.CommandBytes),
|
|
||||||
ac.Command,
|
|
||||||
strings.Join(ac.Parameters, "\",\""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func bytesToHex(b []byte) string {
|
|
||||||
hex := make([]string, len(b))
|
|
||||||
for i, ch := range b {
|
|
||||||
hex[i] = fmt.Sprintf("%X", ch)
|
|
||||||
}
|
|
||||||
return strings.Join(hex, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) {
|
|
||||||
if s == "" {
|
|
||||||
return defaultValue, nil
|
|
||||||
}
|
|
||||||
parsedValue, err := strconv.ParseInt(s, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue, err
|
|
||||||
}
|
|
||||||
return int16(parsedValue), nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue