From dcd4e252fa2be2effea7a8d69f903a8965bab968 Mon Sep 17 00:00:00 2001 From: DarthSim Date: Wed, 31 Jan 2018 14:47:57 +0600 Subject: [PATCH] Use tmux control mode --- launch/command.go | 95 ------------------ launch/command_center.go | 36 ------- launch/handler.go | 19 ---- launch/process.go | 117 ----------------------- launch/writer.go | 26 ----- main.go | 12 --- start/command.go | 69 ++++++------- start/command_center.go | 13 +-- start/process.go | 202 +++++++++++++++++++++++---------------- start/tmux.go | 147 ++++++++++++++++++++++++++++ term/term.go | 89 ----------------- term/term_bsd.go | 8 -- term/term_linux.go | 6 -- 13 files changed, 305 insertions(+), 534 deletions(-) delete mode 100644 launch/command.go delete mode 100644 launch/command_center.go delete mode 100644 launch/handler.go delete mode 100644 launch/process.go delete mode 100644 launch/writer.go create mode 100644 start/tmux.go delete mode 100644 term/term.go delete mode 100644 term/term_bsd.go delete mode 100644 term/term_linux.go diff --git a/launch/command.go b/launch/command.go deleted file mode 100644 index 54eafae..0000000 --- a/launch/command.go +++ /dev/null @@ -1,95 +0,0 @@ -package launch - -import ( - "fmt" - "io" - "net" - "os" - - "github.com/DarthSim/overmind/term" -) - -type command struct { - processName string - cmdLine string - port string - socketPath string - keepAlive bool - restart bool - writer writerHelper - proc *process -} - -func newCommand(procName, cmdLine, port, socketPath string, keepAlive bool) (*command, error) { - return &command{ - processName: procName, - cmdLine: cmdLine, - port: port, - socketPath: socketPath, - keepAlive: keepAlive, - }, nil -} - -func (c *command) Run() error { - conn, err := c.establishConn() - if err != nil { - return err - } - - c.writer = writerHelper{io.MultiWriter(conn, os.Stdout)} - - tp, err := term.GetParams(os.Stdin) - if err != nil { - return err - } - - if err = term.MakeRaw(os.Stdin); err != nil { - return err - } - defer term.SetParams(os.Stdin, tp) - - os.Setenv("PORT", c.port) - - for { - if c.proc, err = runProcess(c.cmdLine, c.writer, tp, c.keepAlive); err != nil { - return err - } - - c.proc.Wait() - - c.writer.WriteBoldLine("Exited") - - c.proc.WaitKeepAlive() - - if !c.restart { - break - } - - c.restart = false - c.writer.WriteBoldLine("Restarting...") - } - - return nil -} - -func (c *command) Stop() { - c.proc.Stop() -} - -func (c *command) Restart() { - c.restart = true - c.Stop() -} - -func (c *command) establishConn() (net.Conn, error) { - conn, err := net.Dial("unix", c.socketPath) - if err != nil { - return nil, err - } - - go newCommandCenter(c, conn).Start() - - fmt.Fprintf(conn, "attach %v\n", c.processName) - - return conn, nil -} diff --git a/launch/command_center.go b/launch/command_center.go deleted file mode 100644 index df5624a..0000000 --- a/launch/command_center.go +++ /dev/null @@ -1,36 +0,0 @@ -package launch - -import ( - "net" - - "github.com/DarthSim/overmind/utils" -) - -type commandCenter struct { - command *command - conn net.Conn -} - -func newCommandCenter(command *command, conn net.Conn) *commandCenter { - return &commandCenter{ - command: command, - conn: conn, - } -} - -func (c *commandCenter) Start() { - utils.ScanLines(c.conn, func(b []byte) bool { - if c.command.proc == nil { - return true - } - - switch string(b) { - case "stop": - c.command.Stop() - case "restart": - c.command.Restart() - } - - return true - }) -} diff --git a/launch/handler.go b/launch/handler.go deleted file mode 100644 index b11c0c7..0000000 --- a/launch/handler.go +++ /dev/null @@ -1,19 +0,0 @@ -package launch - -import ( - "github.com/DarthSim/overmind/utils" - - "gopkg.in/urfave/cli.v1" -) - -// Run runs the launch command -func Run(c *cli.Context) error { - keepAlive := len(c.Args().Get(4)) > 0 - - cmd, err := newCommand(c.Args().Get(0), c.Args().Get(1), c.Args().Get(2), c.Args().Get(3), keepAlive) - utils.FatalOnErr(err) - - utils.FatalOnErr(cmd.Run()) - - return nil -} diff --git a/launch/process.go b/launch/process.go deleted file mode 100644 index da722bd..0000000 --- a/launch/process.go +++ /dev/null @@ -1,117 +0,0 @@ -package launch - -import ( - "io" - "os" - "os/exec" - "syscall" - "time" - - "github.com/DarthSim/overmind/term" - "github.com/DarthSim/overmind/utils" - "github.com/pkg/term/termios" -) - -const runningCheckInterval = 100 * time.Millisecond - -type process struct { - cmd *exec.Cmd - writer writerHelper - interrupted bool - keepAlive bool -} - -func runProcess(cmdLine string, writer writerHelper, tp term.Params, keepAlive bool) (*process, error) { - pty, tty, err := termios.Pty() - if err != nil { - return nil, err - } - - if err := term.SetParams(pty, tp); err != nil { - return nil, err - } - - proc := process{ - cmd: exec.Command("/bin/sh", "-c", cmdLine), - writer: writer, - keepAlive: keepAlive, - } - - go io.Copy(proc.writer, pty) - go io.Copy(pty, os.Stdin) - - proc.cmd.Stdout = tty - proc.cmd.Stderr = tty - proc.cmd.Stdin = tty - proc.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} - - if err := proc.cmd.Start(); err != nil { - proc.writer.WriteErr(utils.ConvertError(err)) - return nil, err - } - - go func(p *process, pty, tty *os.File) { - defer pty.Close() - defer tty.Close() - - if err := p.cmd.Wait(); err != nil { - p.writer.WriteErr(utils.ConvertError(err)) - } - }(&proc, pty, tty) - - return &proc, nil -} - -func (p *process) Wait() { - for _ = range time.Tick(runningCheckInterval) { - if !p.Running() { - return - } - } -} - -func (p *process) WaitKeepAlive() { - for _ = range time.Tick(runningCheckInterval) { - if !p.keepAlive { - return - } - } -} - -func (p *process) Running() bool { - return p.cmd.Process != nil && p.cmd.ProcessState == nil -} - -func (p *process) Stop() { - p.keepAlive = false - - if !p.Running() { - return - } - - if p.interrupted { - // Ok, we tried this easy way, it's time to kill - p.writer.WriteBoldLine("Killing...") - p.signal(syscall.SIGKILL) - } else { - p.writer.WriteBoldLine("Interrupting...") - p.signal(syscall.SIGINT) - p.interrupted = true - } -} - -func (p *process) signal(sig os.Signal) { - if !p.Running() { - return - } - - group, err := os.FindProcess(-p.cmd.Process.Pid) - if err != nil { - p.writer.WriteErr(err) - return - } - - if err = group.Signal(sig); err != nil { - p.writer.WriteErr(err) - } -} diff --git a/launch/writer.go b/launch/writer.go deleted file mode 100644 index 886edae..0000000 --- a/launch/writer.go +++ /dev/null @@ -1,26 +0,0 @@ -package launch - -import ( - "fmt" - "io" -) - -type writerHelper struct { - writer io.Writer -} - -func (w writerHelper) Write(p []byte) (int, error) { - return w.writer.Write(p) -} - -func (w writerHelper) WriteLine(str string) { - fmt.Fprintln(w.writer, str) -} - -func (w writerHelper) WriteBoldLine(str string) { - fmt.Fprintf(w.writer, "\033[1m%v\033[0m\n", str) -} - -func (w writerHelper) WriteErr(err error) { - fmt.Fprintf(w.writer, "\033[0;31m%v\033[0m\n", err) -} diff --git a/main.go b/main.go index ca0f5a0..f229848 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/DarthSim/overmind/launch" "github.com/DarthSim/overmind/start" "github.com/joho/godotenv" @@ -40,16 +39,6 @@ func setupStartCmd() cli.Command { } } -func setupLaunchCmd() cli.Command { - return cli.Command{ - Name: "launch", - Usage: "Launch process, connect to overmind socket, wait for instructions", - Action: launch.Run, - ArgsUsage: "[process name] [shell command] [path to overmind socket] [keep alive]", - Hidden: true, - } -} - func setupRestartCmd() cli.Command { c := cmdRestartHandler{} @@ -116,7 +105,6 @@ func main() { app.Commands = []cli.Command{ setupStartCmd(), - setupLaunchCmd(), setupRestartCmd(), setupConnectCmd(), setupKillCmd(), diff --git a/start/command.go b/start/command.go index c09b7e2..1af397d 100644 --- a/start/command.go +++ b/start/command.go @@ -16,17 +16,16 @@ import ( var defaultColors = []int{2, 3, 4, 5, 6, 42, 130, 103, 129, 108} type command struct { - title string - timeout int - output *multiOutput - cmdCenter *commandCenter - doneTrig chan bool - doneWg sync.WaitGroup - stopTrig chan os.Signal - processes processesMap - tmuxSocket string - tmuxSession string - canDie []string + title string + timeout int + tmux *tmuxClient + output *multiOutput + cmdCenter *commandCenter + doneTrig chan bool + doneWg sync.WaitGroup + stopTrig chan os.Signal + processes processesMap + scriptsDir string } func newCommand(h *Handler) (*command, error) { @@ -50,13 +49,19 @@ func newCommand(h *Handler) (*command, error) { c.title = filepath.Base(root) } + session := utils.EscapeTitle(c.title) nanoid, err := gonanoid.Nanoid() if err != nil { return nil, err } - c.tmuxSession = utils.EscapeTitle(c.title) - c.tmuxSocket = fmt.Sprintf("overmind-%s-%s", c.tmuxSession, nanoid) + instanceID := fmt.Sprintf("overmind-%s-%s", session, nanoid) + + c.tmux, err = newTmuxClient(session, instanceID, root) + + if err != nil { + return nil, err + } c.output = newMultiOutput(pf.MaxNameLength()) @@ -67,11 +72,14 @@ func newCommand(h *Handler) (*command, error) { colors = h.Colors } - c.canDie = utils.SplitAndTrim(h.CanDie) + canDie := utils.SplitAndTrim(h.CanDie) + + c.scriptsDir = filepath.Join(os.TempDir(), instanceID) + os.MkdirAll(c.scriptsDir, 0700) for i, e := range pf { if len(procNames) == 0 || utils.StringsContain(procNames, e.Name) { - c.processes[e.Name] = newProcess(e.Name, c.tmuxSocket, c.tmuxSession, colors[i%len(colors)], e.Command, root, e.Port, c.output, utils.StringsContain(c.canDie, e.Name)) + c.processes[e.Name] = newProcess(c.tmux, e.Name, colors[i%len(colors)], e.Command, e.Port, c.output, utils.StringsContain(canDie, e.Name), c.scriptsDir) } } @@ -102,6 +110,9 @@ func (c *command) Run() error { // Session should be killed after all windows exit, but just for sure... c.killSession() + // Cleanup created scripts + os.RemoveAll(c.scriptsDir) + return nil } @@ -118,19 +129,11 @@ func (c *command) stopCommandCenter() { } func (c *command) runProcesses() { - c.output.WriteBoldLinef(nil, "Tmux socket name: %v", c.tmuxSocket) - c.output.WriteBoldLinef(nil, "Tmux session ID: %v", c.tmuxSession) - - newSession := true + c.output.WriteBoldLinef(nil, "Tmux socket name: %v", c.tmux.Socket) + c.output.WriteBoldLinef(nil, "Tmux session ID: %v", c.tmux.Session) for _, p := range c.processes { - if err := p.Start(c.cmdCenter.SocketPath, newSession); err != nil { - c.output.WriteErr(p, err) - c.doneTrig <- true - break - } - - newSession = false + p.Start() c.output.WriteBoldLinef(p, "Started with pid %v...", p.Pid()) c.doneWg.Add(1) @@ -149,12 +152,14 @@ func (c *command) waitForExit() { c.waitForDoneOrStop() - for { - for _, proc := range c.processes { - proc.Stop() - } + for _, proc := range c.processes { + proc.Stop() + } + + c.waitForTimeoutOrStop() - c.waitForTimeoutOrStop() + for _, proc := range c.processes { + proc.Kill() } } @@ -173,5 +178,5 @@ func (c *command) waitForTimeoutOrStop() { } func (c *command) killSession() { - utils.RunCmd("tmux", "-L", c.tmuxSocket, "kill-session", "-t", c.tmuxSession) + utils.RunCmd("tmux", "-L", c.tmux.Socket, "kill-session", "-t", c.tmux.Session) } diff --git a/start/command_center.go b/start/command_center.go index 3d45e81..a3893bc 100644 --- a/start/command_center.go +++ b/start/command_center.go @@ -79,9 +79,6 @@ func (c *commandCenter) handleConnection(conn net.Conn) { } switch cmd { - case "attach": - c.processAttach(cmd, args, conn) - return false case "restart": c.processRestart(cmd, args) case "kill": @@ -94,14 +91,6 @@ func (c *commandCenter) handleConnection(conn net.Conn) { }) } -func (c *commandCenter) processAttach(cmd string, args []string, conn net.Conn) { - if len(args) > 0 { - if proc, ok := c.processes[args[0]]; ok { - proc.AttachConnection(conn) - } - } -} - func (c *commandCenter) processRestart(cmd string, args []string) { for _, n := range args { if p, ok := c.processes[n]; ok { @@ -119,7 +108,7 @@ func (c *commandCenter) processKill() { func (c *commandCenter) processGetConnection(cmd string, args []string, conn net.Conn) { if len(args) > 0 { if proc, ok := c.processes[args[0]]; ok { - fmt.Fprintf(conn, "%s %s\n", proc.tmuxSocket, proc.WindowID()) + fmt.Fprintf(conn, "%s %s\n", proc.tmux.Socket, proc.WindowID()) } else { fmt.Fprintln(conn, "") } diff --git a/start/process.go b/start/process.go index fa90e3c..2ffe48b 100644 --- a/start/process.go +++ b/start/process.go @@ -2,108 +2,98 @@ package start import ( "fmt" - "net" + "io" "os" - "os/exec" - "strconv" - "strings" + "path/filepath" "syscall" "time" - "github.com/DarthSim/overmind/term" "github.com/DarthSim/overmind/utils" ) const runningCheckInterval = 100 * time.Millisecond type process struct { - command string - root string - port int - tmuxSocket string - tmuxSession string - output *multiOutput - canDie bool - conn *processConnection - proc *os.Process - - Name string - Color int -} + output *multiOutput -type processesMap map[string]*process + proc *os.Process + procGroup *os.Process -func newProcess(name, tmuxSocket, tmuxSession string, color int, command, root string, port int, output *multiOutput, canDie bool) *process { - return &process{ - command: command, - root: root, - port: port, - tmuxSocket: tmuxSocket, - tmuxSession: tmuxSession, - output: output, - canDie: canDie, - Name: name, - Color: color, - } -} + canDie bool + canDieNow bool + keepingAlive bool + dead bool + interrupted bool + restart bool -func (p *process) WindowID() string { - return fmt.Sprintf("%s:%s", p.tmuxSession, p.Name) + tmux *tmuxClient + tmuxPane string + + in io.Writer + out io.Reader + + Name string + Color int + Command string } -func (p *process) Start(socketPath string, newSession bool) (err error) { - if p.Running() { - return - } +type processesMap map[string]*process - canDie := "" - if p.canDie { - canDie = "true" - } +func newProcess(tmux *tmuxClient, name string, color int, command string, port int, output *multiOutput, canDie bool, scriptDir string) *process { + out, in := io.Pipe() - args := []string{ - "-n", p.Name, "-P", "-F", "#{pane_pid}", - os.Args[0], "launch", p.Name, p.command, strconv.Itoa(p.port), socketPath, canDie, - "\\;", "allow-rename", "off", - } + scriptFile, err := os.Create(filepath.Join(scriptDir, name)) + utils.FatalOnErr(err) - if newSession { - ws, e := term.GetSize(os.Stdout) - if e != nil { - return e - } + fmt.Fprintf(scriptFile, "export PORT=%d\n", port) + fmt.Fprintln(scriptFile, command) - args = append([]string{"new", "-d", "-s", p.tmuxSession, "-x", strconv.Itoa(int(ws.Cols)), "-y", strconv.Itoa(int(ws.Rows))}, args...) - } else { - args = append([]string{"neww", "-a", "-t", p.tmuxSession}, args...) - } + utils.FatalOnErr(scriptFile.Chmod(0744)) - args = append([]string{"-L", p.tmuxSocket}, args...) + utils.FatalOnErr(scriptFile.Close()) - var pid []byte - var ipid int + return &process{ + output: output, + tmux: tmux, - cmd := exec.Command("tmux", args...) - cmd.Dir = p.root + canDie: canDie, + canDieNow: canDie, - if pid, err = cmd.Output(); err == nil { - if ipid, err = strconv.Atoi(strings.TrimSpace(string(pid))); err == nil { - p.proc, err = os.FindProcess(ipid) - } + in: in, + out: out, + + Name: name, + Color: color, + Command: scriptFile.Name(), } +} + +func (p *process) WindowID() string { + return fmt.Sprintf("%s:%s", p.tmux.Session, p.Name) +} + +func (p *process) Start() { + if !p.Running() { + p.tmux.AddProcess(p) - err = utils.ConvertError(err) + p.waitPid() - return + go p.scanOuput() + go p.observe() + } } func (p *process) Pid() int { - return p.proc.Pid + if p.proc == nil { + return 0 + } + + return -p.proc.Pid } func (p *process) Wait() { - for _ = range time.Tick(runningCheckInterval) { - if !p.Running() { + for range time.Tick(runningCheckInterval) { + if p.dead { return } } @@ -114,12 +104,25 @@ func (p *process) Running() bool { } func (p *process) Stop() { - if p.Running() && p.conn != nil { - p.conn.Stop() + p.canDieNow = false + + if p.interrupted { + // Ok, we have tried once, time to go brutal + p.Kill() + return } + + if p.Running() { + p.output.WriteBoldLine(p, []byte("Interrupting...")) + p.proc.Signal(syscall.SIGINT) + } + + p.interrupted = true } func (p *process) Kill() { + p.canDieNow = false + if p.Running() { p.output.WriteBoldLine(p, []byte("Killing...")) p.proc.Signal(syscall.SIGKILL) @@ -127,24 +130,59 @@ func (p *process) Kill() { } func (p *process) Restart() { - if p.conn != nil { - p.conn.Restart() - } + p.restart = true + p.Stop() } -func (p *process) AttachConnection(conn net.Conn) { - if p.conn == nil { - p.conn = &processConnection{conn} - go p.scanConn() +func (p *process) waitPid() { + for range time.Tick(runningCheckInterval) { + if p.Pid() != 0 { + break + } } } -func (p *process) scanConn() { - err := utils.ScanLines(p.conn.Reader(), func(b []byte) bool { +func (p *process) scanOuput() { + err := utils.ScanLines(p.out, func(b []byte) bool { p.output.WriteLine(p, b) return true }) if err != nil { - p.output.WriteErr(p, fmt.Errorf("Connection error: %v", err)) + p.output.WriteErr(p, fmt.Errorf("Output error: %v", err)) + } +} + +func (p *process) observe() { + for range time.Tick(runningCheckInterval) { + if !p.Running() { + if !p.keepingAlive { + p.output.WriteBoldLine(p, []byte("Exited")) + p.keepingAlive = true + } + + if !p.canDieNow { + p.keepingAlive = false + p.proc = nil + + if p.restart { + p.respawn() + } else { + p.dead = true + break + } + } + } } } + +func (p *process) respawn() { + p.output.WriteBoldLine(p, []byte("Restarting...")) + + p.restart = false + p.canDieNow = p.canDie + p.interrupted = false + + p.tmux.RespawnProcess(p) + + p.waitPid() +} diff --git a/start/tmux.go b/start/tmux.go new file mode 100644 index 0000000..eeefa02 --- /dev/null +++ b/start/tmux.go @@ -0,0 +1,147 @@ +package start + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "regexp" + "strconv" + "sync" + "syscall" + + "github.com/pkg/term/termios" +) + +var tmuxUnescapeRe = regexp.MustCompile(`\\(\d{3})`) +var tmuxOutputRe = regexp.MustCompile("%(\\S+) (.+)") +var tmuxProcessRe = regexp.MustCompile("%(\\d+) (.+) (\\d+)") +var outputRe = regexp.MustCompile("%(\\d+) (.+)") + +const tmuxPanesCmd = "list-windows -F \"%%overmind-process #{pane_id} #{window_name} #{pane_pid}\"" + +type tmuxClient struct { + pty *os.File + + processesByPane processesMap + processesByName processesMap + + cmdMutex sync.Mutex + + initKilled bool + + Socket string + Session string +} + +func newTmuxClient(session, socket, root string) (*tmuxClient, error) { + pty, tty, err := termios.Pty() + if err != nil { + return nil, err + } + + t := tmuxClient{ + pty: pty, + processesByName: make(processesMap), + processesByPane: make(processesMap), + + Session: session, + Socket: socket, + } + + cmd := exec.Command("tmux", "-CC", "-L", socket, "new", "-n", "__init__", "-s", session) + cmd.Stdout = tty + // cmd.Stdout = io.MultiWriter(tty, os.Stdout) + cmd.Stderr = os.Stderr + cmd.Stdin = tty + cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} + cmd.Dir = root + + if err := cmd.Start(); err != nil { + return nil, err + } + + t.sendCmd("setw -g remain-on-exit on") + + go t.listen() + + return &t, nil +} + +func (t *tmuxClient) sendCmd(cmd string, arg ...interface{}) { + t.cmdMutex.Lock() + defer t.cmdMutex.Unlock() + + fmt.Fprintln(t.pty, fmt.Sprintf(cmd, arg...)) +} + +func (t *tmuxClient) listen() { + scanner := bufio.NewScanner(t.pty) + + for scanner.Scan() { + // fmt.Println(scanner.Text()) + tmuxOut := tmuxOutputRe.FindStringSubmatch(scanner.Text()) + + if len(tmuxOut) < 2 { + continue + } + + switch tmuxOut[1] { + case "overmind-process": + procbind := tmuxProcessRe.FindStringSubmatch(tmuxOut[2]) + if len(procbind) > 3 { + t.mapProcess(procbind[1], procbind[2], procbind[3]) + } + case "output": + output := outputRe.FindStringSubmatch(tmuxOut[2]) + if len(output) > 2 { + t.sendOutput(output[1], output[2]) + } + } + } +} + +func (t *tmuxClient) mapProcess(pane, name, pid string) { + if p, ok := t.processesByName[name]; ok { + t.processesByPane[pane] = p + p.tmuxPane = pane + + if ipid, err := strconv.Atoi(pid); err == nil { + p.proc, _ = os.FindProcess(-ipid) + } + } +} + +func (t *tmuxClient) sendOutput(name, str string) { + if proc, ok := t.processesByPane[name]; ok { + unescaped := tmuxUnescapeRe.ReplaceAllStringFunc(str, func(src string) string { + code, _ := strconv.ParseUint(src[1:], 8, 8) + return string([]byte{byte(code)}) + }) + + fmt.Fprint(proc.in, unescaped) + } +} + +func (t *tmuxClient) listPanes() { + t.sendCmd(tmuxPanesCmd) +} + +func (t *tmuxClient) AddProcess(p *process) { + t.processesByName[p.Name] = p + t.sendCmd("neww -n %s %s", p.Name, p.Command) + + if !t.initKilled { + // Ok, we have at least one process running, we can kill __init__ window + t.sendCmd("movew -s %s -t __init__", p.Name) + t.sendCmd("killw -t __init__") + t.initKilled = true + } + + t.listPanes() +} + +func (t *tmuxClient) RespawnProcess(p *process) { + t.sendCmd("respawn-pane -t %s %s", p.Name, p.Command) + t.listPanes() +} diff --git a/term/term.go b/term/term.go deleted file mode 100644 index 4f3fce9..0000000 --- a/term/term.go +++ /dev/null @@ -1,89 +0,0 @@ -package term - -import ( - "os" - "unsafe" - - "golang.org/x/sys/unix" -) - -// Size represents terminal size -type Size struct { - Rows uint16 - Cols uint16 - Xpixels uint16 - Ypixels uint16 -} - -// Params contains terminal state and size -type Params struct { - termios unix.Termios - ws Size -} - -func ioctl(fd, request, argp uintptr) error { - if _, _, err := unix.Syscall6(unix.SYS_IOCTL, fd, request, argp, 0, 0, 0); err != 0 { - return err - } - return nil -} - -func getTermios(f *os.File) (t unix.Termios, err error) { - err = ioctl(f.Fd(), ioctlReadTermios, uintptr(unsafe.Pointer(&t))) - return -} - -func setTermios(f *os.File, t unix.Termios) error { - return ioctl(f.Fd(), ioctlWriteTermios, uintptr(unsafe.Pointer(&t))) -} - -// GetSize returns terminal size -func GetSize(f *os.File) (ws Size, err error) { - err = ioctl(f.Fd(), unix.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) - return -} - -// SetSize sets new terminsl size -func SetSize(f *os.File, ws Size) error { - return ioctl(f.Fd(), unix.TIOCSWINSZ, uintptr(unsafe.Pointer(&ws))) -} - -// GetParams returns terminal params -func GetParams(f *os.File) (p Params, err error) { - if p.termios, err = getTermios(f); err != nil { - panic(err) - } - if p.ws, err = GetSize(f); err != nil { - panic(err) - } - return -} - -// SetParams applies provided params to terminal -func SetParams(f *os.File, p Params) (err error) { - if err = setTermios(f, p.termios); err != nil { - panic(err) - } - if err = SetSize(f, p.ws); err != nil { - panic(err) - } - return -} - -// MakeRaw makes terminal raw -func MakeRaw(f *os.File) error { - termios, err := getTermios(f) - if err != nil { - return err - } - - termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON - termios.Oflag &^= unix.OPOST - termios.Cflag &^= unix.CSIZE | unix.PARENB - termios.Cflag |= unix.CS8 - termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN - termios.Cc[unix.VMIN] = 1 - termios.Cc[unix.VTIME] = 0 - - return setTermios(f, termios) -} diff --git a/term/term_bsd.go b/term/term_bsd.go deleted file mode 100644 index b2edcc2..0000000 --- a/term/term_bsd.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build darwin dragonfly freebsd netbsd openbsd - -package term - -import "golang.org/x/sys/unix" - -const ioctlReadTermios = unix.TIOCGETA -const ioctlWriteTermios = unix.TIOCSETA diff --git a/term/term_linux.go b/term/term_linux.go deleted file mode 100644 index a69862c..0000000 --- a/term/term_linux.go +++ /dev/null @@ -1,6 +0,0 @@ -package term - -import "golang.org/x/sys/unix" - -const ioctlReadTermios = unix.TCGETS -const ioctlWriteTermios = unix.TCSETS