Re-implemented removed functionality in Nasin
We also have a plugin system now :3
This commit is contained in:
parent
363779a947
commit
39b8b96513
79
nasin/application.go
Normal file
79
nasin/application.go
Normal file
@ -0,0 +1,79 @@
|
||||
package nasin
|
||||
|
||||
import "image"
|
||||
import "errors"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
|
||||
// Application is
|
||||
type Application interface {
|
||||
Name () string
|
||||
Init () error
|
||||
}
|
||||
|
||||
// Run runs the given application. This function will block until the
|
||||
// application exits.
|
||||
func Run (application Application) {
|
||||
loadPlugins()
|
||||
|
||||
backend, err := instantiateBackend()
|
||||
if err != nil {
|
||||
println("nasin: cannot start application:", err.Error())
|
||||
return
|
||||
}
|
||||
tomo.SetBackend(backend)
|
||||
|
||||
if application == nil { panic("nasin: nil application") }
|
||||
err = application.Init()
|
||||
if err != nil {
|
||||
println("nasin: backend exited with error:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = backend.Run()
|
||||
if err != nil {
|
||||
println("nasin: backend exited with error:", err.Error())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stop stops the event loop
|
||||
func Stop () {
|
||||
assertBackend()
|
||||
tomo.GetBackend().Stop()
|
||||
}
|
||||
|
||||
// Do executes the specified callback within the main thread as soon as
|
||||
// possible.
|
||||
func Do (callback func()) {
|
||||
assertBackend()
|
||||
tomo.GetBackend().Do(callback)
|
||||
}
|
||||
|
||||
// NewWindow creates a new window within the specified bounding rectangle. The
|
||||
// position on screen may be overridden by the backend or operating system.
|
||||
func NewWindow (bounds image.Rectangle) (tomo.MainWindow, error) {
|
||||
assertBackend()
|
||||
return tomo.GetBackend().NewWindow(bounds)
|
||||
}
|
||||
|
||||
func assertBackend () {
|
||||
if tomo.GetBackend() == nil {
|
||||
panic("nasin: no running tomo backend")
|
||||
}
|
||||
}
|
||||
|
||||
func instantiateBackend () (backend tomo.Backend, err error) {
|
||||
// find a suitable backend
|
||||
for _, factory := range factories {
|
||||
backend, err = factory()
|
||||
if err == nil && backend != nil { return }
|
||||
}
|
||||
|
||||
// if none were found, but there was no error produced, produce an error
|
||||
if err == nil {
|
||||
return nil, errors.New("no available tomo backends")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
3
nasin/doc.go
Normal file
3
nasin/doc.go
Normal file
@ -0,0 +1,3 @@
|
||||
// Package nasin provides a high-level framework for Tomo applications. Nasin
|
||||
// also automatically handles themes, backend instantiation, and plugin support.
|
||||
package nasin
|
119
nasin/plugin.go
Normal file
119
nasin/plugin.go
Normal file
@ -0,0 +1,119 @@
|
||||
package nasin
|
||||
|
||||
import "os"
|
||||
import "fmt"
|
||||
// TODO: possibly fork the official plugin module and add support for other
|
||||
// operating systems? perhaps enhance the Lookup function with
|
||||
// the generic extract function we have here for extra type safety goodness.
|
||||
import "plugin"
|
||||
import "strings"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
|
||||
type expectsFunc func () (int, int, int)
|
||||
type nameFunc func () string
|
||||
type descriptionFunc func () string
|
||||
type backendFactory func () (tomo.Backend, error)
|
||||
type themeFactory func () tomo.Theme
|
||||
|
||||
var factories []backendFactory
|
||||
var theme tomo.Theme
|
||||
|
||||
func loadPlugins () {
|
||||
// TODO: do not hardcode all these paths here, have separate files that
|
||||
// build on different platforms that set these paths.
|
||||
|
||||
pathVariable := os.Getenv("NASIN_PLUGIN_PATH")
|
||||
paths := strings.Split(pathVariable, ":")
|
||||
paths = append (
|
||||
paths,
|
||||
"/usr/lib/nasin/plugins",
|
||||
"/usr/local/lib/nasin/plugins")
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
paths = append (
|
||||
paths,
|
||||
filepath.Join(homeDir, ".local/lib/nasin/plugins"))
|
||||
}
|
||||
|
||||
for _, dir := range paths {
|
||||
loadPluginsFrom(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func loadPluginsFrom (dir string) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
// its no big deal if one of the dirs doesn't exist
|
||||
if err != nil { return }
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() { continue }
|
||||
if filepath.Ext(entry.Name()) != ".so" { continue }
|
||||
pluginPath := filepath.Join(dir, entry.Name())
|
||||
loadPlugin(pluginPath)
|
||||
}
|
||||
}
|
||||
|
||||
func loadPlugin (path string) {
|
||||
die := func (reason string) {
|
||||
println (
|
||||
"nasin: could not load plugin at ",
|
||||
path + ":", reason)
|
||||
}
|
||||
|
||||
plugin, err := plugin.Open(path)
|
||||
if err != nil {
|
||||
die(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// check for and obtain basic plugin functions
|
||||
expects, ok := extract[expectsFunc](plugin, "Expects")
|
||||
if !ok { die("does not implement Expects() (int, int, int)"); return }
|
||||
name, ok := extract[nameFunc](plugin, "Name")
|
||||
if !ok { die("does not implement Name() string"); return }
|
||||
_, ok = extract[descriptionFunc](plugin, "Description")
|
||||
if !ok { die("does not implement Description() string"); return }
|
||||
|
||||
// check for version compatibility
|
||||
// TODO: have exported version type in tomo base package, and have a
|
||||
// function within that that gives the current tomo/nasin version. call
|
||||
// that here.
|
||||
major, minor, patch := expects()
|
||||
currentVersion := version { 0, 0, 0 }
|
||||
pluginVersion := version { major, minor, patch }
|
||||
if !pluginVersion.CompatibleABI(currentVersion) {
|
||||
die (
|
||||
"plugin (" + pluginVersion.String() +
|
||||
") incompatible with nasin/tomo version (" +
|
||||
currentVersion.String() + ")")
|
||||
return
|
||||
}
|
||||
|
||||
// if it's a backend plugin...
|
||||
newBackend, ok := extract[backendFactory](plugin, "NewBackend")
|
||||
if ok { factories = append(factories, newBackend) }
|
||||
|
||||
// if it's a theme plugin...
|
||||
newTheme, ok := extract[themeFactory](plugin, "NewTheme")
|
||||
if ok { theme = newTheme() }
|
||||
|
||||
println("nasin: loaded plugin", name())
|
||||
}
|
||||
|
||||
func extract[T any] (plugin *plugin.Plugin, name string) (value T, ok bool) {
|
||||
symbol, err := plugin.Lookup(name)
|
||||
if err != nil { return }
|
||||
value, ok = symbol.(T)
|
||||
return
|
||||
}
|
||||
|
||||
type version [3]int
|
||||
|
||||
func (version version) CompatibleABI (other version) bool {
|
||||
return version[0] == other[0] && version[1] == other[1]
|
||||
}
|
||||
|
||||
func (version version) String () string {
|
||||
return fmt.Sprint(version[0], ".", version[1], ".", version[2])
|
||||
}
|
Reference in New Issue
Block a user