diff --git a/client.go b/client.go index ec514eb..8e562a0 100644 --- a/client.go +++ b/client.go @@ -2,30 +2,44 @@ 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 + 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 { return &ttyShareClient{ - url: url, - connection: nil, + 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()) } - os.Stdout.Write(msgWrite.Data) + 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 }