// 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" "fmt" "os" "runtime" "runtime/debug" "runtime/pprof" "time" "github.com/awesome-gocui/gocui" "go.wit.com/log" ) // sent via -ldflags var VERSION string var BUILDTIME string var PLUGIN string = "gocui" func toolkitClose() { me.baseGui.Close() } // sets defaults and establishes communication // to this toolkit from the wit/gui golang package func initPlugin() { defer func() { if r := recover(); r != nil { fmt.Fprintf(outf, "PANIC: initPlugin() recovered %v\n", r) return } }() var err error // todo: make this a tmp file that goes away 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) } me.starttime = time.Now() log.Log(INFO, "Init() of awesome-gocui") // init the config struct default values Set(&me, "default") // todo: some early output still goes to the /tmp/ file //time.Sleep(200 * time.Millisecond) log.CaptureMode(me.stdout) // 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 Set(&me.dropdown, "default") // s := fmt.Sprintln("fake default check =", me.FakeW, "dropdown.Id", me.dropdown.Id) // me.stdout.Write([]byte(s)) me.mouse.mouseUp = true me.mouse.clicktime = time.Millisecond * 200 me.mouse.doubletime = time.Millisecond * 400 me.myTree = initTree() me.newWindowTrigger = make(chan *guiWidget, 1) go newWindowTrigger() 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 } } log.Log(NOW, "Init() start pluginChan") os.Stdout = outf log.CaptureMode(outf) // init gocui g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { return } 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) time.Sleep(100 * time.Millisecond) fmt.Fprintln(outf, "hello world", time.Since(me.starttime)) // coreStdout() // createStdout(g) // tell 'tree' that we are okay to start talking to me.myTree.InitOK() me.ok = true // this tells init() it's okay to work with gocui go gocuiMain() } // This goroutine sits in gocui's MainLoop() func gocuiMain() { defer func() { if r := recover(); r != nil { log.Warn("PANIC ecovered in gocuiMain()", r) fmt.Fprintf(outf, "PANIC recovered in r = %v", r) os.Stderr = outf os.Stdout = outf debug.PrintStack() pprof.Lookup("goroutine").WriteTo(outf, 1) panic(outf) } }() // me.stdout.Write([]byte("begin gogui.MainLoop()\n")) if err := me.baseGui.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) { log.Log(NOW, "g.MainLoop() panic err =", err) // normally panic here panic("gocuiTKmainloop OOPS") } } 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()") } func main() { } // this hack is to wait for the application to send something // before trying to do anything. todo: rethink this someday func waitOK() { defer func() { if r := recover(); r != nil { fmt.Fprintln(outf, "INIT PLUGIN recovered in r", r) return } }() for { if me.ok { return } time.Sleep(10 * time.Millisecond) } } // this hack is to wait for the application to send something // before trying to do anything. todo: rethink this someday func waitFirstWindow() { for { if me.firstWindowOk { 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() { defer func() { if r := recover(); r != nil { fmt.Fprintln(outf, "INIT PLUGIN recovered in r", r) return } }() 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 { // artificially pause clock while dragging. // this is a reminder to make this refresh code smarter // after the switch to protocol buffers if me.mouse.mouseUp { // log.Info("refresh now on mouseUp") // todo: add logic here to see if the application has changed anything // me.baseGui.UpdateAsync(testRefresh) // probably don't need this 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) } } lastRefresh = time.Now() } else { me.baseGui.Update(testRefresh) if time.Since(lastRefresh) > 3*time.Second { 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) } } 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() time.Sleep(200 * time.Millisecond) redoWindows(3, 3) me.firstWindowOk = true if !me.stdout.init { me.stdout.init = true relocateStdoutOffscreen() } if me.textbox.tk == nil { initTextbox() me.textbox.tk.prepTextbox() } tk.makeWindowActive() } } }