mirror of https://github.com/chzyer/readline.git
411 lines
7.3 KiB
Go
411 lines
7.3 KiB
Go
package readline
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type MsgType int16
|
|
|
|
const (
|
|
T_DATA = MsgType(iota)
|
|
T_WIDTH
|
|
T_WIDTH_REPORT
|
|
T_ISTTY_REPORT
|
|
T_RAW
|
|
T_ERAW // exit raw
|
|
)
|
|
|
|
type RemoteSvr struct {
|
|
closed int32
|
|
width int32
|
|
reciveChan chan struct{}
|
|
writeChan chan *writeCtx
|
|
conn net.Conn
|
|
isTerminal bool
|
|
funcWidthChan func()
|
|
|
|
dataBufM sync.Mutex
|
|
dataBuf bytes.Buffer
|
|
}
|
|
|
|
type writeReply struct {
|
|
n int
|
|
err error
|
|
}
|
|
|
|
type writeCtx struct {
|
|
msg *Message
|
|
reply chan *writeReply
|
|
}
|
|
|
|
func newWriteCtx(msg *Message) *writeCtx {
|
|
return &writeCtx{
|
|
msg: msg,
|
|
reply: make(chan *writeReply),
|
|
}
|
|
}
|
|
|
|
func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) {
|
|
rs := &RemoteSvr{
|
|
width: -1,
|
|
conn: conn,
|
|
writeChan: make(chan *writeCtx),
|
|
reciveChan: make(chan struct{}),
|
|
}
|
|
buf := bufio.NewReader(rs.conn)
|
|
|
|
if err := rs.init(buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
go rs.readLoop(buf)
|
|
go rs.writeLoop()
|
|
return rs, nil
|
|
}
|
|
|
|
func (r *RemoteSvr) init(buf *bufio.Reader) error {
|
|
m, err := ReadMessage(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// receive isTerminal
|
|
if m.Type != T_ISTTY_REPORT {
|
|
return fmt.Errorf("unexpected init message")
|
|
}
|
|
r.GotIsTerminal(m.Data)
|
|
|
|
// receive width
|
|
m, err = ReadMessage(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if m.Type != T_WIDTH_REPORT {
|
|
return fmt.Errorf("unexpected init message")
|
|
}
|
|
r.GotReportWidth(m.Data)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *RemoteSvr) HandleConfig(cfg *Config) {
|
|
cfg.Stderr = r
|
|
cfg.Stdout = r
|
|
cfg.Stdin = r
|
|
cfg.FuncExitRaw = r.ExitRawMode
|
|
cfg.FuncIsTerminal = r.IsTerminal
|
|
cfg.FuncMakeRaw = r.EnterRawMode
|
|
cfg.FuncExitRaw = r.ExitRawMode
|
|
cfg.FuncGetWidth = r.GetWidth
|
|
cfg.FuncOnWidthChanged = func(f func()) {
|
|
r.funcWidthChan = f
|
|
}
|
|
}
|
|
|
|
func (r *RemoteSvr) IsTerminal() bool {
|
|
return r.isTerminal
|
|
}
|
|
|
|
func (r *RemoteSvr) Read(b []byte) (int, error) {
|
|
r.dataBufM.Lock()
|
|
n, err := r.dataBuf.Read(b)
|
|
r.dataBufM.Unlock()
|
|
if n == 0 && err == io.EOF {
|
|
<-r.reciveChan
|
|
r.dataBufM.Lock()
|
|
n, err = r.dataBuf.Read(b)
|
|
r.dataBufM.Unlock()
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (r *RemoteSvr) writeMsg(m *Message) error {
|
|
ctx := newWriteCtx(m)
|
|
r.writeChan <- ctx
|
|
reply := <-ctx.reply
|
|
return reply.err
|
|
}
|
|
|
|
func (r *RemoteSvr) Write(b []byte) (int, error) {
|
|
ctx := newWriteCtx(NewMessage(T_DATA, b))
|
|
r.writeChan <- ctx
|
|
reply := <-ctx.reply
|
|
return reply.n, reply.err
|
|
}
|
|
|
|
func (r *RemoteSvr) EnterRawMode() error {
|
|
return r.writeMsg(NewMessage(T_RAW, nil))
|
|
}
|
|
|
|
func (r *RemoteSvr) ExitRawMode() error {
|
|
return r.writeMsg(NewMessage(T_ERAW, nil))
|
|
}
|
|
|
|
func (r *RemoteSvr) writeLoop() {
|
|
defer r.Close()
|
|
|
|
for {
|
|
ctx, ok := <-r.writeChan
|
|
if !ok {
|
|
break
|
|
}
|
|
n, err := ctx.msg.WriteTo(r.conn)
|
|
ctx.reply <- &writeReply{n, err}
|
|
}
|
|
}
|
|
|
|
func (r *RemoteSvr) Close() {
|
|
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
|
close(r.writeChan)
|
|
r.conn.Close()
|
|
}
|
|
}
|
|
|
|
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
|
|
defer r.Close()
|
|
for {
|
|
m, err := ReadMessage(buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
switch m.Type {
|
|
case T_DATA:
|
|
r.dataBufM.Lock()
|
|
r.dataBuf.Write(m.Data)
|
|
r.dataBufM.Unlock()
|
|
select {
|
|
case r.reciveChan <- struct{}{}:
|
|
default:
|
|
}
|
|
case T_WIDTH_REPORT:
|
|
r.GotReportWidth(m.Data)
|
|
case T_ISTTY_REPORT:
|
|
r.GotIsTerminal(m.Data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *RemoteSvr) GotIsTerminal(data []byte) {
|
|
if binary.BigEndian.Uint16(data) == 0 {
|
|
r.isTerminal = false
|
|
} else {
|
|
r.isTerminal = true
|
|
}
|
|
}
|
|
|
|
func (r *RemoteSvr) GotReportWidth(data []byte) {
|
|
atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data)))
|
|
if r.funcWidthChan != nil {
|
|
r.funcWidthChan()
|
|
}
|
|
}
|
|
|
|
func (r *RemoteSvr) GetWidth() int {
|
|
return int(atomic.LoadInt32(&r.width))
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
type Message struct {
|
|
Type MsgType
|
|
Data []byte
|
|
}
|
|
|
|
func ReadMessage(r io.Reader) (*Message, error) {
|
|
m := new(Message)
|
|
var length int32
|
|
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil {
|
|
return nil, err
|
|
}
|
|
m.Data = make([]byte, int(length)-2)
|
|
if _, err := io.ReadFull(r, m.Data); err != nil {
|
|
return nil, err
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func NewMessage(t MsgType, data []byte) *Message {
|
|
return &Message{t, data}
|
|
}
|
|
|
|
func (m *Message) WriteTo(w io.Writer) (int, error) {
|
|
buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4))
|
|
binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2))
|
|
binary.Write(buf, binary.BigEndian, m.Type)
|
|
buf.Write(m.Data)
|
|
n, err := buf.WriteTo(w)
|
|
return int(n), err
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
type RemoteCli struct {
|
|
conn net.Conn
|
|
raw RawMode
|
|
receiveChan chan struct{}
|
|
|
|
data bytes.Buffer
|
|
dataM sync.Mutex
|
|
}
|
|
|
|
func NewRemoteCli(conn net.Conn) (*RemoteCli, error) {
|
|
r := &RemoteCli{
|
|
conn: conn,
|
|
receiveChan: make(chan struct{}),
|
|
}
|
|
if err := r.init(); err != nil {
|
|
return nil, err
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (r *RemoteCli) init() error {
|
|
if err := r.reportIsTerminal(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := r.reportWidth(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// register sig for width changed
|
|
DefaultOnWidthChanged(func() {
|
|
r.reportWidth()
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (r *RemoteCli) writeMsg(m *Message) error {
|
|
r.dataM.Lock()
|
|
_, err := m.WriteTo(r.conn)
|
|
r.dataM.Unlock()
|
|
return err
|
|
}
|
|
|
|
func (r *RemoteCli) Write(b []byte) (int, error) {
|
|
m := NewMessage(T_DATA, b)
|
|
r.dataM.Lock()
|
|
n, err := m.WriteTo(r.conn)
|
|
r.dataM.Unlock()
|
|
return n, err
|
|
}
|
|
|
|
func (r *RemoteCli) reportWidth() error {
|
|
screenWidth := GetScreenWidth()
|
|
data := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data, uint16(screenWidth))
|
|
msg := NewMessage(T_WIDTH_REPORT, data)
|
|
|
|
if err := r.writeMsg(msg); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *RemoteCli) reportIsTerminal() error {
|
|
isTerminal := DefaultIsTerminal()
|
|
data := make([]byte, 2)
|
|
if isTerminal {
|
|
binary.BigEndian.PutUint16(data, 1)
|
|
} else {
|
|
binary.BigEndian.PutUint16(data, 0)
|
|
}
|
|
msg := NewMessage(T_ISTTY_REPORT, data)
|
|
if err := r.writeMsg(msg); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *RemoteCli) readLoop() {
|
|
buf := bufio.NewReader(r.conn)
|
|
for {
|
|
msg, err := ReadMessage(buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
switch msg.Type {
|
|
case T_ERAW:
|
|
r.raw.Exit()
|
|
case T_RAW:
|
|
r.raw.Enter()
|
|
case T_DATA:
|
|
os.Stdout.Write(msg.Data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *RemoteCli) Serve() error {
|
|
go func() {
|
|
for {
|
|
n, _ := io.Copy(r, os.Stdin)
|
|
if n == 0 {
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
defer r.raw.Exit()
|
|
r.readLoop()
|
|
return nil
|
|
}
|
|
|
|
func ListenRemote(n, addr string, cfg *Config, h func(*Instance)) error {
|
|
ln, err := net.Listen(n, addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
break
|
|
}
|
|
go func() {
|
|
defer conn.Close()
|
|
rl, err := HandleConn(*cfg, conn)
|
|
if err != nil {
|
|
return
|
|
}
|
|
h(rl)
|
|
}()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func HandleConn(cfg Config, conn net.Conn) (*Instance, error) {
|
|
r, err := NewRemoteSvr(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.HandleConfig(&cfg)
|
|
|
|
rl, err := NewEx(&cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rl, nil
|
|
}
|
|
|
|
func DialRemote(n, addr string) error {
|
|
conn, err := net.Dial(n, addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
cli, err := NewRemoteCli(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return cli.Serve()
|
|
}
|