|
|
|
@ -2,23 +2,35 @@ package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"os"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os/signal"
|
|
|
|
|
"sync"
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
|
|
ttyServer "github.com/elisescu/tty-share/server"
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
|
"github.com/moby/term"
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ttyShareClient struct {
|
|
|
|
|
url string
|
|
|
|
|
connection *websocket.Conn
|
|
|
|
|
detachKeys string
|
|
|
|
|
wcChan chan os.Signal
|
|
|
|
|
writeFlag uint32 // used with atomic
|
|
|
|
|
winSizes struct {
|
|
|
|
|
thisW uint16
|
|
|
|
|
thisH uint16
|
|
|
|
|
remoteW uint16
|
|
|
|
|
remoteH uint16
|
|
|
|
|
}
|
|
|
|
|
winSizesMutex sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTtyShareClient(url string, detachKeys string) *ttyShareClient {
|
|
|
|
@ -26,6 +38,8 @@ func newTtyShareClient(url string, detachKeys string) *ttyShareClient {
|
|
|
|
|
url: url,
|
|
|
|
|
connection: nil,
|
|
|
|
|
detachKeys: detachKeys,
|
|
|
|
|
wcChan: make(chan os.Signal, 1),
|
|
|
|
|
writeFlag: 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -37,7 +51,6 @@ type wsTextWriter struct {
|
|
|
|
|
conn *websocket.Conn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (w *wsTextWriter) Write(data []byte) (n int, err error) {
|
|
|
|
|
err = w.conn.WriteMessage(websocket.TextMessage, data)
|
|
|
|
|
return len(data), err
|
|
|
|
@ -55,6 +68,33 @@ func (kl *keyListener) Read(data []byte) (n int, err error) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ttyShareClient) updateAndDecideStdoutMuted() {
|
|
|
|
|
log.Infof("This window: %dx%d. Remote window: %dx%d", c.winSizes.thisW, c.winSizes.thisH, c.winSizes.remoteW, c.winSizes.remoteH)
|
|
|
|
|
|
|
|
|
|
if c.winSizes.thisH < c.winSizes.remoteH || c.winSizes.thisW < c.winSizes.remoteW {
|
|
|
|
|
atomic.StoreUint32(&c.writeFlag, 0)
|
|
|
|
|
clearScreen()
|
|
|
|
|
fmt.Printf("\r\n\nYour terminal window has to be bigger than %dx%d\r\nDetach with <%s>, resize your window, and reconect.\r\n",
|
|
|
|
|
c.winSizes.remoteW, c.winSizes.remoteH, c.detachKeys)
|
|
|
|
|
} else {
|
|
|
|
|
if atomic.LoadUint32(&c.writeFlag) == 0 { // clear the screen when changing back to "write"
|
|
|
|
|
// TODO: notify the remote side to "refresh" the content.
|
|
|
|
|
clearScreen()
|
|
|
|
|
}
|
|
|
|
|
atomic.StoreUint32(&c.writeFlag, 1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ttyShareClient) updateThisWinSize() {
|
|
|
|
|
size, err := term.GetWinsize(os.Stdin.Fd())
|
|
|
|
|
if err == nil {
|
|
|
|
|
c.winSizesMutex.Lock()
|
|
|
|
|
c.winSizes.thisW = size.Width
|
|
|
|
|
c.winSizes.thisH = size.Height
|
|
|
|
|
c.winSizesMutex.Unlock()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ttyShareClient) Run() (err error) {
|
|
|
|
|
log.Debugf("Connecting as a client to %s ..", c.url)
|
|
|
|
|
|
|
|
|
@ -91,12 +131,26 @@ func (c *ttyShareClient) Run() (err error) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state, err := terminal.MakeRaw(0)
|
|
|
|
|
defer terminal.Restore(0, state)
|
|
|
|
|
state, err := term.MakeRaw(os.Stdin.Fd())
|
|
|
|
|
defer term.RestoreTerminal(os.Stdin.Fd(), state)
|
|
|
|
|
|
|
|
|
|
c.connection = conn
|
|
|
|
|
// Clear the screen before processing any incoming data
|
|
|
|
|
|
|
|
|
|
clearScreen()
|
|
|
|
|
// start monitoring the size of the terminal
|
|
|
|
|
signal.Notify(c.wcChan, syscall.SIGWINCH)
|
|
|
|
|
defer signal.Stop(c.wcChan)
|
|
|
|
|
|
|
|
|
|
monitorWinChanges := func() {
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-c.wcChan:
|
|
|
|
|
log.Debugf("Detected new win size")
|
|
|
|
|
c.updateThisWinSize()
|
|
|
|
|
c.updateAndDecideStdoutMuted()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readLoop := func() {
|
|
|
|
|
for {
|
|
|
|
@ -120,15 +174,21 @@ func (c *ttyShareClient) Run() (err error) {
|
|
|
|
|
log.Errorf("Cannot read JSON: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if atomic.LoadUint32(&c.writeFlag) != 0 {
|
|
|
|
|
os.Stdout.Write(msgWrite.Data)
|
|
|
|
|
}
|
|
|
|
|
case ttyServer.MsgIDWinSize:
|
|
|
|
|
log.Infof("Remote window changed its size")
|
|
|
|
|
// We ignore the window size changes - can't do much about that for
|
|
|
|
|
// now.
|
|
|
|
|
|
|
|
|
|
// TODO: Maybe just clear the screen, and display an error message
|
|
|
|
|
// if the remote window gets bigger than this terminal window - when
|
|
|
|
|
// it does, it usually messes up the output
|
|
|
|
|
var msgRemoteWinSize ttyServer.MsgTTYWinSize
|
|
|
|
|
err := json.Unmarshal(msg.Data, &msgRemoteWinSize)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
c.winSizesMutex.Lock()
|
|
|
|
|
c.winSizes.remoteW = uint16(msgRemoteWinSize.Cols)
|
|
|
|
|
c.winSizes.remoteH = uint16(msgRemoteWinSize.Rows)
|
|
|
|
|
c.winSizesMutex.Unlock()
|
|
|
|
|
c.updateThisWinSize()
|
|
|
|
|
c.updateAndDecideStdoutMuted()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -148,8 +208,11 @@ func (c *ttyShareClient) Run() (err error) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
go monitorWinChanges()
|
|
|
|
|
go writeLoop()
|
|
|
|
|
readLoop()
|
|
|
|
|
|
|
|
|
|
clearScreen()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|