// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 // Copyright 2014 The gocui Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "errors" "os" "runtime" "runtime/debug" "time" "github.com/awesome-gocui/gocui" "go.wit.com/log" "go.wit.com/toolkits/tree" ) // sent via -ldflags var VERSION string var BUILDTIME string func queueToolkitClose() { me.baseGui.Close() } func queueSetChecked(n *tree.Node, b bool) { setChecked(n, b) } // sets defaults and establishes communication // to this toolkit from the wit/gui golang package func init() { log.Log(INFO, "Init() of awesome-gocui") // init the config struct default values Set(&me, "default") // initial stdout window settings me.stdout.w = 180 me.stdout.h = 40 me.stdout.lastW = 4 me.stdout.lastH = 20 // just make up unique values for these me.dropdown.wId = -77 me.textbox.wId = -55 me.stdout.wId = -4 me.clock.wId = -5 me.mouse.mouseUp = true me.mouse.clicktime = time.Millisecond * 100 me.mouse.doubletime = time.Millisecond * 300 me.myTree = tree.New() me.myTree.PluginName = "gocui" go refreshGocui() // read in defaults from config protobuf if val, err := me.myTree.ConfigFind("stdout"); err == nil { if val == "true" { me.stdout.startOnscreen = true me.stdout.Write([]byte("starting with stdout onscreen\n")) } else { me.stdout.Write([]byte("starting with stdout offscreen\n")) } } if val, err := me.myTree.ConfigFind("dark"); err == nil { if val == "true" { me.dark = true } } else { // macos iterm2 really only works with dark mode right now if runtime.GOOS == "macos" { me.dark = true } } me.myTree.NodeAction = newaction me.myTree.Add = newAdd me.myTree.SetTitle = newSetTitle me.myTree.SetLabel = newSetLabel me.myTree.SetText = newSetText me.myTree.AddText = newAddText me.myTree.SetChecked = queueSetChecked me.myTree.ToolkitClose = queueToolkitClose me.newWindowTrigger = make(chan *guiWidget, 1) go newWindowTrigger() log.Log(NOW, "Init() start pluginChan") // log.Sleep(.1) // probably not needed, but in here for now under development go mainGogui() // log.Sleep(.1) // probably not needed, but in here for now under development } func standardExit() { log.Log(NOW, "standardExit() doing baseGui.Close()") me.baseGui.Close() log.Log(NOW, "standardExit() doing outf.Close()") outf.Close() // log(true, "standardExit() setOutput(os.Stdout)") // setOutput(os.Stdout) log.Log(NOW, "standardExit() send back Quit()") // go sendBackQuit() // don't stall here in case the // induces a delay in case the callback channel is broken time.Sleep(200 * time.Millisecond) log.Log(NOW, "standardExit() exit()") os.Exit(0) } func standardClose() { log.Log(NOW, "standardExit() doing baseGui.Close()") me.baseGui.Close() log.Log(NOW, "standardExit() doing outf.Close()") outf.Close() // os.Stdin = os.Stdin // os.Stdout = os.Stdout // os.Stderr = os.Stderr log.Log(NOW, "standardExit() send back Quit()") } var outf *os.File func main() { } var origStdout *os.File var origStderr *os.File func mainGogui() { defer func() { if r := recover(); r != nil { log.Warn("YAHOOOO Recovered in guiMain application:", r) log.Warn("Recovered from panic:", r) me.baseGui.Close() log.CaptureMode(nil) log.Warn("YAHOOOO Recovered in guiMain application:", r) log.Warn("Recovered from panic:", r) me.myTree.SendToolkitPanic() return } }() var err error outf, err = os.OpenFile("/tmp/captureMode.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Info("error opening file:", err) os.Exit(0) } origStdout = os.Stdout os.Stdout = outf defer outf.Close() log.CaptureMode(outf) gocuiMain() } // This initializes the gocui package // it runs SetManagerFunc which passes every input // event (keyboard, mouse, etc) to the function "gocuiEvent()" func gocuiMain() { defer func() { if r := recover(); r != nil { log.Warn("YAHOOOO Recovered in gocuiMain()", r) log.Warn("Recovered from panic:", r) me.baseGui.Close() // allow gocui to close if possible, then print stack log.Sleep(1) os.Stdout = origStdout os.Stderr = origStderr me.myTree.SendToolkitPanic() log.Warn("Stack trace:") debug.PrintStack() // panic("BUMMER 2") // attempt to switch to the nocui toolkit log.Sleep(1) me.myTree.SendToolkitLoad("nocui") log.Sleep(3) me.myTree.SendToolkitLoad("nocui") // panic("BUMMER") return } }() g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { return } defer g.Close() me.baseGui = g g.Cursor = true g.Mouse = true // this sets the function that is run on every event. For example: // When you click the mouse, move the mouse, or press a key on the keyboard // This is equivalent to xev or similar to cat /dev/input on linux g.SetManagerFunc(gocuiEvent) // register how the 'gocui' will work as a GO toolkit plugin // all applications will use these keys. they are universal. // registered event handlers still have the events sent to gocuiEvent() above registerHandlers(g) me.stdout.Write([]byte("begin gogui.MainLoop()\n")) if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) { log.Log(NOW, "g.MainLoop() panic err =", err) // normally panic here panic("gocuiTKmainloop OOPS") } } // this hack is to wait for the application to send something // before trying to do anything. todo: rethink this someday func waitOK() { for { if me.ok { return } time.Sleep(10 * time.Millisecond) } } // empty function. this triggers gocui to refresh the screen func testRefresh(*gocui.Gui) error { // log.Info("in testRefresh") return nil } // refresh the screen 10 times a second func refreshGocui() { var lastRefresh time.Time lastRefresh = time.Now() for { time.Sleep(100 * time.Millisecond) // log.Info("refresh checking ok") if !me.ok { continue } if time.Since(lastRefresh) > 1000*time.Millisecond { if me.mouse.mouseUp { // log.Info("refresh now on mouseUp") // todo: add logic here to see if the application has changed anything me.baseGui.Update(testRefresh) if me.clock.tk != nil && !me.showHelp { // also double check the gocui view exists if me.clock.tk.v != nil { me.clock.tk.v.Clear() me.clock.tk.labelN = time.Now().Format("15:04:05") me.clock.tk.v.WriteString(me.clock.tk.labelN) } } } else { // log.Info("refresh skip on mouseDown") // me.baseGui.Update() } lastRefresh = time.Now() } } } // set the widget start width & height func newWindowTrigger() { log.Log(NOW, "newWindowTriggerl() START") for { // log.Log(NOW, "GO plugin toolkit made a new window") select { case tk := <-me.newWindowTrigger: // log.Log(NOW, "newWindowTrigger() got new window", tk.cuiName) time.Sleep(200 * time.Millisecond) waitOK() redoWindows(1, -1) if !me.stdout.init { me.stdout.init = true relocateStdoutOffscreen() } tk.makeWindowActive() // place the new window relative to the mouse tk.redrawWindow(me.mouse.downW+8, me.mouse.downH-2) // tk.redrawWindow(tk.gocuiSize.w0, tk.gocuiSize.h0) setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn // log.Log(NOW, "newWindowTrigger() after sleep") } } }