From 8a4ad8fbd40754b18528fb14431e20325fb3cbfb Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 20 Sep 2023 12:25:02 -0400 Subject: [PATCH 1/3] Turn off terminal echo while progressbar is present. --- internal/osutils/termecho/termecho.go | 9 +++++ internal/osutils/termecho/termecho_darwin.go | 6 ++++ internal/osutils/termecho/termecho_linux.go | 6 ++++ internal/osutils/termecho/termecho_unix.go | 32 +++++++++++++++++ internal/osutils/termecho/termecho_windows.go | 35 +++++++++++++++++++ internal/runbits/runtime/progress/progress.go | 19 +++++++--- 6 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 internal/osutils/termecho/termecho.go create mode 100644 internal/osutils/termecho/termecho_darwin.go create mode 100644 internal/osutils/termecho/termecho_linux.go create mode 100644 internal/osutils/termecho/termecho_unix.go create mode 100644 internal/osutils/termecho/termecho_windows.go diff --git a/internal/osutils/termecho/termecho.go b/internal/osutils/termecho/termecho.go new file mode 100644 index 0000000000..4d84d4e37a --- /dev/null +++ b/internal/osutils/termecho/termecho.go @@ -0,0 +1,9 @@ +package termecho + +func Off() error { + return toggle(false) +} + +func On() error { + return toggle(true) +} diff --git a/internal/osutils/termecho/termecho_darwin.go b/internal/osutils/termecho/termecho_darwin.go new file mode 100644 index 0000000000..30dedb72d5 --- /dev/null +++ b/internal/osutils/termecho/termecho_darwin.go @@ -0,0 +1,6 @@ +package termecho + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA +const ioctlWriteTermios = unix.TIOCSETA diff --git a/internal/osutils/termecho/termecho_linux.go b/internal/osutils/termecho/termecho_linux.go new file mode 100644 index 0000000000..3bc972354e --- /dev/null +++ b/internal/osutils/termecho/termecho_linux.go @@ -0,0 +1,6 @@ +package termecho + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/internal/osutils/termecho/termecho_unix.go b/internal/osutils/termecho/termecho_unix.go new file mode 100644 index 0000000000..2b04217ec3 --- /dev/null +++ b/internal/osutils/termecho/termecho_unix.go @@ -0,0 +1,32 @@ +//go:build linux || darwin +// +build linux darwin + +package termecho + +import ( + "os" + + "github.com/ActiveState/cli/internal/errs" + "golang.org/x/sys/unix" +) + +func toggle(on bool) error { + fd := int(os.Stdin.Fd()) + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return errs.Wrap(err, "Could not get termios") + } + + newState := *termios // copy + if !on { + newState.Lflag &^= unix.ECHO + } else { + newState.Lflag |= unix.ECHO + } + err = unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState) + if err != nil { + return errs.Wrap(err, "Could not set termios") + } + + return nil +} diff --git a/internal/osutils/termecho/termecho_windows.go b/internal/osutils/termecho/termecho_windows.go new file mode 100644 index 0000000000..4ca872f039 --- /dev/null +++ b/internal/osutils/termecho/termecho_windows.go @@ -0,0 +1,35 @@ +package termecho + +import ( + "os" + + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/logging" + "golang.org/x/sys/windows" +) + +func toggle(on bool) error { + fd := windows.Handle(os.Stdin.Fd()) + var mode uint32 + err := windows.GetConsoleMode(fd, &mode) + if err != nil { + if shell := os.Getenv("SHELL"); shell != "" { + logging.Debug("Cannot turn off terminal echo in %s", shell) + return nil + } + return errs.Wrap(err, "Error calling GetConsoleMode") + } + + newMode := mode + if !on { + newMode &^= windows.ENABLE_ECHO_INPUT + } else { + newMode |= windows.ENABLE_ECHO_INPUT + } + err = windows.SetConsoleMode(fd, newMode) + if err != nil { + return errs.Wrap(err, "Error calling SetConsoleMode") + } + + return nil +} diff --git a/internal/runbits/runtime/progress/progress.go b/internal/runbits/runtime/progress/progress.go index 2a08b5ca74..54c8596cac 100644 --- a/internal/runbits/runtime/progress/progress.go +++ b/internal/runbits/runtime/progress/progress.go @@ -7,17 +7,17 @@ import ( "sync" "time" - "github.com/ActiveState/cli/internal/multilog" - "github.com/go-openapi/strfmt" - "github.com/vbauerster/mpb/v7" - "golang.org/x/net/context" - "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/multilog" + "github.com/ActiveState/cli/internal/osutils/termecho" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/pkg/platform/runtime/artifact" "github.com/ActiveState/cli/pkg/platform/runtime/setup/events" + "github.com/go-openapi/strfmt" + "github.com/vbauerster/mpb/v7" + "golang.org/x/net/context" ) type step struct { @@ -89,6 +89,10 @@ type ProgressDigester struct { } func NewProgressIndicator(w io.Writer, out output.Outputer) *ProgressDigester { + err := termecho.Off() + if err != nil { + multilog.Error("Unable to turn off terminal echoing: %v", errs.JoinMessage(err)) + } ctx, cancel := context.WithCancel(context.Background()) return &ProgressDigester{ mainProgress: mpb.NewWithContext( @@ -380,5 +384,10 @@ Still expecting: // Blank line to separate progress from rest of output p.out.Notice("") + err := termecho.On() + if err != nil { + multilog.Error("Unable to turn terminal echoing back on: %v", errs.JoinMessage(err)) + } + return nil } From 7c4556310c32bb4ea34e8a0f4b0fa0707172805e Mon Sep 17 00:00:00 2001 From: mitchell Date: Mon, 13 Nov 2023 15:48:13 -0500 Subject: [PATCH 2/3] Disable terminal echo while State Tool is running. The exception is for prompts and subshells (which temporarily re-enable echoing). --- cmd/state/main.go | 7 +++++++ internal/osutils/termecho/termecho.go | 9 --------- internal/runbits/activation/activation.go | 2 ++ internal/runbits/runtime/progress/progress.go | 17 ++++------------ internal/subshell/bash/bash.go | 15 ++++++++++++++ internal/subshell/cmd/cmd.go | 9 +++++++++ internal/subshell/fish/fish.go | 16 +++++++++++++++ internal/subshell/subshell.go | 6 ++++++ internal/subshell/tcsh/tcsh.go | 16 +++++++++++++++ internal/subshell/termecho/termecho.go | 20 +++++++++++++++++++ .../termecho/termecho_darwin.go | 0 .../termecho/termecho_linux.go | 0 .../termecho/termecho_unix.go | 0 .../termecho/termecho_windows.go | 5 ----- internal/subshell/zsh/zsh.go | 16 +++++++++++++++ 15 files changed, 111 insertions(+), 27 deletions(-) delete mode 100644 internal/osutils/termecho/termecho.go create mode 100644 internal/subshell/termecho/termecho.go rename internal/{osutils => subshell}/termecho/termecho_darwin.go (100%) rename internal/{osutils => subshell}/termecho/termecho_linux.go (100%) rename internal/{osutils => subshell}/termecho/termecho_unix.go (100%) rename internal/{osutils => subshell}/termecho/termecho_windows.go (76%) diff --git a/cmd/state/main.go b/cmd/state/main.go index 56850b62da..ca683d0e54 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -207,6 +207,13 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up conditional, which accesses a lot of primer data sshell := subshell.New(cfg) + if isInteractive { + // Disable terminal echo while State Tool is running. + // Other than in prompts and subshells (which temporarily re-enable echo), user typing should + // not interfere with output (e.g. runtime progress bars). + sshell.TurnOffEcho() + defer sshell.TurnOnEcho() + } conditional := constraints.NewPrimeConditional(auth, pj, sshell.Shell()) project.RegisterConditional(conditional) diff --git a/internal/osutils/termecho/termecho.go b/internal/osutils/termecho/termecho.go deleted file mode 100644 index 4d84d4e37a..0000000000 --- a/internal/osutils/termecho/termecho.go +++ /dev/null @@ -1,9 +0,0 @@ -package termecho - -func Off() error { - return toggle(false) -} - -func On() error { - return toggle(true) -} diff --git a/internal/runbits/activation/activation.go b/internal/runbits/activation/activation.go index 865c7a9211..8afe8309d6 100644 --- a/internal/runbits/activation/activation.go +++ b/internal/runbits/activation/activation.go @@ -67,6 +67,8 @@ func ActivateAndWait( if err := ss.Activate(proj, cfg, out); err != nil { return locale.WrapError(err, "error_could_not_activate_subshell", "Could not activate a new subshell.") } + ss.TurnOnEcho() // temporarily re-enable echo while the subshell is active + defer ss.TurnOffEcho() a, err := process.NewActivation(cfg, os.Getpid()) if err != nil { diff --git a/internal/runbits/runtime/progress/progress.go b/internal/runbits/runtime/progress/progress.go index 54c8596cac..786336b807 100644 --- a/internal/runbits/runtime/progress/progress.go +++ b/internal/runbits/runtime/progress/progress.go @@ -7,17 +7,17 @@ import ( "sync" "time" + "github.com/go-openapi/strfmt" + "github.com/vbauerster/mpb/v7" + "golang.org/x/net/context" + "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/internal/osutils/termecho" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/pkg/platform/runtime/artifact" "github.com/ActiveState/cli/pkg/platform/runtime/setup/events" - "github.com/go-openapi/strfmt" - "github.com/vbauerster/mpb/v7" - "golang.org/x/net/context" ) type step struct { @@ -89,10 +89,6 @@ type ProgressDigester struct { } func NewProgressIndicator(w io.Writer, out output.Outputer) *ProgressDigester { - err := termecho.Off() - if err != nil { - multilog.Error("Unable to turn off terminal echoing: %v", errs.JoinMessage(err)) - } ctx, cancel := context.WithCancel(context.Background()) return &ProgressDigester{ mainProgress: mpb.NewWithContext( @@ -384,10 +380,5 @@ Still expecting: // Blank line to separate progress from rest of output p.out.Notice("") - err := termecho.On() - if err != nil { - multilog.Error("Unable to turn terminal echoing back on: %v", errs.JoinMessage(err)) - } - return nil } diff --git a/internal/subshell/bash/bash.go b/internal/subshell/bash/bash.go index 53ca17bdb7..2060dc3928 100644 --- a/internal/subshell/bash/bash.go +++ b/internal/subshell/bash/bash.go @@ -15,6 +15,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -219,3 +220,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} diff --git a/internal/subshell/cmd/cmd.go b/internal/subshell/cmd/cmd.go index 3601eb6f78..0b41ba33a9 100644 --- a/internal/subshell/cmd/cmd.go +++ b/internal/subshell/cmd/cmd.go @@ -12,6 +12,7 @@ import ( "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -203,3 +204,11 @@ func (v *SubShell) IsActive() bool { func (v *SubShell) IsAvailable() bool { return runtime.GOOS == "windows" } + +func (v *SubShell) TurnOffEcho() { + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + termecho.Off() +} diff --git a/internal/subshell/fish/fish.go b/internal/subshell/fish/fish.go index da50347327..5e2e03b77e 100644 --- a/internal/subshell/fish/fish.go +++ b/internal/subshell/fish/fish.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" @@ -14,6 +15,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -195,3 +197,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} diff --git a/internal/subshell/subshell.go b/internal/subshell/subshell.go index 589a51f52e..8a95368c6d 100644 --- a/internal/subshell/subshell.go +++ b/internal/subshell/subshell.go @@ -84,6 +84,12 @@ type SubShell interface { // IsAvailable returns whether the shell is available on the system IsAvailable() bool + + // TurnOffEcho turns off input echoing. + TurnOffEcho() + + // TurnOnEcho turns on input echoing. + TurnOnEcho() } // New returns the subshell relevant to the current process, but does not activate it diff --git a/internal/subshell/tcsh/tcsh.go b/internal/subshell/tcsh/tcsh.go index 11b8908485..1bb564b4dd 100644 --- a/internal/subshell/tcsh/tcsh.go +++ b/internal/subshell/tcsh/tcsh.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" @@ -13,6 +14,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -189,3 +191,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} diff --git a/internal/subshell/termecho/termecho.go b/internal/subshell/termecho/termecho.go new file mode 100644 index 0000000000..5fc36e17b8 --- /dev/null +++ b/internal/subshell/termecho/termecho.go @@ -0,0 +1,20 @@ +package termecho + +import ( + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/multilog" +) + +func Off() { + err := toggle(false) + if err != nil { + multilog.Error("Unable to turn off terminal echoing: %v", errs.JoinMessage(err)) + } +} + +func On() { + err := toggle(true) + if err != nil { + multilog.Error("Unable to turn off terminal echoing: %v", errs.JoinMessage(err)) + } +} diff --git a/internal/osutils/termecho/termecho_darwin.go b/internal/subshell/termecho/termecho_darwin.go similarity index 100% rename from internal/osutils/termecho/termecho_darwin.go rename to internal/subshell/termecho/termecho_darwin.go diff --git a/internal/osutils/termecho/termecho_linux.go b/internal/subshell/termecho/termecho_linux.go similarity index 100% rename from internal/osutils/termecho/termecho_linux.go rename to internal/subshell/termecho/termecho_linux.go diff --git a/internal/osutils/termecho/termecho_unix.go b/internal/subshell/termecho/termecho_unix.go similarity index 100% rename from internal/osutils/termecho/termecho_unix.go rename to internal/subshell/termecho/termecho_unix.go diff --git a/internal/osutils/termecho/termecho_windows.go b/internal/subshell/termecho/termecho_windows.go similarity index 76% rename from internal/osutils/termecho/termecho_windows.go rename to internal/subshell/termecho/termecho_windows.go index 4ca872f039..29d3c5eb6d 100644 --- a/internal/osutils/termecho/termecho_windows.go +++ b/internal/subshell/termecho/termecho_windows.go @@ -4,7 +4,6 @@ import ( "os" "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/logging" "golang.org/x/sys/windows" ) @@ -13,10 +12,6 @@ func toggle(on bool) error { var mode uint32 err := windows.GetConsoleMode(fd, &mode) if err != nil { - if shell := os.Getenv("SHELL"); shell != "" { - logging.Debug("Cannot turn off terminal echo in %s", shell) - return nil - } return errs.Wrap(err, "Error calling GetConsoleMode") } diff --git a/internal/subshell/zsh/zsh.go b/internal/subshell/zsh/zsh.go index 68b4599f3b..a502651c1d 100644 --- a/internal/subshell/zsh/zsh.go +++ b/internal/subshell/zsh/zsh.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "github.com/ActiveState/cli/internal/constants" @@ -18,6 +19,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -239,3 +241,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} From 750654a27eab28bf7b3edcf2b3b425848529432a Mon Sep 17 00:00:00 2001 From: mitchell Date: Tue, 14 Nov 2023 17:09:56 -0500 Subject: [PATCH 3/3] Fixed typo. --- internal/subshell/bash/bash.go | 2 +- internal/subshell/cmd/cmd.go | 2 +- internal/subshell/fish/fish.go | 2 +- internal/subshell/tcsh/tcsh.go | 2 +- internal/subshell/zsh/zsh.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/subshell/bash/bash.go b/internal/subshell/bash/bash.go index 2060dc3928..ccd5bd5381 100644 --- a/internal/subshell/bash/bash.go +++ b/internal/subshell/bash/bash.go @@ -232,5 +232,5 @@ func (v *SubShell) TurnOnEcho() { if runtime.GOOS == "windows" { return // not supported } - termecho.Off() + termecho.On() } diff --git a/internal/subshell/cmd/cmd.go b/internal/subshell/cmd/cmd.go index 0b41ba33a9..01b482922e 100644 --- a/internal/subshell/cmd/cmd.go +++ b/internal/subshell/cmd/cmd.go @@ -210,5 +210,5 @@ func (v *SubShell) TurnOffEcho() { } func (v *SubShell) TurnOnEcho() { - termecho.Off() + termecho.On() } diff --git a/internal/subshell/fish/fish.go b/internal/subshell/fish/fish.go index 5e2e03b77e..0792c75571 100644 --- a/internal/subshell/fish/fish.go +++ b/internal/subshell/fish/fish.go @@ -209,5 +209,5 @@ func (v *SubShell) TurnOnEcho() { if runtime.GOOS == "windows" { return // not supported } - termecho.Off() + termecho.On() } diff --git a/internal/subshell/tcsh/tcsh.go b/internal/subshell/tcsh/tcsh.go index 1bb564b4dd..7eefd5f92d 100644 --- a/internal/subshell/tcsh/tcsh.go +++ b/internal/subshell/tcsh/tcsh.go @@ -203,5 +203,5 @@ func (v *SubShell) TurnOnEcho() { if runtime.GOOS == "windows" { return // not supported } - termecho.Off() + termecho.On() } diff --git a/internal/subshell/zsh/zsh.go b/internal/subshell/zsh/zsh.go index a502651c1d..13720b0707 100644 --- a/internal/subshell/zsh/zsh.go +++ b/internal/subshell/zsh/zsh.go @@ -253,5 +253,5 @@ func (v *SubShell) TurnOnEcho() { if runtime.GOOS == "windows" { return // not supported } - termecho.Off() + termecho.On() }