Files
asciinema/terminal/terminal.go
2015-03-05 15:57:12 +01:00

125 lines
2.4 KiB
Go

package terminal
import (
"io"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
"github.com/asciinema/asciinema/Godeps/_workspace/src/code.google.com/p/go.crypto/ssh/terminal"
"github.com/asciinema/asciinema/Godeps/_workspace/src/github.com/kr/pty"
"github.com/asciinema/asciinema/ptyx"
"github.com/asciinema/asciinema/util"
)
type Terminal interface {
Size() (int, int, error)
Record(string, io.Writer) error
Write([]byte) error
}
type Pty struct {
Stdin *os.File
Stdout *os.File
}
func NewTerminal() Terminal {
return &Pty{Stdin: os.Stdin, Stdout: os.Stdout}
}
func (p *Pty) Size() (int, int, error) {
return pty.Getsize(p.Stdout)
}
func (p *Pty) Record(command string, stdoutCopy io.Writer) error {
// start command in pty
cmd := exec.Command("sh", "-c", command)
cmd.Env = append(os.Environ(), "ASCIINEMA_REC=1")
master, err := pty.Start(cmd)
if err != nil {
return err
}
defer master.Close()
// install WINCH signal handler
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGWINCH)
defer signal.Stop(signals)
go func() {
for _ = range signals {
p.resize(master)
}
}()
defer close(signals)
// put stdin in raw mode (if it's a tty)
fd := int(p.Stdin.Fd())
if terminal.IsTerminal(fd) {
oldState, err := terminal.MakeRaw(fd)
if err != nil {
return err
}
defer terminal.Restore(fd, oldState)
}
// do initial resize
p.resize(master)
// start stdin -> master copying
stop := util.Copy(master, p.Stdin)
// copy pty master -> p.stdout & stdoutCopy
stdout := io.MultiWriter(p.Stdout, stdoutCopy)
stdoutWaitChan := make(chan struct{})
go func() {
io.Copy(stdout, master)
stdoutWaitChan <- struct{}{}
}()
// wait for the process to exit and reap it
cmd.Wait()
// wait for master -> stdout copying to finish
//
// sometimes after process exits reading from master blocks forever (race condition?)
// we're using timeout here to overcome this problem
select {
case <-stdoutWaitChan:
case <-time.After(200 * time.Millisecond):
}
// stop stdin -> master copying
stop()
return nil
}
func (p *Pty) Write(data []byte) error {
_, err := p.Stdout.Write(data)
if err != nil {
return err
}
err = p.Stdout.Sync()
if err != nil {
return err
}
return nil
}
func (p *Pty) resize(f *os.File) {
var rows, cols int
if terminal.IsTerminal(int(p.Stdout.Fd())) {
rows, cols, _ = p.Size()
} else {
rows = 24
cols = 80
}
ptyx.Setsize(f, rows, cols)
}