Files
asciinema/terminal/terminal.go

131 lines
2.7 KiB
Go
Raw Permalink Normal View History

2014-08-03 19:50:39 +02:00
package terminal
2014-08-03 20:25:14 +02:00
import (
"io"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
2014-08-03 20:25:14 +02:00
2015-03-05 15:57:12 +01:00
"github.com/asciinema/asciinema/Godeps/_workspace/src/code.google.com/p/go.crypto/ssh/terminal"
"github.com/asciinema/asciinema/Godeps/_workspace/src/github.com/creack/termios/raw"
2015-03-05 15:57:12 +01:00
"github.com/asciinema/asciinema/Godeps/_workspace/src/github.com/kr/pty"
"github.com/asciinema/asciinema/Godeps/_workspace/src/golang.org/x/text/encoding/unicode"
"github.com/asciinema/asciinema/Godeps/_workspace/src/golang.org/x/text/transform"
2015-03-05 15:57:12 +01:00
"github.com/asciinema/asciinema/ptyx"
"github.com/asciinema/asciinema/util"
2014-08-03 20:25:14 +02:00
)
2014-08-03 19:50:39 +02:00
type Terminal interface {
Size() (int, int, error)
Record(string, io.Writer) error
Write([]byte) error
2014-08-03 19:50:39 +02:00
}
2014-08-03 20:25:14 +02:00
type Pty struct {
Stdin *os.File
Stdout *os.File
}
func NewTerminal() Terminal {
2014-08-03 20:25:14 +02:00
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, w io.Writer) error {
2014-08-09 21:49:10 +02:00
// start command in pty
cmd := exec.Command("sh", "-c", command)
2014-08-03 20:25:14 +02:00
cmd.Env = append(os.Environ(), "ASCIINEMA_REC=1")
master, err := pty.Start(cmd)
if err != nil {
return err
}
defer master.Close()
2014-08-09 21:49:10 +02:00
// install WINCH signal handler
2014-08-03 20:25:14 +02:00
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 := p.Stdin.Fd()
if terminal.IsTerminal(int(fd)) {
oldState, err := raw.MakeRaw(fd)
2014-08-03 20:25:14 +02:00
if err != nil {
return err
}
defer raw.TcSetAttr(fd, oldState)
2014-08-03 20:25:14 +02:00
}
2014-08-09 21:49:10 +02:00
// do initial resize
2014-08-03 20:25:14 +02:00
p.resize(master)
2014-08-09 21:49:10 +02:00
// start stdin -> master copying
stop := util.Copy(master, p.Stdin)
// copy pty master -> p.stdout & w
stdout := transform.NewWriter(w, unicode.UTF8.NewEncoder())
defer stdout.Close()
stdoutWaitChan := make(chan struct{})
go func() {
io.Copy(io.MultiWriter(p.Stdout, 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):
}
2014-08-03 20:25:14 +02:00
2014-08-09 21:49:10 +02:00
// stop stdin -> master copying
stop()
2014-08-03 20:25:14 +02:00
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
}
2014-08-03 20:25:14 +02:00
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)
}