diff --git a/.gitignore b/.gitignore index 964026bf..cca55468 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ v2/cmd/wails/internal/commands/initialise/templates/testtemplates/ /v3/.task /v3/examples/build/bin/testapp /websitev3/site/ +/v3/examples/plugins/bin/testapp diff --git a/v3/examples/plugins/build/appicon.png b/v3/examples/plugins/build/appicon.png index e69de29b..63617fe4 100644 Binary files a/v3/examples/plugins/build/appicon.png and b/v3/examples/plugins/build/appicon.png differ diff --git a/v3/examples/plugins/go.mod b/v3/examples/plugins/go.mod index 8a2d76a3..dd1ddaac 100644 --- a/v3/examples/plugins/go.mod +++ b/v3/examples/plugins/go.mod @@ -14,6 +14,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect diff --git a/v3/examples/plugins/go.sum b/v3/examples/plugins/go.sum index c16c742b..9477ac48 100644 --- a/v3/examples/plugins/go.sum +++ b/v3/examples/plugins/go.sum @@ -24,6 +24,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= diff --git a/v3/examples/plugins/icon.ico b/v3/examples/plugins/icon.ico new file mode 100644 index 00000000..bfa0690b Binary files /dev/null and b/v3/examples/plugins/icon.ico differ diff --git a/v3/examples/plugins/icons.icns b/v3/examples/plugins/icons.icns new file mode 100644 index 00000000..1b5bd4c8 Binary files /dev/null and b/v3/examples/plugins/icons.icns differ diff --git a/v3/examples/plugins/main.go b/v3/examples/plugins/main.go index a28398eb..d5c0b66c 100644 --- a/v3/examples/plugins/main.go +++ b/v3/examples/plugins/main.go @@ -8,6 +8,7 @@ import ( "github.com/wailsapp/wails/v3/plugins/log" "github.com/wailsapp/wails/v3/plugins/single_instance" "github.com/wailsapp/wails/v3/plugins/sqlite" + "github.com/wailsapp/wails/v3/plugins/start_at_login" "os" "plugin_demo/plugins/hashes" ) @@ -27,7 +28,9 @@ func main() { "hashes": hashes.NewPlugin(), "browser": browser.NewPlugin(), "log": log.NewPlugin(), - "sqlite": sqlite.NewPlugin(&sqlite.Config{}), + "sqlite": sqlite.NewPlugin(&sqlite.Config{ + DBFile: "test.db", + }), "kvstore": kvstore.NewPlugin(&kvstore.Config{ Filename: "store.json", AutoSave: true, @@ -36,6 +39,7 @@ func main() { // When true, the original app will be activated when a second instance is launched ActivateAppOnSubsequentLaunch: true, }), + "start_at_login": start_at_login.NewPlugin(), }, Assets: application.AssetOptions{ FS: assets, diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 7bf850cd..9b22f353 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -67,11 +67,6 @@ func New(appOptions Options) *App { println("Fatal error in application initialisation: ", err.Error()) os.Exit(1) } - err = result.bindings.AddPlugins(appOptions.Plugins) - if err != nil { - println("Fatal error in application initialisation: ", err.Error()) - os.Exit(1) - } result.plugins = NewPluginManager(appOptions.Plugins, srv) err = result.plugins.Init() @@ -80,6 +75,12 @@ func New(appOptions Options) *App { os.Exit(1) } + err = result.bindings.AddPlugins(appOptions.Plugins) + if err != nil { + println("Fatal error in application initialisation: ", err.Error()) + os.Exit(1) + } + return result } diff --git a/v3/pkg/mac/mac.go b/v3/pkg/mac/mac.go new file mode 100644 index 00000000..73e0258b --- /dev/null +++ b/v3/pkg/mac/mac.go @@ -0,0 +1,24 @@ +//go:build darwin + +// Package mac provides a set of functions to interact with the macOS platform. +package mac + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework ServiceManagement + +#import +#import + +// Get the bundle ID +char* getBundleID() { + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + return (char*)[bundleID UTF8String]; +} +*/ +import "C" + +// GetBundleID returns the bundle ID of the application. +func GetBundleID() string { + return C.GoString(C.getBundleID()) +} diff --git a/v3/plugins/sqlite/plugin.go b/v3/plugins/sqlite/plugin.go index 4018b84c..95c5ef52 100644 --- a/v3/plugins/sqlite/plugin.go +++ b/v3/plugins/sqlite/plugin.go @@ -4,12 +4,20 @@ import ( "database/sql" _ "embed" "errors" + "fmt" "github.com/wailsapp/wails/v3/pkg/application" _ "modernc.org/sqlite" + "strings" ) -//go:embed plugin.js -var js string +//go:embed sqlite_close.js +var closejs string + +//go:embed sqlite_open.js +var openjs string + +//go:embed sqlite_execute_select.js +var executeselectjs string // ---------------- Plugin Setup ---------------- // This is the main plugin struct. It can be named anything you like. @@ -28,6 +36,7 @@ type Plugin struct { app *application.App conn *sql.DB callableMethods []string + js string } func NewPlugin(config *Config) *Plugin { @@ -56,18 +65,24 @@ func (p *Plugin) Name() string { func (p *Plugin) Init(app *application.App) error { p.app = app p.callableMethods = []string{"Execute", "Select"} + p.js = executeselectjs if p.config.CanOpenFromJS { p.callableMethods = append(p.callableMethods, "Open") + p.js += openjs } if p.config.CanCloseFromJS { p.callableMethods = append(p.callableMethods, "Close") + p.js += closejs } - if p.config.DBFile != "" { - _, err := p.Open(p.config.DBFile) - if err != nil { - return err - } + if p.config.DBFile == "" { + return errors.New(`no database file specified. Please set DBFile in the config to either a filename or use ":memory:" to use an in-memory database`) } + _, err := p.Open(p.config.DBFile) + if err != nil { + return err + } + + p.js += fmt.Sprintf("\nwindow.sqlite = { %s };", strings.Join(p.callableMethods, ", ")) return nil } @@ -77,7 +92,7 @@ func (p *Plugin) CallableByJS() []string { } func (p *Plugin) InjectJS() string { - return js + return p.js } // ---------------- Plugin Methods ---------------- diff --git a/v3/plugins/sqlite/sqlite_close.js b/v3/plugins/sqlite/sqlite_close.js new file mode 100644 index 00000000..1bf476de --- /dev/null +++ b/v3/plugins/sqlite/sqlite_close.js @@ -0,0 +1,8 @@ + +/** + * Close a sqlite DB. + * @returns {Promise} + */ +function Close() { + return wails.Plugin("sqlite", "Close"); +} diff --git a/v3/plugins/sqlite/plugin.js b/v3/plugins/sqlite/sqlite_execute_select.js similarity index 58% rename from v3/plugins/sqlite/plugin.js rename to v3/plugins/sqlite/sqlite_execute_select.js index c8d04708..0d20cb55 100644 --- a/v3/plugins/sqlite/plugin.js +++ b/v3/plugins/sqlite/sqlite_execute_select.js @@ -1,19 +1,3 @@ -/** - * Open a sqlite DB. - * @param filename {string} - file to open. - * @returns {Promise} - */ -function Open(filename) { - return wails.Plugin("sqlite", "Open", filename); -} - -/** - * Close a sqlite DB. - * @returns {Promise} - */ -function Close() { - return wails.Plugin("sqlite", "Close"); -} /** * Execute a SQL statement. @@ -34,11 +18,3 @@ function Execute(statement, ...args) { function Select(statement, ...args) { return wails.Plugin("sqlite", "Select", statement, ...args); } - -window.sqlite = { - Open: Open, - Close: Close, - Execute: Execute, - Select: Select -}; - diff --git a/v3/plugins/sqlite/sqlite_open.js b/v3/plugins/sqlite/sqlite_open.js new file mode 100644 index 00000000..22526210 --- /dev/null +++ b/v3/plugins/sqlite/sqlite_open.js @@ -0,0 +1,8 @@ +/** + * Open a sqlite DB. + * @param filename {string} - file to open. + * @returns {Promise} + */ +function Open(filename) { + return wails.Plugin("sqlite", "Open", filename); +} diff --git a/v3/plugins/start_at_login/README.md b/v3/plugins/start_at_login/README.md new file mode 100644 index 00000000..b69960d3 --- /dev/null +++ b/v3/plugins/start_at_login/README.md @@ -0,0 +1,34 @@ +# start_at_login Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "start_at_login": start_at_login.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js + wails.Plugin("start_at_login","StartAtLogin", true).then((result) => console.log(result)) + wails.Plugin("start_at_login","IsStartAtLogin").then((result) => console.log(result)) +``` + +To use this from Go, create a new instance of the plugin, then call the methods on that: + +```go + start_at_login := start_at_login.NewPlugin() + start_at_login.StartAtLogin(true) +``` + +## Support + +If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository). +Please do not contact the Wails team for support. \ No newline at end of file diff --git a/v3/plugins/start_at_login/plugin.go b/v3/plugins/start_at_login/plugin.go new file mode 100644 index 00000000..18a0223c --- /dev/null +++ b/v3/plugins/start_at_login/plugin.go @@ -0,0 +1,54 @@ +package start_at_login + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/logger" + "github.com/wailsapp/wails/v3/pkg/mac" + "runtime" +) + +type Plugin struct { + app *application.App + disabled bool +} + +func NewPlugin() *Plugin { + return &Plugin{} +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() {} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/start_at_login" +} + +func (p *Plugin) Init(app *application.App) error { + p.app = app + if runtime.GOOS == "darwin" { + bundleID := mac.GetBundleID() + if bundleID == "" { + p.app.Log(&logger.Message{ + Level: "INFO", + Message: "Application is not in bundle. StartAtLogin will not work.", + }) + p.disabled = true + return nil + } + } + return nil +} + +func (p *Plugin) CallableByJS() []string { + return []string{ + "StartAtLogin", + "IsStartAtLogin", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} diff --git a/v3/plugins/start_at_login/plugin.toml b/v3/plugins/start_at_login/plugin.toml new file mode 100644 index 00000000..fa359f46 --- /dev/null +++ b/v3/plugins/start_at_login/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "start_at_login" plugin. + +Name = "start_at_login" +Description = "Plugin to control whether the application should start at login" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io/plugins/start_at_login" +Repository = "https://github.com/wailsapp/wails/v3/plugins/start_at_login" +License = "MIT" + + diff --git a/v3/plugins/start_at_login/plugin_darwin.go b/v3/plugins/start_at_login/plugin_darwin.go new file mode 100644 index 00000000..3c2444d5 --- /dev/null +++ b/v3/plugins/start_at_login/plugin_darwin.go @@ -0,0 +1,66 @@ +//go:build darwin + +package start_at_login + +import ( + "fmt" + "github.com/pkg/errors" + "github.com/samber/lo" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func (p *Plugin) StartAtLogin(enabled bool) error { + if p.disabled { + return nil + } + exe, err := os.Executable() + if err != nil { + return errors.Wrap(err, "Error running os.Executable:") + } + binName := filepath.Base(exe) + if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) { + return fmt.Errorf("app needs to be running as package.app file to start at login") + } + appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName) + var command string + if enabled { + command = fmt.Sprintf("tell application \"System Events\" to make login item at end with properties {name: \"%s\",path:\"%s\", hidden:false}", binName, appPath) + } else { + command = fmt.Sprintf("tell application \"System Events\" to delete login item \"%s\"", binName) + } + + cmd := exec.Command("osascript", "-e", command) + _, err = cmd.CombinedOutput() + if err != nil { + return err + } + return nil +} + +func (p *Plugin) IsStartAtLogin() (bool, error) { + if p.disabled { + return false, nil + } + exe, err := os.Executable() + if err != nil { + return false, err + } + binName := filepath.Base(exe) + if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) { + return false, fmt.Errorf("app needs to be running as package.app file to start at login") + } + appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName) + appName := strings.TrimSuffix(filepath.Base(appPath), ".app") + cmd := exec.Command("osascript", "-e", `tell application "System Events" to get the name of every login item`) + results, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + resultsString := strings.TrimSpace(string(results)) + startupApps := strings.Split(resultsString, ", ") + result := lo.Contains(startupApps, appName) + return result, nil +}