package gui import ( "errors" "os" "runtime/debug" "time" "go.wit.com/lib/protobuf/guipb" "go.wit.com/log" "go.wit.com/widget" ) // const Xaxis = 0 // stack things horizontally // const Yaxis = 1 // stack things vertically func initNew() { defer func() { if r := recover(); r != nil { crash(r, "watchCallback()") } }() log.Log(INFO, "init() has been run") me.counter = 0 // me.prefix = "wit" // Populates the top of the binary tree me.rootNode = addNode() me.rootNode.progname = "guiBinaryTree" me.rootNode.WidgetType = widget.Root me.rootNode.hidden = false // always send the rootNode to the toolkits me.widgets = guipb.NewWidgets() // used to pass debugging flags to the toolkit plugins flag := me.rootNode.newNode("flag", 0) flag.WidgetType = widget.Flag flag = me.rootNode.newNode("stdout", 0) flag.WidgetType = widget.Stdout me.guiChan = make(chan widget.Action, 1) version, err := getGuiVersion() log.Println("GO GUI version", version, showVersion()+"", err) if version == "" { log.Warn("Warning: compiled without version", err) log.Sleep(1) } go watchCallback() } // what genius figured this out? // originally from github.com/dimasma0305/GoFetch func showVersion() string { // var runes rune // color1 := "\x1b[0;29m  \x1b[0m" // runes = []rune(color1) // view.WriteRunes(runes) color1 := "\x1b[0;29m  \x1b[0m" color2 := "\x1b[0;31m  \x1b[0m" color3 := "\x1b[0;32m  \x1b[0m" color4 := "\x1b[0;33m  \x1b[0m" color5 := "\x1b[0;34m  \x1b[0m" color6 := "\x1b[0;35m  \x1b[0m" color7 := "\x1b[0;36m  \x1b[0m" color8 := "\x1b[0;37m  \x1b[0m" return color1 + " " + color2 + " " + color3 + " " + color4 + " " + color5 + " " + color6 + " " + color7 + " " + color8 } var GUIVERSION string func getGuiVersion() (string, error) { var found string tmp, ok := debug.ReadBuildInfo() if !ok { return "", errors.New("debug.ReadBuildInfo() not ok") } if tmp == nil { return "", errors.New("compiled without GO module support") } // log.Println("mod.Path = ", tmp.Path) // log.Println("mod.Main.Path = ", tmp.Main.Path) // log.Println("mod.Main.Version = ", tmp.Main.Version) // log.Println("mod.Main.Sum = ", tmp.Main.Sum) for _, value := range tmp.Deps { if value.Path == "go.wit.com/gui" { found = value.Version } // log.Println("\tmod.Path = ", value.Path) // log.Println("\tmod.Version = ", value.Version) } if found != "" { // log.Println("GUI build version:", found) return found, nil } if GUIVERSION != "" { // log.Println("GUI build ldflag:", GUIVERSION) return GUIVERSION, nil } if os.Getenv("GUIVERSION") != "" { found = os.Getenv("GUIVERSION") + "-dirty" // log.Println("GUI build $GUIVERSION", found) return found, nil } found = "pre-v1-GO111" // log.Println("GUI build version:", found) return found, errors.New("GO111 developer build") } // lookup the widget by the id sent from the toolkit func (n *Node) findId(i int) *Node { if n == nil { return nil } if n.id == i { return n } for _, child := range n.children { newN := child.findId(i) if newN != nil { return newN } } return nil } func pluginCounter(a *widget.Action) { var found bool = false for _, aplug := range allPlugins { if a.ProgName == aplug.name { aplug.count += 1 found = true } } if !found { // TODO: fix this by making seperate channels for each plugin? log.Log(WARN, "ListToolkits() got event from unidentified plugin") } } func UnloadToolkits() { if me.rootNode == nil { log.Log(INFO, "gui rootNode == nil. can't UnloadToolkits()") return } for _, aplug := range allPlugins { me.rootNode.CloseToolkit(aplug.name) time.Sleep(100 * time.Millisecond) // maybe a good idea for now } } func toolkitPanic(pname string) { log.Log(WARN, "toolkitPanic() in", pname) log.Log(WARN, "toolkitPanic() unload toolkit plugin here", pname) me.rootNode.ListToolkits() for _, aplug := range allPlugins { if aplug.name == pname { log.Log(WARN, "toolkitPanic() FOUND PLUGIN =", aplug.name) log.Log(WARN, "toolkitPanic() unload here aplug.dead =", aplug.dead) log.Log(WARN, "toolkitPanic() TODO: stop talking to plugin:", pname) aplug.dead = true log.Log(WARN, "toolkitPanic() unload here aplug.dead =", aplug.dead) // me.rootNode.CloseToolkit(aplug.name) // panic("panic trapped!") // log.Sleep(.5) // me.rootNode.LoadToolkit("gocui") } } // log.UnsetTmp() // StandardExit() log.Log(WARN, "toolkitPanic() attempt to load nocui") me.rootNode.LoadToolkit("nocui") } func crash(r any, what string) { log.Warn("PANIC ecovered in ", r, what) UnloadToolkits() log.Warn("PANIC ecovered in before n.Custom()", r, what) panic(what) } func watchCallback() { defer func() { if r := recover(); r != nil { crash(r, "watchCallback()") } }() log.Log(INFO, "guiChan() START") for { log.Log(CHANGE, "guiChan() select restarted") select { case a := <-me.guiChan: pluginCounter(&a) if a.ActionType == widget.ToolkitPanic { toolkitPanic(a.ProgName) break } // 99.9% of events are just widget changes n := me.rootNode.findId(a.WidgetId) if n != nil { log.Log(INFO, "guiChan() FOUND widget id =", n.id, n.progname) n.gotUserEvent(a) break } // if not a widget change, something more bizare if a.ActionType == widget.UserQuit { log.Log(WARN, "guiChan() User sent Quit()") log.Exit("wit/gui toolkit.UserQuit") break } if a.ActionType == widget.ToolkitLoad { newPlug := widget.GetString(a.Value) log.Log(WARN, "Attempt to load a new toolkit", newPlug, "here") me.rootNode.LoadToolkit(newPlug) } if a.ActionType == widget.EnableDebug { log.Log(WARN, "guiChan() Enable Debugging Window") log.Log(WARN, "guiChan() TODO: not implemented") log.Log(WARN, "guiChan() Listing Toolkits:") PLUG.SetBool(true) me.rootNode.ListToolkits() me.rootNode.ListChildren(true) /* for i, aplug := range allPlugins { log.Log("plug =", i, aplug.name) if aplug.name == "andlabs" { log.Log("Found plug =", i, aplug.name) //closePlugin(aplug) allPlugins = allPlugins[1:] } } */ break } log.Log(WARN, "guiChan() n == nil is widget id in a table?", a.ActionType, a.WidgetId) log.Log(WARN, "guiChan() todo: list tables here") log.Log(WARN, "guiChan() Action could not be found or handled", a.ActionType, a) } } } // stores the value returned then // spawns the user defined custom routine // hopefully everything is designed smart enough // that it doesn't matter what happens outside of here // TODO: implement throttling someday func (n *Node) gotUserEvent(a widget.Action) { defer func() { if r := recover(); r != nil { crash(r, "watchCallback()") } }() log.Log(CHANGE, "gotUserEvent() received event node =", n.id, n.progname, a.Value) if !n.IsEnabled() { log.Log(WARN, "ignoring plugin event for a disabled widget", n.id, n.progname, a.Value) log.Log(WARN, "this can't be fixed until switching go.wit.com/gui to protobuf") return } if ok, pb, w := n.isWidgetInTable(a.WidgetId); ok { // log.Log(WARN, "gui.gotUserEvent() action =", a) // log.Log(WARN, "gui.gotUserEvent() widget is in pb table", n.id, n.progname) if w == nil { log.Log(WARN, "gui.gotUserEvent() widget == nil", n.id, n.progname) return } log.Log(WARN, "gui.gotUserEvent() found widget in pb table", pb.GetUuid(), w) pb.Custom(w) return } else { log.Log(INFO, "gui.gotUserEvent() widget is not in pb table", n.id, n.progname) } switch n.WidgetType { case widget.Dropdown: // n.checked = a.State.Checked // TODO: do this and/or time to switch to protobuf n.currentS = a.State.CurrentS case widget.Combobox: // n.checked = a.State.Checked // TODO: do this and/or time to switch to protobuf n.currentS = a.State.CurrentS case widget.Checkbox: // n.checked = a.State.Checked // TODO: do this and/or time to switch to protobuf n.checked = a.State.Checked default: } n.currentS = a.State.CurrentS log.Log(CHANGE, "gotUserEvent() n.Bool() =", n.Bool(), "n.String() =", n.String()) if n.Custom == nil { log.Log(CHANGE, "a Custom() function was not set for this widget") return } go something(n) } func something(n *Node) { defer func() { if r := recover(); r != nil { crash(r, "watchCallback()") } }() n.Custom() } // There should only be one of these per application // This is due to restrictions by being cross platform // some toolkit's on some operating systems don't support more than one // Keep things simple. Do the default expected thing whenever possible func New() *Node { /* if argGui.GuiStdout { log.Log(WARN, "--gui-stdout doesn't work yet") // trapStdout() } */ if argGui.GuiPluginHack != "" { // does os.Exec() and does not return testPluginAndExit() } if argGui.GuiVerbose { INFO.SetBool(true) } initNew() return me.rootNode } func NoGui() bool { return argGui.NoGui } // try to load andlabs, if that doesn't work, fall back to the console func (n *Node) Default() *Node { var err error // used to check if plugins load or not if argGui.GuiPluginHack != "" { // does os.Exec() and does not return testPluginAndExit() os.Exit(0) } if argGui.NoGui { log.Log(WARN, "--no-gui chill Winston. I don't need no gui") return n } if argGui.GuiPlugin != "" { log.Log(WARN, "New.Default() try toolkit =", argGui.GuiPlugin) if n, err = n.LoadToolkit(argGui.GuiPlugin); err == nil { return n } log.Log(WARN, "LoadToolkit() failed for =", argGui.GuiPlugin) return nil } if me.appPlugin != "" { if n, err = n.LoadToolkit(me.appPlugin); err == nil { return n } } // if DISPLAY isn't set, return since gtk can't load // TODO: figure out how to check what to do in macos and mswindows if os.Getenv("DISPLAY") == "" { log.Log(WARN, "gui.Default() DISPLAY not set") log.Log(WARN, "gui.Default() attempting ncurses gui") if n, err = n.LoadToolkit("gocui"); err == nil { return n } return nil } if n, err = n.LoadToolkit("andlabs"); err == nil { return n } log.Log(WARN, "LoadToolkit() failed to load andlabs") if n, err = n.LoadToolkit("gocui"); err == nil { return n } log.Log(WARN, "LoadToolkit() failed to load gocui") log.Log(WARN, "") log.Log(WARN, "### Error ####") log.Log(WARN, "The GUI golang plugins did not load.") log.Log(WARN, "You will have to rebuild them") log.Log(WARN, "go-clone go.wit.com/toolkits//") log.Log(WARN, "TODO: try to rebuild them here") log.Log(WARN, "TODO: falling back to STDIN interface") log.Log(WARN, "### Error ####") log.Log(WARN, "") log.Log(WARN, "sleep 2 seconds") log.Sleep(2) if n, err = n.LoadToolkit("nocui"); err == nil { return n } log.Log(WARN, "LoadToolkit() failed to load nocui") return n } // The window is destroyed but the application does not quit func (n *Node) StandardClose() { log.Log(INFO, "wit/gui Standard Window Close. name =", n.progname) log.Log(INFO, "wit/gui Standard Window Close. n.Custom exit =", n.Custom) } // The window is destroyed and the application exits // TODO: properly exit the plugin since Quit() doesn't do it func StandardExit() { log.Log(INFO, "wit/gui Standard Window Exit. running os.Exit()") log.Log(INFO, "StandardExit() attempt to exit each toolkit plugin") for i, plug := range allPlugins { log.Log(INFO, "NewButton()", i, plug) } log.Exit(0) } // The window is destroyed and the application exits // TODO: properly exit the plugin since Quit() doesn't do it func (n *Node) StandardExit() { log.Log(INFO, "wit/gui Standard Window Exit. running os.Exit()") log.Log(INFO, "StandardExit() attempt to exit each toolkit plugin") for i, plug := range allPlugins { log.Log(INFO, "NewButton()", i, plug) n.CloseToolkit(plug.name) } log.Exit(0) } var origStdout *os.File var origStderr *os.File var guioutf *os.File var guierrf *os.File // THIS DOES NOT WORK. // this needs to work like screen. somehow make pseudo tty's or something // to correctly isolate and trap STDOUT and STDERR so the gocui can work func trapStdout() { // attempts to control STDOUT var err error log.Log(WARN, "trapStdout() START") guioutf, err = os.OpenFile("/tmp/go-gui.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Error(err, "error opening /tmp/go-gui.log") os.Exit(0) } origStdout = os.Stdout os.Stdout = guioutf // defer guioutf.Close() // setOutput(outf) // log("This is a test log entry") guierrf, err = os.OpenFile("/tmp/go-gui.err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) if err != nil { log.Error(err, "error opening /tmp/go-gui.err") os.Exit(0) } // defer guierrf.Close() origStderr = os.Stderr os.Stderr = guierrf // maybe all this should be deleted now? println("TEST println") println("TEST println") println("TEST println") // maybe test fmt? or, maybe all this should be deleted now? // fmt.Println("TEST fmt") // fmt.Println("TEST fmt") log.Info("TEST log") log.Info("TEST log") log.Info("TEST log") // defer guioutf.Close() } // sets the applications requested plugin // the command line arguements override this func (n *Node) SetAppDefaultPlugin(plugname string) { me.appPlugin = plugname }