364 lines
9.8 KiB
Go
364 lines
9.8 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
|
|
|
|
// startup whatever might need to be setup in the plugin
|
|
Init func()
|
|
|
|
// This passes the go channel to the plugin
|
|
// the plugin then passes actions back to
|
|
// here where they are processed. If you wit/gui this is how
|
|
// you are passed information like a user clicking a button
|
|
// or a user changing a dropdown menu or a checkbox
|
|
//
|
|
// from this channel, the information is then passed into your
|
|
// Custom() function
|
|
//
|
|
// the custom functions are run from inside of your own goroutine
|
|
// where you initialize the gui
|
|
Callback func(chan toolkit.Action)
|
|
|
|
// This is how actions are sent to the plugin
|
|
//
|
|
// for example, when you you create a new button, it sends
|
|
// a structure to the goroutine that is handling the gui
|
|
// each toolkit has it's own goroutine and each one is sent this
|
|
// add button request
|
|
pluginChan chan toolkit.Action
|
|
PluginChannel func() chan toolkit.Action
|
|
|
|
// deprecate all this
|
|
// TODO: make Main() main() and never allow the user to call it
|
|
// run plugin.Main() when the plugin is loaded
|
|
// Main func(func ()) // this never returns. Each plugin must have it's own goroutine
|
|
// Quit func()
|
|
}
|
|
|
|
var allPlugins []*aplug
|
|
|
|
// loads and initializes a toolkit (andlabs/ui, gocui, etc)
|
|
// attempts to locate the .so file
|
|
func FindPlugin(name string) *aplug {
|
|
var newPlug *aplug
|
|
newPlug = new(aplug)
|
|
|
|
log(logInfo, "FindPlugin() START")
|
|
newPlug.InitOk = false
|
|
|
|
for _, aplug := range allPlugins {
|
|
log(debugGui, "FindPlugin() already loaded toolkit plugin =", aplug.name)
|
|
if (aplug.name == name) {
|
|
log(debugError, "FindPlugin() SKIPPING", name, "as you can't load it twice")
|
|
return aplug
|
|
}
|
|
}
|
|
|
|
// locate the shared library file
|
|
filename := name + ".so"
|
|
loadPlugin(newPlug, filename)
|
|
if (newPlug.plug == nil) {
|
|
log(true, "attempt to find plugin", filename, "failed")
|
|
return nil
|
|
}
|
|
// newPlug.Ok = true
|
|
newPlug.name = name
|
|
|
|
// deprecate Init(?)
|
|
newPlug.Init = loadFuncE(newPlug, "Init")
|
|
|
|
// should make a goroutine that never exits
|
|
// newPlug.Main = loadFuncF(newPlug, "Main")
|
|
|
|
// should send things to the goroutine above
|
|
// newPlug.Queue = loadFuncF(&newPlug, "Queue")
|
|
|
|
// unload the plugin and restore state
|
|
// newPlug.Quit = loadFuncE(newPlug, "Quit")
|
|
|
|
// Sends instructions like "Add", "Delete", "Disable", etc
|
|
// Sends a widget (button, checkbox, etc) and it's parent widget
|
|
// newPlug.Action = loadFuncA(newPlug, "Action")
|
|
|
|
// this tells the toolkit plugin how to send user events back to us
|
|
// for things like: the user clicked on the 'Check IPv6'
|
|
newPlug.Callback = sendCallback(newPlug, "Callback")
|
|
|
|
// this let's us know where to send requests to the toolkit
|
|
// for things like: add a new button called 'Check IPv6'
|
|
newPlug.PluginChannel = getPluginChannel(newPlug, "PluginChannel")
|
|
|
|
allPlugins = append(allPlugins, newPlug)
|
|
|
|
log(debugPlugin, "FindPlugin() END", newPlug.name, filename)
|
|
newPlug.Init()
|
|
|
|
// set the communication to the plugins
|
|
newPlug.pluginChan = newPlug.PluginChannel()
|
|
newPlug.Callback(Config.guiChan)
|
|
|
|
newPlug.InitOk = true
|
|
|
|
sleep(1) // temp hack until chan communication is setup
|
|
|
|
// TODO: find a new way to do this that is locking, safe and accurate
|
|
Config.rootNode.redraw(newPlug)
|
|
|
|
return newPlug
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// newPlug.PluginChannel = getPluginChannel(newPlug, "PluginChannel")
|
|
func getPluginChannel(p *aplug, funcName string) func() chan toolkit.Action {
|
|
var newfunc func() chan toolkit.Action
|
|
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() chan toolkit.Action)
|
|
if !ok {
|
|
log(debugGui, "function name =", funcName, "names didn't map correctly. Fix the plugin name =", p.name)
|
|
return nil
|
|
}
|
|
return newfunc
|
|
}
|
|
|
|
func sendCallback(p *aplug, funcName string) func(chan toolkit.Action) {
|
|
var newfunc func(chan toolkit.Action)
|
|
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(chan toolkit.Action))
|
|
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
|
|
}
|
|
|
|
// does this fix loadFuncE problems?
|
|
// TODO: still need to move to channels here
|
|
func loadFuncA(p *aplug, funcName string) func(*toolkit.Action) {
|
|
var newfunc func(*toolkit.Action)
|
|
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.Action))
|
|
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 {
|
|
log(logError, "loadPlugin() error. exiting here?")
|
|
return
|
|
}
|
|
|
|
// 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)
|
|
log(true, "loading plugin", filename, "worked")
|
|
return plug
|
|
}
|
|
|
|
// 2023/04/06 Queue() is also being used and channels are being used. memcopy() only
|
|
func newaction(a *toolkit.Action, n *Node, where *Node) {
|
|
if (n != nil) {
|
|
a.WidgetId = n.id
|
|
a.WidgetType = n.widget.Type
|
|
a.ActionType = a.ActionType
|
|
}
|
|
|
|
// TODO: redo this grid logic
|
|
if (where != nil) {
|
|
log(logInfo, "Action() START on where X,Y, Next X,Y =", where.Name, where.X, where.Y, where.NextX, where.NextY)
|
|
a.ParentId = where.id
|
|
switch where.widget.Type {
|
|
case toolkit.Grid:
|
|
// where.Dump(true)
|
|
log(logInfo, "Action() START on Grid (X,Y)", where.X, where.Y, "put next thing at (X,Y) =", where.NextX, where.NextY)
|
|
//
|
|
// fix values here if they are invalid. Index starts at 1
|
|
if (where.NextX < 1) {
|
|
where.NextX = 1
|
|
}
|
|
if (where.NextY < 1) {
|
|
where.NextY = 1
|
|
}
|
|
//
|
|
a.X = where.NextX
|
|
a.Y = where.NextY
|
|
log(logInfo, "Action() END on Grid (X,Y)", where.X, where.Y, "put next thing at (X,Y) =", where.NextX, where.NextY)
|
|
default:
|
|
}
|
|
}
|
|
|
|
for _, aplug := range allPlugins {
|
|
log(debugPlugin, "Action() aplug =", aplug.name, "Action type=", a.ActionType)
|
|
if (aplug.pluginChan == nil) {
|
|
log(logInfo, "Action() retrieving the aplug.PluginChannel()", aplug.name)
|
|
aplug.pluginChan = aplug.PluginChannel()
|
|
log(logInfo, "Action() retrieved", aplug.pluginChan)
|
|
}
|
|
log(logInfo, "Action() SEND to pluginChan", aplug.name)
|
|
aplug.pluginChan <- *a
|
|
sleep(.02)
|
|
}
|
|
// increment where to put the next widget in a grid or table
|
|
if (where != nil) {
|
|
switch where.widget.Type {
|
|
case toolkit.Grid:
|
|
log(logInfo, "Action() START size (X,Y)", where.X, where.Y, "put next thing at (X,Y) =", where.NextX, where.NextY)
|
|
where.NextY += 1
|
|
if (where.NextY > where.Y) {
|
|
where.NextX += 1
|
|
where.NextY = 1
|
|
}
|
|
log(logInfo, "Action() END size (X,Y)", where.X, where.Y, "put next thing at (X,Y) =", where.NextX, where.NextY)
|
|
default:
|
|
}
|
|
}
|
|
}
|