Skip to content

Commit

Permalink
Help the user upgrade to a more colorful TERM setting
Browse files Browse the repository at this point in the history
If the user runs in a terminal emulator with TERM=xterm or TERM=screen,
then termshark will only use 8-colors. This is fine but the experience
is better with 256 colors available (or more) - for example, for packet
coloring. If the user knows to change TERM to xterm-256color, that's
also fine, but not all termshark users will know to do this. This change
adds a workflow to help the user run in a higher-color mode, if
possible. On startup, termshark will check to see if (a) only 8 colors
are in use and (b) if a 256-color version of the TERM variable is
available. If so, termshark opens a dialog to suggest switching. If the
user hits "No, don't ask", then termshark sets main.disable-term-helper
in termshark.toml and won't ask again. If the user hits yes, termshark
will restart using the 256-color TERM. After 3 seconds, termshark will
open a dialog to ask the user to confirm that everything looks ok - the
default is no, and if the user hits that, or if 10 seconds elapse
without input, termshark reverts back to the original setting. If the
user hits yes, termshark will ask if the user wants to save that TERM as
the default to use; if yes, main.term will be set in termshark.toml.
  • Loading branch information
gcla committed Sep 2, 2021
1 parent 564d612 commit 1c15fed
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
- Added a config option, main.disk-cache-size-mb, that can be set to have termshark limit the size
of its pcap cache directory. When the directory size exceeds its limit, termshark deletes oldest
pcap files first.
- Added a workflow that helps a user to upgrade from a low-color TERM setting if termshark detects that
there is a 256-color version available in the terminfo database.

### Changed

Expand Down
73 changes: 70 additions & 3 deletions cmd/termshark/termshark.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,52 @@ func main() {

res := cmain()
ensureGoroutinesStopWG.Wait()

if !termshark.ShouldSwitchTerminal && !termshark.ShouldSwitchBack {
os.Exit(res)
}

os.Clearenv()
for _, e := range termshark.OriginalEnv {
ks := strings.SplitN(e, "=", 2)
if len(ks) == 2 {
os.Setenv(ks[0], ks[1])
}
}

exe, err := os.Executable()
if err != nil {
log.Warnf("Unexpected error determining termshark executable: %v", err)
os.Exit(1)
}

switch {
case termshark.ShouldSwitchTerminal:
os.Setenv("TERMSHARK_ORIGINAL_TERM", os.Getenv("TERM"))
os.Setenv("TERM", fmt.Sprintf("%s-256color", os.Getenv("TERM")))
case termshark.ShouldSwitchBack:
os.Setenv("TERM", os.Getenv("TERMSHARK_ORIGINAL_TERM"))
os.Setenv("TERMSHARK_ORIGINAL_TERM", "")
}

// Need exec because we really need to re-initialize everything, including have
// all init() functions be called again
err = syscall.Exec(exe, os.Args, os.Environ())
if err != nil {
log.Warnf("Unexpected error exec-ing termshark %s: %v", exe, err)
res = 1
}

os.Exit(res)
}

func cmain() int {
startedSuccessfully := false // true if we reached the point where packets were received and the UI started.
uiSuspended := false // true if the UI was suspended due to SIGTSTP

// Preserve in case we need to re-exec e.g. if the user switches TERM
termshark.OriginalEnv = os.Environ()

sigChan := make(chan os.Signal, 100)
// SIGINT and SIGQUIT will arrive only via an external kill command,
// not the keyboard, because our line discipline is set up to pass
Expand Down Expand Up @@ -275,7 +314,7 @@ func cmain() int {
// terminal emumlator supports 256 colors.
termVar := termshark.ConfString("main.term", "")
if termVar != "" {
log.Infof("Configuration file overrides TERM setting, using TERM=%s", termVar)
fmt.Fprintf(os.Stderr, "Configuration file overrides TERM setting, using TERM=%s\n", termVar)
os.Setenv("TERM", termVar)
}

Expand Down Expand Up @@ -1098,8 +1137,9 @@ Loop:
// Need to do that here because the app won't know how many colors the screen
// has (and therefore which variant of the theme to load) until the screen is
// activated.
mode := theme.Mode(app.GetColorMode()).String() // more concise
themeName := termshark.ConfString(fmt.Sprintf("main.theme-%s", mode), "default")
mode := app.GetColorMode()
modeStr := theme.Mode(mode) // more concise
themeName := termshark.ConfString(fmt.Sprintf("main.theme-%s", modeStr), "default")
loaded := false
if themeName != "" {
err = theme.Load(themeName, app)
Expand Down Expand Up @@ -1132,6 +1172,33 @@ Loop:

ui.StartUIChan = nil // make sure it's not triggered again

if runtime.GOOS != "windows" {
if mode == gowid.Mode8Colors {
// If exists is true, it means we already tried and then reverted back, so
// just load up termshark normally with no further interruption.
if _, exists := os.LookupEnv("TERMSHARK_ORIGINAL_TERM"); !exists {
if !termshark.ConfBool("main.disable-term-helper", false) {
err = termshark.Does256ColorTermExist()
if err != nil {
log.Infof("Must use 8-color mode because 256-color version of TERM=%s unavailable - %v.", os.Getenv("TERM"), err)
} else {
time.AfterFunc(time.Duration(3)*time.Second, func() {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.SuggestSwitchingTerm(app)
}))
})
}
}
}
} else if os.Getenv("TERMSHARK_ORIGINAL_TERM") != "" {
time.AfterFunc(time.Duration(3)*time.Second, func() {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.IsTerminalLegible(app)
}))
})
}
}

