diff --git a/go.mod b/go.mod index 2ff4943d3..88b038632 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/briandowns/openweathermap v0.0.0-20180804155945-5f41b7c9d92d github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/creack/pty v1.1.11 github.com/digitalocean/godo v1.44.0 github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v1.13.1 diff --git a/go.sum b/go.sum index 5f1f9853b..372dbd076 100644 --- a/go.sum +++ b/go.sum @@ -162,6 +162,8 @@ github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dancannon/gorethink v4.0.0+incompatible h1:KFV7Gha3AuqT+gr0B/eKvGhbjmUv0qGF43aKCIKVE9A= github.com/dancannon/gorethink v4.0.0+incompatible/go.mod h1:BLvkat9KmZc1efyYwhz3WnybhRZtgF1K929FD8z1avU= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= @@ -923,6 +925,8 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/modules/cmdrunner/settings.go b/modules/cmdrunner/settings.go index 7f88f89f8..faa7dda91 100644 --- a/modules/cmdrunner/settings.go +++ b/modules/cmdrunner/settings.go @@ -18,6 +18,7 @@ type Settings struct { args []string `help:"The arguments to the command, with each item as an element in an array. Example: for curl -I cisco.com, the arguments array would be ['-I', 'cisco.com']."` cmd string `help:"The terminal command to be run, withouth the arguments. Ie: ping, whoami, curl."` tail bool `help:"Automatically scroll to the end of the command output."` + pty bool `help:"Run the command in a pseudo-terminal. Some apps will behave differently if they feel in a terminal. For example, some apps will produce colorized output in a terminal, and non-colorized output otherwise. Default false" optional:"true"` maxLines int `help:"Maximum number of lines kept in the buffer."` // The dimensions of the module @@ -32,6 +33,7 @@ func NewSettingsFromYAML(name string, moduleConfig *config.Config, globalConfig args: utils.ToStrs(moduleConfig.UList("args")), cmd: moduleConfig.UString("cmd"), + pty: moduleConfig.UBool("pty", false), tail: moduleConfig.UBool("tail", false), maxLines: moduleConfig.UInt("maxLines", 256), } diff --git a/modules/cmdrunner/widget.go b/modules/cmdrunner/widget.go index aa733e6b2..0eca5aecd 100644 --- a/modules/cmdrunner/widget.go +++ b/modules/cmdrunner/widget.go @@ -3,11 +3,13 @@ package cmdrunner import ( "bytes" "fmt" + "io" "os" "os/exec" "strings" "sync" + "github.com/creack/pty" "github.com/rivo/tview" "github.com/wtfutil/wtf/view" ) @@ -121,23 +123,45 @@ func runCommandLoop(widget *Widget) { <-widget.runChan widget.resetBuffer() cmd := exec.Command(widget.settings.cmd, widget.settings.args...) - cmd.Stdout = widget cmd.Env = widget.environment() - err := cmd.Run() - - // The command has exited, print any error messages + var err error + if widget.settings.pty { + err = runCommandPty(widget, cmd) + } else { + err = runCommand(widget, cmd) + } if err != nil { - widget.m.Lock() - _, writeErr := widget.buffer.WriteString(err.Error()) - if writeErr != nil { - return - } - widget.m.Unlock() + widget.handleError(err) } widget.redrawChan <- true } } +func runCommand(widget *Widget, cmd *exec.Cmd) error { + cmd.Stdout = widget + return cmd.Run() +} + +func runCommandPty(widget *Widget, cmd *exec.Cmd) error { + f, err := pty.Start(cmd) + // The command has exited, print any error messages + if err != nil { + return err + } + + _, err = io.Copy(widget.buffer, f) + return err +} + +func (widget *Widget) handleError(err error) { + widget.m.Lock() + defer widget.m.Unlock() + _, writeErr := widget.buffer.WriteString(err.Error()) + if writeErr != nil { + return + } +} + func redrawLoop(widget *Widget) { for { widget.Redraw(widget.content)