mirror of
https://github.com/golang/go
synced 2024-11-20 10:04:45 -07:00
exp/terminal: split terminal handling from exp/ssh
This change splits terminal handling from exp/ssh, as suggested several times in the ssh code review. shell.go and shell_test.go are copies from exp/ssh with minimal changes, so don't need another full review. A future CL will remove that code from exp/ssh. R=bradfitz, r, dave, rsc CC=golang-dev https://golang.org/cl/5278049
This commit is contained in:
parent
ec158f77bd
commit
7bc4f8de0f
15
src/pkg/exp/terminal/Makefile
Normal file
15
src/pkg/exp/terminal/Makefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
include ../../../Make.inc
|
||||||
|
|
||||||
|
TARG=exp/terminal
|
||||||
|
GOFILES=\
|
||||||
|
shell.go\
|
||||||
|
|
||||||
|
ifneq ($(GOOS),windows)
|
||||||
|
GOFILES+=terminal.go
|
||||||
|
endif
|
||||||
|
|
||||||
|
include ../../../Make.pkg
|
359
src/pkg/exp/terminal/shell.go
Normal file
359
src/pkg/exp/terminal/shell.go
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shell contains the state for running a VT100 terminal that is capable of
|
||||||
|
// reading lines of input.
|
||||||
|
type Shell struct {
|
||||||
|
c io.ReadWriter
|
||||||
|
prompt string
|
||||||
|
|
||||||
|
// line is the current line being entered.
|
||||||
|
line []byte
|
||||||
|
// pos is the logical position of the cursor in line
|
||||||
|
pos int
|
||||||
|
|
||||||
|
// cursorX contains the current X value of the cursor where the left
|
||||||
|
// edge is 0. cursorY contains the row number where the first row of
|
||||||
|
// the current line is 0.
|
||||||
|
cursorX, cursorY int
|
||||||
|
// maxLine is the greatest value of cursorY so far.
|
||||||
|
maxLine int
|
||||||
|
|
||||||
|
termWidth, termHeight int
|
||||||
|
|
||||||
|
// outBuf contains the terminal data to be sent.
|
||||||
|
outBuf []byte
|
||||||
|
// remainder contains the remainder of any partial key sequences after
|
||||||
|
// a read. It aliases into inBuf.
|
||||||
|
remainder []byte
|
||||||
|
inBuf [256]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShell runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||||
|
// a local terminal, that terminal must first have been put into raw mode.
|
||||||
|
// prompt is a string that is written at the start of each input line (i.e.
|
||||||
|
// "> ").
|
||||||
|
func NewShell(c io.ReadWriter, prompt string) *Shell {
|
||||||
|
return &Shell{
|
||||||
|
c: c,
|
||||||
|
prompt: prompt,
|
||||||
|
termWidth: 80,
|
||||||
|
termHeight: 24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyCtrlD = 4
|
||||||
|
keyEnter = '\r'
|
||||||
|
keyEscape = 27
|
||||||
|
keyBackspace = 127
|
||||||
|
keyUnknown = 256 + iota
|
||||||
|
keyUp
|
||||||
|
keyDown
|
||||||
|
keyLeft
|
||||||
|
keyRight
|
||||||
|
keyAltLeft
|
||||||
|
keyAltRight
|
||||||
|
)
|
||||||
|
|
||||||
|
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||||
|
// the key and the remainder of the input. Otherwise it returns -1.
|
||||||
|
func bytesToKey(b []byte) (int, []byte) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != keyEscape {
|
||||||
|
return int(b[0]), b[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||||
|
switch b[2] {
|
||||||
|
case 'A':
|
||||||
|
return keyUp, b[3:]
|
||||||
|
case 'B':
|
||||||
|
return keyDown, b[3:]
|
||||||
|
case 'C':
|
||||||
|
return keyRight, b[3:]
|
||||||
|
case 'D':
|
||||||
|
return keyLeft, b[3:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||||
|
switch b[5] {
|
||||||
|
case 'C':
|
||||||
|
return keyAltRight, b[6:]
|
||||||
|
case 'D':
|
||||||
|
return keyAltLeft, b[6:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here then we have a key that we don't recognise, or a
|
||||||
|
// partial sequence. It's not clear how one should find the end of a
|
||||||
|
// sequence without knowing them all, but it seems that [a-zA-Z] only
|
||||||
|
// appears at the end of a sequence.
|
||||||
|
for i, c := range b[0:] {
|
||||||
|
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
|
||||||
|
return keyUnknown, b[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue appends data to the end of ss.outBuf
|
||||||
|
func (ss *Shell) queue(data []byte) {
|
||||||
|
if len(ss.outBuf)+len(data) > cap(ss.outBuf) {
|
||||||
|
newOutBuf := make([]byte, len(ss.outBuf), 2*(len(ss.outBuf)+len(data)))
|
||||||
|
copy(newOutBuf, ss.outBuf)
|
||||||
|
ss.outBuf = newOutBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
oldLen := len(ss.outBuf)
|
||||||
|
ss.outBuf = ss.outBuf[:len(ss.outBuf)+len(data)]
|
||||||
|
copy(ss.outBuf[oldLen:], data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'}
|
||||||
|
|
||||||
|
func isPrintable(key int) bool {
|
||||||
|
return key >= 32 && key < 127
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveCursorToPos appends data to ss.outBuf which will move the cursor to the
|
||||||
|
// given, logical position in the text.
|
||||||
|
func (ss *Shell) moveCursorToPos(pos int) {
|
||||||
|
x := len(ss.prompt) + pos
|
||||||
|
y := x / ss.termWidth
|
||||||
|
x = x % ss.termWidth
|
||||||
|
|
||||||
|
up := 0
|
||||||
|
if y < ss.cursorY {
|
||||||
|
up = ss.cursorY - y
|
||||||
|
}
|
||||||
|
|
||||||
|
down := 0
|
||||||
|
if y > ss.cursorY {
|
||||||
|
down = y - ss.cursorY
|
||||||
|
}
|
||||||
|
|
||||||
|
left := 0
|
||||||
|
if x < ss.cursorX {
|
||||||
|
left = ss.cursorX - x
|
||||||
|
}
|
||||||
|
|
||||||
|
right := 0
|
||||||
|
if x > ss.cursorX {
|
||||||
|
right = x - ss.cursorX
|
||||||
|
}
|
||||||
|
|
||||||
|
movement := make([]byte, 3*(up+down+left+right))
|
||||||
|
m := movement
|
||||||
|
for i := 0; i < up; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'A'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < down; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'B'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < left; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'D'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < right; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'C'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.cursorX = x
|
||||||
|
ss.cursorY = y
|
||||||
|
ss.queue(movement)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLineLength = 4096
|
||||||
|
|
||||||
|
// handleKey processes the given key and, optionally, returns a line of text
|
||||||
|
// that the user has entered.
|
||||||
|
func (ss *Shell) handleKey(key int) (line string, ok bool) {
|
||||||
|
switch key {
|
||||||
|
case keyBackspace:
|
||||||
|
if ss.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ss.pos--
|
||||||
|
|
||||||
|
copy(ss.line[ss.pos:], ss.line[1+ss.pos:])
|
||||||
|
ss.line = ss.line[:len(ss.line)-1]
|
||||||
|
ss.writeLine(ss.line[ss.pos:])
|
||||||
|
ss.moveCursorToPos(ss.pos)
|
||||||
|
ss.queue(eraseUnderCursor)
|
||||||
|
case keyAltLeft:
|
||||||
|
// move left by a word.
|
||||||
|
if ss.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ss.pos--
|
||||||
|
for ss.pos > 0 {
|
||||||
|
if ss.line[ss.pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ss.pos--
|
||||||
|
}
|
||||||
|
for ss.pos > 0 {
|
||||||
|
if ss.line[ss.pos] == ' ' {
|
||||||
|
ss.pos++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ss.pos--
|
||||||
|
}
|
||||||
|
ss.moveCursorToPos(ss.pos)
|
||||||
|
case keyAltRight:
|
||||||
|
// move right by a word.
|
||||||
|
for ss.pos < len(ss.line) {
|
||||||
|
if ss.line[ss.pos] == ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ss.pos++
|
||||||
|
}
|
||||||
|
for ss.pos < len(ss.line) {
|
||||||
|
if ss.line[ss.pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ss.pos++
|
||||||
|
}
|
||||||
|
ss.moveCursorToPos(ss.pos)
|
||||||
|
case keyLeft:
|
||||||
|
if ss.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ss.pos--
|
||||||
|
ss.moveCursorToPos(ss.pos)
|
||||||
|
case keyRight:
|
||||||
|
if ss.pos == len(ss.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ss.pos++
|
||||||
|
ss.moveCursorToPos(ss.pos)
|
||||||
|
case keyEnter:
|
||||||
|
ss.moveCursorToPos(len(ss.line))
|
||||||
|
ss.queue([]byte("\r\n"))
|
||||||
|
line = string(ss.line)
|
||||||
|
ok = true
|
||||||
|
ss.line = ss.line[:0]
|
||||||
|
ss.pos = 0
|
||||||
|
ss.cursorX = 0
|
||||||
|
ss.cursorY = 0
|
||||||
|
ss.maxLine = 0
|
||||||
|
default:
|
||||||
|
if !isPrintable(key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ss.line) == maxLineLength {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ss.line) == cap(ss.line) {
|
||||||
|
newLine := make([]byte, len(ss.line), 2*(1+len(ss.line)))
|
||||||
|
copy(newLine, ss.line)
|
||||||
|
ss.line = newLine
|
||||||
|
}
|
||||||
|
ss.line = ss.line[:len(ss.line)+1]
|
||||||
|
copy(ss.line[ss.pos+1:], ss.line[ss.pos:])
|
||||||
|
ss.line[ss.pos] = byte(key)
|
||||||
|
ss.writeLine(ss.line[ss.pos:])
|
||||||
|
ss.pos++
|
||||||
|
ss.moveCursorToPos(ss.pos)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Shell) writeLine(line []byte) {
|
||||||
|
for len(line) != 0 {
|
||||||
|
if ss.cursorX == ss.termWidth {
|
||||||
|
ss.queue([]byte("\r\n"))
|
||||||
|
ss.cursorX = 0
|
||||||
|
ss.cursorY++
|
||||||
|
if ss.cursorY > ss.maxLine {
|
||||||
|
ss.maxLine = ss.cursorY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingOnLine := ss.termWidth - ss.cursorX
|
||||||
|
todo := len(line)
|
||||||
|
if todo > remainingOnLine {
|
||||||
|
todo = remainingOnLine
|
||||||
|
}
|
||||||
|
ss.queue(line[:todo])
|
||||||
|
ss.cursorX += todo
|
||||||
|
line = line[todo:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Shell) Write(buf []byte) (n int, err os.Error) {
|
||||||
|
return ss.c.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine returns a line of input from the terminal.
|
||||||
|
func (ss *Shell) ReadLine() (line string, err os.Error) {
|
||||||
|
ss.writeLine([]byte(ss.prompt))
|
||||||
|
ss.c.Write(ss.outBuf)
|
||||||
|
ss.outBuf = ss.outBuf[:0]
|
||||||
|
|
||||||
|
for {
|
||||||
|
// ss.remainder is a slice at the beginning of ss.inBuf
|
||||||
|
// containing a partial key sequence
|
||||||
|
readBuf := ss.inBuf[len(ss.remainder):]
|
||||||
|
var n int
|
||||||
|
n, err = ss.c.Read(readBuf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
ss.remainder = ss.inBuf[:n+len(ss.remainder)]
|
||||||
|
rest := ss.remainder
|
||||||
|
lineOk := false
|
||||||
|
for !lineOk {
|
||||||
|
var key int
|
||||||
|
key, rest = bytesToKey(rest)
|
||||||
|
if key < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if key == keyCtrlD {
|
||||||
|
return "", os.EOF
|
||||||
|
}
|
||||||
|
line, lineOk = ss.handleKey(key)
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
n := copy(ss.inBuf[:], rest)
|
||||||
|
ss.remainder = ss.inBuf[:n]
|
||||||
|
} else {
|
||||||
|
ss.remainder = nil
|
||||||
|
}
|
||||||
|
ss.c.Write(ss.outBuf)
|
||||||
|
ss.outBuf = ss.outBuf[:0]
|
||||||
|
if lineOk {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
110
src/pkg/exp/terminal/shell_test.go
Normal file
110
src/pkg/exp/terminal/shell_test.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockTerminal struct {
|
||||||
|
toSend []byte
|
||||||
|
bytesPerRead int
|
||||||
|
received []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockTerminal) Read(data []byte) (n int, err os.Error) {
|
||||||
|
n = len(data)
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n > len(c.toSend) {
|
||||||
|
n = len(c.toSend)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return 0, os.EOF
|
||||||
|
}
|
||||||
|
if c.bytesPerRead > 0 && n > c.bytesPerRead {
|
||||||
|
n = c.bytesPerRead
|
||||||
|
}
|
||||||
|
copy(data, c.toSend[:n])
|
||||||
|
c.toSend = c.toSend[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockTerminal) Write(data []byte) (n int, err os.Error) {
|
||||||
|
c.received = append(c.received, data...)
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose(t *testing.T) {
|
||||||
|
c := &MockTerminal{}
|
||||||
|
ss := NewShell(c, "> ")
|
||||||
|
line, err := ss.ReadLine()
|
||||||
|
if line != "" {
|
||||||
|
t.Errorf("Expected empty line but got: %s", line)
|
||||||
|
}
|
||||||
|
if err != os.EOF {
|
||||||
|
t.Errorf("Error should have been EOF but got: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyPressTests = []struct {
|
||||||
|
in string
|
||||||
|
line string
|
||||||
|
err os.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
os.EOF,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"\r",
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"foo\r",
|
||||||
|
"foo",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a\x1b[Cb\r", // right
|
||||||
|
"ab",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a\x1b[Db\r", // left
|
||||||
|
"ba",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a\177b\r", // backspace
|
||||||
|
"b",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyPresses(t *testing.T) {
|
||||||
|
for i, test := range keyPressTests {
|
||||||
|
for j := 0; j < len(test.in); j++ {
|
||||||
|
c := &MockTerminal{
|
||||||
|
toSend: []byte(test.in),
|
||||||
|
bytesPerRead: j,
|
||||||
|
}
|
||||||
|
ss := NewShell(c, "> ")
|
||||||
|
line, err := ss.ReadLine()
|
||||||
|
if line != test.line {
|
||||||
|
t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
src/pkg/exp/terminal/terminal.go
Normal file
103
src/pkg/exp/terminal/terminal.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err.String())
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
termios syscall.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
var termios syscall.Termios
|
||||||
|
_, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCGETS), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return e == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 int) (*State, os.Error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCGETS), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); e != 0 {
|
||||||
|
return nil, os.Errno(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF
|
||||||
|
newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
|
||||||
|
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCSETS), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); e != 0 {
|
||||||
|
return nil, os.Errno(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) os.Error {
|
||||||
|
_, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCSETS), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
|
||||||
|
return os.Errno(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, os.Error) {
|
||||||
|
var oldState syscall.Termios
|
||||||
|
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCGETS), uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 {
|
||||||
|
return nil, os.Errno(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCSETS), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); e != 0 {
|
||||||
|
return nil, os.Errno(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TCSETS), uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var buf [16]byte
|
||||||
|
var ret []byte
|
||||||
|
for {
|
||||||
|
n, errno := syscall.Read(fd, buf[:])
|
||||||
|
if errno != 0 {
|
||||||
|
return nil, os.Errno(errno)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return nil, os.EOF
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if buf[n-1] == '\n' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[:n]...)
|
||||||
|
if n < len(buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
@ -41,6 +41,7 @@ Input to godefs. See also mkerrors.sh and mkall.sh
|
|||||||
#include <linux/filter.h>
|
#include <linux/filter.h>
|
||||||
#include <linux/netlink.h>
|
#include <linux/netlink.h>
|
||||||
#include <linux/rtnetlink.h>
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <termios.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <ustat.h>
|
#include <ustat.h>
|
||||||
@ -288,3 +289,109 @@ struct my_epoll_event {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef struct my_epoll_event $EpollEvent;
|
typedef struct my_epoll_event $EpollEvent;
|
||||||
|
|
||||||
|
// Terminal handling
|
||||||
|
|
||||||
|
typedef struct termios $Termios;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
$VINTR = VINTR,
|
||||||
|
$VQUIT = VQUIT,
|
||||||
|
$VERASE = VERASE,
|
||||||
|
$VKILL = VKILL,
|
||||||
|
$VEOF = VEOF,
|
||||||
|
$VTIME = VTIME,
|
||||||
|
$VMIN = VMIN,
|
||||||
|
$VSWTC = VSWTC,
|
||||||
|
$VSTART = VSTART,
|
||||||
|
$VSTOP = VSTOP,
|
||||||
|
$VSUSP = VSUSP,
|
||||||
|
$VEOL = VEOL,
|
||||||
|
$VREPRINT = VREPRINT,
|
||||||
|
$VDISCARD = VDISCARD,
|
||||||
|
$VWERASE = VWERASE,
|
||||||
|
$VLNEXT = VLNEXT,
|
||||||
|
$VEOL2 = VEOL2,
|
||||||
|
$IGNBRK = IGNBRK,
|
||||||
|
$BRKINT = BRKINT,
|
||||||
|
$IGNPAR = IGNPAR,
|
||||||
|
$PARMRK = PARMRK,
|
||||||
|
$INPCK = INPCK,
|
||||||
|
$ISTRIP = ISTRIP,
|
||||||
|
$INLCR = INLCR,
|
||||||
|
$IGNCR = IGNCR,
|
||||||
|
$ICRNL = ICRNL,
|
||||||
|
$IUCLC = IUCLC,
|
||||||
|
$IXON = IXON,
|
||||||
|
$IXANY = IXANY,
|
||||||
|
$IXOFF = IXOFF,
|
||||||
|
$IMAXBEL = IMAXBEL,
|
||||||
|
$IUTF8 = IUTF8,
|
||||||
|
$OPOST = OPOST,
|
||||||
|
$OLCUC = OLCUC,
|
||||||
|
$ONLCR = ONLCR,
|
||||||
|
$OCRNL = OCRNL,
|
||||||
|
$ONOCR = ONOCR,
|
||||||
|
$ONLRET = ONLRET,
|
||||||
|
$OFILL = OFILL,
|
||||||
|
$OFDEL = OFDEL,
|
||||||
|
$B0 = B0,
|
||||||
|
$B50 = B50,
|
||||||
|
$B75 = B75,
|
||||||
|
$B110 = B110,
|
||||||
|
$B134 = B134,
|
||||||
|
$B150 = B150,
|
||||||
|
$B200 = B200,
|
||||||
|
$B300 = B300,
|
||||||
|
$B600 = B600,
|
||||||
|
$B1200 = B1200,
|
||||||
|
$B1800 = B1800,
|
||||||
|
$B2400 = B2400,
|
||||||
|
$B4800 = B4800,
|
||||||
|
$B9600 = B9600,
|
||||||
|
$B19200 = B19200,
|
||||||
|
$B38400 = B38400,
|
||||||
|
$CSIZE = CSIZE,
|
||||||
|
$CS5 = CS5,
|
||||||
|
$CS6 = CS6,
|
||||||
|
$CS7 = CS7,
|
||||||
|
$CS8 = CS8,
|
||||||
|
$CSTOPB = CSTOPB,
|
||||||
|
$CREAD = CREAD,
|
||||||
|
$PARENB = PARENB,
|
||||||
|
$PARODD = PARODD,
|
||||||
|
$HUPCL = HUPCL,
|
||||||
|
$CLOCAL = CLOCAL,
|
||||||
|
$B57600 = B57600,
|
||||||
|
$B115200 = B115200,
|
||||||
|
$B230400 = B230400,
|
||||||
|
$B460800 = B460800,
|
||||||
|
$B500000 = B500000,
|
||||||
|
$B576000 = B576000,
|
||||||
|
$B921600 = B921600,
|
||||||
|
$B1000000 = B1000000,
|
||||||
|
$B1152000 = B1152000,
|
||||||
|
$B1500000 = B1500000,
|
||||||
|
$B2000000 = B2000000,
|
||||||
|
$B2500000 = B2500000,
|
||||||
|
$B3000000 = B3000000,
|
||||||
|
$B3500000 = B3500000,
|
||||||
|
$B4000000 = B4000000,
|
||||||
|
$ISIG = ISIG,
|
||||||
|
$ICANON = ICANON,
|
||||||
|
$XCASE = XCASE,
|
||||||
|
$ECHO = ECHO,
|
||||||
|
$ECHOE = ECHOE,
|
||||||
|
$ECHOK = ECHOK,
|
||||||
|
$ECHONL = ECHONL,
|
||||||
|
$NOFLSH = NOFLSH,
|
||||||
|
$TOSTOP = TOSTOP,
|
||||||
|
$ECHOCTL = ECHOCTL,
|
||||||
|
$ECHOPRT = ECHOPRT,
|
||||||
|
$ECHOKE = ECHOKE,
|
||||||
|
$FLUSHO = FLUSHO,
|
||||||
|
$PENDIN = PENDIN,
|
||||||
|
$IEXTEN = IEXTEN,
|
||||||
|
$TCGETS = TCGETS,
|
||||||
|
$TCSETS = TCSETS,
|
||||||
|
};
|
||||||
|
@ -105,6 +105,105 @@ const (
|
|||||||
SizeofSockFilter = 0x8
|
SizeofSockFilter = 0x8
|
||||||
SizeofSockFprog = 0x10
|
SizeofSockFprog = 0x10
|
||||||
SizeofInotifyEvent = 0x10
|
SizeofInotifyEvent = 0x10
|
||||||
|
VINTR = 0
|
||||||
|
VQUIT = 0x1
|
||||||
|
VERASE = 0x2
|
||||||
|
VKILL = 0x3
|
||||||
|
VEOF = 0x4
|
||||||
|
VTIME = 0x5
|
||||||
|
VMIN = 0x6
|
||||||
|
VSWTC = 0x7
|
||||||
|
VSTART = 0x8
|
||||||
|
VSTOP = 0x9
|
||||||
|
VSUSP = 0xa
|
||||||
|
VEOL = 0xb
|
||||||
|
VREPRINT = 0xc
|
||||||
|
VDISCARD = 0xd
|
||||||
|
VWERASE = 0xe
|
||||||
|
VLNEXT = 0xf
|
||||||
|
VEOL2 = 0x10
|
||||||
|
IGNBRK = 0x1
|
||||||
|
BRKINT = 0x2
|
||||||
|
IGNPAR = 0x4
|
||||||
|
PARMRK = 0x8
|
||||||
|
INPCK = 0x10
|
||||||
|
ISTRIP = 0x20
|
||||||
|
INLCR = 0x40
|
||||||
|
IGNCR = 0x80
|
||||||
|
ICRNL = 0x100
|
||||||
|
IUCLC = 0x200
|
||||||
|
IXON = 0x400
|
||||||
|
IXANY = 0x800
|
||||||
|
IXOFF = 0x1000
|
||||||
|
IMAXBEL = 0x2000
|
||||||
|
IUTF8 = 0x4000
|
||||||
|
OPOST = 0x1
|
||||||
|
OLCUC = 0x2
|
||||||
|
ONLCR = 0x4
|
||||||
|
OCRNL = 0x8
|
||||||
|
ONOCR = 0x10
|
||||||
|
ONLRET = 0x20
|
||||||
|
OFILL = 0x40
|
||||||
|
OFDEL = 0x80
|
||||||
|
B0 = 0
|
||||||
|
B50 = 0x1
|
||||||
|
B75 = 0x2
|
||||||
|
B110 = 0x3
|
||||||
|
B134 = 0x4
|
||||||
|
B150 = 0x5
|
||||||
|
B200 = 0x6
|
||||||
|
B300 = 0x7
|
||||||
|
B600 = 0x8
|
||||||
|
B1200 = 0x9
|
||||||
|
B1800 = 0xa
|
||||||
|
B2400 = 0xb
|
||||||
|
B4800 = 0xc
|
||||||
|
B9600 = 0xd
|
||||||
|
B19200 = 0xe
|
||||||
|
B38400 = 0xf
|
||||||
|
CSIZE = 0x30
|
||||||
|
CS5 = 0
|
||||||
|
CS6 = 0x10
|
||||||
|
CS7 = 0x20
|
||||||
|
CS8 = 0x30
|
||||||
|
CSTOPB = 0x40
|
||||||
|
CREAD = 0x80
|
||||||
|
PARENB = 0x100
|
||||||
|
PARODD = 0x200
|
||||||
|
HUPCL = 0x400
|
||||||
|
CLOCAL = 0x800
|
||||||
|
B57600 = 0x1001
|
||||||
|
B115200 = 0x1002
|
||||||
|
B230400 = 0x1003
|
||||||
|
B460800 = 0x1004
|
||||||
|
B500000 = 0x1005
|
||||||
|
B576000 = 0x1006
|
||||||
|
B921600 = 0x1007
|
||||||
|
B1000000 = 0x1008
|
||||||
|
B1152000 = 0x1009
|
||||||
|
B1500000 = 0x100a
|
||||||
|
B2000000 = 0x100b
|
||||||
|
B2500000 = 0x100c
|
||||||
|
B3000000 = 0x100d
|
||||||
|
B3500000 = 0x100e
|
||||||
|
B4000000 = 0x100f
|
||||||
|
ISIG = 0x1
|
||||||
|
ICANON = 0x2
|
||||||
|
XCASE = 0x4
|
||||||
|
ECHO = 0x8
|
||||||
|
ECHOE = 0x10
|
||||||
|
ECHOK = 0x20
|
||||||
|
ECHONL = 0x40
|
||||||
|
NOFLSH = 0x80
|
||||||
|
TOSTOP = 0x100
|
||||||
|
ECHOCTL = 0x200
|
||||||
|
ECHOPRT = 0x400
|
||||||
|
ECHOKE = 0x800
|
||||||
|
FLUSHO = 0x1000
|
||||||
|
PENDIN = 0x4000
|
||||||
|
IEXTEN = 0x8000
|
||||||
|
TCGETS = 0x5401
|
||||||
|
TCSETS = 0x5402
|
||||||
)
|
)
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
@ -514,3 +613,15 @@ type EpollEvent struct {
|
|||||||
Fd int32
|
Fd int32
|
||||||
Pad int32
|
Pad int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Line uint8
|
||||||
|
Cc [32]uint8
|
||||||
|
Pad_godefs_0 [3]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user