defer func() {
// Do this to make sure the program quits quickly if quit is invoked
// mid-load. It's safe to call this if a pcap isn't being loaded.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gcla/deep v1.0.2
github.com/gcla/gowid v1.2.1-0.20210821221812-8682c44dd77f
github.com/gcla/gowid v1.2.1-0.20210902022514-507cfa6ef3ed
github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359
github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29
github.com/go-test/deep v1.0.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/gcla/deep v1.0.2 h1:qBOx6eepcOSRYnHJ+f2ih4hP4Vca1YnLtXxp73n5KWI=
github.com/gcla/deep v1.0.2/go.mod h1:evE9pbpSGhItmFoBIk8hPOIC/keKTGYhFl6Le1Av+GE=
github.com/gcla/gowid v1.2.1-0.20210821221812-8682c44dd77f h1:Yoj7Qn6+Ms9i+8t5/m+P6aywYaw7HsFqFaaoj71oOrc=
github.com/gcla/gowid v1.2.1-0.20210821221812-8682c44dd77f/go.mod h1:GL9KUFwKHjWS80G7N4hkA++mpOleVQpNvKIHyW9n0i4=
github.com/gcla/gowid v1.2.1-0.20210902022514-507cfa6ef3ed h1:BQp9rokK2GVxViqKTdJhZVDwytBwbT2KtsYdnq5rWmo=
github.com/gcla/gowid v1.2.1-0.20210902022514-507cfa6ef3ed/go.mod h1:GL9KUFwKHjWS80G7N4hkA++mpOleVQpNvKIHyW9n0i4=
github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 h1:3xEhacR7pIJV8daurdBygptxhzTJeYFqJp1V6SDl+pE=
github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359/go.mod h1:Wn+pZpM98JHSOYkPDtmdvlqmc0OzQGHWOsHB2d28WtQ=
github.com/gcla/tcell v1.1.2-0.20200927150251-decc2045f510 h1:TlEZ0DHOvn0P79nHtkfemw7XFn2h8Lacd6AZpXrPU/o=
Expand Down
177 changes: 177 additions & 0 deletions ui/switchterm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2019-2021 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.

// Package ui contains user-interface functions and helpers for termshark.
package ui

import (
"fmt"
"os"
"time"

"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/dialog"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/holder"
"github.com/gcla/gowid/widgets/paragraph"
"github.com/gcla/termshark/v2"
)

//======================================================================

// SuggestSwitchingTerm will open a dialog asking the user if they would like to try
// a more colorful TERM setting.
func SuggestSwitchingTerm(app gowid.IApp) {
var switchTerm *dialog.Widget

Yes := dialog.Button{
Msg: "Yes",
Action: gowid.MakeWidgetCallback("exec", gowid.WidgetChangedFunction(func(app gowid.IApp, w gowid.IWidget) {
termshark.ShouldSwitchTerminal = true
switchTerm.Close(app)
RequestQuit()
})),
}
No := dialog.Button{
Msg: "No",
}
NoAsk := dialog.Button{
Msg: "No, don't ask",
Action: gowid.MakeWidgetCallback("exec", gowid.WidgetChangedFunction(func(app gowid.IApp, w gowid.IWidget) {
termshark.SetConf("main.disable-term-helper", true)
switchTerm.Close(app)
})),
}

term := os.Getenv("TERM")
term256 := term + "-256color"

switchTerm = dialog.New(
framed.NewSpace(paragraph.New(fmt.Sprintf("Termshark is running with TERM=%s. Your terminal database contains %s. Would you like to switch for a more colorful experience? Termshark will need to restart.", term, term256))),
dialog.Options{
Buttons: []dialog.Button{Yes, No, NoAsk},
NoShadow: true,
BackgroundStyle: gowid.MakePaletteRef("dialog"),
BorderStyle: gowid.MakePaletteRef("dialog"),
ButtonStyle: gowid.MakePaletteRef("dialog-button"),
Modal: true,
FocusOnWidget: false,
},
)

switchTerm.Open(appView, gowid.RenderWithRatio{R: 0.5}, app)
}

//======================================================================

// IsTerminalLegible will open up a dialog asking the user to confirm that their
// running termshark is legible, having upgraded the TERM variable to a 256-color
// version and restarted.
func IsTerminalLegible(app gowid.IApp) {

var saveTerm *dialog.Widget

YesSave := dialog.Button{
Msg: "Yes",
Action: gowid.MakeWidgetCallback("exec", gowid.WidgetChangedFunction(func(app gowid.IApp, w gowid.IWidget) {
termshark.SetConf("main.term", os.Getenv("TERM"))
saveTerm.Close(app)
})),
}
NoSave := dialog.Button{
Msg: "No",
}

saveTerm = dialog.New(
framed.NewSpace(paragraph.New(fmt.Sprintf("Do you want to save TERM=%s in termshark's config to use as the default?", os.Getenv("TERM")))),
dialog.Options{
Buttons: []dialog.Button{YesSave, NoSave},
NoShadow: true,
BackgroundStyle: gowid.MakePaletteRef("dialog"),
BorderStyle: gowid.MakePaletteRef("dialog"),
ButtonStyle: gowid.MakePaletteRef("dialog-button"),
Modal: true,
FocusOnWidget: false,
},
)

tick := time.NewTicker(time.Duration(1) * time.Second)
stopC := make(chan struct{})

var legibleTerm *dialog.Widget

No := dialog.Button{
Msg: "No",
Action: gowid.MakeWidgetCallback("exec", gowid.WidgetChangedFunction(func(app gowid.IApp, w gowid.IWidget) {
close(stopC)
termshark.ShouldSwitchBack = true
legibleTerm.Close(app)
RequestQuit()
})),
}
Yes := dialog.Button{
Msg: "Yes",
Action: gowid.MakeWidgetCallback("exec", gowid.WidgetChangedFunction(func(app gowid.IApp, w gowid.IWidget) {
close(stopC)
legibleTerm.Close(app)
saveTerm.Open(appView, gowid.RenderWithRatio{R: 0.5}, app)
})),
}

secs := 10

tw := func(count int) *paragraph.Widget {
return paragraph.New(fmt.Sprintf("Is the terminal legible? If no selection is made, termshark will revert to its original TERM setting in %d seconds.", secs))
}

message := holder.New(tw(secs))

termshark.TrackedGo(func() {
Loop:
for {
select {
case <-tick.C:
secs--
switch {
case secs >= 0:
app.Run(gowid.RunFunction(func(app gowid.IApp) {
message.SetSubWidget(tw(secs), app)
}))
case secs < 0:
tick.Stop()
close(stopC)
app.Run(gowid.RunFunction(func(app gowid.IApp) {
termshark.ShouldSwitchBack = true
legibleTerm.Close(app)
RequestQuit()
}))
}
case <-stopC:
break Loop
}
}
}, Goroutinewg)

legibleTerm = dialog.New(
framed.NewSpace(message),
dialog.Options{
Buttons: []dialog.Button{Yes, No},
NoShadow: true,
BackgroundStyle: gowid.MakePaletteRef("dialog"),
BorderStyle: gowid.MakePaletteRef("dialog"),
ButtonStyle: gowid.MakePaletteRef("dialog-button"),
Modal: true,
FocusOnWidget: false,
StartIdx: 1,
},
)

legibleTerm.Open(appView, gowid.RenderWithRatio{R: 0.5}, app)
}

//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
17 changes: 13 additions & 4 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/gcla/termshark/v2/system"
"github.com/gcla/termshark/v2/widgets/resizable"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/terminfo/dynamic"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
"github.com/shibukawa/configdir"
Expand Down Expand Up @@ -99,10 +100,13 @@ var InternalErr = InternalError{}
//======================================================================

var (
UserGuideURL string = "https://termshark.io/userguide"
FAQURL string = "https://termshark.io/faq"
BugURL string = "https://github.com/gcla/termshark/issues/new?assignees=&labels=&template=bug_report.md&title="
FeatureURL string = "https://github.com/gcla/termshark/issues/new?assignees=&labels=&template=feature_request.md&title="
UserGuideURL string = "https://termshark.io/userguide"
FAQURL string = "https://termshark.io/faq"
BugURL string = "https://github.com/gcla/termshark/issues/new?assignees=&labels=&template=bug_report.md&title="
FeatureURL string = "https://github.com/gcla/termshark/issues/new?assignees=&labels=&template=feature_request.md&title="
OriginalEnv []string
ShouldSwitchTerminal bool
ShouldSwitchBack bool
)

//======================================================================
Expand Down Expand Up @@ -1322,6 +1326,11 @@ func FileSizeDifferentTo(filename string, cur int64) (int64, bool) {
return newSize, diff
}

func Does256ColorTermExist() error {
_, _, e := dynamic.LoadTerminfo(fmt.Sprintf("%s-256color", os.Getenv("TERM")))
return e
}

//======================================================================
// Local Variables:
// mode: Go
Expand Down

0 comments on commit 1c15fed

Please sign in to comment.