diff --git a/ui/dialog.go b/ui/dialog.go index 21d3afd..7f26ed4 100644 --- a/ui/dialog.go +++ b/ui/dialog.go @@ -17,6 +17,7 @@ import ( "github.com/gcla/gowid/widgets/text" "github.com/gcla/termshark/v2" "github.com/gcla/termshark/v2/widgets/appkeys" + "github.com/gcla/termshark/v2/widgets/minibuffer" ) //====================================================================== @@ -27,6 +28,7 @@ var ( hmiddle gowid.HAlignMiddle vmiddle gowid.VAlignMiddle YesNo *dialog.Widget + MiniBuffer *minibuffer.Widget PleaseWait *dialog.Widget ) diff --git a/ui/lastline.go b/ui/lastline.go new file mode 100644 index 0000000..12f94c9 --- /dev/null +++ b/ui/lastline.go @@ -0,0 +1,223 @@ +// Copyright 2019-2020 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" + "strconv" + + "github.com/gcla/gowid" + "github.com/gcla/gowid/gwutil" + "github.com/gcla/termshark/v2" + "github.com/gcla/termshark/v2/widgets/minibuffer" +) + +//====================================================================== + +var notEnoughArgumentsErr = fmt.Errorf("Not enough arguments provided") + +type minibufferFn func(gowid.IApp, ...string) error + +func (m minibufferFn) Run(app gowid.IApp, args ...string) error { + return m(app, args...) +} + +func (m minibufferFn) OfferCompletion() bool { + return true +} + +func (m minibufferFn) Arguments([]string) []minibuffer.IArg { + return nil +} + +type quietMinibufferFn func(gowid.IApp, ...string) error + +func (m quietMinibufferFn) Run(app gowid.IApp, args ...string) error { + return m(app, args...) +} + +func (m quietMinibufferFn) OfferCompletion() bool { + return false +} + +func (m quietMinibufferFn) Arguments([]string) []minibuffer.IArg { + return nil +} + +//====================================================================== + +type boolArg struct { + arg string +} + +var _ minibuffer.IArg = boolArg{} + +func (s boolArg) OfferCompletion() bool { + return true +} + +// return these in sorted order +func (s boolArg) Completions() []string { + return []string{"false", "true"} +} + +//====================================================================== + +type onOffArg struct { + arg string +} + +var _ minibuffer.IArg = onOffArg{} + +func (s onOffArg) OfferCompletion() bool { + return true +} + +// return these in sorted order +func (s onOffArg) Completions() []string { + return []string{"off", "on"} +} + +//====================================================================== + +type unhelpfulArg struct { + arg string +} + +var _ minibuffer.IArg = unhelpfulArg{} + +func (s unhelpfulArg) OfferCompletion() bool { + return false +} + +// return these in sorted order +func (s unhelpfulArg) Completions() []string { + return nil +} + +//====================================================================== + +type setArg struct { + arg string +} + +var _ minibuffer.IArg = setArg{} + +func (s setArg) OfferCompletion() bool { + return true +} + +// return these in sorted order +func (s setArg) Completions() []string { + return []string{ + "auto-scroll", + "copy-command-timeout", + "dark-mode", + "disable-shark-fin", + "packet-colors", + } +} + +//====================================================================== + +func stringIn(s string, a []string) bool { + for _, s2 := range a { + if s == s2 { + return true + } + } + return false +} + +func parseOnOff(str string) (bool, error) { + switch str { + case "on", "ON", "On": + return true, nil + case "off", "OFF", "Off": + return false, nil + } + return false, strconv.ErrSyntax +} + +type setCommand struct{} + +var _ minibuffer.IAction = setCommand{} + +func (d setCommand) Run(app gowid.IApp, args ...string) error { + var err error + var b bool + var i uint64 + if len(args) == 3 { + switch args[1] { + case "auto-scroll": + if b, err = parseOnOff(args[2]); err == nil { + AutoScroll = b + termshark.SetConf("main.auto-scroll", AutoScroll) + OpenMessage(fmt.Sprintf("Packet auto-scroll is now %s", gwutil.If(b, "on", "off").(string)), appView, app) + } + case "copy-command-timeout": + if i, err = strconv.ParseUint(args[2], 10, 32); err == nil { + termshark.SetConf("main.copy-command-timeout", i) + OpenMessage(fmt.Sprintf("Copy command timeout is now %ds", i), appView, app) + } + case "dark-mode": + if b, err = parseOnOff(args[2]); err == nil { + DarkMode = b + termshark.SetConf("main.dark-mode", DarkMode) + } + case "disable-shark-fin": + if b, err = strconv.ParseBool(args[2]); err == nil { + termshark.SetConf("main.disable-shark-fin", DarkMode) + OpenMessage(fmt.Sprintf("Shark-saver is now %s", gwutil.If(b, "off", "on").(string)), appView, app) + } + case "packet-colors": + if b, err = parseOnOff(args[2]); err == nil { + PacketColors = b + termshark.SetConf("main.packet-colors", PacketColors) + OpenMessage(fmt.Sprintf("Packet colors are now %s", gwutil.If(b, "on", "off").(string)), appView, app) + } + } + } else { + err = notEnoughArgumentsErr + } + + if err != nil { + OpenMessage(fmt.Sprintf("Error: %s", err), appView, app) + } + + return err +} + +func (d setCommand) OfferCompletion() bool { + return true +} + +func (d setCommand) Arguments(toks []string) []minibuffer.IArg { + res := make([]minibuffer.IArg, 0) + res = append(res, setArg{}) + + if len(toks) > 0 { + onOffCmds := []string{"auto-scroll", "dark-mode", "packet-colors"} + boolCmds := []string{"disable-shark-fin"} + intCmds := []string{"disk-cache-size-mb", "copy-command-timeout"} + + if stringIn(toks[0], boolCmds) { + res = append(res, boolArg{}) + } else if stringIn(toks[0], intCmds) { + res = append(res, unhelpfulArg{}) + } else if stringIn(toks[0], onOffCmds) { + res = append(res, onOffArg{}) + } + } + + return res +} + +//====================================================================== +// Local Variables: +// mode: Go +// fill-column: 110 +// End: diff --git a/ui/messages.go b/ui/messages.go index 55e83c2..b625359 100644 --- a/ui/messages.go +++ b/ui/messages.go @@ -69,6 +69,7 @@ A wireshark-inspired tui for tshark. Analyze network traffic interactively from 't' - In bytes view, switch hex ⟷ ascii '+/-' - Adjust horizontal split '' - Adjust vertical split +':' - Last line mode (minibuffer) '?' - Display help In the filter, type a wireshark display filter expression. diff --git a/ui/ui.go b/ui/ui.go index 1295545..db7375d 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -56,6 +56,7 @@ import ( "github.com/gcla/termshark/v2/widgets/filter" "github.com/gcla/termshark/v2/widgets/hexdumper2" "github.com/gcla/termshark/v2/widgets/ifwidget" + "github.com/gcla/termshark/v2/widgets/minibuffer" "github.com/gcla/termshark/v2/widgets/resizable" "github.com/gcla/termshark/v2/widgets/rossshark" "github.com/gcla/termshark/v2/widgets/withscrollbar" @@ -938,6 +939,72 @@ func reallyQuit(app gowid.IApp) { YesNo.Open(appView, units(len(msgt)+20), app) } +func lastLineMode(app gowid.IApp) { + MiniBuffer = minibuffer.New() + + MiniBuffer.Register("quit", minibufferFn(func(gowid.IApp, ...string) error { + // Delay because when control returns to the minibuffer, it closes itself + // if there is no error, but that has the side effect of closing this + // dialog too :-( + app.Run(gowid.RunFunction(func(app gowid.IApp) { + reallyQuit(app) + })) + return nil + })) + + // force quit + MiniBuffer.Register("q!", quietMinibufferFn(func(gowid.IApp, ...string) error { + QuitRequestedChan <- struct{}{} + return nil + })) + + MiniBuffer.Register("help", minibufferFn(func(gowid.IApp, ...string) error { + app.Run(gowid.RunFunction(func(app gowid.IApp) { + OpenTemplatedDialog(appView, "UIHelp", app) + })) + return nil + })) + + MiniBuffer.Register("?", quietMinibufferFn(func(gowid.IApp, ...string) error { + app.Run(gowid.RunFunction(func(app gowid.IApp) { + OpenTemplatedDialog(appView, "UIHelp", app) + })) + return nil + })) + + MiniBuffer.Register("convs", minibufferFn(func(gowid.IApp, ...string) error { + app.Run(gowid.RunFunction(func(app gowid.IApp) { + openConvsUi(app) + })) + return nil + })) + + MiniBuffer.Register("streams", minibufferFn(func(gowid.IApp, ...string) error { + app.Run(gowid.RunFunction(func(app gowid.IApp) { + startStreamReassembly(app) + })) + return nil + })) + + MiniBuffer.Register("capinfo", minibufferFn(func(gowid.IApp, ...string) error { + app.Run(gowid.RunFunction(func(app gowid.IApp) { + startCapinfo(app) + })) + return nil + })) + + MiniBuffer.Register("clear", minibufferFn(func(gowid.IApp, ...string) error { + app.Run(gowid.RunFunction(func(app gowid.IApp) { + reallyClear(app) + })) + return nil + })) + + MiniBuffer.Register("set", setCommand{}) + + minibuffer.Open(MiniBuffer, appView, ratio(1.0), flow, app) +} + //====================================================================== // getCurrentStructModel will return a termshark model of a packet section of PDML given a row number, @@ -1404,6 +1471,8 @@ func appKeyPress(evk *tcell.EventKey, app gowid.IApp) bool { app.Sync() } else if evk.Rune() == 'q' || evk.Rune() == 'Q' { reallyQuit(app) + } else if evk.Rune() == ':' { + lastLineMode(app) } else if evk.Key() == tcell.KeyEscape { gowid.SetFocusPath(mainview, menuPathMain, app) gowid.SetFocusPath(altview1, menuPathAlt, app)