Switch remote deploy to vendored source builds
Move remote deployment to a vendored source bundle built on the target host via Docker so redeploys no longer require local cross-compilation or host Go installation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
45
vendor/github.com/wailsapp/wails/v2/internal/app/app.go
generated
vendored
Normal file
45
vendor/github.com/wailsapp/wails/v2/internal/app/app.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
frontend frontend.Frontend
|
||||
logger *logger.Logger
|
||||
options *options.App
|
||||
|
||||
menuManager *menumanager.Manager
|
||||
|
||||
// Indicates if the app is in debug mode
|
||||
debug bool
|
||||
|
||||
// Indicates if the devtools is enabled
|
||||
devtoolsEnabled bool
|
||||
|
||||
// OnStartup/OnShutdown
|
||||
startupCallback func(ctx context.Context)
|
||||
shutdownCallback func(ctx context.Context)
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Shutdown the application
|
||||
func (a *App) Shutdown() {
|
||||
if a.frontend != nil {
|
||||
a.frontend.Quit()
|
||||
}
|
||||
}
|
||||
|
||||
// SetApplicationMenu sets the application menu
|
||||
func (a *App) SetApplicationMenu(menu *menu.Menu) {
|
||||
if a.frontend != nil {
|
||||
a.frontend.MenuSetApplicationMenu(menu)
|
||||
}
|
||||
}
|
||||
124
vendor/github.com/wailsapp/wails/v2/internal/app/app_bindings.go
generated
vendored
Normal file
124
vendor/github.com/wailsapp/wails/v2/internal/app/app_bindings.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
//go:build bindings
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/leaanthony/gosod"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime/wrapper"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/project"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func (a *App) Run() error {
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{
|
||||
a.options.OnStartup,
|
||||
a.options.OnShutdown,
|
||||
a.options.OnDomReady,
|
||||
a.options.OnBeforeClose,
|
||||
}
|
||||
|
||||
// Check for CLI Flags
|
||||
bindingFlags := flag.NewFlagSet("bindings", flag.ContinueOnError)
|
||||
|
||||
var tsPrefixFlag *string
|
||||
var tsPostfixFlag *string
|
||||
var tsOutputTypeFlag *string
|
||||
|
||||
tsPrefix := os.Getenv("tsprefix")
|
||||
if tsPrefix == "" {
|
||||
tsPrefixFlag = bindingFlags.String("tsprefix", "", "Prefix for generated typescript entities")
|
||||
}
|
||||
|
||||
tsSuffix := os.Getenv("tssuffix")
|
||||
if tsSuffix == "" {
|
||||
tsPostfixFlag = bindingFlags.String("tssuffix", "", "Suffix for generated typescript entities")
|
||||
}
|
||||
|
||||
tsOutputType := os.Getenv("tsoutputtype")
|
||||
if tsOutputType == "" {
|
||||
tsOutputTypeFlag = bindingFlags.String("tsoutputtype", "", "Output type for generated typescript entities (classes|interfaces)")
|
||||
}
|
||||
|
||||
_ = bindingFlags.Parse(os.Args[1:])
|
||||
if tsPrefixFlag != nil {
|
||||
tsPrefix = *tsPrefixFlag
|
||||
}
|
||||
if tsPostfixFlag != nil {
|
||||
tsSuffix = *tsPostfixFlag
|
||||
}
|
||||
if tsOutputTypeFlag != nil {
|
||||
tsOutputType = *tsOutputTypeFlag
|
||||
}
|
||||
|
||||
appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated(), a.options.EnumBind)
|
||||
|
||||
appBindings.SetTsPrefix(tsPrefix)
|
||||
appBindings.SetTsSuffix(tsSuffix)
|
||||
appBindings.SetOutputType(tsOutputType)
|
||||
|
||||
err := generateBindings(appBindings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateApp creates the app!
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
|
||||
result := &App{
|
||||
logger: myLogger,
|
||||
options: appoptions,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func generateBindings(bindings *binding.Bindings) error {
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectConfig, err := project.Load(cwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wailsjsbasedir := filepath.Join(projectConfig.GetWailsJSDir(), "wailsjs")
|
||||
|
||||
runtimeDir := filepath.Join(wailsjsbasedir, "runtime")
|
||||
_ = os.RemoveAll(runtimeDir)
|
||||
extractor := gosod.New(wrapper.RuntimeWrapper)
|
||||
err = extractor.Extract(runtimeDir, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
goBindingsDir := filepath.Join(wailsjsbasedir, "go")
|
||||
err = os.RemoveAll(goBindingsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = fs.MkDirs(goBindingsDir)
|
||||
|
||||
err = bindings.GenerateGoBindings(goBindingsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fs.SetPermissions(wailsjsbasedir, 0755)
|
||||
}
|
||||
7
vendor/github.com/wailsapp/wails/v2/internal/app/app_debug.go
generated
vendored
Normal file
7
vendor/github.com/wailsapp/wails/v2/internal/app/app_debug.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build debug
|
||||
|
||||
package app
|
||||
|
||||
func IsDebug() bool {
|
||||
return true
|
||||
}
|
||||
7
vendor/github.com/wailsapp/wails/v2/internal/app/app_debug_not.go
generated
vendored
Normal file
7
vendor/github.com/wailsapp/wails/v2/internal/app/app_debug_not.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !debug
|
||||
|
||||
package app
|
||||
|
||||
func IsDebug() bool {
|
||||
return false
|
||||
}
|
||||
18
vendor/github.com/wailsapp/wails/v2/internal/app/app_default_unix.go
generated
vendored
Normal file
18
vendor/github.com/wailsapp/wails/v2/internal/app/app_default_unix.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build !dev && !production && !bindings && (linux || darwin)
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func (a *App) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateApp creates the app!
|
||||
func CreateApp(_ *options.App) (*App, error) {
|
||||
return nil, fmt.Errorf(`Wails applications will not build without the correct build tags.`)
|
||||
}
|
||||
27
vendor/github.com/wailsapp/wails/v2/internal/app/app_default_windows.go
generated
vendored
Normal file
27
vendor/github.com/wailsapp/wails/v2/internal/app/app_default_windows.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//go:build !dev && !production && !bindings && windows
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func (a *App) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateApp creates the app!
|
||||
func CreateApp(_ *options.App) (*App, error) {
|
||||
result := w32.MessageBox(0,
|
||||
`Wails applications will not build without the correct build tags.
|
||||
Please use "wails build" or press "OK" to open the documentation on how to use "go build"`,
|
||||
"Error",
|
||||
w32.MB_ICONERROR|w32.MB_OKCANCEL)
|
||||
if result == 1 {
|
||||
exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://wails.io/docs/guides/manual-builds").Start()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
298
vendor/github.com/wailsapp/wails/v2/internal/app/app_dev.go
generated
vendored
Normal file
298
vendor/github.com/wailsapp/wails/v2/internal/app/app_dev.go
generated
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
//go:build dev
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
iofs "io/fs"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
pkglogger "github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func (a *App) Run() error {
|
||||
err := a.frontend.Run(a.ctx)
|
||||
a.frontend.RunMainLoop()
|
||||
a.frontend.WindowClose()
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback(a.ctx)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateApp creates the app!
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "debug", true)
|
||||
ctx = context.WithValue(ctx, "devtoolsEnabled", true)
|
||||
|
||||
// Set up logger if the appoptions.LogLevel is an invalid value, set it to the default log level
|
||||
appoptions.LogLevel, err = pkglogger.StringToLogLevel(appoptions.LogLevel.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
|
||||
// Check for CLI Flags
|
||||
devFlags := flag.NewFlagSet("dev", flag.ContinueOnError)
|
||||
|
||||
var assetdirFlag *string
|
||||
var devServerFlag *string
|
||||
var frontendDevServerURLFlag *string
|
||||
var loglevelFlag *string
|
||||
|
||||
assetdir := os.Getenv("assetdir")
|
||||
if assetdir == "" {
|
||||
assetdirFlag = devFlags.String("assetdir", "", "Directory to serve assets")
|
||||
}
|
||||
|
||||
devServer := os.Getenv("devserver")
|
||||
if devServer == "" {
|
||||
devServerFlag = devFlags.String("devserver", "", "Address to bind the wails dev server to")
|
||||
}
|
||||
|
||||
frontendDevServerURL := os.Getenv("frontenddevserverurl")
|
||||
if frontendDevServerURL == "" {
|
||||
frontendDevServerURLFlag = devFlags.String("frontenddevserverurl", "", "URL of the external frontend dev server")
|
||||
}
|
||||
|
||||
loglevel := os.Getenv("loglevel")
|
||||
appLogLevel := appoptions.LogLevel.String()
|
||||
if loglevel != "" {
|
||||
appLogLevel = loglevel
|
||||
}
|
||||
loglevelFlag = devFlags.String("loglevel", appLogLevel, "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
|
||||
// If we weren't given the assetdir in the environment variables
|
||||
if assetdir == "" {
|
||||
// Parse args but ignore errors in case -appargs was used to pass in args for the app.
|
||||
_ = devFlags.Parse(os.Args[1:])
|
||||
if assetdirFlag != nil {
|
||||
assetdir = *assetdirFlag
|
||||
}
|
||||
if devServerFlag != nil {
|
||||
devServer = *devServerFlag
|
||||
}
|
||||
if frontendDevServerURLFlag != nil {
|
||||
frontendDevServerURL = *frontendDevServerURLFlag
|
||||
}
|
||||
if loglevelFlag != nil {
|
||||
loglevel = *loglevelFlag
|
||||
}
|
||||
}
|
||||
|
||||
assetConfig, err := assetserver.BuildAssetServerConfig(appoptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if assetConfig.Assets == nil && frontendDevServerURL != "" {
|
||||
myLogger.Warning("No AssetServer.Assets has been defined but a frontend DevServer, the frontend DevServer will not be used.")
|
||||
frontendDevServerURL = ""
|
||||
assetdir = ""
|
||||
}
|
||||
|
||||
if frontendDevServerURL != "" {
|
||||
_, port, err := net.SplitHostPort(devServer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine port of DevServer: %s", err)
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "assetserverport", port)
|
||||
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
|
||||
externalURL, err := url.Parse(frontendDevServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if externalURL.Host == "" {
|
||||
return nil, fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
|
||||
}
|
||||
|
||||
waitCb := func() { myLogger.Debug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
|
||||
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
|
||||
myLogger.Error("Timeout waiting for frontend DevServer")
|
||||
}
|
||||
|
||||
handler := assetserver.NewExternalAssetsHandler(myLogger, assetConfig, externalURL)
|
||||
assetConfig.Assets = nil
|
||||
assetConfig.Handler = handler
|
||||
assetConfig.Middleware = nil
|
||||
|
||||
myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL)
|
||||
} else {
|
||||
if assetdir == "" {
|
||||
// If no assetdir has been defined, let's try to infer it from the project root and the asset FS.
|
||||
assetdir, err = tryInferAssetDirFromFS(assetConfig.Assets)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to infer the AssetDir from your Assets fs.FS: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if assetdir != "" {
|
||||
// Let's override the assets to serve from on disk, if needed
|
||||
absdir, err := filepath.Abs(assetdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
myLogger.Info("Serving assets from disk: %s", absdir)
|
||||
assetConfig.Assets = os.DirFS(absdir)
|
||||
|
||||
ctx = context.WithValue(ctx, "assetdir", assetdir)
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate deprecated options to the new AssetServer option
|
||||
appoptions.Assets = nil
|
||||
appoptions.AssetsHandler = nil
|
||||
appoptions.AssetServer = &assetConfig
|
||||
|
||||
if devServer != "" {
|
||||
ctx = context.WithValue(ctx, "devserver", devServer)
|
||||
}
|
||||
|
||||
if loglevel != "" {
|
||||
level, err := pkglogger.StringToLogLevel(loglevel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only set the log level if it's different from the appoptions.LogLevel
|
||||
if level != appoptions.LogLevel {
|
||||
myLogger.SetLogLevel(level)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach logger to context
|
||||
ctx = context.WithValue(ctx, "logger", myLogger)
|
||||
ctx = context.WithValue(ctx, "buildtype", "dev")
|
||||
|
||||
// Preflight checks
|
||||
err = PreflightChecks(appoptions, myLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
|
||||
var menuManager *menumanager.Manager
|
||||
|
||||
// Process the application menu
|
||||
if appoptions.Menu != nil {
|
||||
// Create the menu manager
|
||||
menuManager = menumanager.NewManager()
|
||||
err = menuManager.SetApplicationMenu(appoptions.Menu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{
|
||||
appoptions.OnStartup,
|
||||
appoptions.OnShutdown,
|
||||
appoptions.OnDomReady,
|
||||
appoptions.OnBeforeClose,
|
||||
}
|
||||
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false, appoptions.EnumBind)
|
||||
|
||||
eventHandler := runtime.NewEvents(myLogger)
|
||||
ctx = context.WithValue(ctx, "events", eventHandler)
|
||||
messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery)
|
||||
|
||||
// Create the frontends and register to event handler
|
||||
desktopFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
|
||||
appFrontend := devserver.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher, menuManager, desktopFrontend)
|
||||
eventHandler.AddFrontend(appFrontend)
|
||||
eventHandler.AddFrontend(desktopFrontend)
|
||||
|
||||
ctx = context.WithValue(ctx, "frontend", appFrontend)
|
||||
result := &App{
|
||||
ctx: ctx,
|
||||
frontend: appFrontend,
|
||||
logger: myLogger,
|
||||
menuManager: menuManager,
|
||||
startupCallback: appoptions.OnStartup,
|
||||
shutdownCallback: appoptions.OnShutdown,
|
||||
debug: true,
|
||||
devtoolsEnabled: true,
|
||||
}
|
||||
|
||||
result.options = appoptions
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
|
||||
if _, isEmbedFs := assets.(embed.FS); !isEmbedFs {
|
||||
// We only infer the assetdir for embed.FS assets
|
||||
return "", nil
|
||||
}
|
||||
|
||||
path, err := fs.FindPathToFile(assets, "index.html")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(path, "index.html")); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = fmt.Errorf(
|
||||
"inferred assetdir '%s' does not exist or does not contain an 'index.html' file, "+
|
||||
"please specify it with -assetdir or set it in wails.json",
|
||||
path)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
|
||||
if timeout == 0 {
|
||||
timeout = time.Minute
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
waitCB()
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
8
vendor/github.com/wailsapp/wails/v2/internal/app/app_devtools.go
generated
vendored
Normal file
8
vendor/github.com/wailsapp/wails/v2/internal/app/app_devtools.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build devtools
|
||||
|
||||
package app
|
||||
|
||||
// Note: devtools flag is also added in debug builds
|
||||
func IsDevtoolsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
9
vendor/github.com/wailsapp/wails/v2/internal/app/app_devtools_not.go
generated
vendored
Normal file
9
vendor/github.com/wailsapp/wails/v2/internal/app/app_devtools_not.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !devtools
|
||||
|
||||
package app
|
||||
|
||||
// IsDevtoolsEnabled returns true if devtools should be enabled
|
||||
// Note: devtools flag is also added in debug builds
|
||||
func IsDevtoolsEnabled() bool {
|
||||
return false
|
||||
}
|
||||
8
vendor/github.com/wailsapp/wails/v2/internal/app/app_obfuscated.go
generated
vendored
Normal file
8
vendor/github.com/wailsapp/wails/v2/internal/app/app_obfuscated.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build obfuscated
|
||||
|
||||
package app
|
||||
|
||||
// IsObfuscated returns true if the obfuscated build tag is set
|
||||
func IsObfuscated() bool {
|
||||
return true
|
||||
}
|
||||
8
vendor/github.com/wailsapp/wails/v2/internal/app/app_obfuscated_not.go
generated
vendored
Normal file
8
vendor/github.com/wailsapp/wails/v2/internal/app/app_obfuscated_not.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build !obfuscated
|
||||
|
||||
package app
|
||||
|
||||
// IsObfuscated returns false if the obfuscated build tag is not set
|
||||
func IsObfuscated() bool {
|
||||
return false
|
||||
}
|
||||
12
vendor/github.com/wailsapp/wails/v2/internal/app/app_preflight_unix.go
generated
vendored
Normal file
12
vendor/github.com/wailsapp/wails/v2/internal/app/app_preflight_unix.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build (linux || darwin) && !bindings
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func PreflightChecks(_ *options.App, _ *logger.Logger) error {
|
||||
return nil
|
||||
}
|
||||
27
vendor/github.com/wailsapp/wails/v2/internal/app/app_preflight_windows.go
generated
vendored
Normal file
27
vendor/github.com/wailsapp/wails/v2/internal/app/app_preflight_windows.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//go:build windows && !bindings
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/wv2installer"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func PreflightChecks(options *options.App, logger *logger.Logger) error {
|
||||
|
||||
_ = options
|
||||
|
||||
// Process the webview2 runtime situation. We can pass a strategy in via the `webview2` flag for `wails build`.
|
||||
// This will determine how wv2runtime.Process will handle a lack of valid runtime.
|
||||
installedVersion, err := wv2installer.Process(options)
|
||||
if installedVersion != "" {
|
||||
logger.Debug("WebView2 Runtime Version '%s' installed. Minimum version required: %s.",
|
||||
installedVersion, wv2installer.MinimumRuntimeVersion)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
104
vendor/github.com/wailsapp/wails/v2/internal/app/app_production.go
generated
vendored
Normal file
104
vendor/github.com/wailsapp/wails/v2/internal/app/app_production.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
//go:build production
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func (a *App) Run() error {
|
||||
err := a.frontend.Run(a.ctx)
|
||||
a.frontend.RunMainLoop()
|
||||
a.frontend.WindowClose()
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback(a.ctx)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateApp creates the app!
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
|
||||
debug := IsDebug()
|
||||
devtoolsEnabled := IsDevtoolsEnabled()
|
||||
ctx = context.WithValue(ctx, "debug", debug)
|
||||
ctx = context.WithValue(ctx, "devtoolsEnabled", devtoolsEnabled)
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
if IsDebug() {
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
} else {
|
||||
myLogger.SetLogLevel(appoptions.LogLevelProduction)
|
||||
}
|
||||
ctx = context.WithValue(ctx, "logger", myLogger)
|
||||
ctx = context.WithValue(ctx, "obfuscated", IsObfuscated())
|
||||
|
||||
// Preflight Checks
|
||||
err = PreflightChecks(appoptions, myLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the menu manager
|
||||
menuManager := menumanager.NewManager()
|
||||
|
||||
// Process the application menu
|
||||
if appoptions.Menu != nil {
|
||||
err = menuManager.SetApplicationMenu(appoptions.Menu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{
|
||||
appoptions.OnStartup,
|
||||
appoptions.OnShutdown,
|
||||
appoptions.OnDomReady,
|
||||
appoptions.OnBeforeClose,
|
||||
}
|
||||
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated(), appoptions.EnumBind)
|
||||
eventHandler := runtime.NewEvents(myLogger)
|
||||
ctx = context.WithValue(ctx, "events", eventHandler)
|
||||
// Attach logger to context
|
||||
if debug {
|
||||
ctx = context.WithValue(ctx, "buildtype", "debug")
|
||||
} else {
|
||||
ctx = context.WithValue(ctx, "buildtype", "production")
|
||||
}
|
||||
|
||||
messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery)
|
||||
appFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
|
||||
eventHandler.AddFrontend(appFrontend)
|
||||
|
||||
ctx = context.WithValue(ctx, "frontend", appFrontend)
|
||||
result := &App{
|
||||
ctx: ctx,
|
||||
frontend: appFrontend,
|
||||
logger: myLogger,
|
||||
menuManager: menuManager,
|
||||
startupCallback: appoptions.OnStartup,
|
||||
shutdownCallback: appoptions.OnShutdown,
|
||||
debug: debug,
|
||||
devtoolsEnabled: devtoolsEnabled,
|
||||
options: appoptions,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
384
vendor/github.com/wailsapp/wails/v2/internal/binding/binding.go
generated
vendored
Normal file
384
vendor/github.com/wailsapp/wails/v2/internal/binding/binding.go
generated
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/typescriptify"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
type Bindings struct {
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
exemptions slicer.StringSlicer
|
||||
|
||||
structsToGenerateTS map[string]map[string]interface{}
|
||||
enumsToGenerateTS map[string]map[string]interface{}
|
||||
tsPrefix string
|
||||
tsSuffix string
|
||||
tsInterface bool
|
||||
obfuscate bool
|
||||
}
|
||||
|
||||
// NewBindings returns a new Bindings object
|
||||
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool, enumsToBind []interface{}) *Bindings {
|
||||
result := &Bindings{
|
||||
db: newDB(),
|
||||
logger: logger.CustomLogger("Bindings"),
|
||||
structsToGenerateTS: make(map[string]map[string]interface{}),
|
||||
enumsToGenerateTS: make(map[string]map[string]interface{}),
|
||||
obfuscate: obfuscate,
|
||||
}
|
||||
|
||||
for _, exemption := range exemptions {
|
||||
if exemption == nil {
|
||||
continue
|
||||
}
|
||||
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
|
||||
// Yuk yuk yuk! Is there a better way?
|
||||
name = strings.TrimSuffix(name, "-fm")
|
||||
result.exemptions.Add(name)
|
||||
}
|
||||
|
||||
for _, enum := range enumsToBind {
|
||||
result.AddEnumToGenerateTS(enum)
|
||||
}
|
||||
|
||||
// Add the structs to bind
|
||||
for _, ptr := range structPointersToBind {
|
||||
err := result.Add(ptr)
|
||||
if err != nil {
|
||||
logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add the given struct methods to the Bindings
|
||||
func (b *Bindings) Add(structPtr interface{}) error {
|
||||
methods, err := b.getMethods(structPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
b.db.AddMethod(method.Path.Package, method.Path.Struct, method.Path.Name, method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bindings) DB() *DB {
|
||||
return b.db
|
||||
}
|
||||
|
||||
func (b *Bindings) ToJSON() (string, error) {
|
||||
return b.db.ToJSON()
|
||||
}
|
||||
|
||||
func (b *Bindings) GenerateModels() ([]byte, error) {
|
||||
models := map[string]string{}
|
||||
var seen slicer.StringSlicer
|
||||
var seenEnumsPackages slicer.StringSlicer
|
||||
allStructNames := b.getAllStructNames()
|
||||
allStructNames.Sort()
|
||||
allEnumNames := b.getAllEnumNames()
|
||||
allEnumNames.Sort()
|
||||
for packageName, structsToGenerate := range b.structsToGenerateTS {
|
||||
thisPackageCode := ""
|
||||
w := typescriptify.New()
|
||||
w.WithPrefix(b.tsPrefix)
|
||||
w.WithSuffix(b.tsSuffix)
|
||||
w.WithInterface(b.tsInterface)
|
||||
w.Namespace = packageName
|
||||
w.WithBackupDir("")
|
||||
w.KnownStructs = allStructNames
|
||||
w.KnownEnums = allEnumNames
|
||||
// sort the structs
|
||||
var structNames []string
|
||||
for structName := range structsToGenerate {
|
||||
structNames = append(structNames, structName)
|
||||
}
|
||||
sort.Strings(structNames)
|
||||
for _, structName := range structNames {
|
||||
fqstructname := packageName + "." + structName
|
||||
if seen.Contains(fqstructname) {
|
||||
continue
|
||||
}
|
||||
structInterface := structsToGenerate[structName]
|
||||
w.Add(structInterface)
|
||||
}
|
||||
|
||||
// if we have enums for this package, add them as well
|
||||
var enums, enumsExist = b.enumsToGenerateTS[packageName]
|
||||
if enumsExist {
|
||||
// Sort the enum names first to make the output deterministic
|
||||
sortedEnumNames := make([]string, 0, len(enums))
|
||||
for enumName := range enums {
|
||||
sortedEnumNames = append(sortedEnumNames, enumName)
|
||||
}
|
||||
sort.Strings(sortedEnumNames)
|
||||
|
||||
for _, enumName := range sortedEnumNames {
|
||||
enum := enums[enumName]
|
||||
fqemumname := packageName + "." + enumName
|
||||
if seen.Contains(fqemumname) {
|
||||
continue
|
||||
}
|
||||
w.AddEnum(enum)
|
||||
}
|
||||
seenEnumsPackages.Add(packageName)
|
||||
}
|
||||
|
||||
str, err := w.Convert(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thisPackageCode += str
|
||||
seen.AddSlice(w.GetGeneratedStructs())
|
||||
models[packageName] = thisPackageCode
|
||||
}
|
||||
|
||||
// Add outstanding enums to the models that were not in packages with structs
|
||||
for packageName, enumsToGenerate := range b.enumsToGenerateTS {
|
||||
if seenEnumsPackages.Contains(packageName) {
|
||||
continue
|
||||
}
|
||||
|
||||
thisPackageCode := ""
|
||||
w := typescriptify.New()
|
||||
w.WithPrefix(b.tsPrefix)
|
||||
w.WithSuffix(b.tsSuffix)
|
||||
w.WithInterface(b.tsInterface)
|
||||
w.Namespace = packageName
|
||||
w.WithBackupDir("")
|
||||
|
||||
for enumName, enum := range enumsToGenerate {
|
||||
fqemumname := packageName + "." + enumName
|
||||
if seen.Contains(fqemumname) {
|
||||
continue
|
||||
}
|
||||
w.AddEnum(enum)
|
||||
}
|
||||
str, err := w.Convert(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thisPackageCode += str
|
||||
models[packageName] = thisPackageCode
|
||||
}
|
||||
|
||||
// Sort the package names first to make the output deterministic
|
||||
sortedPackageNames := make([]string, 0, len(models))
|
||||
for packageName := range models {
|
||||
sortedPackageNames = append(sortedPackageNames, packageName)
|
||||
}
|
||||
sort.Strings(sortedPackageNames)
|
||||
|
||||
var modelsData bytes.Buffer
|
||||
for _, packageName := range sortedPackageNames {
|
||||
modelData := models[packageName]
|
||||
if strings.TrimSpace(modelData) == "" {
|
||||
continue
|
||||
}
|
||||
modelsData.WriteString("export namespace " + packageName + " {\n")
|
||||
sc := bufio.NewScanner(strings.NewReader(modelData))
|
||||
for sc.Scan() {
|
||||
modelsData.WriteString("\t" + sc.Text() + "\n")
|
||||
}
|
||||
modelsData.WriteString("\n}\n\n")
|
||||
}
|
||||
return modelsData.Bytes(), nil
|
||||
}
|
||||
|
||||
func (b *Bindings) WriteModels(modelsDir string) error {
|
||||
modelsData, err := b.GenerateModels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Don't write if we don't have anything
|
||||
if len(modelsData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
filename := filepath.Join(modelsDir, "models.ts")
|
||||
err = os.WriteFile(filename, modelsData, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bindings) AddEnumToGenerateTS(e interface{}) {
|
||||
enumType := reflect.TypeOf(e)
|
||||
|
||||
var packageName string
|
||||
var enumName string
|
||||
// enums should be represented as array of all possible values
|
||||
if hasElements(enumType) {
|
||||
enum := enumType.Elem()
|
||||
// simple enum represented by struct with Value/TSName fields
|
||||
if enum.Kind() == reflect.Struct {
|
||||
_, tsNamePresented := enum.FieldByName("TSName")
|
||||
enumT, valuePresented := enum.FieldByName("Value")
|
||||
if tsNamePresented && valuePresented {
|
||||
packageName = getPackageName(enumT.Type.String())
|
||||
enumName = enumT.Type.Name()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// otherwise expecting implementation with TSName() https://github.com/tkrajina/typescriptify-golang-structs#enums-with-tsname
|
||||
} else {
|
||||
packageName = getPackageName(enumType.Elem().String())
|
||||
enumName = enumType.Elem().Name()
|
||||
}
|
||||
if b.enumsToGenerateTS[packageName] == nil {
|
||||
b.enumsToGenerateTS[packageName] = make(map[string]interface{})
|
||||
}
|
||||
if b.enumsToGenerateTS[packageName][enumName] != nil {
|
||||
return
|
||||
}
|
||||
b.enumsToGenerateTS[packageName][enumName] = e
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, s interface{}) {
|
||||
if b.structsToGenerateTS[packageName] == nil {
|
||||
b.structsToGenerateTS[packageName] = make(map[string]interface{})
|
||||
}
|
||||
if b.structsToGenerateTS[packageName][structName] != nil {
|
||||
return
|
||||
}
|
||||
b.structsToGenerateTS[packageName][structName] = s
|
||||
|
||||
// Iterate this struct and add any struct field references
|
||||
structType := reflect.TypeOf(s)
|
||||
for hasElements(structType) {
|
||||
structType = structType.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
field := structType.Field(i)
|
||||
if field.Anonymous || !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
kind := field.Type.Kind()
|
||||
if kind == reflect.Struct {
|
||||
fqname := field.Type.String()
|
||||
sNameSplit := strings.SplitN(fqname, ".", 2)
|
||||
if len(sNameSplit) < 2 {
|
||||
continue
|
||||
}
|
||||
sName := sNameSplit[1]
|
||||
pName := getPackageName(fqname)
|
||||
a := reflect.New(field.Type)
|
||||
if b.hasExportedJSONFields(field.Type) {
|
||||
s := reflect.Indirect(a).Interface()
|
||||
b.AddStructToGenerateTS(pName, sName, s)
|
||||
}
|
||||
} else {
|
||||
fType := field.Type
|
||||
for hasElements(fType) {
|
||||
fType = fType.Elem()
|
||||
}
|
||||
if fType.Kind() == reflect.Struct {
|
||||
fqname := fType.String()
|
||||
sNameSplit := strings.SplitN(fqname, ".", 2)
|
||||
if len(sNameSplit) < 2 {
|
||||
continue
|
||||
}
|
||||
sName := sNameSplit[1]
|
||||
pName := getPackageName(fqname)
|
||||
a := reflect.New(fType)
|
||||
if b.hasExportedJSONFields(fType) {
|
||||
s := reflect.Indirect(a).Interface()
|
||||
b.AddStructToGenerateTS(pName, sName, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bindings) SetTsPrefix(prefix string) *Bindings {
|
||||
b.tsPrefix = prefix
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bindings) SetTsSuffix(postfix string) *Bindings {
|
||||
b.tsSuffix = postfix
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bindings) SetOutputType(outputType string) *Bindings {
|
||||
if outputType == "interfaces" {
|
||||
b.tsInterface = true
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bindings) getAllStructNames() *slicer.StringSlicer {
|
||||
var result slicer.StringSlicer
|
||||
for packageName, structsToGenerate := range b.structsToGenerateTS {
|
||||
for structName := range structsToGenerate {
|
||||
result.Add(packageName + "." + structName)
|
||||
}
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
func (b *Bindings) getAllEnumNames() *slicer.StringSlicer {
|
||||
var result slicer.StringSlicer
|
||||
for packageName, enumsToGenerate := range b.enumsToGenerateTS {
|
||||
for enumName := range enumsToGenerate {
|
||||
result.Add(packageName + "." + enumName)
|
||||
}
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
func (b *Bindings) hasExportedJSONFields(typeOf reflect.Type) bool {
|
||||
for i := 0; i < typeOf.NumField(); i++ {
|
||||
jsonFieldName := ""
|
||||
f := typeOf.Field(i)
|
||||
// function, complex, and channel types cannot be json-encoded
|
||||
if f.Type.Kind() == reflect.Chan ||
|
||||
f.Type.Kind() == reflect.Func ||
|
||||
f.Type.Kind() == reflect.UnsafePointer ||
|
||||
f.Type.Kind() == reflect.Complex128 ||
|
||||
f.Type.Kind() == reflect.Complex64 {
|
||||
continue
|
||||
}
|
||||
jsonTag, hasTag := f.Tag.Lookup("json")
|
||||
if !hasTag && f.IsExported() {
|
||||
return true
|
||||
}
|
||||
if len(jsonTag) == 0 {
|
||||
continue
|
||||
}
|
||||
jsonTagParts := strings.Split(jsonTag, ",")
|
||||
if len(jsonTagParts) > 0 {
|
||||
jsonFieldName = jsonTagParts[0]
|
||||
}
|
||||
for _, t := range jsonTagParts {
|
||||
if t == "-" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if jsonFieldName != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
109
vendor/github.com/wailsapp/wails/v2/internal/binding/boundMethod.go
generated
vendored
Normal file
109
vendor/github.com/wailsapp/wails/v2/internal/binding/boundMethod.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type BoundedMethodPath struct {
|
||||
Package string
|
||||
Struct string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (p *BoundedMethodPath) FullName() string {
|
||||
return fmt.Sprintf("%s.%s.%s", p.Package, p.Struct, p.Name)
|
||||
}
|
||||
|
||||
// BoundMethod defines all the data related to a Go method that is
|
||||
// bound to the Wails application
|
||||
type BoundMethod struct {
|
||||
Path *BoundedMethodPath `json:"path"`
|
||||
Inputs []*Parameter `json:"inputs,omitempty"`
|
||||
Outputs []*Parameter `json:"outputs,omitempty"`
|
||||
Comments string `json:"comments,omitempty"`
|
||||
Method reflect.Value `json:"-"`
|
||||
}
|
||||
|
||||
// InputCount returns the number of inputs this bound method has
|
||||
func (b *BoundMethod) InputCount() int {
|
||||
return len(b.Inputs)
|
||||
}
|
||||
|
||||
// OutputCount returns the number of outputs this bound method has
|
||||
func (b *BoundMethod) OutputCount() int {
|
||||
return len(b.Outputs)
|
||||
}
|
||||
|
||||
// ParseArgs method converts the input json into the types expected by the method
|
||||
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
||||
result := make([]interface{}, b.InputCount())
|
||||
if len(args) != b.InputCount() {
|
||||
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Path.FullName(), b.InputCount())
|
||||
}
|
||||
for index, arg := range args {
|
||||
typ := b.Inputs[index].reflectType
|
||||
inputValue := reflect.New(typ).Interface()
|
||||
err := json.Unmarshal(arg, inputValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inputValue == nil {
|
||||
result[index] = reflect.Zero(typ).Interface()
|
||||
} else {
|
||||
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Call will attempt to call this bound method with the given args
|
||||
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
|
||||
// Check inputs
|
||||
expectedInputLength := len(b.Inputs)
|
||||
actualInputLength := len(args)
|
||||
if expectedInputLength != actualInputLength {
|
||||
return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Path.FullName(), expectedInputLength, actualInputLength)
|
||||
}
|
||||
|
||||
/** Convert inputs to reflect values **/
|
||||
|
||||
// Create slice for the input arguments to the method call
|
||||
callArgs := make([]reflect.Value, expectedInputLength)
|
||||
|
||||
// Iterate over given arguments
|
||||
for index, arg := range args {
|
||||
// Save the converted argument
|
||||
callArgs[index] = reflect.ValueOf(arg)
|
||||
}
|
||||
|
||||
// Do the call
|
||||
callResults := b.Method.Call(callArgs)
|
||||
|
||||
//** Check results **//
|
||||
var returnValue interface{}
|
||||
var err error
|
||||
|
||||
switch b.OutputCount() {
|
||||
case 1:
|
||||
// Loop over results and determine if the result
|
||||
// is an error or not
|
||||
for _, result := range callResults {
|
||||
interfac := result.Interface()
|
||||
temp, ok := interfac.(error)
|
||||
if ok {
|
||||
err = temp
|
||||
} else {
|
||||
returnValue = interfac
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
returnValue = callResults[0].Interface()
|
||||
if temp, ok := callResults[1].Interface().(error); ok {
|
||||
err = temp
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue, err
|
||||
}
|
||||
134
vendor/github.com/wailsapp/wails/v2/internal/binding/db.go
generated
vendored
Normal file
134
vendor/github.com/wailsapp/wails/v2/internal/binding/db.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// DB is our database of method bindings
|
||||
type DB struct {
|
||||
// map[packagename] -> map[structname] -> map[methodname]*method
|
||||
store map[string]map[string]map[string]*BoundMethod
|
||||
|
||||
// This uses fully qualified method names as a shortcut for store traversal.
|
||||
// It used for performance gains at runtime
|
||||
methodMap map[string]*BoundMethod
|
||||
|
||||
// This uses ids to reference bound methods at runtime
|
||||
obfuscatedMethodArray []*ObfuscatedMethod
|
||||
|
||||
// Lock to ensure sync access to the data
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type ObfuscatedMethod struct {
|
||||
method *BoundMethod
|
||||
methodName string
|
||||
}
|
||||
|
||||
func newDB() *DB {
|
||||
return &DB{
|
||||
store: make(map[string]map[string]map[string]*BoundMethod),
|
||||
methodMap: make(map[string]*BoundMethod),
|
||||
obfuscatedMethodArray: []*ObfuscatedMethod{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetMethodFromStore returns the method for the given package/struct/method names
|
||||
// nil is returned if any one of those does not exist
|
||||
func (d *DB) GetMethodFromStore(packageName string, structName string, methodName string) *BoundMethod {
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
|
||||
structMap, exists := d.store[packageName]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
methodMap, exists := structMap[structName]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return methodMap[methodName]
|
||||
}
|
||||
|
||||
// GetMethod returns the method for the given qualified method name
|
||||
// qualifiedMethodName is "packagename.structname.methodname"
|
||||
func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod {
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
|
||||
return d.methodMap[qualifiedMethodName]
|
||||
}
|
||||
|
||||
// GetObfuscatedMethod returns the method for the given ID
|
||||
func (d *DB) GetObfuscatedMethod(id int) *BoundMethod {
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
|
||||
if len(d.obfuscatedMethodArray) <= id {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.obfuscatedMethodArray[id].method
|
||||
}
|
||||
|
||||
// AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName
|
||||
func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) {
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
// Get the map associated with the package name
|
||||
structMap, exists := d.store[packageName]
|
||||
if !exists {
|
||||
// Create a new map for this packagename
|
||||
d.store[packageName] = make(map[string]map[string]*BoundMethod)
|
||||
structMap = d.store[packageName]
|
||||
}
|
||||
|
||||
// Get the map associated with the struct name
|
||||
methodMap, exists := structMap[structName]
|
||||
if !exists {
|
||||
// Create a new map for this packagename
|
||||
structMap[structName] = make(map[string]*BoundMethod)
|
||||
methodMap = structMap[structName]
|
||||
}
|
||||
|
||||
// Store the method definition
|
||||
methodMap[methodName] = methodDefinition
|
||||
|
||||
// Store in the methodMap
|
||||
key := packageName + "." + structName + "." + methodName
|
||||
d.methodMap[key] = methodDefinition
|
||||
d.obfuscatedMethodArray = append(d.obfuscatedMethodArray, &ObfuscatedMethod{method: methodDefinition, methodName: key})
|
||||
}
|
||||
|
||||
// ToJSON converts the method map to JSON
|
||||
func (d *DB) ToJSON() (string, error) {
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
|
||||
d.UpdateObfuscatedCallMap()
|
||||
|
||||
bytes, err := json.Marshal(&d.store)
|
||||
|
||||
// Return zero copy string as this string will be read only
|
||||
result := *(*string)(unsafe.Pointer(&bytes))
|
||||
return result, err
|
||||
}
|
||||
|
||||
// UpdateObfuscatedCallMap sets up the secure call mappings
|
||||
func (d *DB) UpdateObfuscatedCallMap() map[string]int {
|
||||
mappings := make(map[string]int)
|
||||
|
||||
for id, k := range d.obfuscatedMethodArray {
|
||||
mappings[k.methodName] = id
|
||||
}
|
||||
|
||||
return mappings
|
||||
}
|
||||
248
vendor/github.com/wailsapp/wails/v2/internal/binding/generate.go
generated
vendored
Normal file
248
vendor/github.com/wailsapp/wails/v2/internal/binding/generate.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
var (
|
||||
mapRegex *regexp.Regexp
|
||||
keyPackageIndex int
|
||||
keyTypeIndex int
|
||||
valueArrayIndex int
|
||||
valuePackageIndex int
|
||||
valueTypeIndex int
|
||||
)
|
||||
|
||||
func init() {
|
||||
mapRegex = regexp.MustCompile(`(?:map\[(?:(?P<keyPackage>\w+)\.)?(?P<keyType>\w+)])?(?P<valueArray>\[])?(?:\*?(?P<valuePackage>\w+)\.)?(?P<valueType>.+)`)
|
||||
keyPackageIndex = mapRegex.SubexpIndex("keyPackage")
|
||||
keyTypeIndex = mapRegex.SubexpIndex("keyType")
|
||||
valueArrayIndex = mapRegex.SubexpIndex("valueArray")
|
||||
valuePackageIndex = mapRegex.SubexpIndex("valuePackage")
|
||||
valueTypeIndex = mapRegex.SubexpIndex("valueType")
|
||||
}
|
||||
|
||||
func (b *Bindings) GenerateGoBindings(baseDir string) error {
|
||||
store := b.db.store
|
||||
var obfuscatedBindings map[string]int
|
||||
if b.obfuscate {
|
||||
obfuscatedBindings = b.db.UpdateObfuscatedCallMap()
|
||||
}
|
||||
for packageName, structs := range store {
|
||||
packageDir := filepath.Join(baseDir, packageName)
|
||||
err := fs.Mkdir(packageDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for structName, methods := range structs {
|
||||
var jsoutput bytes.Buffer
|
||||
jsoutput.WriteString(`// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
`)
|
||||
var tsBody bytes.Buffer
|
||||
var tsContent bytes.Buffer
|
||||
tsContent.WriteString(`// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
`)
|
||||
// Sort the method names alphabetically
|
||||
methodNames := make([]string, 0, len(methods))
|
||||
for methodName := range methods {
|
||||
methodNames = append(methodNames, methodName)
|
||||
}
|
||||
sort.Strings(methodNames)
|
||||
|
||||
var importNamespaces slicer.StringSlicer
|
||||
for _, methodName := range methodNames {
|
||||
// Get the method details
|
||||
methodDetails := methods[methodName]
|
||||
|
||||
// Generate JS
|
||||
var args slicer.StringSlicer
|
||||
for count := range methodDetails.Inputs {
|
||||
arg := fmt.Sprintf("arg%d", count+1)
|
||||
args.Add(arg)
|
||||
}
|
||||
argsString := args.Join(", ")
|
||||
jsoutput.WriteString(fmt.Sprintf("\nexport function %s(%s) {", methodName, argsString))
|
||||
jsoutput.WriteString("\n")
|
||||
if b.obfuscate {
|
||||
id := obfuscatedBindings[strings.Join([]string{packageName, structName, methodName}, ".")]
|
||||
jsoutput.WriteString(fmt.Sprintf(" return ObfuscatedCall(%d, [%s]);", id, argsString))
|
||||
} else {
|
||||
jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString))
|
||||
}
|
||||
jsoutput.WriteString("\n}\n")
|
||||
|
||||
// Generate TS
|
||||
tsBody.WriteString(fmt.Sprintf("\nexport function %s(", methodName))
|
||||
|
||||
args.Clear()
|
||||
for count, input := range methodDetails.Inputs {
|
||||
arg := fmt.Sprintf("arg%d", count+1)
|
||||
entityName := entityFullReturnType(input.TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
|
||||
args.Add(arg + ":" + goTypeToTypescriptType(entityName, &importNamespaces))
|
||||
}
|
||||
tsBody.WriteString(args.Join(",") + "):")
|
||||
// now build Typescript return types
|
||||
// If there is no return value or only returning error, TS returns Promise<void>
|
||||
// If returning single value, TS returns Promise<type>
|
||||
// If returning single value or error, TS returns Promise<type>
|
||||
// If returning two values, TS returns Promise<type1|type2>
|
||||
// Otherwise, TS returns Promise<type1> (instead of throwing Go error?)
|
||||
var returnType string
|
||||
if methodDetails.OutputCount() == 0 {
|
||||
returnType = "Promise<void>"
|
||||
} else if methodDetails.OutputCount() == 1 && methodDetails.Outputs[0].TypeName == "error" {
|
||||
returnType = "Promise<void>"
|
||||
} else {
|
||||
outputTypeName := entityFullReturnType(methodDetails.Outputs[0].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
|
||||
firstType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
|
||||
returnType = "Promise<" + firstType
|
||||
if methodDetails.OutputCount() == 2 && methodDetails.Outputs[1].TypeName != "error" {
|
||||
outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
|
||||
secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
|
||||
returnType += "|" + secondType
|
||||
}
|
||||
returnType += ">"
|
||||
}
|
||||
tsBody.WriteString(returnType + ";\n")
|
||||
}
|
||||
|
||||
importNamespaces.Deduplicate()
|
||||
importNamespaces.Each(func(namespace string) {
|
||||
tsContent.WriteString("import {" + namespace + "} from '../models';\n")
|
||||
})
|
||||
tsContent.WriteString(tsBody.String())
|
||||
|
||||
jsfilename := filepath.Join(packageDir, structName+".js")
|
||||
err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tsfilename := filepath.Join(packageDir, structName+".d.ts")
|
||||
err = os.WriteFile(tsfilename, tsContent.Bytes(), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
err := b.WriteModels(baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fullyQualifiedName(packageName string, typeName string) string {
|
||||
if len(packageName) > 0 {
|
||||
return packageName + "." + typeName
|
||||
}
|
||||
|
||||
switch true {
|
||||
case len(typeName) == 0:
|
||||
return ""
|
||||
case typeName == "interface{}" || typeName == "interface {}":
|
||||
return "any"
|
||||
case typeName == "string":
|
||||
return "string"
|
||||
case typeName == "error":
|
||||
return "Error"
|
||||
case
|
||||
strings.HasPrefix(typeName, "int"),
|
||||
strings.HasPrefix(typeName, "uint"),
|
||||
strings.HasPrefix(typeName, "float"):
|
||||
return "number"
|
||||
case typeName == "bool":
|
||||
return "boolean"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`)
|
||||
)
|
||||
|
||||
func arrayifyValue(valueArray string, valueType string) string {
|
||||
valueType = strings.ReplaceAll(valueType, "*", "")
|
||||
gidx := strings.IndexRune(valueType, '[')
|
||||
if gidx > 0 { // its a generic type
|
||||
rem := strings.SplitN(valueType, "[", 2)
|
||||
valueType = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_")
|
||||
}
|
||||
|
||||
if len(valueArray) == 0 {
|
||||
return valueType
|
||||
}
|
||||
|
||||
return "Array<" + valueType + ">"
|
||||
}
|
||||
|
||||
func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string {
|
||||
matches := mapRegex.FindStringSubmatch(input)
|
||||
keyPackage := matches[keyPackageIndex]
|
||||
keyType := matches[keyTypeIndex]
|
||||
valueArray := matches[valueArrayIndex]
|
||||
valuePackage := matches[valuePackageIndex]
|
||||
valueType := matches[valueTypeIndex]
|
||||
// fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n",
|
||||
// input,
|
||||
// keyPackage,
|
||||
// keyType,
|
||||
// valueArray,
|
||||
// valuePackage,
|
||||
// valueType)
|
||||
|
||||
// byte array is special case
|
||||
if valueArray == "[]" && valueType == "byte" {
|
||||
return "string"
|
||||
}
|
||||
|
||||
// if any packages, make sure they're saved
|
||||
if len(keyPackage) > 0 {
|
||||
importNamespaces.Add(keyPackage)
|
||||
}
|
||||
|
||||
if len(valuePackage) > 0 {
|
||||
importNamespaces.Add(valuePackage)
|
||||
}
|
||||
|
||||
key := fullyQualifiedName(keyPackage, keyType)
|
||||
var value string
|
||||
if strings.HasPrefix(valueType, "map") {
|
||||
value = goTypeToJSDocType(valueType, importNamespaces)
|
||||
} else {
|
||||
value = fullyQualifiedName(valuePackage, valueType)
|
||||
}
|
||||
|
||||
if len(key) > 0 {
|
||||
return fmt.Sprintf("Record<%s, %s>", key, arrayifyValue(valueArray, value))
|
||||
}
|
||||
|
||||
return arrayifyValue(valueArray, value)
|
||||
}
|
||||
|
||||
func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string {
|
||||
return goTypeToJSDocType(input, importNamespaces)
|
||||
}
|
||||
|
||||
func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string {
|
||||
if strings.ContainsRune(input, '.') {
|
||||
nameSpace, returnType := getSplitReturn(input)
|
||||
return nameSpace + "." + prefix + returnType + suffix
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
28
vendor/github.com/wailsapp/wails/v2/internal/binding/parameter.go
generated
vendored
Normal file
28
vendor/github.com/wailsapp/wails/v2/internal/binding/parameter.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package binding
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Parameter defines a Go method parameter
|
||||
type Parameter struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
TypeName string `json:"type"`
|
||||
reflectType reflect.Type
|
||||
}
|
||||
|
||||
func newParameter(Name string, Type reflect.Type) *Parameter {
|
||||
return &Parameter{
|
||||
Name: Name,
|
||||
TypeName: Type.String(),
|
||||
reflectType: Type,
|
||||
}
|
||||
}
|
||||
|
||||
// IsType returns true if the given
|
||||
func (p *Parameter) IsType(typename string) bool {
|
||||
return p.TypeName == typename
|
||||
}
|
||||
|
||||
// IsError returns true if the parameter type is an error
|
||||
func (p *Parameter) IsError() bool {
|
||||
return p.IsType("error")
|
||||
}
|
||||
200
vendor/github.com/wailsapp/wails/v2/internal/binding/reflect.go
generated
vendored
Normal file
200
vendor/github.com/wailsapp/wails/v2/internal/binding/reflect.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isStructPtr returns true if the value given is a
|
||||
// pointer to a struct
|
||||
func isStructPtr(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Ptr &&
|
||||
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// isFunction returns true if the given value is a function
|
||||
func isFunction(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Func
|
||||
}
|
||||
|
||||
// isStruct returns true if the value given is a struct
|
||||
func isStruct(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func normalizeStructName(name string) string {
|
||||
return strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
name,
|
||||
",",
|
||||
"-",
|
||||
),
|
||||
"*",
|
||||
"",
|
||||
),
|
||||
"]",
|
||||
"__",
|
||||
),
|
||||
"[",
|
||||
"__",
|
||||
)
|
||||
}
|
||||
|
||||
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
// Create result placeholder
|
||||
var result []*BoundMethod
|
||||
|
||||
// Check type
|
||||
if !isStructPtr(value) {
|
||||
|
||||
if isStruct(value) {
|
||||
name := reflect.ValueOf(value).Type().Name()
|
||||
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
|
||||
}
|
||||
|
||||
if isFunction(value) {
|
||||
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
|
||||
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a pointer to a struct.")
|
||||
}
|
||||
|
||||
// Process Struct
|
||||
structType := reflect.TypeOf(value)
|
||||
structValue := reflect.ValueOf(value)
|
||||
structName := structType.Elem().Name()
|
||||
structNameNormalized := normalizeStructName(structName)
|
||||
pkgPath := strings.TrimSuffix(structType.Elem().String(), fmt.Sprintf(".%s", structName))
|
||||
|
||||
// Process Methods
|
||||
for i := 0; i < structType.NumMethod(); i++ {
|
||||
methodDef := structType.Method(i)
|
||||
methodName := methodDef.Name
|
||||
method := structValue.MethodByName(methodName)
|
||||
|
||||
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
|
||||
if b.exemptions.Contains(methodReflectName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create new method
|
||||
boundMethod := &BoundMethod{
|
||||
Path: &BoundedMethodPath{
|
||||
Package: pkgPath,
|
||||
Struct: structNameNormalized,
|
||||
Name: methodName,
|
||||
},
|
||||
Inputs: nil,
|
||||
Outputs: nil,
|
||||
Comments: "",
|
||||
Method: method,
|
||||
}
|
||||
|
||||
// Iterate inputs
|
||||
methodType := method.Type()
|
||||
inputParamCount := methodType.NumIn()
|
||||
var inputs []*Parameter
|
||||
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
|
||||
input := methodType.In(inputIndex)
|
||||
thisParam := newParameter("", input)
|
||||
|
||||
thisInput := input
|
||||
|
||||
if thisInput.Kind() == reflect.Slice {
|
||||
thisInput = thisInput.Elem()
|
||||
}
|
||||
|
||||
// Process struct pointer params
|
||||
if thisInput.Kind() == reflect.Ptr {
|
||||
if thisInput.Elem().Kind() == reflect.Struct {
|
||||
typ := thisInput.Elem()
|
||||
a := reflect.New(typ)
|
||||
s := reflect.Indirect(a).Interface()
|
||||
name := typ.Name()
|
||||
packageName := getPackageName(thisInput.String())
|
||||
b.AddStructToGenerateTS(packageName, name, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Process struct params
|
||||
if thisInput.Kind() == reflect.Struct {
|
||||
a := reflect.New(thisInput)
|
||||
s := reflect.Indirect(a).Interface()
|
||||
name := thisInput.Name()
|
||||
packageName := getPackageName(thisInput.String())
|
||||
b.AddStructToGenerateTS(packageName, name, s)
|
||||
}
|
||||
|
||||
inputs = append(inputs, thisParam)
|
||||
}
|
||||
|
||||
boundMethod.Inputs = inputs
|
||||
|
||||
// Iterate outputs
|
||||
// TODO: Determine what to do about limiting return types
|
||||
// especially around errors.
|
||||
outputParamCount := methodType.NumOut()
|
||||
var outputs []*Parameter
|
||||
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
|
||||
output := methodType.Out(outputIndex)
|
||||
thisParam := newParameter("", output)
|
||||
|
||||
thisOutput := output
|
||||
|
||||
if thisOutput.Kind() == reflect.Slice {
|
||||
thisOutput = thisOutput.Elem()
|
||||
}
|
||||
|
||||
// Process struct pointer params
|
||||
if thisOutput.Kind() == reflect.Ptr {
|
||||
if thisOutput.Elem().Kind() == reflect.Struct {
|
||||
typ := thisOutput.Elem()
|
||||
a := reflect.New(typ)
|
||||
s := reflect.Indirect(a).Interface()
|
||||
name := typ.Name()
|
||||
packageName := getPackageName(thisOutput.String())
|
||||
b.AddStructToGenerateTS(packageName, name, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Process struct params
|
||||
if thisOutput.Kind() == reflect.Struct {
|
||||
a := reflect.New(thisOutput)
|
||||
s := reflect.Indirect(a).Interface()
|
||||
name := thisOutput.Name()
|
||||
packageName := getPackageName(thisOutput.String())
|
||||
b.AddStructToGenerateTS(packageName, name, s)
|
||||
}
|
||||
|
||||
outputs = append(outputs, thisParam)
|
||||
}
|
||||
boundMethod.Outputs = outputs
|
||||
|
||||
// Save method in result
|
||||
result = append(result, boundMethod)
|
||||
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getPackageName(in string) string {
|
||||
result := strings.Split(in, ".")[0]
|
||||
result = strings.ReplaceAll(result, "[]", "")
|
||||
result = strings.ReplaceAll(result, "*", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func getSplitReturn(in string) (string, string) {
|
||||
result := strings.SplitN(in, ".", 2)
|
||||
return result[0], result[1]
|
||||
}
|
||||
|
||||
func hasElements(typ reflect.Type) bool {
|
||||
kind := typ.Kind()
|
||||
return kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map
|
||||
}
|
||||
5
vendor/github.com/wailsapp/wails/v2/internal/frontend/calls.go
generated
vendored
Normal file
5
vendor/github.com/wailsapp/wails/v2/internal/frontend/calls.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package frontend
|
||||
|
||||
type Calls interface {
|
||||
Callback(message string)
|
||||
}
|
||||
33
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.h
generated
vendored
Normal file
33
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.h
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef AppDelegate_h
|
||||
#define AppDelegate_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WailsContext.h"
|
||||
|
||||
@interface AppDelegate : NSResponder <NSApplicationDelegate, NSTouchBarProvider>
|
||||
|
||||
@property bool alwaysOnTop;
|
||||
@property bool startHidden;
|
||||
@property (retain) NSString* singleInstanceUniqueId;
|
||||
@property bool singleInstanceLockEnabled;
|
||||
@property bool startFullscreen;
|
||||
@property (retain) WailsWindow* mainWindow;
|
||||
|
||||
@end
|
||||
|
||||
extern void HandleOpenFile(char *);
|
||||
|
||||
extern void HandleSecondInstanceData(char * message);
|
||||
|
||||
void SendDataToFirstInstance(char * singleInstanceUniqueId, char * text);
|
||||
|
||||
char* GetMacOsNativeTempDir();
|
||||
|
||||
#endif /* AppDelegate_h */
|
||||
100
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.m
generated
vendored
Normal file
100
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.m
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "CustomProtocol.h"
|
||||
#import "message.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
|
||||
{
|
||||
const char* utf8FileName = filename.UTF8String;
|
||||
HandleOpenFile((char*)utf8FileName);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<NSUserActivityRestoring>> * _Nullable))restorationHandler {
|
||||
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
NSURL *url = userActivity.webpageURL;
|
||||
if (url) {
|
||||
HandleOpenURL((char*)[[url absoluteString] UTF8String]);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
processMessage("Q");
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
if (self.alwaysOnTop) {
|
||||
[self.mainWindow setLevel:NSFloatingWindowLevel];
|
||||
}
|
||||
if ( !self.startHidden ) {
|
||||
[self.mainWindow makeKeyAndOrderFront:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
if ( self.startFullscreen ) {
|
||||
NSWindowCollectionBehavior behaviour = [self.mainWindow collectionBehavior];
|
||||
behaviour |= NSWindowCollectionBehaviorFullScreenPrimary;
|
||||
[self.mainWindow setCollectionBehavior:behaviour];
|
||||
[self.mainWindow toggleFullScreen:nil];
|
||||
}
|
||||
|
||||
if ( self.singleInstanceLockEnabled ) {
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleSecondInstanceNotification:) name:self.singleInstanceUniqueId object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void SendDataToFirstInstance(char * singleInstanceUniqueId, char * message) {
|
||||
// we pass message in object because otherwise sandboxing will prevent us from sending it https://developer.apple.com/forums/thread/129437
|
||||
NSString * myString = [NSString stringWithUTF8String:message];
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:[NSString stringWithUTF8String:singleInstanceUniqueId]
|
||||
object:(__bridge const void *)(myString)
|
||||
userInfo:nil
|
||||
deliverImmediately:YES];
|
||||
}
|
||||
|
||||
char* GetMacOsNativeTempDir() {
|
||||
NSString *tempDir = NSTemporaryDirectory();
|
||||
char *copy = strdup([tempDir UTF8String]);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (void)handleSecondInstanceNotification:(NSNotification *)note;
|
||||
{
|
||||
if (note.object != nil) {
|
||||
NSString * message = (__bridge NSString *)note.object;
|
||||
const char* utf8Message = message.UTF8String;
|
||||
HandleSecondInstanceData((char*)utf8Message);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@synthesize touchBar;
|
||||
|
||||
@end
|
||||
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.h
generated
vendored
Normal file
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.h
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Application.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef Application_h
|
||||
#define Application_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WailsContext.h"
|
||||
|
||||
#define WindowStartsNormal 0
|
||||
#define WindowStartsMaximised 1
|
||||
#define WindowStartsMinimised 2
|
||||
#define WindowStartsFullscreen 3
|
||||
|
||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int contentProtection, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId, bool enableDragAndDrop, bool disableWebViewDragAndDrop);
|
||||
void Run(void*, const char* url);
|
||||
|
||||
void SetTitle(void* ctx, const char *title);
|
||||
void Center(void* ctx);
|
||||
void SetSize(void* ctx, int width, int height);
|
||||
void SetAlwaysOnTop(void* ctx, int onTop);
|
||||
void SetMinSize(void* ctx, int width, int height);
|
||||
void SetMaxSize(void* ctx, int width, int height);
|
||||
void SetPosition(void* ctx, int x, int y);
|
||||
void Fullscreen(void* ctx);
|
||||
void UnFullscreen(void* ctx);
|
||||
void Minimise(void* ctx);
|
||||
void UnMinimise(void* ctx);
|
||||
void ToggleMaximise(void* ctx);
|
||||
void Maximise(void* ctx);
|
||||
void UnMaximise(void* ctx);
|
||||
void Hide(void* ctx);
|
||||
void Show(void* ctx);
|
||||
void HideApplication(void* ctx);
|
||||
void ShowApplication(void* ctx);
|
||||
void SetBackgroundColour(void* ctx, int r, int g, int b, int a);
|
||||
void ExecJS(void* ctx, const char*);
|
||||
void Quit(void*);
|
||||
void WindowPrint(void* ctx);
|
||||
|
||||
const char* GetSize(void *ctx);
|
||||
const char* GetPosition(void *ctx);
|
||||
const bool IsFullScreen(void *ctx);
|
||||
const bool IsMinimised(void *ctx);
|
||||
const bool IsMaximised(void *ctx);
|
||||
|
||||
/* Dialogs */
|
||||
|
||||
void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength);
|
||||
void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters);
|
||||
void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters);
|
||||
|
||||
/* Application Menu */
|
||||
void* NewMenu(const char* name);
|
||||
void AppendSubmenu(void* parent, void* child);
|
||||
void AppendRole(void *inctx, void *inMenu, int role);
|
||||
void SetAsApplicationMenu(void *inctx, void *inMenu);
|
||||
void UpdateApplicationMenu(void *inctx);
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen);
|
||||
void* AppendMenuItem(void* inctx, void* nsmenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID);
|
||||
void AppendSeparator(void* inMenu);
|
||||
void UpdateMenuItem(void* nsmenuitem, int checked);
|
||||
void RunMainLoop(void);
|
||||
void ReleaseContext(void *inctx);
|
||||
|
||||
/* Notifications */
|
||||
bool IsNotificationAvailable(void *inctx);
|
||||
bool CheckBundleIdentifier(void *inctx);
|
||||
bool EnsureDelegateInitialized(void *inctx);
|
||||
void RequestNotificationAuthorization(void *inctx, int channelID);
|
||||
void CheckNotificationAuthorization(void *inctx, int channelID);
|
||||
void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json);
|
||||
void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json);
|
||||
void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle);
|
||||
void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId);
|
||||
void RemoveAllPendingNotifications(void *inctx);
|
||||
void RemovePendingNotification(void *inctx, const char *identifier);
|
||||
void RemoveAllDeliveredNotifications(void *inctx);
|
||||
void RemoveDeliveredNotification(void *inctx, const char *identifier);
|
||||
|
||||
NSString* safeInit(const char* input);
|
||||
|
||||
#endif /* Application_h */
|
||||
501
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.m
generated
vendored
Normal file
501
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Application.m
generated
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// Application.m
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WailsContext.h"
|
||||
#import "Application.h"
|
||||
#import "AppDelegate.h"
|
||||
#import "WindowDelegate.h"
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsMenuItem.h"
|
||||
|
||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int contentProtection, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId, bool enableDragAndDrop, bool disableWebViewDragAndDrop) {
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
WailsContext *result = [WailsContext new];
|
||||
|
||||
result.devtoolsEnabled = devtoolsEnabled;
|
||||
result.defaultContextMenuEnabled = defaultContextMenuEnabled;
|
||||
|
||||
if ( windowStartState == WindowStartsFullscreen ) {
|
||||
fullscreen = 1;
|
||||
}
|
||||
|
||||
[result CreateWindow:width :height :frameless :resizable :zoomable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled :preferences :enableDragAndDrop :disableWebViewDragAndDrop];
|
||||
[result SetTitle:safeInit(title)];
|
||||
[result Center];
|
||||
|
||||
if (contentProtection == 1 &&
|
||||
[result.mainWindow respondsToSelector:@selector(setSharingType:)]) {
|
||||
[result.mainWindow setSharingType:NSWindowSharingNone];
|
||||
}
|
||||
|
||||
switch( windowStartState ) {
|
||||
case WindowStartsMaximised:
|
||||
[result.mainWindow zoom:nil];
|
||||
break;
|
||||
case WindowStartsMinimised:
|
||||
//TODO: Can you start a mac app minimised?
|
||||
break;
|
||||
}
|
||||
|
||||
if ( startsHidden == 1 ) {
|
||||
result.startHidden = true;
|
||||
}
|
||||
|
||||
if ( fullscreen == 1 ) {
|
||||
result.startFullscreen = true;
|
||||
}
|
||||
|
||||
if ( singleInstanceLockEnabled == 1 ) {
|
||||
result.singleInstanceLockEnabled = true;
|
||||
result.singleInstanceUniqueId = safeInit(singleInstanceUniqueId);
|
||||
}
|
||||
|
||||
result.alwaysOnTop = alwaysOnTop;
|
||||
result.hideOnClose = hideWindowOnClose;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExecJS(void* inctx, const char *script) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *nsscript = safeInit(script);
|
||||
ON_MAIN_THREAD(
|
||||
[ctx ExecJS:nsscript];
|
||||
[nsscript release];
|
||||
);
|
||||
}
|
||||
|
||||
void SetTitle(void* inctx, const char *title) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetTitle:_title];
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void SetBackgroundColour(void *inctx, int r, int g, int b, int a) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetBackgroundColour:r :g :b :a];
|
||||
);
|
||||
}
|
||||
|
||||
void SetSize(void* inctx, int width, int height) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetSize:width :height];
|
||||
);
|
||||
}
|
||||
|
||||
void SetAlwaysOnTop(void* inctx, int onTop) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetAlwaysOnTop:onTop];
|
||||
);
|
||||
}
|
||||
|
||||
void SetMinSize(void* inctx, int width, int height) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetMinSize:width :height];
|
||||
);
|
||||
}
|
||||
|
||||
void SetMaxSize(void* inctx, int width, int height) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetMaxSize:width :height];
|
||||
);
|
||||
}
|
||||
|
||||
void SetPosition(void* inctx, int x, int y) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SetPosition:x :y];
|
||||
);
|
||||
}
|
||||
|
||||
void Center(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Center];
|
||||
);
|
||||
}
|
||||
|
||||
void Fullscreen(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Fullscreen];
|
||||
);
|
||||
}
|
||||
|
||||
void UnFullscreen(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx UnFullscreen];
|
||||
);
|
||||
}
|
||||
|
||||
void Minimise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Minimise];
|
||||
);
|
||||
}
|
||||
|
||||
void UnMinimise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx UnMinimise];
|
||||
);
|
||||
}
|
||||
|
||||
void Maximise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Maximise];
|
||||
);
|
||||
}
|
||||
|
||||
void ToggleMaximise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx ToggleMaximise];
|
||||
);
|
||||
}
|
||||
|
||||
const char* GetSize(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSRect frame = [ctx.mainWindow frame];
|
||||
NSString *result = [NSString stringWithFormat:@"%d,%d", (int)frame.size.width, (int)frame.size.height];
|
||||
return [result UTF8String];
|
||||
}
|
||||
|
||||
const char* GetPosition(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSScreen* screen = [ctx getCurrentScreen];
|
||||
NSRect windowFrame = [ctx.mainWindow frame];
|
||||
NSRect screenFrame = [screen visibleFrame];
|
||||
int x = windowFrame.origin.x - screenFrame.origin.x;
|
||||
int y = windowFrame.origin.y - screenFrame.origin.y;
|
||||
y = screenFrame.size.height - y - windowFrame.size.height;
|
||||
NSString *result = [NSString stringWithFormat:@"%d,%d",x,y];
|
||||
return [result UTF8String];
|
||||
}
|
||||
|
||||
const bool IsFullScreen(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
return [ctx IsFullScreen];
|
||||
}
|
||||
|
||||
const bool IsMinimised(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
return [ctx IsMinimised];
|
||||
}
|
||||
|
||||
const bool IsMaximised(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
return [ctx IsMaximised];
|
||||
}
|
||||
|
||||
void UnMaximise(void* inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx UnMaximise];
|
||||
);
|
||||
}
|
||||
|
||||
void Quit(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
[NSApp stop:ctx];
|
||||
[NSApp abortModal];
|
||||
}
|
||||
|
||||
void Hide(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Hide];
|
||||
);
|
||||
}
|
||||
|
||||
void Show(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx Show];
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void HideApplication(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx HideApplication];
|
||||
);
|
||||
}
|
||||
|
||||
void ShowApplication(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
[ctx ShowApplication];
|
||||
);
|
||||
}
|
||||
|
||||
NSString* safeInit(const char* input) {
|
||||
NSString *result = nil;
|
||||
if (input != nil) {
|
||||
result = [NSString stringWithUTF8String:input];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
|
||||
NSString *_dialogType = safeInit(dialogType);
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_message = safeInit(message);
|
||||
NSString *_button1 = safeInit(button1);
|
||||
NSString *_button2 = safeInit(button2);
|
||||
NSString *_button3 = safeInit(button3);
|
||||
NSString *_button4 = safeInit(button4);
|
||||
NSString *_defaultButton = safeInit(defaultButton);
|
||||
NSString *_cancelButton = safeInit(cancelButton);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
[ctx MessageDialog:_dialogType :_title :_message :_button1 :_button2 :_button3 :_button4 :_defaultButton :_cancelButton :iconData :iconDataLength];
|
||||
)
|
||||
}
|
||||
|
||||
void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters) {
|
||||
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_defaultFilename = safeInit(defaultFilename);
|
||||
NSString *_defaultDirectory = safeInit(defaultDirectory);
|
||||
NSString *_filters = safeInit(filters);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
[ctx OpenFileDialog:_title :_defaultFilename :_defaultDirectory :allowDirectories :allowFiles :canCreateDirectories :treatPackagesAsDirectories :resolveAliases :showHiddenFiles :allowMultipleSelection :_filters];
|
||||
)
|
||||
}
|
||||
|
||||
void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters) {
|
||||
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_defaultFilename = safeInit(defaultFilename);
|
||||
NSString *_defaultDirectory = safeInit(defaultDirectory);
|
||||
NSString *_filters = safeInit(filters);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
[ctx SaveFileDialog:_title :_defaultFilename :_defaultDirectory :canCreateDirectories :treatPackagesAsDirectories :showHiddenFiles :_filters];
|
||||
)
|
||||
}
|
||||
|
||||
void AppendRole(void *inctx, void *inMenu, int role) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
[menu appendRole :ctx :role];
|
||||
}
|
||||
|
||||
void* NewMenu(const char *name) {
|
||||
NSString *title = @"";
|
||||
if (name != nil) {
|
||||
title = [NSString stringWithUTF8String:name];
|
||||
}
|
||||
WailsMenu *result = [[WailsMenu new] initWithNSTitle:title];
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppendSubmenu(void* inparent, void* inchild) {
|
||||
WailsMenu *parent = (__bridge WailsMenu*) inparent;
|
||||
WailsMenu *child = (__bridge WailsMenu*) inchild;
|
||||
[parent appendSubmenu:child];
|
||||
}
|
||||
|
||||
void SetAsApplicationMenu(void *inctx, void *inMenu) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
ctx.applicationMenu = menu;
|
||||
}
|
||||
|
||||
void UpdateApplicationMenu(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app setMainMenu:ctx.applicationMenu];
|
||||
)
|
||||
}
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
NSString *_description = safeInit(description);
|
||||
|
||||
[ctx SetAbout :_title :_description :imagedata :datalen];
|
||||
}
|
||||
|
||||
void* AppendMenuItem(void* inctx, void* inMenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
NSString *_label = safeInit(label);
|
||||
NSString *_shortcutKey = safeInit(shortcutKey);
|
||||
|
||||
return [menu AppendMenuItem:ctx :_label :_shortcutKey :modifiers :disabled :checked :menuItemID];
|
||||
}
|
||||
|
||||
void UpdateMenuItem(void* nsmenuitem, int checked) {
|
||||
ON_MAIN_THREAD(
|
||||
WailsMenuItem *menuItem = (__bridge WailsMenuItem*) nsmenuitem;
|
||||
[menuItem setState:(checked == 1?NSControlStateValueOn:NSControlStateValueOff)];
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
void AppendSeparator(void* inMenu) {
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
[menu AppendSeparator];
|
||||
}
|
||||
|
||||
|
||||
bool IsNotificationAvailable(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
return [ctx IsNotificationAvailable];
|
||||
}
|
||||
|
||||
bool CheckBundleIdentifier(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
return [ctx CheckBundleIdentifier];
|
||||
}
|
||||
|
||||
bool EnsureDelegateInitialized(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
return [ctx EnsureDelegateInitialized];
|
||||
}
|
||||
|
||||
void RequestNotificationAuthorization(void *inctx, int channelID) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RequestNotificationAuthorization:channelID];
|
||||
}
|
||||
|
||||
void CheckNotificationAuthorization(void *inctx, int channelID) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx CheckNotificationAuthorization:channelID];
|
||||
}
|
||||
|
||||
void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx SendNotification:channelID :identifier :title :subtitle :body :data_json];
|
||||
}
|
||||
|
||||
void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
|
||||
[ctx SendNotificationWithActions:channelID :identifier :title :subtitle :body :categoryId :actions_json];
|
||||
}
|
||||
|
||||
void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
|
||||
[ctx RegisterNotificationCategory:channelID :categoryId :actions_json :hasReplyField :replyPlaceholder :replyButtonTitle];
|
||||
}
|
||||
|
||||
void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
|
||||
[ctx RemoveNotificationCategory:channelID :categoryId];
|
||||
}
|
||||
|
||||
void RemoveAllPendingNotifications(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemoveAllPendingNotifications];
|
||||
}
|
||||
|
||||
void RemovePendingNotification(void *inctx, const char *identifier) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemovePendingNotification:identifier];
|
||||
}
|
||||
|
||||
void RemoveAllDeliveredNotifications(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemoveAllDeliveredNotifications];
|
||||
}
|
||||
|
||||
void RemoveDeliveredNotification(void *inctx, const char *identifier) {
|
||||
WailsContext *ctx = (__bridge WailsContext*)inctx;
|
||||
[ctx RemoveDeliveredNotification:identifier];
|
||||
}
|
||||
|
||||
|
||||
void Run(void *inctx, const char* url) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
AppDelegate* delegate = [AppDelegate new];
|
||||
[app setDelegate:(id)delegate];
|
||||
ctx.appdelegate = delegate;
|
||||
delegate.mainWindow = ctx.mainWindow;
|
||||
delegate.alwaysOnTop = ctx.alwaysOnTop;
|
||||
delegate.startHidden = ctx.startHidden;
|
||||
delegate.singleInstanceLockEnabled = ctx.singleInstanceLockEnabled;
|
||||
delegate.singleInstanceUniqueId = ctx.singleInstanceUniqueId;
|
||||
delegate.startFullscreen = ctx.startFullscreen;
|
||||
|
||||
NSString *_url = safeInit(url);
|
||||
[ctx loadRequest:_url];
|
||||
[_url release];
|
||||
|
||||
[app setMainMenu:ctx.applicationMenu];
|
||||
}
|
||||
|
||||
void RunMainLoop(void) {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app run];
|
||||
}
|
||||
|
||||
void ReleaseContext(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
[ctx release];
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/q/33319295
|
||||
void WindowPrint(void *inctx) {
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
if (@available(macOS 11.0, *)) {
|
||||
ON_MAIN_THREAD(
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WKWebView* webView = ctx.webview;
|
||||
|
||||
// I think this should be exposed as a config
|
||||
// It directly affects the printed output/PDF
|
||||
NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo];
|
||||
pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic;
|
||||
pInfo.verticalPagination = NSPrintingPaginationModeAutomatic;
|
||||
pInfo.verticallyCentered = YES;
|
||||
pInfo.horizontallyCentered = YES;
|
||||
pInfo.orientation = NSPaperOrientationLandscape;
|
||||
pInfo.leftMargin = 0;
|
||||
pInfo.rightMargin = 0;
|
||||
pInfo.topMargin = 0;
|
||||
pInfo.bottomMargin = 0;
|
||||
|
||||
NSPrintOperation *po = [webView printOperationWithPrintInfo:pInfo];
|
||||
po.showsPrintPanel = YES;
|
||||
po.showsProgressPanel = YES;
|
||||
|
||||
po.view.frame = webView.bounds;
|
||||
|
||||
[po runOperationModalForWindow:ctx.mainWindow delegate:ctx.mainWindow.delegate didRunSelector:nil contextInfo:nil];
|
||||
)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.h
generated
vendored
Normal file
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.h
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef CustomProtocol_h
|
||||
#define CustomProtocol_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
extern void HandleOpenURL(char*);
|
||||
|
||||
@interface CustomProtocolSchemeHandler : NSObject
|
||||
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
|
||||
@end
|
||||
|
||||
void StartCustomProtocolHandler(void);
|
||||
|
||||
#endif /* CustomProtocol_h */
|
||||
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.m
generated
vendored
Normal file
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/CustomProtocol.m
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "CustomProtocol.h"
|
||||
|
||||
@implementation CustomProtocolSchemeHandler
|
||||
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
|
||||
[event paramDescriptorForKeyword:keyDirectObject];
|
||||
|
||||
NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
|
||||
|
||||
HandleOpenURL((char*)[[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]);
|
||||
}
|
||||
@end
|
||||
|
||||
void StartCustomProtocolHandler(void) {
|
||||
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
|
||||
|
||||
[appleEventManager setEventHandler:[CustomProtocolSchemeHandler class]
|
||||
andSelector:@selector(handleGetURLEvent:withReplyEvent:)
|
||||
forEventClass:kInternetEventClass
|
||||
andEventID: kAEGetURL];
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Role.h
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/Role.h
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Role.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 24/10/21.
|
||||
//
|
||||
|
||||
#ifndef Role_h
|
||||
#define Role_h
|
||||
|
||||
typedef int Role;
|
||||
|
||||
static const Role AppMenu = 1;
|
||||
static const Role EditMenu = 2;
|
||||
static const Role WindowMenu = 3;
|
||||
|
||||
#endif /* Role_h */
|
||||
18
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.h
generated
vendored
Normal file
18
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.h
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// WailsAlert.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 20/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsAlert_h
|
||||
#define WailsAlert_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface WailsAlert : NSAlert
|
||||
- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton;
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsAlert_h */
|
||||
31
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.m
generated
vendored
Normal file
31
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsAlert.m
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WailsAlert.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 20/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "WailsAlert.h"
|
||||
|
||||
@implementation WailsAlert
|
||||
|
||||
- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton {
|
||||
if( text == nil ) {
|
||||
return;
|
||||
}
|
||||
NSButton *button = [self addButtonWithTitle:text];
|
||||
if( defaultButton != nil && [text isEqualToString:defaultButton]) {
|
||||
[button setKeyEquivalent:@"\r"];
|
||||
} else if( cancelButton != nil && [text isEqualToString:cancelButton]) {
|
||||
[button setKeyEquivalent:@"\033"];
|
||||
} else {
|
||||
[button setKeyEquivalent:@""];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
123
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.h
generated
vendored
Normal file
123
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.h
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// WailsContext.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsContext_h
|
||||
#define WailsContext_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import "WailsWebView.h"
|
||||
|
||||
#if __has_include(<UniformTypeIdentifiers/UTType.h>)
|
||||
#define USE_NEW_FILTERS
|
||||
#import <UniformTypeIdentifiers/UTType.h>
|
||||
#endif
|
||||
|
||||
#define ON_MAIN_THREAD(str) dispatch_async(dispatch_get_main_queue(), ^{ str; });
|
||||
#define unicode(input) [NSString stringWithFormat:@"%C", input]
|
||||
|
||||
@interface WailsWindow : NSWindow
|
||||
|
||||
@property NSSize userMinSize;
|
||||
@property NSSize userMaxSize;
|
||||
|
||||
- (BOOL) canBecomeKeyWindow;
|
||||
- (void) applyWindowConstraints;
|
||||
- (void) disableWindowConstraints;
|
||||
@end
|
||||
|
||||
@interface WailsContext : NSObject <WKURLSchemeHandler,WKScriptMessageHandler,WKNavigationDelegate,WKUIDelegate>
|
||||
|
||||
@property (retain) WailsWindow* mainWindow;
|
||||
@property (retain) WailsWebView* webview;
|
||||
@property (nonatomic, assign) id appdelegate;
|
||||
|
||||
@property bool hideOnClose;
|
||||
@property bool shuttingDown;
|
||||
@property bool startHidden;
|
||||
@property bool startFullscreen;
|
||||
|
||||
@property bool singleInstanceLockEnabled;
|
||||
@property (retain) NSString* singleInstanceUniqueId;
|
||||
|
||||
@property (retain) NSEvent* mouseEvent;
|
||||
|
||||
@property bool alwaysOnTop;
|
||||
|
||||
@property bool devtoolsEnabled;
|
||||
@property bool defaultContextMenuEnabled;
|
||||
|
||||
@property (retain) WKUserContentController* userContentController;
|
||||
|
||||
@property (retain) NSMenu* applicationMenu;
|
||||
|
||||
@property (retain) NSImage* aboutImage;
|
||||
@property (retain) NSString* aboutTitle;
|
||||
@property (retain) NSString* aboutDescription;
|
||||
|
||||
struct Preferences {
|
||||
bool *tabFocusesLinks;
|
||||
bool *textInteractionEnabled;
|
||||
bool *fullscreenEnabled;
|
||||
};
|
||||
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop;
|
||||
- (void) SetSize:(int)width :(int)height;
|
||||
- (void) SetPosition:(int)x :(int) y;
|
||||
- (void) SetMinSize:(int)minWidth :(int)minHeight;
|
||||
- (void) SetMaxSize:(int)maxWidth :(int)maxHeight;
|
||||
- (void) SetTitle:(NSString*)title;
|
||||
- (void) SetAlwaysOnTop:(int)onTop;
|
||||
- (void) Center;
|
||||
- (void) Fullscreen;
|
||||
- (void) UnFullscreen;
|
||||
- (bool) IsFullScreen;
|
||||
- (void) Minimise;
|
||||
- (void) UnMinimise;
|
||||
- (bool) IsMinimised;
|
||||
- (void) Maximise;
|
||||
- (void) ToggleMaximise;
|
||||
- (void) UnMaximise;
|
||||
- (bool) IsMaximised;
|
||||
- (void) SetBackgroundColour:(int)r :(int)g :(int)b :(int)a;
|
||||
- (void) HideMouse;
|
||||
- (void) ShowMouse;
|
||||
- (void) Hide;
|
||||
- (void) Show;
|
||||
- (void) HideApplication;
|
||||
- (void) ShowApplication;
|
||||
- (void) Quit;
|
||||
|
||||
- (void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength;
|
||||
- (void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters;
|
||||
- (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters;
|
||||
|
||||
- (bool) IsNotificationAvailable;
|
||||
- (bool) CheckBundleIdentifier;
|
||||
- (bool) EnsureDelegateInitialized;
|
||||
- (void) RequestNotificationAuthorization:(int)channelID;
|
||||
- (void) CheckNotificationAuthorization:(int)channelID;
|
||||
- (void) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON;
|
||||
- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)actionsJSON;
|
||||
- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle;
|
||||
- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId;
|
||||
- (void) RemoveAllPendingNotifications;
|
||||
- (void) RemovePendingNotification:(const char *)identifier;
|
||||
- (void) RemoveAllDeliveredNotifications;
|
||||
- (void) RemoveDeliveredNotification:(const char *)identifier;
|
||||
|
||||
- (void) loadRequest:(NSString*)url;
|
||||
- (void) ExecJS:(NSString*)script;
|
||||
- (NSScreen*) getCurrentScreen;
|
||||
|
||||
- (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen;
|
||||
- (void) dealloc;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsContext_h */
|
||||
1115
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.m
generated
vendored
Normal file
1115
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsContext.m
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.h
generated
vendored
Normal file
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.h
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// WailsMenu.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 25/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsMenu_h
|
||||
#define WailsMenu_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "Role.h"
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
@interface WailsMenu : NSMenu
|
||||
|
||||
//- (void) AddMenuByRole :(Role)role;
|
||||
- (WailsMenu*) initWithNSTitle :(NSString*)title;
|
||||
- (void) appendSubmenu :(WailsMenu*)child;
|
||||
- (void) appendRole :(WailsContext*)ctx :(Role)role;
|
||||
|
||||
- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags;
|
||||
- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID;
|
||||
- (void) AppendSeparator;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsMenu_h */
|
||||
340
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.m
generated
vendored
Normal file
340
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenu.m
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WailsMenu.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 25/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsMenuItem.h"
|
||||
#import "Role.h"
|
||||
|
||||
@implementation WailsMenu
|
||||
|
||||
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
|
||||
NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
|
||||
[result setKeyEquivalentModifierMask:flags];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
|
||||
NSMenuItem *result = [NSMenuItem new];
|
||||
if ( title != nil ) {
|
||||
[result setTitle:title];
|
||||
}
|
||||
if (selector != nil) {
|
||||
[result setAction:selector];
|
||||
}
|
||||
if (key) {
|
||||
[result setKeyEquivalent:key];
|
||||
}
|
||||
if( flags != 0 ) {
|
||||
[result setKeyEquivalentModifierMask:flags];
|
||||
}
|
||||
result.target = ctx;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key {
|
||||
return [self newMenuItem :title :selector :key :0];
|
||||
}
|
||||
|
||||
- (WailsMenu*) initWithNSTitle:(NSString *)title {
|
||||
if( title != nil ) {
|
||||
[super initWithTitle:title];
|
||||
} else {
|
||||
[self init];
|
||||
}
|
||||
[self setAutoenablesItems:NO];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) appendSubmenu :(WailsMenu*)child {
|
||||
NSMenuItem *childMenuItem = [[NSMenuItem new] autorelease];
|
||||
[childMenuItem setTitle:child.title];
|
||||
[self addItem:childMenuItem];
|
||||
[childMenuItem setSubmenu:child];
|
||||
}
|
||||
|
||||
- (void) appendRole :(WailsContext*)ctx :(Role)role {
|
||||
|
||||
switch(role) {
|
||||
case AppMenu:
|
||||
{
|
||||
NSString *appName = [NSRunningApplication currentApplication].localizedName;
|
||||
if( appName == nil ) {
|
||||
appName = [[NSProcessInfo processInfo] processName];
|
||||
}
|
||||
WailsMenu *appMenu = [[[WailsMenu new] initWithNSTitle:appName] autorelease];
|
||||
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
[appMenu addItem:[self newMenuItem:[@"Hide " stringByAppendingString:appName] :@selector(hide:) :@"h" :NSEventModifierFlagCommand]];
|
||||
[appMenu addItem:[self newMenuItem:@"Hide Others" :@selector(hideOtherApplications:) :@"h" :(NSEventModifierFlagOption | NSEventModifierFlagCommand)]];
|
||||
[appMenu addItem:[self newMenuItem:@"Show All" :@selector(unhideAllApplications:) :@""]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
id quitTitle = [@"Quit " stringByAppendingString:appName];
|
||||
NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
|
||||
quitMenuItem.target = ctx;
|
||||
[appMenu addItem:quitMenuItem];
|
||||
[self appendSubmenu:appMenu];
|
||||
break;
|
||||
}
|
||||
case EditMenu:
|
||||
{
|
||||
WailsMenu *editMenu = [[[WailsMenu new] initWithNSTitle:@"Edit"] autorelease];
|
||||
[editMenu addItem:[self newMenuItem:@"Undo" :@selector(undo:) :@"z" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Redo" :@selector(redo:) :@"z" :(NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
[editMenu addItem:[self newMenuItem:@"Cut" :@selector(cut:) :@"x" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Copy" :@selector(copy:) :@"c" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Paste" :@selector(paste:) :@"v" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[self newMenuItem:@"Paste and Match Style" :@selector(pasteAsRichText:) :@"v" :(NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
|
||||
[editMenu addItem:[self newMenuItem:@"Delete" :@selector(delete:) :[self accel:@"backspace"] :0]];
|
||||
[editMenu addItem:[self newMenuItem:@"Select All" :@selector(selectAll:) :@"a" :NSEventModifierFlagCommand]];
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
// NSMenuItem *speechMenuItem = [[NSMenuItem new] autorelease];
|
||||
// [speechMenuItem setTitle:@"Speech"];
|
||||
// [editMenu addItem:speechMenuItem];
|
||||
WailsMenu *speechMenu = [[[WailsMenu new] initWithNSTitle:@"Speech"] autorelease];
|
||||
[speechMenu addItem:[self newMenuItem:@"Start Speaking" :@selector(startSpeaking:) :@""]];
|
||||
[speechMenu addItem:[self newMenuItem:@"Stop Speaking" :@selector(stopSpeaking:) :@""]];
|
||||
[editMenu appendSubmenu:speechMenu];
|
||||
[self appendSubmenu:editMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
case WindowMenu:
|
||||
{
|
||||
WailsMenu *windowMenu = [[[WailsMenu new] initWithNSTitle:@"Window"] autorelease];
|
||||
[windowMenu addItem:[self newMenuItem:@"Minimize" :@selector(performMiniaturize:) :@"m" :NSEventModifierFlagCommand]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Zoom" :@selector(performZoom:) :@""]];
|
||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Full Screen" :@selector(enterFullScreenMode:) :@"f" :(NSEventModifierFlagControl | NSEventModifierFlagCommand)]];
|
||||
[self appendSubmenu:windowMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID {
|
||||
|
||||
NSString *nslabel = @"";
|
||||
if (label != nil ) {
|
||||
nslabel = label;
|
||||
}
|
||||
WailsMenuItem *menuItem = [WailsMenuItem new];
|
||||
|
||||
// Label
|
||||
menuItem.title = nslabel;
|
||||
|
||||
// Process callback
|
||||
menuItem.menuItemID = menuItemID;
|
||||
menuItem.action = @selector(handleClick);
|
||||
menuItem.target = menuItem;
|
||||
|
||||
// Shortcut
|
||||
if (shortcutKey != nil) {
|
||||
[menuItem setKeyEquivalent:[self accel:shortcutKey]];
|
||||
[menuItem setKeyEquivalentModifierMask:modifiers];
|
||||
}
|
||||
|
||||
// Enabled/Disabled
|
||||
[menuItem setEnabled:!disabled];
|
||||
|
||||
// Checked
|
||||
[menuItem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
|
||||
|
||||
[self addItem:menuItem];
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
- (void) AppendSeparator {
|
||||
[self addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
|
||||
- (NSString*) accel :(NSString*)key {
|
||||
|
||||
// Guard against no accelerator key
|
||||
if( key == NULL ) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
if( [key isEqualToString:@"backspace"] ) {
|
||||
return unicode(0x0008);
|
||||
}
|
||||
if( [key isEqualToString:@"tab"] ) {
|
||||
return unicode(0x0009);
|
||||
}
|
||||
if( [key isEqualToString:@"return"] ) {
|
||||
return unicode(0x000d);
|
||||
}
|
||||
if( [key isEqualToString:@"enter"] ) {
|
||||
return unicode(0x000d);
|
||||
}
|
||||
if( [key isEqualToString:@"escape"] ) {
|
||||
return unicode(0x001b);
|
||||
}
|
||||
if( [key isEqualToString:@"left"] ) {
|
||||
return unicode(0x001c);
|
||||
}
|
||||
if( [key isEqualToString:@"right"] ) {
|
||||
return unicode(0x001d);
|
||||
}
|
||||
if( [key isEqualToString:@"up"] ) {
|
||||
return unicode(0x001e);
|
||||
}
|
||||
if( [key isEqualToString:@"down"] ) {
|
||||
return unicode(0x001f);
|
||||
}
|
||||
if( [key isEqualToString:@"space"] ) {
|
||||
return unicode(0x0020);
|
||||
}
|
||||
if( [key isEqualToString:@"delete"] ) {
|
||||
return unicode(0x007f);
|
||||
}
|
||||
if( [key isEqualToString:@"home"] ) {
|
||||
return unicode(0x2196);
|
||||
}
|
||||
if( [key isEqualToString:@"end"] ) {
|
||||
return unicode(0x2198);
|
||||
}
|
||||
if( [key isEqualToString:@"page up"] ) {
|
||||
return unicode(0x21de);
|
||||
}
|
||||
if( [key isEqualToString:@"page down"] ) {
|
||||
return unicode(0x21df);
|
||||
}
|
||||
if( [key isEqualToString:@"f1"] ) {
|
||||
return unicode(0xf704);
|
||||
}
|
||||
if( [key isEqualToString:@"f2"] ) {
|
||||
return unicode(0xf705);
|
||||
}
|
||||
if( [key isEqualToString:@"f3"] ) {
|
||||
return unicode(0xf706);
|
||||
}
|
||||
if( [key isEqualToString:@"f4"] ) {
|
||||
return unicode(0xf707);
|
||||
}
|
||||
if( [key isEqualToString:@"f5"] ) {
|
||||
return unicode(0xf708);
|
||||
}
|
||||
if( [key isEqualToString:@"f6"] ) {
|
||||
return unicode(0xf709);
|
||||
}
|
||||
if( [key isEqualToString:@"f7"] ) {
|
||||
return unicode(0xf70a);
|
||||
}
|
||||
if( [key isEqualToString:@"f8"] ) {
|
||||
return unicode(0xf70b);
|
||||
}
|
||||
if( [key isEqualToString:@"f9"] ) {
|
||||
return unicode(0xf70c);
|
||||
}
|
||||
if( [key isEqualToString:@"f10"] ) {
|
||||
return unicode(0xf70d);
|
||||
}
|
||||
if( [key isEqualToString:@"f11"] ) {
|
||||
return unicode(0xf70e);
|
||||
}
|
||||
if( [key isEqualToString:@"f12"] ) {
|
||||
return unicode(0xf70f);
|
||||
}
|
||||
if( [key isEqualToString:@"f13"] ) {
|
||||
return unicode(0xf710);
|
||||
}
|
||||
if( [key isEqualToString:@"f14"] ) {
|
||||
return unicode(0xf711);
|
||||
}
|
||||
if( [key isEqualToString:@"f15"] ) {
|
||||
return unicode(0xf712);
|
||||
}
|
||||
if( [key isEqualToString:@"f16"] ) {
|
||||
return unicode(0xf713);
|
||||
}
|
||||
if( [key isEqualToString:@"f17"] ) {
|
||||
return unicode(0xf714);
|
||||
}
|
||||
if( [key isEqualToString:@"f18"] ) {
|
||||
return unicode(0xf715);
|
||||
}
|
||||
if( [key isEqualToString:@"f19"] ) {
|
||||
return unicode(0xf716);
|
||||
}
|
||||
if( [key isEqualToString:@"f20"] ) {
|
||||
return unicode(0xf717);
|
||||
}
|
||||
if( [key isEqualToString:@"f21"] ) {
|
||||
return unicode(0xf718);
|
||||
}
|
||||
if( [key isEqualToString:@"f22"] ) {
|
||||
return unicode(0xf719);
|
||||
}
|
||||
if( [key isEqualToString:@"f23"] ) {
|
||||
return unicode(0xf71a);
|
||||
}
|
||||
if( [key isEqualToString:@"f24"] ) {
|
||||
return unicode(0xf71b);
|
||||
}
|
||||
if( [key isEqualToString:@"f25"] ) {
|
||||
return unicode(0xf71c);
|
||||
}
|
||||
if( [key isEqualToString:@"f26"] ) {
|
||||
return unicode(0xf71d);
|
||||
}
|
||||
if( [key isEqualToString:@"f27"] ) {
|
||||
return unicode(0xf71e);
|
||||
}
|
||||
if( [key isEqualToString:@"f28"] ) {
|
||||
return unicode(0xf71f);
|
||||
}
|
||||
if( [key isEqualToString:@"f29"] ) {
|
||||
return unicode(0xf720);
|
||||
}
|
||||
if( [key isEqualToString:@"f30"] ) {
|
||||
return unicode(0xf721);
|
||||
}
|
||||
if( [key isEqualToString:@"f31"] ) {
|
||||
return unicode(0xf722);
|
||||
}
|
||||
if( [key isEqualToString:@"f32"] ) {
|
||||
return unicode(0xf723);
|
||||
}
|
||||
if( [key isEqualToString:@"f33"] ) {
|
||||
return unicode(0xf724);
|
||||
}
|
||||
if( [key isEqualToString:@"f34"] ) {
|
||||
return unicode(0xf725);
|
||||
}
|
||||
if( [key isEqualToString:@"f35"] ) {
|
||||
return unicode(0xf726);
|
||||
}
|
||||
// if( [key isEqualToString:@"Insert"] ) {
|
||||
// return unicode(0xf727);
|
||||
// }
|
||||
// if( [key isEqualToString:@"PrintScreen"] ) {
|
||||
// return unicode(0xf72e);
|
||||
// }
|
||||
// if( [key isEqualToString:@"ScrollLock"] ) {
|
||||
// return unicode(0xf72f);
|
||||
// }
|
||||
if( [key isEqualToString:@"numLock"] ) {
|
||||
return unicode(0xf739);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
22
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.h
generated
vendored
Normal file
22
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.h
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// WailsMenuItem.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 27/10/21.
|
||||
//
|
||||
|
||||
#ifndef WailsMenuItem_h
|
||||
#define WailsMenuItem_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface WailsMenuItem : NSMenuItem
|
||||
|
||||
@property int menuItemID;
|
||||
|
||||
- (void) handleClick;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WailsMenuItem_h */
|
||||
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.m
generated
vendored
Normal file
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsMenuItem.m
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WailsMenuItem.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 27/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "WailsMenuItem.h"
|
||||
#include "message.h"
|
||||
|
||||
|
||||
@implementation WailsMenuItem
|
||||
|
||||
- (void) handleClick {
|
||||
processCallback(self.menuItemID);
|
||||
}
|
||||
|
||||
@end
|
||||
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.h
generated
vendored
Normal file
14
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.h
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef WailsWebView_h
|
||||
#define WailsWebView_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
// We will override WKWebView, so we can detect file drop in obj-c
|
||||
// and grab their file path, to then inject into JS
|
||||
@interface WailsWebView : WKWebView
|
||||
@property bool disableWebViewDragAndDrop;
|
||||
@property bool enableDragAndDrop;
|
||||
@end
|
||||
|
||||
#endif /* WailsWebView_h */
|
||||
122
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.m
generated
vendored
Normal file
122
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WailsWebView.m
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
#import "WailsWebView.h"
|
||||
#import "message.h"
|
||||
|
||||
|
||||
@implementation WailsWebView
|
||||
@synthesize disableWebViewDragAndDrop;
|
||||
@synthesize enableDragAndDrop;
|
||||
|
||||
- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender
|
||||
{
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super prepareForDragOperation: sender];
|
||||
}
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [super prepareForDragOperation: sender];
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super performDragOperation: sender];
|
||||
}
|
||||
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
||||
// if no types, then we'll just let the WKWebView handle the drag-n-drop as normal
|
||||
NSArray<NSPasteboardType> * types = [pboard types];
|
||||
if( !types )
|
||||
return [super performDragOperation: sender];
|
||||
|
||||
// getting all NSURL types
|
||||
NSArray<Class> *url_class = @[[NSURL class]];
|
||||
NSDictionary *options = @{};
|
||||
NSArray<NSURL*> *files = [pboard readObjectsForClasses:url_class options:options];
|
||||
|
||||
// collecting all file paths
|
||||
NSMutableArray *files_strs = [[NSMutableArray alloc] init];
|
||||
for (NSURL *url in files)
|
||||
{
|
||||
const char *fs_path = [url fileSystemRepresentation]; //Will be UTF-8 encoded
|
||||
NSString *fs_path_str = [[NSString alloc] initWithCString:fs_path encoding:NSUTF8StringEncoding];
|
||||
[files_strs addObject:fs_path_str];
|
||||
// NSLog( @"performDragOperation: file path: %s", fs_path );
|
||||
}
|
||||
|
||||
NSString *joined=[files_strs componentsJoinedByString:@"\n"];
|
||||
|
||||
// Release the array of file paths
|
||||
[files_strs release];
|
||||
|
||||
int dragXLocation = [sender draggingLocation].x - [self frame].origin.x;
|
||||
int dragYLocation = [self frame].size.height - [sender draggingLocation].y; // Y coordinate is inverted, so we need to subtract from the height
|
||||
|
||||
// NSLog( @"draggingUpdated: X coord: %d", dragXLocation );
|
||||
// NSLog( @"draggingUpdated: Y coord: %d", dragYLocation );
|
||||
|
||||
NSString *message = [NSString stringWithFormat:@"DD:%d:%d:%@", dragXLocation, dragYLocation, joined];
|
||||
|
||||
const char* res = message.UTF8String;
|
||||
|
||||
processMessage(res);
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [super performDragOperation: sender];
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super draggingUpdated: sender];
|
||||
}
|
||||
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
||||
// if no types, then we'll just let the WKWebView handle the drag-n-drop as normal
|
||||
NSArray<NSPasteboardType> * types = [pboard types];
|
||||
if( !types ) {
|
||||
return [super draggingUpdated: sender];
|
||||
}
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
// we should call supper as otherwise events will not pass
|
||||
[super draggingUpdated: sender];
|
||||
|
||||
// pass NSDragOperationGeneric = 4 to show regular hover for drag and drop. As we want to ignore webkit behaviours that depends on webpage
|
||||
return 4;
|
||||
}
|
||||
|
||||
return [super draggingUpdated: sender];
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
|
||||
if ( !enableDragAndDrop ) {
|
||||
return [super draggingEntered: sender];
|
||||
}
|
||||
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
||||
// if no types, then we'll just let the WKWebView handle the drag-n-drop as normal
|
||||
NSArray<NSPasteboardType> * types = [pboard types];
|
||||
if( !types ) {
|
||||
return [super draggingEntered: sender];
|
||||
}
|
||||
|
||||
if ( disableWebViewDragAndDrop ) {
|
||||
// we should call supper as otherwise events will not pass
|
||||
[super draggingEntered: sender];
|
||||
|
||||
// pass NSDragOperationGeneric = 4 to show regular hover for drag and drop. As we want to ignore webkit behaviours that depends on webpage
|
||||
return 4;
|
||||
}
|
||||
|
||||
return [super draggingEntered: sender];
|
||||
}
|
||||
|
||||
@end
|
||||
25
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.h
generated
vendored
Normal file
25
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.h
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// WindowDelegate.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#ifndef WindowDelegate_h
|
||||
#define WindowDelegate_h
|
||||
|
||||
#import "WailsContext.h"
|
||||
|
||||
@interface WindowDelegate : NSObject <NSWindowDelegate>
|
||||
|
||||
@property bool hideOnClose;
|
||||
|
||||
@property (assign) WailsContext* ctx;
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)notification;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* WindowDelegate_h */
|
||||
38
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.m
generated
vendored
Normal file
38
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/WindowDelegate.m
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build darwin
|
||||
//
|
||||
// WindowDelegate.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "WindowDelegate.h"
|
||||
#import "message.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
@implementation WindowDelegate
|
||||
- (BOOL)windowShouldClose:(WailsWindow *)sender {
|
||||
if( self.hideOnClose ) {
|
||||
[NSApp hide:nil];
|
||||
return false;
|
||||
}
|
||||
processMessage("Q");
|
||||
return false;
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)notification {
|
||||
[self.ctx.mainWindow applyWindowConstraints];
|
||||
}
|
||||
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
[self.ctx.mainWindow disableWindowConstraints];
|
||||
}
|
||||
|
||||
- (NSApplicationPresentationOptions)window:(WailsWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
|
||||
return NSApplicationPresentationAutoHideToolbar | NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationFullScreen;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
24
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/browser.go
generated
vendored
Normal file
24
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/browser.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/utils"
|
||||
)
|
||||
|
||||
// BrowserOpenURL Use the default browser to open the url
|
||||
func (f *Frontend) BrowserOpenURL(rawURL string) {
|
||||
url, err := utils.ValidateAndSanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Specific method implementation
|
||||
if err := browser.OpenURL(url); err != nil {
|
||||
f.logger.Error("Unable to open default system browser")
|
||||
}
|
||||
}
|
||||
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/callbacks.go
generated
vendored
Normal file
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/callbacks.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
func (f *Frontend) handleCallback(menuItemID uint) error {
|
||||
menuItem := getMenuItemForID(menuItemID)
|
||||
if menuItem == nil {
|
||||
return errors.New("unknown menuItem ID: " + strconv.Itoa(int(menuItemID)))
|
||||
}
|
||||
|
||||
wailsMenuItem := menuItem.wailsMenuItem
|
||||
if wailsMenuItem.Type == menu.CheckboxType {
|
||||
wailsMenuItem.Checked = !wailsMenuItem.Checked
|
||||
C.UpdateMenuItem(menuItem.nsmenuitem, bool2Cint(wailsMenuItem.Checked))
|
||||
}
|
||||
if wailsMenuItem.Type == menu.RadioType {
|
||||
// Ignore if we clicked the item that is already checked
|
||||
if !wailsMenuItem.Checked {
|
||||
for _, item := range menuItem.radioGroupMembers {
|
||||
if item.wailsMenuItem.Checked {
|
||||
item.wailsMenuItem.Checked = false
|
||||
C.UpdateMenuItem(item.nsmenuitem, C.int(0))
|
||||
}
|
||||
}
|
||||
wailsMenuItem.Checked = true
|
||||
C.UpdateMenuItem(menuItem.nsmenuitem, C.int(1))
|
||||
}
|
||||
}
|
||||
if wailsMenuItem.Click != nil {
|
||||
go wailsMenuItem.Click(&menu.CallbackData{MenuItem: wailsMenuItem})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
34
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/calloc.go
generated
vendored
Normal file
34
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/calloc.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Calloc handles alloc/dealloc of C data
|
||||
type Calloc struct {
|
||||
pool []unsafe.Pointer
|
||||
}
|
||||
|
||||
// NewCalloc creates a new allocator
|
||||
func NewCalloc() Calloc {
|
||||
return Calloc{}
|
||||
}
|
||||
|
||||
// String creates a new C string and retains a reference to it
|
||||
func (c Calloc) String(in string) *C.char {
|
||||
result := C.CString(in)
|
||||
c.pool = append(c.pool, unsafe.Pointer(result))
|
||||
return result
|
||||
}
|
||||
|
||||
// Free frees all allocated C memory
|
||||
func (c Calloc) Free() {
|
||||
for _, str := range c.pool {
|
||||
C.free(str)
|
||||
}
|
||||
c.pool = []unsafe.Pointer{}
|
||||
}
|
||||
50
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/clipboard.go
generated
vendored
Normal file
50
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// ensureUTF8Env returns the current environment with LANG set to en_US.UTF-8
|
||||
// if it is not already set. This is needed because packaged macOS apps do not
|
||||
// inherit the terminal's LANG variable, causing pbpaste/pbcopy to default to
|
||||
// an ASCII-compatible encoding that mangles non-ASCII text.
|
||||
func ensureUTF8Env() []string {
|
||||
env := os.Environ()
|
||||
if _, ok := os.LookupEnv("LANG"); !ok {
|
||||
env = append(env, "LANG=en_US.UTF-8")
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardGetText() (string, error) {
|
||||
pasteCmd := exec.Command("pbpaste")
|
||||
pasteCmd.Env = ensureUTF8Env()
|
||||
out, err := pasteCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardSetText(text string) error {
|
||||
copyCmd := exec.Command("pbcopy")
|
||||
copyCmd.Env = ensureUTF8Env()
|
||||
in, err := copyCmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := in.Write([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyCmd.Wait()
|
||||
}
|
||||
196
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/dialog.go
generated
vendored
Normal file
196
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
// Obj-C dialog methods send the response to this channel
|
||||
var (
|
||||
messageDialogResponse = make(chan int)
|
||||
openFileDialogResponse = make(chan string)
|
||||
saveFileDialogResponse = make(chan string)
|
||||
dialogLock sync.Mutex
|
||||
)
|
||||
|
||||
// OpenDirectoryDialog prompts the user to select a directory
|
||||
func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
results, err := f.openDialog(&options, false, false, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var selected string
|
||||
if len(results) > 0 {
|
||||
selected = results[0]
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool, allowfiles bool, allowdirectories bool) ([]string, error) {
|
||||
dialogLock.Lock()
|
||||
defer dialogLock.Unlock()
|
||||
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
title := c.String(options.Title)
|
||||
defaultFilename := c.String(options.DefaultFilename)
|
||||
defaultDirectory := c.String(options.DefaultDirectory)
|
||||
allowDirectories := bool2Cint(allowdirectories)
|
||||
allowFiles := bool2Cint(allowfiles)
|
||||
canCreateDirectories := bool2Cint(options.CanCreateDirectories)
|
||||
treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
|
||||
resolveAliases := bool2Cint(options.ResolvesAliases)
|
||||
showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
|
||||
allowMultipleFileSelection := bool2Cint(multiple)
|
||||
|
||||
var filterStrings slicer.StringSlicer
|
||||
if options.Filters != nil {
|
||||
for _, filter := range options.Filters {
|
||||
thesePatterns := strings.Split(filter.Pattern, ";")
|
||||
for _, pattern := range thesePatterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern != "" {
|
||||
filterStrings.Add(pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
filterStrings.Deduplicate()
|
||||
}
|
||||
filters := filterStrings.Join(";")
|
||||
C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters))
|
||||
|
||||
result := <-openFileDialogResponse
|
||||
|
||||
var parsedResults []string
|
||||
err := json.Unmarshal([]byte(result), &parsedResults)
|
||||
|
||||
return parsedResults, err
|
||||
}
|
||||
|
||||
// OpenFileDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
results, err := f.openDialog(&options, false, true, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var selected string
|
||||
if len(results) > 0 {
|
||||
selected = results[0]
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// OpenMultipleFilesDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
|
||||
return f.openDialog(&options, true, true, false)
|
||||
}
|
||||
|
||||
// SaveFileDialog prompts the user to select a file
|
||||
func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
|
||||
dialogLock.Lock()
|
||||
defer dialogLock.Unlock()
|
||||
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
title := c.String(options.Title)
|
||||
defaultFilename := c.String(options.DefaultFilename)
|
||||
defaultDirectory := c.String(options.DefaultDirectory)
|
||||
canCreateDirectories := bool2Cint(options.CanCreateDirectories)
|
||||
treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
|
||||
showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
|
||||
|
||||
var filterStrings slicer.StringSlicer
|
||||
if options.Filters != nil {
|
||||
for _, filter := range options.Filters {
|
||||
thesePatterns := strings.Split(filter.Pattern, ";")
|
||||
for _, pattern := range thesePatterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern != "" {
|
||||
filterStrings.Add(pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
filterStrings.Deduplicate()
|
||||
}
|
||||
filters := filterStrings.Join(";")
|
||||
C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters))
|
||||
|
||||
result := <-saveFileDialogResponse
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MessageDialog show a message dialog to the user
|
||||
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
|
||||
dialogLock.Lock()
|
||||
defer dialogLock.Unlock()
|
||||
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
dialogType := c.String(string(options.Type))
|
||||
title := c.String(options.Title)
|
||||
message := c.String(options.Message)
|
||||
defaultButton := c.String(options.DefaultButton)
|
||||
cancelButton := c.String(options.CancelButton)
|
||||
const MaxButtons = 4
|
||||
var buttons [MaxButtons]*C.char
|
||||
for index, buttonText := range options.Buttons {
|
||||
if index == MaxButtons {
|
||||
return "", fmt.Errorf("max %d buttons supported (%d given)", MaxButtons, len(options.Buttons))
|
||||
}
|
||||
buttons[index] = c.String(buttonText)
|
||||
}
|
||||
|
||||
var iconData unsafe.Pointer
|
||||
var iconDataLength C.int
|
||||
if options.Icon != nil {
|
||||
iconData = unsafe.Pointer(&options.Icon[0])
|
||||
iconDataLength = C.int(len(options.Icon))
|
||||
}
|
||||
|
||||
C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength)
|
||||
|
||||
result := <-messageDialogResponse
|
||||
|
||||
selectedC := buttons[result]
|
||||
var selected string
|
||||
if selectedC != nil {
|
||||
selected = options.Buttons[result]
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
//export processMessageDialogResponse
|
||||
func processMessageDialogResponse(selection int) {
|
||||
messageDialogResponse <- selection
|
||||
}
|
||||
|
||||
//export processOpenFileDialogResponse
|
||||
func processOpenFileDialogResponse(cselection *C.char) {
|
||||
selection := C.GoString(cselection)
|
||||
openFileDialogResponse <- selection
|
||||
}
|
||||
|
||||
//export processSaveFileDialogResponse
|
||||
func processSaveFileDialogResponse(cselection *C.char) {
|
||||
selection := C.GoString(cselection)
|
||||
saveFileDialogResponse <- selection
|
||||
}
|
||||
525
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/frontend.go
generated
vendored
Normal file
525
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/frontend.go
generated
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "CustomProtocol.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
type bindingsMessage struct {
|
||||
message string
|
||||
source string
|
||||
isMainFrame bool
|
||||
}
|
||||
|
||||
var (
|
||||
messageBuffer = make(chan string, 100)
|
||||
bindingsMessageBuffer = make(chan *bindingsMessage, 100)
|
||||
requestBuffer = make(chan webview.Request, 100)
|
||||
callbackBuffer = make(chan uint, 10)
|
||||
openFilepathBuffer = make(chan string, 100)
|
||||
openUrlBuffer = make(chan string, 100)
|
||||
secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
|
||||
)
|
||||
|
||||
type Frontend struct {
|
||||
// Context
|
||||
ctx context.Context
|
||||
|
||||
frontendOptions *options.App
|
||||
logger *logger.Logger
|
||||
debug bool
|
||||
devtoolsEnabled bool
|
||||
|
||||
// Keep single instance lock file, so that it will not be GC and lock will exist while app is running
|
||||
singleInstanceLockFile *os.File
|
||||
|
||||
// Assets
|
||||
assets *assetserver.AssetServer
|
||||
startURL *url.URL
|
||||
|
||||
// main window handle
|
||||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
func (f *Frontend) RunMainLoop() {
|
||||
C.RunMainLoop()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowClose() {
|
||||
C.ReleaseContext(f.mainWindow.context)
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
result := &Frontend{
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
bindings: appBindings,
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
// this should be initialized as early as possible to handle first instance launch
|
||||
C.StartCustomProtocolHandler()
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
} else {
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
var err error
|
||||
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
|
||||
bindings, err = appBindings.ToJSON()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
appBindings.DB().UpdateObfuscatedCallMap()
|
||||
}
|
||||
|
||||
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
assets.ExpectedWebViewHost = result.startURL.Host
|
||||
result.assets = assets
|
||||
|
||||
go result.startRequestProcessor()
|
||||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startBindingsMessageProcessor()
|
||||
go result.startCallbackProcessor()
|
||||
go result.startFileOpenProcessor()
|
||||
go result.startUrlOpenProcessor()
|
||||
go result.startSecondInstanceProcessor()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Frontend) startFileOpenProcessor() {
|
||||
for filePath := range openFilepathBuffer {
|
||||
f.ProcessOpenFileEvent(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startUrlOpenProcessor() {
|
||||
for url := range openUrlBuffer {
|
||||
f.ProcessOpenUrlEvent(url)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startSecondInstanceProcessor() {
|
||||
for secondInstanceData := range secondInstanceBuffer {
|
||||
if f.frontendOptions.SingleInstanceLock != nil &&
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil {
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startMessageProcessor() {
|
||||
for message := range messageBuffer {
|
||||
f.processMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startBindingsMessageProcessor() {
|
||||
for msg := range bindingsMessageBuffer {
|
||||
// Apple webkit doesn't provide origin of main frame. So we can't verify in case of iFrame that top level origin is allowed.
|
||||
if !msg.isMainFrame {
|
||||
f.logger.Error("Blocked request from not main frame")
|
||||
continue
|
||||
}
|
||||
|
||||
origin, err := f.originValidator.GetOriginFromURL(msg.source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err))
|
||||
continue
|
||||
}
|
||||
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
continue
|
||||
}
|
||||
|
||||
f.processMessage(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startCallbackProcessor() {
|
||||
for callback := range callbackBuffer {
|
||||
err := f.handleCallback(callback)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReload() {
|
||||
f.ExecJS("runtime.WindowReload();")
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReloadApp() {
|
||||
f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSystemDefaultTheme() {
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetLightTheme() {
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetDarkTheme() {
|
||||
}
|
||||
|
||||
func (f *Frontend) Run(ctx context.Context) error {
|
||||
f.ctx = ctx
|
||||
|
||||
if f.frontendOptions.SingleInstanceLock != nil {
|
||||
f.singleInstanceLockFile = SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
|
||||
}
|
||||
|
||||
_debug := ctx.Value("debug")
|
||||
_devtoolsEnabled := ctx.Value("devtoolsEnabled")
|
||||
|
||||
if _debug != nil {
|
||||
f.debug = _debug.(bool)
|
||||
}
|
||||
if _devtoolsEnabled != nil {
|
||||
f.devtoolsEnabled = _devtoolsEnabled.(bool)
|
||||
}
|
||||
|
||||
mainWindow := NewWindow(f.frontendOptions, f.debug, f.devtoolsEnabled)
|
||||
f.mainWindow = mainWindow
|
||||
f.mainWindow.Center()
|
||||
|
||||
go func() {
|
||||
if f.frontendOptions.OnStartup != nil {
|
||||
f.frontendOptions.OnStartup(f.ctx)
|
||||
}
|
||||
}()
|
||||
mainWindow.Run(f.startURL.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowCenter() {
|
||||
f.mainWindow.Center()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) {
|
||||
f.mainWindow.SetAlwaysOnTop(onTop)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetPosition(x, y int) {
|
||||
f.mainWindow.SetPosition(x, y)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowGetPosition() (int, int) {
|
||||
return f.mainWindow.GetPosition()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSize(width, height int) {
|
||||
f.mainWindow.SetSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowGetSize() (int, int) {
|
||||
return f.mainWindow.Size()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetTitle(title string) {
|
||||
f.mainWindow.SetTitle(title)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowFullscreen() {
|
||||
f.mainWindow.Fullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnfullscreen() {
|
||||
f.mainWindow.UnFullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowShow() {
|
||||
f.mainWindow.Show()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowHide() {
|
||||
f.mainWindow.Hide()
|
||||
}
|
||||
|
||||
func (f *Frontend) Show() {
|
||||
f.mainWindow.ShowApplication()
|
||||
}
|
||||
|
||||
func (f *Frontend) Hide() {
|
||||
f.mainWindow.HideApplication()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowMaximise() {
|
||||
f.mainWindow.Maximise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowToggleMaximise() {
|
||||
f.mainWindow.ToggleMaximise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnmaximise() {
|
||||
f.mainWindow.UnMaximise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowMinimise() {
|
||||
f.mainWindow.Minimise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnminimise() {
|
||||
f.mainWindow.UnMinimise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetMinSize(width int, height int) {
|
||||
f.mainWindow.SetMinSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetMaxSize(width int, height int) {
|
||||
f.mainWindow.SetMaxSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
|
||||
if col == nil {
|
||||
return
|
||||
}
|
||||
f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
|
||||
}
|
||||
|
||||
func (f *Frontend) ScreenGetAll() ([]frontend.Screen, error) {
|
||||
return GetAllScreens(f.mainWindow.context)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMaximised() bool {
|
||||
return f.mainWindow.IsMaximised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMinimised() bool {
|
||||
return f.mainWindow.IsMinimised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsNormal() bool {
|
||||
return f.mainWindow.IsNormal()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsFullscreen() bool {
|
||||
return f.mainWindow.IsFullScreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) Quit() {
|
||||
if f.frontendOptions.OnBeforeClose != nil {
|
||||
go func() {
|
||||
if !f.frontendOptions.OnBeforeClose(f.ctx) {
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowPrint() {
|
||||
f.mainWindow.Print()
|
||||
}
|
||||
|
||||
type EventNotify struct {
|
||||
Name string `json:"name"`
|
||||
Data []interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (f *Frontend) Notify(name string, data ...interface{}) {
|
||||
notification := EventNotify{
|
||||
Name: name,
|
||||
Data: data,
|
||||
}
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
|
||||
}
|
||||
|
||||
func (f *Frontend) processMessage(message string) {
|
||||
if message == "DomReady" {
|
||||
if f.frontendOptions.OnDomReady != nil {
|
||||
f.frontendOptions.OnDomReady(f.ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "runtime:ready" {
|
||||
cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue)
|
||||
f.ExecJS(cmd)
|
||||
|
||||
if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.EnableFileDrop {
|
||||
f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if message == "wails:openInspector" {
|
||||
showInspector(f.mainWindow.context)
|
||||
return
|
||||
}
|
||||
|
||||
//if strings.HasPrefix(message, "systemevent:") {
|
||||
// f.processSystemEvent(message)
|
||||
// return
|
||||
//}
|
||||
|
||||
go func() {
|
||||
result, err := f.dispatcher.ProcessMessage(message, f)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
f.Callback(result)
|
||||
return
|
||||
}
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
|
||||
switch result[0] {
|
||||
case 'c':
|
||||
// Callback from a method call
|
||||
f.Callback(result[1:])
|
||||
default:
|
||||
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Frontend) ProcessOpenFileEvent(filePath string) {
|
||||
if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnFileOpen != nil {
|
||||
f.frontendOptions.Mac.OnFileOpen(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) ProcessOpenUrlEvent(url string) {
|
||||
if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnUrlOpen != nil {
|
||||
f.frontendOptions.Mac.OnUrlOpen(url)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
}
|
||||
|
||||
func (f *Frontend) ExecJS(js string) {
|
||||
f.mainWindow.ExecJS(js)
|
||||
}
|
||||
|
||||
//func (f *Frontend) processSystemEvent(message string) {
|
||||
// sl := strings.Split(message, ":")
|
||||
// if len(sl) != 2 {
|
||||
// f.logger.Error("Invalid system message: %s", message)
|
||||
// return
|
||||
// }
|
||||
// switch sl[1] {
|
||||
// case "fullscreen":
|
||||
// f.mainWindow.DisableSizeConstraints()
|
||||
// case "unfullscreen":
|
||||
// f.mainWindow.EnableSizeConstraints()
|
||||
// default:
|
||||
// f.logger.Error("Unknown system message: %s", message)
|
||||
// }
|
||||
//}
|
||||
|
||||
//export processMessage
|
||||
func processMessage(message *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processBindingMessage
|
||||
func processBindingMessage(message *C.char, source *C.char, fromMainFrame bool) {
|
||||
goMessage := C.GoString(message)
|
||||
goSource := C.GoString(source)
|
||||
bindingsMessageBuffer <- &bindingsMessage{
|
||||
message: goMessage,
|
||||
source: goSource,
|
||||
isMainFrame: fromMainFrame,
|
||||
}
|
||||
}
|
||||
|
||||
//export processCallback
|
||||
func processCallback(callbackID uint) {
|
||||
callbackBuffer <- callbackID
|
||||
}
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) {
|
||||
requestBuffer <- webview.NewRequest(wkURLSchemeTask)
|
||||
}
|
||||
|
||||
//export HandleOpenFile
|
||||
func HandleOpenFile(filePath *C.char) {
|
||||
goFilepath := C.GoString(filePath)
|
||||
openFilepathBuffer <- goFilepath
|
||||
}
|
||||
|
||||
//export HandleOpenURL
|
||||
func HandleOpenURL(url *C.char) {
|
||||
goUrl := C.GoString(url)
|
||||
openUrlBuffer <- goUrl
|
||||
}
|
||||
10
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector.go
generated
vendored
Normal file
10
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build darwin && !(dev || debug || devtools)
|
||||
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func showInspector(_ unsafe.Pointer) {
|
||||
}
|
||||
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector_dev.go
generated
vendored
Normal file
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/inspector_dev.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
//go:build darwin && (dev || debug || devtools)
|
||||
|
||||
package darwin
|
||||
|
||||
// We are using private APIs here, make sure this is only included in a dev/debug build and not in a production build.
|
||||
// Otherwise the binary might get rejected by the AppReview-Team when pushing it to the AppStore.
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "WailsContext.h"
|
||||
|
||||
extern void processMessage(const char *message);
|
||||
|
||||
@interface _WKInspector : NSObject
|
||||
- (void)show;
|
||||
- (void)detach;
|
||||
@end
|
||||
|
||||
@interface WKWebView ()
|
||||
- (_WKInspector *)_inspector;
|
||||
@end
|
||||
|
||||
void showInspector(void *inctx) {
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120000
|
||||
ON_MAIN_THREAD(
|
||||
if (@available(macOS 12.0, *)) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
|
||||
@try {
|
||||
[ctx.webview._inspector show];
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"Opening the inspector failed: %@", exception.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
|
||||
// Detach must be deferred a little bit and is ignored directly after a show.
|
||||
@try {
|
||||
[ctx.webview._inspector detach];
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"Detaching the inspector failed: %@", exception.reason);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSLog(@"Opening the inspector needs at least MacOS 12");
|
||||
}
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setupF12hotkey() {
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
||||
if (event.keyCode == 111 &&
|
||||
event.modifierFlags & NSEventModifierFlagFunction &&
|
||||
event.modifierFlags & NSEventModifierFlagCommand &&
|
||||
event.modifierFlags & NSEventModifierFlagShift) {
|
||||
processMessage("wails:openInspector");
|
||||
return nil;
|
||||
}
|
||||
return event;
|
||||
}];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
C.setupF12hotkey()
|
||||
}
|
||||
|
||||
func showInspector(context unsafe.Pointer) {
|
||||
C.showInspector(context)
|
||||
}
|
||||
243
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/main.m
generated
vendored
Normal file
243
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/main.m
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
//go:build ignore
|
||||
// main.m
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 10/10/21.
|
||||
//
|
||||
|
||||
// ****** This file is used for testing purposes only ******
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
|
||||
void processMessage(const char*t) {
|
||||
NSLog(@"processMessage called");
|
||||
}
|
||||
|
||||
void processMessageDialogResponse(int t) {
|
||||
NSLog(@"processMessage called");
|
||||
}
|
||||
|
||||
void processOpenFileDialogResponse(const char *t) {
|
||||
NSLog(@"processMessage called %s", t);
|
||||
}
|
||||
void processSaveFileDialogResponse(const char *t) {
|
||||
NSLog(@"processMessage called %s", t);
|
||||
}
|
||||
|
||||
void processCallback(int callbackID) {
|
||||
NSLog(@"Process callback %d", callbackID);
|
||||
}
|
||||
|
||||
void processURLRequest(void *ctx, unsigned long long requestId, const char* url, const char *method, const char *headers, const void *body, int bodyLen) {
|
||||
NSLog(@"processURLRequest called");
|
||||
const char myByteArray[] = { 0x3c,0x68,0x31,0x3e,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21,0x3c,0x2f,0x68,0x31,0x3e };
|
||||
// void *inctx, const char *url, int statusCode, const char *headers, void* data, int datalength
|
||||
ProcessURLResponse(ctx, requestId, 200, "{\"Content-Type\": \"text/html\"}", (void*)myByteArray, 21);
|
||||
}
|
||||
|
||||
unsigned char _Users_username_Pictures_SaltBae_png[] = {
|
||||
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00,
|
||||
0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61,
|
||||
0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, 0x00, 0x00, 0x7a,
|
||||
0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80,
|
||||
0xe8, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a,
|
||||
0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, 0x00,
|
||||
0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b,
|
||||
0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x01, 0xd5, 0x69, 0x54,
|
||||
0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64,
|
||||
0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78,
|
||||
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62,
|
||||
0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20,
|
||||
0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50,
|
||||
0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22,
|
||||
0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44,
|
||||
0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d,
|
||||
0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
|
||||
0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f,
|
||||
0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79,
|
||||
0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64,
|
||||
0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78,
|
||||
0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68,
|
||||
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f,
|
||||
0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f,
|
||||
0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f,
|
||||
0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
|
||||
0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
|
||||
0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68,
|
||||
0x6f, 0x74, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e,
|
||||
0x32, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68, 0x6f, 0x74,
|
||||
0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44,
|
||||
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a,
|
||||
0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46,
|
||||
0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74,
|
||||
0x61, 0x3e, 0x0a, 0x02, 0xd8, 0x80, 0x05, 0x00, 0x00, 0x04, 0xdc, 0x49,
|
||||
0x44, 0x41, 0x54, 0x38, 0x11, 0x1d, 0x94, 0x49, 0x6c, 0x1b, 0x65, 0x18,
|
||||
0x86, 0x9f, 0x99, 0xf9, 0x67, 0xc6, 0x6b, 0xbc, 0x26, 0xce, 0xda, 0xa4,
|
||||
0x25, 0x69, 0x0b, 0x2d, 0x28, 0x34, 0x2c, 0x95, 0x00, 0x89, 0x45, 0x08,
|
||||
0x5a, 0x95, 0x03, 0x08, 0x09, 0x21, 0xe0, 0x80, 0x38, 0xc3, 0x85, 0x03,
|
||||
0xe2, 0x00, 0x47, 0xc4, 0x1d, 0x38, 0x70, 0xe3, 0xc6, 0x01, 0x01, 0x42,
|
||||
0x20, 0x54, 0x7a, 0x2a, 0x6b, 0x0b, 0x94, 0xd2, 0xd2, 0x25, 0x69, 0x9b,
|
||||
0xa4, 0x0d, 0x2d, 0xa9, 0xb3, 0x78, 0x89, 0x9d, 0xf1, 0x2c, 0x9e, 0x85,
|
||||
0x2f, 0xb5, 0x35, 0xb6, 0x35, 0x96, 0xde, 0x79, 0xdf, 0xef, 0x7f, 0x9f,
|
||||
0x4f, 0xfb, 0xe0, 0xad, 0x37, 0x12, 0xfd, 0xf0, 0xb3, 0x9c, 0xfb, 0xb7,
|
||||
0xc5, 0x8d, 0x46, 0x9b, 0x71, 0x5b, 0xf1, 0xd0, 0xf4, 0x18, 0xdb, 0xeb,
|
||||
0x4b, 0x1c, 0xff, 0xf1, 0x57, 0x98, 0xdc, 0x87, 0x72, 0x3a, 0x8c, 0x3a,
|
||||
0xcb, 0x8c, 0xea, 0x31, 0x35, 0xb7, 0xc3, 0x99, 0xba, 0xc3, 0xd7, 0xab,
|
||||
0x3e, 0x87, 0x2a, 0x8a, 0xb3, 0xff, 0xdc, 0xe0, 0x9b, 0x8f, 0x5f, 0xa2,
|
||||
0x1c, 0xc5, 0xfc, 0x72, 0xc9, 0x41, 0x99, 0x71, 0x48, 0xca, 0x84, 0x3c,
|
||||
0x3e, 0xda, 0xd2, 0x05, 0x9a, 0xb1, 0xc7, 0x35, 0x67, 0x1c, 0xdd, 0x4c,
|
||||
0x68, 0xeb, 0x26, 0xd9, 0x30, 0x26, 0x09, 0x23, 0x5c, 0x3f, 0xc2, 0xd3,
|
||||
0x43, 0xc2, 0x24, 0x21, 0x4e, 0x34, 0x40, 0x27, 0x89, 0x13, 0xf9, 0x1e,
|
||||
0x22, 0x6e, 0xd5, 0x45, 0x43, 0x63, 0xc6, 0xd2, 0x50, 0xa9, 0xc4, 0x67,
|
||||
0x24, 0x15, 0x72, 0xa9, 0x7e, 0x95, 0xfa, 0x4f, 0x27, 0x78, 0x64, 0x76,
|
||||
0x86, 0x23, 0x61, 0xc0, 0xf0, 0x58, 0x15, 0xc3, 0x29, 0x71, 0x06, 0x45,
|
||||
0x2e, 0xa5, 0x48, 0xbb, 0x0a, 0x3d, 0x89, 0xa0, 0x8f, 0x08, 0x8a, 0x8e,
|
||||
0x08, 0xbb, 0xc1, 0x8e, 0xb0, 0x8d, 0xdd, 0x0f, 0xc9, 0x84, 0x06, 0x65,
|
||||
0x34, 0xf4, 0xed, 0x8d, 0xff, 0x58, 0xbd, 0xfc, 0x27, 0x17, 0x2f, 0x9e,
|
||||
0xe3, 0xf0, 0x81, 0x49, 0x5e, 0xde, 0x5f, 0xe1, 0x9e, 0x82, 0xcd, 0xdc,
|
||||
0x78, 0x8d, 0xd9, 0xb2, 0xc9, 0x56, 0x12, 0x32, 0x94, 0x4f, 0x91, 0xcb,
|
||||
0x88, 0x68, 0xda, 0x42, 0x13, 0x77, 0x11, 0xa2, 0xa8, 0xc3, 0x5a, 0x5f,
|
||||
0x46, 0x30, 0x65, 0x52, 0x29, 0xe4, 0x24, 0x4d, 0x8e, 0xcc, 0x68, 0x19,
|
||||
0xe5, 0x76, 0xbb, 0xac, 0x5c, 0x98, 0xa7, 0xb3, 0xed, 0xd0, 0x37, 0x62,
|
||||
0xa2, 0xb0, 0xc7, 0x89, 0xe5, 0x2e, 0x03, 0x0d, 0x97, 0x95, 0x46, 0x8f,
|
||||
0x31, 0xd7, 0xa6, 0x63, 0x81, 0x65, 0x25, 0x84, 0xba, 0x45, 0x5f, 0x65,
|
||||
0x31, 0x2c, 0x71, 0x6b, 0x77, 0x69, 0xf5, 0x7a, 0xbc, 0xb0, 0x3b, 0xcd,
|
||||
0xf9, 0xa5, 0x90, 0xd1, 0xb0, 0xcd, 0xd4, 0xb0, 0xdc, 0xd7, 0xc4, 0xfa,
|
||||
0xf0, 0x78, 0x95, 0x7b, 0x27, 0xab, 0x5c, 0x5e, 0x6e, 0xd2, 0xee, 0x05,
|
||||
0xdc, 0xd8, 0xea, 0xf1, 0xf7, 0xe2, 0x1a, 0xc7, 0xee, 0x1a, 0x62, 0x2e,
|
||||
0x1f, 0xe3, 0xe8, 0xb6, 0xc4, 0x4c, 0xd3, 0x6d, 0x6e, 0xd0, 0x6b, 0xfc,
|
||||
0x4c, 0xe3, 0xd4, 0x1f, 0xc4, 0x4b, 0xf3, 0x1c, 0x2c, 0x65, 0x29, 0x67,
|
||||
0x4d, 0xbe, 0xfb, 0xad, 0x45, 0x65, 0x0c, 0xea, 0x7e, 0x1f, 0x15, 0x6b,
|
||||
0x09, 0x0b, 0x8b, 0xb7, 0x19, 0xc9, 0xa5, 0x78, 0x75, 0x6e, 0x18, 0xdf,
|
||||
0xf5, 0x79, 0x72, 0xd0, 0xa2, 0x2d, 0xb3, 0x3a, 0xbb, 0xb4, 0x41, 0x3e,
|
||||
0x53, 0xe6, 0xf4, 0xca, 0x3c, 0xa5, 0x7c, 0x86, 0xe9, 0xfd, 0x47, 0x18,
|
||||
0x2e, 0xbd, 0xce, 0xd1, 0x97, 0x26, 0x78, 0xbc, 0x7e, 0x1d, 0xff, 0xcc,
|
||||
0xa7, 0x5c, 0x71, 0x74, 0x16, 0xe3, 0x18, 0xd7, 0x1e, 0x23, 0xe8, 0xac,
|
||||
0xa3, 0x0c, 0xcd, 0x60, 0x22, 0x6f, 0x43, 0x36, 0x43, 0x3b, 0x19, 0xc6,
|
||||
0x08, 0x7a, 0xe0, 0x6c, 0xe3, 0x27, 0x8a, 0xdb, 0x4e, 0xc0, 0xd4, 0xa0,
|
||||
0xcd, 0x27, 0xaf, 0xbd, 0xcb, 0x86, 0x36, 0xc6, 0xcc, 0xfe, 0x59, 0xd2,
|
||||
0xca, 0x90, 0x93, 0x36, 0x70, 0xaf, 0x9c, 0xe4, 0xcb, 0x6f, 0x65, 0x54,
|
||||
0xd9, 0x47, 0x59, 0x70, 0xbb, 0x74, 0x1b, 0x0e, 0x89, 0xe7, 0xa3, 0xc7,
|
||||
0x12, 0x39, 0x63, 0xea, 0x68, 0x12, 0x6b, 0x53, 0x5c, 0x9e, 0xef, 0x76,
|
||||
0xf0, 0x55, 0x86, 0x0d, 0x17, 0x56, 0x9a, 0x4d, 0x94, 0x95, 0x65, 0xe6,
|
||||
0xbe, 0x67, 0x98, 0xbe, 0xfb, 0x21, 0x52, 0xd2, 0x43, 0xaf, 0x5d, 0x47,
|
||||
0x6b, 0x5c, 0xa3, 0x59, 0xbf, 0xc2, 0x62, 0xdd, 0x26, 0xa5, 0x12, 0x6a,
|
||||
0x41, 0x44, 0xdf, 0xbd, 0xcd, 0x92, 0x17, 0xa0, 0xb6, 0x03, 0x43, 0xba,
|
||||
0x66, 0x91, 0xe9, 0xdc, 0xc2, 0xce, 0xed, 0xa1, 0xfc, 0xc0, 0x2b, 0x14,
|
||||
0xff, 0xfd, 0x1e, 0x4b, 0xb3, 0xa9, 0x29, 0x87, 0x81, 0xd2, 0x04, 0x8e,
|
||||
0x66, 0x89, 0x58, 0x00, 0x7e, 0x07, 0xaf, 0xdb, 0xa4, 0xbb, 0xb5, 0x49,
|
||||
0xb9, 0xaa, 0x18, 0xb9, 0x77, 0x8e, 0xcd, 0xdb, 0x6d, 0x1e, 0x1c, 0xb5,
|
||||
0x38, 0x7d, 0xa5, 0xcf, 0xaa, 0x08, 0xeb, 0x77, 0x3f, 0x35, 0xc7, 0xda,
|
||||
0xfc, 0x02, 0xaa, 0xf6, 0x1c, 0xbb, 0x9f, 0x78, 0x9f, 0x89, 0x43, 0x47,
|
||||
0xa4, 0x6f, 0x3d, 0x06, 0xed, 0x90, 0x92, 0x79, 0x95, 0xd4, 0xe4, 0xfd,
|
||||
0x98, 0x66, 0x4a, 0x6a, 0xd7, 0xc7, 0x0b, 0x62, 0xa4, 0xe3, 0x8c, 0x4d,
|
||||
0xc4, 0xe8, 0x85, 0x98, 0xe5, 0x46, 0x44, 0x26, 0x97, 0x21, 0xe9, 0xf7,
|
||||
0xf9, 0x61, 0xc5, 0xe3, 0xd4, 0x66, 0x84, 0xd2, 0x70, 0xc9, 0xee, 0x79,
|
||||
0x98, 0x43, 0xc7, 0x5e, 0x27, 0xb6, 0x8a, 0xd2, 0x5a, 0x1f, 0xf3, 0xa9,
|
||||
0xf7, 0x88, 0xce, 0x7d, 0x85, 0x71, 0xe0, 0x79, 0x98, 0x7a, 0x90, 0x9e,
|
||||
0x1b, 0xd0, 0x13, 0x52, 0x4a, 0x66, 0x97, 0x7d, 0x33, 0x1e, 0xed, 0xae,
|
||||
0xc7, 0x87, 0x1f, 0x7d, 0xce, 0xc2, 0xd5, 0x3a, 0xe6, 0xde, 0x02, 0xcb,
|
||||
0xdb, 0x3e, 0xbe, 0xa6, 0x91, 0x95, 0x62, 0x6b, 0x2f, 0xce, 0x90, 0x3c,
|
||||
0xfd, 0xce, 0x71, 0x0e, 0xcc, 0x3e, 0x82, 0x13, 0xf4, 0x09, 0xd5, 0x00,
|
||||
0x16, 0x82, 0x98, 0xb3, 0x49, 0x24, 0xb1, 0x83, 0xc8, 0xc0, 0xd6, 0x3a,
|
||||
0x54, 0x33, 0xab, 0x14, 0x8c, 0x16, 0x4e, 0x38, 0xcc, 0xe5, 0xeb, 0x4d,
|
||||
0x5e, 0x7b, 0xfb, 0x4d, 0xaa, 0x79, 0xa1, 0x45, 0x1c, 0x9b, 0xd2, 0x94,
|
||||
0xcc, 0x0e, 0x8c, 0x52, 0x7a, 0x65, 0x17, 0xc7, 0xa9, 0x0c, 0x8e, 0xe2,
|
||||
0xf7, 0xba, 0xa8, 0xc8, 0x13, 0x87, 0x32, 0x87, 0x0b, 0x27, 0x30, 0x36,
|
||||
0x57, 0xe8, 0xea, 0x15, 0xce, 0x06, 0x65, 0x5e, 0x3d, 0x5a, 0x94, 0x53,
|
||||
0xb7, 0x59, 0x58, 0xdf, 0x25, 0xc4, 0xe4, 0xc9, 0x65, 0x3d, 0xb4, 0xb4,
|
||||
0x4e, 0x37, 0x0c, 0x29, 0x98, 0x4a, 0xe8, 0x11, 0xde, 0x85, 0x42, 0x43,
|
||||
0x1c, 0xaa, 0x38, 0x55, 0xc4, 0xb4, 0x2c, 0x22, 0x3d, 0xcd, 0xfa, 0xea,
|
||||
0x0d, 0xf4, 0x8d, 0x1f, 0xc9, 0x5f, 0xfa, 0x82, 0x6d, 0xc7, 0xe1, 0xa6,
|
||||
0x57, 0xe3, 0x56, 0x6e, 0x96, 0xbf, 0x16, 0x1f, 0xa3, 0x54, 0xaa, 0x91,
|
||||
0x16, 0x5a, 0xb2, 0xa9, 0x04, 0xaf, 0x67, 0xc9, 0xac, 0x6c, 0xfa, 0x32,
|
||||
0x9e, 0x48, 0xea, 0xa5, 0x0b, 0x89, 0x3b, 0x54, 0x47, 0xf2, 0xa1, 0xf2,
|
||||
0x2a, 0x4d, 0xeb, 0xf4, 0x17, 0xdc, 0xd4, 0x72, 0x6c, 0xb5, 0x36, 0x28,
|
||||
0xb6, 0x7e, 0x17, 0x04, 0xd3, 0xac, 0x7a, 0x42, 0xc1, 0xf4, 0x6e, 0x9e,
|
||||
0xbf, 0x6b, 0xb7, 0x3c, 0x3a, 0x21, 0x67, 0xcb, 0x41, 0x48, 0x07, 0x91,
|
||||
0xde, 0x1a, 0xe2, 0xaa, 0x9c, 0xb1, 0x59, 0xdb, 0x12, 0x25, 0xc1, 0x32,
|
||||
0x92, 0xea, 0xc9, 0xaf, 0x3b, 0x97, 0xca, 0xca, 0xfe, 0x5b, 0xfe, 0xe5,
|
||||
0x33, 0x29, 0xeb, 0x16, 0x95, 0xd2, 0x24, 0xeb, 0xda, 0x30, 0xeb, 0x95,
|
||||
0x1a, 0xd3, 0xf7, 0x0f, 0x51, 0x1c, 0xd9, 0x0b, 0x99, 0x12, 0x7a, 0x4a,
|
||||
0xd0, 0xd3, 0x25, 0x9a, 0x88, 0x45, 0xb1, 0x04, 0x33, 0x2c, 0x8a, 0x99,
|
||||
0x34, 0x6b, 0x75, 0x19, 0x91, 0x9d, 0x92, 0x29, 0x89, 0xa0, 0x2c, 0x8b,
|
||||
0x9d, 0xd8, 0x7a, 0x5e, 0x04, 0x07, 0x87, 0x66, 0x28, 0x56, 0x67, 0xb9,
|
||||
0xd6, 0xd2, 0x39, 0xd9, 0xec, 0x33, 0x30, 0xb2, 0x8b, 0xea, 0xae, 0x83,
|
||||
0x18, 0xb9, 0x31, 0x34, 0xbb, 0x42, 0x22, 0x0b, 0x21, 0x96, 0x3c, 0x61,
|
||||
0xac, 0xcb, 0x95, 0x60, 0x2a, 0xe9, 0x68, 0x79, 0x08, 0x36, 0x56, 0x65,
|
||||
0x27, 0x4a, 0xd9, 0x83, 0x00, 0xcf, 0x0b, 0xf1, 0xfc, 0x10, 0x15, 0x0a,
|
||||
0x6a, 0x75, 0x77, 0x8b, 0x86, 0xdc, 0x58, 0x57, 0x45, 0x52, 0xe9, 0x84,
|
||||
0x81, 0x7c, 0x91, 0x28, 0x55, 0x23, 0x96, 0x13, 0xd7, 0x24, 0xbe, 0xac,
|
||||
0x17, 0xfa, 0xf2, 0x78, 0x63, 0xc7, 0x82, 0x08, 0xda, 0xa6, 0xc5, 0x50,
|
||||
0x55, 0x04, 0xe5, 0x65, 0x5b, 0x06, 0xde, 0xce, 0xf0, 0x24, 0xf3, 0x4e,
|
||||
0x70, 0xb5, 0x15, 0x6a, 0x34, 0x7b, 0x11, 0x9d, 0xbe, 0x10, 0x53, 0xd0,
|
||||
0xa8, 0x86, 0x2e, 0x76, 0xb6, 0x2a, 0x9d, 0x2c, 0x48, 0x3c, 0x5b, 0xa2,
|
||||
0xc8, 0x3a, 0x37, 0xd4, 0x9d, 0xed, 0x6c, 0x4a, 0xab, 0x95, 0x6e, 0x08,
|
||||
0x66, 0x3d, 0x5a, 0xad, 0x4d, 0x18, 0xc8, 0xca, 0xfa, 0xd5, 0x85, 0x6f,
|
||||
0xf9, 0x5f, 0xde, 0x02, 0x30, 0xff, 0x03, 0x8c, 0x47, 0x35, 0xad, 0xbc,
|
||||
0xbf, 0x26, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
|
||||
0x42, 0x60, 0x82
|
||||
|
||||
};
|
||||
|
||||
unsigned int _Users_username_Pictures_SaltBae_png_len = 1863;
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
// insert code here...
|
||||
int frameless = 0;
|
||||
int resizable = 1;
|
||||
int zoomable = 0;
|
||||
int fullscreen = 1;
|
||||
int fullSizeContent = 1;
|
||||
int hideTitleBar = 0;
|
||||
int titlebarAppearsTransparent = 0;
|
||||
int hideTitle = 0;
|
||||
int useToolbar = 0;
|
||||
int hideToolbarSeparator = 0;
|
||||
int webviewIsTransparent = 1;
|
||||
int alwaysOnTop = 0;
|
||||
int hideWindowOnClose = 0;
|
||||
const char* appearance = "NSAppearanceNameDarkAqua";
|
||||
int windowIsTranslucent = 1;
|
||||
int devtoolsEnabled = 1;
|
||||
int defaultContextMenuEnabled = 1;
|
||||
int windowStartState = 0;
|
||||
int startsHidden = 0;
|
||||
WailsContext *result = Create("OI OI!",400,400, frameless, resizable, zoomable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled, windowStartState,
|
||||
startsHidden, 400, 400, 600, 600, false);
|
||||
SetBackgroundColour(result, 255, 0, 0, 255);
|
||||
void *m = NewMenu("");
|
||||
SetAbout(result, "Fake title", "I am a description", _Users_username_Pictures_SaltBae_png, _Users_username_Pictures_SaltBae_png_len);
|
||||
// AddMenuByRole(result, 1);
|
||||
|
||||
AppendRole(result, m, 1);
|
||||
AppendRole(result, m, 2);
|
||||
void* submenu = NewMenu("test");
|
||||
void* menuITem = AppendMenuItem(result, submenu, "Woohoo", "p", 0, 0, 0, 470);
|
||||
AppendSubmenu(m, submenu);
|
||||
UpdateMenuItem(menuITem, 1);
|
||||
SetAsApplicationMenu(result, m);
|
||||
// SetPosition(result, 100, 100);
|
||||
|
||||
|
||||
|
||||
Run((void*)CFBridgingRetain(result));
|
||||
return 0;
|
||||
}
|
||||
134
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menu.go
generated
vendored
Normal file
134
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menu.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
type NSMenu struct {
|
||||
context unsafe.Pointer
|
||||
nsmenu unsafe.Pointer
|
||||
}
|
||||
|
||||
func NewNSMenu(context unsafe.Pointer, name string) *NSMenu {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
title := c.String(name)
|
||||
nsmenu := C.NewMenu(title)
|
||||
return &NSMenu{
|
||||
context: context,
|
||||
nsmenu: nsmenu,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *NSMenu) AddSubMenu(label string) *NSMenu {
|
||||
result := NewNSMenu(m.context, label)
|
||||
C.AppendSubmenu(m.nsmenu, result.nsmenu)
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *NSMenu) AppendRole(role menu.Role) {
|
||||
C.AppendRole(m.context, m.nsmenu, C.int(role))
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
id uint
|
||||
nsmenuitem unsafe.Pointer
|
||||
wailsMenuItem *menu.MenuItem
|
||||
radioGroupMembers []*MenuItem
|
||||
}
|
||||
|
||||
func (m *NSMenu) AddMenuItem(menuItem *menu.MenuItem) *MenuItem {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
var modifier C.int
|
||||
var key *C.char
|
||||
if menuItem.Accelerator != nil {
|
||||
modifier = C.int(keys.ToMacModifier(menuItem.Accelerator))
|
||||
key = c.String(menuItem.Accelerator.Key)
|
||||
}
|
||||
|
||||
result := &MenuItem{
|
||||
wailsMenuItem: menuItem,
|
||||
}
|
||||
|
||||
result.id = createMenuItemID(result)
|
||||
result.nsmenuitem = C.AppendMenuItem(m.context, m.nsmenu, c.String(menuItem.Label), key, modifier, bool2Cint(menuItem.Disabled), bool2Cint(menuItem.Checked), C.int(result.id))
|
||||
return result
|
||||
}
|
||||
|
||||
//func (w *Window) SetApplicationMenu(menu *menu.Menu) {
|
||||
//w.applicationMenu = menu
|
||||
//processMenu(w, menu)
|
||||
//}
|
||||
|
||||
func processMenu(parent *NSMenu, wailsMenu *menu.Menu) {
|
||||
var radioGroups []*MenuItem
|
||||
|
||||
for _, menuItem := range wailsMenu.Items {
|
||||
if menuItem.SubMenu != nil {
|
||||
if len(radioGroups) > 0 {
|
||||
processRadioGroups(radioGroups)
|
||||
radioGroups = []*MenuItem{}
|
||||
}
|
||||
submenu := parent.AddSubMenu(menuItem.Label)
|
||||
processMenu(submenu, menuItem.SubMenu)
|
||||
} else {
|
||||
lastMenuItem := processMenuItem(parent, menuItem)
|
||||
if menuItem.Type == menu.RadioType {
|
||||
radioGroups = append(radioGroups, lastMenuItem)
|
||||
} else {
|
||||
if len(radioGroups) > 0 {
|
||||
processRadioGroups(radioGroups)
|
||||
radioGroups = []*MenuItem{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processRadioGroups(groups []*MenuItem) {
|
||||
for _, item := range groups {
|
||||
item.radioGroupMembers = groups
|
||||
}
|
||||
}
|
||||
|
||||
func processMenuItem(parent *NSMenu, menuItem *menu.MenuItem) *MenuItem {
|
||||
if menuItem.Hidden {
|
||||
return nil
|
||||
}
|
||||
if menuItem.Role != 0 {
|
||||
parent.AppendRole(menuItem.Role)
|
||||
return nil
|
||||
}
|
||||
if menuItem.Type == menu.SeparatorType {
|
||||
C.AppendSeparator(parent.nsmenu)
|
||||
return nil
|
||||
}
|
||||
|
||||
return parent.AddMenuItem(menuItem)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
f.mainWindow.UpdateApplicationMenu()
|
||||
}
|
||||
54
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menuitem.go
generated
vendored
Normal file
54
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/menuitem.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
menuItemToID = make(map[*MenuItem]uint)
|
||||
idToMenuItem = make(map[uint]*MenuItem)
|
||||
menuItemLock sync.Mutex
|
||||
menuItemIDCounter uint = 0
|
||||
)
|
||||
|
||||
func createMenuItemID(item *MenuItem) uint {
|
||||
menuItemLock.Lock()
|
||||
defer menuItemLock.Unlock()
|
||||
counter := 0
|
||||
for {
|
||||
menuItemIDCounter++
|
||||
value := idToMenuItem[menuItemIDCounter]
|
||||
if value == nil {
|
||||
break
|
||||
}
|
||||
counter++
|
||||
if counter == math.MaxInt {
|
||||
log.Fatal("insane amounts of menuitems detected! Aborting before the collapse of the world!")
|
||||
}
|
||||
}
|
||||
idToMenuItem[menuItemIDCounter] = item
|
||||
menuItemToID[item] = menuItemIDCounter
|
||||
return menuItemIDCounter
|
||||
}
|
||||
|
||||
func getMenuItemForID(id uint) *MenuItem {
|
||||
menuItemLock.Lock()
|
||||
defer menuItemLock.Unlock()
|
||||
return idToMenuItem[id]
|
||||
}
|
||||
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/message.h
generated
vendored
Normal file
30
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/message.h
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// message.h
|
||||
// test
|
||||
//
|
||||
// Created by Lea Anthony on 14/10/21.
|
||||
//
|
||||
|
||||
#ifndef export_h
|
||||
#define export_h
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
void processMessage(const char *);
|
||||
void processBindingMessage(const char *, const char *, bool);
|
||||
void processURLRequest(void *, void*);
|
||||
void processMessageDialogResponse(int);
|
||||
void processOpenFileDialogResponse(const char*);
|
||||
void processSaveFileDialogResponse(const char*);
|
||||
void processCallback(int);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* export_h */
|
||||
465
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/notifications.go
generated
vendored
Normal file
465
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/notifications.go
generated
vendored
Normal file
@@ -0,0 +1,465 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS:-x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa
|
||||
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
#cgo LDFLAGS: -framework UserNotifications
|
||||
#endif
|
||||
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
// Package-scoped variable only accessible within this file
|
||||
var (
|
||||
currentFrontend *Frontend
|
||||
frontendMutex sync.RWMutex
|
||||
// Notification channels
|
||||
channels map[int]chan notificationChannel
|
||||
channelsLock sync.Mutex
|
||||
nextChannelID int
|
||||
|
||||
notificationResultCallback func(result frontend.NotificationResult)
|
||||
callbackLock sync.RWMutex
|
||||
)
|
||||
|
||||
const DefaultActionIdentifier = "DEFAULT_ACTION"
|
||||
const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier"
|
||||
|
||||
// setCurrentFrontend sets the current frontend instance
|
||||
// This is called when RequestNotificationAuthorization or CheckNotificationAuthorization is called
|
||||
func setCurrentFrontend(f *Frontend) {
|
||||
frontendMutex.Lock()
|
||||
defer frontendMutex.Unlock()
|
||||
currentFrontend = f
|
||||
}
|
||||
|
||||
// getCurrentFrontend gets the current frontend instance
|
||||
func getCurrentFrontend() *Frontend {
|
||||
frontendMutex.RLock()
|
||||
defer frontendMutex.RUnlock()
|
||||
return currentFrontend
|
||||
}
|
||||
|
||||
type notificationChannel struct {
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
|
||||
func (f *Frontend) InitializeNotifications() error {
|
||||
if !f.IsNotificationAvailable() {
|
||||
return fmt.Errorf("notifications are not available on this system")
|
||||
}
|
||||
if !f.checkBundleIdentifier() {
|
||||
return fmt.Errorf("notifications require a valid bundle identifier")
|
||||
}
|
||||
if !bool(C.EnsureDelegateInitialized(f.mainWindow.context)) {
|
||||
return fmt.Errorf("failed to initialize notification center delegate")
|
||||
}
|
||||
|
||||
channels = make(map[int]chan notificationChannel)
|
||||
nextChannelID = 0
|
||||
|
||||
setCurrentFrontend(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupNotifications is a macOS stub that does nothing.
|
||||
// (Linux-specific cleanup)
|
||||
func (f *Frontend) CleanupNotifications() {
|
||||
// No cleanup needed on macOS
|
||||
}
|
||||
|
||||
func (f *Frontend) IsNotificationAvailable() bool {
|
||||
return bool(C.IsNotificationAvailable(f.mainWindow.context))
|
||||
}
|
||||
|
||||
func (f *Frontend) checkBundleIdentifier() bool {
|
||||
return bool(C.CheckBundleIdentifier(f.mainWindow.context))
|
||||
}
|
||||
|
||||
func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
|
||||
C.RequestNotificationAuthorization(f.mainWindow.context, C.int(id))
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
return result.Success, result.Error
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
|
||||
C.CheckNotificationAuthorization(f.mainWindow.context, C.int(id))
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
return result.Success, result.Error
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cIdentifier := C.CString(options.ID)
|
||||
cTitle := C.CString(options.Title)
|
||||
cSubtitle := C.CString(options.Subtitle)
|
||||
cBody := C.CString(options.Body)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
defer C.free(unsafe.Pointer(cTitle))
|
||||
defer C.free(unsafe.Pointer(cSubtitle))
|
||||
defer C.free(unsafe.Pointer(cBody))
|
||||
|
||||
var cDataJSON *C.char
|
||||
if options.Data != nil {
|
||||
jsonData, err := json.Marshal(options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification data: %w", err)
|
||||
}
|
||||
cDataJSON = C.CString(string(jsonData))
|
||||
defer C.free(unsafe.Pointer(cDataJSON))
|
||||
}
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.SendNotification(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("sending notification failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("sending notification timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions and inputs.
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cIdentifier := C.CString(options.ID)
|
||||
cTitle := C.CString(options.Title)
|
||||
cSubtitle := C.CString(options.Subtitle)
|
||||
cBody := C.CString(options.Body)
|
||||
cCategoryID := C.CString(options.CategoryID)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
defer C.free(unsafe.Pointer(cTitle))
|
||||
defer C.free(unsafe.Pointer(cSubtitle))
|
||||
defer C.free(unsafe.Pointer(cBody))
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
||||
var cDataJSON *C.char
|
||||
if options.Data != nil {
|
||||
jsonData, err := json.Marshal(options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification data: %w", err)
|
||||
}
|
||||
cDataJSON = C.CString(string(jsonData))
|
||||
defer C.free(unsafe.Pointer(cDataJSON))
|
||||
}
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.SendNotificationWithActions(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("sending notification failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("sending notification timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
// Registering a category with the same name as a previously registered NotificationCategory will override it.
|
||||
func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cCategoryID := C.CString(category.ID)
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
||||
actionsJSON, err := json.Marshal(category.Actions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification category: %w", err)
|
||||
}
|
||||
cActionsJSON := C.CString(string(actionsJSON))
|
||||
defer C.free(unsafe.Pointer(cActionsJSON))
|
||||
|
||||
var cReplyPlaceholder, cReplyButtonTitle *C.char
|
||||
if category.HasReplyField {
|
||||
cReplyPlaceholder = C.CString(category.ReplyPlaceholder)
|
||||
cReplyButtonTitle = C.CString(category.ReplyButtonTitle)
|
||||
defer C.free(unsafe.Pointer(cReplyPlaceholder))
|
||||
defer C.free(unsafe.Pointer(cReplyButtonTitle))
|
||||
}
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.RegisterNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField),
|
||||
cReplyPlaceholder, cReplyButtonTitle)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("category registration failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("category registration timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory remove a previously registered NotificationCategory.
|
||||
func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cCategoryID := C.CString(categoryId)
|
||||
defer C.free(unsafe.Pointer(cCategoryID))
|
||||
|
||||
id, resultCh := f.registerChannel()
|
||||
C.RemoveNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID)
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
close(resultCh)
|
||||
if !result.Success {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("category removal failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
f.cleanupChannel(id)
|
||||
return fmt.Errorf("category removal timed out: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications removes all pending notifications.
|
||||
func (f *Frontend) RemoveAllPendingNotifications() error {
|
||||
C.RemoveAllPendingNotifications(f.mainWindow.context)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification removes a pending notification matching the unique identifier.
|
||||
func (f *Frontend) RemovePendingNotification(identifier string) error {
|
||||
cIdentifier := C.CString(identifier)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
C.RemovePendingNotification(f.mainWindow.context, cIdentifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications removes all delivered notifications.
|
||||
func (f *Frontend) RemoveAllDeliveredNotifications() error {
|
||||
C.RemoveAllDeliveredNotifications(f.mainWindow.context)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification removes a delivered notification matching the unique identifier.
|
||||
func (f *Frontend) RemoveDeliveredNotification(identifier string) error {
|
||||
cIdentifier := C.CString(identifier)
|
||||
defer C.free(unsafe.Pointer(cIdentifier))
|
||||
C.RemoveDeliveredNotification(f.mainWindow.context, cIdentifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotification is a macOS stub that always returns nil.
|
||||
// Use one of the following instead:
|
||||
// RemoveAllPendingNotifications
|
||||
// RemovePendingNotification
|
||||
// RemoveAllDeliveredNotifications
|
||||
// RemoveDeliveredNotification
|
||||
// (Linux-specific)
|
||||
func (f *Frontend) RemoveNotification(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
|
||||
callbackLock.Lock()
|
||||
notificationResultCallback = callback
|
||||
callbackLock.Unlock()
|
||||
}
|
||||
|
||||
//export captureResult
|
||||
func captureResult(channelID C.int, success C.bool, errorMsg *C.char) {
|
||||
f := getCurrentFrontend()
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resultCh, exists := f.GetChannel(int(channelID))
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if errorMsg != nil {
|
||||
err = fmt.Errorf("%s", C.GoString(errorMsg))
|
||||
}
|
||||
|
||||
resultCh <- notificationChannel{
|
||||
Success: bool(success),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
//export didReceiveNotificationResponse
|
||||
func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) {
|
||||
result := frontend.NotificationResult{}
|
||||
|
||||
if err != nil {
|
||||
errMsg := C.GoString(err)
|
||||
result.Error = fmt.Errorf("notification response error: %s", errMsg)
|
||||
handleNotificationResult(result)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if jsonPayload == nil {
|
||||
result.Error = fmt.Errorf("received nil JSON payload in notification response")
|
||||
handleNotificationResult(result)
|
||||
return
|
||||
}
|
||||
|
||||
payload := C.GoString(jsonPayload)
|
||||
|
||||
var response frontend.NotificationResponse
|
||||
if err := json.Unmarshal([]byte(payload), &response); err != nil {
|
||||
result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err)
|
||||
handleNotificationResult(result)
|
||||
return
|
||||
}
|
||||
|
||||
if response.ActionIdentifier == AppleDefaultActionIdentifier {
|
||||
response.ActionIdentifier = DefaultActionIdentifier
|
||||
}
|
||||
|
||||
result.Response = response
|
||||
handleNotificationResult(result)
|
||||
}
|
||||
|
||||
func handleNotificationResult(result frontend.NotificationResult) {
|
||||
callbackLock.Lock()
|
||||
callback := notificationResultCallback
|
||||
callbackLock.Unlock()
|
||||
|
||||
if callback != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log panic but don't crash the app
|
||||
fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
|
||||
}
|
||||
}()
|
||||
callback(result)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
func (f *Frontend) registerChannel() (int, chan notificationChannel) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
// Initialize channels map if it's nil
|
||||
if channels == nil {
|
||||
channels = make(map[int]chan notificationChannel)
|
||||
nextChannelID = 0
|
||||
}
|
||||
|
||||
id := nextChannelID
|
||||
nextChannelID++
|
||||
|
||||
resultCh := make(chan notificationChannel, 1)
|
||||
|
||||
channels[id] = resultCh
|
||||
return id, resultCh
|
||||
}
|
||||
|
||||
func (f *Frontend) GetChannel(id int) (chan notificationChannel, bool) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
if channels == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ch, exists := channels[id]
|
||||
if exists {
|
||||
delete(channels, id)
|
||||
}
|
||||
return ch, exists
|
||||
}
|
||||
|
||||
func (f *Frontend) cleanupChannel(id int) {
|
||||
channelsLock.Lock()
|
||||
defer channelsLock.Unlock()
|
||||
|
||||
if channels == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ch, exists := channels[id]; exists {
|
||||
delete(channels, id)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
118
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/screen.go
generated
vendored
Normal file
118
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/screen.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
typedef struct Screen {
|
||||
int isCurrent;
|
||||
int isPrimary;
|
||||
int height;
|
||||
int width;
|
||||
int pHeight;
|
||||
int pWidth;
|
||||
} Screen;
|
||||
|
||||
|
||||
int GetNumScreens(){
|
||||
return [[NSScreen screens] count];
|
||||
}
|
||||
|
||||
int screenUniqueID(NSScreen *screen){
|
||||
// adapted from https://stackoverflow.com/a/1237490/4188138
|
||||
NSDictionary* screenDictionary = [screen deviceDescription];
|
||||
NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
|
||||
CGDirectDisplayID aID = [screenID unsignedIntValue];
|
||||
return aID;
|
||||
}
|
||||
|
||||
Screen GetNthScreen(int nth, void *inctx){
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSArray<NSScreen *> *screens = [NSScreen screens];
|
||||
NSScreen* nthScreen = [screens objectAtIndex:nth];
|
||||
NSScreen* currentScreen = [ctx getCurrentScreen];
|
||||
|
||||
Screen returnScreen;
|
||||
returnScreen.isCurrent = (int)(screenUniqueID(currentScreen)==screenUniqueID(nthScreen));
|
||||
// TODO properly handle screen mirroring
|
||||
// from apple documentation:
|
||||
// https://developer.apple.com/documentation/appkit/nsscreen/1388393-screens?language=objc
|
||||
// The screen at index 0 in the returned array corresponds to the primary screen of the user’s system. This is the screen that contains the menu bar and whose origin is at the point (0, 0). In the case of mirroring, the first screen is the largest drawable display; if all screens are the same size, it is the screen with the highest pixel depth. This primary screen may not be the same as the one returned by the mainScreen method, which returns the screen with the active window.
|
||||
returnScreen.isPrimary = nth==0;
|
||||
returnScreen.height = (int) nthScreen.frame.size.height;
|
||||
returnScreen.width = (int) nthScreen.frame.size.width;
|
||||
|
||||
returnScreen.pWidth = 0;
|
||||
returnScreen.pHeight = 0;
|
||||
|
||||
// https://stackoverflow.com/questions/13859109/how-to-programmatically-determine-native-pixel-resolution-of-retina-macbook-pro
|
||||
CGDirectDisplayID sid = ((NSNumber *)[nthScreen.deviceDescription
|
||||
objectForKey:@"NSScreenNumber"]).unsignedIntegerValue;
|
||||
|
||||
CFArrayRef ms = CGDisplayCopyAllDisplayModes(sid, NULL);
|
||||
CFIndex n = CFArrayGetCount(ms);
|
||||
for (int i = 0; i < n; i++) {
|
||||
CGDisplayModeRef m = (CGDisplayModeRef) CFArrayGetValueAtIndex(ms, i);
|
||||
if (CGDisplayModeGetIOFlags(m) & kDisplayModeNativeFlag) {
|
||||
// This corresponds with "System Settings" -> General -> About -> Displays
|
||||
returnScreen.pWidth = CGDisplayModeGetPixelWidth(m);
|
||||
returnScreen.pHeight = CGDisplayModeGetPixelHeight(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(ms);
|
||||
|
||||
if (returnScreen.pWidth == 0 || returnScreen.pHeight == 0) {
|
||||
// If there was no native resolution take a best fit approach and use the backing pixel size.
|
||||
NSRect pSize = [nthScreen convertRectToBacking:nthScreen.frame];
|
||||
returnScreen.pHeight = (int) pSize.size.height;
|
||||
returnScreen.pWidth = (int) pSize.size.width;
|
||||
}
|
||||
return returnScreen;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
func GetAllScreens(wailsContext unsafe.Pointer) ([]frontend.Screen, error) {
|
||||
err := error(nil)
|
||||
screens := []frontend.Screen{}
|
||||
numScreens := int(C.GetNumScreens())
|
||||
for screeNum := 0; screeNum < numScreens; screeNum++ {
|
||||
screenNumC := C.int(screeNum)
|
||||
cScreen := C.GetNthScreen(screenNumC, wailsContext)
|
||||
|
||||
screen := frontend.Screen{
|
||||
Height: int(cScreen.height),
|
||||
Width: int(cScreen.width),
|
||||
IsCurrent: cScreen.isCurrent == C.int(1),
|
||||
IsPrimary: cScreen.isPrimary == C.int(1),
|
||||
|
||||
Size: frontend.ScreenSize{
|
||||
Height: int(cScreen.height),
|
||||
Width: int(cScreen.width),
|
||||
},
|
||||
PhysicalSize: frontend.ScreenSize{
|
||||
Height: int(cScreen.pHeight),
|
||||
Width: int(cScreen.pWidth),
|
||||
},
|
||||
}
|
||||
screens = append(screens, screen)
|
||||
}
|
||||
return screens, err
|
||||
}
|
||||
95
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/single_instance.go
generated
vendored
Normal file
95
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/single_instance.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa
|
||||
#import "AppDelegate.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func SetupSingleInstance(uniqueID string) *os.File {
|
||||
lockFilePath := getTempDir()
|
||||
lockFileName := uniqueID + ".lock"
|
||||
file, err := createLockFile(lockFilePath + "/" + lockFileName)
|
||||
// if lockFile exist – send notification to second instance
|
||||
if err != nil {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
singleInstanceUniqueId := c.String(uniqueID)
|
||||
|
||||
data, err := options.NewSecondInstanceData()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
C.SendDataToFirstInstance(singleInstanceUniqueId, c.String(string(serialized)))
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
//export HandleSecondInstanceData
|
||||
func HandleSecondInstanceData(secondInstanceMessage *C.char) {
|
||||
message := C.GoString(secondInstanceMessage)
|
||||
|
||||
var secondInstanceData options.SecondInstanceData
|
||||
|
||||
err := json.Unmarshal([]byte(message), &secondInstanceData)
|
||||
if err == nil {
|
||||
secondInstanceBuffer <- secondInstanceData
|
||||
}
|
||||
}
|
||||
|
||||
// createLockFile tries to create a file with given name and acquire an
|
||||
// exclusive lock on it. If the file already exists AND is still locked, it will
|
||||
// fail.
|
||||
func createLockFile(filename string) (*os.File, error) {
|
||||
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open lockfile %s: %s", filename, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
// Flock failed for some other reason than other instance already lock it. Print it in logs for possible debugging.
|
||||
if !strings.Contains(err.Error(), "resource temporarily unavailable") {
|
||||
fmt.Printf("Failed to lock lockfile %s: %s", filename, err)
|
||||
}
|
||||
file.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// If app is sandboxed, golang os.TempDir() will return path that will not be accessible. So use native macOS temp dir function.
|
||||
func getTempDir() string {
|
||||
cstring := C.GetMacOsNativeTempDir()
|
||||
path := C.GoString(cstring)
|
||||
C.free(unsafe.Pointer(cstring))
|
||||
|
||||
return path
|
||||
}
|
||||
313
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/window.go
generated
vendored
Normal file
313
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/window.go
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type Window struct {
|
||||
context unsafe.Pointer
|
||||
|
||||
applicationMenu *menu.Menu
|
||||
}
|
||||
|
||||
func bool2Cint(value bool) C.int {
|
||||
if value {
|
||||
return C.int(1)
|
||||
}
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
func bool2CboolPtr(value bool) *C.bool {
|
||||
v := C.bool(value)
|
||||
return &v
|
||||
}
|
||||
|
||||
func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
|
||||
frameless := bool2Cint(frontendOptions.Frameless)
|
||||
resizable := bool2Cint(!frontendOptions.DisableResize)
|
||||
fullscreen := bool2Cint(frontendOptions.Fullscreen)
|
||||
alwaysOnTop := bool2Cint(frontendOptions.AlwaysOnTop)
|
||||
hideWindowOnClose := bool2Cint(frontendOptions.HideWindowOnClose)
|
||||
startsHidden := bool2Cint(frontendOptions.StartHidden)
|
||||
devtoolsEnabled := bool2Cint(devtools)
|
||||
defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu)
|
||||
singleInstanceEnabled := bool2Cint(frontendOptions.SingleInstanceLock != nil)
|
||||
|
||||
var fullSizeContent, hideTitleBar, zoomable, hideTitle, useToolbar, webviewIsTransparent C.int
|
||||
var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent, contentProtection C.int
|
||||
var appearance, title *C.char
|
||||
var preferences C.struct_Preferences
|
||||
|
||||
width := C.int(frontendOptions.Width)
|
||||
height := C.int(frontendOptions.Height)
|
||||
minWidth := C.int(frontendOptions.MinWidth)
|
||||
minHeight := C.int(frontendOptions.MinHeight)
|
||||
maxWidth := C.int(frontendOptions.MaxWidth)
|
||||
maxHeight := C.int(frontendOptions.MaxHeight)
|
||||
windowStartState := C.int(int(frontendOptions.WindowStartState))
|
||||
|
||||
title = c.String(frontendOptions.Title)
|
||||
|
||||
singleInstanceUniqueIdStr := ""
|
||||
if frontendOptions.SingleInstanceLock != nil {
|
||||
singleInstanceUniqueIdStr = frontendOptions.SingleInstanceLock.UniqueId
|
||||
}
|
||||
singleInstanceUniqueId := c.String(singleInstanceUniqueIdStr)
|
||||
|
||||
enableFraudulentWebsiteWarnings := C.bool(frontendOptions.EnableFraudulentWebsiteDetection)
|
||||
|
||||
enableDragAndDrop := C.bool(frontendOptions.DragAndDrop != nil && frontendOptions.DragAndDrop.EnableFileDrop)
|
||||
disableWebViewDragAndDrop := C.bool(frontendOptions.DragAndDrop != nil && frontendOptions.DragAndDrop.DisableWebViewDrop)
|
||||
|
||||
if frontendOptions.Mac != nil {
|
||||
mac := frontendOptions.Mac
|
||||
if mac.TitleBar != nil {
|
||||
fullSizeContent = bool2Cint(mac.TitleBar.FullSizeContent)
|
||||
hideTitleBar = bool2Cint(mac.TitleBar.HideTitleBar)
|
||||
hideTitle = bool2Cint(mac.TitleBar.HideTitle)
|
||||
useToolbar = bool2Cint(mac.TitleBar.UseToolbar)
|
||||
titlebarAppearsTransparent = bool2Cint(mac.TitleBar.TitlebarAppearsTransparent)
|
||||
hideToolbarSeparator = bool2Cint(mac.TitleBar.HideToolbarSeparator)
|
||||
}
|
||||
|
||||
if mac.Preferences != nil {
|
||||
if mac.Preferences.TabFocusesLinks.IsSet() {
|
||||
preferences.tabFocusesLinks = bool2CboolPtr(mac.Preferences.TabFocusesLinks.Get())
|
||||
}
|
||||
|
||||
if mac.Preferences.TextInteractionEnabled.IsSet() {
|
||||
preferences.textInteractionEnabled = bool2CboolPtr(mac.Preferences.TextInteractionEnabled.Get())
|
||||
}
|
||||
|
||||
if mac.Preferences.FullscreenEnabled.IsSet() {
|
||||
preferences.fullscreenEnabled = bool2CboolPtr(mac.Preferences.FullscreenEnabled.Get())
|
||||
}
|
||||
}
|
||||
|
||||
zoomable = bool2Cint(!frontendOptions.Mac.DisableZoom)
|
||||
|
||||
windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent)
|
||||
webviewIsTransparent = bool2Cint(mac.WebviewIsTransparent)
|
||||
contentProtection = bool2Cint(mac.ContentProtection)
|
||||
|
||||
appearance = c.String(string(mac.Appearance))
|
||||
}
|
||||
var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, zoomable, fullscreen, fullSizeContent,
|
||||
hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent,
|
||||
alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, contentProtection, devtoolsEnabled, defaultContextMenuEnabled,
|
||||
windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings,
|
||||
preferences, singleInstanceEnabled, singleInstanceUniqueId, enableDragAndDrop, disableWebViewDragAndDrop,
|
||||
)
|
||||
|
||||
// Create menu
|
||||
result := &Window{
|
||||
context: unsafe.Pointer(context),
|
||||
}
|
||||
|
||||
if frontendOptions.BackgroundColour != nil {
|
||||
result.SetBackgroundColour(frontendOptions.BackgroundColour.R, frontendOptions.BackgroundColour.G, frontendOptions.BackgroundColour.B, frontendOptions.BackgroundColour.A)
|
||||
}
|
||||
|
||||
if frontendOptions.Mac != nil && frontendOptions.Mac.About != nil {
|
||||
title := c.String(frontendOptions.Mac.About.Title)
|
||||
description := c.String(frontendOptions.Mac.About.Message)
|
||||
var icon unsafe.Pointer
|
||||
var length C.int
|
||||
if frontendOptions.Mac.About.Icon != nil {
|
||||
icon = unsafe.Pointer(&frontendOptions.Mac.About.Icon[0])
|
||||
length = C.int(len(frontendOptions.Mac.About.Icon))
|
||||
}
|
||||
C.SetAbout(result.context, title, description, icon, length)
|
||||
}
|
||||
|
||||
if frontendOptions.Menu != nil {
|
||||
result.SetApplicationMenu(frontendOptions.Menu)
|
||||
}
|
||||
|
||||
if debug && frontendOptions.Debug.OpenInspectorOnStartup {
|
||||
showInspector(result.context)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (w *Window) Center() {
|
||||
C.Center(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) Run(url string) {
|
||||
_url := C.CString(url)
|
||||
C.Run(w.context, _url)
|
||||
C.free(unsafe.Pointer(_url))
|
||||
}
|
||||
|
||||
func (w *Window) Quit() {
|
||||
C.Quit(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) SetBackgroundColour(r uint8, g uint8, b uint8, a uint8) {
|
||||
C.SetBackgroundColour(w.context, C.int(r), C.int(g), C.int(b), C.int(a))
|
||||
}
|
||||
|
||||
func (w *Window) ExecJS(js string) {
|
||||
_js := C.CString(js)
|
||||
C.ExecJS(w.context, _js)
|
||||
C.free(unsafe.Pointer(_js))
|
||||
}
|
||||
|
||||
func (w *Window) SetPosition(x int, y int) {
|
||||
C.SetPosition(w.context, C.int(x), C.int(y))
|
||||
}
|
||||
|
||||
func (w *Window) SetSize(width int, height int) {
|
||||
C.SetSize(w.context, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetAlwaysOnTop(onTop bool) {
|
||||
C.SetAlwaysOnTop(w.context, bool2Cint(onTop))
|
||||
}
|
||||
|
||||
func (w *Window) SetTitle(title string) {
|
||||
t := C.CString(title)
|
||||
C.SetTitle(w.context, t)
|
||||
C.free(unsafe.Pointer(t))
|
||||
}
|
||||
|
||||
func (w *Window) Maximise() {
|
||||
C.Maximise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) ToggleMaximise() {
|
||||
C.ToggleMaximise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) UnMaximise() {
|
||||
C.UnMaximise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) IsMaximised() bool {
|
||||
return (bool)(C.IsMaximised(w.context))
|
||||
}
|
||||
|
||||
func (w *Window) Minimise() {
|
||||
C.Minimise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) UnMinimise() {
|
||||
C.UnMinimise(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) IsMinimised() bool {
|
||||
return (bool)(C.IsMinimised(w.context))
|
||||
}
|
||||
|
||||
func (w *Window) IsNormal() bool {
|
||||
return !w.IsMaximised() && !w.IsMinimised() && !w.IsFullScreen()
|
||||
}
|
||||
|
||||
func (w *Window) SetMinSize(width int, height int) {
|
||||
C.SetMinSize(w.context, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetMaxSize(width int, height int) {
|
||||
C.SetMaxSize(w.context, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) Fullscreen() {
|
||||
C.Fullscreen(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) UnFullscreen() {
|
||||
C.UnFullscreen(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) IsFullScreen() bool {
|
||||
return (bool)(C.IsFullScreen(w.context))
|
||||
}
|
||||
|
||||
func (w *Window) Show() {
|
||||
C.Show(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) Hide() {
|
||||
C.Hide(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) ShowApplication() {
|
||||
C.ShowApplication(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) HideApplication() {
|
||||
C.HideApplication(w.context)
|
||||
}
|
||||
|
||||
func parseIntDuo(temp string) (int, int) {
|
||||
split := strings.Split(temp, ",")
|
||||
x, err := strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
y, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (w *Window) GetPosition() (int, int) {
|
||||
var _result *C.char = C.GetPosition(w.context)
|
||||
temp := C.GoString(_result)
|
||||
return parseIntDuo(temp)
|
||||
}
|
||||
|
||||
func (w *Window) Size() (int, int) {
|
||||
var _result *C.char = C.GetSize(w.context)
|
||||
temp := C.GoString(_result)
|
||||
return parseIntDuo(temp)
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(inMenu *menu.Menu) {
|
||||
w.applicationMenu = inMenu
|
||||
w.UpdateApplicationMenu()
|
||||
}
|
||||
|
||||
func (w *Window) UpdateApplicationMenu() {
|
||||
mainMenu := NewNSMenu(w.context, "")
|
||||
if w.applicationMenu != nil {
|
||||
processMenu(mainMenu, w.applicationMenu)
|
||||
}
|
||||
C.SetAsApplicationMenu(w.context, mainMenu.nsmenu)
|
||||
C.UpdateApplicationMenu(w.context)
|
||||
}
|
||||
|
||||
func (w Window) Print() {
|
||||
C.WindowPrint(w.context)
|
||||
}
|
||||
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_darwin.go
generated
vendored
Normal file
20
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
|
||||
return darwin.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_linux.go
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_linux.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/linux"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
|
||||
return linux.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_windows.go
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/desktop_windows.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
|
||||
return windows.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
|
||||
}
|
||||
23
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/browser.go
generated
vendored
Normal file
23
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/browser.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/utils"
|
||||
)
|
||||
|
||||
// BrowserOpenURL Use the default browser to open the url
|
||||
func (f *Frontend) BrowserOpenURL(rawURL string) {
|
||||
url, err := utils.ValidateAndSanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error()))
|
||||
return
|
||||
}
|
||||
// Specific method implementation
|
||||
if err := browser.OpenURL(url); err != nil {
|
||||
f.logger.Error("Unable to open default system browser")
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/calloc.go
generated
vendored
Normal file
35
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/calloc.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Calloc handles alloc/dealloc of C data
|
||||
type Calloc struct {
|
||||
pool []unsafe.Pointer
|
||||
}
|
||||
|
||||
// NewCalloc creates a new allocator
|
||||
func NewCalloc() Calloc {
|
||||
return Calloc{}
|
||||
}
|
||||
|
||||
// String creates a new C string and retains a reference to it
|
||||
func (c Calloc) String(in string) *C.char {
|
||||
result := C.CString(in)
|
||||
c.pool = append(c.pool, unsafe.Pointer(result))
|
||||
return result
|
||||
}
|
||||
|
||||
// Free frees all allocated C memory
|
||||
func (c Calloc) Free() {
|
||||
for _, str := range c.pool {
|
||||
C.free(str)
|
||||
}
|
||||
c.pool = []unsafe.Pointer{}
|
||||
}
|
||||
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/clipboard.go
generated
vendored
Normal file
51
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
|
||||
static gchar* GetClipboardText() {
|
||||
GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
return gtk_clipboard_wait_for_text(clip);
|
||||
}
|
||||
|
||||
static void SetClipboardText(gchar* text) {
|
||||
GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
gtk_clipboard_set_text(clip, text, -1);
|
||||
|
||||
clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
|
||||
gtk_clipboard_set_text(clip, text, -1);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import "sync"
|
||||
|
||||
func (f *Frontend) ClipboardGetText() (string, error) {
|
||||
var text string
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
ctxt := C.GetClipboardText()
|
||||
defer C.g_free(C.gpointer(ctxt))
|
||||
text = C.GoString(ctxt)
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardSetText(text string) error {
|
||||
invokeOnMainThread(func() {
|
||||
ctxt := (*C.gchar)(C.CString(text))
|
||||
defer C.g_free(C.gpointer(ctxt))
|
||||
C.SetClipboardText(ctxt)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/dialog.go
generated
vendored
Normal file
89
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include "gtk/gtk.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_OPEN
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_SAVE
|
||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
|
||||
)
|
||||
|
||||
var openFileResults = make(chan []string)
|
||||
var messageDialogResult = make(chan string)
|
||||
|
||||
func (f *Frontend) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (result string, err error) {
|
||||
f.mainWindow.OpenFileDialog(dialogOptions, 0, GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
results := <-openFileResults
|
||||
if len(results) == 1 {
|
||||
return results[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
|
||||
f.mainWindow.OpenFileDialog(dialogOptions, 1, GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
result := <-openFileResults
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OpenDirectoryDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
|
||||
f.mainWindow.OpenFileDialog(dialogOptions, 0, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||
result := <-openFileResults
|
||||
if len(result) == 1 {
|
||||
return result[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Frontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
|
||||
options := frontend.OpenDialogOptions{
|
||||
DefaultDirectory: dialogOptions.DefaultDirectory,
|
||||
DefaultFilename: dialogOptions.DefaultFilename,
|
||||
Title: dialogOptions.Title,
|
||||
Filters: dialogOptions.Filters,
|
||||
ShowHiddenFiles: dialogOptions.ShowHiddenFiles,
|
||||
CanCreateDirectories: dialogOptions.CanCreateDirectories,
|
||||
}
|
||||
f.mainWindow.OpenFileDialog(options, 0, GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
results := <-openFileResults
|
||||
if len(results) == 1 {
|
||||
return results[0], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Frontend) MessageDialog(dialogOptions frontend.MessageDialogOptions) (string, error) {
|
||||
f.mainWindow.MessageDialog(dialogOptions)
|
||||
return <-messageDialogResult, nil
|
||||
}
|
||||
|
||||
//export processOpenFileResult
|
||||
func processOpenFileResult(carray **C.char) {
|
||||
// Create a Go slice from the C array
|
||||
var result []string
|
||||
goArray := (*[1024]*C.char)(unsafe.Pointer(carray))[:1024:1024]
|
||||
for _, s := range goArray {
|
||||
if s == nil {
|
||||
break
|
||||
}
|
||||
result = append(result, C.GoString(s))
|
||||
}
|
||||
openFileResults <- result
|
||||
}
|
||||
|
||||
//export processMessageDialogResult
|
||||
func processMessageDialogResult(result *C.char) {
|
||||
messageDialogResult <- C.GoString(result)
|
||||
}
|
||||
589
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/frontend.go
generated
vendored
Normal file
589
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/frontend.go
generated
vendored
Normal file
@@ -0,0 +1,589 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
|
||||
// CREDIT: https://github.com/rainycape/magick
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void fix_signal(int signum)
|
||||
{
|
||||
struct sigaction st;
|
||||
|
||||
if (sigaction(signum, NULL, &st) < 0) {
|
||||
goto fix_signal_error;
|
||||
}
|
||||
st.sa_flags |= SA_ONSTACK;
|
||||
if (sigaction(signum, &st, NULL) < 0) {
|
||||
goto fix_signal_error;
|
||||
}
|
||||
return;
|
||||
fix_signal_error:
|
||||
fprintf(stderr, "error fixing handler for signal %d, please "
|
||||
"report this issue to "
|
||||
"https://github.com/wailsapp/wails: %s\n",
|
||||
signum, strerror(errno));
|
||||
}
|
||||
|
||||
static void install_signal_handlers()
|
||||
{
|
||||
#if defined(SIGCHLD)
|
||||
fix_signal(SIGCHLD);
|
||||
#endif
|
||||
#if defined(SIGHUP)
|
||||
fix_signal(SIGHUP);
|
||||
#endif
|
||||
#if defined(SIGINT)
|
||||
fix_signal(SIGINT);
|
||||
#endif
|
||||
#if defined(SIGQUIT)
|
||||
fix_signal(SIGQUIT);
|
||||
#endif
|
||||
#if defined(SIGABRT)
|
||||
fix_signal(SIGABRT);
|
||||
#endif
|
||||
#if defined(SIGFPE)
|
||||
fix_signal(SIGFPE);
|
||||
#endif
|
||||
#if defined(SIGTERM)
|
||||
fix_signal(SIGTERM);
|
||||
#endif
|
||||
#if defined(SIGBUS)
|
||||
fix_signal(SIGBUS);
|
||||
#endif
|
||||
#if defined(SIGSEGV)
|
||||
fix_signal(SIGSEGV);
|
||||
#endif
|
||||
#if defined(SIGXCPU)
|
||||
fix_signal(SIGXCPU);
|
||||
#endif
|
||||
#if defined(SIGXFSZ)
|
||||
fix_signal(SIGXFSZ);
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean install_signal_handlers_idle(gpointer data) {
|
||||
(void)data;
|
||||
install_signal_handlers();
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void fix_signal_handlers_after_gtk_init() {
|
||||
g_idle_add(install_signal_handlers_idle, NULL);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
var initOnce = sync.Once{}
|
||||
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
var secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
|
||||
|
||||
type Frontend struct {
|
||||
|
||||
// Context
|
||||
ctx context.Context
|
||||
|
||||
frontendOptions *options.App
|
||||
logger *logger.Logger
|
||||
debug bool
|
||||
devtoolsEnabled bool
|
||||
|
||||
// Assets
|
||||
assets *assetserver.AssetServer
|
||||
startURL *url.URL
|
||||
|
||||
// main window handle
|
||||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
func (f *Frontend) RunMainLoop() {
|
||||
C.gtk_main()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowClose() {
|
||||
f.mainWindow.Destroy()
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
initOnce.Do(func() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
// Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings
|
||||
if os.Getenv("GDK_BACKEND") == "" && (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") {
|
||||
_ = os.Setenv("GDK_BACKEND", "x11")
|
||||
}
|
||||
|
||||
if ok := C.gtk_init_check(nil, nil); ok != 1 {
|
||||
panic(errors.New("failed to init GTK"))
|
||||
}
|
||||
})
|
||||
|
||||
result := &Frontend{
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
bindings: appBindings,
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
} else {
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
var err error
|
||||
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
|
||||
bindings, err = appBindings.ToJSON()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
appBindings.DB().UpdateObfuscatedCallMap()
|
||||
}
|
||||
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
result.assets = assets
|
||||
|
||||
go result.startRequestProcessor()
|
||||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startBindingsMessageProcessor()
|
||||
|
||||
var _debug = ctx.Value("debug")
|
||||
var _devtoolsEnabled = ctx.Value("devtoolsEnabled")
|
||||
|
||||
if _debug != nil {
|
||||
result.debug = _debug.(bool)
|
||||
}
|
||||
if _devtoolsEnabled != nil {
|
||||
result.devtoolsEnabled = _devtoolsEnabled.(bool)
|
||||
}
|
||||
|
||||
result.mainWindow = NewWindow(appoptions, result.debug, result.devtoolsEnabled)
|
||||
|
||||
C.fix_signal_handlers_after_gtk_init()
|
||||
|
||||
if appoptions.Linux != nil && appoptions.Linux.ProgramName != "" {
|
||||
prgname := C.CString(appoptions.Linux.ProgramName)
|
||||
C.g_set_prgname(prgname)
|
||||
C.free(unsafe.Pointer(prgname))
|
||||
}
|
||||
|
||||
go result.startSecondInstanceProcessor()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Frontend) startMessageProcessor() {
|
||||
for message := range messageBuffer {
|
||||
f.processMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startBindingsMessageProcessor() {
|
||||
for msg := range bindingsMessageBuffer {
|
||||
origin, err := f.originValidator.GetOriginFromURL(msg.source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err))
|
||||
continue
|
||||
}
|
||||
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
continue
|
||||
}
|
||||
|
||||
f.processMessage(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReload() {
|
||||
f.ExecJS("runtime.WindowReload();")
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSystemDefaultTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetLightTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetDarkTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) Run(ctx context.Context) error {
|
||||
f.ctx = ctx
|
||||
|
||||
go func() {
|
||||
if f.frontendOptions.OnStartup != nil {
|
||||
f.frontendOptions.OnStartup(f.ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
if f.frontendOptions.SingleInstanceLock != nil {
|
||||
SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
|
||||
}
|
||||
|
||||
f.mainWindow.Run(f.startURL.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowCenter() {
|
||||
f.mainWindow.Center()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetAlwaysOnTop(b bool) {
|
||||
f.mainWindow.SetKeepAbove(b)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetPosition(x, y int) {
|
||||
f.mainWindow.SetPosition(x, y)
|
||||
}
|
||||
func (f *Frontend) WindowGetPosition() (int, int) {
|
||||
return f.mainWindow.GetPosition()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetSize(width, height int) {
|
||||
f.mainWindow.SetSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowGetSize() (int, int) {
|
||||
return f.mainWindow.Size()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetTitle(title string) {
|
||||
f.mainWindow.SetTitle(title)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowFullscreen() {
|
||||
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
||||
f.ExecJS("window.wails.flags.enableResize = false;")
|
||||
}
|
||||
f.mainWindow.Fullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowUnfullscreen() {
|
||||
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
||||
f.ExecJS("window.wails.flags.enableResize = true;")
|
||||
}
|
||||
f.mainWindow.UnFullscreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReloadApp() {
|
||||
f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowShow() {
|
||||
f.mainWindow.Show()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowHide() {
|
||||
f.mainWindow.Hide()
|
||||
}
|
||||
|
||||
func (f *Frontend) Show() {
|
||||
f.mainWindow.Show()
|
||||
}
|
||||
|
||||
func (f *Frontend) Hide() {
|
||||
f.mainWindow.Hide()
|
||||
}
|
||||
func (f *Frontend) WindowMaximise() {
|
||||
f.mainWindow.Maximise()
|
||||
}
|
||||
func (f *Frontend) WindowToggleMaximise() {
|
||||
f.mainWindow.ToggleMaximise()
|
||||
}
|
||||
func (f *Frontend) WindowUnmaximise() {
|
||||
f.mainWindow.UnMaximise()
|
||||
}
|
||||
func (f *Frontend) WindowMinimise() {
|
||||
f.mainWindow.Minimise()
|
||||
}
|
||||
func (f *Frontend) WindowUnminimise() {
|
||||
f.mainWindow.UnMinimise()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetMinSize(width int, height int) {
|
||||
f.mainWindow.SetMinSize(width, height)
|
||||
}
|
||||
func (f *Frontend) WindowSetMaxSize(width int, height int) {
|
||||
f.mainWindow.SetMaxSize(width, height)
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
|
||||
if col == nil {
|
||||
return
|
||||
}
|
||||
f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
|
||||
}
|
||||
|
||||
func (f *Frontend) ScreenGetAll() ([]Screen, error) {
|
||||
return GetAllScreens(f.mainWindow.asGTKWindow())
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMaximised() bool {
|
||||
return f.mainWindow.IsMaximised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsMinimised() bool {
|
||||
return f.mainWindow.IsMinimised()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsNormal() bool {
|
||||
return f.mainWindow.IsNormal()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowIsFullscreen() bool {
|
||||
return f.mainWindow.IsFullScreen()
|
||||
}
|
||||
|
||||
func (f *Frontend) Quit() {
|
||||
if f.frontendOptions.OnBeforeClose != nil {
|
||||
go func() {
|
||||
if !f.frontendOptions.OnBeforeClose(f.ctx) {
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
f.mainWindow.Quit()
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowPrint() {
|
||||
f.ExecJS("window.print();")
|
||||
}
|
||||
|
||||
type EventNotify struct {
|
||||
Name string `json:"name"`
|
||||
Data []interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (f *Frontend) Notify(name string, data ...interface{}) {
|
||||
notification := EventNotify{
|
||||
Name: name,
|
||||
Data: data,
|
||||
}
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
f.mainWindow.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
|
||||
}
|
||||
|
||||
var edgeMap = map[string]uintptr{
|
||||
"n-resize": C.GDK_WINDOW_EDGE_NORTH,
|
||||
"ne-resize": C.GDK_WINDOW_EDGE_NORTH_EAST,
|
||||
"e-resize": C.GDK_WINDOW_EDGE_EAST,
|
||||
"se-resize": C.GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
"s-resize": C.GDK_WINDOW_EDGE_SOUTH,
|
||||
"sw-resize": C.GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
"w-resize": C.GDK_WINDOW_EDGE_WEST,
|
||||
"nw-resize": C.GDK_WINDOW_EDGE_NORTH_WEST,
|
||||
}
|
||||
|
||||
func (f *Frontend) processMessage(message string) {
|
||||
if message == "DomReady" {
|
||||
if f.frontendOptions.OnDomReady != nil {
|
||||
f.frontendOptions.OnDomReady(f.ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "drag" {
|
||||
if !f.mainWindow.IsFullScreen() {
|
||||
f.startDrag()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "wails:showInspector" {
|
||||
f.mainWindow.ShowInspector()
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "resize:") {
|
||||
if !f.mainWindow.IsFullScreen() {
|
||||
sl := strings.Split(message, ":")
|
||||
if len(sl) != 2 {
|
||||
f.logger.Info("Unknown message returned from dispatcher: %+v", message)
|
||||
return
|
||||
}
|
||||
edge := edgeMap[sl[1]]
|
||||
err := f.startResize(edge)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if message == "runtime:ready" {
|
||||
cmd := fmt.Sprintf(
|
||||
"window.wails.setCSSDragProperties('%s', '%s');\n"+
|
||||
"window.wails.setCSSDropProperties('%s', '%s');\n"+
|
||||
"window.wails.flags.deferDragToMouseMove = true;",
|
||||
f.frontendOptions.CSSDragProperty,
|
||||
f.frontendOptions.CSSDragValue,
|
||||
f.frontendOptions.DragAndDrop.CSSDropProperty,
|
||||
f.frontendOptions.DragAndDrop.CSSDropValue,
|
||||
)
|
||||
|
||||
f.ExecJS(cmd)
|
||||
|
||||
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
||||
f.ExecJS("window.wails.flags.enableResize = true;")
|
||||
}
|
||||
|
||||
if f.frontendOptions.DragAndDrop.EnableFileDrop {
|
||||
f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
result, err := f.dispatcher.ProcessMessage(message, f)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
f.Callback(result)
|
||||
return
|
||||
}
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
|
||||
switch result[0] {
|
||||
case 'c':
|
||||
// Callback from a method call
|
||||
f.Callback(result[1:])
|
||||
default:
|
||||
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
}
|
||||
|
||||
func (f *Frontend) startDrag() {
|
||||
f.mainWindow.StartDrag()
|
||||
}
|
||||
|
||||
func (f *Frontend) startResize(edge uintptr) error {
|
||||
f.mainWindow.StartResize(edge)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) ExecJS(js string) {
|
||||
f.mainWindow.ExecJS(js)
|
||||
}
|
||||
|
||||
type bindingsMessage struct {
|
||||
message string
|
||||
source string
|
||||
}
|
||||
|
||||
var messageBuffer = make(chan string, 100)
|
||||
var bindingsMessageBuffer = make(chan *bindingsMessage, 100)
|
||||
|
||||
//export processMessage
|
||||
func processMessage(message *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processBindingMessage
|
||||
func processBindingMessage(message *C.char, source *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
goSource := C.GoString(source)
|
||||
bindingsMessageBuffer <- &bindingsMessage{
|
||||
message: goMessage,
|
||||
source: goSource,
|
||||
}
|
||||
}
|
||||
|
||||
var requestBuffer = make(chan webview.Request, 100)
|
||||
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(request unsafe.Pointer) {
|
||||
requestBuffer <- webview.NewRequest(request)
|
||||
}
|
||||
|
||||
func (f *Frontend) startSecondInstanceProcessor() {
|
||||
for secondInstanceData := range secondInstanceBuffer {
|
||||
if f.frontendOptions.SingleInstanceLock != nil &&
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil {
|
||||
f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/gtk.go
generated
vendored
Normal file
85
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/gtk.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
|
||||
|
||||
extern void blockClick(GtkWidget* menuItem, gulong handler_id);
|
||||
extern void unblockClick(GtkWidget* menuItem, gulong handler_id);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
func GtkMenuItemWithLabel(label string) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_menu_item_new_with_label(cLabel)
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
func GtkCheckMenuItemWithLabel(label string) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_check_menu_item_new_with_label(cLabel)
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
func GtkRadioMenuItemWithLabel(label string, group *C.GSList) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_radio_menu_item_new_with_label(group, cLabel)
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
//export handleMenuItemClick
|
||||
func handleMenuItemClick(gtkWidget unsafe.Pointer) {
|
||||
// Make sure to execute the final callback on a new goroutine otherwise if the callback e.g. tries to open a dialog, the
|
||||
// main thread will get blocked and so the message loop blocks. As a result the app will block and shows a
|
||||
// "not responding" dialog.
|
||||
|
||||
item := gtkSignalToMenuItem[(*C.GtkWidget)(gtkWidget)]
|
||||
switch item.Type {
|
||||
case menu.CheckboxType:
|
||||
item.Checked = !item.Checked
|
||||
checked := C.int(0)
|
||||
if item.Checked {
|
||||
checked = C.int(1)
|
||||
}
|
||||
for _, gtkCheckbox := range gtkCheckboxCache[item] {
|
||||
handler := gtkSignalHandlers[gtkCheckbox]
|
||||
C.blockClick(gtkCheckbox, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkCheckbox)), checked)
|
||||
C.unblockClick(gtkCheckbox, handler)
|
||||
}
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
case menu.RadioType:
|
||||
gtkRadioItems := gtkRadioMenuCache[item]
|
||||
active := C.gtk_check_menu_item_get_active(C.toGtkCheckMenuItem(gtkWidget))
|
||||
if int(active) == 1 {
|
||||
for _, gtkRadioItem := range gtkRadioItems {
|
||||
handler := gtkSignalHandlers[gtkRadioItem]
|
||||
C.blockClick(gtkRadioItem, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkRadioItem)), 1)
|
||||
C.unblockClick(gtkRadioItem, handler)
|
||||
}
|
||||
item.Checked = true
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
} else {
|
||||
item.Checked = false
|
||||
}
|
||||
default:
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
}
|
||||
}
|
||||
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/invoke.go
generated
vendored
Normal file
78
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/invoke.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
|
||||
#include <stdio.h>
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
extern gboolean invokeCallbacks(void *);
|
||||
|
||||
static inline void triggerInvokesOnMainThread() {
|
||||
g_idle_add((GSourceFunc)invokeCallbacks, NULL);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
m sync.Mutex
|
||||
mainTid int
|
||||
dispatchq []func()
|
||||
)
|
||||
|
||||
func invokeOnMainThread(f func()) {
|
||||
if tryInvokeOnCurrentGoRoutine(f) {
|
||||
return
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
dispatchq = append(dispatchq, f)
|
||||
m.Unlock()
|
||||
|
||||
C.triggerInvokesOnMainThread()
|
||||
}
|
||||
|
||||
func tryInvokeOnCurrentGoRoutine(f func()) bool {
|
||||
m.Lock()
|
||||
mainThreadID := mainTid
|
||||
m.Unlock()
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if mainThreadID != unix.Gettid() {
|
||||
return false
|
||||
}
|
||||
f()
|
||||
return true
|
||||
}
|
||||
|
||||
//export invokeCallbacks
|
||||
func invokeCallbacks(_ unsafe.Pointer) C.gboolean {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
m.Lock()
|
||||
if mainTid == 0 {
|
||||
mainTid = unix.Gettid()
|
||||
}
|
||||
|
||||
q := append([]func(){}, dispatchq...)
|
||||
dispatchq = []func(){}
|
||||
m.Unlock()
|
||||
|
||||
for _, v := range q {
|
||||
v()
|
||||
}
|
||||
return C.G_SOURCE_REMOVE
|
||||
}
|
||||
110
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/keys.go
generated
vendored
Normal file
110
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/keys.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
var namedKeysToGTK = map[string]C.guint{
|
||||
"backspace": C.guint(0xff08),
|
||||
"tab": C.guint(0xff09),
|
||||
"return": C.guint(0xff0d),
|
||||
"enter": C.guint(0xff0d),
|
||||
"escape": C.guint(0xff1b),
|
||||
"left": C.guint(0xff51),
|
||||
"right": C.guint(0xff53),
|
||||
"up": C.guint(0xff52),
|
||||
"down": C.guint(0xff54),
|
||||
"space": C.guint(0xff80),
|
||||
"delete": C.guint(0xff9f),
|
||||
"home": C.guint(0xff95),
|
||||
"end": C.guint(0xff9c),
|
||||
"page up": C.guint(0xff9a),
|
||||
"page down": C.guint(0xff9b),
|
||||
"f1": C.guint(0xffbe),
|
||||
"f2": C.guint(0xffbf),
|
||||
"f3": C.guint(0xffc0),
|
||||
"f4": C.guint(0xffc1),
|
||||
"f5": C.guint(0xffc2),
|
||||
"f6": C.guint(0xffc3),
|
||||
"f7": C.guint(0xffc4),
|
||||
"f8": C.guint(0xffc5),
|
||||
"f9": C.guint(0xffc6),
|
||||
"f10": C.guint(0xffc7),
|
||||
"f11": C.guint(0xffc8),
|
||||
"f12": C.guint(0xffc9),
|
||||
"f13": C.guint(0xffca),
|
||||
"f14": C.guint(0xffcb),
|
||||
"f15": C.guint(0xffcc),
|
||||
"f16": C.guint(0xffcd),
|
||||
"f17": C.guint(0xffce),
|
||||
"f18": C.guint(0xffcf),
|
||||
"f19": C.guint(0xffd0),
|
||||
"f20": C.guint(0xffd1),
|
||||
"f21": C.guint(0xffd2),
|
||||
"f22": C.guint(0xffd3),
|
||||
"f23": C.guint(0xffd4),
|
||||
"f24": C.guint(0xffd5),
|
||||
"f25": C.guint(0xffd6),
|
||||
"f26": C.guint(0xffd7),
|
||||
"f27": C.guint(0xffd8),
|
||||
"f28": C.guint(0xffd9),
|
||||
"f29": C.guint(0xffda),
|
||||
"f30": C.guint(0xffdb),
|
||||
"f31": C.guint(0xffdc),
|
||||
"f32": C.guint(0xffdd),
|
||||
"f33": C.guint(0xffde),
|
||||
"f34": C.guint(0xffdf),
|
||||
"f35": C.guint(0xffe0),
|
||||
"numlock": C.guint(0xff7f),
|
||||
}
|
||||
|
||||
func acceleratorToGTK(accelerator *keys.Accelerator) (C.guint, C.GdkModifierType) {
|
||||
key := parseKey(accelerator.Key)
|
||||
mods := parseModifiers(accelerator.Modifiers)
|
||||
return key, mods
|
||||
}
|
||||
|
||||
func parseKey(key string) C.guint {
|
||||
var result C.guint
|
||||
result, found := namedKeysToGTK[key]
|
||||
if found {
|
||||
return result
|
||||
}
|
||||
// Check for unknown namedkeys
|
||||
// Check if we only have a single character
|
||||
if len(key) != 1 {
|
||||
return C.guint(0)
|
||||
}
|
||||
keyval := rune(key[0])
|
||||
return C.gdk_unicode_to_keyval(C.guint(keyval))
|
||||
}
|
||||
|
||||
func parseModifiers(modifiers []keys.Modifier) C.GdkModifierType {
|
||||
|
||||
var result C.GdkModifierType
|
||||
|
||||
for _, modifier := range modifiers {
|
||||
switch modifier {
|
||||
case keys.ShiftKey:
|
||||
result |= C.GDK_SHIFT_MASK
|
||||
case keys.ControlKey, keys.CmdOrCtrlKey:
|
||||
result |= C.GDK_CONTROL_MASK
|
||||
case keys.OptionOrAltKey:
|
||||
result |= C.GDK_MOD1_MASK
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
169
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/menu.go
generated
vendored
Normal file
169
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/menu.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
static GtkMenuItem *toGtkMenuItem(void *pointer) { return (GTK_MENU_ITEM(pointer)); }
|
||||
static GtkMenuShell *toGtkMenuShell(void *pointer) { return (GTK_MENU_SHELL(pointer)); }
|
||||
static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
|
||||
static GtkRadioMenuItem *toGtkRadioMenuItem(void *pointer) { return (GTK_RADIO_MENU_ITEM(pointer)); }
|
||||
|
||||
extern void handleMenuItemClick(void*);
|
||||
|
||||
void blockClick(GtkWidget* menuItem, gulong handler_id) {
|
||||
g_signal_handler_block (menuItem, handler_id);
|
||||
}
|
||||
|
||||
void unblockClick(GtkWidget* menuItem, gulong handler_id) {
|
||||
g_signal_handler_unblock (menuItem, handler_id);
|
||||
}
|
||||
|
||||
gulong connectClick(GtkWidget* menuItem) {
|
||||
return g_signal_connect(menuItem, "activate", G_CALLBACK(handleMenuItemClick), (void*)menuItem);
|
||||
}
|
||||
|
||||
void addAccelerator(GtkWidget* menuItem, GtkAccelGroup* group, guint key, GdkModifierType mods) {
|
||||
gtk_widget_add_accelerator(menuItem, "activate", group, key, mods, GTK_ACCEL_VISIBLE);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
var menuIdCounter int
|
||||
var menuItemToId map[*menu.MenuItem]int
|
||||
var menuIdToItem map[int]*menu.MenuItem
|
||||
var gtkCheckboxCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
var gtkMenuCache map[*menu.MenuItem]*C.GtkWidget
|
||||
var gtkRadioMenuCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
var gtkSignalHandlers map[*C.GtkWidget]C.gulong
|
||||
var gtkSignalToMenuItem map[*C.GtkWidget]*menu.MenuItem
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
f.mainWindow.SetApplicationMenu(f.mainWindow.applicationMenu)
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(inmenu *menu.Menu) {
|
||||
if inmenu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Setup accelerator group
|
||||
w.accels = C.gtk_accel_group_new()
|
||||
C.gtk_window_add_accel_group(w.asGTKWindow(), w.accels)
|
||||
|
||||
menuItemToId = make(map[*menu.MenuItem]int)
|
||||
menuIdToItem = make(map[int]*menu.MenuItem)
|
||||
gtkCheckboxCache = make(map[*menu.MenuItem][]*C.GtkWidget)
|
||||
gtkMenuCache = make(map[*menu.MenuItem]*C.GtkWidget)
|
||||
gtkRadioMenuCache = make(map[*menu.MenuItem][]*C.GtkWidget)
|
||||
gtkSignalHandlers = make(map[*C.GtkWidget]C.gulong)
|
||||
gtkSignalToMenuItem = make(map[*C.GtkWidget]*menu.MenuItem)
|
||||
|
||||
// Increase ref count?
|
||||
w.menubar = C.gtk_menu_bar_new()
|
||||
|
||||
processMenu(w, inmenu)
|
||||
|
||||
C.gtk_widget_show(w.menubar)
|
||||
}
|
||||
|
||||
func processMenu(window *Window, menu *menu.Menu) {
|
||||
for _, menuItem := range menu.Items {
|
||||
if menuItem.SubMenu != nil {
|
||||
submenu := processSubmenu(menuItem, window.accels)
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(window.menubar)), submenu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processSubmenu(menuItem *menu.MenuItem, group *C.GtkAccelGroup) *C.GtkWidget {
|
||||
existingMenu := gtkMenuCache[menuItem]
|
||||
if existingMenu != nil {
|
||||
return existingMenu
|
||||
}
|
||||
gtkMenu := C.gtk_menu_new()
|
||||
submenu := GtkMenuItemWithLabel(menuItem.Label)
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
menuID := menuIdCounter
|
||||
menuIdToItem[menuID] = menuItem
|
||||
menuItemToId[menuItem] = menuID
|
||||
menuIdCounter++
|
||||
processMenuItem(gtkMenu, menuItem, group)
|
||||
}
|
||||
C.gtk_menu_item_set_submenu(C.toGtkMenuItem(unsafe.Pointer(submenu)), gtkMenu)
|
||||
gtkMenuCache[menuItem] = existingMenu
|
||||
return submenu
|
||||
}
|
||||
|
||||
var currentRadioGroup *C.GSList
|
||||
|
||||
func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkAccelGroup) {
|
||||
if menuItem.Hidden {
|
||||
return
|
||||
}
|
||||
|
||||
if menuItem.Type != menu.RadioType {
|
||||
currentRadioGroup = nil
|
||||
}
|
||||
|
||||
if menuItem.Type == menu.SeparatorType {
|
||||
result := C.gtk_separator_menu_item_new()
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
|
||||
return
|
||||
}
|
||||
|
||||
var result *C.GtkWidget
|
||||
|
||||
switch menuItem.Type {
|
||||
case menu.TextType:
|
||||
result = GtkMenuItemWithLabel(menuItem.Label)
|
||||
case menu.CheckboxType:
|
||||
result = GtkCheckMenuItemWithLabel(menuItem.Label)
|
||||
if menuItem.Checked {
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
|
||||
}
|
||||
gtkCheckboxCache[menuItem] = append(gtkCheckboxCache[menuItem], result)
|
||||
|
||||
case menu.RadioType:
|
||||
result = GtkRadioMenuItemWithLabel(menuItem.Label, currentRadioGroup)
|
||||
currentRadioGroup = C.gtk_radio_menu_item_get_group(C.toGtkRadioMenuItem(unsafe.Pointer(result)))
|
||||
if menuItem.Checked {
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
|
||||
}
|
||||
gtkRadioMenuCache[menuItem] = append(gtkRadioMenuCache[menuItem], result)
|
||||
case menu.SubmenuType:
|
||||
result = processSubmenu(menuItem, group)
|
||||
}
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
|
||||
C.gtk_widget_show(result)
|
||||
|
||||
if menuItem.Click != nil {
|
||||
handler := C.connectClick(result)
|
||||
gtkSignalHandlers[result] = handler
|
||||
gtkSignalToMenuItem[result] = menuItem
|
||||
}
|
||||
|
||||
if menuItem.Disabled {
|
||||
C.gtk_widget_set_sensitive(result, 0)
|
||||
}
|
||||
|
||||
if menuItem.Accelerator != nil {
|
||||
key, mods := acceleratorToGTK(menuItem.Accelerator)
|
||||
C.addAccelerator(result, group, key, mods)
|
||||
}
|
||||
}
|
||||
594
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/notifications.go
generated
vendored
Normal file
594
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/notifications.go
generated
vendored
Normal file
@@ -0,0 +1,594 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
var (
|
||||
conn *dbus.Conn
|
||||
categories map[string]frontend.NotificationCategory = make(map[string]frontend.NotificationCategory)
|
||||
categoriesLock sync.RWMutex
|
||||
notifications map[uint32]*notificationData = make(map[uint32]*notificationData)
|
||||
notificationsLock sync.RWMutex
|
||||
notificationResultCallback func(result frontend.NotificationResult)
|
||||
callbackLock sync.RWMutex
|
||||
appName string
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
|
||||
type notificationData struct {
|
||||
ID string
|
||||
Title string
|
||||
Subtitle string
|
||||
Body string
|
||||
CategoryID string
|
||||
Data map[string]interface{}
|
||||
DBusID uint32
|
||||
ActionMap map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
dbusNotificationInterface = "org.freedesktop.Notifications"
|
||||
dbusNotificationPath = "/org/freedesktop/Notifications"
|
||||
DefaultActionIdentifier = "DEFAULT_ACTION"
|
||||
)
|
||||
|
||||
// Creates a new Notifications Service.
|
||||
func (f *Frontend) InitializeNotifications() error {
|
||||
// Clean up any previous initialization
|
||||
f.CleanupNotifications()
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable: %w", err)
|
||||
}
|
||||
appName = filepath.Base(exe)
|
||||
|
||||
_conn, err := dbus.ConnectSessionBus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to session bus: %w", err)
|
||||
}
|
||||
conn = _conn
|
||||
|
||||
if err := f.loadCategories(); err != nil {
|
||||
f.logger.Warning("Failed to load notification categories: %v", err)
|
||||
}
|
||||
|
||||
var signalCtx context.Context
|
||||
signalCtx, cancel = context.WithCancel(context.Background())
|
||||
|
||||
if err := f.setupSignalHandling(signalCtx); err != nil {
|
||||
return fmt.Errorf("failed to set up notification signal handling: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupNotifications cleans up notification resources
|
||||
func (f *Frontend) CleanupNotifications() {
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
cancel = nil
|
||||
}
|
||||
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) IsNotificationAvailable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RequestNotificationAuthorization is a Linux stub that always returns true, nil.
|
||||
// (authorization is macOS-specific)
|
||||
func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckNotificationAuthorization is a Linux stub that always returns true.
|
||||
// (authorization is macOS-specific)
|
||||
func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("notifications not initialized")
|
||||
}
|
||||
|
||||
hints := map[string]dbus.Variant{}
|
||||
|
||||
body := options.Body
|
||||
if options.Subtitle != "" {
|
||||
body = options.Subtitle + "\n" + body
|
||||
}
|
||||
|
||||
defaultActionID := "default"
|
||||
actions := []string{defaultActionID, "Default"}
|
||||
|
||||
actionMap := map[string]string{
|
||||
defaultActionID: DefaultActionIdentifier,
|
||||
}
|
||||
|
||||
hints["x-notification-id"] = dbus.MakeVariant(options.ID)
|
||||
|
||||
if options.Data != nil {
|
||||
userData, err := json.Marshal(options.Data)
|
||||
if err == nil {
|
||||
hints["x-user-data"] = dbus.MakeVariant(string(userData))
|
||||
}
|
||||
}
|
||||
|
||||
// Call the Notify method on the D-Bus interface
|
||||
obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
|
||||
call := obj.Call(
|
||||
dbusNotificationInterface+".Notify",
|
||||
0,
|
||||
appName,
|
||||
uint32(0),
|
||||
"", // Icon
|
||||
options.Title,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
int32(-1),
|
||||
)
|
||||
|
||||
if call.Err != nil {
|
||||
return fmt.Errorf("failed to send notification: %w", call.Err)
|
||||
}
|
||||
|
||||
var dbusID uint32
|
||||
if err := call.Store(&dbusID); err != nil {
|
||||
return fmt.Errorf("failed to store notification ID: %w", err)
|
||||
}
|
||||
|
||||
notification := ¬ificationData{
|
||||
ID: options.ID,
|
||||
Title: options.Title,
|
||||
Subtitle: options.Subtitle,
|
||||
Body: options.Body,
|
||||
Data: options.Data,
|
||||
DBusID: dbusID,
|
||||
ActionMap: actionMap,
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notifications[dbusID] = notification
|
||||
notificationsLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions.
|
||||
func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("notifications not initialized")
|
||||
}
|
||||
|
||||
categoriesLock.RLock()
|
||||
category, exists := categories[options.CategoryID]
|
||||
categoriesLock.RUnlock()
|
||||
|
||||
if options.CategoryID == "" || !exists {
|
||||
// Fall back to basic notification
|
||||
return f.SendNotification(options)
|
||||
}
|
||||
|
||||
body := options.Body
|
||||
if options.Subtitle != "" {
|
||||
body = options.Subtitle + "\n" + body
|
||||
}
|
||||
|
||||
var actions []string
|
||||
actionMap := make(map[string]string)
|
||||
|
||||
defaultActionID := "default"
|
||||
actions = append(actions, defaultActionID, "Default")
|
||||
actionMap[defaultActionID] = DefaultActionIdentifier
|
||||
|
||||
for _, action := range category.Actions {
|
||||
actions = append(actions, action.ID, action.Title)
|
||||
actionMap[action.ID] = action.ID
|
||||
}
|
||||
|
||||
hints := map[string]dbus.Variant{}
|
||||
|
||||
hints["x-notification-id"] = dbus.MakeVariant(options.ID)
|
||||
|
||||
hints["x-category-id"] = dbus.MakeVariant(options.CategoryID)
|
||||
|
||||
if options.Data != nil {
|
||||
userData, err := json.Marshal(options.Data)
|
||||
if err == nil {
|
||||
hints["x-user-data"] = dbus.MakeVariant(string(userData))
|
||||
}
|
||||
}
|
||||
|
||||
obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
|
||||
call := obj.Call(
|
||||
dbusNotificationInterface+".Notify",
|
||||
0,
|
||||
appName,
|
||||
uint32(0),
|
||||
"", // Icon
|
||||
options.Title,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
int32(-1),
|
||||
)
|
||||
|
||||
if call.Err != nil {
|
||||
return fmt.Errorf("failed to send notification: %w", call.Err)
|
||||
}
|
||||
|
||||
var dbusID uint32
|
||||
if err := call.Store(&dbusID); err != nil {
|
||||
return fmt.Errorf("failed to store notification ID: %w", err)
|
||||
}
|
||||
|
||||
notification := ¬ificationData{
|
||||
ID: options.ID,
|
||||
Title: options.Title,
|
||||
Subtitle: options.Subtitle,
|
||||
Body: options.Body,
|
||||
CategoryID: options.CategoryID,
|
||||
Data: options.Data,
|
||||
DBusID: dbusID,
|
||||
ActionMap: actionMap,
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notifications[dbusID] = notification
|
||||
notificationsLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
|
||||
categoriesLock.Lock()
|
||||
categories[category.ID] = category
|
||||
categoriesLock.Unlock()
|
||||
|
||||
if err := f.saveCategories(); err != nil {
|
||||
f.logger.Warning("Failed to save notification categories: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
||||
func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
|
||||
categoriesLock.Lock()
|
||||
delete(categories, categoryId)
|
||||
categoriesLock.Unlock()
|
||||
|
||||
if err := f.saveCategories(); err != nil {
|
||||
f.logger.Warning("Failed to save notification categories: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications attempts to remove all active notifications.
|
||||
func (f *Frontend) RemoveAllPendingNotifications() error {
|
||||
notificationsLock.Lock()
|
||||
dbusIDs := make([]uint32, 0, len(notifications))
|
||||
for id := range notifications {
|
||||
dbusIDs = append(dbusIDs, id)
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
for _, id := range dbusIDs {
|
||||
f.closeNotification(id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification removes a pending notification.
|
||||
func (f *Frontend) RemovePendingNotification(identifier string) error {
|
||||
var dbusID uint32
|
||||
found := false
|
||||
|
||||
notificationsLock.Lock()
|
||||
for id, notif := range notifications {
|
||||
if notif.ID == identifier {
|
||||
dbusID = id
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f.closeNotification(dbusID)
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux.
|
||||
func (f *Frontend) RemoveAllDeliveredNotifications() error {
|
||||
return f.RemoveAllPendingNotifications()
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux.
|
||||
func (f *Frontend) RemoveDeliveredNotification(identifier string) error {
|
||||
return f.RemovePendingNotification(identifier)
|
||||
}
|
||||
|
||||
// RemoveNotification removes a notification by identifier.
|
||||
func (f *Frontend) RemoveNotification(identifier string) error {
|
||||
return f.RemovePendingNotification(identifier)
|
||||
}
|
||||
|
||||
func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
|
||||
callbackLock.Lock()
|
||||
defer callbackLock.Unlock()
|
||||
|
||||
notificationResultCallback = callback
|
||||
}
|
||||
|
||||
// Helper method to close a notification.
|
||||
func (f *Frontend) closeNotification(id uint32) error {
|
||||
if conn == nil {
|
||||
return fmt.Errorf("notifications not initialized")
|
||||
}
|
||||
|
||||
obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
|
||||
call := obj.Call(dbusNotificationInterface+".CloseNotification", 0, id)
|
||||
|
||||
if call.Err != nil {
|
||||
return fmt.Errorf("failed to close notification: %w", call.Err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) getConfigDir() (string, error) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get user config directory: %w", err)
|
||||
}
|
||||
|
||||
appConfigDir := filepath.Join(configDir, appName)
|
||||
if err := os.MkdirAll(appConfigDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create app config directory: %w", err)
|
||||
}
|
||||
|
||||
return appConfigDir, nil
|
||||
}
|
||||
|
||||
// Save notification categories.
|
||||
func (f *Frontend) saveCategories() error {
|
||||
configDir, err := f.getConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
categoriesFile := filepath.Join(configDir, "notification-categories.json")
|
||||
|
||||
categoriesLock.RLock()
|
||||
categoriesData, err := json.MarshalIndent(categories, "", " ")
|
||||
categoriesLock.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification categories: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(categoriesFile, categoriesData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write notification categories to disk: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load notification categories.
|
||||
func (f *Frontend) loadCategories() error {
|
||||
configDir, err := f.getConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
categoriesFile := filepath.Join(configDir, "notification-categories.json")
|
||||
|
||||
if _, err := os.Stat(categoriesFile); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
categoriesData, err := os.ReadFile(categoriesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read notification categories from disk: %w", err)
|
||||
}
|
||||
|
||||
_categories := make(map[string]frontend.NotificationCategory)
|
||||
if err := json.Unmarshal(categoriesData, &_categories); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal notification categories: %w", err)
|
||||
}
|
||||
|
||||
categoriesLock.Lock()
|
||||
categories = _categories
|
||||
categoriesLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup signal handling for notification actions.
|
||||
func (f *Frontend) setupSignalHandling(ctx context.Context) error {
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusNotificationInterface),
|
||||
dbus.WithMatchMember("ActionInvoked"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusNotificationInterface),
|
||||
dbus.WithMatchMember("NotificationClosed"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := make(chan *dbus.Signal, 10)
|
||||
conn.Signal(c)
|
||||
|
||||
go f.handleSignals(ctx, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle incoming D-Bus signals.
|
||||
func (f *Frontend) handleSignals(ctx context.Context, c chan *dbus.Signal) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case signal, ok := <-c:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch signal.Name {
|
||||
case dbusNotificationInterface + ".ActionInvoked":
|
||||
f.handleActionInvoked(signal)
|
||||
case dbusNotificationInterface + ".NotificationClosed":
|
||||
f.handleNotificationClosed(signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ActionInvoked signal.
|
||||
func (f *Frontend) handleActionInvoked(signal *dbus.Signal) {
|
||||
if len(signal.Body) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
dbusID, ok := signal.Body[0].(uint32)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
actionID, ok := signal.Body[1].(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notification, exists := notifications[dbusID]
|
||||
if exists {
|
||||
delete(notifications, dbusID)
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
appActionID, ok := notification.ActionMap[actionID]
|
||||
if !ok {
|
||||
appActionID = actionID
|
||||
}
|
||||
|
||||
response := frontend.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
ActionIdentifier: appActionID,
|
||||
Title: notification.Title,
|
||||
Subtitle: notification.Subtitle,
|
||||
Body: notification.Body,
|
||||
CategoryID: notification.CategoryID,
|
||||
UserInfo: notification.Data,
|
||||
}
|
||||
|
||||
result := frontend.NotificationResult{
|
||||
Response: response,
|
||||
}
|
||||
|
||||
handleNotificationResult(result)
|
||||
}
|
||||
|
||||
func handleNotificationResult(result frontend.NotificationResult) {
|
||||
callbackLock.Lock()
|
||||
callback := notificationResultCallback
|
||||
callbackLock.Unlock()
|
||||
|
||||
if callback != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log panic but don't crash the app
|
||||
fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
|
||||
}
|
||||
}()
|
||||
callback(result)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle NotificationClosed signal.
|
||||
// Reason codes:
|
||||
// 1 - expired timeout
|
||||
// 2 - dismissed by user (click on X)
|
||||
// 3 - closed by CloseNotification call
|
||||
// 4 - undefined/reserved
|
||||
func (f *Frontend) handleNotificationClosed(signal *dbus.Signal) {
|
||||
if len(signal.Body) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
dbusID, ok := signal.Body[0].(uint32)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
reason, ok := signal.Body[1].(uint32)
|
||||
if !ok {
|
||||
reason = 0 // Unknown reason
|
||||
}
|
||||
|
||||
notificationsLock.Lock()
|
||||
notification, exists := notifications[dbusID]
|
||||
if exists {
|
||||
delete(notifications, dbusID)
|
||||
}
|
||||
notificationsLock.Unlock()
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
if reason == 2 {
|
||||
response := frontend.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
ActionIdentifier: DefaultActionIdentifier,
|
||||
Title: notification.Title,
|
||||
Subtitle: notification.Subtitle,
|
||||
Body: notification.Body,
|
||||
CategoryID: notification.CategoryID,
|
||||
UserInfo: notification.Data,
|
||||
}
|
||||
|
||||
result := frontend.NotificationResult{
|
||||
Response: response,
|
||||
}
|
||||
|
||||
handleNotificationResult(result)
|
||||
}
|
||||
}
|
||||
91
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/screen.go
generated
vendored
Normal file
91
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/screen.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#cgo CFLAGS: -w
|
||||
#include <stdio.h>
|
||||
#include "webkit2/webkit2.h"
|
||||
#include "gtk/gtk.h"
|
||||
#include "gdk/gdk.h"
|
||||
|
||||
typedef struct Screen {
|
||||
int isCurrent;
|
||||
int isPrimary;
|
||||
int height;
|
||||
int width;
|
||||
int scale;
|
||||
} Screen;
|
||||
|
||||
int GetNMonitors(GtkWindow *window){
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
GdkDisplay *display = gdk_window_get_display(gdk_window);
|
||||
return gdk_display_get_n_monitors(display);
|
||||
}
|
||||
|
||||
Screen GetNThMonitor(int monitor_num, GtkWindow *window){
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
GdkDisplay *display = gdk_window_get_display(gdk_window);
|
||||
GdkMonitor *monitor = gdk_display_get_monitor(display,monitor_num);
|
||||
GdkMonitor *currentMonitor = gdk_display_get_monitor_at_window(display,gdk_window);
|
||||
Screen screen;
|
||||
GdkRectangle geometry;
|
||||
gdk_monitor_get_geometry(monitor,&geometry);
|
||||
screen.isCurrent = currentMonitor==monitor;
|
||||
screen.isPrimary = gdk_monitor_is_primary(monitor);
|
||||
screen.height = geometry.height;
|
||||
screen.width = geometry.width;
|
||||
screen.scale = gdk_monitor_get_scale_factor(monitor);
|
||||
return screen;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
|
||||
type Screen = frontend.Screen
|
||||
|
||||
func GetAllScreens(window *C.GtkWindow) ([]Screen, error) {
|
||||
if window == nil {
|
||||
return nil, errors.New("window is nil, cannot perform screen operations")
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
var screens []Screen
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
numMonitors := C.GetNMonitors(window)
|
||||
for i := 0; i < int(numMonitors); i++ {
|
||||
cMonitor := C.GetNThMonitor(C.int(i), window)
|
||||
|
||||
screen := Screen{
|
||||
IsCurrent: cMonitor.isCurrent == 1,
|
||||
IsPrimary: cMonitor.isPrimary == 1,
|
||||
Width: int(cMonitor.width),
|
||||
Height: int(cMonitor.height),
|
||||
|
||||
Size: frontend.ScreenSize{
|
||||
Width: int(cMonitor.width),
|
||||
Height: int(cMonitor.height),
|
||||
},
|
||||
PhysicalSize: frontend.ScreenSize{
|
||||
Width: int(cMonitor.width * cMonitor.scale),
|
||||
Height: int(cMonitor.height * cMonitor.scale),
|
||||
},
|
||||
}
|
||||
screens = append(screens, screen)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return screens, nil
|
||||
}
|
||||
77
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/single_instance.go
generated
vendored
Normal file
77
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/single_instance.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dbusHandler func(string)
|
||||
|
||||
func (f dbusHandler) SendMessage(message string) *dbus.Error {
|
||||
f(message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupSingleInstance(uniqueID string) {
|
||||
id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_")
|
||||
|
||||
dbusName := "org." + id + ".SingleInstance"
|
||||
dbusPath := "/org/" + id + "/SingleInstance"
|
||||
|
||||
conn, err := dbus.ConnectSessionBus()
|
||||
// if we will reach any error during establishing connection or sending message we will just continue.
|
||||
// It should not be the case that such thing will happen actually, but just in case.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f := dbusHandler(func(message string) {
|
||||
var secondInstanceData options.SecondInstanceData
|
||||
|
||||
err := json.Unmarshal([]byte(message), &secondInstanceData)
|
||||
if err == nil {
|
||||
secondInstanceBuffer <- secondInstanceData
|
||||
}
|
||||
})
|
||||
|
||||
err = conn.Export(f, dbus.ObjectPath(dbusPath), dbusName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reply, err := conn.RequestName(dbusName, dbus.NameFlagDoNotQueue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if name already taken, try to send args to existing instance, if no success just launch new instance
|
||||
if reply == dbus.RequestNameReplyExists {
|
||||
data := options.SecondInstanceData{
|
||||
Args: os.Args[1:],
|
||||
}
|
||||
data.WorkingDirectory, err = os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get working directory: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to marshal data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = conn.Object(dbusName, dbus.ObjectPath(dbusPath)).Call(dbusName+".SendMessage", 0, string(serialized)).Store()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
32
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/webkit2.go
generated
vendored
Normal file
32
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/webkit2.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
#include "webkit2/webkit2.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
)
|
||||
|
||||
func validateWebKit2Version(options *options.App) {
|
||||
if C.webkit_get_major_version() == 2 && C.webkit_get_minor_version() >= webview.Webkit2MinMinorVersion {
|
||||
return
|
||||
}
|
||||
|
||||
msg := linux.DefaultMessages()
|
||||
if options.Linux != nil && options.Linux.Messages != nil {
|
||||
msg = options.Linux.Messages
|
||||
}
|
||||
|
||||
v := fmt.Sprintf("2.%d.0", webview.Webkit2MinMinorVersion)
|
||||
showModalDialogAndExit("WebKit2GTK", fmt.Sprintf(msg.WebKit2GTKMinRequired, v))
|
||||
}
|
||||
891
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.c
generated
vendored
Normal file
891
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.c
generated
vendored
Normal file
@@ -0,0 +1,891 @@
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
#include "window.h"
|
||||
|
||||
// These are the x,y,time & button of the last mouse down event
|
||||
// It's used for window dragging
|
||||
static float xroot = 0.0f;
|
||||
static float yroot = 0.0f;
|
||||
static int dragTime = -1;
|
||||
static uint mouseButton = 0;
|
||||
static int wmIsWayland = -1;
|
||||
static int decoratorWidth = -1;
|
||||
static int decoratorHeight = -1;
|
||||
|
||||
// casts
|
||||
void ExecuteOnMainThread(void *f, gpointer jscallback)
|
||||
{
|
||||
g_idle_add((GSourceFunc)f, (gpointer)jscallback);
|
||||
}
|
||||
|
||||
GtkWidget *GTKWIDGET(void *pointer)
|
||||
{
|
||||
return GTK_WIDGET(pointer);
|
||||
}
|
||||
|
||||
GtkWindow *GTKWINDOW(void *pointer)
|
||||
{
|
||||
return GTK_WINDOW(pointer);
|
||||
}
|
||||
|
||||
GtkContainer *GTKCONTAINER(void *pointer)
|
||||
{
|
||||
return GTK_CONTAINER(pointer);
|
||||
}
|
||||
|
||||
GtkBox *GTKBOX(void *pointer)
|
||||
{
|
||||
return GTK_BOX(pointer);
|
||||
}
|
||||
|
||||
extern void processMessage(char *);
|
||||
extern void processBindingMessage(char *, char *);
|
||||
|
||||
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
||||
WebKitJavascriptResult *result,
|
||||
void *data)
|
||||
{
|
||||
// Retrieve webview from content manager
|
||||
WebKitWebView *webview = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(contentManager), "webview"));
|
||||
const char *current_uri = webview ? webkit_web_view_get_uri(webview) : NULL;
|
||||
char *uri = current_uri ? g_strdup(current_uri) : NULL;
|
||||
|
||||
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
||||
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
||||
char *message = jsc_value_to_string(value);
|
||||
#else
|
||||
JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
|
||||
JSValueRef value = webkit_javascript_result_get_value(result);
|
||||
JSStringRef js = JSValueToStringCopy(context, value, NULL);
|
||||
size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
|
||||
char *message = g_new(char, messageSize);
|
||||
JSStringGetUTF8CString(js, message, messageSize);
|
||||
JSStringRelease(js);
|
||||
#endif
|
||||
processBindingMessage(message, uri);
|
||||
g_free(message);
|
||||
if (uri) {
|
||||
g_free(uri);
|
||||
}
|
||||
}
|
||||
|
||||
static bool isNULLRectangle(GdkRectangle input)
|
||||
{
|
||||
return input.x == -1 && input.y == -1 && input.width == -1 && input.height == -1;
|
||||
}
|
||||
|
||||
static gboolean onWayland()
|
||||
{
|
||||
switch (wmIsWayland)
|
||||
{
|
||||
case -1:
|
||||
{
|
||||
char *gdkBackend = getenv("XDG_SESSION_TYPE");
|
||||
if(gdkBackend != NULL && strcmp(gdkBackend, "wayland") == 0)
|
||||
{
|
||||
wmIsWayland = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wmIsWayland = 0;
|
||||
return FALSE;
|
||||
}
|
||||
case 1:
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static GdkMonitor *getCurrentMonitor(GtkWindow *window)
|
||||
{
|
||||
// Get the monitor that the window is currently on
|
||||
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
if (gdk_window == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
GdkMonitor *monitor = gdk_display_get_monitor_at_window(display, gdk_window);
|
||||
|
||||
return GDK_MONITOR(monitor);
|
||||
}
|
||||
|
||||
static GdkRectangle getCurrentMonitorGeometry(GtkWindow *window)
|
||||
{
|
||||
GdkMonitor *monitor = getCurrentMonitor(window);
|
||||
GdkRectangle result;
|
||||
if (monitor == NULL)
|
||||
{
|
||||
result.x = result.y = result.height = result.width = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the geometry of the monitor
|
||||
gdk_monitor_get_geometry(monitor, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getCurrentMonitorScaleFactor(GtkWindow *window)
|
||||
{
|
||||
GdkMonitor *monitor = getCurrentMonitor(window);
|
||||
|
||||
return gdk_monitor_get_scale_factor(monitor);
|
||||
}
|
||||
|
||||
// window
|
||||
|
||||
ulong SetupInvokeSignal(void *contentManager)
|
||||
{
|
||||
return g_signal_connect((WebKitUserContentManager *)contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), NULL);
|
||||
}
|
||||
|
||||
void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len)
|
||||
{
|
||||
GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
|
||||
if (!loader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL))
|
||||
{
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
|
||||
if (pixbuf)
|
||||
{
|
||||
gtk_window_set_icon(window, pixbuf);
|
||||
}
|
||||
}
|
||||
g_object_unref(loader);
|
||||
}
|
||||
|
||||
void SetWindowTransparency(GtkWidget *widget)
|
||||
{
|
||||
GdkScreen *screen = gtk_widget_get_screen(widget);
|
||||
GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
|
||||
|
||||
if (visual != NULL && gdk_screen_is_composited(screen))
|
||||
{
|
||||
gtk_widget_set_app_paintable(widget, true);
|
||||
gtk_widget_set_visual(widget, visual);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkCssProvider *windowCssProvider = NULL;
|
||||
|
||||
void SetBackgroundColour(void *data)
|
||||
{
|
||||
// set webview's background color
|
||||
RGBAOptions *options = (RGBAOptions *)data;
|
||||
|
||||
GdkRGBA colour = {options->r / 255.0, options->g / 255.0, options->b / 255.0, options->a / 255.0};
|
||||
if (options->windowIsTranslucent != NULL && options->windowIsTranslucent == TRUE)
|
||||
{
|
||||
colour.alpha = 0.0;
|
||||
}
|
||||
webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(options->webview), &colour);
|
||||
|
||||
// set window's background color
|
||||
// Get the name of the current locale
|
||||
char *old_locale, *saved_locale;
|
||||
old_locale = setlocale(LC_ALL, NULL);
|
||||
|
||||
// Copy the name so it won’t be clobbered by setlocale.
|
||||
saved_locale = strdup(old_locale);
|
||||
if (saved_locale == NULL)
|
||||
return;
|
||||
|
||||
//Now change the locale to english for so printf always converts floats with a dot decimal separator
|
||||
setlocale(LC_ALL, "en_US.UTF-8");
|
||||
gchar *str = g_strdup_printf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", options->r, options->g, options->b, options->a / 255.0);
|
||||
|
||||
//Restore the original locale.
|
||||
setlocale(LC_ALL, saved_locale);
|
||||
free(saved_locale);
|
||||
|
||||
if (windowCssProvider == NULL)
|
||||
{
|
||||
windowCssProvider = gtk_css_provider_new();
|
||||
gtk_style_context_add_provider(
|
||||
gtk_widget_get_style_context(GTK_WIDGET(options->webviewBox)),
|
||||
GTK_STYLE_PROVIDER(windowCssProvider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
g_object_unref(windowCssProvider);
|
||||
}
|
||||
|
||||
gtk_css_provider_load_from_data(windowCssProvider, str, -1, NULL);
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
static gboolean setTitle(gpointer data)
|
||||
{
|
||||
SetTitleArgs *args = (SetTitleArgs *)data;
|
||||
gtk_window_set_title(args->window, args->title);
|
||||
free((void *)args->title);
|
||||
free((void *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void SetTitle(GtkWindow *window, char *title)
|
||||
{
|
||||
SetTitleArgs *args = malloc(sizeof(SetTitleArgs));
|
||||
args->window = window;
|
||||
args->title = title;
|
||||
ExecuteOnMainThread(setTitle, (gpointer)args);
|
||||
}
|
||||
|
||||
static gboolean setPosition(gpointer data)
|
||||
{
|
||||
SetPositionArgs *args = (SetPositionArgs *)data;
|
||||
gtk_window_move((GtkWindow *)args->window, args->x, args->y);
|
||||
free(args);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void SetPosition(void *window, int x, int y)
|
||||
{
|
||||
GdkRectangle monitorDimensions = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(monitorDimensions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
SetPositionArgs *args = malloc(sizeof(SetPositionArgs));
|
||||
args->window = window;
|
||||
args->x = monitorDimensions.x + x;
|
||||
args->y = monitorDimensions.y + y;
|
||||
ExecuteOnMainThread(setPosition, (gpointer)args);
|
||||
}
|
||||
|
||||
void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height)
|
||||
{
|
||||
GdkGeometry size;
|
||||
size.min_width = size.min_height = size.max_width = size.max_height = 0;
|
||||
|
||||
GdkRectangle monitorSize = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(monitorSize))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int flags = GDK_HINT_MAX_SIZE | GDK_HINT_MIN_SIZE;
|
||||
|
||||
size.max_height = (max_height == 0 ? monitorSize.height : max_height);
|
||||
size.max_width = (max_width == 0 ? monitorSize.width : max_width);
|
||||
size.min_height = min_height;
|
||||
size.min_width = min_width;
|
||||
|
||||
// On Wayland window manager get the decorators and calculate the differences from the windows' size.
|
||||
if(onWayland())
|
||||
{
|
||||
if(decoratorWidth == -1 && decoratorHeight == -1)
|
||||
{
|
||||
int windowWidth, windowHeight;
|
||||
gtk_window_get_size(window, &windowWidth, &windowHeight);
|
||||
|
||||
GtkAllocation windowAllocation;
|
||||
gtk_widget_get_allocation(GTK_WIDGET(window), &windowAllocation);
|
||||
|
||||
decoratorWidth = (windowAllocation.width-windowWidth);
|
||||
decoratorHeight = (windowAllocation.height-windowHeight);
|
||||
}
|
||||
|
||||
// Add the decorator difference to the window so fullscreen and maximise can fill the window.
|
||||
size.max_height = decoratorHeight+size.max_height;
|
||||
size.max_width = decoratorWidth+size.max_width;
|
||||
}
|
||||
|
||||
gtk_window_set_geometry_hints(window, NULL, &size, flags);
|
||||
}
|
||||
|
||||
// function to disable the context menu but propagate the event
|
||||
static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data)
|
||||
{
|
||||
// return true to disable the context menu
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void DisableContextMenu(void *webview)
|
||||
{
|
||||
// Disable the context menu but propagate the event
|
||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
|
||||
}
|
||||
|
||||
static gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void *dummy)
|
||||
{
|
||||
if (event == NULL)
|
||||
{
|
||||
xroot = yroot = 0.0f;
|
||||
dragTime = -1;
|
||||
return FALSE;
|
||||
}
|
||||
mouseButton = event->button;
|
||||
if (event->button == 3)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == 1)
|
||||
{
|
||||
xroot = event->x_root;
|
||||
yroot = event->y_root;
|
||||
dragTime = event->time;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, void *dummy)
|
||||
{
|
||||
if (event == NULL || (event->type == GDK_BUTTON_RELEASE && event->button == 1))
|
||||
{
|
||||
xroot = yroot = 0.0f;
|
||||
dragTime = -1;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void ConnectButtons(void *webview)
|
||||
{
|
||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-press-event", G_CALLBACK(buttonPress), NULL);
|
||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-release-event", G_CALLBACK(buttonRelease), NULL);
|
||||
}
|
||||
|
||||
int IsFullscreen(GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
||||
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
||||
return state & GDK_WINDOW_STATE_FULLSCREEN;
|
||||
}
|
||||
|
||||
int IsMaximised(GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
||||
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
||||
return state & GDK_WINDOW_STATE_MAXIMIZED && !(state & GDK_WINDOW_STATE_FULLSCREEN);
|
||||
}
|
||||
|
||||
int IsMinimised(GtkWidget *widget)
|
||||
{
|
||||
GdkWindow *gdkwindow = gtk_widget_get_window(widget);
|
||||
GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
|
||||
return state & GDK_WINDOW_STATE_ICONIFIED;
|
||||
}
|
||||
|
||||
gboolean Center(gpointer data)
|
||||
{
|
||||
GtkWindow *window = (GtkWindow *)data;
|
||||
|
||||
// Get the geometry of the monitor
|
||||
GdkRectangle m = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(m))
|
||||
{
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// Get the window width/height
|
||||
int windowWidth, windowHeight;
|
||||
gtk_window_get_size(window, &windowWidth, &windowHeight);
|
||||
|
||||
int newX = ((m.width - windowWidth) / 2) + m.x;
|
||||
int newY = ((m.height - windowHeight) / 2) + m.y;
|
||||
|
||||
// Place the window at the center of the monitor
|
||||
gtk_window_move(window, newX, newY);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Show(gpointer data)
|
||||
{
|
||||
gtk_widget_show((GtkWidget *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Hide(gpointer data)
|
||||
{
|
||||
gtk_widget_hide((GtkWidget *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Maximise(gpointer data)
|
||||
{
|
||||
gtk_window_maximize((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean UnMaximise(gpointer data)
|
||||
{
|
||||
gtk_window_unmaximize((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Minimise(gpointer data)
|
||||
{
|
||||
gtk_window_iconify((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean UnMinimise(gpointer data)
|
||||
{
|
||||
gtk_window_present((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean Fullscreen(gpointer data)
|
||||
{
|
||||
GtkWindow *window = (GtkWindow *)data;
|
||||
|
||||
// Get the geometry of the monitor.
|
||||
GdkRectangle m = getCurrentMonitorGeometry(window);
|
||||
if (isNULLRectangle(m))
|
||||
{
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
int scale = getCurrentMonitorScaleFactor(window);
|
||||
SetMinMaxSize(window, 0, 0, m.width * scale, m.height * scale);
|
||||
|
||||
gtk_window_fullscreen(window);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean UnFullscreen(gpointer data)
|
||||
{
|
||||
gtk_window_unfullscreen((GtkWindow *)data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void webviewLoadChanged(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer data)
|
||||
{
|
||||
if (load_event == WEBKIT_LOAD_FINISHED)
|
||||
{
|
||||
processMessage("DomReady");
|
||||
}
|
||||
}
|
||||
|
||||
extern void processURLRequest(void *request);
|
||||
|
||||
// This is called when the close button on the window is pressed
|
||||
gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void *data)
|
||||
{
|
||||
processMessage("Q");
|
||||
// since we handle the close in processMessage tell GTK to not invoke additional handlers - see:
|
||||
// https://docs.gtk.org/gtk3/signal.Widget.delete-event.html
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
char *droppedFiles = NULL;
|
||||
|
||||
static void onDragDataReceived(GtkWidget *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer data)
|
||||
{
|
||||
if(selection_data == NULL || (gtk_selection_data_get_length(selection_data) <= 0) || target_type != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(droppedFiles != NULL) {
|
||||
free(droppedFiles);
|
||||
droppedFiles = NULL;
|
||||
}
|
||||
|
||||
gchar **filenames = NULL;
|
||||
filenames = g_uri_list_extract_uris((const gchar *)gtk_selection_data_get_data(selection_data));
|
||||
if (filenames == NULL) // If unable to retrieve filenames:
|
||||
{
|
||||
g_strfreev(filenames);
|
||||
return;
|
||||
}
|
||||
|
||||
droppedFiles = calloc((size_t)gtk_selection_data_get_length(selection_data), 1);
|
||||
|
||||
int iter = 0;
|
||||
while(filenames[iter] != NULL) // The last URI list element is NULL.
|
||||
{
|
||||
if(iter != 0)
|
||||
{
|
||||
strncat(droppedFiles, "\n", 1);
|
||||
}
|
||||
char *filename = g_filename_from_uri(filenames[iter], NULL, NULL);
|
||||
if (filename == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
strncat(droppedFiles, filename, strlen(filename));
|
||||
|
||||
free(filename);
|
||||
iter++;
|
||||
}
|
||||
|
||||
g_strfreev(filenames);
|
||||
}
|
||||
|
||||
static gboolean onDragDrop(GtkWidget* self, GdkDragContext* context, gint x, gint y, guint time, gpointer user_data)
|
||||
{
|
||||
if(droppedFiles == NULL)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
size_t resLen = strlen(droppedFiles)+(sizeof(gint)*2)+6;
|
||||
char *res = calloc(resLen, 1);
|
||||
|
||||
snprintf(res, resLen, "DD:%d:%d:%s", x, y, droppedFiles);
|
||||
|
||||
if(droppedFiles != NULL) {
|
||||
free(droppedFiles);
|
||||
droppedFiles = NULL;
|
||||
}
|
||||
|
||||
processMessage(res);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// WebView
|
||||
GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop)
|
||||
{
|
||||
GtkWidget *webview = webkit_web_view_new_with_user_content_manager((WebKitUserContentManager *)contentManager);
|
||||
|
||||
// Store webview reference in the content manager
|
||||
g_object_set_data(G_OBJECT((WebKitUserContentManager *)contentManager), "webview", webview);
|
||||
// gtk_container_add(GTK_CONTAINER(window), webview);
|
||||
WebKitWebContext *context = webkit_web_context_get_default();
|
||||
webkit_web_context_register_uri_scheme(context, "wails", (WebKitURISchemeRequestCallback)processURLRequest, NULL, NULL);
|
||||
g_signal_connect(G_OBJECT(webview), "load-changed", G_CALLBACK(webviewLoadChanged), NULL);
|
||||
|
||||
if(disableWebViewDragAndDrop)
|
||||
{
|
||||
gtk_drag_dest_unset(webview);
|
||||
}
|
||||
|
||||
if(enableDragAndDrop)
|
||||
{
|
||||
g_signal_connect(G_OBJECT(webview), "drag-data-received", G_CALLBACK(onDragDataReceived), NULL);
|
||||
g_signal_connect(G_OBJECT(webview), "drag-drop", G_CALLBACK(onDragDrop), NULL);
|
||||
}
|
||||
|
||||
if (hideWindowOnClose)
|
||||
{
|
||||
g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(close_button_pressed), NULL);
|
||||
}
|
||||
|
||||
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
||||
webkit_settings_set_user_agent_with_application_details(settings, "wails.io", "");
|
||||
|
||||
switch (gpuPolicy)
|
||||
{
|
||||
case 0:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
|
||||
break;
|
||||
case 1:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
|
||||
break;
|
||||
case 2:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
|
||||
break;
|
||||
default:
|
||||
webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
void DevtoolsEnabled(void *webview, int enabled, bool showInspector)
|
||||
{
|
||||
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
||||
gboolean genabled = enabled == 1 ? true : false;
|
||||
webkit_settings_set_enable_developer_extras(settings, genabled);
|
||||
|
||||
if (genabled && showInspector)
|
||||
{
|
||||
ShowInspector(webview);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadIndex(void *webview, char *url)
|
||||
{
|
||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url);
|
||||
}
|
||||
|
||||
static gboolean startDrag(gpointer data)
|
||||
{
|
||||
DragOptions *options = (DragOptions *)data;
|
||||
|
||||
// Ignore non-toplevel widgets
|
||||
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
|
||||
if (!GTK_IS_WINDOW(window))
|
||||
{
|
||||
free(data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gtk_window_begin_move_drag(options->mainwindow, mouseButton, xroot, yroot, dragTime);
|
||||
free(data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void StartDrag(void *webview, GtkWindow *mainwindow)
|
||||
{
|
||||
DragOptions *data = malloc(sizeof(DragOptions));
|
||||
data->webview = webview;
|
||||
data->mainwindow = mainwindow;
|
||||
ExecuteOnMainThread(startDrag, (gpointer)data);
|
||||
}
|
||||
|
||||
static gboolean startResize(gpointer data)
|
||||
{
|
||||
ResizeOptions *options = (ResizeOptions *)data;
|
||||
|
||||
// Ignore non-toplevel widgets
|
||||
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
|
||||
if (!GTK_IS_WINDOW(window))
|
||||
{
|
||||
free(data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gtk_window_begin_resize_drag(options->mainwindow, options->edge, mouseButton, xroot, yroot, dragTime);
|
||||
free(data);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge)
|
||||
{
|
||||
ResizeOptions *data = malloc(sizeof(ResizeOptions));
|
||||
data->webview = webview;
|
||||
data->mainwindow = mainwindow;
|
||||
data->edge = edge;
|
||||
ExecuteOnMainThread(startResize, (gpointer)data);
|
||||
}
|
||||
|
||||
void ExecuteJS(void *data)
|
||||
{
|
||||
struct JSCallback *js = data;
|
||||
webkit_web_view_run_javascript(js->webview, js->script, NULL, NULL, NULL);
|
||||
free(js->script);
|
||||
}
|
||||
|
||||
void extern processMessageDialogResult(char *);
|
||||
|
||||
void MessageDialog(void *data)
|
||||
{
|
||||
GtkDialogFlags flags;
|
||||
GtkMessageType messageType;
|
||||
MessageDialogOptions *options = (MessageDialogOptions *)data;
|
||||
if (options->messageType == 0)
|
||||
{
|
||||
messageType = GTK_MESSAGE_INFO;
|
||||
flags = GTK_BUTTONS_OK;
|
||||
}
|
||||
else if (options->messageType == 1)
|
||||
{
|
||||
messageType = GTK_MESSAGE_ERROR;
|
||||
flags = GTK_BUTTONS_OK;
|
||||
}
|
||||
else if (options->messageType == 2)
|
||||
{
|
||||
messageType = GTK_MESSAGE_QUESTION;
|
||||
flags = GTK_BUTTONS_YES_NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageType = GTK_MESSAGE_WARNING;
|
||||
flags = GTK_BUTTONS_OK;
|
||||
}
|
||||
|
||||
GtkWidget *dialog;
|
||||
dialog = gtk_message_dialog_new(GTK_WINDOW(options->window),
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
messageType,
|
||||
flags,
|
||||
options->message, NULL);
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), options->title);
|
||||
GtkResponseType result = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
if (result == GTK_RESPONSE_YES)
|
||||
{
|
||||
processMessageDialogResult("Yes");
|
||||
}
|
||||
else if (result == GTK_RESPONSE_NO)
|
||||
{
|
||||
processMessageDialogResult("No");
|
||||
}
|
||||
else if (result == GTK_RESPONSE_OK)
|
||||
{
|
||||
processMessageDialogResult("OK");
|
||||
}
|
||||
else if (result == GTK_RESPONSE_CANCEL)
|
||||
{
|
||||
processMessageDialogResult("Cancel");
|
||||
}
|
||||
else
|
||||
{
|
||||
processMessageDialogResult("");
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
free(options->title);
|
||||
free(options->message);
|
||||
}
|
||||
|
||||
void extern processOpenFileResult(void *);
|
||||
|
||||
GtkFileFilter **AllocFileFilterArray(size_t ln)
|
||||
{
|
||||
return (GtkFileFilter **)malloc(ln * sizeof(GtkFileFilter *));
|
||||
}
|
||||
|
||||
void freeFileFilterArray(GtkFileFilter **filters)
|
||||
{
|
||||
free(filters);
|
||||
}
|
||||
|
||||
void Opendialog(void *data)
|
||||
{
|
||||
struct OpenFileDialogOptions *options = data;
|
||||
char *label = "_Open";
|
||||
if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
{
|
||||
label = "_Save";
|
||||
}
|
||||
GtkWidget *dlgWidget = gtk_file_chooser_dialog_new(options->title, options->window, options->action,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
label, GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
GtkFileChooser *fc = GTK_FILE_CHOOSER(dlgWidget);
|
||||
// filters
|
||||
if (options->filters != 0)
|
||||
{
|
||||
int index = 0;
|
||||
GtkFileFilter *thisFilter;
|
||||
while (options->filters[index] != NULL)
|
||||
{
|
||||
thisFilter = options->filters[index];
|
||||
gtk_file_chooser_add_filter(fc, thisFilter);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_file_chooser_set_local_only(fc, FALSE);
|
||||
|
||||
if (options->multipleFiles == 1)
|
||||
{
|
||||
gtk_file_chooser_set_select_multiple(fc, TRUE);
|
||||
}
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);
|
||||
if (options->createDirectories == 1)
|
||||
{
|
||||
gtk_file_chooser_set_create_folders(fc, TRUE);
|
||||
}
|
||||
if (options->showHiddenFiles == 1)
|
||||
{
|
||||
gtk_file_chooser_set_show_hidden(fc, TRUE);
|
||||
}
|
||||
|
||||
if (options->defaultDirectory != NULL)
|
||||
{
|
||||
gtk_file_chooser_set_current_folder(fc, options->defaultDirectory);
|
||||
free(options->defaultDirectory);
|
||||
}
|
||||
|
||||
if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
{
|
||||
if (options->defaultFilename != NULL)
|
||||
{
|
||||
gtk_file_chooser_set_current_name(fc, options->defaultFilename);
|
||||
free(options->defaultFilename);
|
||||
}
|
||||
}
|
||||
|
||||
gint response = gtk_dialog_run(GTK_DIALOG(dlgWidget));
|
||||
|
||||
// Max 1024 files to select
|
||||
char **result = calloc(1024, sizeof(char *));
|
||||
int resultIndex = 0;
|
||||
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
GSList *filenames = gtk_file_chooser_get_filenames(fc);
|
||||
GSList *iter = filenames;
|
||||
while (iter)
|
||||
{
|
||||
result[resultIndex++] = (char *)iter->data;
|
||||
iter = g_slist_next(iter);
|
||||
if (resultIndex == 1024)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
processOpenFileResult(result);
|
||||
iter = filenames;
|
||||
while (iter)
|
||||
{
|
||||
g_free(iter->data);
|
||||
iter = g_slist_next(iter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
processOpenFileResult(result);
|
||||
}
|
||||
free(result);
|
||||
|
||||
// Release filters
|
||||
if (options->filters != NULL)
|
||||
{
|
||||
int index = 0;
|
||||
GtkFileFilter *thisFilter;
|
||||
while (options->filters[index] != 0)
|
||||
{
|
||||
thisFilter = options->filters[index];
|
||||
g_object_unref(thisFilter);
|
||||
index++;
|
||||
}
|
||||
freeFileFilterArray(options->filters);
|
||||
}
|
||||
gtk_widget_destroy(dlgWidget);
|
||||
free(options->title);
|
||||
}
|
||||
|
||||
GtkFileFilter *newFileFilter()
|
||||
{
|
||||
GtkFileFilter *result = gtk_file_filter_new();
|
||||
g_object_ref(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ShowInspector(void *webview) {
|
||||
WebKitWebInspector *inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
|
||||
webkit_web_inspector_show(WEBKIT_WEB_INSPECTOR(inspector));
|
||||
}
|
||||
|
||||
void sendShowInspectorMessage() {
|
||||
processMessage("wails:showInspector");
|
||||
}
|
||||
|
||||
void InstallF12Hotkey(void *window)
|
||||
{
|
||||
// When the user presses Ctrl+Shift+F12, call ShowInspector
|
||||
GtkAccelGroup *accel_group = gtk_accel_group_new();
|
||||
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
|
||||
GClosure *closure = g_cclosure_new(G_CALLBACK(sendShowInspectorMessage), window, NULL);
|
||||
gtk_accel_group_connect(accel_group, GDK_KEY_F12, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, closure);
|
||||
}
|
||||
479
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.go
generated
vendored
Normal file
479
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.go
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include "window.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||
)
|
||||
|
||||
func gtkBool(input bool) C.gboolean {
|
||||
if input {
|
||||
return C.gboolean(1)
|
||||
}
|
||||
return C.gboolean(0)
|
||||
}
|
||||
|
||||
type Window struct {
|
||||
appoptions *options.App
|
||||
debug bool
|
||||
devtoolsEnabled bool
|
||||
gtkWindow unsafe.Pointer
|
||||
contentManager unsafe.Pointer
|
||||
webview unsafe.Pointer
|
||||
applicationMenu *menu.Menu
|
||||
menubar *C.GtkWidget
|
||||
webviewBox *C.GtkWidget
|
||||
vbox *C.GtkWidget
|
||||
accels *C.GtkAccelGroup
|
||||
minWidth, minHeight, maxWidth, maxHeight int
|
||||
}
|
||||
|
||||
func bool2Cint(value bool) C.int {
|
||||
if value {
|
||||
return C.int(1)
|
||||
}
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Window {
|
||||
validateWebKit2Version(appoptions)
|
||||
|
||||
result := &Window{
|
||||
appoptions: appoptions,
|
||||
debug: debug,
|
||||
devtoolsEnabled: devtoolsEnabled,
|
||||
minHeight: appoptions.MinHeight,
|
||||
minWidth: appoptions.MinWidth,
|
||||
maxHeight: appoptions.MaxHeight,
|
||||
maxWidth: appoptions.MaxWidth,
|
||||
}
|
||||
|
||||
gtkWindow := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL)
|
||||
C.g_object_ref_sink(C.gpointer(gtkWindow))
|
||||
result.gtkWindow = unsafe.Pointer(gtkWindow)
|
||||
|
||||
webviewName := C.CString("webview-box")
|
||||
defer C.free(unsafe.Pointer(webviewName))
|
||||
result.webviewBox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)
|
||||
C.gtk_widget_set_name(result.webviewBox, webviewName)
|
||||
|
||||
result.vbox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0)
|
||||
C.gtk_container_add(result.asGTKContainer(), result.vbox)
|
||||
|
||||
result.contentManager = unsafe.Pointer(C.webkit_user_content_manager_new())
|
||||
external := C.CString("external")
|
||||
defer C.free(unsafe.Pointer(external))
|
||||
C.webkit_user_content_manager_register_script_message_handler(result.cWebKitUserContentManager(), external)
|
||||
C.SetupInvokeSignal(result.contentManager)
|
||||
|
||||
var webviewGpuPolicy int
|
||||
if appoptions.Linux != nil {
|
||||
webviewGpuPolicy = int(appoptions.Linux.WebviewGpuPolicy)
|
||||
} else {
|
||||
// workaround for https://github.com/wailsapp/wails/issues/2977
|
||||
webviewGpuPolicy = int(linux.WebviewGpuPolicyNever)
|
||||
}
|
||||
|
||||
webview := C.SetupWebview(
|
||||
result.contentManager,
|
||||
result.asGTKWindow(),
|
||||
bool2Cint(appoptions.HideWindowOnClose),
|
||||
C.int(webviewGpuPolicy),
|
||||
bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.DisableWebViewDrop),
|
||||
bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.EnableFileDrop),
|
||||
)
|
||||
result.webview = unsafe.Pointer(webview)
|
||||
buttonPressedName := C.CString("button-press-event")
|
||||
defer C.free(unsafe.Pointer(buttonPressedName))
|
||||
C.ConnectButtons(unsafe.Pointer(webview))
|
||||
|
||||
if devtoolsEnabled {
|
||||
C.DevtoolsEnabled(unsafe.Pointer(webview), C.int(1), C.bool(debug && appoptions.Debug.OpenInspectorOnStartup))
|
||||
// Install Ctrl-Shift-F12 hotkey to call ShowInspector
|
||||
C.InstallF12Hotkey(unsafe.Pointer(gtkWindow))
|
||||
}
|
||||
|
||||
if !(debug || appoptions.EnableDefaultContextMenu) {
|
||||
C.DisableContextMenu(unsafe.Pointer(webview))
|
||||
}
|
||||
|
||||
// Set background colour
|
||||
RGBA := appoptions.BackgroundColour
|
||||
result.SetBackgroundColour(RGBA.R, RGBA.G, RGBA.B, RGBA.A)
|
||||
|
||||
// Setup window
|
||||
result.SetKeepAbove(appoptions.AlwaysOnTop)
|
||||
result.SetResizable(!appoptions.DisableResize)
|
||||
result.SetDefaultSize(appoptions.Width, appoptions.Height)
|
||||
result.SetDecorated(!appoptions.Frameless)
|
||||
result.SetTitle(appoptions.Title)
|
||||
result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
|
||||
result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
|
||||
if appoptions.Linux != nil {
|
||||
if appoptions.Linux.Icon != nil {
|
||||
result.SetWindowIcon(appoptions.Linux.Icon)
|
||||
}
|
||||
if appoptions.Linux.WindowIsTranslucent {
|
||||
C.SetWindowTransparency(gtkWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// Menu
|
||||
result.SetApplicationMenu(appoptions.Menu)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (w *Window) asGTKWidget() *C.GtkWidget {
|
||||
return C.GTKWIDGET(w.gtkWindow)
|
||||
}
|
||||
|
||||
func (w *Window) asGTKWindow() *C.GtkWindow {
|
||||
return C.GTKWINDOW(w.gtkWindow)
|
||||
}
|
||||
|
||||
func (w *Window) asGTKContainer() *C.GtkContainer {
|
||||
return C.GTKCONTAINER(w.gtkWindow)
|
||||
}
|
||||
|
||||
func (w *Window) cWebKitUserContentManager() *C.WebKitUserContentManager {
|
||||
return (*C.WebKitUserContentManager)(w.contentManager)
|
||||
}
|
||||
|
||||
func (w *Window) Fullscreen() {
|
||||
C.ExecuteOnMainThread(C.Fullscreen, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) UnFullscreen() {
|
||||
if !w.IsFullScreen() {
|
||||
return
|
||||
}
|
||||
C.ExecuteOnMainThread(C.UnFullscreen, C.gpointer(w.asGTKWindow()))
|
||||
w.SetMinSize(w.minWidth, w.minHeight)
|
||||
w.SetMaxSize(w.maxWidth, w.maxHeight)
|
||||
}
|
||||
|
||||
func (w *Window) Destroy() {
|
||||
C.gtk_widget_destroy(w.asGTKWidget())
|
||||
C.g_object_unref(C.gpointer(w.gtkWindow))
|
||||
}
|
||||
|
||||
func (w *Window) Close() {
|
||||
C.gtk_window_close(w.asGTKWindow())
|
||||
}
|
||||
|
||||
func (w *Window) Center() {
|
||||
C.ExecuteOnMainThread(C.Center, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) SetPosition(x int, y int) {
|
||||
invokeOnMainThread(func() {
|
||||
C.SetPosition(unsafe.Pointer(w.asGTKWindow()), C.int(x), C.int(y))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) Size() (int, int) {
|
||||
var width, height C.int
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
C.gtk_window_get_size(w.asGTKWindow(), &width, &height)
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return int(width), int(height)
|
||||
}
|
||||
|
||||
func (w *Window) GetPosition() (int, int) {
|
||||
var width, height C.int
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
invokeOnMainThread(func() {
|
||||
C.gtk_window_get_position(w.asGTKWindow(), &width, &height)
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return int(width), int(height)
|
||||
}
|
||||
|
||||
func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
|
||||
w.maxHeight = maxHeight
|
||||
w.maxWidth = maxWidth
|
||||
invokeOnMainThread(func() {
|
||||
C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) SetMinSize(minWidth int, minHeight int) {
|
||||
w.minHeight = minHeight
|
||||
w.minWidth = minWidth
|
||||
invokeOnMainThread(func() {
|
||||
C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) Show() {
|
||||
C.ExecuteOnMainThread(C.Show, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) Hide() {
|
||||
C.ExecuteOnMainThread(C.Hide, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) Maximise() {
|
||||
C.ExecuteOnMainThread(C.Maximise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) UnMaximise() {
|
||||
C.ExecuteOnMainThread(C.UnMaximise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) Minimise() {
|
||||
C.ExecuteOnMainThread(C.Minimise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) UnMinimise() {
|
||||
C.ExecuteOnMainThread(C.UnMinimise, C.gpointer(w.asGTKWindow()))
|
||||
}
|
||||
|
||||
func (w *Window) IsFullScreen() bool {
|
||||
result := C.IsFullscreen(w.asGTKWidget())
|
||||
if result != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Window) IsMaximised() bool {
|
||||
result := C.IsMaximised(w.asGTKWidget())
|
||||
return result > 0
|
||||
}
|
||||
|
||||
func (w *Window) IsMinimised() bool {
|
||||
result := C.IsMinimised(w.asGTKWidget())
|
||||
return result > 0
|
||||
}
|
||||
|
||||
func (w *Window) IsNormal() bool {
|
||||
return !w.IsMaximised() && !w.IsMinimised() && !w.IsFullScreen()
|
||||
}
|
||||
|
||||
func (w *Window) SetBackgroundColour(r uint8, g uint8, b uint8, a uint8) {
|
||||
windowIsTranslucent := false
|
||||
if w.appoptions.Linux != nil && w.appoptions.Linux.WindowIsTranslucent {
|
||||
windowIsTranslucent = true
|
||||
}
|
||||
data := C.RGBAOptions{
|
||||
r: C.uchar(r),
|
||||
g: C.uchar(g),
|
||||
b: C.uchar(b),
|
||||
a: C.uchar(a),
|
||||
webview: w.webview,
|
||||
webviewBox: unsafe.Pointer(w.webviewBox),
|
||||
windowIsTranslucent: gtkBool(windowIsTranslucent),
|
||||
}
|
||||
invokeOnMainThread(func() { C.SetBackgroundColour(unsafe.Pointer(&data)) })
|
||||
|
||||
}
|
||||
|
||||
func (w *Window) SetWindowIcon(icon []byte) {
|
||||
if len(icon) == 0 {
|
||||
return
|
||||
}
|
||||
C.SetWindowIcon(w.asGTKWindow(), (*C.guchar)(&icon[0]), (C.gsize)(len(icon)))
|
||||
}
|
||||
|
||||
func (w *Window) Run(url string) {
|
||||
if w.menubar != nil {
|
||||
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.menubar, 0, 0, 0)
|
||||
}
|
||||
|
||||
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.webviewBox)), C.GTKWIDGET(w.webview), 1, 1, 0)
|
||||
C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.webviewBox, 1, 1, 0)
|
||||
_url := C.CString(url)
|
||||
C.LoadIndex(w.webview, _url)
|
||||
defer C.free(unsafe.Pointer(_url))
|
||||
if w.appoptions.StartHidden {
|
||||
w.Hide()
|
||||
}
|
||||
C.gtk_widget_show_all(w.asGTKWidget())
|
||||
w.Center()
|
||||
switch w.appoptions.WindowStartState {
|
||||
case options.Fullscreen:
|
||||
w.Fullscreen()
|
||||
case options.Minimised:
|
||||
w.Minimise()
|
||||
case options.Maximised:
|
||||
w.Maximise()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) SetKeepAbove(top bool) {
|
||||
C.gtk_window_set_keep_above(w.asGTKWindow(), gtkBool(top))
|
||||
}
|
||||
|
||||
func (w *Window) SetResizable(resizable bool) {
|
||||
C.gtk_window_set_resizable(w.asGTKWindow(), gtkBool(resizable))
|
||||
}
|
||||
|
||||
func (w *Window) SetDefaultSize(width int, height int) {
|
||||
C.gtk_window_set_default_size(w.asGTKWindow(), C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetSize(width int, height int) {
|
||||
C.gtk_window_resize(w.asGTKWindow(), C.gint(width), C.gint(height))
|
||||
}
|
||||
|
||||
func (w *Window) SetDecorated(frameless bool) {
|
||||
C.gtk_window_set_decorated(w.asGTKWindow(), gtkBool(frameless))
|
||||
}
|
||||
|
||||
func (w *Window) SetTitle(title string) {
|
||||
C.SetTitle(w.asGTKWindow(), C.CString(title))
|
||||
}
|
||||
|
||||
func (w *Window) ExecJS(js string) {
|
||||
jscallback := C.JSCallback{
|
||||
webview: w.webview,
|
||||
script: C.CString(js),
|
||||
}
|
||||
invokeOnMainThread(func() { C.ExecuteJS(unsafe.Pointer(&jscallback)) })
|
||||
}
|
||||
|
||||
func (w *Window) StartDrag() {
|
||||
C.StartDrag(w.webview, w.asGTKWindow())
|
||||
}
|
||||
|
||||
func (w *Window) StartResize(edge uintptr) {
|
||||
C.StartResize(w.webview, w.asGTKWindow(), C.GdkWindowEdge(edge))
|
||||
}
|
||||
|
||||
func (w *Window) Quit() {
|
||||
C.gtk_main_quit()
|
||||
}
|
||||
|
||||
func (w *Window) OpenFileDialog(dialogOptions frontend.OpenDialogOptions, multipleFiles int, action C.GtkFileChooserAction) {
|
||||
|
||||
data := C.OpenFileDialogOptions{
|
||||
window: w.asGTKWindow(),
|
||||
title: C.CString(dialogOptions.Title),
|
||||
multipleFiles: C.int(multipleFiles),
|
||||
action: action,
|
||||
}
|
||||
|
||||
if len(dialogOptions.Filters) > 0 {
|
||||
// Create filter array
|
||||
mem := NewCalloc()
|
||||
arraySize := len(dialogOptions.Filters) + 1
|
||||
data.filters = C.AllocFileFilterArray((C.size_t)(arraySize))
|
||||
filters := unsafe.Slice((**C.struct__GtkFileFilter)(unsafe.Pointer(data.filters)), arraySize)
|
||||
for index, filter := range dialogOptions.Filters {
|
||||
thisFilter := C.gtk_file_filter_new()
|
||||
C.g_object_ref(C.gpointer(thisFilter))
|
||||
if filter.DisplayName != "" {
|
||||
cName := mem.String(filter.DisplayName)
|
||||
C.gtk_file_filter_set_name(thisFilter, cName)
|
||||
}
|
||||
if filter.Pattern != "" {
|
||||
for _, thisPattern := range strings.Split(filter.Pattern, ";") {
|
||||
cThisPattern := mem.String(thisPattern)
|
||||
C.gtk_file_filter_add_pattern(thisFilter, cThisPattern)
|
||||
}
|
||||
}
|
||||
// Add filter to array
|
||||
filters[index] = thisFilter
|
||||
}
|
||||
mem.Free()
|
||||
filters[arraySize-1] = nil
|
||||
}
|
||||
|
||||
if dialogOptions.CanCreateDirectories {
|
||||
data.createDirectories = C.int(1)
|
||||
}
|
||||
|
||||
if dialogOptions.ShowHiddenFiles {
|
||||
data.showHiddenFiles = C.int(1)
|
||||
}
|
||||
|
||||
if dialogOptions.DefaultFilename != "" {
|
||||
data.defaultFilename = C.CString(dialogOptions.DefaultFilename)
|
||||
}
|
||||
|
||||
if dialogOptions.DefaultDirectory != "" {
|
||||
data.defaultDirectory = C.CString(dialogOptions.DefaultDirectory)
|
||||
}
|
||||
|
||||
invokeOnMainThread(func() { C.Opendialog(unsafe.Pointer(&data)) })
|
||||
}
|
||||
|
||||
func (w *Window) MessageDialog(dialogOptions frontend.MessageDialogOptions) {
|
||||
|
||||
data := C.MessageDialogOptions{
|
||||
window: w.gtkWindow,
|
||||
title: C.CString(dialogOptions.Title),
|
||||
message: C.CString(dialogOptions.Message),
|
||||
}
|
||||
switch dialogOptions.Type {
|
||||
case frontend.InfoDialog:
|
||||
data.messageType = C.int(0)
|
||||
case frontend.ErrorDialog:
|
||||
data.messageType = C.int(1)
|
||||
case frontend.QuestionDialog:
|
||||
data.messageType = C.int(2)
|
||||
case frontend.WarningDialog:
|
||||
data.messageType = C.int(3)
|
||||
}
|
||||
invokeOnMainThread(func() { C.MessageDialog(unsafe.Pointer(&data)) })
|
||||
}
|
||||
|
||||
func (w *Window) ToggleMaximise() {
|
||||
if w.IsMaximised() {
|
||||
w.UnMaximise()
|
||||
} else {
|
||||
w.Maximise()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) ShowInspector() {
|
||||
invokeOnMainThread(func() { C.ShowInspector(w.webview) })
|
||||
}
|
||||
|
||||
// showModalDialogAndExit shows a modal dialog and exits the app.
|
||||
func showModalDialogAndExit(title, message string) {
|
||||
go func() {
|
||||
data := C.MessageDialogOptions{
|
||||
title: C.CString(title),
|
||||
message: C.CString(message),
|
||||
messageType: C.int(1),
|
||||
}
|
||||
|
||||
C.MessageDialog(unsafe.Pointer(&data))
|
||||
}()
|
||||
|
||||
<-messageDialogResult
|
||||
log.Fatal(message)
|
||||
}
|
||||
128
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.h
generated
vendored
Normal file
128
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.h
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef window_h
|
||||
#define window_h
|
||||
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct DragOptions
|
||||
{
|
||||
void *webview;
|
||||
GtkWindow *mainwindow;
|
||||
} DragOptions;
|
||||
|
||||
typedef struct ResizeOptions
|
||||
{
|
||||
void *webview;
|
||||
GtkWindow *mainwindow;
|
||||
GdkWindowEdge edge;
|
||||
} ResizeOptions;
|
||||
|
||||
typedef struct JSCallback
|
||||
{
|
||||
void *webview;
|
||||
char *script;
|
||||
} JSCallback;
|
||||
|
||||
typedef struct MessageDialogOptions
|
||||
{
|
||||
void *window;
|
||||
char *title;
|
||||
char *message;
|
||||
int messageType;
|
||||
} MessageDialogOptions;
|
||||
|
||||
typedef struct OpenFileDialogOptions
|
||||
{
|
||||
GtkWindow *window;
|
||||
char *title;
|
||||
char *defaultFilename;
|
||||
char *defaultDirectory;
|
||||
int createDirectories;
|
||||
int multipleFiles;
|
||||
int showHiddenFiles;
|
||||
GtkFileChooserAction action;
|
||||
GtkFileFilter **filters;
|
||||
} OpenFileDialogOptions;
|
||||
|
||||
typedef struct RGBAOptions
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
void *webview;
|
||||
void *webviewBox;
|
||||
gboolean windowIsTranslucent;
|
||||
} RGBAOptions;
|
||||
|
||||
typedef struct SetTitleArgs
|
||||
{
|
||||
GtkWindow *window;
|
||||
char *title;
|
||||
} SetTitleArgs;
|
||||
|
||||
typedef struct SetPositionArgs
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
void *window;
|
||||
} SetPositionArgs;
|
||||
|
||||
void ExecuteOnMainThread(void *f, gpointer jscallback);
|
||||
|
||||
GtkWidget *GTKWIDGET(void *pointer);
|
||||
GtkWindow *GTKWINDOW(void *pointer);
|
||||
GtkContainer *GTKCONTAINER(void *pointer);
|
||||
GtkBox *GTKBOX(void *pointer);
|
||||
|
||||
// window
|
||||
ulong SetupInvokeSignal(void *contentManager);
|
||||
|
||||
void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len);
|
||||
void SetWindowTransparency(GtkWidget *widget);
|
||||
void SetBackgroundColour(void *data);
|
||||
void SetTitle(GtkWindow *window, char *title);
|
||||
void SetPosition(void *window, int x, int y);
|
||||
void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height);
|
||||
void DisableContextMenu(void *webview);
|
||||
void ConnectButtons(void *webview);
|
||||
|
||||
int IsFullscreen(GtkWidget *widget);
|
||||
int IsMaximised(GtkWidget *widget);
|
||||
int IsMinimised(GtkWidget *widget);
|
||||
|
||||
gboolean Center(gpointer data);
|
||||
gboolean Show(gpointer data);
|
||||
gboolean Hide(gpointer data);
|
||||
gboolean Maximise(gpointer data);
|
||||
gboolean UnMaximise(gpointer data);
|
||||
gboolean Minimise(gpointer data);
|
||||
gboolean UnMinimise(gpointer data);
|
||||
gboolean Fullscreen(gpointer data);
|
||||
gboolean UnFullscreen(gpointer data);
|
||||
|
||||
// WebView
|
||||
GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop);
|
||||
void LoadIndex(void *webview, char *url);
|
||||
void DevtoolsEnabled(void *webview, int enabled, bool showInspector);
|
||||
void ExecuteJS(void *data);
|
||||
|
||||
// Drag
|
||||
void StartDrag(void *webview, GtkWindow *mainwindow);
|
||||
void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge);
|
||||
|
||||
// Dialog
|
||||
void MessageDialog(void *data);
|
||||
GtkFileFilter **AllocFileFilterArray(size_t ln);
|
||||
void Opendialog(void *data);
|
||||
|
||||
// Inspector
|
||||
void sendShowInspectorMessage();
|
||||
void ShowInspector(void *webview);
|
||||
void InstallF12Hotkey(void *window);
|
||||
|
||||
#endif /* window_h */
|
||||
43
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/browser.go
generated
vendored
Normal file
43
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/browser.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/utils"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var fallbackBrowserPaths = []string{
|
||||
`\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`,
|
||||
`\Program Files\Google\Chrome\Application\chrome.exe`,
|
||||
`\Program Files (x86)\Google\Chrome\Application\chrome.exe`,
|
||||
`\Program Files\Mozilla Firefox\firefox.exe`,
|
||||
}
|
||||
|
||||
// BrowserOpenURL Use the default browser to open the url
|
||||
func (f *Frontend) BrowserOpenURL(rawURL string) {
|
||||
url, err := utils.ValidateAndSanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Specific method implementation
|
||||
err = browser.OpenURL(url)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
for _, fallback := range fallbackBrowserPaths {
|
||||
if err := openBrowser(fallback, url); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
f.logger.Error("Unable to open default system browser")
|
||||
}
|
||||
|
||||
func openBrowser(path, url string) error {
|
||||
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(path), windows.StringToUTF16Ptr(url), nil, windows.SW_SHOWNORMAL)
|
||||
}
|
||||
16
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/clipboard.go
generated
vendored
Normal file
16
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
)
|
||||
|
||||
func (f *Frontend) ClipboardGetText() (string, error) {
|
||||
return win32.GetClipboardText()
|
||||
}
|
||||
|
||||
func (f *Frontend) ClipboardSetText(text string) error {
|
||||
return win32.SetClipboardText(text)
|
||||
}
|
||||
210
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/dialog.go
generated
vendored
Normal file
210
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/dialog.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (f *Frontend) getHandleForDialog() w32.HWND {
|
||||
if f.mainWindow.IsVisible() {
|
||||
return f.mainWindow.Handle()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getDefaultFolder(folder string) (string, error) {
|
||||
if folder == "" {
|
||||
return "", nil
|
||||
}
|
||||
return filepath.Abs(folder)
|
||||
}
|
||||
|
||||
// OpenDirectoryDialog prompts the user to select a directory
|
||||
func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "PickFolder",
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewSelectFolderDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
// OpenFileDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Folder: defaultFolder,
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Title: options.Title,
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewOpenFileDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
// OpenMultipleFilesDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "OpenMultipleFiles",
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewOpenMultipleFilesDialog(config)
|
||||
}, true)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return nil, err
|
||||
}
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
// SaveFileDialog prompts the user to select a file
|
||||
func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "SaveFile",
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
|
||||
if len(options.Filters) > 0 {
|
||||
config.DefaultExtension = strings.TrimPrefix(strings.Split(options.Filters[0].Pattern, ";")[0], "*")
|
||||
}
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewSaveFileDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
|
||||
return invokeSync(f.mainWindow, func() (any, error) {
|
||||
dlg, err := newDlg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err := dlg.Release()
|
||||
if err != nil {
|
||||
println("ERROR: Unable to release dialog:", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
dlg.SetParentWindowHandle(f.getHandleForDialog())
|
||||
if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
|
||||
return multi.ShowAndGetResults()
|
||||
}
|
||||
return dlg.ShowAndGetResult()
|
||||
})
|
||||
}
|
||||
|
||||
func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
|
||||
var flags uint32
|
||||
|
||||
switch options.Type {
|
||||
case frontend.InfoDialog:
|
||||
flags = windows.MB_OK | windows.MB_ICONINFORMATION
|
||||
case frontend.ErrorDialog:
|
||||
flags = windows.MB_ICONERROR | windows.MB_OK
|
||||
case frontend.QuestionDialog:
|
||||
flags = windows.MB_YESNO
|
||||
if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" {
|
||||
flags |= windows.MB_DEFBUTTON2
|
||||
}
|
||||
case frontend.WarningDialog:
|
||||
flags = windows.MB_OK | windows.MB_ICONWARNING
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// MessageDialog show a message dialog to the user
|
||||
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
|
||||
|
||||
title, err := syscall.UTF16PtrFromString(options.Title)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
message, err := syscall.UTF16PtrFromString(options.Message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
flags := calculateMessageDialogFlags(options)
|
||||
|
||||
button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL)
|
||||
// This maps MessageBox return values to strings
|
||||
responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
|
||||
result := "Error"
|
||||
if int(button) < len(responses) {
|
||||
result = responses[button]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter {
|
||||
var result []cfd.FileFilter
|
||||
for _, filter := range filters {
|
||||
result = append(result, cfd.FileFilter(filter))
|
||||
}
|
||||
return result
|
||||
}
|
||||
1004
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/frontend.go
generated
vendored
Normal file
1004
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/frontend.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
203
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/keys.go
generated
vendored
Normal file
203
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/keys.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ModifierMap = map[keys.Modifier]winc.Modifiers{
|
||||
keys.ShiftKey: winc.ModShift,
|
||||
keys.ControlKey: winc.ModControl,
|
||||
keys.OptionOrAltKey: winc.ModAlt,
|
||||
keys.CmdOrCtrlKey: winc.ModControl,
|
||||
}
|
||||
|
||||
func acceleratorToWincShortcut(accelerator *keys.Accelerator) winc.Shortcut {
|
||||
|
||||
if accelerator == nil {
|
||||
return winc.NoShortcut
|
||||
}
|
||||
inKey := strings.ToUpper(accelerator.Key)
|
||||
key, exists := keyMap[inKey]
|
||||
if !exists {
|
||||
return winc.NoShortcut
|
||||
}
|
||||
var modifiers winc.Modifiers
|
||||
if _, exists := shiftMap[inKey]; exists {
|
||||
modifiers = winc.ModShift
|
||||
}
|
||||
for _, mod := range accelerator.Modifiers {
|
||||
modifiers |= ModifierMap[mod]
|
||||
}
|
||||
return winc.Shortcut{
|
||||
Modifiers: modifiers,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
var shiftMap = map[string]struct{}{
|
||||
"~": {},
|
||||
")": {},
|
||||
"!": {},
|
||||
"@": {},
|
||||
"#": {},
|
||||
"$": {},
|
||||
"%": {},
|
||||
"^": {},
|
||||
"&": {},
|
||||
"*": {},
|
||||
"(": {},
|
||||
"_": {},
|
||||
"PLUS": {},
|
||||
"<": {},
|
||||
">": {},
|
||||
"?": {},
|
||||
":": {},
|
||||
`"`: {},
|
||||
"{": {},
|
||||
"}": {},
|
||||
"|": {},
|
||||
}
|
||||
|
||||
var keyMap = map[string]winc.Key{
|
||||
"0": winc.Key0,
|
||||
"1": winc.Key1,
|
||||
"2": winc.Key2,
|
||||
"3": winc.Key3,
|
||||
"4": winc.Key4,
|
||||
"5": winc.Key5,
|
||||
"6": winc.Key6,
|
||||
"7": winc.Key7,
|
||||
"8": winc.Key8,
|
||||
"9": winc.Key9,
|
||||
"A": winc.KeyA,
|
||||
"B": winc.KeyB,
|
||||
"C": winc.KeyC,
|
||||
"D": winc.KeyD,
|
||||
"E": winc.KeyE,
|
||||
"F": winc.KeyF,
|
||||
"G": winc.KeyG,
|
||||
"H": winc.KeyH,
|
||||
"I": winc.KeyI,
|
||||
"J": winc.KeyJ,
|
||||
"K": winc.KeyK,
|
||||
"L": winc.KeyL,
|
||||
"M": winc.KeyM,
|
||||
"N": winc.KeyN,
|
||||
"O": winc.KeyO,
|
||||
"P": winc.KeyP,
|
||||
"Q": winc.KeyQ,
|
||||
"R": winc.KeyR,
|
||||
"S": winc.KeyS,
|
||||
"T": winc.KeyT,
|
||||
"U": winc.KeyU,
|
||||
"V": winc.KeyV,
|
||||
"W": winc.KeyW,
|
||||
"X": winc.KeyX,
|
||||
"Y": winc.KeyY,
|
||||
"Z": winc.KeyZ,
|
||||
"F1": winc.KeyF1,
|
||||
"F2": winc.KeyF2,
|
||||
"F3": winc.KeyF3,
|
||||
"F4": winc.KeyF4,
|
||||
"F5": winc.KeyF5,
|
||||
"F6": winc.KeyF6,
|
||||
"F7": winc.KeyF7,
|
||||
"F8": winc.KeyF8,
|
||||
"F9": winc.KeyF9,
|
||||
"F10": winc.KeyF10,
|
||||
"F11": winc.KeyF11,
|
||||
"F12": winc.KeyF12,
|
||||
"F13": winc.KeyF13,
|
||||
"F14": winc.KeyF14,
|
||||
"F15": winc.KeyF15,
|
||||
"F16": winc.KeyF16,
|
||||
"F17": winc.KeyF17,
|
||||
"F18": winc.KeyF18,
|
||||
"F19": winc.KeyF19,
|
||||
"F20": winc.KeyF20,
|
||||
"F21": winc.KeyF21,
|
||||
"F22": winc.KeyF22,
|
||||
"F23": winc.KeyF23,
|
||||
"F24": winc.KeyF24,
|
||||
|
||||
"`": winc.KeyOEM3,
|
||||
",": winc.KeyOEMComma,
|
||||
".": winc.KeyOEMPeriod,
|
||||
"/": winc.KeyOEM2,
|
||||
";": winc.KeyOEM1,
|
||||
"'": winc.KeyOEM7,
|
||||
"[": winc.KeyOEM4,
|
||||
"]": winc.KeyOEM6,
|
||||
`\`: winc.KeyOEM5,
|
||||
|
||||
"~": winc.KeyOEM3, //
|
||||
")": winc.Key0,
|
||||
"!": winc.Key1,
|
||||
"@": winc.Key2,
|
||||
"#": winc.Key3,
|
||||
"$": winc.Key4,
|
||||
"%": winc.Key5,
|
||||
"^": winc.Key6,
|
||||
"&": winc.Key7,
|
||||
"*": winc.Key8,
|
||||
"(": winc.Key9,
|
||||
"_": winc.KeyOEMMinus,
|
||||
"PLUS": winc.KeyOEMPlus,
|
||||
"<": winc.KeyOEMComma,
|
||||
">": winc.KeyOEMPeriod,
|
||||
"?": winc.KeyOEM2,
|
||||
":": winc.KeyOEM1,
|
||||
`"`: winc.KeyOEM7,
|
||||
"{": winc.KeyOEM4,
|
||||
"}": winc.KeyOEM6,
|
||||
"|": winc.KeyOEM5,
|
||||
|
||||
"SPACE": winc.KeySpace,
|
||||
"TAB": winc.KeyTab,
|
||||
"CAPSLOCK": winc.KeyCapital,
|
||||
"NUMLOCK": winc.KeyNumlock,
|
||||
"SCROLLLOCK": winc.KeyScroll,
|
||||
"BACKSPACE": winc.KeyBack,
|
||||
"DELETE": winc.KeyDelete,
|
||||
"INSERT": winc.KeyInsert,
|
||||
"RETURN": winc.KeyReturn,
|
||||
"ENTER": winc.KeyReturn,
|
||||
"UP": winc.KeyUp,
|
||||
"DOWN": winc.KeyDown,
|
||||
"LEFT": winc.KeyLeft,
|
||||
"RIGHT": winc.KeyRight,
|
||||
"HOME": winc.KeyHome,
|
||||
"END": winc.KeyEnd,
|
||||
"PAGEUP": winc.KeyPrior,
|
||||
"PAGEDOWN": winc.KeyNext,
|
||||
"ESCAPE": winc.KeyEscape,
|
||||
"ESC": winc.KeyEscape,
|
||||
"VOLUMEUP": winc.KeyVolumeUp,
|
||||
"VOLUMEDOWN": winc.KeyVolumeDown,
|
||||
"VOLUMEMUTE": winc.KeyVolumeMute,
|
||||
"MEDIANEXTTRACK": winc.KeyMediaNextTrack,
|
||||
"MEDIAPREVIOUSTRACK": winc.KeyMediaPrevTrack,
|
||||
"MEDIASTOP": winc.KeyMediaStop,
|
||||
"MEDIAPLAYPAUSE": winc.KeyMediaPlayPause,
|
||||
"PRINTSCREEN": winc.KeyPrint,
|
||||
"NUM0": winc.KeyNumpad0,
|
||||
"NUM1": winc.KeyNumpad1,
|
||||
"NUM2": winc.KeyNumpad2,
|
||||
"NUM3": winc.KeyNumpad3,
|
||||
"NUM4": winc.KeyNumpad4,
|
||||
"NUM5": winc.KeyNumpad5,
|
||||
"NUM6": winc.KeyNumpad6,
|
||||
"NUM7": winc.KeyNumpad7,
|
||||
"NUM8": winc.KeyNumpad8,
|
||||
"NUM9": winc.KeyNumpad9,
|
||||
"nummult": winc.KeyMultiply,
|
||||
"numadd": winc.KeyAdd,
|
||||
"numsub": winc.KeySubtract,
|
||||
"numdec": winc.KeyDecimal,
|
||||
"numdiv": winc.KeyDivide,
|
||||
}
|
||||
132
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/menu.go
generated
vendored
Normal file
132
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/menu.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
var checkboxMap = map[*menu.MenuItem][]*winc.MenuItem{}
|
||||
var radioGroupMap = map[*menu.MenuItem][]*winc.MenuItem{}
|
||||
|
||||
func toggleCheckBox(menuItem *menu.MenuItem) {
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
for _, wincMenu := range checkboxMap[menuItem] {
|
||||
wincMenu.SetChecked(menuItem.Checked)
|
||||
}
|
||||
}
|
||||
|
||||
func addCheckBoxToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
|
||||
if checkboxMap[menuItem] == nil {
|
||||
checkboxMap[menuItem] = []*winc.MenuItem{}
|
||||
}
|
||||
checkboxMap[menuItem] = append(checkboxMap[menuItem], wincMenuItem)
|
||||
}
|
||||
|
||||
func toggleRadioItem(menuItem *menu.MenuItem) {
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
for _, wincMenu := range radioGroupMap[menuItem] {
|
||||
wincMenu.SetChecked(menuItem.Checked)
|
||||
}
|
||||
}
|
||||
|
||||
func addRadioItemToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
|
||||
if radioGroupMap[menuItem] == nil {
|
||||
radioGroupMap[menuItem] = []*winc.MenuItem{}
|
||||
}
|
||||
radioGroupMap[menuItem] = append(radioGroupMap[menuItem], wincMenuItem)
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(menu *menu.Menu) {
|
||||
w.applicationMenu = menu
|
||||
processMenu(w, menu)
|
||||
}
|
||||
|
||||
func processMenu(window *Window, menu *menu.Menu) {
|
||||
mainMenu := window.NewMenu()
|
||||
for _, menuItem := range menu.Items {
|
||||
submenu := mainMenu.AddSubMenu(menuItem.Label)
|
||||
if menuItem.SubMenu != nil {
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
processMenuItem(submenu, menuItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
mainMenu.Show()
|
||||
}
|
||||
|
||||
func processMenuItem(parent *winc.MenuItem, menuItem *menu.MenuItem) {
|
||||
if menuItem.Hidden {
|
||||
return
|
||||
}
|
||||
switch menuItem.Type {
|
||||
case menu.SeparatorType:
|
||||
parent.AddSeparator()
|
||||
case menu.TextType:
|
||||
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
|
||||
newItem := parent.AddItem(menuItem.Label, shortcut)
|
||||
//if menuItem.Tooltip != "" {
|
||||
// newItem.SetToolTip(menuItem.Tooltip)
|
||||
//}
|
||||
if menuItem.Click != nil {
|
||||
newItem.OnClick().Bind(func(e *winc.Event) {
|
||||
menuItem.Click(&menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
})
|
||||
})
|
||||
}
|
||||
newItem.SetEnabled(!menuItem.Disabled)
|
||||
|
||||
case menu.CheckboxType:
|
||||
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
|
||||
newItem := parent.AddItem(menuItem.Label, shortcut)
|
||||
newItem.SetCheckable(true)
|
||||
newItem.SetChecked(menuItem.Checked)
|
||||
//if menuItem.Tooltip != "" {
|
||||
// newItem.SetToolTip(menuItem.Tooltip)
|
||||
//}
|
||||
if menuItem.Click != nil {
|
||||
newItem.OnClick().Bind(func(e *winc.Event) {
|
||||
toggleCheckBox(menuItem)
|
||||
menuItem.Click(&menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
})
|
||||
})
|
||||
}
|
||||
newItem.SetEnabled(!menuItem.Disabled)
|
||||
addCheckBoxToMap(menuItem, newItem)
|
||||
case menu.RadioType:
|
||||
shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
|
||||
newItem := parent.AddItemRadio(menuItem.Label, shortcut)
|
||||
newItem.SetCheckable(true)
|
||||
newItem.SetChecked(menuItem.Checked)
|
||||
//if menuItem.Tooltip != "" {
|
||||
// newItem.SetToolTip(menuItem.Tooltip)
|
||||
//}
|
||||
if menuItem.Click != nil {
|
||||
newItem.OnClick().Bind(func(e *winc.Event) {
|
||||
toggleRadioItem(menuItem)
|
||||
menuItem.Click(&menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
})
|
||||
})
|
||||
}
|
||||
newItem.SetEnabled(!menuItem.Disabled)
|
||||
addRadioItemToMap(menuItem, newItem)
|
||||
case menu.SubmenuType:
|
||||
submenu := parent.AddSubMenu(menuItem.Label)
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
processMenuItem(submenu, menuItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
}
|
||||
|
||||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
processMenu(f.mainWindow, f.mainWindow.applicationMenu)
|
||||
}
|
||||
489
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/notifications.go
generated
vendored
Normal file
489
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/notifications.go
generated
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast"
|
||||
"github.com/google/uuid"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
categories map[string]frontend.NotificationCategory
|
||||
categoriesLock sync.RWMutex
|
||||
appName string
|
||||
appGUID string
|
||||
iconPath string = ""
|
||||
exePath string
|
||||
iconOnce sync.Once
|
||||
iconErr error
|
||||
|
||||
notificationResultCallback func(result frontend.NotificationResult)
|
||||
callbackLock sync.RWMutex
|
||||
)
|
||||
|
||||
const DefaultActionIdentifier = "DEFAULT_ACTION"
|
||||
|
||||
const (
|
||||
ToastRegistryPath = `Software\Classes\AppUserModelId\`
|
||||
ToastRegistryGuidKey = "CustomActivator"
|
||||
NotificationCategoriesRegistryPath = `SOFTWARE\%s\NotificationCategories`
|
||||
NotificationCategoriesRegistryKey = "Categories"
|
||||
)
|
||||
|
||||
// NotificationPayload combines the action ID and user data into a single structure
|
||||
type NotificationPayload struct {
|
||||
Action string `json:"action"`
|
||||
Options frontend.NotificationOptions `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
func (f *Frontend) InitializeNotifications() error {
|
||||
categoriesLock.Lock()
|
||||
defer categoriesLock.Unlock()
|
||||
categories = make(map[string]frontend.NotificationCategory)
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable: %w", err)
|
||||
}
|
||||
exePath = exe
|
||||
appName = filepath.Base(exePath)
|
||||
|
||||
appGUID, err = getGUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iconPath = filepath.Join(os.TempDir(), appName+appGUID+".png")
|
||||
|
||||
// Create the registry key for the toast activator
|
||||
key, _, err := registry.CreateKey(registry.CURRENT_USER,
|
||||
`Software\Classes\CLSID\`+appGUID+`\LocalServer32`, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CLSID key: %w", err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", exePath)); err != nil {
|
||||
return fmt.Errorf("failed to set CLSID server path: %w", err)
|
||||
}
|
||||
|
||||
toast.SetAppData(toast.AppData{
|
||||
AppID: appName,
|
||||
GUID: appGUID,
|
||||
IconPath: iconPath,
|
||||
ActivationExe: exePath,
|
||||
})
|
||||
|
||||
toast.SetActivationCallback(func(args string, data []toast.UserData) {
|
||||
result := frontend.NotificationResult{}
|
||||
|
||||
actionIdentifier, options, err := parseNotificationResponse(args)
|
||||
|
||||
if err != nil {
|
||||
result.Error = err
|
||||
} else {
|
||||
// Subtitle is retained but was not shown with the notification
|
||||
response := frontend.NotificationResponse{
|
||||
ID: options.ID,
|
||||
ActionIdentifier: actionIdentifier,
|
||||
Title: options.Title,
|
||||
Subtitle: options.Subtitle,
|
||||
Body: options.Body,
|
||||
CategoryID: options.CategoryID,
|
||||
UserInfo: options.Data,
|
||||
}
|
||||
|
||||
if userText, found := getUserText(data); found {
|
||||
response.UserText = userText
|
||||
}
|
||||
|
||||
result.Response = response
|
||||
}
|
||||
|
||||
handleNotificationResult(result)
|
||||
})
|
||||
|
||||
// Register the COM class factory for toast activation.
|
||||
// This is required for Windows to activate the app when users interact with notifications.
|
||||
// The go-toast library's SetAppData and SetActivationCallback handle the callback setup,
|
||||
// but the COM class factory registration is not exposed via public APIs, so we use
|
||||
// go:linkname to access the internal registerClassFactory function.
|
||||
if err := registerToastClassFactory(wintoast.ClassFactory); err != nil {
|
||||
return fmt.Errorf("CoRegisterClassObject failed: %w", err)
|
||||
}
|
||||
|
||||
return loadCategoriesFromRegistry()
|
||||
}
|
||||
|
||||
// registerToastClassFactory registers the COM class factory required for Windows toast notification activation.
|
||||
// This function uses go:linkname to access the unexported registerClassFactory function from go-toast.
|
||||
// The class factory is necessary for Windows COM activation when users click notification actions.
|
||||
// Without this registration, notification actions will not activate the application.
|
||||
//
|
||||
// This is a workaround until go-toast exports this functionality via a public API.
|
||||
// See: https://git.sr.ht/~jackmordaunt/go-toast
|
||||
//
|
||||
//go:linkname registerToastClassFactory git.sr.ht/~jackmordaunt/go-toast/v2/wintoast.registerClassFactory
|
||||
func registerToastClassFactory(factory *wintoast.IClassFactory) error
|
||||
|
||||
// CleanupNotifications is a Windows stub that does nothing.
|
||||
// (Linux-specific cleanup)
|
||||
func (f *Frontend) CleanupNotifications() {
|
||||
// No cleanup needed on Windows
|
||||
}
|
||||
|
||||
func (f *Frontend) IsNotificationAvailable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows.
|
||||
// (subtitle is only available on macOS and Linux)
|
||||
func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
|
||||
if err := f.saveIconToDir(); err != nil {
|
||||
f.logger.Warning("Error saving icon: %v", err)
|
||||
}
|
||||
|
||||
n := toast.Notification{
|
||||
Title: options.Title,
|
||||
Body: options.Body,
|
||||
ActivationType: toast.Foreground,
|
||||
ActivationArguments: DefaultActionIdentifier,
|
||||
}
|
||||
|
||||
encodedPayload, err := encodePayload(DefaultActionIdentifier, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification payload: %w", err)
|
||||
}
|
||||
n.ActivationArguments = encodedPayload
|
||||
|
||||
return n.Push()
|
||||
}
|
||||
|
||||
// SendNotificationWithActions sends a notification with additional actions and inputs.
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
// (subtitle is only available on macOS and Linux)
|
||||
func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
|
||||
if err := f.saveIconToDir(); err != nil {
|
||||
f.logger.Warning("Error saving icon: %v", err)
|
||||
}
|
||||
|
||||
categoriesLock.RLock()
|
||||
nCategory, categoryExists := categories[options.CategoryID]
|
||||
categoriesLock.RUnlock()
|
||||
|
||||
if options.CategoryID == "" || !categoryExists {
|
||||
f.logger.Warning("Category '%s' not found, sending basic notification without actions", options.CategoryID)
|
||||
return f.SendNotification(options)
|
||||
}
|
||||
|
||||
n := toast.Notification{
|
||||
Title: options.Title,
|
||||
Body: options.Body,
|
||||
ActivationType: toast.Foreground,
|
||||
ActivationArguments: DefaultActionIdentifier,
|
||||
}
|
||||
|
||||
for _, action := range nCategory.Actions {
|
||||
n.Actions = append(n.Actions, toast.Action{
|
||||
Content: action.Title,
|
||||
Arguments: action.ID,
|
||||
})
|
||||
}
|
||||
|
||||
if nCategory.HasReplyField {
|
||||
n.Inputs = append(n.Inputs, toast.Input{
|
||||
ID: "userText",
|
||||
Placeholder: nCategory.ReplyPlaceholder,
|
||||
})
|
||||
|
||||
n.Actions = append(n.Actions, toast.Action{
|
||||
Content: nCategory.ReplyButtonTitle,
|
||||
Arguments: "TEXT_REPLY",
|
||||
InputID: "userText",
|
||||
})
|
||||
}
|
||||
|
||||
encodedPayload, err := encodePayload(n.ActivationArguments, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification payload: %w", err)
|
||||
}
|
||||
n.ActivationArguments = encodedPayload
|
||||
|
||||
for index := range n.Actions {
|
||||
encodedPayload, err := encodePayload(n.Actions[index].Arguments, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification payload: %w", err)
|
||||
}
|
||||
n.Actions[index].Arguments = encodedPayload
|
||||
}
|
||||
|
||||
return n.Push()
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
// Registering a category with the same name as a previously registered NotificationCategory will override it.
|
||||
func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
|
||||
categoriesLock.Lock()
|
||||
defer categoriesLock.Unlock()
|
||||
|
||||
categories[category.ID] = frontend.NotificationCategory{
|
||||
ID: category.ID,
|
||||
Actions: category.Actions,
|
||||
HasReplyField: category.HasReplyField,
|
||||
ReplyPlaceholder: category.ReplyPlaceholder,
|
||||
ReplyButtonTitle: category.ReplyButtonTitle,
|
||||
}
|
||||
|
||||
return saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RemoveNotificationCategory removes a previously registered NotificationCategory.
|
||||
func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
|
||||
categoriesLock.Lock()
|
||||
defer categoriesLock.Unlock()
|
||||
|
||||
delete(categories, categoryId)
|
||||
|
||||
return saveCategoriesToRegistry()
|
||||
}
|
||||
|
||||
// RemoveAllPendingNotifications is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemoveAllPendingNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePendingNotification is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemovePendingNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllDeliveredNotifications is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemoveAllDeliveredNotifications() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDeliveredNotification is a Windows stub that always returns nil.
|
||||
// (macOS and Linux only)
|
||||
func (f *Frontend) RemoveDeliveredNotification(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveNotification is a Windows stub that always returns nil.
|
||||
// (Linux-specific)
|
||||
func (f *Frontend) RemoveNotification(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
|
||||
callbackLock.Lock()
|
||||
defer callbackLock.Unlock()
|
||||
|
||||
notificationResultCallback = callback
|
||||
}
|
||||
|
||||
func (f *Frontend) saveIconToDir() error {
|
||||
iconOnce.Do(func() {
|
||||
hIcon := w32.ExtractIcon(exePath, 0)
|
||||
if hIcon == 0 {
|
||||
iconErr = fmt.Errorf("ExtractIcon failed for %s", exePath)
|
||||
return
|
||||
}
|
||||
defer w32.DestroyIcon(hIcon)
|
||||
iconErr = winc.SaveHIconAsPNG(hIcon, iconPath)
|
||||
})
|
||||
return iconErr
|
||||
}
|
||||
|
||||
func saveCategoriesToRegistry() error {
|
||||
// We assume lock is held by caller
|
||||
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
|
||||
|
||||
key, _, err := registry.CreateKey(
|
||||
registry.CURRENT_USER,
|
||||
registryPath,
|
||||
registry.ALL_ACCESS,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
data, err := json.Marshal(categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return key.SetStringValue(NotificationCategoriesRegistryKey, string(data))
|
||||
}
|
||||
|
||||
func loadCategoriesFromRegistry() error {
|
||||
// We assume lock is held by caller
|
||||
|
||||
registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
|
||||
|
||||
key, err := registry.OpenKey(
|
||||
registry.CURRENT_USER,
|
||||
registryPath,
|
||||
registry.QUERY_VALUE,
|
||||
)
|
||||
if err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
// Not an error, no saved categories
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to open registry key: %w", err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey)
|
||||
if err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
// No value yet, but key exists
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read categories from registry: %w", err)
|
||||
}
|
||||
|
||||
_categories := make(map[string]frontend.NotificationCategory)
|
||||
if err := json.Unmarshal([]byte(data), &_categories); err != nil {
|
||||
return fmt.Errorf("failed to parse notification categories from registry: %w", err)
|
||||
}
|
||||
|
||||
categories = _categories
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserText(data []toast.UserData) (string, bool) {
|
||||
for _, d := range data {
|
||||
if d.Key == "userText" {
|
||||
return d.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// encodePayload combines an action ID and user data into a single encoded string
|
||||
func encodePayload(actionID string, options frontend.NotificationOptions) (string, error) {
|
||||
payload := NotificationPayload{
|
||||
Action: actionID,
|
||||
Options: options,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return actionID, err
|
||||
}
|
||||
|
||||
encodedPayload := base64.StdEncoding.EncodeToString(jsonData)
|
||||
return encodedPayload, nil
|
||||
}
|
||||
|
||||
// decodePayload extracts the action ID and user data from an encoded payload
|
||||
func decodePayload(encodedString string) (string, frontend.NotificationOptions, error) {
|
||||
jsonData, err := base64.StdEncoding.DecodeString(encodedString)
|
||||
if err != nil {
|
||||
return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err)
|
||||
}
|
||||
|
||||
var payload NotificationPayload
|
||||
if err := json.Unmarshal(jsonData, &payload); err != nil {
|
||||
return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to unmarshal notification payload: %w", err)
|
||||
}
|
||||
|
||||
return payload.Action, payload.Options, nil
|
||||
}
|
||||
|
||||
// parseNotificationResponse updated to use structured payload decoding
|
||||
func parseNotificationResponse(response string) (action string, options frontend.NotificationOptions, err error) {
|
||||
actionID, options, err := decodePayload(response)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to decode notification response: %v", err)
|
||||
return response, frontend.NotificationOptions{}, err
|
||||
}
|
||||
|
||||
return actionID, options, nil
|
||||
}
|
||||
|
||||
func handleNotificationResult(result frontend.NotificationResult) {
|
||||
callbackLock.RLock()
|
||||
callback := notificationResultCallback
|
||||
callbackLock.RUnlock()
|
||||
|
||||
if callback != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log panic but don't crash the app
|
||||
fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
|
||||
}
|
||||
}()
|
||||
callback(result)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func getGUID() (string, error) {
|
||||
keyPath := ToastRegistryPath + appName
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
guid, _, err := k.GetStringValue(ToastRegistryGuidKey)
|
||||
k.Close()
|
||||
if err == nil && guid != "" {
|
||||
return guid, nil
|
||||
}
|
||||
}
|
||||
|
||||
guid := generateGUID()
|
||||
|
||||
k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create registry key: %w", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
if err := k.SetStringValue(ToastRegistryGuidKey, guid); err != nil {
|
||||
return "", fmt.Errorf("failed to write GUID to registry: %w", err)
|
||||
}
|
||||
|
||||
return guid, nil
|
||||
}
|
||||
|
||||
func generateGUID() string {
|
||||
guid := uuid.New()
|
||||
return fmt.Sprintf("{%s}", guid.String())
|
||||
}
|
||||
129
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/screen.go
generated
vendored
Normal file
129
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/screen.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
func MonitorsEqual(first w32.MONITORINFO, second w32.MONITORINFO) bool {
|
||||
// Checks to make sure all the fields are the same.
|
||||
// A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
|
||||
return first.DwFlags == second.DwFlags &&
|
||||
first.RcMonitor.Top == second.RcMonitor.Top &&
|
||||
first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
|
||||
first.RcMonitor.Right == second.RcMonitor.Right &&
|
||||
first.RcMonitor.Left == second.RcMonitor.Left &&
|
||||
first.RcWork.Top == second.RcWork.Top &&
|
||||
first.RcWork.Bottom == second.RcWork.Bottom &&
|
||||
first.RcWork.Right == second.RcWork.Right &&
|
||||
first.RcWork.Left == second.RcWork.Left
|
||||
}
|
||||
|
||||
func GetMonitorInfo(hMonitor w32.HMONITOR) (*w32.MONITORINFO, error) {
|
||||
// Adapted from winc.utils.getMonitorInfo TODO: add this to win32
|
||||
// See docs for
|
||||
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
|
||||
|
||||
var info w32.MONITORINFO
|
||||
info.CbSize = uint32(unsafe.Sizeof(info))
|
||||
succeeded := w32.GetMonitorInfo(hMonitor, &info)
|
||||
if !succeeded {
|
||||
return &info, errors.New("Windows call to getMonitorInfo failed")
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, screenContainer *ScreenContainer) uintptr {
|
||||
// adapted from https://stackoverflow.com/a/23492886/4188138
|
||||
|
||||
// see docs for the following pages to better understand this function
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
|
||||
ourMonitorData := Screen{}
|
||||
currentMonHndl := w32.MonitorFromWindow(screenContainer.mainWinHandle, w32.MONITOR_DEFAULTTONEAREST)
|
||||
currentMonInfo, currErr := GetMonitorInfo(currentMonHndl)
|
||||
|
||||
if currErr != nil {
|
||||
screenContainer.errors = append(screenContainer.errors, currErr)
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
// not sure what the consequences of returning false are, so let's just return true and handle it ourselves
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
monInfo, err := GetMonitorInfo(hMonitor)
|
||||
if err != nil {
|
||||
screenContainer.errors = append(screenContainer.errors, err)
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
width := lprcMonitor.Right - lprcMonitor.Left
|
||||
height := lprcMonitor.Bottom - lprcMonitor.Top
|
||||
ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1
|
||||
ourMonitorData.Height = int(height)
|
||||
ourMonitorData.Width = int(width)
|
||||
ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
|
||||
|
||||
ourMonitorData.PhysicalSize.Width = int(width)
|
||||
ourMonitorData.PhysicalSize.Height = int(height)
|
||||
|
||||
var dpiX, dpiY uint
|
||||
w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
|
||||
if dpiX == 0 || dpiY == 0 {
|
||||
screenContainer.errors = append(screenContainer.errors, fmt.Errorf("unable to get DPI for screen"))
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
return w32.TRUE
|
||||
}
|
||||
ourMonitorData.Size.Width = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Width, dpiX)
|
||||
ourMonitorData.Size.Height = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Height, dpiY)
|
||||
|
||||
// the reason we need a container is that we have don't know how many times this function will be called
|
||||
// this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors
|
||||
// and retrieve the values after all EnumProc calls
|
||||
// If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
|
||||
screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
|
||||
// let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
|
||||
screenContainer.errors = append(screenContainer.errors, nil)
|
||||
return w32.TRUE
|
||||
}
|
||||
|
||||
type ScreenContainer struct {
|
||||
monitors []Screen
|
||||
errors []error
|
||||
mainWinHandle w32.HWND
|
||||
}
|
||||
|
||||
func GetAllScreens(mainWinHandle w32.HWND) ([]Screen, error) {
|
||||
// TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
|
||||
monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
|
||||
returnErr := error(nil)
|
||||
errorStrings := []string{}
|
||||
|
||||
dc := w32.GetDC(0)
|
||||
defer w32.ReleaseDC(0, dc)
|
||||
succeeded := w32.EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
|
||||
if !succeeded {
|
||||
return monitorContainer.monitors, errors.New("Windows call to EnumDisplayMonitors failed")
|
||||
}
|
||||
for idx, err := range monitorContainer.errors {
|
||||
if err != nil {
|
||||
errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorStrings) > 0 {
|
||||
returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
|
||||
}
|
||||
return monitorContainer.monitors, returnErr
|
||||
}
|
||||
136
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/single_instance.go
generated
vendored
Normal file
136
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/single_instance.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"golang.org/x/sys/windows"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type COPYDATASTRUCT struct {
|
||||
dwData uintptr
|
||||
cbData uint32
|
||||
lpData uintptr
|
||||
}
|
||||
|
||||
// WMCOPYDATA_SINGLE_INSTANCE_DATA we define our own type for WM_COPYDATA message
|
||||
const WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542
|
||||
|
||||
func SendMessage(hwnd w32.HWND, data string) {
|
||||
arrUtf16, _ := syscall.UTF16FromString(data)
|
||||
|
||||
pCopyData := new(COPYDATASTRUCT)
|
||||
pCopyData.dwData = WMCOPYDATA_SINGLE_INSTANCE_DATA
|
||||
pCopyData.cbData = uint32(len(arrUtf16)*2 + 1)
|
||||
pCopyData.lpData = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(data)))
|
||||
|
||||
w32.SendMessage(hwnd, w32.WM_COPYDATA, 0, uintptr(unsafe.Pointer(pCopyData)))
|
||||
}
|
||||
|
||||
// SetupSingleInstance single instance Windows app
|
||||
func SetupSingleInstance(uniqueId string) {
|
||||
id := "wails-app-" + uniqueId
|
||||
|
||||
className := id + "-sic"
|
||||
windowName := id + "-siw"
|
||||
mutexName := id + "sim"
|
||||
|
||||
_, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName))
|
||||
|
||||
if err != nil {
|
||||
if err == windows.ERROR_ALREADY_EXISTS {
|
||||
// app is already running
|
||||
hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(className), windows.StringToUTF16Ptr(windowName))
|
||||
|
||||
if hwnd != 0 {
|
||||
data := options.SecondInstanceData{
|
||||
Args: os.Args[1:],
|
||||
}
|
||||
data.WorkingDirectory, err = os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get working directory: %v", err)
|
||||
return
|
||||
}
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to marshal data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
SendMessage(hwnd, string(serialized))
|
||||
// exit second instance of app after sending message
|
||||
os.Exit(0)
|
||||
}
|
||||
// if we got any other unknown error we will just start new application instance
|
||||
}
|
||||
} else {
|
||||
createEventTargetWindow(className, windowName)
|
||||
}
|
||||
}
|
||||
|
||||
func createEventTargetWindow(className string, windowName string) w32.HWND {
|
||||
// callback handler in the event target window
|
||||
wndProc := func(
|
||||
hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM,
|
||||
) w32.LRESULT {
|
||||
if msg == w32.WM_COPYDATA {
|
||||
ldata := (*COPYDATASTRUCT)(unsafe.Pointer(lparam))
|
||||
|
||||
if ldata.dwData == WMCOPYDATA_SINGLE_INSTANCE_DATA {
|
||||
serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.lpData)))
|
||||
|
||||
var secondInstanceData options.SecondInstanceData
|
||||
|
||||
err := json.Unmarshal([]byte(serialized), &secondInstanceData)
|
||||
|
||||
if err == nil {
|
||||
secondInstanceBuffer <- secondInstanceData
|
||||
}
|
||||
}
|
||||
|
||||
return w32.LRESULT(0)
|
||||
}
|
||||
|
||||
return w32.DefWindowProc(hwnd, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
var class w32.WNDCLASSEX
|
||||
class.Size = uint32(unsafe.Sizeof(class))
|
||||
class.Style = 0
|
||||
class.WndProc = syscall.NewCallback(wndProc)
|
||||
class.ClsExtra = 0
|
||||
class.WndExtra = 0
|
||||
class.Instance = w32.GetModuleHandle("")
|
||||
class.Icon = 0
|
||||
class.Cursor = 0
|
||||
class.Background = 0
|
||||
class.MenuName = nil
|
||||
class.ClassName = windows.StringToUTF16Ptr(className)
|
||||
class.IconSm = 0
|
||||
|
||||
w32.RegisterClassEx(&class)
|
||||
|
||||
// create event window that will not be visible for user
|
||||
hwnd := w32.CreateWindowEx(
|
||||
0,
|
||||
windows.StringToUTF16Ptr(className),
|
||||
windows.StringToUTF16Ptr(windowName),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
w32.HWND_MESSAGE,
|
||||
0,
|
||||
w32.GetModuleHandle(""),
|
||||
nil,
|
||||
)
|
||||
|
||||
return hwnd
|
||||
}
|
||||
67
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/theme.go
generated
vendored
Normal file
67
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/theme.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||
)
|
||||
|
||||
func (w *Window) UpdateTheme() {
|
||||
|
||||
// Don't redraw theme if nothing has changed
|
||||
if !w.themeChanged {
|
||||
return
|
||||
}
|
||||
w.themeChanged = false
|
||||
|
||||
if win32.IsCurrentlyHighContrastMode() {
|
||||
return
|
||||
}
|
||||
|
||||
if !win32.SupportsThemes() {
|
||||
return
|
||||
}
|
||||
|
||||
var isDarkMode bool
|
||||
switch w.theme {
|
||||
case windows.SystemDefault:
|
||||
isDarkMode = win32.IsCurrentlyDarkMode()
|
||||
case windows.Dark:
|
||||
isDarkMode = true
|
||||
case windows.Light:
|
||||
isDarkMode = false
|
||||
}
|
||||
win32.SetTheme(w.Handle(), isDarkMode)
|
||||
|
||||
// Custom theme processing
|
||||
winOptions := w.frontendOptions.Windows
|
||||
var customTheme *windows.ThemeSettings
|
||||
if winOptions != nil {
|
||||
customTheme = winOptions.CustomTheme
|
||||
}
|
||||
// Custom theme
|
||||
if win32.SupportsCustomThemes() && customTheme != nil {
|
||||
if w.isActive {
|
||||
if isDarkMode {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBar)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleText)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorder)
|
||||
} else {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBar)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleText)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.LightModeBorder)
|
||||
}
|
||||
} else {
|
||||
if isDarkMode {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBarInactive)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleTextInactive)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorderInactive)
|
||||
} else {
|
||||
win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBarInactive)
|
||||
win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleTextInactive)
|
||||
win32.SetBorderColour(w.Handle(), customTheme.LightModeBorderInactive)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/clipboard.go
generated
vendored
Normal file
143
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/clipboard.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||
*/
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
cfUnicodetext = 13
|
||||
gmemMoveable = 0x0002
|
||||
)
|
||||
|
||||
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
|
||||
func waitOpenClipboard() error {
|
||||
started := time.Now()
|
||||
limit := started.Add(time.Second)
|
||||
var r uintptr
|
||||
var err error
|
||||
for time.Now().Before(limit) {
|
||||
r, _, err = procOpenClipboard.Call(0)
|
||||
if r != 0 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func GetClipboardText() (string, error) {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
|
||||
return "", err
|
||||
}
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h, _, err := procGetClipboardData.Call(cfUnicodetext)
|
||||
if h == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
l, _, err := kernelGlobalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
|
||||
|
||||
r, _, err := kernelGlobalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
closed, _, err := procCloseClipboard.Call()
|
||||
if closed == 0 {
|
||||
return "", err
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func SetClipboardText(text string) error {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err := procEmptyClipboard.Call(0)
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := syscall.UTF16FromString(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// "If the hMem parameter identifies a memory object, the object must have
|
||||
// been allocated using the function with the GMEM_MOVEABLE flag."
|
||||
h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
||||
if h == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if h != 0 {
|
||||
kernelGlobalFree.Call(h)
|
||||
}
|
||||
}()
|
||||
|
||||
l, _, err := kernelGlobalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = kernelGlobalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
if err.(syscall.Errno) != 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r, _, err = procSetClipboardData.Call(cfUnicodetext, h)
|
||||
if r == 0 {
|
||||
_, _, _ = procCloseClipboard.Call()
|
||||
return err
|
||||
}
|
||||
h = 0 // suppress deferred cleanup
|
||||
closed, _, err := procCloseClipboard.Call()
|
||||
if closed == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
57
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/consts.go
generated
vendored
Normal file
57
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/consts.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
//go:build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
)
|
||||
|
||||
type HRESULT int32
|
||||
type HANDLE uintptr
|
||||
type HMONITOR HANDLE
|
||||
|
||||
var (
|
||||
moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
|
||||
procGetWindowLong = moduser32.NewProc("GetWindowLongW")
|
||||
procSetClassLong = moduser32.NewProc("SetClassLongW")
|
||||
procSetClassLongPtr = moduser32.NewProc("SetClassLongPtrW")
|
||||
procShowWindow = moduser32.NewProc("ShowWindow")
|
||||
procIsWindowVisible = moduser32.NewProc("IsWindowVisible")
|
||||
procGetWindowRect = moduser32.NewProc("GetWindowRect")
|
||||
procGetMonitorInfo = moduser32.NewProc("GetMonitorInfoW")
|
||||
procMonitorFromWindow = moduser32.NewProc("MonitorFromWindow")
|
||||
procIsClipboardFormatAvailable = moduser32.NewProc("IsClipboardFormatAvailable")
|
||||
procOpenClipboard = moduser32.NewProc("OpenClipboard")
|
||||
procCloseClipboard = moduser32.NewProc("CloseClipboard")
|
||||
procEmptyClipboard = moduser32.NewProc("EmptyClipboard")
|
||||
procGetClipboardData = moduser32.NewProc("GetClipboardData")
|
||||
procSetClipboardData = moduser32.NewProc("SetClipboardData")
|
||||
)
|
||||
var (
|
||||
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
|
||||
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
|
||||
procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
var (
|
||||
modwingdi = syscall.NewLazyDLL("gdi32.dll")
|
||||
procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush")
|
||||
)
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
kernelGlobalFree = kernel32.NewProc("GlobalFree")
|
||||
kernelGlobalLock = kernel32.NewProc("GlobalLock")
|
||||
kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
kernelLstrcpy = kernel32.NewProc("lstrcpyW")
|
||||
)
|
||||
|
||||
var windowsVersion, _ = operatingsystem.GetWindowsVersionInfo()
|
||||
|
||||
func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
|
||||
return windowsVersion.Major >= major &&
|
||||
windowsVersion.Minor >= minor &&
|
||||
windowsVersion.Build >= buildNumber
|
||||
}
|
||||
119
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/theme.go
generated
vendored
Normal file
119
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/theme.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
//go:build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
type DWMWINDOWATTRIBUTE int32
|
||||
|
||||
const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19
|
||||
const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20
|
||||
const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34
|
||||
const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35
|
||||
const DwmwaTextColor DWMWINDOWATTRIBUTE = 36
|
||||
const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38
|
||||
|
||||
const SPI_GETHIGHCONTRAST = 0x0042
|
||||
const HCF_HIGHCONTRASTON = 0x00000001
|
||||
|
||||
// BackdropType defines the type of translucency we wish to use
|
||||
type BackdropType int32
|
||||
|
||||
func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
|
||||
ret, _, err := procDwmSetWindowAttribute.Call(
|
||||
hwnd,
|
||||
uintptr(dwAttribute),
|
||||
uintptr(pvAttribute),
|
||||
cbAttribute)
|
||||
if ret != 0 {
|
||||
_ = err
|
||||
// println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func SupportsThemes() bool {
|
||||
// We can't support Windows versions before 17763
|
||||
return IsWindowsVersionAtLeast(10, 0, 17763)
|
||||
}
|
||||
|
||||
func SupportsCustomThemes() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 17763)
|
||||
}
|
||||
|
||||
func SupportsBackdropTypes() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 22621)
|
||||
}
|
||||
|
||||
func SupportsImmersiveDarkMode() bool {
|
||||
return IsWindowsVersionAtLeast(10, 0, 18985)
|
||||
}
|
||||
|
||||
func SetTheme(hwnd uintptr, useDarkMode bool) {
|
||||
if SupportsThemes() {
|
||||
attr := DwmwaUseImmersiveDarkModeBefore20h1
|
||||
if SupportsImmersiveDarkMode() {
|
||||
attr = DwmwaUseImmersiveDarkMode
|
||||
}
|
||||
var winDark int32
|
||||
if useDarkMode {
|
||||
winDark = 1
|
||||
}
|
||||
dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark))
|
||||
}
|
||||
}
|
||||
|
||||
func EnableTranslucency(hwnd uintptr, backdrop BackdropType) {
|
||||
if SupportsBackdropTypes() {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop))
|
||||
} else {
|
||||
println("Warning: Translucency type unavailable on Windows < 22621")
|
||||
}
|
||||
}
|
||||
|
||||
func SetTitleBarColour(hwnd uintptr, titleBarColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour))
|
||||
}
|
||||
|
||||
func SetTitleTextColour(hwnd uintptr, titleTextColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour))
|
||||
}
|
||||
|
||||
func SetBorderColour(hwnd uintptr, titleBorderColour int32) {
|
||||
dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour))
|
||||
}
|
||||
|
||||
func IsCurrentlyDarkMode() bool {
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return AppsUseLightTheme == 0
|
||||
}
|
||||
|
||||
type highContrast struct {
|
||||
CbSize uint32
|
||||
DwFlags uint32
|
||||
LpszDefaultScheme *int16
|
||||
}
|
||||
|
||||
func IsCurrentlyHighContrastMode() bool {
|
||||
var result highContrast
|
||||
result.CbSize = uint32(unsafe.Sizeof(result))
|
||||
res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if res == 0 {
|
||||
_ = err
|
||||
return false
|
||||
}
|
||||
r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
|
||||
return r
|
||||
}
|
||||
223
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/window.go
generated
vendored
Normal file
223
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32/window.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
//go:build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
)
|
||||
|
||||
const (
|
||||
WS_MAXIMIZE = 0x01000000
|
||||
WS_MINIMIZE = 0x20000000
|
||||
|
||||
GWL_STYLE = -16
|
||||
|
||||
MONITOR_DEFAULTTOPRIMARY = 0x00000001
|
||||
)
|
||||
|
||||
const (
|
||||
SW_HIDE = 0
|
||||
SW_NORMAL = 1
|
||||
SW_SHOWNORMAL = 1
|
||||
SW_SHOWMINIMIZED = 2
|
||||
SW_MAXIMIZE = 3
|
||||
SW_SHOWMAXIMIZED = 3
|
||||
SW_SHOWNOACTIVATE = 4
|
||||
SW_SHOW = 5
|
||||
SW_MINIMIZE = 6
|
||||
SW_SHOWMINNOACTIVE = 7
|
||||
SW_SHOWNA = 8
|
||||
SW_RESTORE = 9
|
||||
SW_SHOWDEFAULT = 10
|
||||
SW_FORCEMINIMIZE = 11
|
||||
)
|
||||
|
||||
const (
|
||||
GCLP_HBRBACKGROUND int32 = -10
|
||||
)
|
||||
|
||||
// Power
|
||||
const (
|
||||
// WM_POWERBROADCAST - Notifies applications that a power-management event has occurred.
|
||||
WM_POWERBROADCAST = 536
|
||||
|
||||
// PBT_APMPOWERSTATUSCHANGE - Power status has changed.
|
||||
PBT_APMPOWERSTATUSCHANGE = 10
|
||||
|
||||
// PBT_APMRESUMEAUTOMATIC -Operation is resuming automatically from a low-power state. This message is sent every time the system resumes.
|
||||
PBT_APMRESUMEAUTOMATIC = 18
|
||||
|
||||
// PBT_APMRESUMESUSPEND - Operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
|
||||
PBT_APMRESUMESUSPEND = 7
|
||||
|
||||
// PBT_APMSUSPEND - System is suspending operation.
|
||||
PBT_APMSUSPEND = 4
|
||||
|
||||
// PBT_POWERSETTINGCHANGE - A power setting change event has been received.
|
||||
PBT_POWERSETTINGCHANGE = 32787
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773244.aspx
|
||||
type MARGINS struct {
|
||||
CxLeftWidth, CxRightWidth, CyTopHeight, CyBottomHeight int32
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162897.aspx
|
||||
type RECT struct {
|
||||
Left, Top, Right, Bottom int32
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145065.aspx
|
||||
type MONITORINFO struct {
|
||||
CbSize uint32
|
||||
RcMonitor RECT
|
||||
RcWork RECT
|
||||
DwFlags uint32
|
||||
}
|
||||
|
||||
func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) {
|
||||
// -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
|
||||
// Also shows the caption buttons if transparent ant translucent but they don't work.
|
||||
// 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
|
||||
// 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
|
||||
// are shown if transparent ant translucent.
|
||||
var margins MARGINS
|
||||
if extend {
|
||||
margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
|
||||
}
|
||||
if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil {
|
||||
log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func IsVisible(hwnd uintptr) bool {
|
||||
ret, _, _ := procIsWindowVisible.Call(hwnd)
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func IsWindowFullScreen(hwnd uintptr) bool {
|
||||
wRect := GetWindowRect(hwnd)
|
||||
m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
|
||||
var mi MONITORINFO
|
||||
mi.CbSize = uint32(unsafe.Sizeof(mi))
|
||||
if !GetMonitorInfo(m, &mi) {
|
||||
return false
|
||||
}
|
||||
return wRect.Left == mi.RcMonitor.Left &&
|
||||
wRect.Top == mi.RcMonitor.Top &&
|
||||
wRect.Right == mi.RcMonitor.Right &&
|
||||
wRect.Bottom == mi.RcMonitor.Bottom
|
||||
}
|
||||
|
||||
func IsWindowMaximised(hwnd uintptr) bool {
|
||||
style := uint32(getWindowLong(hwnd, GWL_STYLE))
|
||||
return style&WS_MAXIMIZE != 0
|
||||
}
|
||||
func IsWindowMinimised(hwnd uintptr) bool {
|
||||
style := uint32(getWindowLong(hwnd, GWL_STYLE))
|
||||
return style&WS_MINIMIZE != 0
|
||||
}
|
||||
|
||||
func RestoreWindow(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_RESTORE)
|
||||
}
|
||||
|
||||
func ShowWindow(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_SHOW)
|
||||
}
|
||||
|
||||
func ShowWindowMaximised(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_MAXIMIZE)
|
||||
}
|
||||
func ShowWindowMinimised(hwnd uintptr) {
|
||||
showWindow(hwnd, SW_MINIMIZE)
|
||||
}
|
||||
|
||||
func SetBackgroundColour(hwnd uintptr, r, g, b uint8) {
|
||||
col := winc.RGB(r, g, b)
|
||||
hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col))
|
||||
setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush)
|
||||
}
|
||||
|
||||
func IsWindowNormal(hwnd uintptr) bool {
|
||||
return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd)
|
||||
}
|
||||
|
||||
func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error {
|
||||
ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
|
||||
hwnd,
|
||||
uintptr(unsafe.Pointer(margins)))
|
||||
|
||||
if ret != 0 {
|
||||
return syscall.GetLastError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool {
|
||||
proc := procSetClassLongPtr
|
||||
if strconv.IntSize == 32 {
|
||||
/*
|
||||
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw
|
||||
Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr.
|
||||
When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function
|
||||
|
||||
=> We have to do this dynamically when directly calling the DLL procedures
|
||||
*/
|
||||
proc = procSetClassLong
|
||||
}
|
||||
|
||||
ret, _, _ := proc.Call(
|
||||
hwnd,
|
||||
uintptr(param),
|
||||
val,
|
||||
)
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func getWindowLong(hwnd uintptr, index int) int32 {
|
||||
ret, _, _ := procGetWindowLong.Call(
|
||||
hwnd,
|
||||
uintptr(index))
|
||||
|
||||
return int32(ret)
|
||||
}
|
||||
|
||||
func showWindow(hwnd uintptr, cmdshow int) bool {
|
||||
ret, _, _ := procShowWindow.Call(
|
||||
hwnd,
|
||||
uintptr(cmdshow))
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func GetWindowRect(hwnd uintptr) *RECT {
|
||||
var rect RECT
|
||||
procGetWindowRect.Call(
|
||||
hwnd,
|
||||
uintptr(unsafe.Pointer(&rect)))
|
||||
|
||||
return &rect
|
||||
}
|
||||
|
||||
func MonitorFromWindow(hwnd uintptr, dwFlags uint32) HMONITOR {
|
||||
ret, _, _ := procMonitorFromWindow.Call(
|
||||
hwnd,
|
||||
uintptr(dwFlags),
|
||||
)
|
||||
return HMONITOR(ret)
|
||||
}
|
||||
|
||||
func GetMonitorInfo(hMonitor HMONITOR, lmpi *MONITORINFO) bool {
|
||||
ret, _, _ := procGetMonitorInfo.Call(
|
||||
uintptr(hMonitor),
|
||||
uintptr(unsafe.Pointer(lmpi)),
|
||||
)
|
||||
return ret != 0
|
||||
}
|
||||
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/.gitignore
generated
vendored
Normal file
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/AUTHORS
generated
vendored
Normal file
12
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# This is the official list of 'Winc' authors for copyright purposes.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
# Contributors
|
||||
# ============
|
||||
|
||||
Tad Vizbaras <tad@etasoft.com>
|
||||
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/LICENSE
generated
vendored
Normal file
21
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 winc Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
181
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/README.md
generated
vendored
Normal file
181
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/README.md
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
# winc
|
||||
|
||||
** This is a fork of [tadvi/winc](https://github.com/tadvi/winc) for the sole purpose of integration
|
||||
with [Wails](https://github.com/wailsapp/wails). This repository comes with ***no support*** **
|
||||
|
||||
Common library for Go GUI apps on Windows. It is for Windows OS only. This makes library smaller than some other UI
|
||||
libraries for Go.
|
||||
|
||||
Design goals: minimalism and simplicity.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No other dependencies except Go standard library.
|
||||
|
||||
## Building
|
||||
|
||||
If you want to package icon files and other resources into binary **rsrc** tool is recommended:
|
||||
|
||||
rsrc -manifest app.manifest -ico=app.ico,application_edit.ico,application_error.ico -o rsrc.syso
|
||||
|
||||
Here app.manifest is XML file in format:
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="App" type="win32"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
```
|
||||
|
||||
Most Windows applications do not display command prompt. Build your Go project with flag to indicate that it is Windows
|
||||
GUI binary:
|
||||
|
||||
go build -ldflags="-H windowsgui"
|
||||
|
||||
## Samples
|
||||
|
||||
Best way to learn how to use the library is to look at the included **examples** projects.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Make sure you have a working Go installation and build environment, see more for details on page below.
|
||||
http://golang.org/doc/install
|
||||
|
||||
2. go get github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc
|
||||
|
||||
## Icons
|
||||
|
||||
When rsrc is used to pack icons into binary it displays IDs of the packed icons.
|
||||
|
||||
```
|
||||
rsrc -manifest app.manifest -ico=app.ico,lightning.ico,edit.ico,application_error.ico -o rsrc.syso
|
||||
Manifest ID: 1
|
||||
Icon app.ico ID: 10
|
||||
Icon lightning.ico ID: 13
|
||||
Icon edit.ico ID: 16
|
||||
Icon application_error.ico ID: 19
|
||||
```
|
||||
|
||||
Use IDs to reference packed icons.
|
||||
|
||||
```
|
||||
const myIcon = 13
|
||||
|
||||
btn.SetResIcon(myIcon) // Set icon on the button.
|
||||
```
|
||||
|
||||
Included source **examples** use basic building via `release.bat` files. Note that icon IDs are order dependent. So if
|
||||
you change they order in -ico flag then icon IDs will be different. If you want to keep order the same, just add new
|
||||
icons to the end of -ico comma separated list.
|
||||
|
||||
## Layout Manager
|
||||
|
||||
SimpleDock is default layout manager.
|
||||
|
||||
Current design of docking and split views allows building simple apps but if you need to have multiple split views in
|
||||
few different directions you might need to create your own layout manager.
|
||||
|
||||
Important point is to have **one** control inside SimpleDock set to dock as **Fill**. Controls that are not set to any
|
||||
docking get placed using SetPos() function. So you can have Panel set to dock at the Top and then have another dock to
|
||||
arrange controls inside that Panel or have controls placed using SetPos() at fixed positions.
|
||||
|
||||

|
||||
|
||||
This is basic layout. Instead of toolbars and status bar you can have Panel or any other control that can resize. Panel
|
||||
can have its own internal Dock that will arrange other controls inside of it.
|
||||
|
||||

|
||||
|
||||
This is layout with extra control(s) on the left. Left side is usually treeview or listview.
|
||||
|
||||
The rule is simple: you either dock controls using SimpleDock OR use SetPos() to set them at fixed positions. That's it.
|
||||
|
||||
At some point **winc** may get more sophisticated layout manager.
|
||||
|
||||
## Dialog Screens
|
||||
|
||||
Dialog screens are not based on Windows resource files (.rc). They are just windows with controls placed at fixed
|
||||
coordinates. This works fine for dialog screens up to 10-14 controls.
|
||||
|
||||
# Minimal Demo
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mainWindow := winc.NewForm(nil)
|
||||
mainWindow.SetSize(400, 300) // (width, height)
|
||||
mainWindow.SetText("Hello World Demo")
|
||||
|
||||
edt := winc.NewEdit(mainWindow)
|
||||
edt.SetPos(10, 20)
|
||||
// Most Controls have default size unless SetSize is called.
|
||||
edt.SetText("edit text")
|
||||
|
||||
btn := winc.NewPushButton(mainWindow)
|
||||
btn.SetText("Show or Hide")
|
||||
btn.SetPos(40, 50) // (x, y)
|
||||
btn.SetSize(100, 40) // (width, height)
|
||||
btn.OnClick().Bind(func(e *winc.Event) {
|
||||
if edt.Visible() {
|
||||
edt.Hide()
|
||||
} else {
|
||||
edt.Show()
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.Center()
|
||||
mainWindow.Show()
|
||||
mainWindow.OnClose().Bind(wndOnClose)
|
||||
|
||||
winc.RunMainLoop() // Must call to start event loop.
|
||||
}
|
||||
|
||||
func wndOnClose(arg *winc.Event) {
|
||||
winc.Exit()
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Result of running sample_minimal.
|
||||
|
||||
## Create Your Own
|
||||
|
||||
It is good practice to create your own controls based on existing structures and event model. Library contains some of
|
||||
the controls built that way: IconButton (button.go), ErrorPanel (panel.go), MultiEdit (edit.go), etc. Please look at
|
||||
existing controls as examples before building your own.
|
||||
|
||||
When designing your own controls keep in mind that types have to be converted from Go into Win32 API and back. This is
|
||||
usually due to string UTF8 and UTF16 conversions. But there are other types of conversions too.
|
||||
|
||||
When developing your own controls you might also need to:
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
|
||||
w32 has Win32 API low level constants and functions.
|
||||
|
||||
Look at **sample_control** for example of custom built window.
|
||||
|
||||
## Companion Package
|
||||
|
||||
[Go package for Windows Systray icon, menu and notifications](https://github.com/tadvi/systray)
|
||||
|
||||
## Credits
|
||||
|
||||
This library is built on
|
||||
|
||||
[AllenDang/gform Windows GUI framework for Go](https://github.com/AllenDang/gform)
|
||||
|
||||
**winc** takes most design decisions from **gform** and adds many more controls and code samples to it.
|
||||
|
||||
|
||||
109
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/app.go
generated
vendored
Normal file
109
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/app.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
package winc
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
var (
|
||||
// resource compilation tool assigns app.ico ID of 3
|
||||
// rsrc -manifest app.manifest -ico app.ico -o rsrc.syso
|
||||
AppIconID = 3
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
gAppInstance = w32.GetModuleHandle("")
|
||||
if gAppInstance == 0 {
|
||||
panic("Error occurred in App.Init")
|
||||
}
|
||||
|
||||
// Initialize the common controls
|
||||
var initCtrls w32.INITCOMMONCONTROLSEX
|
||||
initCtrls.DwSize = uint32(unsafe.Sizeof(initCtrls))
|
||||
initCtrls.DwICC =
|
||||
w32.ICC_LISTVIEW_CLASSES | w32.ICC_PROGRESS_CLASS | w32.ICC_TAB_CLASSES |
|
||||
w32.ICC_TREEVIEW_CLASSES | w32.ICC_BAR_CLASSES
|
||||
|
||||
w32.InitCommonControlsEx(&initCtrls)
|
||||
}
|
||||
|
||||
// SetAppIcon sets resource icon ID for the apps windows.
|
||||
func SetAppIcon(appIconID int) {
|
||||
AppIconID = appIconID
|
||||
}
|
||||
|
||||
func GetAppInstance() w32.HINSTANCE {
|
||||
return gAppInstance
|
||||
}
|
||||
|
||||
func PreTranslateMessage(msg *w32.MSG) bool {
|
||||
// This functions is called by the MessageLoop. It processes the
|
||||
// keyboard accelerator keys and calls Controller.PreTranslateMessage for
|
||||
// keyboard and mouse events.
|
||||
|
||||
processed := false
|
||||
|
||||
if (msg.Message >= w32.WM_KEYFIRST && msg.Message <= w32.WM_KEYLAST) ||
|
||||
(msg.Message >= w32.WM_MOUSEFIRST && msg.Message <= w32.WM_MOUSELAST) {
|
||||
|
||||
if msg.Hwnd != 0 {
|
||||
if controller := GetMsgHandler(msg.Hwnd); controller != nil {
|
||||
// Search the chain of parents for pretranslated messages.
|
||||
for p := controller; p != nil; p = p.Parent() {
|
||||
|
||||
if processed = p.PreTranslateMessage(msg); processed {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processed
|
||||
}
|
||||
|
||||
// RunMainLoop processes messages in main application loop.
|
||||
func RunMainLoop() int {
|
||||
m := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
|
||||
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
|
||||
|
||||
for w32.GetMessage(m, 0, 0, 0) != 0 {
|
||||
|
||||
if !PreTranslateMessage(m) {
|
||||
w32.TranslateMessage(m)
|
||||
w32.DispatchMessage(m)
|
||||
}
|
||||
}
|
||||
|
||||
w32.GdiplusShutdown()
|
||||
return int(m.WParam)
|
||||
}
|
||||
|
||||
// PostMessages processes recent messages. Sometimes helpful for instant window refresh.
|
||||
func PostMessages() {
|
||||
m := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
|
||||
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if w32.GetMessage(m, 0, 0, 0) != 0 {
|
||||
if !PreTranslateMessage(m) {
|
||||
w32.TranslateMessage(m)
|
||||
w32.DispatchMessage(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Exit() {
|
||||
w32.PostQuitMessage(0)
|
||||
}
|
||||
112
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/bitmap.go
generated
vendored
Normal file
112
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/bitmap.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Bitmap struct {
|
||||
handle w32.HBITMAP
|
||||
width, height int
|
||||
}
|
||||
|
||||
func assembleBitmapFromHBITMAP(hbitmap w32.HBITMAP) (*Bitmap, error) {
|
||||
var dib w32.DIBSECTION
|
||||
if w32.GetObject(w32.HGDIOBJ(hbitmap), unsafe.Sizeof(dib), unsafe.Pointer(&dib)) == 0 {
|
||||
return nil, errors.New("GetObject for HBITMAP failed")
|
||||
}
|
||||
|
||||
return &Bitmap{
|
||||
handle: hbitmap,
|
||||
width: int(dib.DsBmih.BiWidth),
|
||||
height: int(dib.DsBmih.BiHeight),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBitmapFromFile(filepath string, background Color) (*Bitmap, error) {
|
||||
var gpBitmap *uintptr
|
||||
var err error
|
||||
|
||||
gpBitmap, err = w32.GdipCreateBitmapFromFile(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer w32.GdipDisposeImage(gpBitmap)
|
||||
|
||||
var hbitmap w32.HBITMAP
|
||||
// Reverse RGB to BGR to satisfy gdiplus color schema.
|
||||
hbitmap, err = w32.GdipCreateHBITMAPFromBitmap(gpBitmap, uint32(RGB(background.B(), background.G(), background.R())))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assembleBitmapFromHBITMAP(hbitmap)
|
||||
}
|
||||
|
||||
func NewBitmapFromResource(instance w32.HINSTANCE, resName *uint16, resType *uint16, background Color) (*Bitmap, error) {
|
||||
var gpBitmap *uintptr
|
||||
var err error
|
||||
var hRes w32.HRSRC
|
||||
|
||||
hRes, err = w32.FindResource(w32.HMODULE(instance), resName, resType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resSize := w32.SizeofResource(w32.HMODULE(instance), hRes)
|
||||
pResData := w32.LockResource(w32.LoadResource(w32.HMODULE(instance), hRes))
|
||||
resBuffer := w32.GlobalAlloc(w32.GMEM_MOVEABLE, resSize)
|
||||
pResBuffer := w32.GlobalLock(resBuffer)
|
||||
w32.MoveMemory(pResBuffer, pResData, resSize)
|
||||
|
||||
stream := w32.CreateStreamOnHGlobal(resBuffer, false)
|
||||
|
||||
gpBitmap, err = w32.GdipCreateBitmapFromStream(stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stream.Release()
|
||||
defer w32.GlobalUnlock(resBuffer)
|
||||
defer w32.GlobalFree(resBuffer)
|
||||
defer w32.GdipDisposeImage(gpBitmap)
|
||||
|
||||
var hbitmap w32.HBITMAP
|
||||
// Reverse gform.RGB to BGR to satisfy gdiplus color schema.
|
||||
hbitmap, err = w32.GdipCreateHBITMAPFromBitmap(gpBitmap, uint32(RGB(background.B(), background.G(), background.R())))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assembleBitmapFromHBITMAP(hbitmap)
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Dispose() {
|
||||
if bm.handle != 0 {
|
||||
w32.DeleteObject(w32.HGDIOBJ(bm.handle))
|
||||
bm.handle = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (bm *Bitmap) GetHBITMAP() w32.HBITMAP {
|
||||
return bm.handle
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Size() (int, int) {
|
||||
return bm.width, bm.height
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Height() int {
|
||||
return bm.height
|
||||
}
|
||||
|
||||
func (bm *Bitmap) Width() int {
|
||||
return bm.width
|
||||
}
|
||||
74
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/brush.go
generated
vendored
Normal file
74
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/brush.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
var DefaultBackgroundBrush = NewSystemColorBrush(w32.COLOR_BTNFACE)
|
||||
|
||||
type Brush struct {
|
||||
hBrush w32.HBRUSH
|
||||
logBrush w32.LOGBRUSH
|
||||
}
|
||||
|
||||
func NewSolidColorBrush(color Color) *Brush {
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(color)}
|
||||
hBrush := w32.CreateBrushIndirect(&lb)
|
||||
if hBrush == 0 {
|
||||
panic("Faild to create solid color brush")
|
||||
}
|
||||
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func NewSystemColorBrush(colorIndex int) *Brush {
|
||||
//lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(colorIndex)}
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_NULL}
|
||||
hBrush := w32.GetSysColorBrush(colorIndex)
|
||||
if hBrush == 0 {
|
||||
panic("GetSysColorBrush failed")
|
||||
}
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func NewHatchedColorBrush(color Color) *Brush {
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_HATCHED, LbColor: w32.COLORREF(color)}
|
||||
hBrush := w32.CreateBrushIndirect(&lb)
|
||||
if hBrush == 0 {
|
||||
panic("Faild to create solid color brush")
|
||||
}
|
||||
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func NewNullBrush() *Brush {
|
||||
lb := w32.LOGBRUSH{LbStyle: w32.BS_NULL}
|
||||
hBrush := w32.CreateBrushIndirect(&lb)
|
||||
if hBrush == 0 {
|
||||
panic("Failed to create null brush")
|
||||
}
|
||||
|
||||
return &Brush{hBrush, lb}
|
||||
}
|
||||
|
||||
func (br *Brush) GetHBRUSH() w32.HBRUSH {
|
||||
return br.hBrush
|
||||
}
|
||||
|
||||
func (br *Brush) GetLOGBRUSH() *w32.LOGBRUSH {
|
||||
return &br.logBrush
|
||||
}
|
||||
|
||||
func (br *Brush) Dispose() {
|
||||
if br.hBrush != 0 {
|
||||
w32.DeleteObject(w32.HGDIOBJ(br.hBrush))
|
||||
br.hBrush = 0
|
||||
}
|
||||
}
|
||||
156
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/buttons.go
generated
vendored
Normal file
156
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/buttons.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
ControlBase
|
||||
onClick EventManager
|
||||
}
|
||||
|
||||
func (bt *Button) OnClick() *EventManager {
|
||||
return &bt.onClick
|
||||
}
|
||||
|
||||
func (bt *Button) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
bt.onClick.Fire(NewEvent(bt, nil))
|
||||
/*case w32.WM_LBUTTONDOWN:
|
||||
w32.SetCapture(bt.Handle())
|
||||
case w32.WM_LBUTTONUP:
|
||||
w32.ReleaseCapture()*/
|
||||
/*case win.WM_GETDLGCODE:
|
||||
println("GETDLGCODE")*/
|
||||
}
|
||||
return w32.DefWindowProc(bt.hwnd, msg, wparam, lparam)
|
||||
//return bt.W32Control.WndProc(msg, wparam, lparam)
|
||||
}
|
||||
|
||||
func (bt *Button) Checked() bool {
|
||||
result := w32.SendMessage(bt.hwnd, w32.BM_GETCHECK, 0, 0)
|
||||
return result == w32.BST_CHECKED
|
||||
}
|
||||
|
||||
func (bt *Button) SetChecked(checked bool) {
|
||||
wparam := w32.BST_CHECKED
|
||||
if !checked {
|
||||
wparam = w32.BST_UNCHECKED
|
||||
}
|
||||
w32.SendMessage(bt.hwnd, w32.BM_SETCHECK, uintptr(wparam), 0)
|
||||
}
|
||||
|
||||
// SetIcon sets icon on the button. Recommended icons are 32x32 with 32bit color depth.
|
||||
func (bt *Button) SetIcon(ico *Icon) {
|
||||
w32.SendMessage(bt.hwnd, w32.BM_SETIMAGE, w32.IMAGE_ICON, uintptr(ico.handle))
|
||||
}
|
||||
|
||||
func (bt *Button) SetResIcon(iconID uint16) {
|
||||
if ico, err := NewIconFromResource(GetAppInstance(), iconID); err == nil {
|
||||
bt.SetIcon(ico)
|
||||
return
|
||||
}
|
||||
panic(fmt.Sprintf("missing icon with icon ID: %d", iconID))
|
||||
}
|
||||
|
||||
type PushButton struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewPushButton(parent Controller) *PushButton {
|
||||
pb := new(PushButton)
|
||||
|
||||
pb.InitControl("BUTTON", parent, 0, w32.BS_PUSHBUTTON|w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD)
|
||||
RegMsgHandler(pb)
|
||||
|
||||
pb.SetFont(DefaultFont)
|
||||
pb.SetText("Button")
|
||||
pb.SetSize(100, 22)
|
||||
|
||||
return pb
|
||||
}
|
||||
|
||||
// SetDefault is used for dialogs to set default button.
|
||||
func (pb *PushButton) SetDefault() {
|
||||
pb.SetAndClearStyleBits(w32.BS_DEFPUSHBUTTON, w32.BS_PUSHBUTTON)
|
||||
}
|
||||
|
||||
// IconButton does not display text, requires SetResIcon call.
|
||||
type IconButton struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewIconButton(parent Controller) *IconButton {
|
||||
pb := new(IconButton)
|
||||
|
||||
pb.InitControl("BUTTON", parent, 0, w32.BS_ICON|w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD)
|
||||
RegMsgHandler(pb)
|
||||
|
||||
pb.SetFont(DefaultFont)
|
||||
// even if text would be set it would not be displayed
|
||||
pb.SetText("")
|
||||
pb.SetSize(100, 22)
|
||||
|
||||
return pb
|
||||
}
|
||||
|
||||
type CheckBox struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewCheckBox(parent Controller) *CheckBox {
|
||||
cb := new(CheckBox)
|
||||
|
||||
cb.InitControl("BUTTON", parent, 0, w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD|w32.BS_AUTOCHECKBOX)
|
||||
RegMsgHandler(cb)
|
||||
|
||||
cb.SetFont(DefaultFont)
|
||||
cb.SetText("CheckBox")
|
||||
cb.SetSize(100, 22)
|
||||
|
||||
return cb
|
||||
}
|
||||
|
||||
type RadioButton struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewRadioButton(parent Controller) *RadioButton {
|
||||
rb := new(RadioButton)
|
||||
|
||||
rb.InitControl("BUTTON", parent, 0, w32.WS_TABSTOP|w32.WS_VISIBLE|w32.WS_CHILD|w32.BS_AUTORADIOBUTTON)
|
||||
RegMsgHandler(rb)
|
||||
|
||||
rb.SetFont(DefaultFont)
|
||||
rb.SetText("RadioButton")
|
||||
rb.SetSize(100, 22)
|
||||
|
||||
return rb
|
||||
}
|
||||
|
||||
type GroupBox struct {
|
||||
Button
|
||||
}
|
||||
|
||||
func NewGroupBox(parent Controller) *GroupBox {
|
||||
gb := new(GroupBox)
|
||||
|
||||
gb.InitControl("BUTTON", parent, 0, w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_GROUP|w32.BS_GROUPBOX)
|
||||
RegMsgHandler(gb)
|
||||
|
||||
gb.SetFont(DefaultFont)
|
||||
gb.SetText("GroupBox")
|
||||
gb.SetSize(100, 100)
|
||||
|
||||
return gb
|
||||
}
|
||||
159
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/canvas.go
generated
vendored
Normal file
159
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type Canvas struct {
|
||||
hwnd w32.HWND
|
||||
hdc w32.HDC
|
||||
doNotDispose bool
|
||||
}
|
||||
|
||||
var nullBrush = NewNullBrush()
|
||||
|
||||
func NewCanvasFromHwnd(hwnd w32.HWND) *Canvas {
|
||||
hdc := w32.GetDC(hwnd)
|
||||
if hdc == 0 {
|
||||
panic(fmt.Sprintf("Create canvas from %v failed.", hwnd))
|
||||
}
|
||||
|
||||
return &Canvas{hwnd: hwnd, hdc: hdc, doNotDispose: false}
|
||||
}
|
||||
|
||||
func NewCanvasFromHDC(hdc w32.HDC) *Canvas {
|
||||
if hdc == 0 {
|
||||
panic("Cannot create canvas from invalid HDC.")
|
||||
}
|
||||
|
||||
return &Canvas{hdc: hdc, doNotDispose: true}
|
||||
}
|
||||
|
||||
func (ca *Canvas) Dispose() {
|
||||
if !ca.doNotDispose && ca.hdc != 0 {
|
||||
if ca.hwnd == 0 {
|
||||
w32.DeleteDC(ca.hdc)
|
||||
} else {
|
||||
w32.ReleaseDC(ca.hwnd, ca.hdc)
|
||||
}
|
||||
|
||||
ca.hdc = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawBitmap(bmp *Bitmap, x, y int) {
|
||||
cdc := w32.CreateCompatibleDC(0)
|
||||
defer w32.DeleteDC(cdc)
|
||||
|
||||
hbmpOld := w32.SelectObject(cdc, w32.HGDIOBJ(bmp.GetHBITMAP()))
|
||||
defer w32.SelectObject(cdc, w32.HGDIOBJ(hbmpOld))
|
||||
|
||||
w, h := bmp.Size()
|
||||
|
||||
w32.BitBlt(ca.hdc, x, y, w, h, cdc, 0, 0, w32.SRCCOPY)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawStretchedBitmap(bmp *Bitmap, rect *Rect) {
|
||||
cdc := w32.CreateCompatibleDC(0)
|
||||
defer w32.DeleteDC(cdc)
|
||||
|
||||
hbmpOld := w32.SelectObject(cdc, w32.HGDIOBJ(bmp.GetHBITMAP()))
|
||||
defer w32.SelectObject(cdc, w32.HGDIOBJ(hbmpOld))
|
||||
|
||||
w, h := bmp.Size()
|
||||
|
||||
rc := rect.GetW32Rect()
|
||||
w32.StretchBlt(ca.hdc, int(rc.Left), int(rc.Top), int(rc.Right), int(rc.Bottom), cdc, 0, 0, w, h, w32.SRCCOPY)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawIcon(ico *Icon, x, y int) bool {
|
||||
return w32.DrawIcon(ca.hdc, x, y, ico.Handle())
|
||||
}
|
||||
|
||||
// DrawFillRect draw and fill rectangle with color.
|
||||
func (ca *Canvas) DrawFillRect(rect *Rect, pen *Pen, brush *Brush) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(brush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Rectangle(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawRect(rect *Rect, pen *Pen) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
// nullBrush is used to make interior of the rect transparent
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(nullBrush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Rectangle(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
func (ca *Canvas) FillRect(rect *Rect, brush *Brush) {
|
||||
w32.FillRect(ca.hdc, rect.GetW32Rect(), brush.GetHBRUSH())
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawEllipse(rect *Rect, pen *Pen) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
// nullBrush is used to make interior of the rect transparent
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(nullBrush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Ellipse(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
// DrawFillEllipse draw and fill ellipse with color.
|
||||
func (ca *Canvas) DrawFillEllipse(rect *Rect, pen *Pen, brush *Brush) {
|
||||
w32Rect := rect.GetW32Rect()
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
previousBrush := w32.SelectObject(ca.hdc, w32.HGDIOBJ(brush.GetHBRUSH()))
|
||||
defer w32.SelectObject(ca.hdc, previousBrush)
|
||||
|
||||
w32.Ellipse(ca.hdc, w32Rect.Left, w32Rect.Top, w32Rect.Right, w32Rect.Bottom)
|
||||
}
|
||||
|
||||
func (ca *Canvas) DrawLine(x, y, x2, y2 int, pen *Pen) {
|
||||
w32.MoveToEx(ca.hdc, x, y, nil)
|
||||
|
||||
previousPen := w32.SelectObject(ca.hdc, w32.HGDIOBJ(pen.GetHPEN()))
|
||||
defer w32.SelectObject(ca.hdc, previousPen)
|
||||
|
||||
w32.LineTo(ca.hdc, int32(x2), int32(y2))
|
||||
}
|
||||
|
||||
// Refer win32 DrawText document for uFormat.
|
||||
func (ca *Canvas) DrawText(text string, rect *Rect, format uint, font *Font, textColor Color) {
|
||||
previousFont := w32.SelectObject(ca.hdc, w32.HGDIOBJ(font.GetHFONT()))
|
||||
defer w32.SelectObject(ca.hdc, w32.HGDIOBJ(previousFont))
|
||||
|
||||
previousBkMode := w32.SetBkMode(ca.hdc, w32.TRANSPARENT)
|
||||
defer w32.SetBkMode(ca.hdc, previousBkMode)
|
||||
|
||||
previousTextColor := w32.SetTextColor(ca.hdc, w32.COLORREF(textColor))
|
||||
defer w32.SetTextColor(ca.hdc, previousTextColor)
|
||||
|
||||
w32.DrawText(ca.hdc, text, len(text), rect.GetW32Rect(), format)
|
||||
}
|
||||
26
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/color.go
generated
vendored
Normal file
26
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/color.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
* Copyright (C) 2010-2013 Allen Dang. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
type Color uint32
|
||||
|
||||
func RGB(r, g, b byte) Color {
|
||||
return Color(uint32(r) | uint32(g)<<8 | uint32(b)<<16)
|
||||
}
|
||||
|
||||
func (c Color) R() byte {
|
||||
return byte(c & 0xff)
|
||||
}
|
||||
|
||||
func (c Color) G() byte {
|
||||
return byte((c >> 8) & 0xff)
|
||||
}
|
||||
|
||||
func (c Color) B() byte {
|
||||
return byte((c >> 16) & 0xff)
|
||||
}
|
||||
70
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/combobox.go
generated
vendored
Normal file
70
vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/combobox.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winc
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
)
|
||||
|
||||
type ComboBox struct {
|
||||
ControlBase
|
||||
onSelectedChange EventManager
|
||||
}
|
||||
|
||||
func NewComboBox(parent Controller) *ComboBox {
|
||||
cb := new(ComboBox)
|
||||
|
||||
cb.InitControl("COMBOBOX", parent, 0, w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_TABSTOP|w32.WS_VSCROLL|w32.CBS_DROPDOWNLIST)
|
||||
RegMsgHandler(cb)
|
||||
|
||||
cb.SetFont(DefaultFont)
|
||||
cb.SetSize(200, 400)
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *ComboBox) DeleteAllItems() bool {
|
||||
return w32.SendMessage(cb.hwnd, w32.CB_RESETCONTENT, 0, 0) == w32.TRUE
|
||||
}
|
||||
|
||||
func (cb *ComboBox) InsertItem(index int, str string) bool {
|
||||
lp := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(str)))
|
||||
return w32.SendMessage(cb.hwnd, w32.CB_INSERTSTRING, uintptr(index), lp) != w32.CB_ERR
|
||||
}
|
||||
|
||||
func (cb *ComboBox) DeleteItem(index int) bool {
|
||||
return w32.SendMessage(cb.hwnd, w32.CB_DELETESTRING, uintptr(index), 0) != w32.CB_ERR
|
||||
}
|
||||
|
||||
func (cb *ComboBox) SelectedItem() int {
|
||||
return int(int32(w32.SendMessage(cb.hwnd, w32.CB_GETCURSEL, 0, 0)))
|
||||
}
|
||||
|
||||
func (cb *ComboBox) SetSelectedItem(value int) bool {
|
||||
return int(int32(w32.SendMessage(cb.hwnd, w32.CB_SETCURSEL, uintptr(value), 0))) == value
|
||||
}
|
||||
|
||||
func (cb *ComboBox) OnSelectedChange() *EventManager {
|
||||
return &cb.onSelectedChange
|
||||
}
|
||||
|
||||
// Message processor
|
||||
func (cb *ComboBox) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_COMMAND:
|
||||
code := w32.HIWORD(uint32(wparam))
|
||||
|
||||
switch code {
|
||||
case w32.CBN_SELCHANGE:
|
||||
cb.onSelectedChange.Fire(NewEvent(cb, nil))
|
||||
}
|
||||
}
|
||||
return w32.DefWindowProc(cb.hwnd, msg, wparam, lparam)
|
||||
//return cb.W32Control.WndProc(msg, wparam, lparam)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user