new-gui/plugin.go

278 lines
6.9 KiB
Go

package gui
// This is based off of the excellent example and documentation here:
// https://github.com/vladimirvivien/go-plugin-example
// There truly are great people in this world.
// It's a pleasure to be here with all of you
import (
"os"
"plugin"
"git.wit.org/wit/gui/toolkit"
)
var err error
type Symbol any
type aplug struct {
// Ok bool
name string
filename string
plug *plugin.Plugin
sym *plugin.Symbol
LoadOk bool
InitOk bool
MainOk bool
Init func()
Main func(func ())
Queue func(func ())
Quit func()
NewWindow func(*toolkit.Widget)
NewButton func(*toolkit.Widget, *toolkit.Widget)
NewGroup func(*toolkit.Widget, *toolkit.Widget)
NewCheckbox func(*toolkit.Widget, *toolkit.Widget)
NewTab func(*toolkit.Widget, *toolkit.Widget)
NewLabel func(*toolkit.Widget, *toolkit.Widget)
NewTextbox func(*toolkit.Widget, *toolkit.Widget)
NewSlider func(*toolkit.Widget, *toolkit.Widget)
NewSpinner func(*toolkit.Widget, *toolkit.Widget)
NewDropdown func(*toolkit.Widget, *toolkit.Widget)
AddDropdownName func(*toolkit.Widget, string)
SetDebugToolkit func(bool)
SetDebugChange func(bool)
ShowDebug func()
}
var allPlugins []*aplug
// loads and initializes a toolkit (andlabs/ui, gocui, etc)
func LoadToolkit(name string) bool {
var newPlug aplug
log(debugGui, "gui.LoadToolkit() START")
newPlug.LoadOk = false
for _, aplug := range allPlugins {
log(debugGui, "gui.LoadToolkit() already loaded toolkit plugin =", aplug.name)
if (aplug.name == name) {
log(debugGui, "gui.LoadToolkit() SKIPPING")
return true
}
}
// locate the shared library file
filename := name + ".so"
loadPlugin(&newPlug, filename)
if (newPlug.plug == nil) {
return false
}
// newPlug.Ok = true
newPlug.name = name
// map all the functions
newPlug.Init = loadFuncE(&newPlug, "Init")
newPlug.Quit = loadFuncE(&newPlug, "Quit")
// this should be laodFuncE()
newPlug.Main = loadFuncF(&newPlug, "Main")
newPlug.Queue = loadFuncF(&newPlug, "Queue")
newPlug.NewWindow = loadFunc1(&newPlug, "NewWindow")
newPlug.NewButton = loadFunc2(&newPlug, "NewButton")
newPlug.NewGroup = loadFunc2(&newPlug, "NewGroup")
newPlug.NewCheckbox = loadFunc2(&newPlug, "NewCheckbox")
newPlug.NewTab = loadFunc2(&newPlug, "NewTab")
newPlug.NewLabel = loadFunc2(&newPlug, "NewLabel")
newPlug.NewTextbox = loadFunc2(&newPlug, "NewTextbox")
newPlug.NewSlider = loadFunc2(&newPlug, "NewSlider")
newPlug.NewSpinner = loadFunc2(&newPlug, "NewSpinner")
newPlug.NewDropdown = loadFunc2(&newPlug, "NewDropdown")
newPlug.AddDropdownName = loadFuncS(&newPlug, "AddDropdownName")
newPlug.SetDebugToolkit = loadFuncB(&newPlug, "SetDebugToolkit")
newPlug.SetDebugChange = loadFuncB(&newPlug, "SetDebugChange")
newPlug.ShowDebug = loadFuncE(&newPlug, "ShowDebug")
allPlugins = append(allPlugins, &newPlug)
log(debugGui, "gui.LoadToolkit() END", newPlug.name, filename)
newPlug.Init()
newPlug.LoadOk = true
return true
}
// TODO: All these functions need to be done a smarter way
// but I haven't worked out the golang syntax to make it smarter
func loadFuncE(p *aplug, funcName string) func() {
var newfunc func()
var ok bool
var test plugin.Symbol
test, err = p.plug.Lookup(funcName)
if err != nil {
log(debugGui, "DID NOT FIND: name =", test, "err =", err)
return nil
}
newfunc, ok = test.(func())
if !ok {
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
return nil
}
return newfunc
}
func loadFunc1(p *aplug, funcName string) func(*toolkit.Widget) {
var newfunc func(*toolkit.Widget)
var ok bool
var test plugin.Symbol
test, err = p.plug.Lookup(funcName)
if err != nil {
log(debugGui, "DID NOT FIND: name =", test, "err =", err)
return nil
}
newfunc, ok = test.(func(*toolkit.Widget))
if !ok {
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
return nil
}
return newfunc
}
func loadFuncS(p *aplug, funcName string) func(*toolkit.Widget, string) {
var newfunc func(*toolkit.Widget, string)
var ok bool
var test plugin.Symbol
test, err = p.plug.Lookup(funcName)
if err != nil {
log(debugGui, "DID NOT FIND: name =", test, "err =", err)
return nil
}
newfunc, ok = test.(func(*toolkit.Widget, string))
if !ok {
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
return nil
}
return newfunc
}
func loadFuncB(p *aplug, funcName string) func(bool) {
var newfunc func(bool)
var ok bool
var test plugin.Symbol
test, err = p.plug.Lookup(funcName)
if err != nil {
log(debugGui, "DID NOT FIND: name =", test, "err =", err)
return nil
}
newfunc, ok = test.(func(bool))
if !ok {
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
return nil
}
return newfunc
}
func loadFunc2(p *aplug, funcName string) func(*toolkit.Widget, *toolkit.Widget) {
var newfunc func(*toolkit.Widget, *toolkit.Widget)
var ok bool
var test plugin.Symbol
test, err = p.plug.Lookup(funcName)
if err != nil {
log(debugGui, "DID NOT FIND: name =", test, "err =", err)
return nil
}
newfunc, ok = test.(func(*toolkit.Widget, *toolkit.Widget))
if !ok {
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
return nil
}
return newfunc
}
// This is probably dangerous and should never be done
// executing arbitrary functions will cause them to run inside the goroutine that
// the GUI toolkit itself is running in. TODO: move to channels here
func loadFuncF(p *aplug, funcName string) func(func ()) {
var newfunc func(func ())
var ok bool
var test plugin.Symbol
test, err = p.plug.Lookup(funcName)
if err != nil {
log(debugGui, "DID NOT FIND: name =", test, "err =", err)
return nil
}
newfunc, ok = test.(func(func ()))
if !ok {
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
return nil
}
return newfunc
}
/*
This searches in the following order for the plugin .so files:
./toolkit/
~/go/src/go.wit.org/gui/toolkit/
/usr/lib/go-gui/
*/
func loadPlugin(p *aplug, name string) {
var filename string
homeDir, err := os.UserHomeDir()
if err != nil {
exit(err)
}
// attempt to write out the file from the internal resource
filename = "toolkit/" + name
p.plug = loadfile(filename)
if (p.plug != nil) {
p.filename = filename
return
}
filename = homeDir + "/go/src/git.wit.org/wit/gui/toolkit/" + name
p.plug = loadfile(filename)
if (p.plug != nil) {
p.filename = filename
return
}
filename = "/usr/lib/go-gui/" + name
p.plug = loadfile(filename)
if (p.plug != nil) {
p.filename = filename
return
}
return
}
// load module
// 1. open the shared object file to load the symbols
func loadfile(filename string) *plugin.Plugin {
plug, err := plugin.Open(filename)
if err != nil {
log(debugGui, "plugin FAILED =", filename, err)
return nil
}
log(debugGui, "plugin WORKED =", filename)
return plug
}